mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-26 19:18:14 -06:00
Keep a composition work area inside the composition and at least one frame long.
Setting the start past the current end, or shortening the composition, stored an inverted interval. Trimming that interval then saved a negative duration.
This commit is contained in:
@@ -76,6 +76,8 @@ fn apply_settings(c: &mut Comp, p: &Value) {
|
||||
c.work_area.1 = c.duration;
|
||||
}
|
||||
c.work_area.1 = c.work_area.1.min(c.duration);
|
||||
// A shorter composition must not leave the start past the end.
|
||||
keep_work_area_ordered(c);
|
||||
}
|
||||
if let Some(pa) = f_p(p, "pixelAspect") {
|
||||
c.pixel_aspect = pa.max(0.1);
|
||||
@@ -196,17 +198,40 @@ fn work_area(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if let Some(e) = end {
|
||||
c.work_area.1 = e.clamp(c.work_area.0 + fd, c.duration);
|
||||
}
|
||||
// A start on its own can land after the current end. Pull it back so the interval
|
||||
// still covers one frame, the same rule as `set: "begin"`.
|
||||
keep_work_area_ordered(c);
|
||||
Ok(())
|
||||
})?;
|
||||
let c = s.project.comp(cid).ok_or(EngineError::NoComp)?;
|
||||
Ok(json!({"start": c.work_area.0.seconds(), "end": c.work_area.1.seconds()}))
|
||||
}
|
||||
|
||||
/// Keep `work_area` inside the composition and at least one frame long.
|
||||
fn keep_work_area_ordered(c: &mut Comp) {
|
||||
let fd = c.frame_duration();
|
||||
c.work_area.1 = c.work_area.1.min(c.duration);
|
||||
if c.work_area.0 < Tick::ZERO {
|
||||
c.work_area.0 = Tick::ZERO;
|
||||
}
|
||||
if c.work_area.0 + fd > c.work_area.1 {
|
||||
c.work_area.0 = (c.work_area.1 - fd).max(Tick::ZERO);
|
||||
}
|
||||
if c.work_area.0 + fd > c.work_area.1 {
|
||||
c.work_area.1 = (c.work_area.0 + fd).min(c.duration);
|
||||
}
|
||||
}
|
||||
|
||||
fn trim_to_wa(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let cid = comp_id(s, p)?;
|
||||
s.edit("Trim Comp to Work Area", None, |proj, _| {
|
||||
let c = proj.comp_mut(cid).ok_or(EngineError::NoComp)?;
|
||||
let (a, b) = c.work_area;
|
||||
let fd = c.frame_duration();
|
||||
// An interval that is already inverted must not become a negative duration.
|
||||
if a < Tick::ZERO || b > c.duration || a + fd > b {
|
||||
return Err(bad("comp.trimToWorkArea", "the work area must cover at least one frame inside the composition"));
|
||||
}
|
||||
for l in &mut c.layers {
|
||||
l.start_time -= a;
|
||||
l.in_point = (l.in_point - a).max(Tick::ZERO);
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
use effectcraft_keyframe::Value as KV;
|
||||
use effectcraft_project::{FrameBlend, ItemKind, MatteKind, Quality, Sampling};
|
||||
use effectcraft_time::Tick;
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::{EngineError, Event, Session};
|
||||
@@ -253,6 +254,69 @@ fn lift_and_extract_work_area() {
|
||||
assert!((spans[1].0 - 2.0).abs() < 1e-6 && (spans[1].1 - 8.0).abs() < 1e-6, "{spans:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn work_area_stays_ordered_when_the_start_passes_the_end() {
|
||||
let ordered = |s: &Session| {
|
||||
let c = s.active_comp().unwrap();
|
||||
let fd = c.frame_duration();
|
||||
assert!(c.duration >= fd, "duration {}", c.duration.seconds());
|
||||
assert!(c.work_area.0 >= Tick::ZERO);
|
||||
assert!(c.work_area.0 + fd <= c.work_area.1, "start {} end {}", c.work_area.0.seconds(), c.work_area.1.seconds());
|
||||
assert!(c.work_area.1 <= c.duration);
|
||||
(c.duration, c.work_area)
|
||||
};
|
||||
let one_frame_at_two = |s: &Session| {
|
||||
let end = Tick::from_seconds_f64(2.0);
|
||||
(end - s.active_comp().unwrap().frame_duration(), end)
|
||||
};
|
||||
|
||||
let mut s = comp();
|
||||
s.execute("comp.workArea", json!({"end": 2.0})).unwrap();
|
||||
s.execute("comp.workArea", json!({"start": 8.0})).unwrap();
|
||||
assert_eq!(ordered(&s).1, one_frame_at_two(&s));
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(ordered(&s).1, (Tick::ZERO, Tick::from_seconds_f64(2.0)));
|
||||
|
||||
s.execute("comp.workArea", json!({"start": 0.0, "end": 2.0})).unwrap();
|
||||
s.execute("comp.trimToWorkArea", json!({})).unwrap();
|
||||
assert_eq!(ordered(&s), (Tick::from_seconds_f64(2.0), (Tick::ZERO, Tick::from_seconds_f64(2.0))));
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(ordered(&s).0, Tick::from_seconds_f64(10.0));
|
||||
|
||||
let mut s = comp();
|
||||
s.execute("comp.workArea", json!({"start": 9.0, "end": 10.0})).unwrap();
|
||||
let before = ordered(&s);
|
||||
s.execute("comp.settings", json!({"duration": 2.0})).unwrap();
|
||||
let (dur, wa) = ordered(&s);
|
||||
assert_eq!(dur, Tick::from_seconds_f64(2.0));
|
||||
assert_eq!(wa, one_frame_at_two(&s));
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(ordered(&s), before);
|
||||
|
||||
let mut s = comp();
|
||||
s.execute("comp.workArea", json!({"end": 2.0})).unwrap();
|
||||
s.execute("time.set", json!({"time": 8.0})).unwrap();
|
||||
s.execute("comp.workArea", json!({"set": "begin"})).unwrap();
|
||||
assert_eq!(ordered(&s).1, one_frame_at_two(&s));
|
||||
let path = tmp("WorkArea.ecproj");
|
||||
s.execute("file.saveAs", json!({"path": path})).unwrap();
|
||||
s.execute("file.open", json!({"path": path})).unwrap();
|
||||
assert_eq!(ordered(&s).1, one_frame_at_two(&s));
|
||||
|
||||
// A project that already has an inverted interval must not trim into a negative duration.
|
||||
let mut s = comp();
|
||||
let cid = s.state.active_comp.unwrap();
|
||||
{
|
||||
let c = std::sync::Arc::make_mut(&mut s.project).comp_mut(cid).unwrap();
|
||||
c.work_area = (Tick::from_seconds_f64(8.0), Tick::from_seconds_f64(2.0));
|
||||
}
|
||||
let err = s.execute("comp.trimToWorkArea", json!({})).unwrap_err();
|
||||
assert!(err.to_string().contains("work area"), "{err}");
|
||||
let c = s.active_comp().unwrap();
|
||||
assert_eq!(c.duration, Tick::from_seconds_f64(10.0));
|
||||
assert_eq!(c.work_area, (Tick::from_seconds_f64(8.0), Tick::from_seconds_f64(2.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn label_group_purge_and_edit_original() {
|
||||
crate::tests_roto::hold_roto_cache_test_lock();
|
||||
|
||||
Reference in New Issue
Block a user