* #3099: placed .ai pages fill the size After Effects stored; null anchors default to the corner
Footage of an .aep keeps the size After Effects stored for a placed PDF / Illustrator / EPS
file (the ArtBox, rounded up), and masks and anchors are measured against it. The page was
drawn as a PDF viewer frames it (CropBox, /Rotate) at its own size, scaled by the width ratio
at full resolution and stretched to the stored aspect below 3/4: masks too large, art off
centre, squeezed at half resolution.
- effectcraft-pdf: Framing::Placed / parse_page_framed (ArtBox clipped to the CropBox,
/Rotate ignored); boxes with a non-finite corner or span are not used
- Footage::placed_page (set by the .aep importer, serialized only when true): the page is
framed so and filled into the stored size (effectcraft_render::vector_fit), decoded,
continuously rasterised and in Create Shapes from Vector Layer; Reload keeps the size;
the frame cache keys it by size and framing
- .aep import/export: a null's absent Anchor Point is [0, 0], as After Effects reads it
- fixture ai-mask-3099.aep (own synthetic files, scrubbed, profiles stubbed) and tests
against After Effects 2026's read-back
* #3099: placed-page framing only for .ai and .pdf, EPS as before; doc comments
After Effects' sizing was measured with PDFs named .ai only; an EPS from an .aep keeps its
bounding box at its own size (listed as not measured in docs/gaps.md and docs/aep-import.md).
Doc comments on the touched functions.
* #3099: shapes from a placed page keep its frame (track matte)
Create Shapes from Vector Layer converted every path of a placed page, so bleed, art past the
artboard or the page's background showed where the footage's raster hides them. The layers it
makes from a placed page now take a hidden "<layer> Frame" shape layer (the footage's
width x height) as their Alpha track matte: drawn at the comp's resolution, its edge is the
raster's on shape and image layers alike. The command returns the frame layer's id too.
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
#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>
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>
* 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>
* 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>
* 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>
The EffectCraft value for the Shadows wheel's blue lift at black was read at input 0.05; at input 0 it is 0.17 (After Effects 0.13).
Part of #3111
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After Effects 2026 presets made by hand (1200 lumetri-58 ... -62) decode
the data the scripted samples could not set.
- RGB curve (-0039): the first of the four curves is the master curve
and arrives as curves/rgbCurves/master. A moved red, green or blue
curve is reported: no sample tells which slot is which channel.
- Hue vs Sat (-0042): y is the offset from neutral, 1 doubling the
saturation (in linear light), so EffectCraft's y is 0.5 + y / 2. The
other four hue / saturation curves stay reported when moved.
- Color Wheels (-0047): four triples of hue (degrees), brightness slider
(0.5 neutral) and distance. Shadows, Midtones and Highlights become
EffectCraft's wheel colours (HSL of that hue, saturation = distance,
lightness 0.5); the brightness sliders and the fourth wheel are
reported.
- Damaged curve or wheel data is reported as damaged.
- Legacy Tone And Saturation is reported when a record says on: its pard
default turns from off to on when the project is saved again (the
25.x / 24.x / 26 copies of 1200), so comparing with it missed it.
- The Input LUT and Look presets confirm -0095 / -0096 hold the names;
the LUT files are After Effects' own and are not embedded.
Evidence: the 62 read-backs still equal the imported values in the new
63-composition 1200 file. The version copies store the same Lumetri
definitions, ranges and values and give the same report. Renders: master
curve within 0.024 (After Effects uses a natural spline, EffectCraft's
Lumetri a monotone one), Hue vs Sat mean 0.008, wheels mean 0.042.
Part of #3111
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Presets of our own (blocks 1300 and 1310) show what the Channel Info stores for a chosen
channel.
- EXtractoR: each record is a kind in byte 0 and the channel name. Named channels are
imported by name (Z, Z, Z renders equal to After Effects) and `(copy)` as the input's
own channel. The kind is byte 0 only: one definition default has `02 00 00 32`.
- IDentifier: byte 19 holds the chosen channel's name, `Object ID` when none is chosen.
After Effects lists only UINT channels and leaves the layer unchanged without one, which
the loss now says. A chosen `ObjectID` / `MaterialID` channel (any case) is the one
EffectCraft reads itself, so it imports without a loss; other names are reported.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The importer now converts After Effects' Lumetri Color. Its parameters are
found by match-name suffix, not by parT index: After Effects inserted
-0127 ... -0130 mid-list and five tone sliders share one valid range.
- Values are read from every value record, also those in the default
state (tdsb 3), where Lumetri keeps High Dynamic Range, HDR Specular and
the second Look popup (EffectParam::recorded_value; stored_value is
unchanged).
- A switched-off section, and HSL Secondary without a picked key colour,
draws nothing in After Effects: its set values are reported in one line
and not imported.
- The HSL key data is decoded into hue / saturation / lightness ranges.
- LUTs and Looks (by name), controls without a counterpart, eyedropper
colours and undecoded curves, wheels and Split Toning are reported.
- EffectCraft's Lumetri sliders take After Effects' valid ranges.
- import_param is split into lookup plus import_mapped for reuse.
Evidence: 1200 samples (After Effects 2026). The 62 read-backs of mapped
controls in sections that act equal the imported values. The other 61
lie in sections that do nothing and are reported. Renders of identity
grades are within 1/255.
Part of #3111
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The importer reported these three plug-ins as lost effects. They now
become EffectCraft's effects of the same name (parameter lists and data
layouts from our own After Effects 2026 project, docs/aep-format.md 3.7):
- CS Color Neutralizer -> ec.color.cccolorneutralizer: Pinning and Blend
w. Original. After Effects' correction is the per-range Red / Green /
Blue sliders (the Unbalance colours are pickers that keep no record),
plus View and Black / White Point; EffectCraft's effect has none of
them, so each is reported when stored.
- EXtractoR -> ec.3d.extractor: Black / White Point, UnMult, and the
default Channel Info (R, G, B, (copy)) as EffectCraft's R, G, B, A
(ExtractorChannels). Other channels are reported: only the default
layout is sampled.
- IDentifier -> ec.3d.identifier: Display by item name, ID. Its Channel
Info names only the channel type (Object ID, identifier_channel_type),
not the channel After Effects reads; After Effects left the sampled EXR
unchanged where EffectCraft draws the footage's object ID channel, so
every instance is reported.
On the sample project no effect is lost any more (29 before); the
parameters above are reported instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Add After Effects project import with bounded parsing, explicit loss reports, and regression coverage.
Fetch the cleaned external test corpus at commit 97262276f625afdeeb61787e4cbb74b3f17af510 and verify each fixture against the pinned SHA-256 manifest.