Project: footage saved with a relative path is found next to the project file (#652)
file.import stores a path as given, so footage imported by the CLI or an agent as `clip.mp4` was saved relative, and opening the project looked it up from the working directory. The CLI, started in the project's folder, found it; the desktop app, started from the Finder, Explorer or a launcher in another folder, showed and rendered neither its picture nor its sound, while the Project panel still listed the saved duration. On open, a relative footage path that doesn't exist from the working directory but does next to the project file is resolved there (After Effects also looks for footage relative to the project). Paths that exist as they are, and absolute ones, are kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
This commit is contained in:
parent
2aefe3e644
commit
8e14c00eaf
|
|
@ -43,7 +43,8 @@ pub(crate) fn open(s: &mut Session, p: &Value) -> Result<Value> {
|
|||
let text = String::from_utf8(bytes).map_err(|_| EngineError::Other("not a text project file".into()))?;
|
||||
// XML copies (File ▸ Save a Copy As XML…) open like the JSON project.
|
||||
let text = if crate::xml_project::is_xml(&text) { crate::xml_project::from_xml(&text).map_err(EngineError::Other)? } else { text };
|
||||
let proj = Project::from_json(&text).map_err(|e| EngineError::Other(format!("{path} is not a project EffectCraft can open: {e}")))?;
|
||||
let mut proj = Project::from_json(&text).map_err(|e| EngineError::Other(format!("{path} is not a project EffectCraft can open: {e}")))?;
|
||||
resolve_relative_footage(&mut proj, path, s.services.as_ref());
|
||||
s.replace_project(proj, Some(path.to_string()));
|
||||
s.note_project_path(path);
|
||||
// Written by a newer EffectCraft: what this version doesn't know would be lost on saving.
|
||||
|
|
@ -62,6 +63,28 @@ pub(crate) fn open(s: &mut Session, p: &Value) -> Result<Value> {
|
|||
Ok(json!({"path": path, "savedBy": saved_by}))
|
||||
}
|
||||
|
||||
/// Footage saved with a relative path (imported by the CLI or an agent as `clip.mp4`, or written
|
||||
/// by hand) is found next to the project file, as After Effects looks for footage relative to
|
||||
/// the project. It used to be looked up from the working directory: the CLI started in the
|
||||
/// project's folder found it, the desktop app (started from the Finder, Explorer or a launcher,
|
||||
/// in another folder) showed and rendered nothing (#652). A path that exists from the working
|
||||
/// directory is kept.
|
||||
fn resolve_relative_footage(p: &mut Project, project_path: &str, services: &dyn crate::Services) {
|
||||
use std::path::Path;
|
||||
let Some(dir) = Path::new(project_path).parent().filter(|d| !d.as_os_str().is_empty()) else { return };
|
||||
let beside = |f: &str| dir.join(f).to_string_lossy().to_string();
|
||||
for item in p.items.values_mut() {
|
||||
let ItemKind::Footage(f) = &mut item.kind else { continue };
|
||||
if f.path.is_empty() || !Path::new(&f.path).is_relative() || services.exists(&f.path) || !services.exists(&beside(&f.path)) {
|
||||
continue;
|
||||
}
|
||||
f.path = beside(&f.path);
|
||||
for frame in f.sequence.iter_mut().filter(|q| Path::new(q.as_str()).is_relative()) {
|
||||
*frame = beside(frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The project file's text for `path`: `.ecproj` JSON, or XML for `.ecprojx` / `.xml` (a
|
||||
/// project opened from an XML copy saves as XML again). Fails, writing nothing, when the project
|
||||
/// wouldn't read back.
|
||||
|
|
|
|||
|
|
@ -193,6 +193,30 @@ mod tests {
|
|||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
|
||||
/// #652: footage saved with a relative path is found next to the project, wherever the
|
||||
/// app was started (the desktop app doesn't start in the project's folder, the CLI often
|
||||
/// does).
|
||||
#[test]
|
||||
fn relative_footage_is_found_next_to_the_project() {
|
||||
let dir = std::env::temp_dir().join(format!("ec-relative-{}", std::process::id()));
|
||||
std::fs::create_dir_all(dir.join("media")).unwrap();
|
||||
let clip = dir.join("media").join("ec-relative-clip.png");
|
||||
std::fs::write(&clip, b"x").unwrap();
|
||||
let mut s = session_with(&["media/ec-relative-clip.png", "/nonexistent/ec/abs.png", "media/ec-relative-gone.png"]);
|
||||
let path = dir.join("p.ecproj");
|
||||
s.execute("file.saveAs", json!({"path": path.to_string_lossy()})).unwrap();
|
||||
let mut o = Session::default();
|
||||
o.execute("file.open", json!({"path": path.to_string_lossy()})).unwrap();
|
||||
let mut paths: Vec<String> =
|
||||
o.project.items.values().filter_map(|i| if let ItemKind::Footage(f) = &i.kind { Some(f.path.clone()) } else { None }).collect();
|
||||
paths.sort();
|
||||
assert_eq!(paths, ["/nonexistent/ec/abs.png", clip.to_str().unwrap(), "media/ec-relative-gone.png"]);
|
||||
assert!(!o.is_dirty(), "finding the files is no edit");
|
||||
let r = o.execute("footage.check", json!({"wait": true})).unwrap();
|
||||
assert_eq!(r["missing"], 2);
|
||||
let _ = std::fs::remove_dir_all(dir);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn open_starts_the_check_when_asked() {
|
||||
let mut s = session_with(&["/nonexistent/ec/a.mov"]);
|
||||
|
|
|
|||
Loading…
Reference in New Issue