mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-02 06:27:11 -06:00
Merge remote-tracking branch 'origin/main' into HEAD
This commit is contained in:
@@ -746,7 +746,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"File...",
|
||||
["File", "Import"],
|
||||
Some("Cmd+I"),
|
||||
"{paths: [string] (files or folders), sequence?: bool (the Sequence checkbox: true imports numbered stills of one run as one image sequence, one picked file bringing its whole run and several picked files that range; default: a numbered still brings its run, except dropped files (drag), which import as stills as in After Effects; folders' runs import as sequences; false imports stills and a folder's files individually), alphabetical?: bool (Force Alphabetical Order: every image of that type in the folder, by name), frameRate?: fps (image sequences; default Settings ▸ Import ▸ Sequence Footage), importAs?: footage|composition|compositionLayerSizes (Photoshop, PDF, Illustrator and EPS files), layer?: name|index (footage of one Photoshop layer), page?: number from 1 (PDF / Illustrator page), drag?: bool (dropped files: Settings ▸ Import ▸ Default Drag Import As; files import as stills unless sequence), addToComp?: bool (also add them to the active comp, at time?, index?, position? as in layer.addItem), background?: bool (probe the files in a background job, jobs.list / jobs.wait; returns {job})}",
|
||||
"{paths: [string] (files or folders), sequence?: bool (the Sequence checkbox: true imports numbered stills of one run as one image sequence, one picked file bringing its whole run and several picked files that range; default false: picked or dropped files import as stills, as in After Effects; folders' runs import as sequences; false imports stills and a folder's files individually), alphabetical?: bool (Force Alphabetical Order: every image of that type in the folder, by name), frameRate?: fps (image sequences; default Settings ▸ Import ▸ Sequence Footage), importAs?: footage|composition|compositionLayerSizes (Photoshop, PDF, Illustrator and EPS files), layer?: name|index (footage of one Photoshop layer), page?: number from 1 (PDF / Illustrator page), drag?: bool (dropped files: Settings ▸ Import ▸ Default Drag Import As; files import as stills unless sequence), addToComp?: bool (also add them to the active comp, at time?, index?, position? as in layer.addItem), background?: bool (probe the files in a background job, jobs.list / jobs.wait; returns {job})}",
|
||||
always,
|
||||
import_cmd
|
||||
),
|
||||
|
||||
@@ -45,14 +45,13 @@ impl SequenceOptions {
|
||||
/// `file.import`'s options. Dropped files (`drag: true`) import as stills, as in After
|
||||
/// Effects, while a folder's numbered stills import as sequences unless `sequence: false`
|
||||
/// (Alt-drag of a folder). The Import dialog passes its `"<format> Sequence"` checkbox (off
|
||||
/// unless ticked) as `sequence`. Without `sequence` or `drag` (scripts, agents) a numbered
|
||||
/// still brings its run, as it always has.
|
||||
/// unless ticked) as `sequence`. Without `sequence` (scripts, agents) a numbered still imports
|
||||
/// as itself, as `ImportOptions.sequence` defaults to false in After Effects (#2018).
|
||||
pub fn for_import(p: &serde_json::Value) -> SequenceOptions {
|
||||
let flag = |k: &str| p.get(k).and_then(serde_json::Value::as_bool);
|
||||
let (sequence, alphabetical) = (flag("sequence"), flag("alphabetical").unwrap_or(false));
|
||||
let dropped = flag("drag").unwrap_or(false);
|
||||
// Force Alphabetical Order is part of the Sequence option.
|
||||
SequenceOptions { sequence: sequence.unwrap_or(alphabetical || !dropped), folders: sequence.unwrap_or(true), alphabetical }
|
||||
SequenceOptions { sequence: sequence.unwrap_or(alphabetical), folders: sequence.unwrap_or(true), alphabetical }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -50,8 +50,20 @@ pub trait Services: Send + Sync {
|
||||
pub struct FsServices;
|
||||
|
||||
impl Services for FsServices {
|
||||
/// Refuses what isn't a regular file: a project can name `/dev/zero` or a FIFO, and reading
|
||||
/// those never ends (#2134).
|
||||
fn read_file(&self, path: &str) -> std::io::Result<Vec<u8>> {
|
||||
std::fs::read(path)
|
||||
use std::io::Read;
|
||||
let mut file = std::fs::File::open(path)?;
|
||||
let meta = file.metadata()?;
|
||||
if !meta.is_file() {
|
||||
return Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("{path} is not a file")));
|
||||
}
|
||||
let mut bytes = Vec::new();
|
||||
// A sparse file can claim far more than memory: an error, not an abort.
|
||||
bytes.try_reserve_exact(usize::try_from(meta.len()).unwrap_or(usize::MAX)).map_err(|e| std::io::Error::new(std::io::ErrorKind::OutOfMemory, e))?;
|
||||
file.read_to_end(&mut bytes)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
fn write_file(&self, path: &str, data: &[u8]) -> std::io::Result<()> {
|
||||
config::atomic_write(std::path::Path::new(path), data)
|
||||
@@ -70,3 +82,21 @@ pub trait Importer: Send + Sync {
|
||||
/// media layer reads from memory (the browser) register them; file-based ones need nothing.
|
||||
fn add_frames(&self, _paths: &[String]) {}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{FsServices, Services};
|
||||
|
||||
/// #2134: a project can name a folder, `/dev/zero` or a FIFO; reading those is an error.
|
||||
#[test]
|
||||
fn fs_services_read_only_regular_files() {
|
||||
let dir = std::env::temp_dir();
|
||||
assert!(FsServices.read_file(&dir.to_string_lossy()).is_err());
|
||||
#[cfg(unix)]
|
||||
assert!(FsServices.read_file("/dev/zero").is_err());
|
||||
let f = dir.join(format!("ec-services-read-{}.txt", std::process::id()));
|
||||
std::fs::write(&f, b"data").unwrap();
|
||||
assert_eq!(FsServices.read_file(&f.to_string_lossy()).unwrap(), b"data");
|
||||
let _ = std::fs::remove_file(f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,10 +104,11 @@ fn numbered_stills_import_as_stills_by_default() {
|
||||
let r = s.execute("file.import", json!({"paths": [dir.clone()], "drag": true})).unwrap();
|
||||
let (name, f) = footage(&s, &r["items"][0]);
|
||||
assert_eq!((name.as_str(), f.kind), ("shot_[0001-0005].png", FootageKind::Sequence));
|
||||
// Scripts and agents naming one frame without `sequence` still get its run.
|
||||
let r = s.execute("file.import", json!({"paths": [at(&dir, "shot_0003.png")]})).unwrap();
|
||||
let (_, f) = footage(&s, &r["items"][0]);
|
||||
assert_eq!(f.kind, FootageKind::Sequence);
|
||||
// #2018: scripts and agents naming one frame without `sequence` get that still, as
|
||||
// `ImportOptions.sequence` defaults to false in After Effects.
|
||||
let r = s.execute("file.import", json!({"paths": [at(&dir, "shot_0005.png")]})).unwrap();
|
||||
let (name, f) = footage(&s, &r["items"][0]);
|
||||
assert_eq!((name.as_str(), f.kind), ("shot_0005.png", FootageKind::Still));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user