mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-13 02:34:44 +00:00
* .aep import: Photoshop layers, placeholder effects, shape direction and Orientation expressions (#3087) - A footage item showing one Photoshop layer shows that layer (record index, bounds and name from its opti) and is named <layer>/<file>; an adjustment layer shows white where it applies (opt-in through SourceLayer::adjustment_white, the direct Photoshop import is unchanged). - Effects without a counterpart arrive as placeholder effects with their parameters, so expressions reading them keep working; pseudo effects are no loss, a Dropdown Menu Control becomes EffectCraft's. Slider ranges are checked, sizes are bounded. - The loss prompt shows every detail line the report keeps and how many more there are. - Shape direction and gradient highlight records in the default state are no loss. - An expression on Orientation is imported with its value or keys. * .aep import: report placeholder records and popup defaults that borrowed definitions leave open (#3087) A value record the borrowed definitions do not name is reported instead of dropped, and a borrowed popup without a record reports that its default item is not known (the first is used). * Merge upstream/main into aep-import-bugs-3087
This commit is contained in:
@@ -18,6 +18,7 @@ mod effects_round2;
|
||||
mod effects_round3;
|
||||
mod effects_thirdparty;
|
||||
mod fx_inject;
|
||||
mod issue_3087;
|
||||
mod lumetri;
|
||||
mod markers;
|
||||
mod masks;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
//! #3087: an expression on Orientation, built into the committed `synthetic.aep` (no external
|
||||
//! files; the values are made up, the layouts are docs/aep-format.md's). The Photoshop layers and
|
||||
//! placeholders of the issue are tested in `synthetic.rs` and `placeholders.rs`.
|
||||
|
||||
use effectcraft_aep::import::{ImportOptions, ImportResult, LossCategory, import};
|
||||
use effectcraft_aep::{Body, Chunk, parse, write};
|
||||
use effectcraft_keyframe::Value;
|
||||
use effectcraft_project::Project;
|
||||
|
||||
const SYNTHETIC: &[u8] = include_bytes!("../../fixtures/synthetic.aep");
|
||||
|
||||
fn visit(chunks: &mut [Chunk<'_>], f: &mut dyn FnMut(&mut Chunk<'_>)) {
|
||||
for c in chunks {
|
||||
f(c);
|
||||
visit(c.children_mut(), f);
|
||||
}
|
||||
}
|
||||
|
||||
fn import_all(bytes: &[u8]) -> (Project, ImportResult) {
|
||||
let mut project = Project::default();
|
||||
let none = |_: &str| false;
|
||||
let r = import(&mut project, bytes, &ImportOptions { allow_losses: true, ..ImportOptions::new(&none) }).unwrap();
|
||||
(project, r)
|
||||
}
|
||||
|
||||
/// `synthetic.aep` with every Orientation set to `[10, 20, 30]` and the expression `text`.
|
||||
fn with_orientation_expression(text: &str) -> Vec<u8> {
|
||||
let mut file = parse(SYNTHETIC).unwrap();
|
||||
let mut patched = 0;
|
||||
visit(&mut file.chunks, &mut |c| {
|
||||
if !c.is_list(b"otst") {
|
||||
return;
|
||||
}
|
||||
for k in c.children_mut() {
|
||||
if k.is_list(b"tdbs") {
|
||||
let kids = k.children_mut();
|
||||
for r in kids.iter_mut() {
|
||||
if let Body::Data(d) = &mut r.body {
|
||||
let d = d.to_mut();
|
||||
match &r.id {
|
||||
b"tdsb" => d.copy_from_slice(&1u32.to_be_bytes()),
|
||||
b"tdb4" => {
|
||||
d[119] = 0;
|
||||
d[120] = 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
let Body::List { children, .. } = &mut k.body else { continue };
|
||||
children.push(Chunk::data(*b"Utf8", text.as_bytes().to_vec()));
|
||||
} else if k.is_list(b"otky") {
|
||||
let mut otda = Vec::new();
|
||||
for v in [10.0f64, 20.0, 30.0] {
|
||||
otda.extend(v.to_be_bytes());
|
||||
}
|
||||
*k = Chunk::list(*b"otky", vec![Chunk::data(*b"otda", otda)]);
|
||||
}
|
||||
}
|
||||
patched += 1;
|
||||
});
|
||||
assert!(patched > 0, "the synthetic project has an Orientation");
|
||||
write(&file).unwrap()
|
||||
}
|
||||
|
||||
/// An expression on Orientation arrives with the value (both were dropped and reported "not
|
||||
/// decoded").
|
||||
#[test]
|
||||
fn an_expression_on_orientation_is_imported() {
|
||||
let (p, r) = import_all(&with_orientation_expression("[1, 2, 3]"));
|
||||
let mut seen = 0;
|
||||
for (_, c) in p.comps() {
|
||||
for l in &c.layers {
|
||||
let Some(o) = l.props.prop("transform/orientation") else { continue };
|
||||
assert_eq!(o.value, Value::Vec3([10.0, 20.0, 30.0]), "{}", l.name);
|
||||
assert_eq!(o.expr.as_ref().map(|e| (e.text.as_str(), e.enabled)), Some(("[1, 2, 3]", true)), "{}", l.name);
|
||||
seen += 1;
|
||||
}
|
||||
}
|
||||
assert!(seen > 0);
|
||||
let orientation: Vec<String> = r
|
||||
.report
|
||||
.losses
|
||||
.iter()
|
||||
.filter(|l| l.category == LossCategory::TransformProperties)
|
||||
.flat_map(|l| l.details.clone())
|
||||
.filter(|d| d.contains("Orientation"))
|
||||
.collect();
|
||||
assert!(orientation.is_empty(), "{orientation:?}");
|
||||
}
|
||||
Reference in New Issue
Block a user