mirror of
https://github.com/storytold/filmcraft.git
synced 2026-10-14 19:26:59 -04:00
Trim: extend each selected edit point to the playhead by its own distance (#528)
This commit is contained in:
1 parent
7bd7621262
commit
f684440ef5
2 files changed
+36
-7
No files matched your search
@@ -169,7 +169,7 @@ pub fn toggle_type(s: &mut Session) -> Result<Value> {
|
||||
|
||||
/// Trim every selected edit point by `delta` (edit point movement; positive = later).
|
||||
fn trim_all(s: &mut Session, delta: Tick) -> Result<Value> {
|
||||
let applied = trim_all_raw(s, delta)?;
|
||||
let applied = trim_all_raw(s, &|_, _| Some(delta))?;
|
||||
let first = applied.first().and_then(Value::as_i64).map(Tick).unwrap_or_default();
|
||||
add_shift(s, first);
|
||||
Ok(json!({"applied": applied}))
|
||||
@@ -193,7 +193,8 @@ fn reset_shift(s: &mut Session) {
|
||||
s.state.trim_shift = TrimShift::default();
|
||||
}
|
||||
|
||||
fn trim_all_raw(s: &mut Session, delta: Tick) -> Result<Vec<Value>> {
|
||||
/// Trim every selected edit point; `delta_for` gives each point's own movement, read from the sequence as it is when that point is reached.
|
||||
fn trim_all_raw(s: &mut Session, delta_for: &dyn Fn(&Sequence, &EditPoint) -> Option<Tick>) -> Result<Vec<Value>> {
|
||||
if s.state.edit_points.is_empty() {
|
||||
return Err(EngineError::Other("no edit points selected".into()));
|
||||
}
|
||||
@@ -201,6 +202,7 @@ fn trim_all_raw(s: &mut Session, delta: Tick) -> Result<Vec<Value>> {
|
||||
let mut applied = Vec::new();
|
||||
for ep in &pts {
|
||||
let seq = s.active_sequence().ok_or(EngineError::NoSequence)?;
|
||||
let Some(delta) = delta_for(seq, ep) else { continue };
|
||||
let r = match ep.kind {
|
||||
TrimKind::Roll => {
|
||||
let Some((l, r)) = roll_pair(seq, ep) else { continue };
|
||||
@@ -226,9 +228,10 @@ pub fn nudge(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
/// `trim.extendToPlayhead`: move each selected edit point to the playhead.
|
||||
pub fn extend_to_playhead(s: &mut Session) -> Result<Value> {
|
||||
let ph = s.playhead();
|
||||
let seq = s.active_sequence().ok_or(EngineError::NoSequence)?;
|
||||
let first = s.state.edit_points.first().and_then(|e| edit_time(seq, e)).ok_or_else(|| EngineError::Other("no edit points selected".into()))?;
|
||||
trim_all(s, ph - first)
|
||||
let applied = trim_all_raw(s, &|seq, e| edit_time(seq, e).map(|t| ph - t))?;
|
||||
let first = applied.first().and_then(Value::as_i64).map(Tick).unwrap_or_default();
|
||||
add_shift(s, first);
|
||||
Ok(json!({"applied": applied}))
|
||||
}
|
||||
|
||||
/// `trim.toPlayhead {side: "previous"|"next", ripple}` (Q/W, ⌥Q/⌥W): remove the material between
|
||||
@@ -502,14 +505,14 @@ fn apply_dynamic(s: &mut Session, offset: Tick) -> Result<Tick> {
|
||||
let redo = std::mem::take(&mut s.history.redo);
|
||||
s.history.limit = usize::MAX;
|
||||
s.project = d.before.clone();
|
||||
let r = if offset == Tick::ZERO { Ok(vec![json!(0)]) } else { trim_all_raw(s, offset) };
|
||||
let r = if offset == Tick::ZERO { Ok(vec![json!(0)]) } else { trim_all_raw(s, &|_, _| Some(offset)) };
|
||||
let applied = match r {
|
||||
Ok(a) => a.first().and_then(Value::as_i64).map(Tick).unwrap_or_default(),
|
||||
Err(_) => {
|
||||
// refused outright (e.g. it would break sync): stay where we were
|
||||
s.project = d.before.clone();
|
||||
if d.offset != Tick::ZERO {
|
||||
let _ = trim_all_raw(s, d.offset);
|
||||
let _ = trim_all_raw(s, &|_, _| Some(d.offset));
|
||||
}
|
||||
d.offset
|
||||
}
|
||||
|
||||
@@ -210,3 +210,29 @@ fn trim_monitor_sides_and_shift_counters() {
|
||||
let seq = s.execute("sequence.inspect", json!({})).unwrap();
|
||||
assert!(!seq["video"][0]["transitions"].as_array().unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extend_to_playhead_moves_each_unequal_out_point_to_the_playhead() {
|
||||
let mut s = demo();
|
||||
s.execute("sequence.linkedSelection", json!({"on": false})).unwrap();
|
||||
let rate = s.sequence_rate();
|
||||
let q = s.active_sequence().unwrap();
|
||||
// a clip on V1 and the overlay on V2 that overlap in time but end at different frames
|
||||
let mut found = None;
|
||||
for a in &q.video_tracks[0].items {
|
||||
for b in &q.video_tracks[1].items {
|
||||
let (lo, hi) = (a.start.max(b.start), a.end().min(b.end()));
|
||||
if found.is_none() && a.end() != b.end() && hi - lo > rate.tick_of(2) {
|
||||
found = Some((a.id, b.id, lo + rate.tick_of(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
let (a, b, ph) = found.expect("demo has overlapping clips on V1 and V2 with different ends");
|
||||
s.execute("playhead.set", json!({"time": ph.0})).unwrap();
|
||||
s.execute("trim.selectEditPoint", json!({"clip": a.0, "edge": "out", "kind": "trim"})).unwrap();
|
||||
s.execute("trim.selectEditPoint", json!({"clip": b.0, "edge": "out", "kind": "trim", "add": true})).unwrap();
|
||||
s.execute("trim.extendToPlayhead", json!({})).unwrap();
|
||||
let q = s.active_sequence().unwrap();
|
||||
assert_eq!(q.find_item(a).unwrap().1.end(), ph);
|
||||
assert_eq!(q.find_item(b).unwrap().1.end(), ph);
|
||||
}
|
||||
Reference in new issue
Block a user