use crate::error::TimecodeError;
use crate::framerate::FrameRate;
pub fn parse_timecode(s: &str, rate: FrameRate) -> Result<(u8, u8, u8, u8), TimecodeError> {
let bytes = s.as_bytes();
if bytes.len() != 11 {
return Err(TimecodeError::InvalidFormat);
}
let h = parse_two_digits(bytes[0], bytes[1])?;
if bytes[2] != b':' {
return Err(TimecodeError::InvalidFormat);
}
let m = parse_two_digits(bytes[3], bytes[4])?;
if bytes[5] != b':' {
return Err(TimecodeError::InvalidFormat);
}
let s_val = parse_two_digits(bytes[6], bytes[7])?;
let sep = bytes[8];
if sep != b':' && sep != b';' {
return Err(TimecodeError::InvalidFormat);
}
if sep == b';' && !rate.is_drop_frame() {
return Err(TimecodeError::InvalidDropFrameRate);
}
let f = parse_two_digits(bytes[9], bytes[10])?;
validate_components(h, m, s_val, f, rate)?;
Ok((h, m, s_val, f))
}
pub fn validate_str(s: &str, rate: FrameRate) -> bool {
parse_timecode(s, rate).is_ok()
}
fn validate_components(h: u8, m: u8, s: u8, f: u8, rate: FrameRate) -> Result<(), TimecodeError> {
if h > 23 {
return Err(TimecodeError::InvalidHours(h));
}
if m > 59 {
return Err(TimecodeError::InvalidMinutes(m));
}
if s > 59 {
return Err(TimecodeError::InvalidSeconds(s));
}
let max_frames = rate.nominal() as u8;
if f >= max_frames {
return Err(TimecodeError::InvalidFrames(f, max_frames));
}
Ok(())
}
fn parse_two_digits(tens: u8, ones: u8) -> Result<u8, TimecodeError> {
if !tens.is_ascii_digit() || !ones.is_ascii_digit() {
return Err(TimecodeError::InvalidFormat);
}
Ok((tens - b'0') * 10 + (ones - b'0'))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn valid_timecode() {
assert_eq!(
parse_timecode("01:23:45:12", FrameRate::Fps24),
Ok((1, 23, 45, 12))
);
}
#[test]
fn drop_frame_separator() {
assert_eq!(
parse_timecode("01:23:45;12", FrameRate::Fps29_97Df),
Ok((1, 23, 45, 12))
);
}
#[test]
fn semicolon_on_non_drop_frame() {
assert!(parse_timecode("01:23:45;12", FrameRate::Fps24).is_err());
}
#[test]
fn too_short() {
assert!(parse_timecode("01:23:45", FrameRate::Fps24).is_err());
}
#[test]
fn invalid_hours() {
assert!(parse_timecode("25:00:00:00", FrameRate::Fps24).is_err());
}
#[test]
fn invalid_frames_for_rate() {
assert!(parse_timecode("00:00:00:24", FrameRate::Fps24).is_err());
assert!(parse_timecode("00:00:00:30", FrameRate::Fps30).is_err());
assert!(parse_timecode("00:00:00:23", FrameRate::Fps24).is_ok());
assert!(parse_timecode("00:00:00:29", FrameRate::Fps30).is_ok());
}
#[test]
fn non_numeric() {
assert!(parse_timecode("ab:cd:ef:gh", FrameRate::Fps24).is_err());
}
}