Merge pull request #798 from MarekSpodBiedry/issue-795

Keep footage dropped on the timeline at its full length (#795)

# Conflicts:
#	crates/engine/src/tests_timeline.rs
This commit is contained in:
Brandon Thomas 2026-10-12 03:22:58 -04:00
commit 2cf28e5e24
4 changed files with 33 additions and 2 deletions

View File

@ -179,7 +179,8 @@ fn edit_into_comp(s: &mut Session, p: &Value, ripple: bool) -> Result<Value> {
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);

View File

@ -296,7 +296,10 @@ fn add_item(s: &mut Session, p: &Value) -> Result<Value> {
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)
})?;

View File

@ -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]));

View File

@ -946,3 +946,20 @@ fn proportional_share_is_linear_in_selection_order() {
assert_eq!(proportional_share(0, 0), 1.0);
assert_eq!(proportional_share(usize::MAX, 3), 1.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);
}