effectcraft/apps/effectcraft-cli/tests/qa_template.rs

116 lines
5.9 KiB
Rust

//! M13.15 scenario 5 — Essential Graphics through the CLI: a MOGRT-style template built by an
//! After Effects-style script (`effectcraft-cli script`), its controls changed with commands on
//! an instance, rendered, exported as a template and imported into a fresh project.
use std::path::PathBuf;
use std::process::Command;
use serde_json::Value;
fn cli(dir: &PathBuf, args: &[&str]) -> (i32, Value) {
let out = Command::new(env!("CARGO_BIN_EXE_effectcraft-cli")).current_dir(dir).args(args).arg("--json").output().unwrap();
let stdout = String::from_utf8_lossy(&out.stdout).to_string();
let v = serde_json::from_str(stdout.trim()).unwrap_or_else(|e| panic!("{args:?}: {e}: {stdout} {}", String::from_utf8_lossy(&out.stderr)));
(out.status.code().unwrap_or(-1), v)
}
fn ok(dir: &PathBuf, args: &[&str]) -> Value {
let (code, v) = cli(dir, args);
assert_eq!(code, 0, "{args:?}: {v}");
v
}
const BUILD: &str = r#"
app.beginUndoGroup("Build Template");
var comp = app.project.items.addComp("Lower Third", 320, 90, 1, 2, 12);
var bar = comp.layers.addSolid([1, 1, 1], "Bar", 320, 40, 1, 2);
bar.property("ADBE Transform Group").property("ADBE Position").setValue([160, 65]);
var t = comp.layers.addText("Jane Doe");
t.name = "Name";
t.property("ADBE Transform Group").property("ADBE Position").setValue([160, 30]);
var fill = bar.property("ADBE Effect Parade").addProperty("ADBE Fill");
t.property("ADBE Text Properties").property("ADBE Text Document").addToMotionGraphicsTemplateAs(comp, "Title");
fill.property("ADBE Fill-0002").addToMotionGraphicsTemplateAs(comp, "Bar Color");
bar.property("ADBE Transform Group").property("ADBE Opacity").addToMotionGraphicsTemplateAs(comp, "Bar Opacity");
comp.motionGraphicsTemplateName = "Lower Third";
app.endUndoGroup();
writeLn("controls: " + comp.motionGraphicsTemplateControllerCount);
comp.motionGraphicsTemplateName;
"#;
#[test]
fn essential_graphics_template_via_script_and_commands() {
let dir = std::env::temp_dir().join(format!("ec-qa-template-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("build.jsx"), BUILD).unwrap();
let r = ok(&dir, &["script", "build.jsx", "--save-as", "t.ecproj"]);
assert_eq!(r["ok"], true, "{r}");
assert_eq!(r["output"], "controls: 3");
let list = ok(&dir, &["exec", "essential.list", r##"{"comp":"Lower Third"}"##, "t.ecproj"]);
let controls: Vec<(String, String)> =
list["controls"].as_array().unwrap().iter().map(|c| (c["name"].as_str().unwrap().to_string(), c["type"].as_str().unwrap().to_string())).collect();
assert_eq!(
controls,
[("Title".into(), "text".into()), ("Bar Color".into(), "color".into()), ("Bar Opacity".into(), "slider".into())],
"ADBE Fill-0002 is the Fill's Color: {list}"
);
// An edit comp with an instance; controls addressed by name.
let r = ok(
&dir,
&[
"run",
"t.ecproj",
"comp.new",
r##"{"name":"Edit","width":320,"height":90,"frameRate":12,"duration":2}"##,
"layer.addItem",
r##"{"item":"Lower Third"}"##,
"comp.new",
r##"{"name":"Plain","width":320,"height":90,"frameRate":12,"duration":2}"##,
"layer.addItem",
r##"{"item":"Lower Third"}"##,
"comp.open",
r##"{"comp":"Edit"}"##,
"essential.set",
r##"{"layer":"#1","control":"Title","value":"John Smith"}"##,
"essential.set",
r##"{"layer":"#1","control":"Bar Color","value":"#00c080"}"##,
"essential.set",
r##"{"layer":"#1","control":"bar opacity","value":50}"##,
"--save",
],
);
let inst = ok(&dir, &["exec", "essential.instance", r##"{"comp":"Edit","layer":"#1"}"##, "t.ecproj"]);
assert!(inst["controls"].as_array().unwrap().iter().all(|c| c["overridden"] == true), "{inst}");
let (code, e) = cli(&dir, &["exec", "essential.set", r##"{"comp":"Edit","layer":"#1","control":"Subtitle","value":"x"}"##, "t.ecproj"]);
assert_eq!(code, 1);
assert!(e["error"].as_str().unwrap().contains("controls: Title, Bar Color, Bar Opacity"), "{e} ({r})");
// Render both instances and compare.
ok(&dir, &["render-frame", "t.ecproj", "--comp", "Edit", "--time", "1", "--out", "edit.png"]);
ok(&dir, &["render-frame", "t.ecproj", "--comp", "Plain", "--time", "1", "--out", "plain.png"]);
let edit = image::open(dir.join("edit.png")).unwrap().to_rgba8();
let plain = image::open(dir.join("plain.png")).unwrap().to_rgba8();
let bar = edit.get_pixel(10, 75).0;
assert!(bar[0] < 15 && (bar[1] as i32 - 96).abs() < 8 && (bar[2] as i32 - 64).abs() < 8, "50 % of #00c080 over black: {bar:?}");
let pbar = plain.get_pixel(10, 75).0;
assert!(pbar[0] > 240 && pbar[1] < 15, "the template's own red: {pbar:?}");
let diff = |y0: u32, y1: u32| -> u32 {
(y0..y1).flat_map(|y| (0..320).map(move |x| (x, y))).filter(|(x, y)| edit.get_pixel(*x, *y).0 != plain.get_pixel(*x, *y).0).count() as u32
};
assert!(diff(10, 45) > 100, "the title text differs");
// Export the template and bring it into a fresh project.
let x = ok(&dir, &["exec", "essential.exportTemplate", r##"{"comp":"Lower Third","path":"lt.ectemplate"}"##, "t.ecproj"]);
assert_eq!(x["controls"], 3, "{x}");
let info = ok(&dir, &["exec", "essential.templateInfo", r##"{"path":"lt.ectemplate"}"##, "--empty"]);
assert_eq!(info["name"], "Lower Third", "{info}");
let imp = ok(&dir, &["run", "--empty", "essential.importTemplate", r##"{"path":"lt.ectemplate"}"##, "--save-as", "fresh.ecproj"]);
assert!(imp["saved"].is_string(), "{imp}");
let list = ok(&dir, &["exec", "essential.list", r##"{"comp":"Lower Third"}"##, "fresh.ecproj"]);
assert_eq!(list["controls"].as_array().unwrap().len(), 3, "{list}");
let _ = std::fs::remove_dir_all(&dir);
}