#1417: Reload Footage re-reads a file's markers; index marker times when merging them

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDvxCS8xcR28VArjzn1x7U
This commit is contained in:
Claude
2026-10-29 11:41:27 +00:00
parent 0dd14e2706
commit b5b77f446e
3 changed files with 45 additions and 6 deletions
+4 -1
View File
@@ -328,8 +328,11 @@ fn update_from_source(s: &mut Session, p: &Value) -> Result<Value> {
if l.markers_locked {
continue;
}
// One set of the times already on the layer: a file with thousands of markers must not
// scan the layer's list for each of them.
let mut times: std::collections::HashSet<_> = l.markers.iter().map(|x| x.time).collect();
for m in ms {
if !l.markers.iter().any(|x| x.time == m.time) {
if times.insert(m.time) {
l.markers.push(m.clone());
n += 1;
}
@@ -81,6 +81,8 @@ fn refreshed(s: &Session, f: &Footage, probed: Footage) -> Footage {
nf.codec = probed.codec;
nf.has_video = probed.has_video;
nf.has_audio = probed.has_audio;
// The markers the file carries now (a WAV's cue points), for Update Markers From Source.
nf.markers = probed.markers;
if matches!(f.kind, FootageKind::Video | FootageKind::Audio) {
nf.duration = probed.duration;
// Interpret Footage ▸ Conform to frame rate stays; the file's own rate is refreshed.
+39 -5
View File
@@ -12,8 +12,9 @@ fn names(s: &mut Session, layer: u64) -> Vec<String> {
list.as_array().unwrap().iter().map(|m| m["comment"].as_str().unwrap().to_string()).collect()
}
/// A one-second mono 8 kHz WAV with two named cue points (a `cue ` chunk and a `LIST` `adtl`).
fn marker_wav() -> Vec<u8> {
/// A one-second mono 8 kHz WAV with the named cue points `(id, sample, name)` (a `cue ` chunk and a
/// `LIST` `adtl`).
fn marker_wav(points: &[(u32, u32, &str)]) -> Vec<u8> {
fn chunk(id: &[u8; 4], body: &[u8]) -> Vec<u8> {
let mut v = id.to_vec();
v.extend((body.len() as u32).to_le_bytes());
@@ -30,10 +31,9 @@ fn marker_wav() -> Vec<u8> {
fmt.extend(16000u32.to_le_bytes());
fmt.extend(2u16.to_le_bytes());
fmt.extend(16u16.to_le_bytes());
let points = [(1u32, 0u32, "Intro"), (2, 4000, "Middle")];
let mut cue = (points.len() as u32).to_le_bytes().to_vec();
let mut adtl = b"adtl".to_vec();
for (id, at, name) in points {
for &(id, at, name) in points {
for v in [id, 0] {
cue.extend(v.to_le_bytes());
}
@@ -66,7 +66,7 @@ fn a_wav_layer_starts_with_the_markers_in_its_file() {
let dir = std::env::temp_dir().join(format!("effectcraft-host-wav-markers-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("cues.wav");
std::fs::write(&path, marker_wav()).unwrap();
std::fs::write(&path, marker_wav(&[(1, 0, "Intro"), (2, 4000, "Middle")])).unwrap();
let r = s.execute_checked("file.import", json!({"paths": [path.to_string_lossy()], "addToComp": true})).unwrap();
assert_eq!(r["errors"], json!([]), "{r}");
let layer = s.active_comp().unwrap().layers.iter().find(|l| matches!(l.source, LayerSource::Footage { .. })).unwrap().id.0;
@@ -90,3 +90,37 @@ fn a_wav_layer_starts_with_the_markers_in_its_file() {
assert_eq!(s.execute_checked("layer.updateMarkersFromSource", json!({})).unwrap()["added"], 0);
let _ = std::fs::remove_dir_all(&dir);
}
/// Reload Footage re-reads the markers the file carries: a cue point added to the file after the
/// import is available to Update Markers From Source (and a layer already made keeps its own).
#[test]
fn reloading_a_wav_picks_up_cue_points_added_to_the_file() {
let mut s = effectcraft_host::session();
s.execute_checked("comp.new", json!({"name": "C", "width": 64, "height": 48, "frameRate": 30, "duration": 2})).unwrap();
let dir = std::env::temp_dir().join(format!("effectcraft-host-wav-reload-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("cues.wav");
std::fs::write(&path, marker_wav(&[(1, 0, "Intro")])).unwrap();
let r = s.execute_checked("file.import", json!({"paths": [path.to_string_lossy()], "addToComp": true})).unwrap();
assert_eq!(r["errors"], json!([]), "{r}");
let (layer, item) = s
.active_comp()
.unwrap()
.layers
.iter()
.find_map(|l| match l.source {
LayerSource::Footage { item } => Some((l.id.0, item.0)),
_ => None,
})
.unwrap();
assert_eq!(names(&mut s, layer), ["Intro"]);
// The file gains a cue point; the layer does not change until asked.
std::fs::write(&path, marker_wav(&[(1, 0, "Intro"), (2, 4000, "Added later")])).unwrap();
let r = s.execute_checked("file.reloadFootage", json!({"items": [item]})).unwrap();
assert_eq!(r["reloaded"], 1, "{r}");
assert_eq!(names(&mut s, layer), ["Intro"]);
let r = s.execute_checked("layer.updateMarkersFromSource", json!({"layers": [layer]})).unwrap();
assert_eq!(r["added"], 1);
assert_eq!(names(&mut s, layer), ["Intro", "Added later"]);
let _ = std::fs::remove_dir_all(&dir);
}