* Add craft-aep: After Effects project container and typed view from EffectCraft The .aep (RIFX 'Egg!') and .aepx container as a lossless chunk tree that writes back byte for byte, and the typed, read-only view over it: compositions, layers, properties, keyframes, effects and their settings data, masks, shapes, text documents, markers, cameras, track mattes, footage, project settings and the saving version. Moved from effectcraft crates/aep/src/container at f6b4da0a with its unit tests, the container integration tests, the aepdump and stubicc tools and docs/aep-format.md. The match-name tables (effect_map, style_map), the importer and the exporter are written against EffectCraft's model and stay there. ExtractorChannels::sources takes the caller's name for a copied channel instead of an EffectCraft constant. * craft-aep: read the Photoshop layer a footage item shows A footage item made from one layer of a .psd stores the layer in its `opti` (tag `8BPS`): the layer record's index (big-endian i32 at 14, -1 for the merged image), the document's two sides (little-endian u32 at 32 and 36), the layer's bounds (little-endian i32 at 78, 82, 86, 90) and its name (UTF-8 from 344). `Footage::psd_layer` reads them as `PsdLayer`. The format part of storytold/effectcraft#3449; the importer's use of it stays in EffectCraft. --------- Co-authored-by: arnav-hiwarkar <asharnav2008@gmail.com>
craft-aep
After Effects projects (.aep, .aepx): the container as a lossless chunk tree, and a typed,
read-only view of what it holds. Bytes in, a neutral model out, and the tree back to bytes.
Mapping the view onto an application's document (which effect becomes which, units, what is
reported as lost) stays in the application.
No file access: callers pass bytes, so the crate builds for wasm32. No dependencies.
| Form | Read | Write |
|---|---|---|
.aep (RIFX Egg!) |
chunk tree and typed view | the chunk tree, byte for byte |
.aepx (XML) |
into the same chunk tree | no |
Container
An .aep is a RIFX container (big-endian RIFF) with form type Egg!: chunks of a 4-byte id, a
big-endian u32 size and a body padded to an even length; LIST chunks start with a 4-byte list
type followed by chunks. parse builds the tree, write serialises it, and for every input that
parses, write(parse(x)) == x byte for byte (container and chunk sizes are recomputed from the
tree; pad bytes, odd bodies and the bytes after the container, an XMP packet, are kept).
The XML form of a project (.aepx) is read into the same tree by read_aepx (a small bounded
XML tokenizer of its own); parse_any picks the reader by content, so the typed view takes
either form. The mapping is in docs/aep-format.md, section 1.
Bodies that look like lists but are not chunk sequences (for example LIST btdk, raw text) stay
opaque rather than failing the parse. Input is untrusted: sizes are checked against the remaining
input, nesting and chunk count are bounded (Limits), nothing is allocated from a declared size,
and truncated or damaged files return Err (or, inside the container, an opaque body).
Typed view
The container layer decodes no chunk body. On top of it, a typed view (Project, Item, Comp,
Layer, Property, ...) reads the fields whose layout was verified against the values the test
files were made with: layer and composition headers (switches, exact times and time stretch, size,
frame rate, work area, shutter phase, display start, renderer), static transform values in After
Effects' units with the "absent = default" rule, keyframe records (interpolation sides, ease per
component, plain and spatial templates), Time Remapping, expressions with their enable switch,
effect instances and parameter values (with the settings data of Curves, Levels, Hue/Saturation,
Cryptomatte, EXtractoR, IDentifier and Puppet meshes), masks and bezier paths with tangents, shape
layer groups, text documents (paragraph and style runs, box, justification, tracking, faux styles,
caps, leading), markers with duration, strings and cue data, cameras and lights with their
options, track mattes, project settings and the working space's name, the version that saved the
file, footage (stills, audio, image sequences, the layer of a Photoshop file an item shows), solids
and precomp links. Every accessor returns
Option or an empty list on damaged data and never panics. Which fields are verified, which are
not decoded, and how it was found is in docs/aep-format.md.
Writing a project from an application's document (building every chunk in code) is not here: it is EffectCraft's exporter, which is written against EffectCraft's own model.
Specifications
Public specifications used for parts the format embeds:
- ICC.1:2010: the profile description of a working-space profile (a v2
descor a v4mluctag), from which the working space's name is read (settings.rs). - RFC 4648 (base64): the profile data in the project's colour settings JSON.
Clean room
Everything here comes from our own observations: project files that we generated ourselves with
After Effects, one change per file, compared byte by byte. No source code or format
documentation of another .aep reader or writer was used, and no Adobe asset is used.
Tests
The unit tests and the robustness tests build their input in code. The tests of the typed view
read generated projects (own test data): a few combined projects, one composition per case
(tests/fixtures/CASES.md). They are not in this repository but in
storytold/effectcraft-corpus, with their
attribution, the generation notes and the inspection of their embedded content.
tests/fixtures/corpus.lock pins the commit and lists the SHA-256 of every file used. To run
against them:
git clone https://github.com/storytold/effectcraft-corpus.git
git -C effectcraft-corpus checkout <commit from corpus.lock>
CRAFT_AEP_FIXTURES=$PWD/effectcraft-corpus/aep CRAFT_AEP_FIXTURES_REQUIRED=1 cargo test -p craft-aep
The tests reach the projects only through tests/it/common/mod.rs, which hands a test its case
as a project of its own cut from the combined file. Without CRAFT_AEP_FIXTURES such a test
prints SKIPPED: fixture <name> missing and passes; with CRAFT_AEP_FIXTURES_REQUIRED set, a
missing project fails it.
Tools
examples/aepdump.rs: prints the chunk tree of a file (ids, sizes, short data, match names):cargo run -p craft-aep --example aepdump -- file.aep.examples/stubicc.rs: replaces every embedded ICC profile of a saved project by a stub that keeps only the profile's description: the JSONcolorProfileDataof the working space in an.aepchunk or the.aepxtext, and a raw profile of an imported image:cargo run -p craft-aep --example stubicc -- in.aep out.aep. A profile it cannot replace stops it before anything is written. Use it before a generated project becomes test data, so no vendor colour data stays in it, and check the result independently.