diff --git a/crates/engine/src/commands.rs b/crates/engine/src/commands.rs index ce502fce..d85abe6a 100644 --- a/crates/engine/src/commands.rs +++ b/crates/engine/src/commands.rs @@ -3148,10 +3148,11 @@ fn build() -> Vec { items.sort_by_key(|i| i.0); items.dedup(); let mut used = std::collections::HashSet::new(); - for it in s.project.sequences() { + for it in s.project.sequences().filter(|it| !items.contains(&it.id)) { if let Some(q) = it.as_sequence() { for t in q.all_tracks() { - used.extend(t.items.iter().map(|x| x.item)); + // Subclips keep the underlying file source alive too. + used.extend(t.items.iter().filter_map(|x| s.project.resolve_media(x.item).map(|(id, _, _)| id))); } } } diff --git a/crates/engine/src/relink_tests.rs b/crates/engine/src/relink_tests.rs index d25dda91..3bafc74c 100644 --- a/crates/engine/src/relink_tests.rs +++ b/crates/engine/src/relink_tests.rs @@ -270,3 +270,47 @@ fn jobs_list_reports_an_exports_offline_media() { assert_eq!(j["offlineMedia"][0]["item"], json!(items[1].0), "{j}"); let _ = std::fs::remove_dir_all(&root); } + +#[test] +fn deleting_bin_keeps_subclip_source_but_removes_unused_media() { + let root = tmp_dir("delete-bin-used-media"); + let (path, items) = saved_project(&root); + let mut s = open(&path); + let p = std::sync::Arc::make_mut(&mut s.project); + let seq = s.state.active_sequence.unwrap(); + let range = filmcraft_time::TimeRange::new(filmcraft_time::Tick::ZERO, p.item(items[0]).unwrap().duration()); + let sub = + p.add_item("Subclip", filmcraft_project::Label::Iris, filmcraft_project::ItemKind::Subclip { parent: items[0], range, restrict_trims: false }, None); + for track in p.sequence_mut(seq).unwrap().all_tracks_mut() { + track.items.retain(|clip| clip.item != items[1]); + for clip in &mut track.items { + clip.item = sub; + } + } + let bin = p.add_bin("Sources", None); + p.move_to_bin(&items, Some(bin)).unwrap(); + let before = s.active_sequence().unwrap().clone(); + s.execute("project.delete", json!({"items": [bin.0, items[0].0, bin.0]})).unwrap(); + assert_eq!(s.active_sequence().unwrap(), &before); + assert!(s.project.root.find_bin(bin).is_none()); + assert!(s.project.item(items[0]).unwrap().as_media().unwrap().offline); + assert!(s.project.item(items[1]).is_none()); + assert!(s.project.resolve_media(sub).unwrap().1.offline); + s.execute("edit.undo", json!({})).unwrap(); + assert_eq!(s.project.root.parent_of(items[0]), Some(bin)); + assert!(s.project.item(items[1]).is_some()); + let _ = std::fs::remove_dir_all(root); +} + +#[test] +fn deleting_sequence_and_its_media_does_not_keep_an_unused_source() { + let root = tmp_dir("delete-sequence-and-media"); + let (path, items) = saved_project(&root); + let mut s = open(&path); + let seq = s.state.active_sequence.unwrap(); + s.execute("project.delete", json!({"items": [seq.0, items[0].0]})).unwrap(); + assert!(s.project.item(seq).is_none()); + assert!(s.project.item(items[0]).is_none()); + assert!(s.project.item(items[1]).is_some()); + let _ = std::fs::remove_dir_all(root); +}