1use crate::error::TimecodeError;
2use crate::framerate::FrameRate;
3
4pub fn parse_timecode(s: &str, rate: FrameRate) -> Result<(u8, u8, u8, u8), TimecodeError> {
5 let bytes = s.as_bytes();
6
7 if bytes.len() != 11 {
8 return Err(TimecodeError::InvalidFormat);
9 }
10
11 let h = parse_two_digits(bytes[0], bytes[1])?;
12 if bytes[2] != b':' {
13 return Err(TimecodeError::InvalidFormat);
14 }
15 let m = parse_two_digits(bytes[3], bytes[4])?;
16 if bytes[5] != b':' {
17 return Err(TimecodeError::InvalidFormat);
18 }
19 let s_val = parse_two_digits(bytes[6], bytes[7])?;
20
21 let sep = bytes[8];
22 if sep != b':' && sep != b';' {
23 return Err(TimecodeError::InvalidFormat);
24 }
25
26 if sep == b';' && !rate.is_drop_frame() {
27 return Err(TimecodeError::InvalidDropFrameRate);
28 }
29
30 let f = parse_two_digits(bytes[9], bytes[10])?;
31
32 validate_components(h, m, s_val, f, rate)?;
33
34 Ok((h, m, s_val, f))
35}
36
37pub fn validate_str(s: &str, rate: FrameRate) -> bool {
38 parse_timecode(s, rate).is_ok()
39}
40
41fn validate_components(h: u8, m: u8, s: u8, f: u8, rate: FrameRate) -> Result<(), TimecodeError> {
42 if h > 23 {
43 return Err(TimecodeError::InvalidHours(h));
44 }
45 if m > 59 {
46 return Err(TimecodeError::InvalidMinutes(m));
47 }
48 if s > 59 {
49 return Err(TimecodeError::InvalidSeconds(s));
50 }
51 let max_frames = rate.nominal() as u8;
52 if f >= max_frames {
53 return Err(TimecodeError::InvalidFrames(f, max_frames));
54 }
55 Ok(())
56}
57
58fn parse_two_digits(tens: u8, ones: u8) -> Result<u8, TimecodeError> {
59 if !tens.is_ascii_digit() || !ones.is_ascii_digit() {
60 return Err(TimecodeError::InvalidFormat);
61 }
62 Ok((tens - b'0') * 10 + (ones - b'0'))
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 #[test]
70 fn valid_timecode() {
71 assert_eq!(
72 parse_timecode("01:23:45:12", FrameRate::Fps24),
73 Ok((1, 23, 45, 12))
74 );
75 }
76
77 #[test]
78 fn drop_frame_separator() {
79 assert_eq!(
80 parse_timecode("01:23:45;12", FrameRate::Fps29_97Df),
81 Ok((1, 23, 45, 12))
82 );
83 }
84
85 #[test]
86 fn semicolon_on_non_drop_frame() {
87 assert!(parse_timecode("01:23:45;12", FrameRate::Fps24).is_err());
88 }
89
90 #[test]
91 fn too_short() {
92 assert!(parse_timecode("01:23:45", FrameRate::Fps24).is_err());
93 }
94
95 #[test]
96 fn invalid_hours() {
97 assert!(parse_timecode("25:00:00:00", FrameRate::Fps24).is_err());
98 }
99
100 #[test]
101 fn invalid_frames_for_rate() {
102 assert!(parse_timecode("00:00:00:24", FrameRate::Fps24).is_err());
103 assert!(parse_timecode("00:00:00:30", FrameRate::Fps30).is_err());
104 assert!(parse_timecode("00:00:00:23", FrameRate::Fps24).is_ok());
105 assert!(parse_timecode("00:00:00:29", FrameRate::Fps30).is_ok());
106 }
107
108 #[test]
109 fn non_numeric() {
110 assert!(parse_timecode("ab:cd:ef:gh", FrameRate::Fps24).is_err());
111 }
112}