Footage: follow stored footage paths only on this computer or the project's share

Footage paths stored in a project are resolved through craft-docpath against
the project file when it is opened or imported: relative paths are taken from
the project's folder, and an item whose path is on another computer, names a
device or is a non-file URL is marked missing without being looked at. The
footage check, the changed-on-disk scan, Reload Footage, Relink, Edit Original
and Reveal in Finder resolve the path again before touching the file, and the
Project panel, Metadata panel and waveforms read no file details of missing
footage.

Lottie image assets are resolved against the Lottie file; one on another
computer is skipped with a warning. Files a 3D model names are read only when
local or on the model's own share.

Reveal in Finder refuses a folder that would open as an app or other bundle.

Files imported or relinked in this session are opened as given.
This commit is contained in:
aiongg committed 2026-10-16 17:43:58 +00:00
1 parent 1064911fc4
commit 816ca95800
26 files changed
+557 -99

No files matched your search

+1
View File
@@ -10,6 +10,7 @@ rust-version.workspace = true
workspace = true
[dependencies]
craft-docpath = { workspace = true }
effectcraft-time = { workspace = true }
effectcraft-project = { workspace = true }
effectcraft-raster = { workspace = true }
+25 -2
View File
@@ -557,9 +557,9 @@ impl Inner {
return m.clone();
}
let loaded = self.read(path).map_err(|e| e.to_string()).and_then(|bytes| {
let dir = std::path::Path::new(path).parent().map(|d| d.to_path_buf()).unwrap_or_default();
// Files the model names are read only when local or on the model's own network share.
let resolve = |uri: &str| -> Option<Vec<u8>> {
let p = dir.join(uri);
let p = craft_docpath::resolve(uri, Some(std::path::Path::new(path))).ok().filter(|p| p.is_absolute())?;
self.read(&p.to_string_lossy()).ok().map(|b| b.to_vec())
};
effectcraft_model::load(path, &bytes, &resolve).map_err(|e| e.to_string())
@@ -1140,4 +1140,27 @@ mod tests {
assert_eq!(px(&f, FloatCurve::Srgb), srgb);
assert_eq!(px(&f, FloatCurve::Gamma24), g24);
}
/// A model's material library is read beside it; one named by a path to another computer
/// is not read.
#[test]
fn model_files_on_other_computers_are_not_read() {
let dir = std::env::temp_dir().join(format!("ec-model-files-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("red.mtl"), "newmtl red\nKd 1 0 0\n").unwrap();
let pool = MediaPool::new();
let red = |name: &str, mtl: &str| {
let path = dir.join(name);
std::fs::write(&path, format!("mtllib {mtl}\nv 0 0 0\nv 1 0 0\nv 0 1 0\nusemtl red\nf 1 2 3\n")).unwrap();
let model = pool.inner.model(&path.to_string_lossy()).unwrap();
model.materials.iter().any(|m| m.base_color[..3] == [1.0, 0.0, 0.0])
};
assert!(red("beside.obj", "red.mtl"));
assert!(!red("unc.obj", r"\\192.0.2.1\share\red.mtl"));
assert!(!red("url.obj", "file://192.0.2.1/s/red.mtl"));
// Linux reads `//dir/red.mtl` as the local file.
#[cfg(unix)]
assert!(!red("slashes.obj", &format!("/{}", dir.join("red.mtl").display())));
let _ = std::fs::remove_dir_all(&dir);
}
}