diff --git a/crates/engine/src/commands/file.rs b/crates/engine/src/commands/file.rs index f2347593..55b2c2b6 100644 --- a/crates/engine/src/commands/file.rs +++ b/crates/engine/src/commands/file.rs @@ -688,7 +688,7 @@ pub fn specs() -> Vec { "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; by default picked files import as stills and folders' numbered stills as sequences; false imports 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), 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: 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})}", always, import_cmd ), diff --git a/crates/engine/src/sequence.rs b/crates/engine/src/sequence.rs index 79142955..c98aba1d 100644 --- a/crates/engine/src/sequence.rs +++ b/crates/engine/src/sequence.rs @@ -42,15 +42,17 @@ impl SequenceOptions { SequenceOptions { sequence, folders: sequence, alphabetical: flag("alphabetical").unwrap_or(false) } } - /// `file.import`'s options, as in After Effects: picked or dropped files import as stills - /// unless `sequence: true` (the Import dialog's " Sequence" checkbox, which is off - /// unless ticked), while a folder's numbered stills import as sequences unless - /// `sequence: false` (Alt-drag of a folder). + /// `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 " Sequence" checkbox (off + /// unless ticked) as `sequence`. Without `sequence` or `drag` (scripts, agents) a numbered + /// still brings its run, as it always has. 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), folders: sequence.unwrap_or(true), alphabetical } + SequenceOptions { sequence: sequence.unwrap_or(alphabetical || !dropped), folders: sequence.unwrap_or(true), alphabetical } } } diff --git a/crates/engine/src/tests_sequence.rs b/crates/engine/src/tests_sequence.rs index d59a9298..a89485f9 100644 --- a/crates/engine/src/tests_sequence.rs +++ b/crates/engine/src/tests_sequence.rs @@ -97,11 +97,15 @@ fn numbered_stills_import_as_stills_by_default() { let r = s.execute("file.import", json!({"paths": [at(&dir, "shot_0002.png")], "drag": true})).unwrap(); let (name, f) = footage(&s, &r["items"][0]); assert_eq!((name.as_str(), f.kind), ("shot_0002.png", FootageKind::Still)); - let r = s.execute("file.import", json!({"paths": [at(&dir, "shot_0001.png"), at(&dir, "shot_0004.png")]})).unwrap(); + let r = s.execute("file.import", json!({"paths": [at(&dir, "shot_0001.png"), at(&dir, "shot_0004.png")], "drag": true})).unwrap(); assert_eq!(r["items"].as_array().unwrap().len(), 2, "{r}"); 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); } #[test] diff --git a/docs/agents.md b/docs/agents.md index 62fcd216..c631bc05 100644 --- a/docs/agents.md +++ b/docs/agents.md @@ -251,10 +251,10 @@ build real projects through these interfaces; they are worked examples of everyt then `keys.velocity {"outSpeed":0,"outInfluence":33.33}`. * **Gradients**: `set_property {"path":"contents/gfill/colors","value":["#0080ff","#ffff00"]}` or `{"colors":[[0,"#0080ff"],[1,[1,1,0]]],"opacities":[[0,1],[1,0.5]]}`. -* **Image sequences**: `file.import {"paths":["/r/shot_0001.png"], "sequence": true}` imports the - file's whole numbered run as one item (several picked frames: that range; a folder, even without - `sequence`: each run in it; without `sequence` picked files import as stills, as in After - Effects; `"alphabetical": true`: Force Alphabetical Order, the files +* **Image sequences**: `file.import {"paths":["/r/shot_0001.png"]}` imports the file's whole + numbered run as one item (several picked frames: that range; a folder: each run in it; + `"sequence": false`: one still; dropped files, `"drag": true`, import as stills unless + `"sequence": true`, as in After Effects; `"alphabetical": true`: Force Alphabetical Order, the files play one after another) at `frameRate` or 30 fps (Settings ▸ Import). Missing frames show colour bars, as in After Effects. `file.interpretFootage {"items":[id], "frameRate":12, "startTimecode":1001}` conforms them and sets Override Start (the item and layers that ran to its end get the new diff --git a/docs/footage.md b/docs/footage.md index d25dcc34..458c4c99 100644 --- a/docs/footage.md +++ b/docs/footage.md @@ -52,9 +52,11 @@ Effects does, so every other frame stays at its number. Render or copy the missi folder and choose File ▸ Reload Footage to fill the gaps. Agents: `file.import {"paths": ["/shots/shot_0001.png"], "sequence": true, "frameRate": 24}`. -Without `sequence`, picked files import as stills and a folder's numbered runs as sequences; +Without `sequence`, a numbered file brings its whole run and a folder's numbered runs import as +sequences, except dropped files (`"drag": true`), which import as stills as in After Effects. `"sequence": true` is the Sequence option (one file brings its whole run), `"sequence": false` -imports a folder's files one by one, and `"alphabetical": true` is Force Alphabetical Order. +imports stills and a folder's files one by one, and `"alphabetical": true` is Force Alphabetical +Order. ## Interpret Footage