diff options
Diffstat (limited to 'yt/src/select/selection_file/duration.rs')
-rw-r--r-- | yt/src/select/selection_file/duration.rs | 111 |
1 files changed, 111 insertions, 0 deletions
diff --git a/yt/src/select/selection_file/duration.rs b/yt/src/select/selection_file/duration.rs new file mode 100644 index 0000000..a38981c --- /dev/null +++ b/yt/src/select/selection_file/duration.rs @@ -0,0 +1,111 @@ +// yt - A fully featured command line YouTube client +// +// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de> +// SPDX-License-Identifier: GPL-3.0-or-later +// +// This file is part of Yt. +// +// You should have received a copy of the License along with this program. +// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>. + +use std::str::FromStr; + +use anyhow::{Context, Result}; + +#[derive(Copy, Clone, Debug)] +pub struct Duration { + time: u32, +} + +impl FromStr for Duration { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + fn parse_num(str: &str, suffix: char) -> Result<u32> { + str.strip_suffix(suffix) + .with_context(|| { + format!("Failed to strip suffix '{}' of number: '{}'", suffix, str) + })? + .parse::<u32>() + .with_context(|| format!("Failed to parse '{}'", suffix)) + } + + if s == "[No Duration]" { + return Ok(Self { time: 0 }); + } + + let buf: Vec<_> = s.split(' ').collect(); + + let hours; + let minutes; + let seconds; + + assert_eq!(buf.len(), 2, "Other lengths should not happen"); + + if buf[0].ends_with('h') { + hours = parse_num(buf[0], 'h')?; + minutes = parse_num(buf[1], 'm')?; + seconds = 0; + } else if buf[0].ends_with('m') { + hours = 0; + minutes = parse_num(buf[0], 'm')?; + seconds = parse_num(buf[1], 's')?; + } else { + unreachable!( + "The first part always ends with 'h' or 'm', but was: {:#?}", + buf + ) + } + + Ok(Self { + time: (hours * 60 * 60) + (minutes * 60) + seconds, + }) + } +} + +impl From<Option<f64>> for Duration { + fn from(value: Option<f64>) -> Self { + Self { + time: value.unwrap_or(0.0).ceil() as u32, + } + } +} + +impl std::fmt::Display for Duration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + const SECOND: u32 = 1; + const MINUTE: u32 = 60 * SECOND; + const HOUR: u32 = 60 * MINUTE; + + let base_hour = self.time - (self.time % HOUR); + let base_min = (self.time % HOUR) - ((self.time % HOUR) % MINUTE); + let base_sec = (self.time % HOUR) % MINUTE; + + let h = base_hour / HOUR; + let m = base_min / MINUTE; + let s = base_sec / SECOND; + + if self.time == 0 { + write!(f, "[No Duration]") + } else if h > 0 { + write!(f, "{h}h {m}m") + } else { + write!(f, "{m}m {s}s") + } + } +} +#[cfg(test)] +mod test { + use super::Duration; + + #[test] + fn test_display_duration_1h() { + let dur = Duration { time: 60 * 60 }; + assert_eq!("1h 0m".to_owned(), dur.to_string()); + } + #[test] + fn test_display_duration_30min() { + let dur = Duration { time: 60 * 30 }; + assert_eq!("30m 0s".to_owned(), dur.to_string()); + } +} |