Merge pull request #2107 from gmrmad1/fix/2073-replace-footage-kind

engine: Replace Footage keeps a still a still (#2073)
This commit is contained in:
BFlat
2026-10-30 05:10:25 +07:00
committed by GitHub
3 changed files with 78 additions and 9 deletions
+28 -4
View File
@@ -878,10 +878,34 @@ fn replace_footage(s: &mut Session, p: &Value) -> Result<Value> {
let path = str_p(p, "path").ok_or_else(|| bad("file.replaceFootage", "missing `path`"))?.to_string();
let item = *items_p(s, p).first().ok_or_else(|| bad("file.replaceFootage", "select footage"))?;
let importer = s.importer.clone().ok_or_else(|| EngineError::Other("media import is not available in this build".into()))?;
// A numbered still brings its image sequence, as on import (`sequence: false` for one frame).
let opts = SequenceOptions::from_params(p);
// Replacing a still keeps it a still; replacing a sequence keeps bringing the new file's run.
// `sequence`/`alphabetical` override that (#2073).
let previous = match s.project.item(item).map(|it| &it.kind) {
Some(ItemKind::Footage(f)) if f.kind == FootageKind::Sequence => (true, true, f.alphabetical),
_ => (false, false, false),
};
// Only `sequence` decides whether the item keeps its kind; `alphabetical` is a sequence
// modifier, not a request to make one.
let inherited = b_p(p, "sequence").is_none();
let mut opts = SequenceOptions::from_params(p);
if inherited {
(opts.sequence, opts.folders) = (previous.0, previous.1);
if previous.0 && b_p(p, "alphabetical").is_none() {
opts.alphabetical = previous.2;
}
}
let src = crate::sequence::source_of(s.services.as_ref(), &path, opts);
let f = crate::sequence::probe(importer.as_ref(), &src, s.sequence_rate()).map_err(|e| EngineError::Other(format!("{path}: {e}")))?;
let mut f = crate::sequence::probe(importer.as_ref(), &src, s.sequence_rate()).map_err(|e| EngineError::Other(format!("{path}: {e}")))?;
// A single-file source comes back as a still; an inherited sequence kind keeps it a
// one-frame sequence, so a later default replacement still brings numbered runs (#2073).
if inherited && previous.0 && matches!(src, crate::sequence::Source::File(_)) && f.kind == FootageKind::Still {
f.kind = FootageKind::Sequence;
f.sequence = vec![f.path.clone()];
f.alphabetical = opts.alphabetical;
f.frame_rate = s.sequence_rate();
f.native_rate = None;
f.sync_sequence_duration();
}
let name = src.name();
// Replacing a missing file relinks the other missing files that moved with it (as After Effects
// does when you locate one).
@@ -1093,7 +1117,7 @@ pub fn specs() -> Vec<CommandSpec> {
"File...",
["File", "Replace Footage"],
Some("Cmd+H"),
"{path, item?, sequence?: bool (default true: a numbered still brings its image sequence), alphabetical?: bool}",
"{path, item?, sequence?: bool (a numbered still brings its image sequence; default: the item keeps its kind, so a still stays a still and a sequence stays a sequence), alphabetical?: bool}",
has_footage_selection,
replace_footage
),