Commit Graph
65 Commits
Author SHA1 Message Date
vangel76 3a9cb48259 Merge remote-tracking branch 'upstream/main' into aep-audit-3300 2026-11-08 18:29:49 +01:00
vangel76 a33fb4abf6 #3300: import .aepx through the flat index
The .aepx reader no longer builds the owned chunk tree element by
element. It decodes every body (hex, text, alias JSON, numbers) into
one buffer and records the chunks as depth-first entries whose bodies
are ranges of that buffer; TreeIndex::from_drafts turns them into the
index the importer reads, which borrows the buffer. read_aepx converts
that index into the owned tree in one pass.

The tree and the errors are those of the previous reader, which the
tests keep as the reference: generated and damaged documents under
seven limit sets, invalid UTF-8, hostile input, the fixture directory,
and imports through both. Bound tests pin the chunk list to the input
and the limit, and check that dropped content leaves nothing behind.
2026-11-08 18:19:12 +01:00
vangel76 5f2d88ea5c Merge remote-tracking branch 'upstream/main' into aep-import-lumetri
# Conflicts:
#	crates/aep/src/container/effect_map.rs
#	crates/aep/src/export/effects.rs
#	crates/aep/src/import/effects.rs
#	crates/aep/tests/it/export/effects.rs
#	crates/aep/tests/it/import/effects.rs
#	docs/aep-import.md
2026-11-08 16:32:43 +01:00
vangel76 5f4e7ffc18 Merge remote-tracking branch 'upstream/main' into aep-audit-3300 2026-11-08 16:21:58 +01:00
vangel76 2e24ae47a1 aep export: the time remap and Transform keys use the shared key-list and match-name helpers; doc comments for the audit's functions (#3300) 2026-11-08 16:21:57 +01:00
FredApps 07be19d402 Merge pull request #3255 from vangel76/aep-import-puppet-3093
AEP import: Puppet arrives as EffectCraft's Puppet, its meshes and pins are reported
2026-11-08 15:46:00 +01:00
FredApps 7ca249d085 Merge pull request #3120 from vangel76/aep-import-thirdparty
.aep import: map CC Color Neutralizer, EXtractoR and IDentifier
2026-11-08 15:45:08 +01:00
FredApps 5832219d76 Merge pull request #3248 from vangel76/text-layer-name-follow-3233
Text layers: the name follows the text until the user renames the layer
2026-11-08 15:44:58 +01:00
FredApps 362b39038f Merge pull request #3256 from vangel76/clamp-time-stretch-3247
Time stretch: hold a loaded or imported stretch to ±1 % to ±9900 %
2026-11-08 15:44:45 +01:00
vangel76 b80ac9edb9 aep: read imports through a flat tree index (#3300)
Import reads an .aep through TreeIndex, one depth-first array of chunks that
borrows the file's bytes, instead of building and freeing the owned Chunk tree
(one Vec per list). The parser mirrors read::Parser step for step: the same
chunk count order against max_chunks, the same depth limit and opaque
fallbacks, the same errors. The typed view reads through Node, a handle to a
chunk of either tree, so the accessors work unchanged on both; .aepx keeps its
owned tree. Chunk, parse and write stay for export and editing.

The array starts small and doubles, never past what the input can hold or the
chunk limit allows; a failed allocation is TooLarge, a tree past u32 indices
TooManyChunks.
2026-11-08 15:00:52 +01:00
vangel76 d2ca544404 Merge remote-tracking branch 'origin/aep-cleanup-3281' into aep-audit-3300 2026-11-08 14:23:06 +01:00
vangel76 a7456fac62 Merge remote-tracking branch 'origin/aep-export-dedup-3278' into aep-audit-3300 2026-11-08 14:23:06 +01:00
vangel76 47d988f658 Merge remote-tracking branch 'origin/aep-export-template-3277' into aep-audit-3300 2026-11-08 14:23:06 +01:00
vangel76 2428b8091e Merge remote-tracking branch 'origin/aep-textdoc-3276' into aep-tree-index-3300 2026-11-08 14:15:51 +01:00
vangel76 d6ec32cbaa aep export: correct the order the natural sort key doc gives for equal numbers (#3277) 2026-11-08 06:19:17 +01:00
vangel76 c8ccf807e1 #3233: AEP import names an unnamed text layer as a following name is cut, so export and import agree 2026-11-08 04:53:01 +01:00
vangel76 e41d6d9d68 Merge remote-tracking branch 'upstream/main' into text-layer-name-follow-3233 2026-11-08 04:42:27 +01:00
vangel76 d2359d607e aep export: the every-layout test counts from a baseline, not from zero (#3278) 2026-11-08 04:41:46 +01:00
vangel76 245e9b6844 aep container: check bdata digits and size before reserving (#3275)
decode_hex reserved the decoded size before it knew the value decodes,
so a large bdata with a bad digit reserved half its length first. It now
refuses a value larger than a chunk's 32-bit size field (TooLarge), checks
every digit in one vectorisable pass, and only then reserves, with
try_reserve_exact so a value that does not fit in memory is an error
instead of an abort.
2026-11-08 04:36:37 +01:00
vangel76 8439daea0b aep export: sort keys as one compact byte string, bounded by the name (#3277)
The cached natural keys were Vecs of (bool, String, u128) segments: a 255-byte
name of alternating letters and digits took about 14 KB, held for every item at
once. Each key is now one byte string whose byte order is the natural order,
at most 5 bytes per name byte. Order and output are unchanged.
2026-11-08 04:36:18 +01:00
vangel76 66cbb9d89e aep: review fixes for #3281: NTSC table beside TimeBase, real-path NTSC round trip, light and camera defaults on import, doc wording 2026-11-08 04:34:39 +01:00
vangel76 9e2461414a aep: remove test-only container accessors, share report and time-base code (#3281)
- Remove 35 public container accessors that only tests called (header,
  camera/light, keys, footage, masks, shapes, version) and the dead
  StyleMap::switch_name. Tests now check the importer's own reading of the
  same bytes, or the decoder it uses; three import tests gain the values
  the removed checks covered.
- Import and export reports share one loss entry (Loss<C>) with record,
  total, count and summary; the JSON shape is pinned for both.
- Collector::lose_with builds a detail only below the detail cap (callers
  switch later).
- The export uses the container's TimeBase; one NTSC table serves import
  and export; TimeBase::frames saturates like units instead of returning 0.
2026-11-08 04:04:49 +01:00
vangel76 30a6e571c9 aep: review fixes for #3276 text documents and markers
text_document reads the first btdk in the Source Text record, the documents
text_document_count counts. The old slot checks (same, is_neutral, undecoded_fields)
join the test oracle and are compared on 20 000 generated slot dictionaries; mixed
dictionary/array chains and a document left open at the depth limit join the parser
differential. An escaped string's byte order mark is drained without a copy.
2026-11-08 03:58:10 +01:00
vangel76 9e82a5e704 aep export: review fixes for #3278
- tree::Fixed replaces tree::fixed: a fixed layout writes every field that
  fits (as the old code did) and keeps the first error; finish() logs it
  once with the layout's description and counts it per thread, so the
  every-layout test cannot race with other tests.
- base::bytes logs one line naming its length and fields.
- masks::build_atom errors when the prototype has no Mask Shape instead of
  dropping the path of every mask quietly.
- log sorted into the dependency list.
2026-11-08 03:56:50 +01:00
vangel76 a4f9a97525 aep export: pin the key-time loop's report, name the layout in the base::bytes log (#3278) 2026-11-08 03:43:04 +01:00
vangel76 5f37f78117 aep export: one key-time loop, key-list rewrite, tdmn decoder and byte writer for the listed modules (#3278)
In masks, shapes, styles, synth, fxsynth, base, camlight, skeleton,
textanim and tree (effects.rs, plan.rs and emit.rs keep their copies for
the follow-up work):
- New export/keys.rs: the key-time loop (timed_keys), the key-list rewrite
  (fill_key_list, set_key_list, replace_cdat) and the spatial key record,
  shared by masks, shapes and styles.
- One tdmn decoder (synth::tdmn_name, lossy UTF-8 up to the first NUL, as the
  importer reads it) and one encoder (synth::tdmn) instead of the copies in
  masks, styles, shapes, textanim, camlight and skeleton.
- One byte writer: tree::put, which errors. base::bytes, synth::tdb4,
  synth::key_list, fxsynth::pard, masks::shph and the keyed path records write
  through it; their fixed layouts keep the infallible signatures behind
  tree::fixed, which logs, and a test builds every layout and requires no error.
- camlight::put_group uses the shared group split (tree::split_group), which
  errors on a match name without a node instead of stopping quietly.

Export output is byte-identical on the corpus and the synthetic fixtures.
2026-11-08 03:43:01 +01:00
vangel76 6c84c81060 aep: an escaped text string drops its byte order mark without a range drain (#3276) 2026-11-08 03:42:48 +01:00
vangel76 34715bbd86 aep: text-dict test builds its prefix without repeat(1) (#3276) 2026-11-08 03:37:19 +01:00
vangel76 32c79babc8 aep: text documents are read without a String per token, defaults built once (#3276)
TextDocument::parse reads a borrowed tree: names, bare words and strings without
escapes point into the body, keys are borrowed when they are /names, numbers parse from
the borrowed bytes, and dictionaries and arrays are moved into vectors of their exact size
when they close. The style and paragraph defaults are built once instead of per document.
parse_text_dict keeps its public TextValue result (the borrowed tree turned owned).

The old owning parser stays as a test-only oracle: a differential test compares both on
40 000 generated bodies (every key form the owned keys canonicalise, non-UTF-8 names,
escapes, unclosed strings, non-finite numbers), every cut of a realistic document and
nesting around the depth limit.
2026-11-08 03:27:12 +01:00
vangel76 cdb81c06ec aep export: note what the concurrency test covers and why a failed template build is not kept (#3277) 2026-11-08 03:26:16 +01:00
vangel76 efa0c94d2b aep container: review fixes (#3275)
- read.rs: reserve with try_reserve_exact, so a raised chunk limit on a
  small target cannot abort before parsing; the capped reservation and a
  deep, retry-heavy nest of broken lists are pinned by tests.
- write.rs: a tree past the 32-bit container size fails with TooLarge
  before anything is serialised.
- aepx.rs: drop an unneeded local and lifetime annotation.
2026-11-08 03:24:05 +01:00
vangel76 38d51dd185 aep container: size sibling lists, hex and write output once; borrow plain .aepx attributes (#3275)
- read.rs: count the chunk headers of a sequence first and allocate the
  children Vec once (bounded by the input and by the chunk limit).
- aepx.rs: decode_hex sizes its output once; attribute values without
  an entity are borrowed instead of copied through decode_entities.
- typed.rs: PropGroup::get compares match names without building a
  String (same result as the lossy compare, invalid UTF-8 included).
- write.rs: reserve the exact output size up front (try_reserve, so a
  hand-built tree cannot abort on the allocation).
- read.rs, settings.rs, values.rs: use the shared bytes.rs readers.

Output is identical on the corpus (tree, written bytes, import).
2026-11-08 03:17:17 +01:00
vangel76 8bf184a2d4 aep export: build the prototype template once, sort names with cached keys (#3277)
The template every export clones from is immutable; it is now built by the
first export of the process and kept (about 0.93 MB). The name sort builds
each natural key once instead of on every comparison. Root solids move into
the Solids folder instead of being copied. The two key-list rewrites share one
emit-local helper and match names compare as bytes instead of a decoded String.
Output is byte-identical.
2026-11-08 03:15:24 +01:00
vangel76 7694c11170 aep: a layer's markers and Source Text are read where they are stored, not searched for (#3276)
A layer's marker list is a direct child of its root tdgp, and its Source Text record
btds a direct child of the ADBE Text Properties group. Markers walked the whole layer
subtree twice per layer (most layers have no marker list, so the walk always ran to the
end), and the three text-document accessors walked it once each.
2026-11-08 03:10:16 +01:00
vangel76 25ae9ad266 Merge remote-tracking branch 'upstream/main' into aep-import-puppet-3093
# Conflicts:
#	docs/aep-format.md
2026-11-08 02:39:14 +01:00
vangel76 3c08eb0647 Merge remote-tracking branch 'upstream/main' into aep-import-lumetri
# Conflicts:
#	crates/aep/src/import/effects.rs
2026-11-08 02:32:59 +01:00
vangel76 b90950c6a2 .aep import: a Channel Info record without its data is reported as damaged
import_extra returned as soon as the arbitrary data was missing, so an
EXtractoR or IDentifier value record whose aRbs/aRbp payload is gone was
silently imported with the default channels; the payload may have chosen
other ones (Z). As for Lumetri's data parameters, a stored record without its
payload now adds a DamagedData loss; a parameter with no record stays the
silent default. The test now asserts the report.
2026-11-08 02:32:56 +01:00
vangel76 67a39d9a2b .aep import: EXtractoR (copy) reads the effect input, not the EXR plane of that name
After Effects' Channel Info kind 2 "(copy)" was imported as R, G, B or A.
EffectCraft's EXtractoR resolves those names through the footage's aux planes
first, and an OpenEXR keeps its own R/G/B/A planes, so "(copy)" read the
original file plane and dropped what an earlier effect did to the channel.

"(copy)" is now a reserved EXtractoR channel name (EXTRACTOR_COPY) that the
CPU and GPU renderers never look up in the aux planes: it is the input's own
channel of the same position. The importer writes it for kind 2.

Render regression: a modified input buffer with same-named R/G/B/A aux planes
of other values; GPU parity case with (copy) on an aux scene with R/G/B.
2026-11-08 02:32:11 +01:00
vangel76 8af16ac7a4 Merge remote-tracking branch 'upstream/main' into aep-import-thirdparty 2026-11-08 02:19:29 +01:00
Valentin Klein 514e16c027 .aep import: effects without definitions borrow them from another instance (#3223)
Refs #3170 (the Color Control part).

Some files have no project-level effect definition list (EfdG); it was seen in
17 projects, all from inside After Effects-made Motion Graphics templates. In
those, one instance per effect type keeps the full parameter definitions
(parT) and every other instance of the type has an empty parT. The others
were reported "without readable parameter definitions" and dropped (129
Color Controls of one template). The definition index now falls back to the
parT of the first instance of the same match name that has its own
(DefinitionSource::OtherInstance). Values still come from each instance's own
records; a value record that borrowed definitions do not name is reported
instead of vanishing.

The "Color ... holds an unreadable value" report came from 0.21.1, which read
only written records (tdsb 1). Reading default-state records (tdsb 3, #3126)
probably fixes it, but that is not confirmed for Color Control; a synthetic
test now pins a default-state colour record.
2026-11-08 01:57:47 +01:00
Valentin Klein 99e24f5867 .aep import: image sequences without a filename prefix or with Force alphabetical order (#3213)
* AEP import: read image sequences without a filename prefix and with Force alphabetical order (#3174)

After Effects leaves out an empty filename prefix, so only one Utf8 (the
suffix, such as `.png`) follows the alias. The single part is now placed by
the first file name stored in `opti`: a name starting with it has it as the
prefix, one ending with it as the suffix; without that evidence a part
starting with `.` is the suffix and anything else is not read.

A sequence imported with Force alphabetical order stores its files in
`LIST StVc` (`StVS` with the count as a little-endian u32, then one Utf8 per
file name) instead of prefix and suffix; that list now replaces the
numbering. A list with a name that is not a plain file name (a separator, a
colon, `.` or `..`) is not read, since the names are joined to the folder.

A folder alias whose naming still cannot be read is reported as an import
loss instead of silently linking the folder. docs/aep-format.md describes
the empty prefix and StVc/StVS.

* AEP import: a listed sequence file name with a control character such as NUL is not read (#3174)

A NUL or another control character in a Force alphabetical order file name
now makes the whole list unreadable, like a separator or a dot directory, so
the import reports the naming instead of linking files that cannot exist.

* AEP import: a sequence file list without a readable count is not read (#3174)

After Effects writes the StVS count before the names. A StVc list without
StVS, or with one shorter than its u32, is now reported as unreadable naming
instead of taking every listed name.

* Doc comments for the functions this PR touches
2026-11-08 01:33:26 +01:00
vangel76 5f73e5b5e9 Time stretch: hold a loaded or imported stretch to ±1 % to ±9900 %
A project file, an .aep, a timeline document or a Lottie file could give a
layer a time stretch of 1e-300 %, 0 or 1e300 %. Layer and comp time divide
by it and the audio mix read about usize::MAX source samples for one block,
which overflowed or tried a huge allocation and aborted the app.

- limits::stretch brings a stretch within After Effects' range (±1 % to
  ±9900 %, Layer.stretch in the scripting guide), sign kept; 0 and NaN are
  100 %. Opening a project file corrects each layer and says so; a stretch
  written as null (NaN in an auto-save) now opens instead of failing.
- .aep import and Lottie import clamp and report the stretch; timeline
  import clamps the clip speed and warns.
- layer.timeStretch refuses a non-number and more than 9900 %; the Time
  Stretch dialog's ranges match.
- Layer::layer_time / comp_time and the audio mix use the clamped stretch,
  so a value set in memory can't divide by zero or read without bound.

Fixes #3247
2026-11-07 17:03:59 +01:00
vangel76 2abd727338 AEP import: Puppet (ADBE FreePin3) arrives as EffectCraft's Puppet, its meshes and pins are reported
After Effects' Puppet was dropped with a one-line "has the effect" loss. It now
arrives as EffectCraft's Puppet (ec.distort.puppet) in its place in the stack,
with its name and fx switch, and the report lists per mesh Triangles, Expansion
and per pin group (Deform, Overlap, Stiffness) the pins, keyed pins and atom
match names; entries outside the known tree are named once, and long lists stop
at ten with "and N more".

No pins are created: a pin's rest place is a vertex of the hidden mesh data and
the unit of its Position is unknown, and no corpus file holds a Puppet. The tree
(docs/aep-format.md 3.7) comes from an external scripting listing, not the
corpus; the sample that settles it is described there. The fx_inject test
helpers can now inject any effect instance, which the new tests use.

Refs #3093
2026-11-07 16:58:23 +01:00
vangel76 61af9e5b72 #3233: a text layer's name follows its text until the user renames it
A text layer's name followed its Source Text whenever the name started with "Text", so a user's
"Text Title" was replaced by the next text edit. The layer now records whether its name follows
the text (`name_follows_text`, optional in the project file). A new text layer named after its
text follows it; a name given at creation or by Rename (Enter, layer.rename, a script's
layer.name, Layer Settings' name) is the user's and stays; renaming to an empty name shows the
text again and follows it; committing the unchanged name changes nothing; copies keep the state.
On-canvas typing and layer.setText share the rule. The name is the text's first paragraph, which
ends at a line feed or a bare carriage return (After Effects' paragraph break).

Projects saved without the state keep the old rule for the names that followed: a name that is
empty, the text's own, or "Text"/"Text 2"... follows; any other name stays. AEP import records a
stored layer name as the user's and no name as following, and names an empty text layer without
a name "Text" instead of "Layer"; export writes no name for a following layer and keeps the name
the user gave, even when it equals the text.

Fixes #3233
2026-11-07 15:41:14 +01:00
vangel76andClaude Opus 5.5 4e9fcee1c5 Merge upstream/main into aep-import-thirdparty
#3119 (the base of this branch) is merged. Conflicts with the Cryptomatte
import (#3109) and the version report: Extra keeps Extractor, Identifier
and Cryptomatte, arbitrary.rs keeps ExtractorChannels /
identifier_channel_type next to CryptomatteData, the effect table counts
66, the docs keep both sides. The exporter from #2594 gets the Extractor
and Identifier arms: like Lumetri they have no prototype, so CC Color
Neutralizer, EXtractoR and IDentifier are reported, not written (new
test imported_plug_in_effects_are_reported_not_written).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 23:11:22 +01:00
vangel76andClaude Opus 5.5 53c501bde9 Merge upstream/main into aep-import-lumetri
Conflicts with the Cryptomatte import (#3109), the quick wins (#3119) and
the version report: Extra keeps both Lumetri and Cryptomatte (the exporter
reports both, writes neither), the Lumetri mapping gets ParamMap's new
limit (None), the effect table counts 64, the docs keep both sections.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 23:09:17 +01:00
ca1ff9cfc2 .aep import: map the Cryptomatte plug-in to EffectCraft's Cryptomatte (#3109)
* AEP import: map the Cryptomatte plug-in to EffectCraft's Cryptomatte

The importer reported every instance of the free Cryptomatte plug-in
(match name `Cryptomatte`) as a lost effect. It now becomes
`ec.3d.cryptomatte`:

- CryptomatteData decodes the plug-in's Settings data (its own
  little-endian layout, documented in docs/aep-format.md 3.7): the
  Cryptomatte layer name and the selection text, both carried verbatim.
- Output maps to Display / Matte Only (Colors -> Colors, Matted RGBA ->
  Matte, Matte Only -> Matte Only). Matted Colors, Selection Mode when on,
  keys or expressions and anything else stored are reported, and
  unreadable data is reported as damaged.
- EffectCraft's Cryptomatte gains a Layer Name text parameter. A layer the
  footage has under exactly that name wins; otherwise the Layer popup
  decides as before. The CPU, GPU and pick paths share one resolver
  (AuxChannels::resolve_crypto_layer_named). Without it, plug-in layer
  names such as Crypto_Material would silently fall back to the first
  layer.

On a real production project this takes the effect losses from 132 to 44.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* AEP import: check Cryptomatte against an After Effects 2026 sample of our own

A sample made in After Effects 2026 (plug-in 2.6.6: a fresh effect,
Selection Mode on, each Output item, with read-backs and renders)
corrected three points of the mapping:

- Selection Mode on was dropped silently: After Effects writes it as a
  default-state record (tdsb 3) holding 1, which the general reader does
  not take. The Cryptomatte step reads that record itself and reports it.
- No Output record is now the plug-in's default Colors, also for an
  instance that borrows the project's definition (every instance after
  the first), which was reported as having no reliable default and fell
  back to EffectCraft's Preview. Keyed or unreadable Output values fall
  back to Colors too, still reported.
- Matted Colors imports as Display Matte (the same matte over the layer's
  own colours) instead of Preview, still reported as an approximation.

docs/aep-format.md 3.7 gets the sampled Output default, the tdsb 3
Selection Mode record and the real fresh Settings data (an empty text of
length 1 and its MurmurHash3, twice, not cry1 and zeros); docs/aep-import.md
the render comparison with After Effects (Matted RGBA matches; Matte Only
differs in alpha; Colors with no layer chosen shows another layer).
Tests: the exact fresh Settings bytes, tdsb 3 Selection Mode, borrowed
definitions, and an opt-in test against the sample once it is in the
corpus (aep/cryptomatte.aep, ignored until the corpus pin includes it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* AEP import: write Cryptomatte's popup items in the test as After Effects does

The injected Cryptomatte definition wrote `pdnm` and `fnam` as `LIST`
chunks. After Effects writes them as plain chunks whose body is chunks
(`NESTED_IDS`), and the reader only looks there, so the tests never read
Output's item names: they passed through the fallback for a definition
with four items and no `pdnm`, which uses the plug-in's own list.

The harness now writes both as After Effects does. New tests check that
the injected definition reads back with its items and instance name,
that Output is mapped by the item the stored index names (items in
another order, case, an unknown item), and that the fallback without a
`pdnm` still works. With the old `LIST` form, or with the mapping
ignoring the items, the new tests fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* AEP import: check Cryptomatte's layer and selection against presets of our own

The After Effects 2026 sample 1400 now has two preset compositions that choose a layer and
pick one object (box_red on CryptoObject) or one material (mat_paint on CryptoMaterial).
CryptomatteData reads both as they are; the picked tokens are the EXR manifest's hashes,
which EffectCraft's selection grammar reads the same as the names.

- The u32 at 108 is MurmurHash3 of the selection text with its NUL (three texts).
- A unit test pins the 127-byte preset data; the sample test now covers all eight
  compositions with their layer and selection.
- Render check: EffectCraft's matte covers exactly the pixels After Effects draws as
  selected, edge coverage within 0.0002. docs/aep-format.md 3.7 and docs/aep-import.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Cryptomatte import: report damaged Settings and Selection Mode records

Review follow-ups for #3109:
- CryptomatteData refuses a selection text without its NUL and checks the zero byte
  at 112 for an empty text too.
- A Settings record without its data, and a Selection Mode record whose value cannot
  be read, are reported as damaged data, not taken as the default.
- Layer Name is the effect's last parameter, so the earlier parameters keep their
  indexes and upgraded instances have the same order as new ones.
- A Cryptomatte layer name is matched as given before it is trimmed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Cryptomatte settings: require the selection's NUL to end its declared length

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Cryptomatte: match rank planes by the exact layer name

crypto_rank_planes looked planes up with a case-insensitive whole-name match, so
layers Crypto_A and crypto_a shared planes. The layer prefix now matches
case-sensitively; component letters still match in either case.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Format

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* AEP: the exporter knows Extra::Cryptomatte; rustfmt

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-06 19:36:42 +01:00
Valentin KleinandClaude Opus 5.5 93cce09f3c .aep import: name the After Effects version in the report; 25.x and 24.x copies import like 2026 (#3136)
* AEP import: the report names the After Effects version of the file (#3115)

svap holds the version of the After Effects that wrote a project, head
bytes 4 to 7 the version the file is written for; Save a Copy As 25.x /
24.x of After Effects 2026 keeps svap and writes the older version into
head. AeVersion decodes the packed word (26.2.1x2), AepFile gains
file_version, saved_by_version and head_revision, and ImportReport gains
aeVersion (fileVersion, savedBy, tested; "unknown" when the file does not
say). A version outside 24 to 26 is imported as usual and gets a
limitation line, not a loss.

The 12 corpus families saved as 2026, 25.x and 24.x copies import to the
same project and report; docs/aep-format.md lists what the copies change
in the container. The fixture tests are ignored until the corpus holds
the files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* AEP import: doc comments on the version tests and helpers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* AEP import docs: tested is a major-version classification from copies saved by After Effects 2026

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* AEP import docs: the importer reports detected losses, not every loss

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* aep tests: compare the imported settings across version copies too

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 17:36:41 +01:00
Valentin KleinandClaude Opus 5.5 ab3674cc2b .aep import: map HDR Highlight Compression, Echo, Linear Wipe, Offset and Color Stabilizer (#3119)
* AEP import: map HDR Highlight Compression, Echo, Linear Wipe, Offset and Color Stabilizer

The importer reported these five effects as lost. They now become
EffectCraft's effects of the same name with every parameter After Effects
stores (ranges, defaults and popup items from our own After Effects 2026
project, documented in docs/aep-format.md 3.7):

- ADBE HDR ToneMap -> ec.utility.hdrcompression (Amount)
- ADBE Echo -> ec.time.echo (Echo Time, Number Of Echoes, Starting
  Intensity, Decay, Echo Operator by item name)
- ADBE Linear Wipe -> ec.transition.linearwipe
- ADBE Offset -> ec.distort.offset (Blend With Original is a stored 0..1
  fraction, a percentage here)
- ADBE Deflicker -> ec.color.colorstabilizer (fresh Stabilize default
  Levels, points, Sample Size)

A table row can name the range EffectCraft's effect works in
(ParamMap::limit): Echo values or keys beyond 255 echoes or a Decay of 1
are imported as stored and reported.

Tests build each effect into the committed synthetic.aep (fx_inject.rs,
which also checks its pard layout reads back). On the sample project the
effect losses go from 75 to 3 (the out-of-range Echo values).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* AEP import: doc comments on the quick-win test helpers and tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* AEP import: Echo keyframe regression for the range loss

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 17:36:34 +01:00
vangel76 924c74f436 Merge remote-tracking branch 'upstream/main' into aep-import-lumetri
# Conflicts:
#	docs/file-format-parity.md
2026-11-06 10:59:50 +01:00