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 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 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}