Reload Footage shows new pixels and keeps Photoshop layers; Reload Footage Changed on Disk

Reload Footage (Cmd+Alt+L) updated an item's metadata but left the media
pool's decoded frames and the layer cache in place, so the old image kept
rendering until Edit > Purge. It also replaced the footage with a probe of
the merged document, so a Photoshop layer item lost its layer and its
Retain Layer Sizes bounds.

Reload now re-reads only what the file decides (size, a movie's duration,
streams and frame rate, codec, a data file's text), keeps the layer or
page and the interpretation, finds a Photoshop layer again by name, and
makes the media layer forget the file (FootageSource::forget, which also
drops the EXR aux-channel cache) and clears the layer cache.

Settings > Import > Reload Footage Changed on Disk (on by default): the
desktop app stamps footage files (size + modification time) on a worker
thread every two seconds and when the window comes back to the front, and
reloads what changed. Not an undo step and it doesn't modify the project;
a file still being written is retried on the next check.
footage.reloadChanged runs one check (CLI, MCP, agents).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Katt
2026-10-10 16:36:50 +05:30
co-authored by Claude Opus 5.5
parent 813c7c4650
commit e6d37c2fa6
10 changed files with 522 additions and 37 deletions
+10 -35
View File
@@ -911,41 +911,7 @@ fn replace_with_solid(s: &mut Session, p: &Value) -> Result<Value> {
fn reload(s: &mut Session, p: &Value) -> Result<Value> {
let items = items_p(s, p);
if s.importer.is_none() {
return Err(EngineError::Other("media import is not available in this build".into()));
}
let mut updates = vec![];
for i in &items {
if let Some(ItemKind::Footage(f)) = s.project.item(*i).map(|x| &x.kind) {
// An image sequence picks up frames added to (or removed from) its run.
let mut nf = match s.probe_footage(&f.path) {
Ok(n) => n,
Err(_) => Footage { missing: true, ..f.clone() },
};
// Keep the interpretation.
nf.alpha = f.alpha;
nf.loop_count = f.loop_count;
nf.pixel_aspect = f.pixel_aspect;
nf.start_timecode = f.start_timecode;
if nf.kind == FootageKind::Sequence && f.kind == FootageKind::Sequence {
nf.frame_rate = f.frame_rate;
nf.native_rate = f.native_rate;
nf.alphabetical = f.alphabetical;
nf.sync_sequence_duration();
}
updates.push((*i, nf));
}
}
let n = updates.len();
s.edit("Reload Footage", None, |proj, _| {
for (i, f) in updates {
if let Some(it) = proj.item_mut(i) {
it.kind = ItemKind::Footage(f);
}
}
Ok(())
})?;
s.events.push(crate::Event::PurgeCaches);
let n = crate::footage_reload::reload_items(s, &items)?;
Ok(json!({"reloaded": n}))
}
@@ -1009,6 +975,15 @@ pub fn specs() -> Vec<CommandSpec> {
has_items,
crate::footage_check::command
),
cmd!(
"footage.reloadChanged",
"Reload Changed Footage",
[],
None,
"{} — reload the footage whose file changed on disk since it was last seen (size or modification time; Settings ▸ Import ▸ Reload Footage Changed on Disk runs this in the background); files seen for the first time are only remembered. Returns {files, reloaded}",
has_items,
crate::footage_reload::command
),
cmd!(
"file.runScript",
"Run Script File...",