Skip to main content

batchcensor/
pos.rs

1use std::fmt;
2
3#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
4pub struct Pos {
5    pub hours: u32,
6    pub minutes: u32,
7    pub seconds: u32,
8    pub milliseconds: u32,
9}
10
11impl Pos {
12    /// Convert into samples given a sample rate.
13    pub fn as_samples(&self, sample_rate: u32) -> Option<u32> {
14        let samples = 0u32
15            .checked_add(self.hours.checked_mul(3600)?.checked_mul(sample_rate)?)?
16            .checked_add(self.minutes.checked_mul(60)?.checked_mul(sample_rate)?)?
17            .checked_add(self.seconds.checked_mul(sample_rate)?)?
18            .checked_add(
19                self.milliseconds
20                    .checked_mul(sample_rate.checked_div(1000)?)?,
21            )?;
22
23        Some(samples)
24    }
25
26    /// Deserialize stringa as a position.
27    pub fn parse(s: &str) -> Option<Pos> {
28        let mut main = s.split(':');
29        let last = main.next_back()?;
30        let mut last = last.split(".");
31
32        let seconds = match last.next()?.trim() {
33            "" => 0,
34            seconds => str::parse::<u32>(seconds).ok()?,
35        };
36
37        let milliseconds = str::parse::<u32>(last.next()?).ok()?;
38
39        let minutes = main
40            .next_back()
41            .and_then(|s| str::parse::<u32>(s).ok())
42            .unwrap_or_default();
43
44        let hours = main
45            .next_back()
46            .and_then(|s| str::parse::<u32>(s).ok())
47            .unwrap_or_default();
48
49        Some(Pos {
50            hours,
51            minutes,
52            seconds,
53            milliseconds,
54        })
55    }
56}
57
58impl fmt::Display for Pos {
59    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
60        if self.hours > 0 {
61            write!(fmt, "{:02}:", self.hours)?;
62        }
63
64        if self.minutes > 0 {
65            write!(fmt, "{:02}:", self.hours)?;
66        }
67
68        if self.seconds > 0 {
69            write!(fmt, "{:02}", self.seconds)?;
70        }
71
72        write!(fmt, ".{:03}", self.milliseconds)?;
73        Ok(())
74    }
75}
76
77impl<'de> serde::Deserialize<'de> for Pos {
78    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
79    where
80        D: serde::Deserializer<'de>,
81    {
82        let s: String = String::deserialize(deserializer)?;
83        Pos::parse(&s).ok_or_else(|| <D::Error as serde::de::Error>::custom("bad position"))
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::Pos;
90
91    #[test]
92    pub fn test() {
93        assert_eq!(
94            Pos {
95                hours: 0,
96                minutes: 0,
97                seconds: 0,
98                milliseconds: 123,
99            },
100            Pos::parse(".123").expect("bad position")
101        );
102
103        assert_eq!(
104            Pos {
105                hours: 0,
106                minutes: 0,
107                seconds: 42,
108                milliseconds: 123,
109            },
110            Pos::parse("42.123").expect("bad position")
111        );
112
113        assert_eq!(
114            Pos {
115                hours: 0,
116                minutes: 21,
117                seconds: 42,
118                milliseconds: 123,
119            },
120            Pos::parse("21:42.123").expect("bad position")
121        );
122
123        assert_eq!(
124            Pos {
125                hours: 12,
126                minutes: 21,
127                seconds: 42,
128                milliseconds: 123,
129            },
130            Pos::parse("12:21:42.123").expect("bad position")
131        );
132    }
133}