#![allow(dead_code)]
use crate::event::EdlEvent;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ChangeKind {
Added,
Removed,
Modified,
Renumbered {
old_number: u32,
new_number: u32,
},
}
#[derive(Debug, Clone)]
pub struct ChangeEntry {
pub kind: ChangeKind,
pub old_event_number: Option<u32>,
pub new_event_number: Option<u32>,
pub description: String,
}
#[derive(Debug, Clone)]
pub struct Changelist {
pub entries: Vec<ChangeEntry>,
}
impl Changelist {
#[must_use]
pub fn len(&self) -> usize {
self.entries.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
#[must_use]
pub fn added_count(&self) -> usize {
self.entries
.iter()
.filter(|e| e.kind == ChangeKind::Added)
.count()
}
#[must_use]
pub fn removed_count(&self) -> usize {
self.entries
.iter()
.filter(|e| e.kind == ChangeKind::Removed)
.count()
}
#[must_use]
pub fn modified_count(&self) -> usize {
self.entries
.iter()
.filter(|e| e.kind == ChangeKind::Modified)
.count()
}
#[must_use]
pub fn to_report(&self) -> String {
let mut lines = Vec::new();
lines.push(format!("EDL Changelist: {} change(s)", self.entries.len()));
lines.push(format!(
" Added: {}, Removed: {}, Modified: {}",
self.added_count(),
self.removed_count(),
self.modified_count()
));
lines.push(String::new());
for entry in &self.entries {
let prefix = match &entry.kind {
ChangeKind::Added => "+",
ChangeKind::Removed => "-",
ChangeKind::Modified => "~",
ChangeKind::Renumbered { .. } => "#",
};
lines.push(format!("{prefix} {}", entry.description));
}
lines.join("\n")
}
}
#[must_use]
pub fn diff_events(old: &[EdlEvent], new: &[EdlEvent]) -> Changelist {
let mut entries = Vec::new();
let old_map: std::collections::HashMap<u32, &EdlEvent> =
old.iter().map(|e| (e.number, e)).collect();
let new_map: std::collections::HashMap<u32, &EdlEvent> =
new.iter().map(|e| (e.number, e)).collect();
for old_ev in old {
match new_map.get(&old_ev.number) {
None => {
entries.push(ChangeEntry {
kind: ChangeKind::Removed,
old_event_number: Some(old_ev.number),
new_event_number: None,
description: format!("Event {} removed (reel: {})", old_ev.number, old_ev.reel),
});
}
Some(new_ev) => {
if events_differ(old_ev, new_ev) {
let desc = describe_modification(old_ev, new_ev);
entries.push(ChangeEntry {
kind: ChangeKind::Modified,
old_event_number: Some(old_ev.number),
new_event_number: Some(new_ev.number),
description: desc,
});
}
}
}
}
for new_ev in new {
if !old_map.contains_key(&new_ev.number) {
entries.push(ChangeEntry {
kind: ChangeKind::Added,
old_event_number: None,
new_event_number: Some(new_ev.number),
description: format!("Event {} added (reel: {})", new_ev.number, new_ev.reel),
});
}
}
entries.sort_by_key(|e| e.new_event_number.or(e.old_event_number).unwrap_or(0));
Changelist { entries }
}
fn events_differ(a: &EdlEvent, b: &EdlEvent) -> bool {
a.reel != b.reel
|| a.track != b.track
|| a.edit_type != b.edit_type
|| a.source_in != b.source_in
|| a.source_out != b.source_out
|| a.record_in != b.record_in
|| a.record_out != b.record_out
|| a.transition_duration != b.transition_duration
|| a.clip_name != b.clip_name
}
fn describe_modification(old: &EdlEvent, new: &EdlEvent) -> String {
let mut parts = Vec::new();
if old.reel != new.reel {
parts.push(format!("reel: {} -> {}", old.reel, new.reel));
}
if old.source_in != new.source_in || old.source_out != new.source_out {
parts.push("source timecodes changed".to_string());
}
if old.record_in != new.record_in || old.record_out != new.record_out {
parts.push("record timecodes changed".to_string());
}
if old.edit_type != new.edit_type {
parts.push(format!("edit type: {} -> {}", old.edit_type, new.edit_type));
}
if old.clip_name != new.clip_name {
parts.push("clip name changed".to_string());
}
let detail = if parts.is_empty() {
"content changed".to_string()
} else {
parts.join(", ")
};
format!("Event {} modified: {detail}", old.number)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::event::{EditType, TrackType};
use crate::timecode::{EdlFrameRate, EdlTimecode};
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, reel: &str) -> EdlEvent {
let tc1 = make_tc(1, 0, 0, 0);
let tc2 = make_tc(1, 0, 5, 0);
EdlEvent::new(
num,
reel.to_string(),
TrackType::Video,
EditType::Cut,
tc1,
tc2,
tc1,
tc2,
)
}
#[test]
fn test_diff_no_changes() {
let events = vec![make_event(1, "A001"), make_event(2, "A002")];
let cl = diff_events(&events, &events);
assert!(cl.is_empty());
}
#[test]
fn test_diff_added_event() {
let old = vec![make_event(1, "A001")];
let new = vec![make_event(1, "A001"), make_event(2, "A002")];
let cl = diff_events(&old, &new);
assert_eq!(cl.added_count(), 1);
assert_eq!(cl.removed_count(), 0);
}
#[test]
fn test_diff_removed_event() {
let old = vec![make_event(1, "A001"), make_event(2, "A002")];
let new = vec![make_event(1, "A001")];
let cl = diff_events(&old, &new);
assert_eq!(cl.removed_count(), 1);
assert_eq!(cl.added_count(), 0);
}
#[test]
fn test_diff_modified_event() {
let old = vec![make_event(1, "A001")];
let new = vec![make_event(1, "B001")];
let cl = diff_events(&old, &new);
assert_eq!(cl.modified_count(), 1);
}
#[test]
fn test_diff_mixed_changes() {
let old = vec![make_event(1, "A001"), make_event(2, "A002")];
let new = vec![make_event(1, "B001"), make_event(3, "A003")];
let cl = diff_events(&old, &new);
assert_eq!(cl.modified_count(), 1); assert_eq!(cl.removed_count(), 1); assert_eq!(cl.added_count(), 1); }
#[test]
fn test_changelist_len() {
let old = vec![make_event(1, "A001")];
let new = vec![make_event(1, "A001"), make_event(2, "A002")];
let cl = diff_events(&old, &new);
assert_eq!(cl.len(), 1);
}
#[test]
fn test_changelist_report() {
let old = vec![make_event(1, "A001")];
let new = vec![make_event(1, "B001")];
let cl = diff_events(&old, &new);
let report = cl.to_report();
assert!(report.contains("1 change(s)"));
assert!(report.contains("Modified: 1"));
}
#[test]
fn test_events_differ_same() {
let a = make_event(1, "A001");
let b = make_event(1, "A001");
assert!(!events_differ(&a, &b));
}
#[test]
fn test_events_differ_reel() {
let a = make_event(1, "A001");
let b = make_event(1, "B001");
assert!(events_differ(&a, &b));
}
#[test]
fn test_describe_modification_reel() {
let a = make_event(1, "A001");
let b = make_event(1, "B001");
let desc = describe_modification(&a, &b);
assert!(desc.contains("reel: A001 -> B001"));
}
#[test]
fn test_diff_empty_lists() {
let cl = diff_events(&[], &[]);
assert!(cl.is_empty());
}
#[test]
fn test_change_kind_equality() {
assert_eq!(ChangeKind::Added, ChangeKind::Added);
assert_ne!(ChangeKind::Added, ChangeKind::Removed);
}
}