#![allow(dead_code)]
#![allow(clippy::cast_precision_loss)]
use crate::{FrameRate, Timecode, TimecodeError};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TimecodegGap {
pub before: Timecode,
pub after: Timecode,
pub gap_frames: i64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ContinuityResult {
Continuous,
Gap(u64),
Overlap(u64),
Repeat,
}
pub fn check_continuity(prev: &Timecode, next: &Timecode) -> ContinuityResult {
let prev_f = prev.to_frames();
let next_f = next.to_frames();
match next_f.cmp(&(prev_f + 1)) {
std::cmp::Ordering::Equal => ContinuityResult::Continuous,
std::cmp::Ordering::Greater => ContinuityResult::Gap(next_f - prev_f - 1),
std::cmp::Ordering::Less => {
if next_f == prev_f {
ContinuityResult::Repeat
} else {
ContinuityResult::Overlap(prev_f + 1 - next_f)
}
}
}
}
#[derive(Debug, Clone)]
pub struct ContinuityMonitor {
frame_rate: FrameRate,
last_tc: Option<Timecode>,
gaps: Vec<TimecodegGap>,
gap_count: u32,
overlap_count: u32,
repeat_count: u32,
frame_count: u64,
}
impl ContinuityMonitor {
pub fn new(frame_rate: FrameRate) -> Self {
Self {
frame_rate,
last_tc: None,
gaps: Vec::new(),
gap_count: 0,
overlap_count: 0,
repeat_count: 0,
frame_count: 0,
}
}
pub fn feed(&mut self, tc: Timecode) -> ContinuityResult {
self.frame_count += 1;
let result = if let Some(ref last) = self.last_tc {
let r = check_continuity(last, &tc);
match &r {
ContinuityResult::Gap(n) => {
self.gap_count += 1;
self.gaps.push(TimecodegGap {
before: *last,
after: tc,
gap_frames: *n as i64,
});
}
ContinuityResult::Overlap(n) => {
self.overlap_count += 1;
self.gaps.push(TimecodegGap {
before: *last,
after: tc,
gap_frames: -(*n as i64),
});
}
ContinuityResult::Repeat => {
self.repeat_count += 1;
}
ContinuityResult::Continuous => {}
}
r
} else {
ContinuityResult::Continuous
};
self.last_tc = Some(tc);
result
}
pub fn gap_count(&self) -> u32 {
self.gap_count
}
pub fn overlap_count(&self) -> u32 {
self.overlap_count
}
pub fn repeat_count(&self) -> u32 {
self.repeat_count
}
pub fn frame_count(&self) -> u64 {
self.frame_count
}
pub fn gaps(&self) -> &[TimecodegGap] {
&self.gaps
}
pub fn reset(&mut self) {
self.last_tc = None;
self.gaps.clear();
self.gap_count = 0;
self.overlap_count = 0;
self.repeat_count = 0;
self.frame_count = 0;
}
pub fn last_timecode(&self) -> Option<&Timecode> {
self.last_tc.as_ref()
}
pub fn report(&self) -> String {
format!(
"Frames: {}, Gaps: {}, Overlaps: {}, Repeats: {}",
self.frame_count, self.gap_count, self.overlap_count, self.repeat_count
)
}
}
pub fn expected_frame_count(start: &Timecode, end: &Timecode) -> u64 {
let s = start.to_frames();
let e = end.to_frames();
e.saturating_sub(s)
}
pub fn find_gaps(timecodes: &[Timecode]) -> Vec<TimecodegGap> {
let mut gaps = Vec::new();
for window in timecodes.windows(2) {
let result = check_continuity(&window[0], &window[1]);
match result {
ContinuityResult::Gap(n) => {
gaps.push(TimecodegGap {
before: window[0],
after: window[1],
gap_frames: n as i64,
});
}
ContinuityResult::Overlap(n) => {
gaps.push(TimecodegGap {
before: window[0],
after: window[1],
gap_frames: -(n as i64),
});
}
_ => {}
}
}
gaps
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TimecodeRange {
pub start: Timecode,
pub end: Timecode,
}
impl TimecodeRange {
pub fn new(start: Timecode, end: Timecode) -> Result<Self, TimecodeError> {
if end.to_frames() < start.to_frames() {
return Err(TimecodeError::InvalidConfiguration);
}
Ok(Self { start, end })
}
pub fn duration_frames(&self) -> u64 {
self.end.to_frames().saturating_sub(self.start.to_frames()) + 1
}
pub fn contains(&self, tc: &Timecode) -> bool {
let f = tc.to_frames();
f >= self.start.to_frames() && f <= self.end.to_frames()
}
pub fn overlaps(&self, other: &TimecodeRange) -> bool {
self.start.to_frames() <= other.end.to_frames()
&& other.start.to_frames() <= self.end.to_frames()
}
}
pub fn split_into_segments(timecodes: &[Timecode]) -> Vec<Vec<Timecode>> {
if timecodes.is_empty() {
return Vec::new();
}
let mut segments = Vec::new();
let mut current = vec![timecodes[0]];
for window in timecodes.windows(2) {
match check_continuity(&window[0], &window[1]) {
ContinuityResult::Continuous => {
current.push(window[1]);
}
_ => {
segments.push(current);
current = vec![window[1]];
}
}
}
segments.push(current);
segments
}
#[cfg(test)]
mod tests {
use super::*;
fn tc(h: u8, m: u8, s: u8, f: u8) -> Timecode {
Timecode::new(h, m, s, f, FrameRate::Fps25).expect("valid timecode")
}
#[test]
fn test_check_continuity_continuous() {
let a = tc(0, 0, 0, 0);
let b = tc(0, 0, 0, 1);
assert_eq!(check_continuity(&a, &b), ContinuityResult::Continuous);
}
#[test]
fn test_check_continuity_gap() {
let a = tc(0, 0, 0, 0);
let b = tc(0, 0, 0, 5);
assert_eq!(check_continuity(&a, &b), ContinuityResult::Gap(4));
}
#[test]
fn test_check_continuity_overlap() {
let a = tc(0, 0, 0, 5);
let b = tc(0, 0, 0, 3);
assert_eq!(check_continuity(&a, &b), ContinuityResult::Overlap(3));
}
#[test]
fn test_check_continuity_repeat() {
let a = tc(0, 0, 0, 5);
let b = tc(0, 0, 0, 5);
assert_eq!(check_continuity(&a, &b), ContinuityResult::Repeat);
}
#[test]
fn test_monitor_continuous() {
let mut mon = ContinuityMonitor::new(FrameRate::Fps25);
for f in 0u8..10 {
let t = tc(0, 0, 0, f);
mon.feed(t);
}
assert_eq!(mon.gap_count(), 0);
assert_eq!(mon.frame_count(), 10);
}
#[test]
fn test_monitor_gap_detection() {
let mut mon = ContinuityMonitor::new(FrameRate::Fps25);
mon.feed(tc(0, 0, 0, 0));
mon.feed(tc(0, 0, 0, 5)); assert_eq!(mon.gap_count(), 1);
assert_eq!(mon.gaps()[0].gap_frames, 4);
}
#[test]
fn test_monitor_reset() {
let mut mon = ContinuityMonitor::new(FrameRate::Fps25);
mon.feed(tc(0, 0, 0, 0));
mon.feed(tc(0, 0, 0, 5));
mon.reset();
assert_eq!(mon.gap_count(), 0);
assert_eq!(mon.frame_count(), 0);
assert!(mon.last_timecode().is_none());
}
#[test]
fn test_monitor_report() {
let mut mon = ContinuityMonitor::new(FrameRate::Fps25);
mon.feed(tc(0, 0, 0, 0));
let report = mon.report();
assert!(report.contains("Frames: 1"));
}
#[test]
fn test_find_gaps() {
let tcs = vec![
tc(0, 0, 0, 0),
tc(0, 0, 0, 1),
tc(0, 0, 0, 5),
tc(0, 0, 0, 6),
];
let gaps = find_gaps(&tcs);
assert_eq!(gaps.len(), 1);
assert_eq!(gaps[0].gap_frames, 3);
}
#[test]
fn test_timecode_range_contains() {
let start = tc(0, 0, 0, 0);
let end = tc(0, 0, 1, 0);
let range = TimecodeRange::new(start, end).expect("valid timecode range");
assert!(range.contains(&tc(0, 0, 0, 10)));
assert!(!range.contains(&tc(0, 0, 2, 0)));
}
#[test]
fn test_timecode_range_duration() {
let start = tc(0, 0, 0, 0);
let end = tc(0, 0, 0, 24);
let range = TimecodeRange::new(start, end).expect("valid timecode range");
assert_eq!(range.duration_frames(), 25);
}
#[test]
fn test_timecode_range_overlaps() {
let r1 = TimecodeRange::new(tc(0, 0, 0, 0), tc(0, 0, 0, 10)).expect("valid timecode range");
let r2 = TimecodeRange::new(tc(0, 0, 0, 5), tc(0, 0, 0, 20)).expect("valid timecode range");
let r3 = TimecodeRange::new(tc(0, 0, 1, 0), tc(0, 0, 1, 10)).expect("valid timecode range");
assert!(r1.overlaps(&r2));
assert!(!r1.overlaps(&r3));
}
#[test]
fn test_split_into_segments() {
let tcs = vec![
tc(0, 0, 0, 0),
tc(0, 0, 0, 1),
tc(0, 0, 0, 2),
tc(0, 0, 1, 0),
tc(0, 0, 1, 1),
];
let segments = split_into_segments(&tcs);
assert_eq!(segments.len(), 2);
assert_eq!(segments[0].len(), 3);
assert_eq!(segments[1].len(), 2);
}
#[test]
fn test_expected_frame_count() {
let start = tc(0, 0, 0, 0);
let end = tc(0, 0, 1, 0);
assert_eq!(expected_frame_count(&start, &end), 25);
}
}