Skip to main content

talw_timecode/
parse.rs

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}