#![allow(dead_code)]
use crate::error::{EdlError, EdlResult};
use crate::event::EdlEvent;
use crate::timecode::{EdlFrameRate, EdlTimecode};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TimeRange {
pub tc_in: EdlTimecode,
pub tc_out: EdlTimecode,
}
impl TimeRange {
pub fn new(tc_in: EdlTimecode, tc_out: EdlTimecode) -> EdlResult<Self> {
if tc_in >= tc_out {
return Err(EdlError::validation("tc_in must be before tc_out"));
}
Ok(Self { tc_in, tc_out })
}
#[must_use]
pub fn duration_frames(&self) -> u64 {
self.tc_out.to_frames() - self.tc_in.to_frames()
}
#[must_use]
#[allow(clippy::cast_precision_loss)]
pub fn duration_seconds(&self, fps: EdlFrameRate) -> f64 {
self.duration_frames() as f64 / fps.as_float()
}
#[must_use]
pub fn contains(&self, tc: &EdlTimecode) -> bool {
*tc >= self.tc_in && *tc < self.tc_out
}
#[must_use]
pub fn overlaps(&self, other: &Self) -> bool {
!(self.tc_out <= other.tc_in || self.tc_in >= other.tc_out)
}
#[must_use]
pub fn intersection(&self, other: &Self) -> Option<Self> {
if !self.overlaps(other) {
return None;
}
let start = if self.tc_in >= other.tc_in {
self.tc_in
} else {
other.tc_in
};
let end = if self.tc_out <= other.tc_out {
self.tc_out
} else {
other.tc_out
};
Some(Self {
tc_in: start,
tc_out: end,
})
}
#[must_use]
pub fn gap_frames(&self, other: &Self) -> u64 {
if self.overlaps(other) {
return 0;
}
if self.tc_out <= other.tc_in {
other.tc_in.to_frames() - self.tc_out.to_frames()
} else {
self.tc_in.to_frames() - other.tc_out.to_frames()
}
}
}
#[derive(Debug, Clone)]
pub struct DurationSummary {
pub total_frames: u64,
pub min_frames: u64,
pub max_frames: u64,
pub avg_frames: f64,
pub event_count: usize,
}
#[allow(clippy::cast_precision_loss)]
pub fn compute_duration_summary(events: &[EdlEvent]) -> EdlResult<DurationSummary> {
if events.is_empty() {
return Err(EdlError::validation("No events to summarize"));
}
let mut total: u64 = 0;
let mut min = u64::MAX;
let mut max: u64 = 0;
for ev in events {
let d = ev.duration_frames();
total += d;
if d < min {
min = d;
}
if d > max {
max = d;
}
}
let avg = total as f64 / events.len() as f64;
Ok(DurationSummary {
total_frames: total,
min_frames: min,
max_frames: max,
avg_frames: avg,
event_count: events.len(),
})
}
pub fn offset_events(
events: &[EdlEvent],
delta_frames: i64,
fps: EdlFrameRate,
) -> EdlResult<Vec<EdlEvent>> {
let mut result = Vec::with_capacity(events.len());
for ev in events {
let mut cloned = ev.clone();
cloned.source_in = offset_tc(ev.source_in, delta_frames, fps)?;
cloned.source_out = offset_tc(ev.source_out, delta_frames, fps)?;
cloned.record_in = offset_tc(ev.record_in, delta_frames, fps)?;
cloned.record_out = offset_tc(ev.record_out, delta_frames, fps)?;
result.push(cloned);
}
Ok(result)
}
fn offset_tc(tc: EdlTimecode, delta: i64, fps: EdlFrameRate) -> EdlResult<EdlTimecode> {
let frames = tc.to_frames() as i64 + delta;
if frames < 0 {
return Err(EdlError::TimecodeOutOfRange(
"Offset would make timecode negative".to_string(),
));
}
EdlTimecode::from_frames(frames as u64, fps)
}
pub fn programme_duration_frames(events: &[EdlEvent]) -> EdlResult<u64> {
if events.is_empty() {
return Err(EdlError::validation("No events provided"));
}
let first_in = events
.iter()
.map(|e| e.record_in.to_frames())
.min()
.unwrap_or(0);
let last_out = events
.iter()
.map(|e| e.record_out.to_frames())
.max()
.unwrap_or(0);
Ok(last_out - first_in)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event::{EditType, TrackType};
fn make_tc(h: u8, m: u8, s: u8, f: u8) -> EdlTimecode {
EdlTimecode::new(h, m, s, f, EdlFrameRate::Fps25).expect("failed to create")
}
fn make_event(num: u32, ri_s: u8, ro_s: u8) -> EdlEvent {
let si = make_tc(1, 0, 0, 0);
let so = make_tc(1, 0, ro_s - ri_s, 0);
let ri = make_tc(1, 0, ri_s, 0);
let ro = make_tc(1, 0, ro_s, 0);
EdlEvent::new(
num,
"A001".to_string(),
TrackType::Video,
EditType::Cut,
si,
so,
ri,
ro,
)
}
#[test]
fn test_time_range_creation() {
let r = TimeRange::new(make_tc(0, 0, 0, 0), make_tc(0, 0, 5, 0));
assert!(r.is_ok());
}
#[test]
fn test_time_range_invalid() {
let tc = make_tc(0, 0, 5, 0);
let r = TimeRange::new(tc, tc);
assert!(r.is_err());
}
#[test]
fn test_time_range_duration_frames() {
let r = TimeRange::new(make_tc(0, 0, 0, 0), make_tc(0, 0, 2, 0)).expect("failed to create");
assert_eq!(r.duration_frames(), 50); }
#[test]
fn test_time_range_duration_seconds() {
let r = TimeRange::new(make_tc(0, 0, 0, 0), make_tc(0, 0, 4, 0)).expect("failed to create");
let secs = r.duration_seconds(EdlFrameRate::Fps25);
assert!((secs - 4.0).abs() < 0.01);
}
#[test]
fn test_time_range_contains() {
let r =
TimeRange::new(make_tc(0, 0, 0, 0), make_tc(0, 0, 10, 0)).expect("failed to create");
assert!(r.contains(&make_tc(0, 0, 5, 0)));
assert!(!r.contains(&make_tc(0, 0, 10, 0)));
}
#[test]
fn test_time_range_overlaps() {
let a =
TimeRange::new(make_tc(0, 0, 0, 0), make_tc(0, 0, 10, 0)).expect("failed to create");
let b =
TimeRange::new(make_tc(0, 0, 5, 0), make_tc(0, 0, 15, 0)).expect("failed to create");
assert!(a.overlaps(&b));
}
#[test]
fn test_time_range_no_overlap() {
let a = TimeRange::new(make_tc(0, 0, 0, 0), make_tc(0, 0, 5, 0)).expect("failed to create");
let b =
TimeRange::new(make_tc(0, 0, 10, 0), make_tc(0, 0, 15, 0)).expect("failed to create");
assert!(!a.overlaps(&b));
}
#[test]
fn test_time_range_intersection() {
let a =
TimeRange::new(make_tc(0, 0, 0, 0), make_tc(0, 0, 10, 0)).expect("failed to create");
let b =
TimeRange::new(make_tc(0, 0, 5, 0), make_tc(0, 0, 15, 0)).expect("failed to create");
let i = a.intersection(&b).expect("intersection should succeed");
assert_eq!(i.tc_in, make_tc(0, 0, 5, 0));
assert_eq!(i.tc_out, make_tc(0, 0, 10, 0));
}
#[test]
fn test_time_range_gap_frames() {
let a = TimeRange::new(make_tc(0, 0, 0, 0), make_tc(0, 0, 5, 0)).expect("failed to create");
let b =
TimeRange::new(make_tc(0, 0, 10, 0), make_tc(0, 0, 15, 0)).expect("failed to create");
assert_eq!(a.gap_frames(&b), 125); }
#[test]
fn test_duration_summary() {
let events = vec![make_event(1, 0, 5), make_event(2, 5, 15)];
let s = compute_duration_summary(&events).expect("operation should succeed");
assert_eq!(s.event_count, 2);
assert_eq!(s.min_frames, 125);
assert_eq!(s.max_frames, 250);
assert_eq!(s.total_frames, 375);
}
#[test]
fn test_duration_summary_empty() {
let result = compute_duration_summary(&[]);
assert!(result.is_err());
}
#[test]
fn test_offset_events() {
let events = vec![make_event(1, 0, 5)];
let shifted =
offset_events(&events, 25, EdlFrameRate::Fps25).expect("operation should succeed");
assert_eq!(
shifted[0].record_in.to_frames(),
events[0].record_in.to_frames() + 25
);
}
#[test]
fn test_offset_events_negative() {
let events = vec![make_event(1, 0, 5)];
let result = offset_events(&events, -999_999, EdlFrameRate::Fps25);
assert!(result.is_err());
}
#[test]
fn test_programme_duration_frames() {
let events = vec![make_event(1, 0, 5), make_event(2, 10, 20)];
let d = programme_duration_frames(&events).expect("operation should succeed");
assert_eq!(d, 500); }
#[test]
fn test_programme_duration_empty() {
let result = programme_duration_frames(&[]);
assert!(result.is_err());
}
}