mirror of
https://github.com/storytold/craft-libs.git
synced 2026-11-12 10:03:51 +00:00
* craft-aep: a flat read-only tree index, borrowed text documents, fewer allocations The container part of storytold/effectcraft#3302: - `TreeIndex`: the chunk tree as one flat array in depth-first order, borrowed from the input, for readers that only look. Same chunks counted against the same limits and the same errors as `parse`. An `.aepx` is indexed by `index_aepx` into one buffer. The typed view reads through either tree (`Node`, `Root`, `Project::from_index`). - Chunk parser: each list of siblings is counted, then reserved once. `.aepx`: hex decoded into an output sized once, attribute values without an entity borrowed. `write` reserves the exact output size. - Text documents are parsed into a tree that borrows from the body; the style and paragraph defaults are built once. - Layer markers and Source Text are read where they are stored instead of searched for. - Container accessors that only tests used are removed. What the importer and exporter in EffectCraft reach across the crate boundary is public here: `Root`, `Draft`, `AepxDrafts`, `index_aepx`, `TreeIndex::from_drafts`, `Project::of`, `ProjectSettings::of`, `Node::marker_list`, `Root::file_version` and `saved_by_version`, `StyleRun::SAMPLED`. `TimeBase::NTSC` holds the rates as plain numerator and denominator instead of an EffectCraft type. * craft-aep: one decoder for an effect parameter definition (`Pard`) The container part of storytold/effectcraft#3319: the fields of a `pard` record (type code, name, default, valid range, slider range, popup item count) are read by one type, `Pard`, which `EffectParam` now reads through. It is public so that an exporter's tests can read back what they encode instead of keeping a second decoder. The rest of that PR (import and export bounds, loss reports) is in EffectCraft's importer and exporter. --------- Co-authored-by: vangel76 <vangel1976@googlemail.com>
83 lines
3.1 KiB
Rust
83 lines
3.1 KiB
Rust
//! Track mattes (cases 402-408): mode and matte layer in the layer header.
|
|
|
|
use crate::container::helpers::{first_comp, open};
|
|
use craft_aep::{MatteMode, Project, TrackMatte};
|
|
|
|
/// `(name, video switch, track matte)` of every layer of comp `C`, top first.
|
|
fn layers(name: &str) -> Option<Vec<(String, bool, Option<TrackMatte>, u32)>> {
|
|
Some({
|
|
let bytes = crate::common::try_fixture(name)?;
|
|
let file = open(&bytes);
|
|
let project = Project::new(&file).expect("project");
|
|
let comp = first_comp(&project);
|
|
comp.layers()
|
|
.map(|l| {
|
|
let h = l.header().expect("header");
|
|
(l.name().or_else(|| project.layer_source(&l)?.display_name()).unwrap_or_default(), h.is_video_enabled(), l.track_matte(), h.id)
|
|
})
|
|
.collect()
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn the_four_matte_modes_and_the_matte_layer_id() {
|
|
for (case, mode) in [
|
|
("403-matte-L-alpha-M.aep", MatteMode::Alpha),
|
|
("404-matte-L-alphainv-M.aep", MatteMode::AlphaInverted),
|
|
("405-matte-L-luma-M.aep", MatteMode::Luma),
|
|
("406-matte-L-lumainv-M.aep", MatteMode::LumaInverted),
|
|
] {
|
|
let l = skip!(layers(case));
|
|
assert_eq!(l.len(), 2, "{case}");
|
|
let (m_name, m_video, m_matte, m_id) = &l[0];
|
|
let (l_name, l_video, l_matte, l_id) = &l[1];
|
|
assert_eq!((m_name.as_str(), l_name.as_str()), ("M", "L"), "{case}");
|
|
// The matte layer M has an id of its own (0x1d in the single-case files).
|
|
assert_ne!(m_id, l_id, "{case}");
|
|
assert_eq!(*m_matte, None, "{case}: the matte layer has no matte itself");
|
|
assert_eq!(*l_matte, Some(TrackMatte { mode, layer: Some(*m_id) }), "{case}");
|
|
// After Effects switches the matte layer's video off by itself.
|
|
assert!(!m_video, "{case}");
|
|
assert!(l_video, "{case}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_matte_layer_below_its_target() {
|
|
let l = skip!(layers("407-matte-M-alpha-L-below.aep"));
|
|
assert_eq!(l[0].2, Some(TrackMatte { mode: MatteMode::Alpha, layer: Some(l[1].3) }));
|
|
// The matte is L (0x1b in the single-case file).
|
|
assert_eq!(l[1].0, "L");
|
|
assert!(l[0].1, "M stays visible");
|
|
assert!(!l[1].1, "L, the matte, is switched off");
|
|
assert_eq!(l[1].2, None);
|
|
}
|
|
|
|
#[test]
|
|
fn a_matte_layer_whose_video_was_switched_back_on() {
|
|
let l = skip!(layers("408-matte-L-alpha-M-visible.aep"));
|
|
assert!(l[0].1, "M visible");
|
|
assert_eq!(l[1].2, Some(TrackMatte { mode: MatteMode::Alpha, layer: Some(l[0].3) }));
|
|
}
|
|
|
|
#[test]
|
|
fn no_layer_without_a_matte_reads_one() {
|
|
for name in crate::container::helpers::FIXTURES.iter().filter(|n| !n.contains("-matte-")) {
|
|
let bytes = fixture!(name);
|
|
let file = open(&bytes);
|
|
let Some(project) = Project::new(&file) else { continue };
|
|
for item in project.all_items() {
|
|
let Some(comp) = item.comp() else { continue };
|
|
for layer in comp.layers() {
|
|
assert_eq!(layer.track_matte(), None, "{name}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn modes_from_codes() {
|
|
assert_eq!(MatteMode::from_code(0), None);
|
|
assert_eq!(MatteMode::from_code(9), Some(MatteMode::Other(9)));
|
|
}
|