pub struct SmpteTimeCode {
pub hours: u8,
pub minutes: u8,
pub seconds: u8,
pub frames: u8,
pub drop_frame: bool,
pub fps: u8,
}
impl SmpteTimeCode {
pub fn parse(value: &str, fps: u8) -> Option<Self> {
let df = value.contains(';');
if df {
let (time_part, frames_str) = value.split_once(';')?;
let hms: Vec<&str> = time_part.split(':').collect();
if hms.len() != 3 {
return None;
}
Some(SmpteTimeCode {
hours: hms[0].parse().ok()?,
minutes: hms[1].parse().ok()?,
seconds: hms[2].parse().ok()?,
frames: frames_str.parse().ok()?,
drop_frame: df,
fps,
})
} else {
let pos = value.rfind(':')?;
let frames_str = &value[pos + 1..];
let hms: Vec<&str> = value[..pos].split(':').collect();
if hms.len() != 3 {
return None;
}
Some(SmpteTimeCode {
hours: hms[0].parse().ok()?,
minutes: hms[1].parse().ok()?,
seconds: hms[2].parse().ok()?,
frames: frames_str.parse().ok()?,
drop_frame: df,
fps,
})
}
}
pub fn to_milliseconds(&self) -> u64 {
let total_frames =
(self.hours as u64 * 3600 + self.minutes as u64 * 60 + self.seconds as u64)
* self.fps as u64
+ self.frames as u64;
total_frames * 1000 / self.fps as u64
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ndf() {
let tc = SmpteTimeCode::parse("00:01:30:00", 30).unwrap();
assert!(!tc.drop_frame);
}
#[test]
fn test_df() {
let tc = SmpteTimeCode::parse("01:00:00;00", 30).unwrap();
assert!(tc.drop_frame);
}
#[test]
fn test_ndf4() {
let tc = SmpteTimeCode::parse("01:02:03:04", 30).unwrap();
assert_eq!(tc.frames, 4);
}
}