From db11dffa183d5c57a355ceba28eaf248b665564a Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Sun, 11 Oct 2026 13:52:01 +0200 Subject: [PATCH 1/2] Keep footage dropped on the timeline at its full length (#795) --- crates/engine/src/commands/layer.rs | 5 ++++- crates/engine/src/tests_timeline.rs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/commands/layer.rs b/crates/engine/src/commands/layer.rs index 09bad38f..3094bbe3 100644 --- a/crates/engine/src/commands/layer.rs +++ b/crates/engine/src/commands/layer.rs @@ -295,7 +295,10 @@ fn add_item(s: &mut Session, p: &Value) -> Result { let mut l = build::layer(proj, &comp, &it.name, src, size, dur); l.start_time = start; l.in_point = start; - l.out_point = fr.snap_nearest((start + dur.unwrap_or(comp.duration)).min(comp.duration)).max(start + fr.frame_duration()); + // Footage and precomps keep their full length past the comp's end (After Effects); items + // without a duration (solids, comp-length stills) end with the comp. + let end = dur.map_or((start + comp.duration).min(comp.duration), |d| start + d); + l.out_point = fr.snap_nearest(end).max(start + fr.frame_duration()); place(&mut l, position); insert_layer_at(proj, st, cid, l, index) })?; diff --git a/crates/engine/src/tests_timeline.rs b/crates/engine/src/tests_timeline.rs index 6a74a23b..6b55970d 100644 --- a/crates/engine/src/tests_timeline.rs +++ b/crates/engine/src/tests_timeline.rs @@ -883,3 +883,20 @@ fn property_edits_apply_to_every_selected_layer() { s.execute("prop.set", json!({"layer": a, "path": "transform/rotation", "value": 30})).unwrap(); assert_eq!(prop(&s, b, "transform/rotation").value.as_f64(), 0.0); } + +#[test] +fn add_item_keeps_footage_longer_than_the_comp_at_full_length() { + let mut s = Session::default(); + s.execute("comp.new", json!({"name": "T", "width": 400, "height": 300, "frameRate": 30, "duration": 4})).unwrap(); + let f = effectcraft_project::Footage { + width: 100, + height: 100, + has_video: true, + duration: effectcraft_time::Tick::from_seconds_f64(10.0), + ..Default::default() + }; + let item = std::sync::Arc::make_mut(&mut s.project).add_item("long.mp4", effectcraft_color::Label::None, None, effectcraft_project::ItemKind::Footage(f)); + let l = s.execute("layer.addItem", json!({"item": item.0})).unwrap()["layer"].as_u64().unwrap(); + let layer = s.active_comp().unwrap().layer(effectcraft_project::LayerId(l)).unwrap(); + assert_eq!(layer.out_point.seconds(), 10.0); +} From 5df37babc32cc9e7957f53c9f03cf457ee20caa5 Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Mon, 12 Oct 2026 08:04:47 +0200 Subject: [PATCH 2/2] Keep Footage panel overlay/insert edits at full length past the comp end (#795) --- crates/engine/src/commands/footage_panel.rs | 3 ++- crates/engine/src/tests_panels.rs | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/commands/footage_panel.rs b/crates/engine/src/commands/footage_panel.rs index f984d53f..7b3bbe5c 100644 --- a/crates/engine/src/commands/footage_panel.rs +++ b/crates/engine/src/commands/footage_panel.rs @@ -179,7 +179,8 @@ fn edit_into_comp(s: &mut Session, p: &Value, ripple: bool) -> Result { let mut l = build::layer(proj, &comp, &it.name, src, size, Some(len)); l.start_time = at - src_in; l.in_point = at; - l.out_point = (at + len).min(comp.duration); + // Keep the full marked length past the comp's end (After Effects); the comp just doesn't show it. + l.out_point = at + len; let lid = l.id; let cm = proj.comp_mut(cid).ok_or(EngineError::NoComp)?; cm.layers.insert(0, l); diff --git a/crates/engine/src/tests_panels.rs b/crates/engine/src/tests_panels.rs index b77c7e9b..c99d92d1 100644 --- a/crates/engine/src/tests_panels.rs +++ b/crates/engine/src/tests_panels.rs @@ -185,6 +185,16 @@ fn scopes_read_the_comp_frame() { // ---------------------------------------------------------------- Footage panel +#[test] +fn footage_panel_overlay_keeps_full_length_past_the_comp_end() { + let (mut s, cid, _clip, item) = setup(32, 24, 8.0, |_| Image::filled(32, 24, [0.5, 0.5, 0.5, 1.0])); + s.execute("footage.open", json!({"item": item.0})).unwrap(); + let r = s.execute("footage.overlayEdit", json!({"item": item.0, "in": 0.0, "out": 4.0, "at": 6.0})).unwrap(); + let ov = LayerId(r["layer"].as_u64().unwrap()); + let l = s.project.comp(cid).unwrap().layer(ov).unwrap(); + assert_eq!((l.in_point.seconds(), l.out_point.seconds()), (6.0, 10.0)); +} + #[test] fn footage_panel_overlay_and_ripple_insert() { let (mut s, cid, clip, item) = setup(32, 24, 8.0, |_| Image::filled(32, 24, [0.5, 0.5, 0.5, 1.0]));