Scripting: CompItem.saveFrameToPng writes the frame as a PNG (#816)

After Effects scripts save a comp frame with saveFrameToPng(time, file);
EffectCraft only had the comp.saveFrameAs command, so the method was
undefined. It now runs comp.saveFrameAs at the given comp time, after the
same file-gate check as other script writes (Allow Scripts to Write Files).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
redonion23
2026-10-13 04:32:48 +02:00
co-authored by Claude Opus 5.5
parent 470ea7a3af
commit a96c80788a
2 changed files with 31 additions and 0 deletions
+8
View File
@@ -1600,6 +1600,14 @@ CompItem.prototype.constructor = CompItem;
__call("essential.rename", { comp: this.__id, control: c[i - 1].id, name: String(name) });
return String(name);
};
// After Effects' CompItem.saveFrameToPng(time, file): Composition ▸ Save Frame As ▸ File at
// `time` (comp seconds), behind the scripting file gate like other writes (#816).
P.saveFrameToPng = function (time, file) {
var f = __str(file);
if (!f) throw __err("saveFrameToPng: missing file");
__file("write", f, "");
__call("comp.saveFrameAs", { comp: this.__id, path: f, time: __num(time, "time") });
};
P.openInEssentialGraphics = function () { __call("comp.openInEssentialGraphics", { comp: this.__id }); };
P.exportAsMotionGraphicsTemplate = function (overwrite, path) {
var f = __str(path);
+23
View File
@@ -362,6 +362,29 @@ fn security_gate_for_files() {
let _ = std::fs::remove_dir_all(&dir);
}
/// #816: CompItem.saveFrameToPng writes the frame as a PNG, behind the file-write preference.
#[test]
fn save_frame_to_png_writes_behind_the_file_gate() {
let dir = std::env::temp_dir().join(format!("ec-save-frame-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let png = dir.join("frame.png");
let mut s = session();
let code = format!(
"var c = app.project.items.addComp('F', 40, 30, 1, 2, 25); c.layers.addSolid([1, 0, 0], 'S', 40, 30, 1); c.saveFrameToPng(1, new File({}))",
serde_json::to_string(&png.to_string_lossy()).unwrap()
);
let o = run_code(&mut s, &code, "frame.jsx");
assert!(o.error.expect("refused").message.contains("Allow Scripts to Write Files"));
assert!(!png.exists() || std::fs::metadata(&png).unwrap().len() == 0);
s.prefs.scripting.allow_scripts_write_files = true;
ok(&mut s, &code);
let bytes = std::fs::read(&png).unwrap();
assert_eq!(&bytes[..8], b"\x89PNG\r\n\x1a\n");
// IHDR width and height.
assert_eq!((u32::from_be_bytes(bytes[16..20].try_into().unwrap()), u32::from_be_bytes(bytes[20..24].try_into().unwrap())), (40, 30));
let _ = std::fs::remove_dir_all(&dir);
}
/// #590: a script can't give itself file and network access (or turn off the warning before
/// executing files) by changing Settings ▸ Scripting & Expressions; the user still can.
#[test]