mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-15 06:42:31 +00:00
Merge upstream/main into effect-controls-lock-3193
This commit is contained in:
commit
b53b18366d
687 files changed
+48550
-39590
No files matched your search
+4
-4
@@ -1,8 +1,8 @@
|
||||
# After Effects project export
|
||||
|
||||
> **Last reviewed:** 2026-10-19 · **Last updated:** 2026-11-05 · **Change:** a 3D layer's changed Material Options are reported, shape and text animator colours outside 0 to 1 are clamped and reported, and a popup expression is no longer reported where the option order is the same (2026-11-05); before that: composition settings the base writes at their defaults (Preserve Frame Rate / Resolution, motion blur samples, adaptive sample limit) and render queue items are reported, and a sequence's folder time is read only when its file passes the document-path policy (2026-11-04); before that: every chunk is built in code, no template is read (2026-10-29); before that: Curves and Mesh Warp are written (R53); before that: a switched-off shape item or layer style is written switched off (R52, 986-988); before that: a reversed layer (negative stretch) is written as After Effects stores it (R36, 981-983); before that: an explicit None (`NO_LAYER`) on Compound Blur, Displacement Map or Set Matte is written as None, and Layer Control on a named layer no longer reports a source choice; before that: layer styles are written (R51, 950-980; read back in After Effects: `export-tests\styles`); before that: an image sequence's folder modification time is written when the caller knows it (`sspc@118`, R50: without it After Effects rescans the folder on open and warns about every gap of a sparse sequence in a modal dialog); before that: an output space equal to the working space is not reported (After Effects renders in the working space, 902; the importer sets it so for Rec.709 Gamma 2.4); before that: one effect table for both directions again: the 27 effects of the importer round 3 and the Layer Control are written too (prototypes from 903 and 076, layer parameters by layer id; not read back in After Effects yet); before that: the importer round 3 is merged: Offset Paths' Copy Offset is written as EffectCraft's value + 1 (826-828, the importer's inverse); before that: sparse sequences checked (860, written as After Effects stores them); before that: the Rec.709 Gamma 2.4 working space is written with a profile authored here (R49, g5); before that: text animators with range selectors are written (R48, t8); before that: a footage layer running past its source is ended there on import, as After Effects does, and less than a frame past is not a loss (LayerTiming); before that: Anchor Point and mask path ease are written (R47); before that: shape operators (polystar, gradients, trim paths, round corners, merge / offset paths, repeater) and keys and expressions on shape properties are written (R46, o2); before that: every effect of the importer's table is written, through one shared table and records built per parameter type (R45, e2); before that: time remapping is written (319, 354, r1, R44); before that: the character and paragraph settings are read back in After Effects (t6) and right-to-left justification is written relative to the direction (t7, R43); before that: the character and paragraph settings the importer decodes are written (scales, baseline shift, kerning, ligatures, tsume, fill / stroke switches and width, justified variants, indents, spacing, composer, direction); before that: track mattes, markers, cameras, lights and shape layers (R39-R42), keys on stretched layers, conformed clips, tga / psd / ai / eps, project settings (620-634, R37), superscript / subscript (526-528); before: precomposition layers (124, 257, 258, 600-602), the effects Tint, Gradient Ramp and Exposure (356-397, 610), footage (351, 353, 603-607, 611-613), text layers (110, 245, t1-t4), the 3D switch (015) and time stretch (018, 316, 317) are exported · **Target:** Adobe After Effects 2026
|
||||
> **Last reviewed:** 2026-10-19 · **Last updated:** 2026-11-08 · **Change:** the Source Text expression and a text animator's Stroke Opacity are written (#3170); before that: a 3D layer's changed Material Options are reported, shape and text animator colours outside 0 to 1 are clamped and reported, and a popup expression is no longer reported where the option order is the same (2026-11-05); before that: composition settings the base writes at their defaults (Preserve Frame Rate / Resolution, motion blur samples, adaptive sample limit) and render queue items are reported, and a sequence's folder time is read only when its file passes the document-path policy (2026-11-04); before that: every chunk is built in code, no template is read (2026-10-29); before that: Curves and Mesh Warp are written (R53); before that: a switched-off shape item or layer style is written switched off (R52, 986-988); before that: a reversed layer (negative stretch) is written as After Effects stores it (R36, 981-983); before that: an explicit None (`NO_LAYER`) on Compound Blur, Displacement Map or Set Matte is written as None, and Layer Control on a named layer no longer reports a source choice; before that: layer styles are written (R51, 950-980; read back in After Effects: `export-tests\styles`); before that: an image sequence's folder modification time is written when the caller knows it (`sspc@118`, R50: without it After Effects rescans the folder on open and warns about every gap of a sparse sequence in a modal dialog); before that: an output space equal to the working space is not reported (After Effects renders in the working space, 902; the importer sets it so for Rec.709 Gamma 2.4); before that: one effect table for both directions again: the 27 effects of the importer round 3 and the Layer Control are written too (prototypes from 903 and 076, layer parameters by layer id; not read back in After Effects yet); before that: the importer round 3 is merged: Offset Paths' Copy Offset is written as EffectCraft's value + 1 (826-828, the importer's inverse); before that: sparse sequences checked (860, written as After Effects stores them); before that: the Rec.709 Gamma 2.4 working space is written with a profile authored here (R49, g5); before that: text animators with range selectors are written (R48, t8); before that: a footage layer running past its source is ended there on import, as After Effects does, and less than a frame past is not a loss (LayerTiming); before that: Anchor Point and mask path ease are written (R47); before that: shape operators (polystar, gradients, trim paths, round corners, merge / offset paths, repeater) and keys and expressions on shape properties are written (R46, o2); before that: every effect of the importer's table is written, through one shared table and records built per parameter type (R45, e2); before that: time remapping is written (319, 354, r1, R44); before that: the character and paragraph settings are read back in After Effects (t6) and right-to-left justification is written relative to the direction (t7, R43); before that: the character and paragraph settings the importer decodes are written (scales, baseline shift, kerning, ligatures, tsume, fill / stroke switches and width, justified variants, indents, spacing, composer, direction); before that: track mattes, markers, cameras, lights and shape layers (R39-R42), keys on stretched layers, conformed clips, tga / psd / ai / eps, project settings (620-634, R37), superscript / subscript (526-528); before: precomposition layers (124, 257, 258, 600-602), the effects Tint, Gradient Ramp and Exposure (356-397, 610), footage (351, 353, 603-607, 611-613), text layers (110, 245, t1-t4), the 3D switch (015) and time stretch (018, 316, 317) are exported · **Target:** Adobe After Effects 2026
|
||||
|
||||
File ▸ Export ▸ After Effects Project… (`file.exportAep`) writes the open project as an `.aep` that
|
||||
File ▸ Export ▸ Adobe After Effects Project… (`file.exportAep`) writes the open project as an `.aep` that
|
||||
After Effects 2026 opens. It is deliberately narrow: what it writes is exact, tested and checked
|
||||
against After Effects itself; everything else is **reported** and never dropped silently. The
|
||||
importer ([aep-import.md](aep-import.md)) is the mirror image; both rest on the clean-room format
|
||||
@@ -67,7 +67,7 @@ the dry run `analyze(&Project) -> ExportReport`. A refused or failed export writ
|
||||
| Expressions | on those five properties: source text and enable switch |
|
||||
| Masks | on solid, adjustment and null layers, in stack order: name, mode (all seven), inverted, path (vertices and tangents in layer pixels, open or closed), feather, opacity, expansion (each only when not at the default), path keys (time, linear, bezier or hold sides with their ease, label) with one shape per key, Mask Feather keys (time, interpolation, value, ease per dimension, label) |
|
||||
| Precompositions | a composition shown as a layer of another: the layer's source is the composition item, the using composition lists each composition it shows once (`idpi`); the anchor is a fraction of the composition's size; precompositions nest to any depth, a loop is reported |
|
||||
| Text | point and box text: the text (paragraphs, CR / LF / CRLF as After Effects' CR), per paragraph the justification (left, right, centre and the four justified variants, codes 3-6), first-line / start / end indent, space before / after, hanging punctuation, composer and direction (paragraph slots `/1`-`/5`, `/21`, `/29`, `/33`), per style run the font (PostScript name), size, fill and stroke colour, tracking, automatic or explicit leading, faux bold and italic, all caps and small caps, horizontal / vertical scale, baseline shift, automatic kerning (metrics, optical, none), ligatures, tsume, whether fill and stroke are drawn, stroke width and stroke over fill (style slots `/6`, `/7`, `/9`, `/11`, `/18`, `/36`, `/56`-`/58`, `/63`, as the importer decodes them, 538-569; written only where they differ from the prototype; read back in After Effects, R43), the justification of a right-to-left paragraph relative to its direction (R43), superscript and subscript (style slot `/13`: normal text is written 0, 527; the prototypes and After Effects' scripted default hold 1 = superscript, drawn at 58.3 % size, 526); the box; the anchor in pixels; the name only when it is not the text's first line; text animators: Anchor Point, Position, Scale, Skew, Skew Axis, Rotation, Opacity, Fill / Stroke Color, Stroke Width, Tracking, Line Spacing, Blur, Character Offset with keys and expressions, range selectors (Start / End / Offset in percent or index units, Units, Based On, Mode, Amount, Shape, Smoothness, Ease High / Low, Randomize Order, Random Seed; R48) |
|
||||
| Text | point and box text: the text (paragraphs, CR / LF / CRLF as After Effects' CR), per paragraph the justification (left, right, centre and the four justified variants, codes 3-6), first-line / start / end indent, space before / after, hanging punctuation, composer and direction (paragraph slots `/1`-`/5`, `/21`, `/29`, `/33`), per style run the font (PostScript name), size, fill and stroke colour, tracking, automatic or explicit leading, faux bold and italic, all caps and small caps, horizontal / vertical scale, baseline shift, automatic kerning (metrics, optical, none), ligatures, tsume, whether fill and stroke are drawn, stroke width and stroke over fill (style slots `/6`, `/7`, `/9`, `/11`, `/18`, `/36`, `/56`-`/58`, `/63`, as the importer decodes them, 538-569; written only where they differ from the prototype; read back in After Effects, R43), the justification of a right-to-left paragraph relative to its direction (R43), superscript and subscript (style slot `/13`: normal text is written 0, 527; the prototypes and After Effects' scripted default hold 1 = superscript, drawn at 58.3 % size, 526); the box; the anchor in pixels; the name only when it is not the text's first line; the Source Text expression, enabled or disabled (`tdb4` bytes 119 / 120 and a `Utf8` after `cdat`, as After Effects wrote it in `text-synthetic.aep`, #3170); text animators: Anchor Point, Position, Scale, Skew, Skew Axis, Rotation, Opacity, Fill / Stroke Color, Stroke Opacity, Stroke Width, Tracking, Line Spacing, Blur, Character Offset with keys and expressions, range selectors (Start / End / Offset in percent or index units, Units, Based On, Mode, Amount, Shape, Smoothness, Ease High / Low, Randomize Order, Random Seed; R48) |
|
||||
| Footage | png, jpeg, OpenEXR, TIFF, Targa, (flat) Photoshop, Illustrator and EPS stills, png, OpenEXR and jpeg sequences, mp4 and mov clips (also conformed to another rate, R38), WAV audio: the path (a sequence's folder and numbering), size, duration, native frame rate, the name (none stored when it is the file's, as After Effects does); After Effects re-reads everything else from the file when it opens the project |
|
||||
| Effects | every effect of the table the importer reads (`effectcraft_aep::effect_map`, one table for both directions): Tint, Gradient Ramp, Exposure, Invert, Fast Box Blur, Gaussian Blur, Drop Shadow, Timewarp, Glow, Fill, Hue/Saturation (master and colorize), Levels (RGB), Directional Blur, Radial Blur, CC Radial Blur, Venetian Blinds, Channel Blur, Black & White, Tritone, Luma Key, Simple Choker, Matte Choker, Transform, Brightness & Contrast, Turbulent Displace, Fractal Noise, Beam, CC Radial Fast Blur, Noise, Levels (Individual Controls), Corner Pin, Grid, Sharpen, Linear Color Key, Radial Wipe, Remove Color Matting, CC Bend It, CC Power Pin, 4-Color Gradient, Change Color, Extract, Noise HLS, Scatter, CC Light Sweep, Compound Blur, Displacement Map, Set Matte, Gaussian Blur (Legacy), Pixel Motion Blur and the Slider, Angle, Checkbox, Color, Point and Layer expression controls: the instance name, every parameter that differs from its default (colours, points, fixed, integer and float sliders, angles, popups by item name, checkboxes, layer parameters: the `tdpi` of their always-present record holds the owning layer's id by default, 0 for None (EffectCraft's unset Layer Control, or the explicit `NO_LAYER` of the other effects), or the named layer's id, 320 / 321 / 904; Layer Control's companion source choice is not reported (After Effects has none for it); the 27 effects of batch 6 (903-905) and the Layer Control are written from prototypes but not yet read back in After Effects), expressions on parameters, Hue/Saturation's master values and Levels' RGB values (arbitrary data), keys on colours, points and fixed / float sliders (time, linear / bezier / hold, the ease of sliders and points, labels), in stack order, several instances of a type per layer and per project (R24-R27, R45) |
|
||||
| Layer styles | the nine styles and Blending Options (Fill Opacity, channel switches, Blend Interior, Blend Ranges; the composition's Global Light on every styled layer) on solid, footage, precomposition, text and shape layers: a record for every parameter that differs from After Effects' default, keys on scalars, expressions (R51) |
|
||||
@@ -93,7 +93,7 @@ at all.
|
||||
| `effects` | an effect outside the table (each instance), a parameter without an After Effects counterpart that carries keys or an expression or a value other than a new instance's (a default that depends on the layer size is not compared), the effect's label colour, a colour parameter's opacity (After Effects' have none; Drop Shadow uses it), a fraction on an integer parameter (written whole), Curves points outside 0..1 (written at the limit), keys on a popup, checkbox, angle or integer slider (the first key's value is written), keys or expressions on Hue/Saturation's or Levels' data values, a fractional Hue/Saturation master value (written whole), a popup item After Effects does not have, keys or an expression on a layer parameter (its value is written), a layer parameter naming a layer that is not exported (the owning layer is written) or whose layer is taken with its masks and effects (After Effects takes the source) |
|
||||
| `markers` | a protected marker region (the switch is not decoded; the marker is written), a marker time out of range, markers on a footage item (not written) |
|
||||
| `masks` | masks on a text or shape layer (the path unit there is not verified; not written), a disabled mask or one without a usable path (not written), and per written mask: keys on Mask Opacity or Expansion (no keyed prototype; the static value is written), expressions on mask properties, the lock, mask motion blur other than Same As Layer, Linear feather falloff, RotoBezier, variable-width feather points |
|
||||
| `text_layers` | per text layer what its document cannot hold: wiggly and expression selectors, animator properties outside the list above (3D rotations, hue / saturation, Character Value, ...), a Tracking Type or Character Range / Alignment other than the default, Path and More Options other than the defaults, Source Text keys (the first key's text is written) and expressions, a manual kerning amount, OpenType features, variable font axes, a font style other than Regular, vertical text, a switched-off range selector (written on), a run without a font name (written in Times New Roman), a fill or stroke opacity (text colours have none), and a value beyond what the document holds (written at the limit): colour channels outside 0..1, font size, tracking, leading, scale, baseline shift, tsume, stroke width, paragraph indents and spacing, box position and size |
|
||||
| `text_layers` | per text layer what its document cannot hold: wiggly and expression selectors, animator properties outside the list above (3D rotations, hue / saturation, Character Value, ...), a Tracking Type or Character Range / Alignment other than the default, Path and More Options other than the defaults, Source Text keys (the first key's text is written), a manual kerning amount, OpenType features, variable font axes, a font style other than Regular, vertical text, a switched-off range selector (written on), a run without a font name (written in Times New Roman), a fill or stroke opacity (text colours have none), and a value beyond what the document holds (written at the limit): colour channels outside 0..1, font size, tracking, leading, scale, baseline shift, tsume, stroke width, paragraph indents and spacing, box position and size |
|
||||
| `shape_layers` | shape items outside the list above (wiggle, zig zag, pucker, twist, ...; not written), dashes / taper / wave, blend mode and composite order, the path direction, per-corner roundness of a rectangle (the uniform Roundness is written), gradient colour stops other than the default, keys on popups (the first key's value is written), keys and expressions on a path |
|
||||
| `cameras`, `lights` | keys or expressions on camera / light options (static value written), iris and highlight settings; parallel and ambient lights (not written), light falloff, radius, shadows; a camera's Auto-Orient Along Path or Towards Camera (written one-node), a spot light not oriented towards its point of interest (written oriented), a light colour channel outside 0..1 |
|
||||
| `model_layers` | each 3D model layer (skipped) |
|
||||
|
||||
+161
-20
@@ -1,6 +1,6 @@
|
||||
# After Effects project format (`.aep`)
|
||||
|
||||
> **Last reviewed:** 2026-11-05 · **Last updated:** 2026-11-05 · **Change:** versions: `svap` is the version that wrote the file, `head` bytes 4 to 7 the version it is written for, and what a Save a Copy As 25.x / 24.x of After Effects 2026 changes (section 1, "Versions"); before that: the stretch numerator is signed (a negative one reverses the layer, 981-983); layer styles (3.12b, 950-980); before that: footage: `sspc@118` of an image sequence is its folder's modification time (860, read-back x1-x3); before that: text: paragraph `/24`, `/35` and style `/68` are neutral for 0 and 1 (732-734, edited in place); before that: keyframes: the ease of mask path keys (599, 700), the spatial speed unit of fractional values (597, 598, 730); before that: text: character and paragraph settings, manual kerning, vertical text, `/nil` is neutral (S17: 538-578, 595, 596); before that: cameras, lights and 3D rotations (S16: the auto Position of a two-node camera is where After Effects renders it, 537; camera and light defaults read back 255, 256; Orientation `otst/otda` 488), Timewarp (529-533), an expression on an effect parameter (534); before that: text: style slot /13 is superscript / subscript (526-528; every scripted text of the corpus is superscript), wrapping box text 517; before that: effects of batch 5 (409-450, 511-516: integer sliders, checkbox defaults in `pard`, arbitrary data of Hue/Saturation and Levels, the parameters of Invert, Fast Box Blur, Gaussian Blur, Drop Shadow, Glow; section 3.7); before that: shape items of batch 5 (451-484, 519: ellipse, fill and stroke opacity, fill rule, group transform, shape layer anchor, trim paths, rectangle roundness, polystar fields, gradient default, nested groups, the defaults of every item; section 3.9); before that: track mattes: mode at `ldta` byte 107 and the matte layer id at byte 160 (cases 402-408, section 3.1); before that: text: automatic leading is verified (400); Exposure (`ADBE Exposure2`, cases 389-397): group start / end parameter types 13 / 14, float slider valid minimum and slider range (section 3.7); before that: text: U+0003 is a paragraph break (386, 387); before that: a stored text Anchor Point is pixels (385, now V); before that: the XML form `.aepx` is read into the same chunk tree (section 1, "The XML form"); the `head` save counter is a `u32` at byte 16; text documents for the importer (stroke colour, automatic leading, the font of each run, the undecoded-slot check, the text-layer Anchor Point, the Source Text record); before that: third round of verified field layouts (explicit-default semantics, type-major bezier ease, exact time stretch and in / out points, Time Remapping state, comp pixel aspect / shutter phase / display start, Point default, Layer Control None, dropdown selection, one `mkif` per mask, polystar and radial gradient records, text styles, camera and light options, marker cue data, audio and image-sequence footage) added to the typed view of `effectcraft-aep` · **Target:** Adobe After Effects 2026
|
||||
> **Last reviewed:** 2026-11-05 · **Last updated:** 2026-11-11 · **Change:** text: a Source Text expression is stored like any property's (`tdb4` bytes 119 / 120, `Utf8`; `text-synthetic.aep`, #3170); before that: footage: the Photoshop layer an item shows and its document's size (`opti` tag `8BPS`, section 3.3, issue 3087); before that: Puppet (`ADBE FreePin3`, 3.7): the property tree from an external scripting listing, **?** until a byte sample exists (#3093); before that: footage: an image sequence without a filename prefix stores only the suffix `Utf8`, and one imported with Force alphabetical order stores its file list in `LIST StVc` / `StVS` (section 3, footage); before that: versions: `svap` is the version that wrote the file, `head` bytes 4 to 7 the version it is written for, and what a Save a Copy As 25.x / 24.x of After Effects 2026 changes (section 1, "Versions"); before that: the stretch numerator is signed (a negative one reverses the layer, 981-983); layer styles (3.12b, 950-980); before that: footage: `sspc@118` of an image sequence is its folder's modification time (860, read-back x1-x3); before that: text: paragraph `/24`, `/35` and style `/68` are neutral for 0 and 1 (732-734, edited in place); before that: keyframes: the ease of mask path keys (599, 700), the spatial speed unit of fractional values (597, 598, 730); before that: text: character and paragraph settings, manual kerning, vertical text, `/nil` is neutral (S17: 538-578, 595, 596); before that: cameras, lights and 3D rotations (S16: the auto Position of a two-node camera is where After Effects renders it, 537; camera and light defaults read back 255, 256; Orientation `otst/otda` 488), Timewarp (529-533), an expression on an effect parameter (534); before that: text: style slot /13 is superscript / subscript (526-528; every scripted text of the corpus is superscript), wrapping box text 517; before that: effects of batch 5 (409-450, 511-516: integer sliders, checkbox defaults in `pard`, arbitrary data of Hue/Saturation and Levels, the parameters of Invert, Fast Box Blur, Gaussian Blur, Drop Shadow, Glow; section 3.7); before that: shape items of batch 5 (451-484, 519: ellipse, fill and stroke opacity, fill rule, group transform, shape layer anchor, trim paths, rectangle roundness, polystar fields, gradient default, nested groups, the defaults of every item; section 3.9); before that: track mattes: mode at `ldta` byte 107 and the matte layer id at byte 160 (cases 402-408, section 3.1); before that: text: automatic leading is verified (400); Exposure (`ADBE Exposure2`, cases 389-397): group start / end parameter types 13 / 14, float slider valid minimum and slider range (section 3.7); before that: text: U+0003 is a paragraph break (386, 387); before that: a stored text Anchor Point is pixels (385, now V); before that: the XML form `.aepx` is read into the same chunk tree (section 1, "The XML form"); the `head` save counter is a `u32` at byte 16; text documents for the importer (stroke colour, automatic leading, the font of each run, the undecoded-slot check, the text-layer Anchor Point, the Source Text record); before that: third round of verified field layouts (explicit-default semantics, type-major bezier ease, exact time stretch and in / out points, Time Remapping state, comp pixel aspect / shutter phase / display start, Point default, Layer Control None, dropdown selection, one `mkif` per mask, polystar and radial gradient records, text styles, camera and light options, marker cue data, audio and image-sequence footage) added to the typed view of `effectcraft-aep` · **Target:** Adobe After Effects 2026
|
||||
|
||||
What `crates/aep` ([`effectcraft-aep`](../crates/aep/README.md)) reads and writes, and the evidence
|
||||
for each statement. Everything here was observed on project files we generated ourselves; no
|
||||
@@ -119,6 +119,11 @@ counterpart (a name that is not 1 to 4 characters) are skipped with their conten
|
||||
with the byte offset. It runs without recursion; the nesting depth (`Limits::max_depth`) and the
|
||||
element count (`Limits::max_chunks`) are bounded, and everything it allocates is bounded by the
|
||||
size of the input.
|
||||
- There are two readers. `read_aepx` builds the owned tree element by element. The importer, which
|
||||
only reads, uses a second reader that decodes every body (hex, text, the alias JSON, the
|
||||
numbers) into one buffer and records the chunks as a flat depth-first list whose bodies are
|
||||
ranges of that buffer; it indexes that buffer (`TreeIndex`) as it indexes an `.aep`. The tests
|
||||
hold both readers to the same tree and the same errors.
|
||||
- Tests: `tests/aepx.rs` compares the five fixture pairs chunk by chunk and byte by byte, compares
|
||||
the typed view of both forms, and runs truncated, bit-flipped, deeply nested and oversized
|
||||
inputs; `crates/aep/tests/it/import/aepx.rs` imports both forms of each pair and expects the same
|
||||
@@ -138,7 +143,7 @@ write on their own.
|
||||
| `svap` (4 bytes, the first top-level chunk) is the version of the After Effects that **wrote** the file: `0f 11 0e 02` in all 36 (a down-saved copy keeps the 2026 word) and in all 1 192 other `.aep` files of the local corpus folders. After Effects opens a file without `svap` (`t-nosvap`). | All 36; corpus scan. |
|
||||
| `head` bytes 4 to 7 are the version the file is **written for**: `0f 11 0e 02` (= `svap`) in the 2026 saves, `0f 0b 06 61` in the 25.x copies, `0f 03 06 41` in the 24.x copies, the same word in every family. After Effects refuses a file whose word is zero ("created with an older version [0.0x0]", `u-zhead`). | All 36; `u-zhead`. |
|
||||
| The word is packed (bit 31 first): bits 25-24 and 21-19 the major, 18-15 the minor, 14-11 the bug-fix, 10-8 a stage (6 in all), 7-0 the build; bits 31-26 (`000011`) and 23-22 (`00`) are the same in all samples and not decoded. `0f110e02` = **26.2.1x2**, which is what the saving After Effects reports for itself (`app.version` recorded by the corpus scripts) - this one sample fixes minor, bug-fix and build. The copies read as **25.6.0x97** and **24.6.0x65** (major **V** from three consecutive majors; minor, bug-fix and build of the copies **E**: one other word decides their positions). `effectcraft_aep::AeVersion`, `AepFile::file_version`, `AepFile::saved_by_version`. | Tests `decodes_the_sampled_words`, `the_container_of_a_copy_states_its_version_in_head_and_the_writer_in_svap`. |
|
||||
| `head` bytes 0 to 1 (a `u16`) step with the major: 97 (`0x61`) in the 2026 saves, 96 in 25.x, 95 in 24.x; bytes 2 to 3 are 5, 9 and 6 (**?**). Bytes 8 to 15 differ between families, not between versions; bytes 16 to 19 are the save counter (one higher in each copy). Nothing is derived from bytes 0 to 3 (`AepFile::head_revision` exposes the `u16`). | All 36. |
|
||||
| `head` bytes 0 to 1 (a `u16`) step with the major: 97 (`0x61`) in the 2026 saves, 96 in 25.x, 95 in 24.x; bytes 2 to 3 are 5, 9 and 6 (**?**). Bytes 8 to 15 differ between families, not between versions; bytes 16 to 19 are the save counter (one higher in each copy). Nothing is derived from bytes 0 to 3, and the reader does not expose them. | All 36. |
|
||||
| The XMP packet after the container is no guide: its `CreatorTool` is "Adobe After Effects 2026" in the copies too. | All 36. |
|
||||
| **26 against 25.x** (per family, chunk tree aligned by item and layer id): besides `head`, `nhed` / `nnhd` differ in 6 bytes (`nhed` 20-23 and 26-27: `827fa930` / `0015` in the 2026 save, `00000000` / `011b` in both copies; session state, see section 4). Every **solid's** `Pin /CLRS` changes: the 2026 save has `dcui` (1 byte, 1), `apid` all `0xff` and an empty `Utf8`; the copies drop `dcui`, set `apid` to a GUID (`601e9973...`, the same in all) and embed a 935-byte JSON with an ICC profile (`baseColorProfile/colorProfileData`). A **footage** item that the 2026 save stores with an assigned profile (`mcsp` = 1 and a profile JSON) loses `mcsp` and that JSON in the copies, and its `epid` becomes all `0xff`. The rest is save noise: the footage alias `ascendcount_base` (the copies were saved in another folder), `sspc` byte 112 of a footage item (a modification time), the `oEXR` `opti` of an EXR item (the copies hold the display device name `\.\DISPLAY1` as UTF-16 at byte 126 and, in the 24.x copy, a few non-zero bytes from byte 2 102 that look like uninitialised memory; the 2026 save has zeros), the plug-ins' sequence data `sdat` of Lumetri Color (16 instances and the project's `EfdG`), the mask counter `mkif` byte 8. No chunk is added or reordered, and no `pard`, `cdat`, `ldta`, `cdta`, `tdb4`, keyframe, text or shape chunk differs. | All 12 families. |
|
||||
| **25.x against 24.x**: only `head` (bytes 1, 3, 5 and 7), and the session noise in `opti` and `sdat` above. | All 12 families. |
|
||||
@@ -216,12 +221,12 @@ the value from the corpus manifest (test file in parentheses at the end of each
|
||||
|
||||
API: `LayerHeader` (`is_video_enabled`/`is_enabled`, `is_audio_enabled`, `is_effects_enabled`,
|
||||
`is_auto_oriented`, `light_type`, `blend_mode`, `stretch` / `stretch_den`, `stretch_ratio`,
|
||||
`stretch_percent`, `in_point`, `out_point`); the same switches on `Layer`; `Layer::track_matte`
|
||||
`in_point`, `out_point`); the same switches on `Layer`; `Layer::track_matte`
|
||||
(`TrackMatte`, `MatteMode`; test `tests/matte.rs`). `stretch` is now the
|
||||
numerator of the ratio (it was the whole multiplier before 50 % and 150 % were sampled). Tests:
|
||||
`tests/header.rs`, `tests/layer_switches.rs`. The layer name itself is the `Utf8` child of the
|
||||
`Layr` (empty while the name is the source's); the layer then shows the name of its source item
|
||||
(`Project::layer_display_name`).
|
||||
(`Project::layer_source(...)` then `Item::display_name`, the rule the importer uses).
|
||||
|
||||
### 3.2 Composition header (`Item/cdta`, 204 bytes; the decoder needs the first 176) and renderer
|
||||
|
||||
@@ -246,9 +251,9 @@ numerator of the ratio (it was the whole multiplier before 50 % and 150 % were s
|
||||
|
||||
The inert `ADBE Time Remapping` record of every AV layer has `tduM` = the source's duration in seconds: 5 for 5 s compositions (124, 257, 258), 3 and 2 for a 3 s and a 2 s composition (600, 601), 1 for the 1 s WAV (351), 0.1 for the three-frame sequence (353), 0 on solids and stills (000, 126). Every property's keyframe time scale (`tdb4` bytes 12 to 15) follows the composition (23 976 in every
|
||||
property of `L` after 229 and 230). The corpus manifest calls the classic renderer
|
||||
`ADBE Advanced 3d`; the file stores `ADBE Escher` / `Classic 3D`. API: `CompHeader::frame_rate_exact`
|
||||
(exact fraction), `duration_frames`, `shutter_phase`, `display_start`, `display_start_seconds`,
|
||||
`display_start_frame`. Tests: `tests/header.rs`, `tests/layer_switches.rs`.
|
||||
`ADBE Advanced 3d`; the file stores `ADBE Escher` / `Classic 3D`. API: `CompHeader::{time_base,
|
||||
frame_rate_fixed, duration_units, shutter_phase, display_start}` (`display_start` is the exact
|
||||
fraction). Tests: `tests/header.rs`, `tests/layer_switches.rs`.
|
||||
|
||||
### 3.3 Items, footage and precomps
|
||||
|
||||
@@ -263,6 +268,7 @@ property of `L` after 229 and 230). The corpus manifest calls the classic render
|
||||
| footage size | `Pin /sspc` | 30, 34 | u32, u32 | width, height in pixels | 000 (1920 x 1080), 022 (100 x 100 null), 126 (8 x 8), 211, 212, 259 | V |
|
||||
| footage name | `Pin /opti` | 26 (solid), 58 (file) | ASCII, NUL terminated | the item's own `Utf8` name is empty for footage | 000 (`L`), 022 (`Null 1`), 126 (`test8x8.png`), 211 (`S100`), 259 (`Blue`) | V |
|
||||
| **solid colour** | `Pin /opti` | 10, 14, 18, 22 | 4 x f32 | ARGB, 0..1: red [1, 0, 0] is `1, 1, 0, 0`, blue [0, 0, 1] is `1, 0, 0, 1` | 000, 259 | V |
|
||||
| **Photoshop layer** | `Pin /opti` (tag `8BPS`) | 14; 32, 36; 78, 82, 86, 90; 344 | i32 BE; 2 x u32 **LE**; 4 x i32 **LE**; UTF-8, NUL terminated | an item made from one layer of a `.psd`: the layer record's index in the file (bottom of the stack = 0; -1 is the merged image), the document's two sides (which is the width: **?**, the one sample is square), the layer's bounds top, left, bottom, right in document pixels (all 0 for a layer without pixels, such as an adjustment layer) and the layer's name. The item's size (`sspc` 30 / 34) is the bounds' size with Retain Layer Sizes, else the document's. `Footage::psd_layer` | the project of issue 3087: ten layers, each index, bounds and name equal to the `.psd`'s layer record at that position (`Layer 1` `[9, -346, 1083, 1086]`, item 1432 x 1074; `test` `[749, 800, 764, 848]`, item 48 x 15; 32 and 36 hold 1000, 1000 in every record, the `.psd`'s size) | V |
|
||||
| file path | `Pin /Als2/alas` | key `fullpath` | JSON string | alias JSON of a file footage item | 126 (the fixture holds a neutral path) | V |
|
||||
| precomp linkage | `Layr/ldta` | 40 | u32 | the layer's source id is the id of the precomp's item | 124 | V |
|
||||
|
||||
@@ -279,7 +285,8 @@ Footage items beyond solids and stills (`Pin /sspc`, 222 bytes; audio and image
|
||||
| bits per pixel | 60 | u32 | RGBA still 32, RGB sequence 24, 0 for solids and audio | 126, 352 | V |
|
||||
| **sample rate** | 160 | f64 | 48 000.0 for the WAV; the same for an mp4 with sound (48 000.0 and 44 100.0 read back from AAC tracks of those rates), absent for an mp4 without (the importer takes a clip's audio from it) | 350, read-back vid-audio | V |
|
||||
| **file size** | 208 | u32 | bytes of the source file(s): png 149, WAV 96 044 (44 + 48 000 x 2), three png of 168 bytes 504 | 126, 350, 352 | V |
|
||||
| **sequence numbering** | 172, 176, 180 | u32 x 3 | first and last frame number and the digits of a sequence: 1, 3, 3 for `seq_001.png`..`seq_003.png` (353), 101, 103, 4 for `shot_0101.exr`..`shot_0103.exr` (603); 0 for single files (604, 605, 607). The name around the number is the pair of `Utf8` chunks between `Als2` and `opti` in `Pin ` (`seq_` / `.png`, `shot_` / `.exr`), absent on single files. `Item::sequence_info`, `SequenceInfo::file_name` | 353, 603, 604, 605, 607 | V |
|
||||
| **sequence numbering** | 172, 176, 180 | u32 x 3 | first and last frame number and the digits of a sequence: 1, 3, 3 for `seq_001.png`..`seq_003.png` (353), 101, 103, 4 for `shot_0101.exr`..`shot_0103.exr` (603); 0 for single files (604, 605, 607). The name around the number is the pair of `Utf8` chunks between `Als2` and `opti` in `Pin ` (`seq_` / `.png`, `shot_` / `.exr`), absent on single files. **An empty prefix is not written**: `0001.png`..`0005.png` leave one `Utf8` (`.png`) there, so a single `Utf8` is the prefix when the first file name in `opti` (byte 58: `0001.png`) starts with it and the suffix when it ends with it; without that evidence the reader takes one starting with `.` as the suffix and reads nothing else. `Item::sequence_info`, `SequenceInfo::file_name` | 353, 603, 604, 605, 607; the prefix-less sequence of issue 3174 (After Effects 25.2) | V |
|
||||
| **sequence file list** (Force alphabetical order) | `Pin /LIST StVc` | `StVS` (4 bytes, `u32` LE), then `Utf8` x count | a sequence imported with Force alphabetical order has, between `Als2` and `opti`, a `LIST StVc` instead of the prefix and suffix `Utf8`s: `StVS` holds the file count as a **little-endian** `u32` (`05 00 00 00`), followed by one `Utf8` per file name in order (`shot_0001.png`..`shot_0005.png`). The numbering at @172 / @176 is then `0xFFFFFFFF` and the digits @180 0; duration and rate are stored as for a numbered sequence (5/30, 30). The list replaces the numbering; a list without a readable `StVS` count, or with a name that is not a plain file name (`/`, `\`, `:`, a control character such as NUL, `.`, `..`), is not read. Without a stored item name the importer names the item after its first listed file (an assumption: what After Effects shows was not sampled). `SequenceInfo::files`, `SequenceInfo::nth_file_name` | the alphabetical sequence of issue 3174 (After Effects 25.2) | V |
|
||||
| **sequence folder time** | 118 | u32 (unaligned) | seconds since 1904-01-01 UTC: the modification time of a sequence's folder when After Effects scanned it (860: 0xe6fb443a = 2026-10-19 03:51:22, the folder's and its files' time). With the folder's time After Effects keeps the stored numbering on open; with another (an older time, the first or the last file's time while the folder's differs: x1, x2 against x3) it scans the folder again and warns about a gap in a modal dialog | 860, `export-tests\sparse860\exp` x1-x3 | V |
|
||||
| still duration denominator | 42 | u32 | 1 for png (126, 607), 600 for jpeg (605), 100 for OpenEXR (604): a still's duration is 0 over a per-format denominator | 126, 604, 605, 607 | V |
|
||||
|
||||
@@ -291,7 +298,8 @@ name. A footage layer shows the item's name while it has no name of its own (351
|
||||
|
||||
Test: `tests/header.rs` (precomp), `tests/text_markers_footage.rs` and `tests/batch3_objects.rs`
|
||||
(footage), `tests/layer_switches.rs` (solid colour). API: `Footage::{duration, native_frame_rate,
|
||||
bits_per_pixel, file_size, audio_sample_rate, is_sequence, sequence_frame_count}`.
|
||||
bits_per_pixel, file_size, audio_sample_rate, is_sequence}`; `Item::sequence_info` (with
|
||||
`SequenceInfo::frame_count`).
|
||||
|
||||
### 3.4 Properties: sparse storage, static values and units
|
||||
|
||||
@@ -400,8 +408,8 @@ out speed, at 40 out influence**). Scale key 1 of 305 (`n = 3`, in 0 / 33 %, out
|
||||
15 / 66) is `0,0, .33,.33, 15,15, .66,.66`. Component `i` uses slot `i` of each block (**V** for the
|
||||
block layout and the units, Scale speed in fractions per second and Mask Feather speed in pixels
|
||||
per second; **E** that different values per dimension sit in the slot of their dimension, because
|
||||
the tool that made the corpus applies one ease to all dimensions; **V** since the exporter wrote three different eases for the three Scale dimensions and After Effects read the same three back, aep-export.md R13). `Keyframe::plain_ease_slots`
|
||||
returns the doubles as stored, `Keyframe::ease_component(i)` one component's `KeyEase`. Layout of
|
||||
the tool that made the corpus applies one ease to all dimensions; **V** since the exporter wrote three different eases for the three Scale dimensions and After Effects read the same three back, aep-export.md R13). `Keyframe::ease_component(i)`
|
||||
returns one component's `KeyEase`, `Keyframe::ease_components` all of them. Layout of
|
||||
the spatial template: `u32` at 8 (1 normally, 0 after the spatial auto-bezier and continuous
|
||||
switches were turned off together, 054), `f64` arc length of the segment to the next key at 16
|
||||
(100 in 204 key 1, 0 on the last key and right after creation; safe to ignore), **ease at 24 in
|
||||
@@ -426,7 +434,7 @@ in tangent (`n` x `f64`, relative offset from the value) at `56 + 8 n`, out tang
|
||||
| **ease, colour switched to bezier** | spatial n=4 @24..55 | keyframe.set_interpolation bezier on two colour keys stores speed 0 and influence 0 on both sides, and After Effects interpolates them linearly (598 Start Color red to blue: 0.75 / 0.5 / 0.25 red at 0.5 / 1 / 1.5 s); a custom colour ease cannot be set by script | 598 | V |
|
||||
| **ease, mask path** | 64-byte record @24..55 | in speed, in influence, out speed, out influence as in the spatial template: 599 out / in influence 0.75, 700 0.2 / 0.5, speeds 0 (the path at 0.5 s is 5.42 % / 28.0 % of the way). The `f64` at 16 is 1 (093) or 2 (599, 700) on the first key and 0 on the last (not decoded) | 093, 599, 700 | V (influence), ? (speed unit) |
|
||||
|
||||
API: `Keyframe::{interpolation_in, interpolation_out, kind, value, ease, ease_component, ease_components, plain_ease_slots, color_distance, spatial}`;
|
||||
API: `Keyframe::{interpolation_in, interpolation_out, kind, value, ease, ease_component, ease_components, spatial}` (the `f64` at byte 16 of a colour key is `SpatialKey::segment_length`);
|
||||
`Property::keys()` hands the property's template to its records. Marker record (16 bytes): time,
|
||||
`03 03` hold bytes, 8 stale bytes. Mask path record (64 bytes): the common fields and the ease at byte 24 (599, 700). Test:
|
||||
`tests/values.rs`, `tests/keyframes.rs`.
|
||||
@@ -460,7 +468,7 @@ The expression engine is project-wide (`LIST ExEn/Utf8`, `javascript-1.0`). API:
|
||||
| **default** | `pard` | 56 | by type | float slider: f64 (Slider 0, **Blurriness 25**); fixed slider and angle: 16.16 (drop shadow opacity 127.5, direction 135, distance 5, softness 0; Fill opacity 1.0 = 100 %; **Tint Amount to Tint 100, Ramp Scatter 0, Blend With Original 0**); **colour: ARGB bytes (drop shadow `ff000000` black, Fill `ffff0000` red; Ramp Start `ff000000`, End `ffffffff`; Tint Map Black To `00000000` and Map White To `00ffffff`: alpha 0, which is what After Effects reads back)**; popup: u32 index (1); **point: two 16.16 fractions of the layer size at 56 and 60 (Point Control 0.5, 0.5; Ramp Start 0.5, 0 and End 0.5, 1)** | 071, 077, 080, 214, 215, 356, 368, 383 | V |
|
||||
| **valid maximum** | `pard` | 128 / 108 | 16.16 / f32 | the largest valid value of a fixed slider (128: Tint Amount 100, Ramp Scatter 512, Blend With Original **1.0**) and of a float slider (108: Blurriness 30 000; **Exposure 100, Offset 2, Gamma Correction 10**). The stored scale differs per slider (below), so this is the reader's only hint at it | 356, 368, 078, 389 | V |
|
||||
| **float slider valid minimum, slider range** | `pard` | 104; 112 / 116 | f32; f32 x 2 | the smallest valid value (Exposure -100, Offset -2, Gamma Correction 0.1 as f32) and the range the slider covers (Exposure -4..4, Offset -0.5..0.5, Gamma 0.1..10); the slider range is repeated as `tdum` / `tduM` (f64) in each value record (391-394). @120 holds the default again as f32 (Gamma 1.0, Blurriness 25), u16 @124 the display decimals (**H**: Exposure 2, Offset 4) | 389, 391-394 | V |
|
||||
| **definitions of a second instance** | top-level `LIST EfdG` | | | one `EfDf` per effect TYPE (`tdmn` = match name, then a copy of ONE instance's `sspc`, including that instance's value records); the `EfDC` count before them is a little-endian u32. When two layers carry the same effect type, only one instance keeps a full `parT`; **the other has an empty `LIST parT` (4 bytes)** but its own `tdgp` (input layer `-0000` with its own `tdpi`, the button record, and its value records). Its definitions come from the `EfdG` copy by match name, its values from its own records, never from the copy (383: the copy holds S100's Start [0.1, 0.2]; L's Ramp reads the 368 defaults). Which instance keeps the full `parT` is **?** (in 383 S100 is both first in the file and newest). The `EfDf` order follows the stack order (365 / 366); `Pefl/pjef` does not | 365, 366, 383 | V |
|
||||
| **definitions of a second instance** | top-level `LIST EfdG` | | | one `EfDf` per effect TYPE (`tdmn` = match name, then a copy of ONE instance's `sspc`, including that instance's value records); the `EfDC` count before them is a little-endian u32. When two layers carry the same effect type, only one instance keeps a full `parT`; **the other has an empty `LIST parT` (4 bytes)** but its own `tdgp` (input layer `-0000` with its own `tdpi`, the button record, and its value records). Its definitions come from the `EfdG` copy by match name, its values from its own records, never from the copy (383: the copy holds S100's Start [0.1, 0.2]; L's Ramp reads the 368 defaults). Which instance keeps the full `parT` is **?** (in 383 S100 is both first in the file and newest). The `EfDf` order follows the stack order (365 / 366); `Pefl/pjef` does not. **Some files have no `EfdG` at all** (seen in 17 projects, all from inside After Effects-made Motion Graphics templates). In each of them every effect type (196 in all; 2 to 14 Color Controls per file) has exactly one instance with a full `parT` and an empty one in every other instance, and the reader takes the others' definitions from that instance (`DefinitionSource::OtherInstance`, #3170). Which instance keeps it is **?** here too | 365, 366, 383; templates | V |
|
||||
| popup items | `parT/pdnm/Utf8` | | text | items separated by `\|` | 077 (`Item 1\|Item 2\|Item 3`), 214 | V |
|
||||
| stored value | `sspc/tdgp/<match>-NNNN` | | `tdbs` | present only for a parameter that differs from its default; `tdsb` 1, `cdat` as in 3.4. **Absent record = the `pard` default.** Some parameters are recorded in the default state (`tdsb` 3) and the record still holds the value After Effects uses: every one of the 25 such records in the corpus and the samples differs from its `pard` default and reads back as recorded (Lumetri Color's hidden controls High Dynamic Range, HDR Specular, the second Look, Legacy Tone And Saturation, Face Detection, Auto Color, Auto Intensity and SemanticAutoTone Progress, 1200; Cryptomatte's Selection Mode on, 1400). No effect record in the default state holds a value that does not count. The reader takes the value in either state (`EffectParam::recorded_value`, `EffectParam::value`); `EffectParam::stored_value` keeps to written records, and a record in any other state (no sample has one) is not read: the importer reports it as damaged data. That other effects record values so is H (no sample shows one) | see below; 1200, 1400 | V (1200, 1400), H (other effects) |
|
||||
|
||||
@@ -558,6 +566,36 @@ compositions, the items from one composition per popup item).** Each set value i
|
||||
| `ADBE Offset` | 1 Shift Center To: point, default the layer centre; 2 Blend With Original: fixed, **max 1 (a 0..1 fraction)**, default 0 |
|
||||
| `ADBE Deflicker` (Color Stabilizer) | 1 Stabilize: popup (Brightness, Levels, Curves), **default 2 (Levels)**; 2 Black Point, 3 Mid Point, 4 White Point: points, defaults 0.2 / 0.5 / 0.8 of the width at half the height; 5 Sample Size: fixed, max 50, default 3. The reference frame the Set Frame button picks is not among the parameters |
|
||||
|
||||
**Importer block 2026-11-05, #3112 (`1300-thirdparty.aep`; V for the parameter lists, H for the
|
||||
data layouts, read from default instances only):**
|
||||
|
||||
| Effect (match name, plug-in version) | Parameters (`parT` index: type, valid range, default) |
|
||||
|---|---|
|
||||
| `CS Color Neutralizer` (CC Color Neutralizer, 1.3) | 1 / 7 / 13 Shadows / Midtones / Highlights Unbalance: colours (black, 127 grey, white; pickers: setting one sets the range's sliders, e.g. [0.27, 0.55, 0.85] gives Red 42.94, Green 1.18, Blue -44.12 = (mean - channel) x 150 / 255 on 8-bit values, and leaves no colour record); 2-6, 8-12, 14-18 a group per range (start, Red, Green, Blue, end) with float sliders -100..100 (slider -50..50), default 0; 19 Pinning: float 0..100 (slider 0..50); 20 Blend w. Original: float 0..100; 21-25 the Special group: 22 View popup (Final Result, Correction B to W, Correction Stair), default 1, 23 Black Point float 0..49.9, default 0, 24 White Point float 50.1..100, default 100 |
|
||||
| `EXtractoR` (2.6.6) | 1 Channel Info (`EXtractoR-0013`): arbitrary data, 688 bytes, below; 2-6 the Process group: 3 Black Point and 4 White Point float sliders, valid ±3.4028235e38 (the float32 range; slider ±1 000), default 0 / 1; 5 UnMult: checkbox (empty `pard` name, the label in `pdnm`), default off |
|
||||
| `IDentifier` (2.6.6) | 1 Channel Info (`IDentifier-0005`): arbitrary data, 148 bytes, below; 2 Display: popup (Colors, Luma Matte, Alpha Matte, Raw), default 1; 3 ID: integer slider, max 2 147 483 647, default 0 |
|
||||
|
||||
EXtractoR's Channel Info is the plug-in's own **little-endian** layout: `u32` 1 and 28 bytes, then
|
||||
four 164-byte records (Red, Green, Blue, Alpha), each a kind in byte 0 (1 a channel of the footage by
|
||||
name: `R`, `G`, `B` by default; 2 the input's own channel, named `(copy)`: Alpha by default) and the
|
||||
channel name NUL-terminated from record byte 35. EffectCraft imports kind 2 as its EXtractoR's `(copy)`,
|
||||
the effect input's channel of the same position as earlier effects left it, not as `A` (which names
|
||||
an OpenEXR footage's own alpha plane). The rest is not read: it differs between instances
|
||||
with the same channels (uninitialised-looking values, also in bytes 1..4 of a record: `02 00 00 32`
|
||||
on Alpha in one definition's default, which is why only byte 0 is the kind). **A chosen channel**
|
||||
(`extractor-07-preset-channel-z`, a preset of our own that reads `Z` into Red, Green and Blue) is kind
|
||||
1 with its name (`Z`), and the `u32` at record byte 4 is then 13, which looks like the channel's
|
||||
position in the EXR's sorted channel list of 14 (the defaults hold 0 there: H). IDentifier's data
|
||||
differs between instances with the same settings except for **the chosen channel's name**,
|
||||
NUL-terminated from byte 19, which is `Object ID` when no channel is chosen. After Effects lists
|
||||
only integer (UINT) channels: in 1300's EXR (a FLOAT `ID` channel) it found none ("No ID channels
|
||||
available"), so that block's preset `identifier-09-preset-channel-id` only sets ID 2; in 1310's
|
||||
EXR (UINT `objectId` and `IndexOB.X` added) the preset `identifier-09-preset-channel-objectid-id2`
|
||||
stores `objectId`, with 15 at byte 4 (the channel's position in the sorted list of 16, as for
|
||||
EXtractoR: H). Without a chosen channel After Effects leaves the layer unchanged (1300 and 1310
|
||||
defaults). The other bytes are not read. API: `ExtractorChannels`,
|
||||
`identifier_channel_type`.
|
||||
|
||||
**Curves and Mesh Warp** (961-965, 985; applied from presets, the data is not scriptable): their
|
||||
arbitrary-data parameter (type 11) always has a record (`tdbs` with a 4-byte `cdat`) and a `LIST
|
||||
aRbs` / `aRbp`, also at the default. Curves (`ADBE CurvesCustom-0001`, 1644 bytes): `u16` 1, 1;
|
||||
@@ -573,6 +611,64 @@ suffix is a parameter id, not the `parT` position (Curves -0002 Channel before -
|
||||
-0005 Quality before -0004). API: `CurvesData`, `MeshData`, `curve_lut`. [V; details: corpus FORMAT
|
||||
S]
|
||||
|
||||
**Lumetri Color** (`ADBE Lumetri`, 1200: 63 compositions in After Effects 2026, 32 bpc, working space
|
||||
None; by script: defaults, all set, one per section, valid minima and maxima, every popup item the script
|
||||
can set, checkboxes toggled, keys, an expression; from presets made by hand, `-58` ... `-62`: an Input
|
||||
LUT, a Look, the RGB curve, Hue vs Sat, the colour wheels): 131 `parT` entries. The `-NNNN` suffix is a
|
||||
parameter id, not the position (`-0127` ... `-0130` sit between `-0005` and `-0026`), so the importer
|
||||
keys by suffix. Float sliders (type 10) store the value After Effects shows; the valid ranges (`pard`
|
||||
104 / 108) are wider than the slider's (112 / 116): Temperature / Tint -300..300 (slider -100..100),
|
||||
Saturation 0..300 (0..200), Exposure -7..7 (-5..5), Contrast, Highlights, Shadows, Whites, Blacks,
|
||||
HDR Specular, Tint Balance -150..150 (-100..100), Faded Film 0..150 (0..100), HDR White 100..1000,
|
||||
HDR Range 100..10000, Refine Blur 0..1000 (0..30), Vignette Amount -5..5 (-3..3); the read-backs of
|
||||
`-19-min` / `-20-max` equal the stored numbers. **Hidden controls are recorded in the default state**
|
||||
(`tdsb` 3) and still hold the value After Effects reads back: High Dynamic Range `-0062` (1.0),
|
||||
HDR Specular `-0064` (22), the second Look popup `-0128` (26.16 as set, read back 26), Legacy Tone And
|
||||
Saturation `-0097` (its `pard` default reads off as the script saved the project and on once the
|
||||
project was opened and saved again, `versions/1200-lumetri-26`, `-25x`, `-24x`; a fresh instance reads
|
||||
back off), Auto Color `-0124`, Face Detection `-0102`, the Auto Intensity `-0123`
|
||||
(`EffectParam::recorded_value`). Each section has an Active checkbox (blank `pard` name; `-0003` Basic
|
||||
Correction, `-0023` Creative, `-0037` Curves, `-0118` RGB Curves, `-0104` / `-0105` / `-0107` / `-0109` /
|
||||
`-0111` the hue / saturation curves, `-0046` Color Wheels, `-0068` HSL Secondary, `-0050` Vignette, on
|
||||
by default); off, the section does nothing (`-03-sec-basic-correction`, `-17-sec-vignette` render as
|
||||
`-00-defaults`). Arbitrary data (type 11, a record and `aRbs` in every instance; empty is `fe fe`):
|
||||
`-0001` an XML copy of the grade written only when the Look changes (not kept current: `-01-set-all`
|
||||
holds defaults for controls set after the Look; it names the LUT file, `Fuji F125 Kodak 2393 (by
|
||||
Adobe).cube`); `-0096` the Look's name, UTF-16LE (`SL BLUE ICE`, `Fuji F125 Kodak 2393 (by Adobe)` in
|
||||
`-59-preset-look-lut`; the popup `-0025` itself has no record, `-0128` can disagree with it:
|
||||
`-07-sec-creative` names item 20 while `-0128` holds 23), `-0095` likewise for the Input LUT
|
||||
(`ALEXA_Default_LogC2Rec709`, `-58-preset-input-lut`; the popups `-0005` / `-0127` keep no record,
|
||||
`-0004`, `-0024` and the assets `-0125` / `-0126` stay `fe fe`: the LUTs are After Effects' own files,
|
||||
named, not embedded); `-0129` the Look colour space's 16-byte id (the GUID `LookColorSpace` of `-0001`
|
||||
in Windows layout: the first 4, 2 and 2 bytes little-endian; zero for None; the Color Space popup
|
||||
`-0130` writes no record of its own, `-22-pop38-1` ... `-53-pop38-33`);
|
||||
`-0032` Split Toning (32 zero bytes); `-0039` the four RGB curves and `-0042`, `-0106`, `-0108`,
|
||||
`-0110`, `-0112` the hue / saturation curves, 520 bytes each: `i32` type (3 for the hue curves, 2 for
|
||||
Luma vs Sat and Sat vs Sat, 0 for an RGB curve), `u32` point count, then little-endian `f64` pairs x, y
|
||||
sorted by x (bytes after the counted points are stale). RGB: x and y 0..1, (0, 0), (1, 1) by default;
|
||||
the first of the four is the master curve (`-60-preset-curves-rgb`: (0.491, 0.787) lifts R, G and B
|
||||
alike, and the render follows a natural cubic spline through the points within 0.003); the order of
|
||||
the other three is H (the `-0001` XML names `ToneCurvesLuma`, `Red`, `Green`, `Blue`; no sample moves
|
||||
one). Hue / saturation: x the hue (0 red, 1/3 green) or the luma / saturation 0..1, y the offset from
|
||||
neutral 0; a fresh curve has no points, the selector colours `-0113` ... `-0117` add three at y = 0,
|
||||
the hue picked +- 0.0556. Hue vs Sat's y scales the saturation by 1 + y in linear light
|
||||
(`-61-preset-curves-huesat`: (0.0155, 0.97) takes pure red's linear saturation to 1.96 times, (0.263,
|
||||
-0.03) and (0.765, -0.02) green, cyan and blue to 0.97); the other four curves' scale is H. `-0047`
|
||||
Color Wheels, 4 `f64` triples: hue in degrees (0 red, 120 green), brightness slider 0..1 (0.5
|
||||
neutral), distance from the centre 0..1; 0, 0.5, 0 each by default; Shadows, Midtones, Highlights, then
|
||||
a fourth wheel of unknown role (`-62-preset-wheels`: Shadows 229.1, 0.370, 0.547 towards blue with the
|
||||
slider down; Midtones 0.1, 0.5, 0.395 towards red; Highlights 93.4, 0.727, 0.319 towards yellow-green
|
||||
with the slider up; the fourth at its default); `-0085` the HSL correction wheels (8 triples, the same
|
||||
default; layout H); `-0073` the HSL key, 40 bytes: a flag byte (1 once a colour was picked), then hue,
|
||||
saturation, lightness, each centre, range, tolerance (`f32` LE, fractions) and an enabled byte (fresh:
|
||||
0.5, 0, 0, on; picking RGB 0.02 0.55 0.85 gives hue centre 0.5605 = that colour's hue and lightness
|
||||
centre 0.435 = its HSL lightness, range 0.05, tolerance 0.1: `-01-set-all`; whether range is the half
|
||||
or full width is H); `-0122` the Auto Tone analysis (UTF-16 JSON). The project saved as 25.x and 24.x
|
||||
copies (`versions/1200-lumetri-25x`, `-24x`: the 58 scripted compositions, saved by After Effects
|
||||
2026) stores every Lumetri definition, valid range and value as the 26 save does. [V for the sliders,
|
||||
checkboxes, popups, the `tdsb` 3 values, the master RGB curve, Hue vs Sat, the colour wheels and the
|
||||
LUT names; H where marked]
|
||||
|
||||
**Cryptomatte plug-in** (match name `Cryptomatte`, the free Cryptomatte plug-in, 2.6.6 in our
|
||||
sample; read from 88 instances in one production project, not made by us, and from our own After
|
||||
Effects 2026 sample 1400 (corpus `aep/cryptomatte.aep`: a fresh instance, Selection Mode on, each
|
||||
@@ -610,6 +706,52 @@ presets, and After Effects draws exactly the picked objects as selected (`crypto
|
||||
`crypto-07`: box and disc, the two objects of `mat_paint`). API: `CryptomatteData` (refuses data without `cry1`, a length past the end or
|
||||
a non-zero byte at 112).
|
||||
|
||||
**Puppet (`ADBE FreePin3`; ?: external listing, not the corpus, #3093).** No file of the
|
||||
corpus, the committed fixtures or the samples holds a Puppet. The tree below comes from outside
|
||||
the corpus: a public scripting listing of the effect's properties (a forum post, an After Effects
|
||||
release from before the Advanced and Bend pins and Density). It is not bytes we have seen; that
|
||||
After Effects writes it by the general rule (a `tdmn` and the `LIST tdgp` or `tdbs` it names,
|
||||
nested in the instance's value group `sspc/tdgp`, as masks, shapes and layer styles are) is
|
||||
**H**:
|
||||
|
||||
```text
|
||||
Puppet ADBE FreePin3
|
||||
On Transparent ADBE FreePin3 On Transparent
|
||||
arap ADBE FreePin3 ARAP Group (hidden)
|
||||
Auto-traced Shapes ADBE FreePin3 Outlines
|
||||
Mesh ADBE FreePin3 Mesh Group (hidden)
|
||||
Mesh 1 ADBE FreePin3 Mesh Atom
|
||||
Mesh ADBE FreePin3 Mesh (hidden: the triangulation)
|
||||
Triangles ADBE FreePin3 Mesh Tri Count
|
||||
Expansion ADBE FreePin3 Mesh Expansion
|
||||
Deform ADBE FreePin3 PosPins
|
||||
Puppet Pin 1 ADBE FreePin3 PosPin Atom
|
||||
Vertex Offset ADBE FreePin3 PosPin Vtx Offset (hidden, 2 values)
|
||||
Vertex Index ADBE FreePin3 PosPin Vtx Index (hidden; 4294967295 = none)
|
||||
Position ADBE FreePin3 PosPin Position
|
||||
Overlap ADBE FreePin3 HghtPins (hidden while empty)
|
||||
Stiffness ADBE FreePin3 StarchPins (hidden while empty)
|
||||
```
|
||||
|
||||
A pin's rest place is not a property: it is a vertex of the hidden mesh (Vertex Index) plus an
|
||||
offset, so it cannot be read without decoding the mesh data. The unit of Position (layer pixels,
|
||||
or a fraction of the layer as effect points are) is **?**. The match names of the Overlap and
|
||||
Starch pin atoms and their properties, of the Advanced and Bend pins (Scale, Rotation) and of
|
||||
Density, Mesh Rotation Refinement and the Puppet Engine are **?**, and so is which of them are
|
||||
`parT` parameters; whether After Effects stores some `parT` parameters of a fresh Puppet even at
|
||||
their defaults (the importer would then report them every time) is **?**; whether a Puppet
|
||||
without pins has an (empty) Mesh Group, or no ARAP group at all, is **?**. The importer
|
||||
(`import/puppet.rs`) therefore brings the effect in as EffectCraft's Puppet without meshes or
|
||||
pins and reports, per mesh, Triangles, Expansion and per pin group the pins, how many are keyed
|
||||
or carry an expression, and their atom match names; every entry outside this tree is named in the
|
||||
report. The sample that settles it: a fresh Puppet with no pins in one composition and, in
|
||||
another, a 400 x 800 solid in a larger composition (pixels against fractions) with a Puppet of
|
||||
three Position pins (one keyed, one moved off its rest place without
|
||||
keys), one Advanced, one Bend, one Starch and one Overlap pin, Triangles, Expansion and Density
|
||||
off their defaults, a second mesh, and a scripting read-back of every property's match name and
|
||||
value, the hidden ones included. Pins added by script are reported to come out disabled, so the
|
||||
pins are placed by hand or by a preset.
|
||||
|
||||
### 3.8 Masks and bezier paths (`tdgp[ADBE Mask Parade]`)
|
||||
|
||||
| Field | Chunk path | Offset | Type | Meaning | Evidence | Conf. |
|
||||
@@ -629,7 +771,7 @@ a non-zero byte at 112).
|
||||
328), so mode and inverted are reported per mask. `mkif` byte 8 is the running mask id counter of the
|
||||
**layer** in the session (1, 2 for the two masks of 328; 329 then gets 3; undo does not reset it; a new
|
||||
layer starts at 1 again): exposed as `Mask::counter`, a session counter and not a stable id. API:
|
||||
`Mask::{mode, inverted, counter}`, `Project::mask_vertices_px` (vertices in layer pixels), `Layer::mask_atoms` (`MaskAtom`: the mask with its atom group, its path property and its shapes by position, for the keys and expressions of the mask properties). Tests:
|
||||
`Mask::{mode, inverted, counter}`, `ShapePath::vertices(size)` (vertices in layer pixels: `size` is the size of the layer's source, `Project::layer_source_size`; a text or shape layer has no source size and its mask boxes are already pixels, so the importer passes 1 x 1), `Layer::mask_atoms` (`MaskAtom`: the mask with its atom group, its path property and its shapes by position, for the keys and expressions of the mask properties). Tests:
|
||||
`tests/masks.rs` (with a synthetic check of the handle order), `tests/batch3_objects.rs`.
|
||||
|
||||
### 3.9 Shape layers (`tdgp[ADBE Root Vectors Group]`)
|
||||
@@ -715,7 +857,7 @@ verified paths from the top level (keys `/98 /0 /1`; `RUN` = `/1/1[0]/0`):
|
||||
| **stroke colour** | `.../6/54/0/1` = `[A R G B]`, the fill's layout; white in the untouched samples, red `[1 1 0 0]` in 574, blue in 595. The stroke switch, width and order are slots `/57`, `/63`, `/58` (see "character settings") | 110, 113, 574, 595 | V |
|
||||
| **automatic leading** | style slot `.../6/4`: `true` = automatic leading (lines advance by 1.2 x the size; `/5` is then not used), `false` = `/5` is the leading. **243: `/4` false, `/5` 98, the second line's baseline is stored 98 px below the first (`/1/1[0]/1/2[0]/6[0]/6[0]/6[0]/6[1]/0/0[1]`)**. **400 (Auto Leading switched on for `A` CR `B`, size 100, leading 98; against its unedited save 399): the run style changes in `/4` (false -> true) and `/5` (98 -> 0.01) only, the stored second baseline moves from 98 to 120 px below the first (1.2 x 100), and After Effects measures the same 120 px (`text.measure`)**; the rest of the diff is save noise (`head`, the comp's modification time, the text's `btgu/pgui`). That `/5` holds 0.01 under automatic leading is seen in 400 only | 243, 339 (`/5`); 400 (`/4`); test `automatic_leading_is_slot_4` | V |
|
||||
| **undecoded slots** | the run style and paragraph dictionaries of an untouched document (244; 110 stores the same bytes) are the reference: a slot that is not decoded and differs from it is listed (`TextDocument::undecoded_style_fields`, `undecoded_paragraph_fields`); a slot the reference lacks is listed only when it is not 0, `false`, empty or `/nil` (a neutral value is taken to carry nothing, so it is not reported as a loss; `/nil` is how 244 stores its paragraph slots `/27`, `/28` and `/36`). Decoded style slots: `/0 /1 /2 /3 /4 /5 /6 /7 /8 /9 /11 /12 /13 /18 /35 /36 /52 /53 /54 /56 /57 /58 /62 /63 /70`; paragraph slots: `/0 /1 /2 /3 /4 /5 /8 /9 /21 /29 /33`. Not found by any scriptable setting (none of the `text.set_style` attributes tried in S17 changes them): style `/20 /38 /78 /87 /88`, paragraph `/31 /32`. **Paragraph `/24`, `/35` and style `/68`** (1, 1 and 0 in a scripted text) were edited in place in the wrapping box text 517 (732: `/24` 0, 733: `/35` 0, 734: `/68` 1, same length); After Effects opens each without a dialog, keeps the value on a re-save and composes the same four lines, baselines and bounds (`text.measure`, `layer.bounds`; 734 renders like 517), so 0 and 1 are not listed (`NEUTRAL_PARAGRAPH_VALUES`, `NEUTRAL_STYLE_VALUES`; their meaning stays unknown, Latin text only). **Paragraph `/34`** is 0 in a scripted text and 7 after Reset Paragraph (727: the only slot that changes); a justified 600 px box text of 88 characters composes the same four lines, baselines and bounds with both values (S18), so 0 and 7 are not listed (`NEUTRAL_PARAGRAPH_VALUES`), other values are | every text sample lists none; 725, 727 | V (reference), E (/34) |
|
||||
| **Source Text record** | the `tdbs` in `btds[ADBE Text Document]` (next to the `btdk`) has no keys and no expression in every sample; Source Text keyframes (presumably one `btdk` per key) and expressions were not sampled, the reader only reports them (`Layer::source_text_property`, `Layer::text_document_count`) | 110-114, 243-248, 334-340 | E |
|
||||
| **Source Text record** | the `tdbs` in `btds[ADBE Text Document]` (next to the `btdk`) has no keys and no expression in every corpus sample. A Source Text expression is stored as on any other property: its text in the `tdbs`'s `Utf8`, `tdb4` byte 120 = 1, byte 119 = 0 when enabled and 1 when disabled (`tests/fixtures/text-synthetic.aep`: `text.sourceText + "!"` enabled, `"X"` disabled; the `btdk` keeps the typed text). Source Text keyframes (presumably one `btdk` per key) were not sampled, the reader only reports them (`Layer::source_text_property`, `Layer::text_document_count`) | 110-114, 243-248, 334-340, text-synthetic | E; expression V |
|
||||
|
||||
API: `TextDocument` (`paragraphs`, `style_runs`, `text_box`, `paragraph_texts`, `font_of`,
|
||||
`incomplete_runs`, `undecoded_style_fields`, `undecoded_paragraph_fields`, `kerning`,
|
||||
@@ -757,7 +899,7 @@ groups as on other layers, with these conventions:
|
||||
|
||||
| What | Path | Stored | Evidence | Conf. |
|
||||
|---|---|---|---|---|
|
||||
| camera Position, default (auto state) | `ADBE Transform Group/ADBE Position`, `tdb4` 76 and 77 = 1 although no expression exists | `[0, 0, -2666.67]` (x and y are 0 whatever the composition size; z = -zoom). A **one-node** camera reports `[0, 0, -2666.67]` (251). **Correction (S16): a reopened two-node camera reports `[0, 0, -2666.67]` too (256 read back), and After Effects renders it there**: the default two-node camera of 537 above the 3D solid of 486 renders from `[0, 0, -2666.67]` towards the point of interest, not like the composition's default view (537 differs from 486 in 24 % of the grid points, and EffectCraft's import with the stored position matches 537 but for 4 edge points). The `[960, 540, ...]` of the batch-3 notes was not reproduced (535 and 537, created by script, read back the stored value too); `CameraPosition::reported` keeps the old rule and is marked as contradicted | 256, 251, 537 | V |
|
||||
| camera Position, default (auto state) | `ADBE Transform Group/ADBE Position`, `tdb4` 76 and 77 = 1 although no expression exists | `[0, 0, -2666.67]` (x and y are 0 whatever the composition size; z = -zoom). A **one-node** camera reports `[0, 0, -2666.67]` (251). **Correction (S16): a reopened two-node camera reports `[0, 0, -2666.67]` too (256 read back), and After Effects renders it there**: the default two-node camera of 537 above the 3D solid of 486 renders from `[0, 0, -2666.67]` towards the point of interest, not like the composition's default view (537 differs from 486 in 24 % of the grid points, and EffectCraft's import with the stored position matches 537 but for 4 edge points). The `[960, 540, ...]` of the batch-3 notes was not reproduced (535 and 537, created by script, read back the stored value too); the reader keeps only the stored value (`CameraPosition::stored`) | 256, 251, 537 | V |
|
||||
| camera Position, set by the user | same, `tdb4` 76 and 77 = 0 | the absolute value `[100, 200, -1000]` | 253 | V |
|
||||
| camera point of interest | `ADBE Anchor Point` | `[960, 540, 0]` in **pixels** (not a fraction); the record's `tdsb` is 1 on a two-node and 3 on the one-node camera (not asserted) | 251, 253, 256 | V (value), E (`tdsb`) |
|
||||
| light Position, default | same as the camera | `[0, 0, -1333.33]`, as After Effects reports it (255 read back); the point of interest `[960, 540, 0]` | 254, 255 | V |
|
||||
@@ -773,10 +915,8 @@ groups as on other layers, with these conventions:
|
||||
|
||||
A point light made by converting a spot light has a different property set (`Orientation`, `Rotate
|
||||
X/Y/Z`, `Cone Angle`, `Cone Feather 2` in the default state) that a spot light lacks (252); whether that
|
||||
is an artefact of the conversion is untested. API: `Layer::camera_position`,
|
||||
`CameraPosition::reported`, `Layer::camera_point_of_interest`, `Layer::light_intensity`,
|
||||
`Layer::{camera_zoom, camera_depth_of_field, camera_focus_distance, camera_aperture, light_color,
|
||||
light_cone_angle, light_cone_feather}`, `DEFAULT_CAMERA_ZOOM` (2 666.67 is verified for the 1920 px
|
||||
is an artefact of the conversion is untested. API: `Layer::camera_position`; the camera and light
|
||||
options are read by the importer's table (`import/camera.rs`); `DEFAULT_CAMERA_ZOOM` (2 666.67 is verified for the 1920 px
|
||||
composition only), `Layer::orientation` (`Orientation`; test `orientation_is_otst_otda` in
|
||||
`tests/batch5.rs`). Render checks against After Effects (20 px grid, 5 184 points): 486 1 point
|
||||
off, 535 2, 536 0, 537 4 (anti-aliased edges); **488 (Orientation `[10, 20, 30]`): 272 points
|
||||
@@ -894,6 +1034,7 @@ another value).
|
||||
**Effects**
|
||||
|
||||
- `pard` byte 48 (flags: **?**), 68 / 72 (the point default as a percent pair: **V** in the corpus for 074 and 368, not exposed), 120 (the float slider default again as f32: **V**, not exposed), 124 (display decimals: **H**), 132..147 (fixed slider slider-max, default copy and flags; the low bit of @144 is set on the two percent-displayed sliders and clear on Scatter: **H** "display as percent"), the colour default copy at @60, and the stale memory after the parameter name. The fixed slider valid minimum is not decoded (the float slider one is, 389).
|
||||
- Puppet (`ADBE FreePin3`, 3.7): the hidden mesh data, a pin's Vertex Index and Vertex Offset (its rest place), the unit of a pin's Position, the Overlap, Starch, Advanced and Bend pins, Density, Mesh Rotation Refinement and the Puppet Engine: no sample (**?**).
|
||||
- Effects renamed, or stacked twice on ONE layer (`Gradient Ramp 2`: does the second keep an empty `parT`?); which instance keeps the full `parT` when a second instance is created on a layer below the first; expressions on effect parameters; keyed per-channel Exposure values and a keyed checkbox; Curves / Levels (arrays), Fractal Noise, and whether the `tdpi` of a Layer Control holds an id or an index after the referenced layer is reordered or deleted.
|
||||
|
||||
**Masks, shapes**
|
||||
|
||||
+121
-18
@@ -1,9 +1,16 @@
|
||||
# After Effects project import
|
||||
|
||||
> **Last reviewed:** 2026-11-05 · **Last updated:** 2026-11-05 · **Change:** the report names the After Effects version a file is written for and the one that wrote it (`aeVersion`); copies saved as 25.x and 24.x import like the 2026 save; before that: Curves and Mesh Warp are imported (961-965, 985; FORMAT S of the corpus); before that: a shape item's eye switch is imported (bit 0 of its group flags, 986 / 987: a switched-off Fill under Trim Paths is no longer drawn, render equal to After Effects), and a layer style's (switch group `tdsb` 0, 988); before that: a negative time stretch (Time Reverse Layer) is imported: in and out point in order, the layer plays backwards (981-983: render equal to After Effects at 7 points); before that: After Effects' None on Compound Blur, Displacement Map and Set Matte arrives as an explicit None (`NO_LAYER`), not as the owning layer; before that: layer styles are imported (the nine styles and Blending Options, 951-979; Global Light becomes the composition's); before that: the 27 effects of round 3 are in the table (`effectcraft_aep::effect_map`); before that: a layer of a clip, sequence or sound ends at its source's end (After Effects ends it there when it opens a project, read-back test f1), unless it is remapped or looped or runs less than a frame past; before that: keys of shape colours are read (the reader took their 152-byte spatial records for plain ones: `tdb4` byte 7 is 0 there, 854); before that: the effect table moved to `effectcraft_aep::effect_map`; point defaults in percent and a popup default slot of 0 (After Effects' save of a project it opened, e1 / e2 re-saves) are read; before that: the justification of right-to-left paragraphs is read relative to the direction (t7); before that: 24 more layer blend modes (906, 907); before that: 27 more effects are imported (batch 6: 903-905), with layer parameters; before that: a Rec. 709 working space also sets the output space (After Effects renders in the working space, 902); before that: a mask whose path was never set arrives as the source rectangle (900, 901); before that: Offset Paths' Copy Offset arrives as EffectCraft's (After Effects' value minus 1, 828); before that: project settings, image sequences, conformed clips and effect instance names are imported, next to the mattes, shapes, text settings and animators, effects, markers, time remapping, cameras and lights); before that: parallel and ambient lights (701, 702) and keyed Orientation (703) are imported; before that: masks on text and shape layers (705, 706: AE vs EffectCraft render 12 and 0 of 5184 points, glyph edges) and Merge Paths, Offset Paths, Repeater (707-710, 731; merge renders equal but for edges; Repeater copy opacity and Offset Paths' outline differ in EffectCraft's renderer) are imported; before that: keyframes: the ease of Anchor Point, effect point, effect colour and mask path keys is imported (597-599, 700, 730: within 0.5 px of After Effects' read-backs); before that: effects: Directional Blur, Radial Blur, CC Radial Blur, Venetian Blinds, Channel Blur, Black & White, Tritone, Luma Key, Simple Choker, Matte Choker and Transform are imported (711-723; render checks: CC Radial Blur and Channel Blur equal, Directional Blur within 5/255; Venetian Blinds, Radial Blur, Black & White (8/255), Tritone (19/255), Luma Key, Matte Choker and Transform differ in EffectCraft's renderer, see STATUS); before that: text: character and paragraph settings (stroke, scales, baseline shift, kerning, ligatures, tsume, indents, spacing, justified text, composer, direction, vertical text; 538-578) and text animators with range selectors (579-594, 114) are imported; render checks 594 (keyed range selector at 0 / 0.5 / 1 / 1.5 / 2.5 s), 595, 596 equal but for glyph edges; before that: cameras, spot and point lights, X / Y Rotation and Orientation, Timewarp and expressions on effect parameters are imported (render checks 486, 535-537 equal but for edges; 488 differs by 5.25 %, cause in EffectCraft's renderer not isolated); before that: text: superscript and subscript are imported (526-528); before that: effects: Invert, Fast Box Blur, Gaussian Blur, Drop Shadow, Glow, Fill, Hue/Saturation, Levels and five expression controls are imported, popups are mapped by item name, checkbox defaults come from the file; before that: shape layers are imported (groups, paths, paints, trim paths, round corners; 451-484, 519 added); before that: markers and Time Remapping are imported (509, 510 added); before that: track mattes are imported (402-408); before that: text: automatic leading is verified (400); effects: Exposure is imported (Channels, all twelve sliders, Bypass, keys), next to Tint and Gradient Ramp; before that: text: U+0003 is a paragraph break (386, 387); before that: a stored text Anchor Point is pixels (385, now V); before that: effects: Tint and Gradient Ramp are imported (all parameters and keys), other effects stay reported; `.aepx` is accepted; text layers Line truncated
|
||||
> **Last reviewed:** 2026-11-05 · **Last updated:** 2026-11-11 · **Change:** a text animator's Stroke Opacity and a text layer's Source Text expression are imported (#3170); before that: an effect without a counterpart arrives as a placeholder with its parameters, values, keys and expressions; it renders nothing and is still reported (#3087); before that: an item made from one layer of a Photoshop file shows that layer (#3087); before that: the losses prompt shows every detail line and how many have none (`unlisted`); unmapped effects are listed most used first (#3365); a shape property recorded in the default state is no loss; an expression on Orientation is imported with its value or keys (#3087); before that: dropped projects and projects picked in File ▸ Import ▸ File… import as projects, several asked about one at a time; `file.import` hands them to `file.importAep`; the menu entries are named Adobe After Effects Project… (#3103); before that: After Effects' Puppet arrives as EffectCraft's Puppet without its meshes and pins, which are reported mesh by mesh (#3093; the storage is not sampled yet, aep-format 3.7); before that: Lumetri Color's master RGB curve, Hue vs Sat curve and colour wheels are imported (1200 presets made by hand); before that: Lumetri Color is imported (1200: every slider, checkbox and popup with a counterpart, by match name, also from records in the default state; switched-off sections, LUTs, looks, undecoded curves and wheels are reported); before that: the report names the After Effects version a file is written for and the one that wrote it (`aeVersion`); copies saved as 25.x and 24.x import like the 2026 save; before that: Curves and Mesh Warp are imported (961-965, 985; FORMAT S of the corpus); before that: a shape item's eye switch is imported (bit 0 of its group flags, 986 / 987: a switched-off Fill under Trim Paths is no longer drawn, render equal to After Effects), and a layer style's (switch group `tdsb` 0, 988); before that: a negative time stretch (Time Reverse Layer) is imported: in and out point in order, the layer plays backwards (981-983: render equal to After Effects at 7 points); before that: After Effects' None on Compound Blur, Displacement Map and Set Matte arrives as an explicit None (`NO_LAYER`), not as the owning layer; before that: layer styles are imported (the nine styles and Blending Options, 951-979; Global Light becomes the composition's); before that: the 27 effects of round 3 are in the table (`effectcraft_aep::effect_map`); before that: a layer of a clip, sequence or sound ends at its source's end (After Effects ends it there when it opens a project, read-back test f1), unless it is remapped or looped or runs less than a frame past; before that: keys of shape colours are read (the reader took their 152-byte spatial records for plain ones: `tdb4` byte 7 is 0 there, 854); before that: the effect table moved to `effectcraft_aep::effect_map`; point defaults in percent and a popup default slot of 0 (After Effects' save of a project it opened, e1 / e2 re-saves) are read; before that: the justification of right-to-left paragraphs is read relative to the direction (t7); before that: 24 more layer blend modes (906, 907); before that: 27 more effects are imported (batch 6: 903-905), with layer parameters; before that: a Rec. 709 working space also sets the output space (After Effects renders in the working space, 902); before that: a mask whose path was never set arrives as the source rectangle (900, 901); before that: Offset Paths' Copy Offset arrives as EffectCraft's (After Effects' value minus 1, 828); before that: project settings, image sequences, conformed clips and effect instance names are imported, next to the mattes, shapes, text settings and animators, effects, markers, time remapping, cameras and lights); before that: parallel and ambient lights (701, 702) and keyed Orientation (703) are imported; before that: masks on text and shape layers (705, 706: AE vs EffectCraft render 12 and 0 of 5184 points, glyph edges) and Merge Paths, Offset Paths, Repeater (707-710, 731; merge renders equal but for edges; Repeater copy opacity and Offset Paths' outline differ in EffectCraft's renderer) are imported; before that: keyframes: the ease of Anchor Point, effect point, effect colour and mask path keys is imported (597-599, 700, 730: within 0.5 px of After Effects' read-backs); before that: effects: Directional Blur, Radial Blur, CC Radial Blur, Venetian Blinds, Channel Blur, Black & White, Tritone, Luma Key, Simple Choker, Matte Choker and Transform are imported (711-723; render checks: CC Radial Blur and Channel Blur equal, Directional Blur within 5/255; Venetian Blinds, Radial Blur, Black & White (8/255), Tritone (19/255), Luma Key, Matte Choker and Transform differ in EffectCraft's renderer, see STATUS); before that: text: character and paragraph settings (stroke, scales, baseline shift, kerning, ligatures, tsume, indents, spacing, justified teLine truncated
|
||||
|
||||
File ▸ Import ▸ After Effects Project… (`file.importAep`) reads an `.aep` or its XML form `.aepx`
|
||||
File ▸ Import ▸ Adobe After Effects Project… (`file.importAep`) reads an `.aep` or its XML form `.aepx`
|
||||
and adds its items to the open project, in a folder named after the file (as After Effects does when it imports a project).
|
||||
A project dropped on the window, or picked in an Import File dialog (File ▸ Import ▸ File… or
|
||||
Multiple Files…, the Home screen, the Project panel), imports the same way. Several are asked
|
||||
about one at a time: while the losses prompt shows, the others wait, and Cancel skips only the
|
||||
one it asks about. The other files of the drop or pick import as footage, into the Project panel
|
||||
only (not into a composition, as the project opens its own); while the prompt shows, a Photoshop file or image sequence imports with its defaults instead of
|
||||
opening its own dialog. Agents giving an `.aep` to `file.import` get the refusal when parts would
|
||||
be lost (`file.importAep` takes `allowLosses` and `dryRun`).
|
||||
It is deliberately narrow: what it brings over is exact and tested, everything else is **reported**
|
||||
and never dropped silently. Wider coverage grows with the reader's verified field layouts
|
||||
([aep-format.md](aep-format.md)); this page lists what exists today.
|
||||
@@ -51,19 +58,19 @@ refused losses) leaves the project unchanged.
|
||||
| Project settings | colour depth, working space (None, sRGB, Rec. 709 Gamma 2.4; another one is reported as `project settings`), working gamma, Linearize Working Space, Blend Colors Using 1.0 Gamma, time display (timecode, frames, feet + frames 35 / 16 mm), frame count start (620-634). A colour depth, time display, frames-per-foot or frame count start code
|
||||
outside the sampled ones keeps the current value and is reported as `project settings`. After Effects renders in the working space (902: in a Rec. 709 Gamma 2.4 project an sRGB PNG step of 128 comes out as 135 and a grey 0.5 solid as 128, in the Render Queue output and in the captures), while EffectCraft converts its output to `output_space` (sRGB when unset): a Rec. 709 working space therefore also sets the output space to Rec. 709 (902 within 1 level of After Effects, was up to 13). They come in `ImportResult::settings`; the library never changes the project's own settings, `file.importAep` applies them when the project has no compositions yet (or with `applySettings: true`) |
|
||||
| Composition | name, size, exact frame rate, duration, work area, background colour (8 bit), pixel aspect, composition motion blur switch, shutter angle and phase (as stored), display start time, Classic/Advanced 3D renderer |
|
||||
| Layers | solid, null, adjustment, footage, precomposition and **text** layers: name (the source's name when the layer has none; for a text layer its first line), in/out/start and time stretch (exact ratios, e.g. 50 % and 150 %); a layer of a clip, sequence or sound ends at its source's end as After Effects ends it (unless remapped, looped or less than a frame past; f1), enabled/audio/solo/shy/locked/effects/motion blur/collapse/3D/guide switches, draft quality, label, blend mode (Normal, Multiply, Add, Screen, Overlay, Difference; Soft Light, Hard Light, Darken, Lighten, Exclusion, Color Dodge, Color Burn by name, 906; Classic Difference, Hue, Saturation, Color, Luminosity, the Stencil and Silhouette modes, Classic Color Dodge / Burn, Linear Dodge / Burn, Linear, Vivid and Pin Light, Hard Mix by render, 907: each code rendered by After Effects matched exactly one EffectCraft mode; 906 renders within 1/255 of After Effects), parent, **track matte** (alpha, alpha inverted, luma, luma inverted; the matte layer anywhere in the composition, also below its target; 402-408) |
|
||||
| Transform | Anchor Point, Position, Scale, Rotation, Opacity, **X and Y Rotation** (486, 487) and **Orientation**, static (488) or keyed (703: key values, times, linear and hold sides; a bezier side becomes linear and is reported; After Effects slerps between the keys, EffectCraft interpolates each angle, so in-between values differ, e.g. 703 at 1 s `[6.26, 9.35, 15.22]` against `[5, 10, 15]`; an expression-driven orientation is reported): the stored value, or the default when the file stores none; keyframes with time, linear/bezier/hold on each side, temporal ease (speed and influence, per dimension for Scale), automatic or manual spatial tangents, roving and key labels |
|
||||
| Expressions | on the transform and mask properties and on effect, camera and light parameters (534): source text and enable switch |
|
||||
| Layers | solid, null, adjustment, footage, precomposition and **text** layers: name (the source's name when the layer has none; for a text layer the first 40 characters of its first line, as EffectCraft names a text layer after its text), in/out/start and time stretch (exact ratios, e.g. 50 % and 150 %); a layer of a clip, sequence or sound ends at its source's end as After Effects ends it (unless remapped, looped or less than a frame past; f1), enabled/audio/solo/shy/locked/effects/motion blur/collapse/3D/guide switches, draft quality, label, blend mode (Normal, Multiply, Add, Screen, Overlay, Difference; Soft Light, Hard Light, Darken, Lighten, Exclusion, Color Dodge, Color Burn by name, 906; Classic Difference, Hue, Saturation, Color, Luminosity, the Stencil and Silhouette modes, Classic Color Dodge / Burn, Linear Dodge / Burn, Linear, Vivid and Pin Light, Hard Mix by render, 907: each code rendered by After Effects matched exactly one EffectCraft mode; 906 renders within 1/255 of After Effects), parent, **track matte** (alpha, alpha inverted, luma, luma inverted; the matte layer anywhere in the composition, also below its target; 402-408) |
|
||||
| Transform | Anchor Point, Position, Scale, Rotation, Opacity, **X and Y Rotation** (486, 487) and **Orientation**, static (488) or keyed (703: key values, times, linear and hold sides; a bezier side becomes linear and is reported; After Effects slerps between the keys, EffectCraft interpolates each angle, so in-between values differ, e.g. 703 at 1 s `[6.26, 9.35, 15.22]` against `[5, 10, 15]`), with its expression as on any property: the stored value, or the default when the file stores none; keyframes with time, linear/bezier/hold on each side, temporal ease (speed and influence, per dimension for Scale), automatic or manual spatial tangents, roving and key labels |
|
||||
| Expressions | on the transform and mask properties, on effect, camera and light parameters (534) and on a text layer's Source Text (`text-synthetic.aep`, #3170): source text and enable switch |
|
||||
| Cameras and lights | **cameras** (one-node and two-node, from `ldta` byte 38 bit `0x40`): point of interest, Position as stored (also the auto Position `[0, 0, -zoom]`, where After Effects renders it, 537), orientation and rotations, Zoom (absent: `-z` of the auto Position, else the 50 mm default), Depth of Field, Focus Distance (absent: the zoom), Aperture (absent: 25.309 at 1920 px, read back from 256; scaled with the width, which is not sampled, E); **spot, point, parallel and ambient lights** (light type 1, 2, 0, 3: 701, 702): point of interest (not for point and ambient lights), Position, Intensity, Color, Cone Angle, Cone Feather, Casts Shadows; keys and expressions as for the transform; 251-256, 341-345, 535-537, 701, 702. Environment lights are reported |
|
||||
| Text | the Source Text of a text layer: the text (paragraphs separated by CR; a hard line break is a CR too; U+0003 is laid out by After Effects exactly like a CR (386 against 387) and becomes a CR), per style run the font (PostScript name), size, fill and stroke colour, whether fill and stroke are drawn, stroke width, stroke over fill (one switch for the text), tracking, leading (automatic or explicit), faux bold, faux italic, all caps, small caps, superscript and subscript (526-528), horizontal and vertical scale, baseline shift, automatic kerning (metrics, optical, none), ligatures, tsume (538-578); per paragraph the justification (left, centre, right and the four justified variants), first-line / start / end indent, space before / after, direction, composer, hanging punctuation (in a right-to-left paragraph the stored justification is relative to the direction: After Effects lays 0 and 3 out flush right and 1 and 4 flush left, read-back test t7, so they arrive as right / left); point text or box text (box size and top-left in layer pixels); vertical right-to-left text (575) |
|
||||
| Text animators | every animator with its name and its range selectors (Start, End, Offset, or the Index range when Units is Index; Units, Based On, Mode, Amount, Shape, Smoothness, Ease High / Low, Randomize Order, Random Seed) and the properties Anchor Point, Position, Scale, Skew, Skew Axis, Rotation, Opacity, Fill Color, Stroke Color, Stroke Width, Tracking Amount, Line Spacing, Blur, Character Offset and Character Range, with keys and expressions (579-594, 114, 728). Values are stored as After Effects shows them; popups are 1-based in the file |
|
||||
| Text animators | every animator with its name and its range selectors (Start, End, Offset, or the Index range when Units is Index; Units, Based On, Mode, Amount, Shape, Smoothness, Ease High / Low, Randomize Order, Random Seed) and the properties Anchor Point, Position, Scale, Skew, Skew Axis, Rotation, Opacity, Fill Color, Stroke Color, Stroke Opacity (`text-synthetic.aep`, #3170), Stroke Width, Tracking Amount, Line Spacing, Blur, Character Offset and Character Range, with keys and expressions (579-594, 114, 728). Values are stored as After Effects shows them; popups are 1-based in the file |
|
||||
| Masks | every mask of an imported layer, in stack order (on text and shape layers, which have no source, the stored mask box is in layer pixels: 705, 706): name, path (vertices and tangents in layer pixels, open or closed), mode (None, Add, Subtract, Intersect, Lighten, Darken, Difference), inverted, feather (x, y in pixels), opacity (percent), expansion (pixels); path keyframes (time, linear or hold sides, roving, label), feather / opacity / expansion keyframes with ease |
|
||||
| Effects | **Curves** (961-964: the five curves' points from the arbitrary data as EffectCraft's `x,y` points in 0..1; render within 6 levels: EffectCraft interpolates monotone, After Effects stores a natural spline) and **Mesh Warp** (965, 985: Rows, Columns, Quality and the Distortion Mesh as per-vertex offsets in layer pixels; Bezier handles moved on their own are kept as eight extra numbers of the vertex for the export and reported, EffectCraft draws the mesh bilinear; the header is rows + 1, columns + 1; render 985: 1.4 % of pixels > 2 levels around the moved vertex), **Tint**, **Gradient Ramp**, **Exposure**, **Invert**, **Fast Box Blur**, **Gaussian Blur**, **Drop Shadow**, **Glow**, **Fill**, **Hue/Saturation**, **Levels**, **Timewarp** (Method, Adjust Time By, Speed with keys, Source Frame; 529-533), **Directional Blur**, **Radial Blur**, **CC Radial Blur**, **Venetian Blinds**, **Channel Blur**, **Black & White**, **Tritone**, **Luma Key**, **Simple Choker**, **Matte Choker**, **Transform** (all parameters; 711-723) and the expression controls **Slider**, **Angle**, **Checkbox**, **Color** and **Point Control** (table below) on any imported layer (also text and shape layers and layers with masks), in stack order, with the instance name, every parameter (static value or default) and its keyframes; also the second and later instances of a type, whose parameter definitions are only in the project's shared definition list. Other effects are reported (below) |
|
||||
| Effects | **Lumetri Color** (1200; see *Lumetri Color* below), **Curves** (961-964: the five curves' points from the arbitrary data as EffectCraft's `x,y` points in 0..1; render within 6 levels: EffectCraft interpolates monotone, After Effects stores a natural spline) and **Mesh Warp** (965, 985: Rows, Columns, Quality and the Distortion Mesh as per-vertex offsets in layer pixels; Bezier handles moved on their own are kept as eight extra numbers of the vertex for the export and reported, EffectCraft draws the mesh bilinear; the header is rows + 1, columns + 1; render 985: 1.4 % of pixels > 2 levels around the moved vertex), **Tint**, **Gradient Ramp**, **Exposure**, **Invert**, **Fast Box Blur**, **Gaussian Blur**, **Drop Shadow**, **Glow**, **Fill**, **Hue/Saturation**, **Levels**, **Timewarp** (Method, Adjust Time By, Speed with keys, Source Frame; 529-533), **Directional Blur**, **Radial Blur**, **CC Radial Blur**, **Venetian Blinds**, **Channel Blur**, **Black & White**, **Tritone**, **Luma Key**, **Simple Choker**, **Matte Choker**, **Transform** (all parameters; 711-723) and the expression controls **Slider**, **Angle**, **Checkbox**, **Color** and **Point Control** (table below) on any imported layer (also text and shape layers and layers with masks), in stack order, with the instance name, every parameter (static value or default) and its keyframes; also the second and later instances of a type, whose parameter definitions are only in the project's shared definition list. **Puppet** arrives in its place with its name and fx switch but without meshes and pins (a pin's rest place lies in the mesh data, not decoded yet); per mesh the report gives Triangles, Expansion and each pin group's pins, keyed pins and atom match names. Other effects are reported (below) |
|
||||
| Layer styles | Drop Shadow, Inner Shadow, Outer Glow, Inner Glow, Bevel and Emboss, Satin, Color Overlay, Gradient Overlay and Stroke when applied (switch group `tdsb` 1): every parameter, starting from After Effects' defaults (Use Global Light is off there), with keys on scalars and colours and expressions; Blending Options: Fill Opacity, the channel switches, Blend Interior Styles as Group, Use Blend Ranges, and Global Light Angle / Altitude, which the composition adopts (one setting per composition here). Units and popups through `effectcraft_aep::style_map` (aep-format 3.12b). Render check against After Effects (`proof\styles`): drop shadow, inner shadow, satin, colour overlay, stroke within a few levels; glow and bevel intensities and the Gradient Overlay direction differ in EffectCraft's renderer |
|
||||
| Shape layers | groups (nested, with anchor, position, scale, skew, skew axis, rotation, opacity), rectangle (size, position, roundness), ellipse (size, position), polystar (star or polygon, points, position, rotation, radii, roundness), bezier path (vertices and tangents in layer pixels, path keys), fill (colour, opacity, fill rule), stroke (colour, opacity, width, cap, join, miter limit), gradient fill and stroke (linear or radial, start and end point; the default white-to-black colours), trim paths (start, end, offset, trim multiple shapes), round corners (radius), merge paths (mode; 707, 708), offset paths (amount, line join, miter limit, copies, copy offset; 710; After Effects offsets copy *i* by Amount x (*i* + Copy Offset), default 1, EffectCraft by Amount x (*i* + 1 + copyOffset), so the copy offset arrives 1 less, also its keys; an expression on it is kept and reported; 826-828, 828 renders equal to After Effects on every grid point), repeater (copies, offset, composite, transform; 709), in file order, with keyframes and expressions, the operators' defaults as After Effects reads them back (731); the layer anchor in pixels; 238-242, 330-333, 451-484, 519, 705-710, 731 |
|
||||
| Markers | layer and composition markers: time (layer time for layer markers, exact frames), duration (1/600 s units, 509, 510), comment, chapter, URL, frame target, label, cue point (name, event or navigation, parameters); 122, 123, 249, 347-349, 509, 510 |
|
||||
| Time Remapping | an enabled remap becomes the `timeRemap` property (source seconds, keys in layer time with their interpolation and ease, expression), first in the layer's properties as Layer > Time > Enable Time Remapping puts it; 319, 354 |
|
||||
| Footage | a link to the stored path; **missing-footage placeholder** when the path does not exist where the import runs (listed in `missingFootage`), a normal link when it does. The importer never opens footage. **An image sequence** (its alias is the folder) becomes a sequence of its files, named from the stored numbering (`shot_0101.exr` .. `shot_0103.exr` in 603, `seq_001.png` .. in 353), at its native frame rate; the first file is the footage's path and the one checked. Sequences and clips take the stored native frame rate and duration (603, 606). A sequence of more than 100 000 frames, or beyond a million file names in one import, links only its first file and is reported |
|
||||
| Footage | a link to the stored path; **missing-footage placeholder** when the path does not exist where the import runs (listed in `missingFootage`), a normal link when it does. The importer never opens footage. **An image sequence** (its alias is the folder) becomes a sequence of its files, named from the stored numbering (`shot_0101.exr` .. `shot_0103.exr` in 603, `seq_001.png` .. in 353), at its native frame rate; the first file is the footage's path and the one checked. Sequences and clips take the stored native frame rate and duration (603, 606). A sequence of more than 100 000 frames, or beyond a million file names in one import, links only its first file and is reported. **One layer of a Photoshop file** shows that layer (`Footage::layer`: its index and name; at its own size with Retain Layer Sizes, else on the document) and is named `<layer>/<file>` unless the item has a name; a layer without pixels (an adjustment layer) keeps the merged image and is reported; so is a layer as big as the document but not at its corner, which the item's size does not tell apart (shown at its own size), and an item as big as neither the layer nor the document (shown on the document) |
|
||||
|
||||
Units, the "absent = default" rule and the keyframe templates are the reader's
|
||||
([aep-format.md](aep-format.md), sections 3.3–3.6). The importer converts them to EffectCraft's:
|
||||
@@ -135,7 +142,15 @@ Units, the "absent = default" rule and the keyframe templates are the reader's
|
||||
text does not have; the runs are cut to the text. A range style splits `Hello` into runs of 1,
|
||||
2 and 2 characters (248, 340).
|
||||
- **Null objects.** The hidden placeholder solid of a null object is not an item (null layers
|
||||
have no source item here).
|
||||
have no source item here). A null's Anchor Point that is not stored is its top-left corner
|
||||
`[0, 0]`, not the centre, as After Effects 2026 reads it back (#3099).
|
||||
- **Placed Illustrator and PDF files** (`.ai`, `.pdf`; `Footage::placed_page`): the page is
|
||||
framed as After Effects places it (the ArtBox clipped to the crop box, `/Rotate` ignored) and
|
||||
drawn to fill the stored footage size, which masks and anchors are measured against (#3099).
|
||||
Create Shapes from Vector Layer on such a layer adds a hidden `<layer> Frame` shape layer, the
|
||||
Alpha track matte of every layer it makes, so they show only the frame, as the footage does.
|
||||
EPS files keep their bounding box at their own size: how After Effects sizes them is not
|
||||
measured.
|
||||
- **Track mattes.** The mode and the matte layer's id are in the target layer's header
|
||||
(aep-format.md 3.1). The matte layer can be any layer of the composition (407: below its
|
||||
target); it keeps its stored video switch (After Effects switches it off when the matte is set,
|
||||
@@ -180,7 +195,9 @@ parameter gets the file's value:
|
||||
| Batch 6 (903 defaults, 904 / 905 set values): `ADBE Brightness & Contrast 2`, `ADBE Turbulent Displace`, `ADBE Fractal Noise`, `ADBE Laser` (Beam), `CC Radial Fast Blur`, `ADBE Noise2`, `ADBE Pro Levels2` (Levels (Individual Controls)), `ADBE Corner Pin`, `ADBE Grid`, `ADBE Sharpen`, `ADBE Linear Color Key2`, `ADBE Radial Wipe`, `ADBE Remove Color Matting`, `CC Bend It`, `CC Power Pin`, `ADBE 4ColorGradient`, `ADBE Change Color`, `ADBE Extract`, `ADBE Noise HLS2`, `ADBE Scatter`, `CC Light Sweep`, `ADBE Compound Blur`, `ADBE Displacement Map`, `ADBE Set Matte3`, `ADBE Gaussian Blur` (legacy), `ADBE OFMotionBlur` (Pixel Motion Blur), `ADBE Layer Control` | the EffectCraft effect of the same name, every parameter EffectCraft has | sliders as stored (each checked against its valid maximum); Beam Length, Time and Softness and CC Power Pin Perspective are 0..1 fractions, percentages here; points as layer pixels; popups by item name (Set Matte's `Red Channel` ... is `Red` ...); twirl-down group markers and the Levels histogram carry nothing; **layer parameters** (below) | 903, 904, 905; 220, 320, 321 |
|
||||
| `Cryptomatte` (the Cryptomatte plug-in) 1 Settings data, 2 Output, 3 Selection Mode | `ec.3d.cryptomatte` `layerName`, `selection`; `display`, `matteOnly` | the layer name and the selection text from the data, verbatim (`CryptomatteData`; Layer Name picks the footage's Cryptomatte layer of exactly that name, the Layer popup is the fallback; the `<hex>` selection tokens are EffectCraft's selection grammar); Output Colors -> Display Colors, Matted RGBA -> Display Matte, Matte Only -> Matte Only on (Display Matte); **Matted Colors** (the ID colours keyed by the selection) has no counterpart: imported as Display Matte (the same matte over the layer's own colours) and reported; no Output record is the plug-in's default Colors (also for a borrowed definition; Colors too for an Output that is keyed, has an expression or cannot be read, each reported); Selection Mode (the plug-in's picking switch) is reported when on, also in the default-state record After Effects writes for it; keys or expressions on any of them are reported; unreadable data is damaged data (no layer, no selection). **Render-compared** with After Effects (1400): with a chosen layer and a picked object (the two preset compositions, Output Colors) EffectCraft's matte covers exactly the pixels After Effects draws as selected (6 012 fully covered for the box on CryptoObject, 10 962 for box and disc on CryptoMaterial, the same counts; on the 190 edge pixels of the latter the coverage implied by After Effects' frame is EffectCraft's within 0.0002); After Effects' Colors draws the selection as `c * (1 - m) + m` (a fully selected pixel white), EffectCraft's Colors as `c * (1 - m / 2) + m / 2`, and the ID colours themselves differ, so Colors is not compared per pixel. With the fresh data (empty selection, every matte 0): Matted RGBA matches (transparent); Matte Only differs in alpha (After Effects: the matte in RGB, opaque; EffectCraft: the matte in RGBA, so the same over black and as a luma matte, not as an alpha matte); Matted Colors is transparent in After Effects and in the imported Matte; Colors is a preview of the IDs and not compared per pixel (After Effects' colours are near black and, with no layer chosen, show the footage's first Cryptomatte layer, CryptoMaterial in 1400, where EffectCraft's Layer popup starts at CryptoObject); what Selection Mode draws is not reproduced | aep-format 3.7 (88 instances of one project; 1400); test `import::cryptomatte` |
|
||||
| Importer block 2026-11-05 (`1100-quickwins.aep`: per effect defaults, set values, minimum and maximum, every popup item, a keyed and an expression parameter): `ADBE HDR ToneMap` (HDR Highlight Compression) 1 Amount; `ADBE Echo` 1 Echo Time, 2 Number Of Echoes, 3 Starting Intensity, 4 Decay, 5 Echo Operator; `ADBE Linear Wipe` 1 Transition Completion, 2 Wipe Angle, 3 Feather; `ADBE Offset` 1 Shift Center To, 2 Blend With Original; `ADBE Deflicker` (Color Stabilizer) 1 Stabilize, 2-4 Black / Mid / White Point, 5 Sample Size | `ec.utility.hdrcompression` `amount`; `ec.time.echo` `echoTime`, `numberOfEchoes`, `startingIntensity`, `decay`, `echoOperator`; `ec.transition.linearwipe` `completion`, `angle`, `feather`; `ec.distort.offset` `shift`, `blend`; `ec.color.colorstabilizer` `stabilize`, `blackPoint`, `midPoint`, `whitePoint`, `sampleSize` | percent (float, valid 0..100, default 100); seconds (float, valid ±30 000), integer (valid 30 000), 0..1 (float), Decay as stored (float, valid 0..100), popup by name: EffectCraft's Echo uses at most 255 echoes and a Decay up to 1, a value or key beyond that is imported as stored and **reported**; percent (fixed, 100), degrees, pixels (fixed, 32 000); point in layer pixels, Blend 0..1 x 100 = percent; popup by name (**fresh default Levels**), points in layer pixels, Sample Size as stored (fixed, valid 50, default 3; its unit, pixels here, is not verified). Color Stabilizer's reference frame (Set Frame) is not a parameter in the file and is not imported (EffectCraft's Reference Frame stays 0 s) | `tests/it/import/effects_quickwins.rs`; render comparison below |
|
||||
| Importer block 2026-11-05 (`1300-thirdparty.aep`), plug-ins that ship with After Effects: `CS Color Neutralizer` (CC Color Neutralizer) 19 Pinning, 20 Blend w. Original; `EXtractoR` 1 Channel Info data, 3 Black Point, 4 White Point, 5 UnMult; `IDentifier` 1 Channel Info data, 2 Display, 3 ID | `ec.color.cccolorneutralizer` `pinning`, `blendWOriginal`; `ec.3d.extractor` `red`, `green`, `blue`, `alpha`, `blackPoint`, `whitePoint`, `unMult`; `ec.3d.identifier` `channelType`, `display`, `id` | CC Color Neutralizer: percent (float, valid 100); its correction is the Red / Green / Blue slider of each range (the Unbalance colours are pickers that set them and keep no record), with View and Black / White Point: **EffectCraft's effect has no counterpart, each is reported when stored** (EffectCraft's Unbalance / Balance stay at their identity defaults). EXtractoR: each output channel by the footage channel's name, which EffectCraft's EXtractoR reads by the same name (the default R, G, B; `Z` from our preset), and `(copy)` as the input's own channel of that position (EffectCraft's R, G, B or A; the default Alpha is A); a record of another kind or without a name is reported and R, G, B, A used (`ExtractorChannels`), Black and White Point as stored (float, valid ±3.4e38; a value beyond ±1e9 is reported as damaged data, as for every number), UnMult a checkbox, Clip stays off. IDentifier: Display by name, ID as stored (integer, valid 2 147 483 647); the Channel Info names the chosen channel (`identifier_channel_type`): After Effects lists only the footage's integer (UINT) channels, and with none chosen (the data then says `Object ID`) it leaves the layer unchanged, where EffectCraft draws the footage's object ID channel: **reported**; a chosen channel named `ObjectID` or `MaterialID` (without regard to case, `objectId` in our preset) is the one EffectCraft's IDentifier reads itself (Channel Type Object ID or Material ID), any other name is reported (EffectCraft's IDentifier has no channel choice) | `tests/it/import/effects_thirdparty.rs`; render comparison below |
|
||||
| `ADBE Slider Control`, `ADBE Angle Control`, `ADBE Checkbox Control`, `ADBE Color Control`, `ADBE Point Control` (parameter 1) | `ec.control.slider` `slider`, `ec.control.angle` `angle`, `ec.control.checkbox` `checkbox`, `ec.control.color` `color`, `ec.control.point` `point` | float (valid max 1 000 000) as stored; degrees; checkbox; colour; point in layer pixels | 071, 073, 217-219, 221, 304, 310 |
|
||||
| `ADBE Lumetri` by match-name suffix (1200), not by index: -0062 High Dynamic Range; -0063 HDR White; -0007 / -0008 Temperature / Tint; -0020 Saturation; -0011 Exposure; -0012 ... -0016 Contrast, Highlights, Shadows, Whites, Blacks; -0064 HDR Specular; -0026 Look Intensity; -0028 Faded Film; -0029 Sharpen; -0030 Vibrance; -0031 Saturation; -0033 Tint Balance; -0074 Show Mask; -0075 mask style; -0076 Invert Mask; -0081 Denoise; -0082 Blur; -0086 ... -0090 the HSL correction's Temperature, Tint, Contrast, Sharpen, Saturation; -0051 ... -0054 Vignette Amount, Midpoint, Roundness, Feather; -0073 the HSL key data; -0039 the first of the four RGB curves; -0042 Hue vs Sat; -0047 the Shadows, Midtones and Highlights wheels | `highDynamicRange`, `basicCorrection/tone/hdrWhite`, `basicCorrection/whiteBalance/temperature` / `tint`, `basicCorrection/saturation`, `basicCorrection/tone/exposure`, `.../contrast`, `highlights`, `shadows`, `whites`, `blacks`, `hdrSpecular`, `creative/lookIntensity`, `creative/adjustments/fadedFilm`, `sharpen`, `vibrance`, `creativeSaturation`, `tintBalance`, `hslSecondary/key/showMask`, `maskStyle`, `invertMask`, `hslSecondary/refine/denoise`, `blur`, `hslSecondary/correction/*`, `vignette/*`; `hslSecondary/key/hue*`, `saturation*`, `lightness*`; `curves/rgbCurves/master`; `curves/hueSaturationCurves/hueVsSat`; `colorWheels/shadowsWheel`, `midtonesWheel`, `highlightsWheel` | sliders: the stored number x 1 (valid maxima 1000, 300, 300, 7, 150, 200, 150, 100, 100, 300, 150, 100, 1000, 5, 100 as in `pard`); checkboxes and the popup as elsewhere, **also from records in the default state** (`tdsb` 3); the key: hue centre x 360 degrees, range x 360 as the half width, tolerance x 360 as the softness, saturation and lightness centre +- range x 100 percent, tolerance x 100, once a colour was picked; the master curve's points as `x,y` (0..1); Hue vs Sat's points with y as `0.5 + y / 2` (After Effects' y 1 doubles the saturation, EffectCraft's y 1 too); a wheel's hue (degrees) and distance as the HSL colour of that hue with saturation = distance and lightness 0.5 | 1200: all 62 read-backs of mapped controls in sections that act equal after import (min, max, sections, popups); the other 61 lie in sections that do nothing and are reported; keys and the expression on HDR White (`-56-keys`, `-57-expr`); the presets `-60-preset-curves-rgb`, `-61-preset-curves-huesat`, `-62-preset-wheels` |
|
||||
|
||||
- **Nothing to import:** the implicit input-layer parameter (index 0), the Swap Colors buttons
|
||||
(Tint 4, Ramp 8: they always have a record and hold no value), Exposure's twirl-down group
|
||||
@@ -232,6 +249,44 @@ parameter gets the file's value:
|
||||
column just outside the layer: After Effects' Glow draws a dark halo about 10 px wide outside the
|
||||
layer (red 0.55, 0.65, 0.74, 0.92 at 0, 1, 2, 5 px), EffectCraft's Glow none (an effect
|
||||
difference, recorded for a separate fix).
|
||||
- **Lumetri Color** (1200, `crates/aep/src/import/lumetri.rs`). The parameters are found by match
|
||||
name (After Effects inserted `-0127` ... `-0130` mid-list; five tone sliders share one valid range,
|
||||
so an index shift would not be caught). Values come 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. EffectCraft's Lumetri has no section switches: a section that is off in After Effects
|
||||
(Basic Correction, Creative, Curves / RGB Curves, each hue / saturation curve, Color Wheels, HSL
|
||||
Secondary, Vignette), and HSL Secondary without a picked key colour, does nothing there, so its set
|
||||
values are listed in one report line and not imported (the frame stays as After Effects draws it).
|
||||
The Input LUT and the Look are reported by name (LUT files are not in the project). Controls
|
||||
without a counterpart (the Auto tone's Intensity, RGB Curves' HDR Range, Color Wheels' HDR White and
|
||||
Face Detection, Legacy Tone And Saturation, Auto Color, SpeedGrade Custom Layer), the eyedropper
|
||||
colours, the red, green and blue curves (only the master curve is sampled, so which is which is
|
||||
not known), the colour wheels' brightness sliders and their fourth wheel, Split Toning, the HSL
|
||||
correction wheels and moved points of the other four hue / saturation curves (not sampled) are
|
||||
reported when set. The master RGB curve arrives as its points; Hue vs Sat with y as `0.5 + y / 2`
|
||||
(After Effects' y is the offset from neutral, 1 doubling the saturation; EffectCraft's y = 1
|
||||
doubles it); a colour wheel's hue and distance as the HSL colour of that hue with saturation =
|
||||
distance, lightness 0.5 (EffectCraft's wheels have no brightness slider). 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. Render check at 0.5 s against After
|
||||
Effects' 32 bpc PNGs (stored as value x 6553.5 in 16 bits, so read as raw / 6553.5): every
|
||||
composition whose grade After Effects draws as identity (defaults, set-all with every section off,
|
||||
the switched-off sections, the popups, keys, expression) is within 1/255 in EffectCraft; the
|
||||
graded ones differ by EffectCraft's Lumetri formulas, not by the import: White Balance (sec-color:
|
||||
After Effects warms and brightens, max 0.10 above the default; EffectCraft darkens, mean error
|
||||
0.015), Exposure / tone (sec-light: After Effects rolls highlights off and lifts the blacks to 0.18,
|
||||
EffectCraft clips at 1 and keeps black at 0; mean 0.12), Creative adjustments (sec-adjustments:
|
||||
Faded Film lifts black to 0.07 in After Effects, 0.035 here) and the extremes (min / max: mean 0.063 /
|
||||
0.454; max: After Effects' vignette at +5 and the tone sliders give mid grey, EffectCraft white).
|
||||
The 32 bpc PNGs hold the pixel value with no transfer curve (the defaults' ramp reads x / 320
|
||||
exactly), so values above 1 survive up to about 10; the comparison clips After Effects at 1 as
|
||||
EffectCraft's 8-bit frame does. The hand-made presets: the master RGB curve within 0.024 (mean
|
||||
0.009; After Effects' curve is a natural cubic spline through the points, within 0.003, while
|
||||
EffectCraft's Lumetri interpolates monotone); Hue vs Sat mean 0.008, max 0.18 (After Effects
|
||||
scales the saturation in linear light, red reaching 1.26; EffectCraft in display values, 8.3 % of
|
||||
pixels > 2 levels); the colour wheels mean 0.042, max 0.13 (the hues go the same way: Shadows
|
||||
lift blue at black by 0.13 in After Effects, 0.17 here; Highlights add green; the brightness
|
||||
sliders are not imported); the Input LUT and the Look are not imported (mean 0.088 / 0.076).
|
||||
- **Colour.** After Effects stores the colour its picker shows as ARGB doubles 0..255 in the
|
||||
project's working-space encoding; EffectCraft's parameter colours are straight RGBA 0..1 that the
|
||||
render pipeline hands to the effect as stored (`crates/render/src/color.rs`: authored colours are
|
||||
@@ -263,30 +318,54 @@ parameter gets the file's value:
|
||||
`approximated_keyframes`.
|
||||
- **The fx switch** of each instance (the `tdsb` of its value group: 1 on, 0 off; `fx-switch.aep`)
|
||||
is imported and exported, and so is an expression on a stored effect parameter (its text and its switch).
|
||||
- **Effects without a counterpart** (third-party plug-ins, pseudo effects of expression controls,
|
||||
`Pseudo/…`) arrive as **placeholders** (#3087): an effect group whose effect id is the After
|
||||
Effects match name (expressions also find it by that name, `effect("Pseudo/0.25")`; its match id
|
||||
is that name as one path segment, `Pseudo_0_25`). Like a missing plug-in it renders nothing and is listed by Find Missing
|
||||
Effects. It holds the effect's parameters flat (without the twirl-down group markers), named as
|
||||
in After Effects, each with its value, keys and expression, in the units After Effects'
|
||||
expressions read (sliders and angles as stored, a float slider with its range, points in layer pixels, colours, checkboxes,
|
||||
popups over the file's items with separators, read 1-based; layer parameters name their layer,
|
||||
without a record the layer itself as for Layer Control, and their keys and expression are
|
||||
reported),
|
||||
so expressions that read them (`effect("Settings")("Speed")`) keep working. A parameter's id is
|
||||
its match name (`CC Cylinder-0003`), or `param<index>` when that name is no path segment (a
|
||||
pseudo effect's `Pseudo/0.25-0001`). Paths and arbitrary data are not kept; a popup whose
|
||||
definition is borrowed, or whose default slot holds no item, and that has no value record takes
|
||||
its first item (reported). One import keeps at
|
||||
most 200 000 placeholder parameters and popup items (a popup at most 1024 items; parameters it
|
||||
does not keep count, and an item one more per 16 bytes of its name); the rest is
|
||||
reported, and once that is spent a later effect without a counterpart arrives without a
|
||||
placeholder (reported). A placeholder is still reported in `effects` and counted in
|
||||
`imported.placeholderEffects`; .aep export reports it and leaves it out.
|
||||
|
||||
## What is reported
|
||||
|
||||
`ImportReport` (`serde`, returned as JSON) has `imported` (counts, including `textLayers`),
|
||||
`losses` (per category: `count` exact, up to 20 `details`), `missingFootage`, `missingFonts`
|
||||
(PostScript names; not losses), `limitations` and `aeVersion`. The categories:
|
||||
`losses` (per category: `count` exact; up to 20 `details`, plus one line per unmapped effect with its
|
||||
number of instances, the most used first; `unlisted`, how many of the `count` no line describes),
|
||||
`missingFootage`, `missingFonts`
|
||||
(PostScript names; not losses), `limitations` and `aeVersion`. The losses prompt shows each
|
||||
category with how many occurrences are left out or changed, every detail line, and "… and N more"
|
||||
for the `unlisted` ones. The categories:
|
||||
|
||||
| Category | Counted |
|
||||
|---|---|
|
||||
| `effects` | each effect on an imported layer that is not in the table (by name and match name); for an imported effect: each stored parameter outside the table, a parameter whose type or valid maximum differs from the table, keys or an expression on a layer parameter, an effect without readable parameter definitions |
|
||||
| `effects` | each effect on an imported layer that is not in the table (by name and match name; it arrives as a placeholder, "kept without rendering", unless it has no readable parameter definitions); for an imported effect: each stored parameter outside the table, a parameter whose type or valid maximum differs from the table, keys or an expression on a layer parameter, an effect without readable parameter definitions |
|
||||
| `masks` | a mask that is not imported because its path cannot be used (a Mask Path entry without shapes, a mask on a text or shape layer whose path was never set (its default is not sampled), unreadable, no vertices, a point count that is not a multiple of three, more than 65 536 vertices, numbers that are not finite), a mask mode code outside the seven known ones (imported as Add) |
|
||||
| `layer_styles` | per layer: a style in switch state 3 (not sampled; state 0 is imported as a style switched off, 988), Pattern Overlay, an unknown style or parameter, a gradient (`GCst`, not decoded: EffectCraft's default gradient stays), keys on a popup or checkbox, a value that does not fit, styles on a layer that takes none here (adjustment, camera, light) |
|
||||
| `markers` | a marker with an unknown cue type (imported as an event cue) |
|
||||
| `text_layers` ("text layer features") | per text layer, what of it is not imported: wiggly and expression selectors and unmapped animator properties, Path Options or More Options with set values, Source Text keyframes or an expression, character style or paragraph fields that are not decoded and differ from the default (named by their slot, e.g. `/68`), an unknown capitalisation or justification code, manual kerning, No Break, the stroke line join, the Japanese leading model, a digit set other than the default and Arabic, control characters of unknown meaning (they are kept in the text; U+0003 is not one of them, see Text above). The text layer itself is imported |
|
||||
| `shape_layers` ("shape layers") | what of a shape layer is not imported: shape items other than groups, rectangle, ellipse, polystar, path, fill, stroke, gradient fill and stroke, trim paths, round corners, merge paths, repeater and offset paths (for example wiggle or twist), stroke dashes, taper and wave with values, item blend modes and composite order, shape direction, gradient colour stops other than the default, gradient highlight values other than 0, material options, groups nested deeper than 32, a line cap or join of the unsampled third kind (imported as it), a path without a readable path. The layer itself is imported |
|
||||
| `text_layers` ("text layer features") | per text layer, what of it is not imported: wiggly and expression selectors and unmapped animator properties, Path Options or More Options with set values, Source Text keyframes or an expression whose flags are unreadable, character style or paragraph fields that are not decoded and differ from the default (named by their slot, e.g. `/68`), an unknown capitalisation or justification code, manual kerning, No Break, the stroke line join, the Japanese leading model, a digit set other than the default and Arabic, control characters of unknown meaning (they are kept in the text; U+0003 is not one of them, see Text above). The text layer itself is imported |
|
||||
| `shape_layers` ("shape layers") | what of a shape layer is not imported: shape items other than groups, rectangle, ellipse, polystar, path, fill, stroke, gradient fill and stroke, trim paths, round corners, merge paths, repeater and offset paths (for example wiggle or twist), stroke dashes, taper and wave with values, item blend modes and composite order, shape direction, gradient colour stops other than the default, gradient highlight values other than 0 (only from written records: one in the default state holds the default), material options, groups nested deeper than 32, a line cap or join of the unsampled third kind (imported as it), a path without a readable path. The layer itself is imported |
|
||||
| `lights` | each environment light (the layer is skipped; the other layers of the composition are imported) |
|
||||
| `blend_modes` | a blend mode code that is not mapped (1, 3 = Dissolve or Dancing Dissolve, 21 / 22 = Alpha Add or Luminescent Premul, codes above 34; the layer stays Normal) |
|
||||
| `transform_properties` | a stored transform property that is not imported (keyed or expression-driven orientation, separated position dimensions), and the stored camera and light options that are not mapped (Blur Level, Iris, Falloff, ...) |
|
||||
| `transform_properties` | a stored transform property that is not imported (an orientation that cannot be read, or set on a layer without one, separated position dimensions), and the stored camera and light options that are not mapped (Blur Level, Iris, Falloff, ...) |
|
||||
| `time_remapping` | an enabled Time Remap without a readable keyframe |
|
||||
| `approximated_keyframes` | a key imported in a simpler form (unknown interpolation code, bezier ease that cannot be decoded: a colour key with a non-zero ease, non-finite ease data; a mask path key with a speed other than 0, imported unverified) |
|
||||
| `parent_links` | a parent that was not imported (a skipped camera, a missing layer) or that forms a loop |
|
||||
| `track_mattes` | a track matte of unknown mode, whose matte layer was not imported or does not exist, a layer matted by itself, or a matte of the older "layer above" form on the top layer |
|
||||
| `unsupported_layers` | an unknown layer type, a layer without a usable source, a precomposition loop (the layer is cut) |
|
||||
| `unsupported_items` | a project item of an unknown kind |
|
||||
| `unsupported_items` | a project item of an unknown kind; an item made from a Photoshop layer without pixels (it shows the merged image), or from one as big as the document but not at its corner (shown at its own size), or an item as big as neither the layer nor the document (shown on the document) |
|
||||
| `damaged_data` | a header, key or value that could not be read, numbers that are not finite or beyond ±1e9 (default used); an unreadable text document (the text layer is imported with empty text), damaged style runs |
|
||||
|
||||
`limitations` states what the reader cannot see at all, so it can be neither imported nor counted:
|
||||
@@ -352,6 +431,26 @@ ranges of Lumetri Color among them), are those of the 2026 plug-in in all three
|
||||
over 25 (EffectCraft's samples drift, After Effects' frame stays unchanged) and with keyed
|
||||
sample points. The renders do not exercise a Decay above 1 (After Effects' set value has
|
||||
Starting Intensity 0) or what Color Stabilizer corrects (the test card does not flicker).
|
||||
- `crates/aep/tests/it/import/effects_thirdparty.rs` (#3112): CC Color Neutralizer, EXtractoR and
|
||||
IDentifier built into `synthetic.aep` like the #3110 effects. Render check of the 29 compositions
|
||||
of `1300-thirdparty.aep` (as above): CC Color Neutralizer at its defaults and the minimum and
|
||||
maximum compositions match within one level (no composition isolates Pinning: the maximum has
|
||||
Blend w. Original 100, which shows the original), the compositions with reported Red /
|
||||
Green / Blue corrections or View differ as reported (up to 5 levels, View 25); EXtractoR at its
|
||||
defaults and with UnMult within 3 levels on 6 % of the pixels, keyed and expression Black Point
|
||||
within one level, the ±3.4e38 compositions differ (reported as damaged data). IDentifier: After
|
||||
Effects' frame is the unchanged EXR in every variant (its default Object ID finds no channel in
|
||||
that EXR), while EffectCraft's IDentifier finds the EXR's `ID` channel and draws it; the import
|
||||
reports this for every IDentifier. The follow-up presets (our own): `extractor-07-preset-channel-z`
|
||||
(`Z` into Red, Green and Blue) imports without a loss and renders equal to After Effects (within
|
||||
3e-5 at After Effects' 1/10 PNG scale; the background's Z of 100 clips there in both). IDentifier
|
||||
needs an integer channel: block 1310 (`1310-identifier-uint.aep`, the EXR with UINT channels
|
||||
`objectId` and `IndexOB.X` added) with the preset `identifier-09-preset-channel-objectid-id2`
|
||||
(`objectId`, ID 2) imports without a loss, and EffectCraft's IDentifier finds the same channel:
|
||||
the ID regions are the same pixels (8 449, 2 400, 1 515, 1 256, 780), the colours differ (After
|
||||
Effects draws flat colours per ID and does not highlight the chosen ID, EffectCraft draws its
|
||||
own ID colours with the chosen ID white). At its defaults (no channel chosen) After Effects
|
||||
leaves the EXR unchanged in 1310 too.
|
||||
- `crates/aep/tests/it/import/combined.rs`: masks and effects on the same layer, an effect on a text
|
||||
layer, and the same through `.aepx` (built in-test from the fixtures: the effect parade of one
|
||||
fixture grafted onto the layer of another).
|
||||
@@ -394,6 +493,10 @@ ranges of Lumetri Color among them), are those of the 2026 plug-in in all three
|
||||
- `crates/aep/tests/it/import/effects_round3.rs`: the 24 effects of 905 with their values and After
|
||||
Effects' defaults, the layer parameters of 905 and the Layer Controls of 220, 320, 321, hostile
|
||||
layer ids (missing, 0, out of range) reported or neutral.
|
||||
- `crates/aep/tests/it/import/text_synthetic.rs` (the committed `tests/fixtures/text-synthetic.aep`,
|
||||
no corpus needed): an animator's Stroke Opacity, static and keyed; the Source Text expression,
|
||||
enabled and disabled, also after an export and a second import; and, as a guard, one whose `tdb4`
|
||||
flags are unreadable stays reported (#3170).
|
||||
- `crates/aep/tests/it/import/text_anim.rs`: the animators and range selectors of 579-594 and 114
|
||||
(every selector setting, every mapped property, 594's keyed Start with its ease), the character
|
||||
and paragraph settings of 538-578, 595, 596, and what is reported (548 a line join without a
|
||||
@@ -428,10 +531,10 @@ generated corpus and its case numbers, not a claim about what was or was not see
|
||||
|
||||
## Export
|
||||
|
||||
The other direction, File ▸ Export ▸ After Effects Project… (`file.exportAep`), is described in
|
||||
The other direction, File ▸ Export ▸ Adobe After Effects Project… (`file.exportAep`), is described in
|
||||
[aep-export.md](aep-export.md).
|
||||
|
||||
## Not done yet
|
||||
|
||||
Effects outside the table (Lumetri; EXtractoR: its channel choice is arbitrary data; Stroke: its path is a mask reference), environment lights, the ease of bezier Orientation keys, wiggly and expression selectors and the other text animator properties (Character Value, Character Alignment other than its default, the Hue / Saturation / Brightness / Opacity variants, 3D rotations, variable font axes), manual kerning, No Break, the stroke line join, the Japanese leading model, Source Text keyframes, layer-style gradients, Pattern Overlay and the switch state 3, Mesh Warp handles moved on their own,
|
||||
Lumetri Color's section switches (a switched-off section is reported and its values left out), its LUTs and looks (files After Effects names: reported by name), its red, green and blue curves (no sample tells which is which), the colour wheels' brightness sliders and the fourth wheel, Split Toning and the HSL correction wheels (not sampled: reported when set) and moved points of the four hue / saturation curves other than Hue vs Sat (the scale of y is not sampled); effects outside the table (EXtractoR: its channel choice is arbitrary data; Stroke: its path is a mask reference), environment lights, the ease of bezier Orientation keys, wiggly and expression selectors and the other text animator properties (Character Value, Character Alignment other than its default, the Hue / Saturation / Brightness variants and Fill Opacity, 3D rotations, variable font axes), manual kerning, No Break, the stroke line join, the Japanese leading model, Source Text keyframes, layer-style gradients, Pattern Overlay and the switch state 3, Mesh Warp handles moved on their own,
|
||||
separated dimensions and footage interpretation need verified layouts in the reader first.
|
||||
+85
-5
@@ -118,7 +118,7 @@ See [MCP conventions](mcp.md) for core tools, resources, strict arguments, and r
|
||||
| `list_effects {filter?, compact?}` | Effect ids, names, categories, GPU / 32-bpc support and parameters; `compact` lists only ids, names and categories. |
|
||||
| `list_fonts {query?, variable?, rescan?}` | Font families text layers can use, bundled and installed, with their styles, origin, whether each is a variable font and own-language name; `variable` lists only variable fonts; `rescan` picks up fonts installed since launch. |
|
||||
| `add_effect {layer, effect, values?, comp?}` | Apply an effect and set its parameters in one call and one undo step (a failing value leaves nothing applied); returns the instance path (`effects/#n`) and its parameter paths. |
|
||||
| `render_frame {comp?, time?, max_side?, path?, inline?, transparent?}` | Returns a PNG image of a frame; `transparent: true` keeps the alpha (as an RGB + Alpha render writes it) instead of compositing over the comp background. |
|
||||
| `render_frame {comp?, time? \| times?, columns?, labels?, max_side?, path?, inline?, transparent?}` | Returns a PNG image of a frame; `transparent: true` keeps the alpha (as an RGB + Alpha render writes it) instead of compositing over the comp background. `times` (1 to 64 comp seconds, not with `time`) returns one contact-sheet PNG instead, the frames tiled row by row in the order given: `columns` sets the grid width (default near-square), `max_side` applies to each cell (default 320; a sheet is at most 7680 pixels on a side), `labels` (default true) draws each cell's time. The metadata gives `comp`, `times`, `columns`, `rows`, `cellWidth`, `cellHeight`, `width` and `height`. `path` writes the sheet in a headless session; bridged, a sheet comes back inline only. |
|
||||
| `open_project {path \| demo \| new}` / `save_project {path?}` | Open and save files. |
|
||||
| `undo {steps?}` / `redo {steps?}` | History. |
|
||||
| `history {goto?}` | The branching undo history (History panel): every state with its index, id, label, branch depth and whether it's current; `goto` (an index or id) jumps to any state, on any branch, without losing the others. |
|
||||
@@ -129,7 +129,7 @@ See [MCP conventions](mcp.md) for core tools, resources, strict arguments, and r
|
||||
| `command_run {id, params?}` | Run a command. |
|
||||
| `command_batch {steps, stop_on_error?}` | Run commands in order with per-step results; each edit has its own undo step. |
|
||||
| `doc_inspect` | Project overview and active composition. |
|
||||
| `render_preview` | Render a PNG without writing a file (`comp`, `time`, `max_side`, `transparent`). |
|
||||
| `render_preview` | Render a PNG without writing a file (`comp`, `time`, `max_side`, `transparent`), or a contact sheet of several times (`times`, `columns`, `labels`, as `render_frame`). |
|
||||
|
||||
Layers are referenced by id (from `get_comp`), `"#n"` (1-based index from the top) or name. Comps are
|
||||
referenced by id or name, and default to the active comp. An id may also be sent as a string (`"98"`):
|
||||
@@ -322,6 +322,10 @@ build real projects through these interfaces; they are worked examples of everyt
|
||||
then `keys.velocity {"outSpeed":0,"outInfluence":33.33}`.
|
||||
* **Gradients**: `set_property {"path":"contents/gfill/colors","value":["#0080ff","#ffff00"]}` or
|
||||
`{"colors":[[0,"#0080ff"],[1,[1,1,0]]],"opacities":[[0,1],[1,0.5]]}`.
|
||||
* **Projects**: `file.import` takes `.ecproj` / `.ecprojx` and After Effects projects (`.aep`, `.aepx`)
|
||||
too, each into a folder named after it (`projects` in the result). An After Effects project that
|
||||
would lose parts is refused (an `errors` entry, nothing imported): `file.importAep {"path": …,
|
||||
"dryRun": true}` lists the losses, `"allowLosses": true` imports the rest.
|
||||
* **Image sequences**: `file.import {"paths":["/r/shot_0001.png"], "sequence": true}` imports the
|
||||
file's whole numbered run as one item (several picked frames: that range; a folder: each run in
|
||||
it, also without `sequence`). Without `"sequence": true` a picked or dropped numbered file imports
|
||||
@@ -488,8 +492,9 @@ between the layer's In and Out points in the background ("Analyzing in backgroun
|
||||
then "Stabilizing..."; `warp.status` reports `progress`, `banner`, whether the effect is
|
||||
`analyzed`, and the current frame's `warp` matrix, `autoScale` and `crop`); `warp.cancel` stops it
|
||||
without writing anything. The analysis is stored in the effect (saved with the project) and is
|
||||
one undo step; trimming, slipping, time-remapping or replacing the layer's source clears it and
|
||||
the app re-analyses automatically (headless: run `warp.analyze` again). Settings are ordinary
|
||||
one undo step; trimming, slipping, time-remapping or replacing the layer's source, editing its
|
||||
masks, the effects above the Warp Stabilizer or, on a precomp layer, the precomp's contents
|
||||
clears it and the app re-analyses automatically (headless: run `warp.analyze` again). Settings are ordinary
|
||||
properties under the instance's groups, e.g. `effects/#1/stabilization/result` (0 Smooth Motion,
|
||||
1 No Motion), `effects/#1/stabilization/smoothness`, `effects/#1/stabilization/method`,
|
||||
`effects/#1/borders/framing` (0 Stabilize Only … 3 Stabilize, Synthesize Edges),
|
||||
@@ -536,7 +541,8 @@ Each edit is one undo step; the first create adds the one-node "3D Tracker Camer
|
||||
startFrame?: 1, includePoints?: true}` exports the solve as an executable Blender scene script:
|
||||
baked camera/lens/focus, synced video background, points and ground. See
|
||||
[Blender camera export](blender-camera-export.md) for framing, assumptions and limitations.
|
||||
Changing the layer's frames clears the analysis; changing `effects/#1/shotType`,
|
||||
Changing the layer's frames, masks, the effects above the tracker or a precomp's contents
|
||||
clears the analysis; changing `effects/#1/shotType`,
|
||||
`effects/#1/horizontalAngleOfView`, `effects/#1/advanced/solveMethod` re-solves the stored tracks.
|
||||
|
||||
```sh
|
||||
@@ -713,6 +719,11 @@ in the Plug-in Library: [publishing-plugins.md](publishing-plugins.md).
|
||||
step (separated X/Y/Z Position; Orientation and X/Y/Z Rotation on 3D layers).
|
||||
* Rename a layer with Enter (or double-click a name): the whole name is selected, Enter or a
|
||||
click elsewhere commits, Escape cancels; `layer.rename {layer, name}` does it directly.
|
||||
A text layer's name follows its text until it is renamed; renaming it to an empty name makes
|
||||
it follow the text again (After Effects). `layer.setText` and typing on canvas rename a text
|
||||
layer whose name still follows its text; `layer.newText {name}` and a name set by
|
||||
`layer.settings` or a script's `layer.name = …` (which runs `layer.rename`) stay, and an empty
|
||||
`layer.settings` name follows the text again too.
|
||||
* Ctrl/Cmd-click on the current-time display (Timeline or Composition panel) toggles the Time
|
||||
Display Style between Timecode and Frames: `file.projectSettings {"timeDisplay": "frames"}`.
|
||||
* `prop.pickWhip {layer, prop, target: {layer, prop, dimension?}}` links a property as its
|
||||
@@ -856,6 +867,75 @@ not read or written.
|
||||
`<comp>` is an id or name, or `-` for the active comp. `<value>` is JSON (`50`, `[960,540]`,
|
||||
`"#ff0000"`) or a bare string.
|
||||
|
||||
## Repro scripts
|
||||
|
||||
Every journaled command the app runs (menus, panels, shortcuts, agents, scripts) goes into the session's
|
||||
command journal. A **repro script** is that journal as JSON lines, so a bug or crash becomes
|
||||
something an agent can replay and a regression test becomes a data file.
|
||||
|
||||
```text
|
||||
// comments: lines starting with // or #; blank lines are skipped
|
||||
{"start": "new" | "demo" | {"project": "rel/or/abs.ecproj"}, "allow_errors": false} optional, first line
|
||||
{"command": "comp.new", "params": {"name": "Main", "frameRate": 30}}
|
||||
{"command": "comp.settings", "params": {"frameRate": 0}, "expect_error": "frameRate"}
|
||||
{"expect": {"query": "comp.info", "pointer": "/frameRate", "equals": 30}}
|
||||
{"expect_no_error": true}
|
||||
```
|
||||
|
||||
- `start`: the project to begin with (default `new`). A relative `project` path is relative to the
|
||||
script. Other header keys (`version`, `app`, `os`, `crash`, `note`) are for people.
|
||||
- `command`: run like the app runs it. It must succeed, unless it has `expect_error` (`true`, or a
|
||||
text the error must contain) or the header says `allow_errors` (errors are reported but
|
||||
tolerated). A panic always fails the replay.
|
||||
- `expect`: run a command, normally a query (`prop.get`, `comp.info`, `layer.tree`, `project.summary`),
|
||||
and compare the value at the JSON `pointer` of its result with `equals` (numbers within 1e-6).
|
||||
Without `equals` it only checks that the query succeeds.
|
||||
- `expect_no_error`: the previous command succeeded.
|
||||
|
||||
Where scripts come from:
|
||||
|
||||
- `journal.export {path?}` returns `{script, steps}` (or writes `path`): the commands since the
|
||||
project was last replaced (`file.newProject`, `file.open`, `file.openDemoProject`). Queries and
|
||||
saves are not in it; commands a script or `file.runScript` ran are covered by that one step.
|
||||
A successful Revert starts a new replay boundary at the saved file it reopened.
|
||||
- `help.exportRepro {path?}` saves the same for a bug report, by default to `Repros/` in the
|
||||
settings folder (macOS `~/Library/Application Support/EffectCraft/Repros`, next to `Logs/`).
|
||||
Confined automation must give an explicit path inside a write root; `journal.export {}`
|
||||
remains available because it only returns the script in memory.
|
||||
- **Crash repros**: when the last-resort guard catches a panic in a command, the app writes
|
||||
`Repros/crash-<unix time>-<command>.jsonl` there automatically (the newest 20 are kept): the
|
||||
journal, then the command that panicked, with `"allow_errors": true` and the error in the header.
|
||||
The error the user or agent sees names the file. Headless sessions without a settings folder
|
||||
(the CLI, tests) don't write one.
|
||||
|
||||
Privacy: a repro holds command ids and parameters only, never project, image or media file
|
||||
contents. Parameters can include file paths, layer and item names and text typed into text
|
||||
layers, so read a repro before sharing it.
|
||||
|
||||
Replay one headless (a fully wired session, like the CLI's other subcommands):
|
||||
|
||||
```sh
|
||||
effectcraft-cli replay crash.jsonl # per-step ok/FAIL lines, then the first failure
|
||||
effectcraft-cli replay crash.jsonl --json # {ok, panicked, steps, first_failure, skipped}
|
||||
effectcraft-cli replay crash.jsonl --keep-going
|
||||
```
|
||||
|
||||
Replaying a repro runs its commands with your privileges and file access, including commands
|
||||
that write files or run scripts. Read repros from untrusted sources before running them, or use
|
||||
a disposable environment.
|
||||
|
||||
It exits with status 1 when a step fails, an expectation is not met or a command panics, and
|
||||
stops at the first failure unless `--keep-going`. Steps that work on the user's files (`file.open`
|
||||
of their project, imports) need those files; replace them with a `start` project or generated
|
||||
content when you turn a report into a test. A repro only sees commands: UI state changed
|
||||
without one (a hover, a half-finished drag) is not in it.
|
||||
|
||||
**Regression tests are data files.** A crash fix's regression test is the repro, trimmed to the
|
||||
steps that matter, with an `expect_error` or `expect` line where the fix changed the outcome,
|
||||
dropped into [`tests/repros/`](../tests/repros). `crates/host/tests/it/repros.rs` replays every
|
||||
`*.jsonl` there and fails on any panic or unmet expectation
|
||||
(`cargo nextest run -p effectcraft-host --test it -E 'test(/^repros::/)'`).
|
||||
|
||||
## Seeing the UI
|
||||
|
||||
To work on the UI, start the app with `cargo run -p effectcraft -- --control 9877` (add `--demo` to
|
||||
|
||||
+62
-30
@@ -18,10 +18,7 @@ cpal or muda. Everything in L0 to L4, the egui UI and the web app also build for
|
||||
| L0 | `time` | `Tick` (254 016 000 000 per second), rational frame rates incl. NTSC, SMPTE and drop-frame timecode |
|
||||
| L0 | `geom` | Vectors, matrices, quaternions, the layer transform (anchor, position, scale, orientation, rotation) |
|
||||
| L0 | `color` | sRGB and linear, HSL/HSV, luminance, the 38 blend modes, label colors |
|
||||
| L0 | `vp9enc` | VP9 encoder (profile 0, 8-bit 4:2:0, key and inter frames with motion search, loop filter and rate control; lossless at quality 100) for WebM export, from the VP9 bitstream specification |
|
||||
| L0 | `opusenc` | Opus encoder: RFC 6716 SILK (NB/MB/WB), hybrid (SWB/FB) and CELT (FB) modes chosen by bitrate and application (audio / voice), 48 kHz 20 ms packets, mono/stereo, and the RFC 7845 `OpusHead`, for WebM export audio |
|
||||
| L0 | `hevcenc` | HEVC (H.265) encoder from ITU-T H.265: Main / Main 10 4:2:0, IDR + P slices (quarter-pel motion, merge/AMVP), CABAC, deblocking, bitrate or constant-QP rate control, for MP4 (`hvc1`) export |
|
||||
| L0 | `av1enc` | AV1 encoder from the AV1 bitstream specification: Main profile 8/10-bit 4:2:0, key + inter frames (quarter-pel motion), deblocking, for MP4 (`av01`) and WebM export |
|
||||
| L1 | `raster` | Premultiplied float images, sampling, affine and projective warps, blurs, compositing (parallel with rayon) |
|
||||
| L1 | `keyframe` | Animated values, keyframes with temporal ease and spatial Bezier, roving, hold, velocity |
|
||||
| L1 | `segment` | Swappable trained models for Roto Brush and face tracking: the `MaskModel` and `FaceModel` interfaces, a registry of open-licensed models (authors, licence, URL, size, SHA-256), PyTorch checkpoint and TensorFlow Lite readers (with an interpreter), SHA-256, MobileSAM (TinyViT + Segment Anything decoder) and MediaPipe Face Landmarker (BlazeFace + Face Mesh V2) on rayon and ndarray's safe GEMM |
|
||||
@@ -43,7 +40,7 @@ cpal or muda. Everything in L0 to L4, the egui UI and the web app also build for
|
||||
| L3 | `interchange` | Premiere Pro interop through timeline interchange (FCP7 XML, FCPXML, OTIO, EDL, AAF, OMF via FilmCraft's `filmcraft-interchange`): sequences ↔ compositions (§6a) |
|
||||
| L3 | `aep` | After Effects projects in one crate (like `lottie`): `container` reads and writes the `.aep` container (RIFX `Egg!`) as a lossless chunk tree with a byte-identical round trip, bounded for hostile input, reads the `.aepx` XML form into the same tree, and has a typed view of items, layers, properties, effects, masks, shapes, text, markers, footage, cameras, lights and layer styles, from our own observations of generated files ([aep-format.md](aep-format.md)); `import` maps the typed view into a project (items, settings, compositions, every layer kind, Transform, keyframes, expressions, masks, text, shapes, cameras, lights, mattes, markers, time remapping, layer styles, the effects of a shared table, footage) with a report of everything not imported ([aep-import.md](aep-import.md)); `export` builds every chunk in code (prototypes in the layout After Effects writes, cloned and edited only at verified fields) for the same scope, with a report of everything not exported, verified by opening the output in After Effects ([aep-export.md](aep-export.md)) |
|
||||
| L3 | `lottie` | Lottie JSON / dotLottie import and export (layers, precomps, eased and spatial keyframes, shapes, masks, mattes) with a warnings list for what Lottie cannot express |
|
||||
| L3 | `plugin` | Effect plug-ins: loads sandboxed WebAssembly effect modules (plug-in API v1; the wasmi interpreter behind the `wasm` feature, on for native hosts) into the effect registry ([plugins.md](plugins.md)) |
|
||||
| L3 | `plugin` | Effect plug-ins: loads sandboxed WebAssembly effect modules (WebAssembly ABI v1 and v2, the latter with neighbouring frames; the wasmi interpreter behind the `wasm` feature, on for native hosts) into the effect registry ([plugins.md](plugins.md)) |
|
||||
| L3 | `helper-host` | External effect helpers: finds helper programs, runs each as a child process and registers its effects (plug-in API 2) over a versioned pipe + shared-file protocol; a crashing helper is an error on the effect, never a crash ([external-helpers.md](external-helpers.md), [ADR 0001](adr/0001-external-effect-helpers.md)). Native only |
|
||||
| L4 | `engine` | `Session`: project, branching undo history, editor state, the command registry and menus, the ScriptUI window model (`scriptui`) |
|
||||
| L4 | `script` | Scripting: JavaScript (boa) with an After Effects-style object model (`app.project`, comps, layers, properties, render queue) and ScriptUI (dialogs, palettes, dockable panels; script-host threads so dialogs block `show()`), whose edits run engine commands; AE match names |
|
||||
@@ -121,7 +118,7 @@ are not rewritten, so an expression that computes an effect point on a shape or
|
||||
other values may need `+ [thisComp.width / 2, thisComp.height / 2]`. Effects that use the layer
|
||||
bounds (edge pinning, wipes, Offset, generators) now cover the whole comp-sized bounds instead of
|
||||
their bottom-right quarter. Tracking data of effects that analyse footage (Warp Stabilizer, 3D
|
||||
Camera Tracker, Mocha shape) is left as it is: footage has a source rectangle, whose effect space
|
||||
Camera Tracker, Tracked Shape Matte) is left as it is: footage has a source rectangle, whose effect space
|
||||
did not change.
|
||||
|
||||
## 4. Rendering
|
||||
@@ -240,16 +237,43 @@ converted on demand with the HDR step and without the exposure; the Info panel s
|
||||
frame's float values. The browser keeps 8-bit frames for now (its frame workers send RGBA8).
|
||||
|
||||
A persistent **disk cache** (`render::disk_cache`, Settings ▸ Media & Disk Cache) backs both
|
||||
the layer cache and the viewer's RAM preview: layer buffers that were slow to render and every
|
||||
preview frame are written (LZ4, atomic rename, checksummed) under 128-bit content keys — the
|
||||
comp's content hash (the comp, what it uses, footage file size and time) plus frame, scale and
|
||||
view for frames; the layer key salted with the footage fingerprint for layers — so entries
|
||||
survive restarts and never go stale. Frame entries are 8-bit RGBA, or for 32 bpc viewer frames
|
||||
half floats with their bytes shuffled into planes before LZ4 (the entry header's format byte
|
||||
tells them apart; the render options in the key keep the two kinds apart). LRU eviction keeps it under the size limit; Edit ▸ Purge
|
||||
empties it, `cache.diskStats` reports it, and the timeline draws disk-only frames in blue. In the
|
||||
browser the frames live in the Origin Private File System: frame workers write them, the page
|
||||
keeps the index (`DiskIndex`) and reads hits ([web.md](web.md#disk-cache)).
|
||||
the layer cache and the viewer's RAM preview: layer buffers that were slow to render, every
|
||||
preview frame and Roto Brush / Object Matte segmentations are written (LZ4, atomic rename,
|
||||
checksummed) and survive restarts. The design (#3184):
|
||||
|
||||
- **Keys from memory.** A frame's key is its comp's content key plus frame, scale, view and
|
||||
render options; the content key hashes the project settings and the items the comp can draw
|
||||
(its layers but those that can't change the picture, nested comps, footage with its settings
|
||||
and paths, solids, Media Replacement footage, proxies: `Project::pixel_reach`). Expressions
|
||||
among them add what they can read: every layer of their comps, the markers, the comps and
|
||||
footage they name with literal names (`comp("Name")`, `footage("Name")`, and which items have
|
||||
those names), or the whole project when they compute a name or reach items another way
|
||||
(`expression_reach`). An edit to anything else keeps the key. A layer buffer's key is the
|
||||
layer cache's content key (which names the footage it uses with its paths) and the renderer
|
||||
version. Nothing here looks at a file, and nothing ever scans the project's files.
|
||||
- **File lists checked on hit.** Every frame and layer entry keeps the source files it was
|
||||
made of, with the size and modification time they were read with (the browser: size and
|
||||
content hash). An entry is used only while they are unchanged: one `metadata` call per listed
|
||||
file when it is read. A changed, missing or unreadable file, a damaged entry or an older
|
||||
format is a miss; the entry is deleted and the frame renders again and replaces it.
|
||||
- **Incomplete lists stay in memory.** A frame or buffer whose files can't all be named (a
|
||||
source that doesn't say what it read, more than 4096 files) is shown but never written.
|
||||
- **Reload Footage drops entries.** File ▸ Reload Footage deletes the frame and layer entries
|
||||
whose lists name the item's files (the writer thread reads each entry's list, not its
|
||||
pixels), so a file replaced by one with the same size and time is read again. A reload while
|
||||
the cache is off is remembered for the session and applied when it is switched on, before it
|
||||
is read. A render already under way offers entries made of the files as they were: each
|
||||
render takes the cache's drop generation before it reads, and an entry listing a file dropped
|
||||
after that is refused (`DiskCache::drop_generation`); the frame is shown, never stored.
|
||||
- **Edits keep what they can't change.** RAM frames outside an edit's dirty spans are kept
|
||||
(`Project::dirty_spans`), and their disk entries are linked to the comp's new key.
|
||||
|
||||
Frame entries are 8-bit RGBA, or for 32 bpc viewer frames half floats with their bytes shuffled
|
||||
into planes before LZ4 (the entry header's format byte tells them apart; the render options in
|
||||
the key keep the two kinds apart). LRU eviction keeps the cache under the size limit; Edit ▸
|
||||
Purge empties it, `cache.diskStats` reports it, and the timeline draws disk-only frames in blue.
|
||||
In the browser the frames live in the Origin Private File System: frame workers write them, the
|
||||
page keeps the index (`DiskIndex`) and reads hits ([web.md](web.md#disk-cache)).
|
||||
|
||||
**Deferred readbacks** (`effectcraft-gpu` `deferred`): where a readback can't be waited for (WebGPU
|
||||
in a browser worker) a frame renders in passes. `Accelerator::frame_begin` / `pass_begin` /
|
||||
@@ -304,7 +328,7 @@ before conversion (a quarter of the conversions; the result differs from convert
|
||||
in blocks where colours clip) and resampled from there, as other frames are from full size.
|
||||
|
||||
**GPU compositor** (`crates/gpu`; Project Settings ▸ Video Rendering and Effects ▸ Mercury GPU
|
||||
Acceleration, the default, or Mercury Software Only; `render.backend`). The CPU renderer is the
|
||||
Acceleration, the default, or Software Rendering; `render.backend`). The CPU renderer is the
|
||||
reference and keeps rendering layer content (sources, masks, CPU effects, layer styles) into the
|
||||
layer cache. The render crate defines an `Accelerator` trait; `effectcraft_gpu::Gpu` implements it
|
||||
on wgpu compute shaders. `RenderOpts::backend` picks `Cpu`, `Gpu` or `Auto` (an accelerator
|
||||
@@ -313,10 +337,13 @@ whichever compositor measured faster for it (`render::auto::AutoPick`, kept by t
|
||||
per comp, output scale and path (readback or viewer display) it averages CPU and GPU frame times
|
||||
(warm-up on both sides, the fastest warm-up frame starting the average, single stalls capped),
|
||||
picks the GPU unless the CPU is clearly faster, and re-measures the other side every 48 frames.
|
||||
Render Queue jobs don't use Auto: every frame of a job renders on the project's renderer (the
|
||||
GPU compositor with Mercury GPU Acceleration), so runs of a render don't differ by timing.
|
||||
Light comps (a few small layers, which the CPU composites only within their bounds) then stay on
|
||||
the CPU instead of paying for full-frame passes and a readback. A GPU frame walks the comp like
|
||||
the CPU instead of paying for full-frame passes and a readback. Render Queue jobs don't use
|
||||
Auto per frame: a job renders every frame on one compositor, never a mix (#3335). A GPU
|
||||
Acceleration job with an accelerator times both on its first frames
|
||||
(`export::pipeline::Cx::probe`, jobs of 12 frames or more) and keeps the faster, the CPU only
|
||||
when clearly faster; `EFFECTCRAFT_RQ_RENDERER=gpu|cpu` fixes the choice, and readback-deferred
|
||||
accelerators (the web) render on the project's renderer. A GPU frame walks the comp like
|
||||
`draw_comp`: cached layer buffers are uploaded once per buffer, then transformed with the CPU's
|
||||
sampling (nearest, bilinear, Catmull-Rom bicubic, the same minification pre-filter), motion-blur
|
||||
sub-samples are accumulated, and track mattes, Preserve Transparency, layer style passes,
|
||||
@@ -343,16 +370,16 @@ Preserve Transparency / blend kernels (a 3D track matte renders solo through the
|
||||
Environment Light Background layers draw their sky in a kernel (`adv_sky`, from
|
||||
`Renderer::sky_draw`) before the rest of the run, in Classic and Advanced 3D comps. Wireframe
|
||||
outlines draw on the GPU from the CPU's pixel list (`Renderer::wireframe_pixels`). GPU effects
|
||||
(`effects::GPU_EFFECTS`, 280 of them: blurs, colour correction, keying incl. Key Light, mattes,
|
||||
channel, stylize, distortion and warps (Warp, Bezier Warp, Smear, Reshape, CC Bend It, CC Page
|
||||
Turn, CC Bender, CC Blobbylize), Cartoon, bevels, shapes, the transitions (CC light family, CC
|
||||
transitions, Block Dissolve), perspective (Radial Shadow, CC Cylinder / Sphere / Spotlight /
|
||||
(`effects::GPU_EFFECTS`, 280 of them: blurs, colour correction, keying incl. Screen Color Key, mattes,
|
||||
channel, stylize, distortion and warps (Warp, Bezier Warp, Smear, Reshape, Two-Point Bend, CC Page
|
||||
Turn, Flexible Bend, Organic Displacement), Cartoon, bevels, shapes, the transitions (CC light family, CC
|
||||
transitions, Block Dissolve), perspective (Radial Shadow, Cylinder Wrap / Sphere / Spotlight /
|
||||
Environment, 3D Glasses), generators, noise, grain, text (Numbers, Timecode), time, 3D Channel (aux channels as an
|
||||
extra texture), Immersive Video, colour management (colour programs from
|
||||
`effects::color_program`, LUTs in a storage buffer) and the simulations' render passes (the
|
||||
simulation stays on the CPU and hands its plan — sprites, pieces, blobs, grids — to a tiled
|
||||
rasteriser; the particle effects too, `gpu::fx_particles`: CC Particle World / Systems II,
|
||||
Particle Playground with its Layer Map frames in one atlas, CC Ball Action, CC Pixel Polly, CC
|
||||
rasteriser; the particle effects too, `gpu::fx_particles`: Particle Emitter 3D / Systems II,
|
||||
Particle Playground with its Layer Map frames in one atlas, Ball Grid, Pixel Shatter, CC
|
||||
Scatterize; plans too large for one item table draw in several passes over the previous one),
|
||||
and the geometry generators (Lightning, Advanced Lightning, Radio Waves, Stroke, Scribble,
|
||||
Vegas, Write-on, Audio Spectrum / Waveform, Basic / Path Text: bolts, strokes, waves, audio
|
||||
@@ -363,7 +390,7 @@ as kernels, in one module per family (`gpu::fx_*` with `shaders/fx_*.wgsl`); con
|
||||
effects run as one chain with one upload and one readback. Statistics that need the whole frame
|
||||
(Auto Levels / Contrast / Color, Equalize, Shadow/Highlight, Color Stabilizer, Remove Grain's
|
||||
noise level) are measured on the CPU from one readback and applied on the GPU; effects reading
|
||||
other frames (Echo, Posterize Time, Time Difference, Time Displacement, CC Force Motion Blur, CC
|
||||
other frames (Echo, Posterize Time, Time Difference, Time Displacement, Temporal Motion Blur, CC
|
||||
Wide Time, Pixel Motion Blur, Timewarp) upload the frames the host renders (`time_frames`) and
|
||||
combine them on the GPU; Time Displacement's per-pixel times and Timewarp's motion vectors
|
||||
(`time_fx::timewarp_plan`) are computed on the CPU. Numbers and Timecode rasterise their glyph
|
||||
@@ -385,7 +412,12 @@ as NaNs (a kernel that skips pixels fails the oracle) and validation errors pani
|
||||
(`ui-egui::frames`); headless renders, the CLI (unless `--gpu`) and CI use the CPU. Outside
|
||||
tests, device errors are logged rather than fatal. A viewer frame that runs out of video memory
|
||||
(`Gpu::within_memory`) renders on the CPU, the uploaded layers and pooled textures are freed, and
|
||||
the RAM preview keeps half as many GPU frames from then on. The same guard catches a frame's
|
||||
the RAM preview keeps half as many GPU frames from then on. However many frame threads the viewer
|
||||
runs, at most two of them start GPU render attempts at once. Shared-pool workers use the CPU
|
||||
when both slots are busy rather than blocking Rayon helpers; dedicated single-thread preview
|
||||
lanes wait for admission and remain on the GPU, so
|
||||
working-texture pressure is bounded by concurrent attempts rather than the thread count (#3028).
|
||||
A single large frame or retained frames can still exceed the available video memory. The same guard catches a frame's
|
||||
validation errors (an invalid texture, often left by a failed allocation): that frame renders on
|
||||
the CPU and the kept textures are freed, but the compositor stays on and, with no allocation
|
||||
failure before it, the RAM preview keeps its GPU frames (#1319). A frame whose render panics is
|
||||
@@ -406,12 +438,12 @@ sentinel is implemented here. Browser scope futures are not synchronously awaite
|
||||
forwards shared-device errors, while owned worker devices install their own handlers. On the web
|
||||
(WebGPU) the GPU composites viewer frames; steps that need a readback fall back to the CPU (the
|
||||
Info panel's pixel readout reads GPU frames back asynchronously). **GPU particles**
|
||||
(`effects::psim`, `gpu::particles`): CC Particle World, CC Particle Systems II and Particle
|
||||
(`effects::psim`, `gpu::particles`): Particle Emitter 3D, Particle Emitter 2D and Particle
|
||||
Playground cannons without interacting forces evolve every particle independently, so the GPU
|
||||
backend (reached through `EffectHost::particles` when the GPU compositor is active) simulates one
|
||||
particle per invocation from the CPU's birth schedule and keeps per-key state checkpoints on the
|
||||
GPU, like `SimCache`; the CPU simulation stays the oracle (tests compare ids and positions).
|
||||
CC Particle World, CC Particle Systems II and CC Mr. Mercury sample their Birth Rate at every
|
||||
Particle Emitter 3D, Particle Emitter 2D and Liquid Particles sample their Birth Rate at every
|
||||
step (`EffectHost::param_at`, #1373), so a keyed rate only decides the births of its own time;
|
||||
the rate is left out of the simulation key, and a checkpoint is resumed only with the same rate history (the CPU state's `hist`, the GPU
|
||||
checkpoint's births). The rate's keyframes are read at the step's exact layer time
|
||||
@@ -602,7 +634,7 @@ Unix milliseconds. The layer's Start Time becomes `dataStart + videoStart − fi
|
||||
(`videoStart` given, or the file's creation date on the desktop; reduced to a time of day when
|
||||
the data has no dates).
|
||||
|
||||
**Lumetri Scopes** (`raster::scopes`) are density plots of the viewer frame (rendered over the
|
||||
**Video Scopes** (`raster::scopes`) are density plots of the viewer frame (rendered over the
|
||||
comp background) with BT.601 / BT.709 / BT.2020 luma and colour-difference coefficients.
|
||||
|
||||
## 5. Expressions
|
||||
|
||||
@@ -8,7 +8,8 @@ the AI models named in `Co-Authored-By` trailers.
|
||||
- Two files, both **compiled into the binary** by the UI crate's `build.rs` (nothing is read from
|
||||
disk or the network at run time; the web build has them too):
|
||||
- [`contributors/contributors.json`](../contributors/contributors.json): commit stats per GitHub
|
||||
username. Never hand-edit.
|
||||
username. Never hand-edit. Release builds regenerate it for the release commit
|
||||
(`.github/workflows/release.yml`, `credits` job); the copy in the repository is not updated.
|
||||
- [`contributors/people.toml`](../contributors/people.toml): the names people chose for
|
||||
themselves. **Each contributor adds only their own entry**, in their own PR.
|
||||
- Each contributor gets one line: GitHub username (always), display name (opt-in, or their synced
|
||||
|
||||
@@ -60,6 +60,26 @@ export formats, parity summary), `jobs.list` (Progress panel jobs, e.g. the back
|
||||
Every engine command is reachable through `engine.execute`, and every interactive widget of every
|
||||
panel registers an element id; both are enforced by `crates/ui-egui/tests/it/ui_agent_audit.rs`.
|
||||
|
||||
### Waiting for a render
|
||||
|
||||
On native desktop builds, `engine.execute` with `renderQueue.render` and `wait: true`
|
||||
(the default) keeps the window responsive while its reply waits for the render worker.
|
||||
`script.run` invoked through the control channel also yields the window while
|
||||
`app.project.renderQueue.render()` waits. JavaScript resumes after completion or cancellation;
|
||||
the original request receives its result and accumulated output only after the continuation
|
||||
finishes or shows a modal dialog. Several renders in one script keep the same request pending.
|
||||
Headless scripts retain their synchronous behavior.
|
||||
|
||||
While a script waits for rendering, the window shows progress and a **Stop Render** button
|
||||
(`scriptRender.stop`). Other editing and script callbacks pause so they cannot enter the
|
||||
suspended script's undo group. `ui.inspect`, `ui.elements`, command/menu listings, resize,
|
||||
focus, and the engine commands `renderQueue.list`, `renderQueue.stop`, `editor.state`,
|
||||
`app.capabilities`, `command.list`, `scriptui.list` and `scriptui.get` remain available.
|
||||
Other control requests return an error. Use another connection for inspection or Stop,
|
||||
since requests on one connection are answered in order. A window-close request is canceled
|
||||
during this wait; stop the render and then close normally to use the unsaved-changes prompt.
|
||||
The control transport's 60-second request timeout still applies to long renders.
|
||||
|
||||
## UI state
|
||||
|
||||
| Method | Params | Result |
|
||||
|
||||
@@ -64,6 +64,7 @@ the GPU is performance, not parity.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-12 | minor | Parameters take After Effects' recorded valid ranges (hard range; defaults and sliders unchanged): Levels (Individual Controls) levels −10000..10000 and Gamma from 0; 4-Color Gradient Blend to 10000 and Jitter to 500 %; Change Color Hue Transform ±1800°; Noise HLS (and Auto) amounts to 1000 % and Grain Size from 0; Grid Border to 2000; Venetian Blinds Width 1..32000 and Feather to 32000; Radial Wipe Feather to 32000; Scatter Amount to 1000; Beam thicknesses to 4000; Transform Scale ±30000 %; Turbulent Displace Random Seed to 100000. A test checks every mapped parameter's range against the recorded table (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Glow (original colours): bright pass per channel, 8 × (value − threshold + 0.061), measured against After Effects 2026 (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Glow (colour channels): the glow covers the layer's blurred alpha, blur σ = 0.384 × radius, measured against After Effects 2026 renders (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Radial Blur repeats the edge instead of sampling transparency outside the layer, measured against After Effects 2026 renders (CPU; Radial Blur has no GPU path) |
|
||||
|
||||
+72
-72
@@ -48,21 +48,21 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
| Effect | Id | Params | GPU | 32 | Status |
|
||||
|---|---|---:|:-:|:-:|---|
|
||||
| Bilateral Blur | `ec.blur.bilateral` | 3 | GPU | 32 | Implemented |
|
||||
| CC Cross Blur | `ec.blur.cccross` | 3 | GPU | 32 | Implemented |
|
||||
| CC Radial Blur | `ec.blur.ccradial` | 4 | GPU | 32 | Implemented |
|
||||
| CC Radial Fast Blur | `ec.blur.ccradialfast` | 3 | GPU | 32 | Implemented |
|
||||
| CC Vector Blur | `ec.blur.ccvector` | 4 | GPU | 32 | Implemented |
|
||||
| Camera Lens Blur | `ec.blur.cameralens` | 16 | GPU | 32 | Implemented |
|
||||
| Camera-Shake Deblur | `ec.blur.camerashakedeblur` | 8 | | 32 | Implemented |
|
||||
| Channel Blur | `ec.blur.channel` | 6 | GPU | 32 | Implemented |
|
||||
| Compound Blur | `ec.blur.compound` | 4 | GPU | 32 | Implemented |
|
||||
| Cross Blur | `ec.blur.cccross` | 3 | GPU | 32 | Implemented |
|
||||
| Directional Blur | `ec.blur.directional` | 2 | GPU | 32 | Implemented |
|
||||
| Fast Box Blur | `ec.blur.fastbox` | 4 | GPU | 32 | Implemented |
|
||||
| Fast Radial Blur | `ec.blur.ccradialfast` | 3 | GPU | 32 | Implemented |
|
||||
| Gaussian Blur | `ec.blur.gaussian` | 3 | GPU | 32 | Implemented |
|
||||
| Radial Blur | `ec.blur.radial` | 4 | GPU | 32 | Implemented |
|
||||
| Sharpen | `ec.blur.sharpen` | 1 | GPU | 32 | Implemented |
|
||||
| Smart Blur | `ec.blur.smart` | 3 | GPU | 32 | Implemented |
|
||||
| Unsharp Mask | `ec.blur.unsharp` | 3 | GPU | 32 | Implemented |
|
||||
| Variable Radial Blur | `ec.blur.ccradial` | 4 | GPU | 32 | Implemented |
|
||||
| Vector Blur | `ec.blur.ccvector` | 4 | GPU | 32 | Implemented |
|
||||
|
||||
## Channel
|
||||
|
||||
@@ -70,7 +70,6 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
|---|---|---:|:-:|:-:|---|
|
||||
| Arithmetic | `ec.channel.arithmetic` | 5 | GPU | 32 | Implemented |
|
||||
| Blend | `ec.channel.blend` | 4 | GPU | 32 | Implemented |
|
||||
| CC Composite | `ec.channel.cccomposite` | 3 | GPU | 32 | Implemented |
|
||||
| Calculations | `ec.channel.calculations` | 9 | GPU | 32 | Implemented |
|
||||
| Channel Combiner | `ec.channel.combiner` | 6 | GPU | 32 | Implemented |
|
||||
| Compound Arithmetic | `ec.channel.compoundarithmetic` | 6 | GPU | 32 | Implemented |
|
||||
@@ -81,6 +80,7 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
| Set Matte | `ec.channel.setmatte` | 6 | GPU | 32 | Implemented |
|
||||
| Shift Channels | `ec.channel.shiftchannels` | 4 | GPU | 32 | Implemented |
|
||||
| Solid Composite | `ec.channel.solidcomposite` | 4 | GPU | 32 | Implemented |
|
||||
| Source Composite | `ec.channel.cccomposite` | 3 | GPU | 32 | Implemented |
|
||||
|
||||
## Color Correction
|
||||
|
||||
@@ -92,18 +92,18 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
| Black & White | `ec.color.blackwhite` | 8 | GPU | 32 | Implemented |
|
||||
| Brightness & Contrast | `ec.color.brightnesscontrast` | 3 | GPU | 32 | Implemented |
|
||||
| Broadcast Colors | `ec.color.broadcast` | 3 | GPU | 32 | Implemented |
|
||||
| CC Color Neutralizer | `ec.color.cccolorneutralizer` | 11 | GPU | 32 | Implemented |
|
||||
| CC Color Offset | `ec.color.cccoloroffset` | 4 | GPU | 32 | Implemented |
|
||||
| CC Kernel | `ec.color.cckernel` | 12 | GPU | 32 | Implemented |
|
||||
| CC Toner | `ec.color.cctoner` | 7 | GPU | 32 | Implemented |
|
||||
| Change Color | `ec.color.changecolor` | 9 | GPU | 32 | Implemented |
|
||||
| Change to Color | `ec.color.changetocolor` | 9 | GPU | 32 | Implemented |
|
||||
| Channel Mixer | `ec.color.channelmixer` | 13 | GPU | 32 | Implemented |
|
||||
| Channel Offset | `ec.color.cccoloroffset` | 4 | GPU | 32 | Implemented |
|
||||
| Color Balance | `ec.color.colorbalance` | 10 | GPU | 32 | Implemented |
|
||||
| Color Balance (HLS) | `ec.color.colorbalancehls` | 3 | GPU | 32 | Implemented |
|
||||
| Color Grading | `ec.color.lumetri` | 66 | GPU | 32 | Implemented |
|
||||
| Color Link | `ec.color.colorlink` | 6 | GPU | 32 | Implemented |
|
||||
| Color Neutralizer | `ec.color.cccolorneutralizer` | 11 | GPU | 32 | Implemented |
|
||||
| Color Stabilizer | `ec.color.colorstabilizer` | 6 | GPU | 32 | Implemented |
|
||||
| Colorama | `ec.color.colorama` | 20 | GPU | 32 | Implemented |
|
||||
| Convolution Kernel | `ec.color.cckernel` | 12 | GPU | 32 | Implemented |
|
||||
| Curves | `ec.color.curves` | 0 | GPU | 32 | Implemented |
|
||||
| Equalize | `ec.color.equalize` | 2 | GPU | 32 | Implemented |
|
||||
| Exposure | `ec.color.exposure` | 14 | GPU | 32 | Implemented |
|
||||
@@ -112,7 +112,7 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
| Leave Color | `ec.color.leavecolor` | 5 | GPU | 32 | Implemented |
|
||||
| Levels | `ec.color.levels` | 36 | GPU | 32 | Implemented |
|
||||
| Levels (Individual Controls) | `ec.color.levelsic` | 27 | GPU | 32 | Implemented |
|
||||
| Lumetri Color | `ec.color.lumetri` | 66 | GPU | 32 | Implemented |
|
||||
| Multi-Tone Tint | `ec.color.cctoner` | 7 | GPU | 32 | Implemented |
|
||||
| OCIO CDL Transform | `ec.color.ociocdl` | 12 | GPU | 32 | Implemented |
|
||||
| OCIO Color Space Transform | `ec.color.ociocolorspace` | 7 | GPU | 32 | Implemented |
|
||||
| OCIO Display Transform | `ec.color.ociodisplay` | 9 | GPU | 32 | Implemented |
|
||||
@@ -133,40 +133,40 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
|---|---|---:|:-:|:-:|---|
|
||||
| Bezier Warp | `ec.distort.bezierwarp` | 13 | GPU | 32 | Implemented |
|
||||
| Bulge | `ec.distort.bulge` | 6 | GPU | 32 | Implemented |
|
||||
| CC Bend It | `ec.distort.ccbendit` | 4 | GPU | 32 | Implemented |
|
||||
| CC Bender | `ec.distort.ccbender` | 5 | GPU | 32 | Implemented |
|
||||
| CC Blobbylize | `ec.distort.ccblobbylize` | 15 | GPU | 32 | Implemented |
|
||||
| CC Flo Motion | `ec.distort.ccflomotion` | 7 | GPU | 32 | Implemented |
|
||||
| CC Griddler | `ec.distort.ccgriddler` | 5 | GPU | 32 | Implemented |
|
||||
| CC Lens | `ec.distort.cclens` | 3 | GPU | 32 | Implemented |
|
||||
| CC Page Turn | `ec.distort.ccpageturn` | 7 | GPU | 32 | Implemented |
|
||||
| CC Power Pin | `ec.distort.ccpowerpin` | 10 | GPU | 32 | Implemented |
|
||||
| CC Ripple Pulse | `ec.distort.ccripplepulse` | 5 | GPU | 32 | Implemented |
|
||||
| CC Slant | `ec.distort.ccslant` | 4 | GPU | 32 | Implemented |
|
||||
| CC Smear | `ec.distort.ccsmear` | 4 | GPU | 32 | Implemented |
|
||||
| CC Split | `ec.distort.ccsplit` | 3 | GPU | 32 | Implemented |
|
||||
| CC Split 2 | `ec.distort.ccsplit2` | 4 | GPU | 32 | Implemented |
|
||||
| CC Tiler | `ec.distort.cctiler` | 3 | GPU | 32 | Implemented |
|
||||
| Corner Pin | `ec.distort.cornerpin` | 4 | GPU | 32 | Implemented |
|
||||
| Detail-preserving Upscale | `ec.distort.upscale` | 4 | | 32 | Implemented |
|
||||
| Directional Smear | `ec.distort.ccsmear` | 4 | GPU | 32 | Implemented |
|
||||
| Displacement Map | `ec.distort.displacementmap` | 8 | GPU | 32 | Implemented |
|
||||
| Dual Lens Warp | `ec.distort.ccflomotion` | 7 | GPU | 32 | Implemented |
|
||||
| Dual Split Warp | `ec.distort.ccsplit2` | 4 | GPU | 32 | Implemented |
|
||||
| Flexible Bend | `ec.distort.ccbender` | 5 | GPU | 32 | Implemented |
|
||||
| Lens Distortion | `ec.distort.cclens` | 3 | GPU | 32 | Implemented |
|
||||
| Liquify | `ec.distort.liquify` | 13 | GPU | 32 | Implemented |
|
||||
| Magnify | `ec.distort.magnify` | 10 | GPU | 32 | Implemented |
|
||||
| Mesh Warp | `ec.distort.meshwarp` | 3 | GPU | 32 | Implemented |
|
||||
| Mirror | `ec.distort.mirror` | 2 | GPU | 32 | Implemented |
|
||||
| Offset | `ec.distort.offset` | 2 | GPU | 32 | Implemented |
|
||||
| Optics Compensation | `ec.distort.opticscompensation` | 6 | GPU | 32 | Implemented |
|
||||
| Organic Displacement | `ec.distort.ccblobbylize` | 15 | GPU | 32 | Implemented |
|
||||
| Page Curl | `ec.distort.ccpageturn` | 7 | GPU | 32 | Implemented |
|
||||
| Perspective Pin | `ec.distort.ccpowerpin` | 10 | GPU | 32 | Implemented |
|
||||
| Polar Coordinates | `ec.distort.polar` | 2 | GPU | 32 | Implemented |
|
||||
| Pulsed Ripple | `ec.distort.ccripplepulse` | 5 | GPU | 32 | Implemented |
|
||||
| Puppet | `ec.distort.puppet` | 1 | | 32 | Implemented |
|
||||
| Reshape | `ec.distort.reshape` | 6 | GPU | 32 | Implemented |
|
||||
| Ripple | `ec.distort.ripple` | 7 | GPU | 32 | Implemented |
|
||||
| Rolling Shutter Repair | `ec.distort.rollingshutterrepair` | 5 | | 32 | Implemented |
|
||||
| Scaled Tiles | `ec.distort.cctiler` | 3 | GPU | 32 | Implemented |
|
||||
| Slant | `ec.distort.ccslant` | 4 | GPU | 32 | Implemented |
|
||||
| Smear | `ec.distort.smear` | 8 | GPU | 32 | Implemented |
|
||||
| Spherize | `ec.distort.spherize` | 2 | GPU | 32 | Implemented |
|
||||
| Split Warp | `ec.distort.ccsplit` | 3 | GPU | 32 | Implemented |
|
||||
| Tile Grid | `ec.distort.ccgriddler` | 5 | GPU | 32 | Implemented |
|
||||
| Transform | `ec.distort.transform` | 12 | GPU | 32 | Implemented |
|
||||
| Turbulent Displace | `ec.distort.turbulentdisplace` | 11 | GPU | 32 | Implemented |
|
||||
| Twirl | `ec.distort.twirl` | 3 | GPU | 32 | Implemented |
|
||||
| Twirl (Legacy) | `ec.distort.twirllegacy` | 3 | GPU | 32 | Implemented |
|
||||
| Two-Point Bend | `ec.distort.ccbendit` | 4 | GPU | 32 | Implemented |
|
||||
| Warp | `ec.distort.warp` | 5 | GPU | 32 | Implemented |
|
||||
| Warp Stabilizer | `ec.distort.warpstabilizer` | 20 | | 32 | Implemented |
|
||||
| Wave Warp | `ec.distort.wavewarp` | 7 | GPU | 32 | Implemented |
|
||||
@@ -194,11 +194,6 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
| Audio Spectrum | `ec.generate.audiospectrum` | 23 | GPU | 32 | Implemented |
|
||||
| Audio Waveform | `ec.generate.audiowaveform` | 17 | GPU | 32 | Implemented |
|
||||
| Beam | `ec.generate.beam` | 11 | GPU | 32 | Implemented |
|
||||
| CC Glue Gun | `ec.generate.ccgluegun` | 7 | GPU | 32 | Implemented |
|
||||
| CC Light Burst 2.5 | `ec.generate.cclightburst` | 6 | GPU | 32 | Implemented |
|
||||
| CC Light Rays | `ec.generate.cclightrays` | 8 | GPU | 32 | Implemented |
|
||||
| CC Light Sweep | `ec.generate.cclightsweep` | 9 | GPU | 32 | Implemented |
|
||||
| CC Threads | `ec.generate.ccthreads` | 8 | GPU | 32 | Implemented |
|
||||
| Cell Pattern | `ec.generate.cellpattern` | 14 | GPU | 32 | Implemented |
|
||||
| Checkerboard | `ec.generate.checkerboard` | 10 | GPU | 32 | Implemented |
|
||||
| Circle | `ec.generate.circle` | 11 | GPU | 32 | Implemented |
|
||||
@@ -209,11 +204,16 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
| Gradient Ramp | `ec.generate.gradientramp` | 7 | GPU | 32 | Implemented |
|
||||
| Grid | `ec.generate.grid` | 12 | GPU | 32 | Implemented |
|
||||
| Lens Flare | `ec.generate.lensflare` | 4 | GPU | 32 | Implemented |
|
||||
| Light Rays | `ec.generate.cclightrays` | 8 | GPU | 32 | Implemented |
|
||||
| Light Sweep | `ec.generate.cclightsweep` | 9 | GPU | 32 | Implemented |
|
||||
| Paint Beads | `ec.generate.ccgluegun` | 7 | GPU | 32 | Implemented |
|
||||
| Paint Bucket | `ec.generate.paintbucket` | 11 | GPU | 32 | Implemented |
|
||||
| Radial Light Burst | `ec.generate.cclightburst` | 6 | GPU | 32 | Implemented |
|
||||
| Radio Waves | `ec.generate.radiowaves` | 31 | GPU | 32 | Implemented |
|
||||
| Scribble | `ec.generate.scribble` | 25 | GPU | 32 | Implemented |
|
||||
| Stroke | `ec.generate.stroke` | 11 | GPU | 32 | Implemented |
|
||||
| Vegas | `ec.generate.vegas` | 26 | GPU | 32 | Implemented |
|
||||
| Woven Threads | `ec.generate.ccthreads` | 8 | GPU | 32 | Implemented |
|
||||
| Write-on | `ec.generate.writeon` | 10 | GPU | 32 | Implemented |
|
||||
|
||||
## Immersive Video
|
||||
@@ -238,15 +238,15 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
| Effect | Id | Params | GPU | 32 | Status |
|
||||
|---|---|---:|:-:|:-:|---|
|
||||
| Advanced Spill Suppressor | `ec.key.advancedspill` | 8 | GPU | 32 | Implemented |
|
||||
| CC Simple Wire Removal | `ec.key.ccsimplewireremoval` | 7 | GPU | 32 | Implemented |
|
||||
| Color Difference Key | `ec.key.colordifference` | 16 | GPU | 32 | Implemented |
|
||||
| Color Range | `ec.key.colorrange` | 8 | GPU | 32 | Implemented |
|
||||
| Difference Matte | `ec.key.differencematte` | 6 | GPU | 32 | Implemented |
|
||||
| Extract | `ec.key.extract` | 6 | GPU | 32 | Implemented |
|
||||
| Inner/Outer Key | `ec.key.innerouter` | 88 | GPU | 32 | Implemented |
|
||||
| Key Cleaner | `ec.key.keycleaner` | 4 | GPU | 32 | Implemented |
|
||||
| Key Light | `ec.keying.keylight` | 56 | GPU | 32 | Implemented |
|
||||
| Line Removal | `ec.key.ccsimplewireremoval` | 7 | GPU | 32 | Implemented |
|
||||
| Linear Color Key | `ec.key.linearcolor` | 6 | GPU | 32 | Implemented |
|
||||
| Screen Color Key | `ec.keying.keylight` | 56 | GPU | 32 | Implemented |
|
||||
| Screen Key | `ec.key.screen` | 11 | GPU | 32 | Implemented |
|
||||
| Unmult | `ec.key.unmult` | 6 | GPU | 32 | Implemented |
|
||||
|
||||
@@ -255,12 +255,12 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
| Effect | Id | Params | GPU | 32 | Status |
|
||||
|---|---|---:|:-:|:-:|---|
|
||||
| Matte Choker | `ec.matte.mattechoker` | 7 | GPU | 32 | Implemented |
|
||||
| Mocha shape | `ec.obsolete.mochashape` | 6 | | 32 | Implemented |
|
||||
| Object Matte | `ec.matte.objectmatte` | 13 | | 32 | Implemented |
|
||||
| Refine Hard Matte | `ec.matte.refinehard` | 16 | GPU | 32 | Implemented |
|
||||
| Refine Soft Matte | `ec.matte.refinesoft` | 18 | GPU | 32 | Implemented |
|
||||
| Roto Brush & Refine Edge | `ec.matte.rotobrush` | 25 | | 32 | Implemented |
|
||||
| Simple Choker | `ec.matte.simplechoker` | 2 | GPU | 32 | Implemented |
|
||||
| Tracked Shape Matte | `ec.obsolete.mochashape` | 6 | | 32 | Implemented |
|
||||
|
||||
## Noise & Grain
|
||||
|
||||
@@ -308,65 +308,65 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
| 3D Glasses | `ec.perspective.3dglasses` | 8 | GPU | 32 | Implemented |
|
||||
| Bevel Alpha | `ec.perspective.bevelalpha` | 4 | GPU | 32 | Implemented |
|
||||
| Bevel Edges | `ec.perspective.beveledges` | 4 | GPU | 32 | Implemented |
|
||||
| CC Cylinder | `ec.perspective.cccylinder` | 15 | GPU | 32 | Implemented |
|
||||
| CC Environment | `ec.perspective.ccenvironment` | 4 | GPU | 32 | Implemented |
|
||||
| CC Sphere | `ec.perspective.ccsphere` | 14 | GPU | 32 | Implemented |
|
||||
| CC Spotlight | `ec.perspective.ccspotlight` | 8 | GPU | 32 | Implemented |
|
||||
| Cylinder Wrap | `ec.perspective.cccylinder` | 15 | GPU | 32 | Implemented |
|
||||
| Drop Shadow | `ec.perspective.dropshadow` | 6 | GPU | 32 | Implemented |
|
||||
| Environment Map | `ec.perspective.ccenvironment` | 4 | GPU | 32 | Implemented |
|
||||
| Radial Shadow | `ec.perspective.radialshadow` | 9 | GPU | 32 | Implemented |
|
||||
| Sphere Wrap | `ec.perspective.ccsphere` | 14 | GPU | 32 | Implemented |
|
||||
| Surface Spotlight | `ec.perspective.ccspotlight` | 8 | GPU | 32 | Implemented |
|
||||
|
||||
## Simulation
|
||||
|
||||
| Effect | Id | Params | GPU | 32 | Status |
|
||||
|---|---|---:|:-:|:-:|---|
|
||||
| CC Ball Action | `ec.sim.ccballaction` | 8 | GPU | 32 | Implemented |
|
||||
| CC Bubbles | `ec.sim.ccbubbles` | 7 | GPU | 32 | Implemented |
|
||||
| CC Drizzle | `ec.sim.ccdrizzle` | 14 | GPU | 32 | Implemented |
|
||||
| CC Hair | `ec.sim.cchair` | 20 | GPU | 32 | Implemented |
|
||||
| CC Mr. Mercury | `ec.sim.ccmrmercury` | 23 | GPU | 32 | Implemented |
|
||||
| CC Particle Systems II | `ec.sim.ccparticlesystems2` | 21 | GPU | 32 | Implemented |
|
||||
| CC Particle World | `ec.sim.ccparticleworld` | 36 | GPU | 32 | Implemented |
|
||||
| CC Pixel Polly | `ec.sim.ccpixelpolly` | 10 | GPU | 32 | Implemented |
|
||||
| CC Rainfall | `ec.sim.ccrainfall` | 14 | GPU | 32 | Implemented |
|
||||
| CC Scatterize | `ec.sim.ccscatterize` | 4 | GPU | 32 | Implemented |
|
||||
| CC Snowfall | `ec.sim.ccsnowfall` | 19 | GPU | 32 | Implemented |
|
||||
| CC Star Burst | `ec.sim.ccstarburst` | 6 | GPU | 32 | Implemented |
|
||||
| Ball Grid | `ec.sim.ccballaction` | 8 | GPU | 32 | Implemented |
|
||||
| Bubbles | `ec.sim.ccbubbles` | 7 | GPU | 32 | Implemented |
|
||||
| Card Dance | `ec.sim.carddance` | 53 | GPU | 32 | Implemented |
|
||||
| Caustics | `ec.sim.caustics` | 28 | GPU | 32 | Implemented |
|
||||
| Foam | `ec.sim.foam` | 36 | GPU | 32 | Implemented |
|
||||
| Liquid Particles | `ec.sim.ccmrmercury` | 23 | GPU | 32 | Implemented |
|
||||
| Particle Emitter 2D | `ec.sim.ccparticlesystems2` | 21 | GPU | 32 | Implemented |
|
||||
| Particle Emitter 3D | `ec.sim.ccparticleworld` | 36 | GPU | 32 | Implemented |
|
||||
| Particle Playground | `ec.sim.particleplayground` | 92 | GPU | 32 | Implemented |
|
||||
| Pixel Scatter | `ec.sim.ccscatterize` | 4 | GPU | 32 | Implemented |
|
||||
| Pixel Shatter | `ec.sim.ccpixelpolly` | 10 | GPU | 32 | Implemented |
|
||||
| Rainfall | `ec.sim.ccrainfall` | 14 | GPU | 32 | Implemented |
|
||||
| Shatter | `ec.sim.shatter` | 60 | GPU | 32 | Implemented |
|
||||
| Snowfall | `ec.sim.ccsnowfall` | 19 | GPU | 32 | Implemented |
|
||||
| Starfield | `ec.sim.ccstarburst` | 6 | GPU | 32 | Implemented |
|
||||
| Surface Fibers | `ec.sim.cchair` | 20 | GPU | 32 | Implemented |
|
||||
| Water Ripples | `ec.sim.ccdrizzle` | 14 | GPU | 32 | Implemented |
|
||||
| Wave World | `ec.sim.waveworld` | 35 | GPU | 32 | Implemented |
|
||||
|
||||
## Stylize
|
||||
|
||||
| Effect | Id | Params | GPU | 32 | Status |
|
||||
|---|---|---:|:-:|:-:|---|
|
||||
| Block Reveal | `ec.stylize.ccblockload` | 5 | GPU | 32 | Implemented |
|
||||
| Brush Strokes | `ec.stylize.brushstrokes` | 7 | GPU | 32 | Implemented |
|
||||
| CC Block Load | `ec.stylize.ccblockload` | 5 | GPU | 32 | Implemented |
|
||||
| CC Burn Film | `ec.stylize.ccburnfilm` | 3 | GPU | 32 | Implemented |
|
||||
| CC Glass | `ec.stylize.ccglass` | 17 | GPU | 32 | Implemented |
|
||||
| CC HexTile | `ec.stylize.cchextile` | 5 | GPU | 32 | Implemented |
|
||||
| CC Kaleida | `ec.stylize.cckaleida` | 4 | GPU | 32 | Implemented |
|
||||
| CC Mr. Smoothie | `ec.stylize.ccmrsmoothie` | 7 | GPU | 32 | Implemented |
|
||||
| CC Plastic | `ec.stylize.ccplastic` | 18 | GPU | 32 | Implemented |
|
||||
| CC RepeTile | `ec.stylize.ccrepetile` | 5 | GPU | 32 | Implemented |
|
||||
| CC Threshold | `ec.stylize.ccthreshold` | 4 | GPU | 32 | Implemented |
|
||||
| CC Threshold RGB | `ec.stylize.ccthresholdrgb` | 7 | GPU | 32 | Implemented |
|
||||
| CC Vignette | `ec.stylize.ccvignette` | 4 | GPU | 32 | Implemented |
|
||||
| Cartoon | `ec.stylize.cartoon` | 12 | GPU | 32 | Implemented |
|
||||
| Channel Threshold | `ec.stylize.ccthreshold` | 4 | GPU | 32 | Implemented |
|
||||
| Color Emboss | `ec.stylize.coloremboss` | 4 | GPU | 32 | Implemented |
|
||||
| Edge Tile Repeat | `ec.stylize.ccrepetile` | 5 | GPU | 32 | Implemented |
|
||||
| Emboss | `ec.stylize.emboss` | 4 | GPU | 32 | Implemented |
|
||||
| Film Burn | `ec.stylize.ccburnfilm` | 3 | GPU | 32 | Implemented |
|
||||
| Find Edges | `ec.stylize.findedges` | 2 | GPU | 32 | Implemented |
|
||||
| Glass Relief | `ec.stylize.ccglass` | 17 | GPU | 32 | Implemented |
|
||||
| Glow | `ec.stylize.glow` | 14 | GPU | 32 | Implemented |
|
||||
| Hexagonal Tiles | `ec.stylize.cchextile` | 5 | GPU | 32 | Implemented |
|
||||
| Kaleidoscope | `ec.stylize.cckaleida` | 4 | GPU | 32 | Implemented |
|
||||
| Mosaic | `ec.stylize.mosaic` | 3 | GPU | 32 | Implemented |
|
||||
| Motion Tile | `ec.stylize.motiontile` | 8 | GPU | 32 | Implemented |
|
||||
| Palette Map | `ec.stylize.ccmrsmoothie` | 7 | GPU | 32 | Implemented |
|
||||
| Plastic Shading | `ec.stylize.ccplastic` | 18 | GPU | 32 | Implemented |
|
||||
| Posterize | `ec.stylize.posterize` | 1 | GPU | 32 | Implemented |
|
||||
| RGB Threshold | `ec.stylize.ccthresholdrgb` | 7 | GPU | 32 | Implemented |
|
||||
| Roughen Edges | `ec.stylize.roughenedges` | 13 | GPU | 32 | Implemented |
|
||||
| Scatter | `ec.stylize.scatter` | 3 | GPU | 32 | Implemented |
|
||||
| Strobe Light | `ec.stylize.strobe` | 7 | GPU | 32 | Implemented |
|
||||
| Texturize | `ec.stylize.texturize` | 4 | GPU | 32 | Implemented |
|
||||
| Threshold | `ec.stylize.threshold` | 1 | GPU | 32 | Implemented |
|
||||
| Vignette | `ec.stylize.ccvignette` | 4 | GPU | 32 | Implemented |
|
||||
|
||||
## Text
|
||||
|
||||
@@ -379,11 +379,11 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
|
||||
| Effect | Id | Params | GPU | 32 | Status |
|
||||
|---|---|---:|:-:|:-:|---|
|
||||
| CC Force Motion Blur | `ec.time.ccforcemotionblur` | 4 | GPU | 32 | Implemented |
|
||||
| CC Wide Time | `ec.time.ccwidetime` | 3 | GPU | 32 | Implemented |
|
||||
| Echo | `ec.time.echo` | 5 | GPU | 32 | Implemented |
|
||||
| Pixel Motion Blur | `ec.time.pixelmotionblur` | 4 | GPU | 32 | Implemented |
|
||||
| Posterize Time | `ec.time.posterizetime` | 1 | GPU | 32 | Implemented |
|
||||
| Temporal Blend | `ec.time.ccwidetime` | 3 | GPU | 32 | Implemented |
|
||||
| Temporal Motion Blur | `ec.time.ccforcemotionblur` | 4 | GPU | 32 | Implemented |
|
||||
| Time Difference | `ec.time.timedifference` | 5 | GPU | 32 | Implemented |
|
||||
| Time Displacement | `ec.time.timedisplacement` | 4 | GPU | 32 | Implemented |
|
||||
| Timewarp | `ec.time.timewarp` | 28 | GPU | 32 | Implemented |
|
||||
@@ -393,29 +393,28 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
| Effect | Id | Params | GPU | 32 | Status |
|
||||
|---|---|---:|:-:|:-:|---|
|
||||
| Block Dissolve | `ec.transition.blockdissolve` | 5 | GPU | 32 | Implemented |
|
||||
| CC Glass Wipe | `ec.transition.ccglasswipe` | 5 | GPU | 32 | Implemented |
|
||||
| CC Grid Wipe | `ec.transition.ccgridwipe` | 7 | GPU | 32 | Implemented |
|
||||
| CC Image Wipe | `ec.transition.ccimagewipe` | 7 | GPU | 32 | Implemented |
|
||||
| CC Jaws | `ec.transition.ccjaws` | 6 | GPU | 32 | Implemented |
|
||||
| CC Light Wipe | `ec.transition.cclightwipe` | 8 | GPU | 32 | Implemented |
|
||||
| CC Line Sweep | `ec.transition.cclinesweep` | 5 | GPU | 32 | Implemented |
|
||||
| CC Radial ScaleWipe | `ec.transition.ccradialscalewipe` | 3 | GPU | 32 | Implemented |
|
||||
| CC Scale Wipe | `ec.transition.ccscalewipe` | 3 | GPU | 32 | Implemented |
|
||||
| CC Twister | `ec.transition.cctwister` | 5 | GPU | 32 | Implemented |
|
||||
| CC WarpoMatic | `ec.transition.ccwarpomatic` | 8 | GPU | 32 | Implemented |
|
||||
| Card Wipe | `ec.transition.cardwipe` | 44 | GPU | 32 | Implemented |
|
||||
| Glass Wipe | `ec.transition.ccglasswipe` | 5 | GPU | 32 | Implemented |
|
||||
| Gradient Wipe | `ec.transition.gradientwipe` | 5 | GPU | 32 | Implemented |
|
||||
| Grid Wipe | `ec.transition.ccgridwipe` | 7 | GPU | 32 | Implemented |
|
||||
| Iris Wipe | `ec.transition.iriswipe` | 7 | GPU | 32 | Implemented |
|
||||
| Light Wipe | `ec.transition.cclightwipe` | 8 | GPU | 32 | Implemented |
|
||||
| Line Sweep | `ec.transition.cclinesweep` | 5 | GPU | 32 | Implemented |
|
||||
| Linear Wipe | `ec.transition.linearwipe` | 3 | GPU | 32 | Implemented |
|
||||
| Luminance Image Wipe | `ec.transition.ccimagewipe` | 7 | GPU | 32 | Implemented |
|
||||
| Radial Scale Wipe | `ec.transition.ccradialscalewipe` | 3 | GPU | 32 | Implemented |
|
||||
| Radial Wipe | `ec.transition.radialwipe` | 5 | GPU | 32 | Implemented |
|
||||
| Scale Wipe | `ec.transition.ccscalewipe` | 3 | GPU | 32 | Implemented |
|
||||
| Serrated Wipe | `ec.transition.ccjaws` | 6 | GPU | 32 | Implemented |
|
||||
| Twist Wipe | `ec.transition.cctwister` | 5 | GPU | 32 | Implemented |
|
||||
| Venetian Blinds | `ec.transition.venetian` | 4 | GPU | 32 | Implemented |
|
||||
| Warp Dissolve | `ec.transition.ccwarpomatic` | 8 | GPU | 32 | Implemented |
|
||||
|
||||
## Utility
|
||||
|
||||
| Effect | Id | Params | GPU | 32 | Status |
|
||||
|---|---|---:|:-:|:-:|---|
|
||||
| Apply Color LUT | `ec.utility.applylut` | 1 | GPU | 32 | Implemented |
|
||||
| CC Overbrights | `ec.utility.ccoverbrights` | 2 | GPU | 32 | Implemented |
|
||||
| Cineon Converter | `ec.utility.cineon` | 7 | GPU | 32 | Implemented |
|
||||
| Color Profile Converter | `ec.utility.colorprofileconverter` | 7 | GPU | 32 | Implemented |
|
||||
| Face Measurements | `ec.utility.facemeasurements` | 14 | | 32 | Implemented |
|
||||
@@ -423,4 +422,5 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
| Grow Bounds | `ec.utility.growbounds` | 1 | GPU | 32 | Implemented |
|
||||
| HDR Compander | `ec.utility.hdrcompander` | 3 | GPU | 32 | Implemented |
|
||||
| HDR Highlight Compression | `ec.utility.hdrcompression` | 1 | GPU | 32 | Implemented |
|
||||
| Out-of-Range Display | `ec.utility.ccoverbrights` | 2 | GPU | 32 | Implemented |
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
# Descriptive feature names
|
||||
|
||||
Related to #1365. This is a naming cleanup, not a trademark clearance or a product rename.
|
||||
|
||||
## Naming policy
|
||||
|
||||
Use short, descriptive labels for EffectCraft implementations. Do not reuse a vendor prefix or
|
||||
coined feature name as our branding. Qualify similar effects by what distinguishes them; each
|
||||
English display name remains unique across the registry.
|
||||
|
||||
- Renderer: **GPU Acceleration** / **Software Rendering**.
|
||||
- Panel: **Video Scopes**.
|
||||
- The table below covers the 69 former CC effects plus color grading, the shape matte and keying.
|
||||
|
||||
## Compatibility contract
|
||||
|
||||
Only presentation names change. Effect IDs, command IDs, parameter IDs, enum values, panel IDs,
|
||||
serialized types and AEP import/export match names stay unchanged. Old effect display names
|
||||
remain case-insensitive lookup and search aliases. Existing project instance names, custom names,
|
||||
expressions, animation and values are not rewritten. The UI displays a current label for an exact
|
||||
legacy default name, optionally followed by its numeric instance suffix; all other names stay
|
||||
verbatim. A user-entered name identical to a legacy default cannot be distinguished from it, but
|
||||
its saved value is still retained.
|
||||
|
||||
External format specifications, comparison documents, attribution records and compatibility
|
||||
fixtures may still name the external product or effect they actually describe.
|
||||
|
||||
## Display mapping
|
||||
|
||||
| Stable ID | Previous display name | Current display name |
|
||||
|---|---|---|
|
||||
| `ec.sim.ccballaction` | CC Ball Action | Ball Grid |
|
||||
| `ec.distort.ccbendit` | CC Bend It | Two-Point Bend |
|
||||
| `ec.distort.ccbender` | CC Bender | Flexible Bend |
|
||||
| `ec.distort.ccblobbylize` | CC Blobbylize | Organic Displacement |
|
||||
| `ec.stylize.ccblockload` | CC Block Load | Block Reveal |
|
||||
| `ec.sim.ccbubbles` | CC Bubbles | Bubbles |
|
||||
| `ec.stylize.ccburnfilm` | CC Burn Film | Film Burn |
|
||||
| `ec.color.cccolorneutralizer` | CC Color Neutralizer | Color Neutralizer |
|
||||
| `ec.color.cccoloroffset` | CC Color Offset | Channel Offset |
|
||||
| `ec.channel.cccomposite` | CC Composite | Source Composite |
|
||||
| `ec.blur.cccross` | CC Cross Blur | Cross Blur |
|
||||
| `ec.perspective.cccylinder` | CC Cylinder | Cylinder Wrap |
|
||||
| `ec.sim.ccdrizzle` | CC Drizzle | Water Ripples |
|
||||
| `ec.perspective.ccenvironment` | CC Environment | Environment Map |
|
||||
| `ec.distort.ccflomotion` | CC Flo Motion | Dual Lens Warp |
|
||||
| `ec.time.ccforcemotionblur` | CC Force Motion Blur | Temporal Motion Blur |
|
||||
| `ec.stylize.ccglass` | CC Glass | Glass Relief |
|
||||
| `ec.transition.ccglasswipe` | CC Glass Wipe | Glass Wipe |
|
||||
| `ec.generate.ccgluegun` | CC Glue Gun | Paint Beads |
|
||||
| `ec.transition.ccgridwipe` | CC Grid Wipe | Grid Wipe |
|
||||
| `ec.distort.ccgriddler` | CC Griddler | Tile Grid |
|
||||
| `ec.sim.cchair` | CC Hair | Surface Fibers |
|
||||
| `ec.stylize.cchextile` | CC HexTile | Hexagonal Tiles |
|
||||
| `ec.transition.ccimagewipe` | CC Image Wipe | Luminance Image Wipe |
|
||||
| `ec.transition.ccjaws` | CC Jaws | Serrated Wipe |
|
||||
| `ec.stylize.cckaleida` | CC Kaleida | Kaleidoscope |
|
||||
| `ec.color.cckernel` | CC Kernel | Convolution Kernel |
|
||||
| `ec.distort.cclens` | CC Lens | Lens Distortion |
|
||||
| `ec.generate.cclightburst` | CC Light Burst 2.5 | Radial Light Burst |
|
||||
| `ec.generate.cclightrays` | CC Light Rays | Light Rays |
|
||||
| `ec.generate.cclightsweep` | CC Light Sweep | Light Sweep |
|
||||
| `ec.transition.cclightwipe` | CC Light Wipe | Light Wipe |
|
||||
| `ec.transition.cclinesweep` | CC Line Sweep | Line Sweep |
|
||||
| `ec.sim.ccmrmercury` | CC Mr. Mercury | Liquid Particles |
|
||||
| `ec.stylize.ccmrsmoothie` | CC Mr. Smoothie | Palette Map |
|
||||
| `ec.utility.ccoverbrights` | CC Overbrights | Out-of-Range Display |
|
||||
| `ec.distort.ccpageturn` | CC Page Turn | Page Curl |
|
||||
| `ec.sim.ccparticlesystems2` | CC Particle Systems II | Particle Emitter 2D |
|
||||
| `ec.sim.ccparticleworld` | CC Particle World | Particle Emitter 3D |
|
||||
| `ec.sim.ccpixelpolly` | CC Pixel Polly | Pixel Shatter |
|
||||
| `ec.stylize.ccplastic` | CC Plastic | Plastic Shading |
|
||||
| `ec.distort.ccpowerpin` | CC Power Pin | Perspective Pin |
|
||||
| `ec.blur.ccradial` | CC Radial Blur | Variable Radial Blur |
|
||||
| `ec.blur.ccradialfast` | CC Radial Fast Blur | Fast Radial Blur |
|
||||
| `ec.transition.ccradialscalewipe` | CC Radial ScaleWipe | Radial Scale Wipe |
|
||||
| `ec.sim.ccrainfall` | CC Rainfall | Rainfall |
|
||||
| `ec.stylize.ccrepetile` | CC RepeTile | Edge Tile Repeat |
|
||||
| `ec.distort.ccripplepulse` | CC Ripple Pulse | Pulsed Ripple |
|
||||
| `ec.transition.ccscalewipe` | CC Scale Wipe | Scale Wipe |
|
||||
| `ec.sim.ccscatterize` | CC Scatterize | Pixel Scatter |
|
||||
| `ec.key.ccsimplewireremoval` | CC Simple Wire Removal | Line Removal |
|
||||
| `ec.distort.ccslant` | CC Slant | Slant |
|
||||
| `ec.distort.ccsmear` | CC Smear | Directional Smear |
|
||||
| `ec.sim.ccsnowfall` | CC Snowfall | Snowfall |
|
||||
| `ec.perspective.ccsphere` | CC Sphere | Sphere Wrap |
|
||||
| `ec.distort.ccsplit` | CC Split | Split Warp |
|
||||
| `ec.distort.ccsplit2` | CC Split 2 | Dual Split Warp |
|
||||
| `ec.perspective.ccspotlight` | CC Spotlight | Surface Spotlight |
|
||||
| `ec.sim.ccstarburst` | CC Star Burst | Starfield |
|
||||
| `ec.generate.ccthreads` | CC Threads | Woven Threads |
|
||||
| `ec.stylize.ccthreshold` | CC Threshold | Channel Threshold |
|
||||
| `ec.stylize.ccthresholdrgb` | CC Threshold RGB | RGB Threshold |
|
||||
| `ec.distort.cctiler` | CC Tiler | Scaled Tiles |
|
||||
| `ec.color.cctoner` | CC Toner | Multi-Tone Tint |
|
||||
| `ec.transition.cctwister` | CC Twister | Twist Wipe |
|
||||
| `ec.blur.ccvector` | CC Vector Blur | Vector Blur |
|
||||
| `ec.stylize.ccvignette` | CC Vignette | Vignette |
|
||||
| `ec.transition.ccwarpomatic` | CC WarpoMatic | Warp Dissolve |
|
||||
| `ec.time.ccwidetime` | CC Wide Time | Temporal Blend |
|
||||
| `ec.color.lumetri` | Lumetri Color | Color Grading |
|
||||
| `ec.obsolete.mochashape` | Mocha shape | Tracked Shape Matte |
|
||||
| `ec.keying.keylight` | Key Light | Screen Color Key |
|
||||
|
||||
`Roto Brush & Refine Edge` and `Warp Stabilizer` keep their current names; renaming them is
|
||||
deferred.
|
||||
|
||||
No effect algorithms or formats are changed.
|
||||
@@ -22,7 +22,7 @@ trips and renders, checked by tests, not against After Effects), **partial** (na
|
||||
|
||||
| Format | After Effects | EffectCraft read | Write | Fidelity / notes | Tested by |
|
||||
|---|---|---|---|---|---|
|
||||
| Project `.aep` | R/W (native) | partial | partial | Main file format; the clean-room method is in [aep-format.md](aep-format.md), the maintainers' decision on it is open (#190). `crates/aep` reads and writes the RIFX container losslessly (byte-identical round trip) with a typed view of the decoded fields, from our own generated files only ([aep-format.md](aep-format.md)). File ▸ Import ▸ After Effects Project… (`file.importAep`, `effectcraft_aep::import`): folders, project settings, compositions, every layer kind, Transform, keyframes, expressions, masks, text with animators, shapes, cameras, lights, track mattes, markers, time remapping, layer styles, the built-in effects of a table shared with the exporter (Curves, Levels, Glow, Fractal Noise, Gradient Ramp, … and the expression controls), footage and image sequences; everything else is reported ([aep-import.md](aep-import.md)). File ▸ Export ▸ After Effects Project… (`file.exportAep`, `effectcraft_aep::export`) writes the same scope with every chunk built in code; exports were opened and read back in After Effects 2026 ([aep-export.md](aep-export.md)); everything else is reported. Effects outside the table (plug-ins, Lumetri, …) are not converted | `crates/aep` tests (`tests/container`, `tests/import`, `tests/export`) |
|
||||
| Project `.aep` | R/W (native) | partial | partial | Main file format; the clean-room method is in [aep-format.md](aep-format.md), the maintainers' decision on it is open (#190). `crates/aep` reads and writes the RIFX container losslessly (byte-identical round trip) with a typed view of the decoded fields, from our own generated files only ([aep-format.md](aep-format.md)). File ▸ Import ▸ Adobe After Effects Project… (`file.importAep`, `effectcraft_aep::import`): folders, project settings, compositions, every layer kind, Transform, keyframes, expressions, masks, text with animators, shapes, cameras, lights, track mattes, markers, time remapping, layer styles, the built-in effects of a table shared with the exporter (Lumetri Color, Curves, Levels, Glow, Fractal Noise, Gradient Ramp, … and the expression controls), footage and image sequences; everything else is reported ([aep-import.md](aep-import.md)). File ▸ Export ▸ Adobe After Effects Project… (`file.exportAep`, `effectcraft_aep::export`) writes the same scope with every chunk built in code; exports were opened and read back in After Effects 2026 ([aep-export.md](aep-export.md)); everything else is reported. Effects outside the table (plug-ins, …) are not converted | `crates/aep` tests (`tests/container`, `tests/import`, `tests/export`) |
|
||||
| XML project `.aepx` | R/W | partial | none | Read into the same chunk tree as `.aep` by `crates/aep` (`read_aepx`; mapping in [aep-format.md](aep-format.md) section 1), so `file.importAep` imports it with the same coverage as `.aep`; not written | `crates/aep` `aepx` tests (container and import) |
|
||||
| Templates `.aet` / `.aetx` | R/W | none | none | Own `.ectemplate` (Home ▸ Templates, Save as Template) | `templates.rs` tests |
|
||||
| Motion Graphics template `.mogrt` | W (and R in Premiere) | none | none | Own `.ectemplate` Essential Graphics templates; FilmCraft rejects our `.fcgt` export (#611) | `essential.rs` tests |
|
||||
|
||||
@@ -70,6 +70,9 @@ next to it.
|
||||
Agents: `file.importProject {"path": "/projects/titles.ecproj"}` returns the comps, the root comps,
|
||||
`primaryComp` (the only root comp, if there is one) and any missing media.
|
||||
|
||||
After Effects projects (`.aep`, `.aepx`) given to File ▸ Import ▸ File… or dropped on the window
|
||||
import as File ▸ Import ▸ Adobe After Effects Project… does ([aep-import.md](aep-import.md)).
|
||||
|
||||
## Finding a layer's source in the Project panel
|
||||
|
||||
Right-click a layer in the Timeline or Composition panel and choose **Reveal Layer Source in
|
||||
|
||||
+7
-1
@@ -61,7 +61,7 @@ are grouped by area; the issue tracker has the rest.
|
||||
| 2 | **Fidelity unmeasured** beyond scalar ease | [fidelity/](fidelity/README.md): 168 samples, one property type; `compare_frames` helper exists, no corpus | Every "done" feature may still differ; users find the differences | 60–100 h harness + P0/P1 corpus; more per feature | G1 · [target-app-parity.md](target-app-parity.md) |
|
||||
| 3 | **Open crash reports** | Open: #580, #501, #426, #341, #234, #879. Now closed: #591, #306, #511 | A crash loses work; outranks features ([AGENTS.md](../AGENTS.md)) | 10–25 h | G3 · this file |
|
||||
| 4 | ~~**No CI gate on pull requests**~~ closed | `.github/workflows/ci.yml` runs `cargo xtask ci` (fmt, clippy, tests, layers, assets, wasm) on every pull request and push to `main` through craft-common-actions' shared `gate.yml` (#2204) | — | done | G3 · this file |
|
||||
| 5 | **Playback and scrubbing feel** | #595 stutter on cached frames (partly addressed: nearest-first RAM preview prefetch, cache bars at 1/3 resolution), #304, #504 / #335 audio late or crackling, #586 puppet lag (maintainers asked for details); #329 slow scrubbing (#784) and #554 closed | Preview is where motion designers spend their day | 25–50 h | G5 · [hardware-parity.md](hardware-parity.md) |
|
||||
| 5 | **Playback and scrubbing feel** | #595 stutter on cached frames (partly addressed: nearest-first RAM preview prefetch, cache bars at 1/3 resolution; an uncached first playback plays at the rate frames render instead of freezing between bursts, with frame lanes four threads wide), #304, #504 / #335 audio late or crackling, #586 puppet lag (maintainers asked for details); #329 slow scrubbing (#784) and #554 closed | Preview is where motion designers spend their day | 25–50 h | G5 · [hardware-parity.md](hardware-parity.md) |
|
||||
| 6 | **Linux and Windows interaction bugs** | Open: #488 rename arrows (hardening landed, reporter asked for details), #492 anchor drag, #268 file drops on Wayland (winit), #613 Direct3D 12 compositor. Now closed: #491, #493, #467, #320, #302, #596 | Many reports come from Linux; some Windows GPUs fail to start the compositor | 25–45 h | G2 · [ui-parity.md](ui-parity.md) |
|
||||
| 7 | **Animation presets: 76 against 621** | After Effects ships 621 `.ffx` presets (Behaviors, Backgrounds, Shapes, Synthetics, Text, Transitions…); we ship 76 original presets (8 text-animator; 23 Transitions: wipes, clock and fan reveals, blinds, dissolves, card flips, CC wipes, fades, #757; 23 Backgrounds: drifting gradients, fog, cloud and smoke, plasma and magma, cells, hexagons, scrolling grids and checks, pulse rings, bokeh, light sweep, #1096, driven by expressions so they keep moving, not seamless loops; 22 Behaviors: wiggle, shake, float, orbit, bounce, drift, pendulum, spin, pulse, heartbeat, blink, strobe, flicker on transform and opacity, #1510) and read only our `.ecpreset`. Missing: Shapes, Synthetics, Image and more Text | Presets are how many designers start; tutorials assume them | 15–35 h (original presets; `.ffx` reading is out of clean-room scope) | G8 · [effects-parity.md](effects-parity.md) |
|
||||
| 8 | **Mask and shape bugs** | Open: #1004 mask vertex multi-select, #999 auto-pan, #1042 / #1033 vertex nodes under the bounding box, #959 / #960 shape group toggles and operators. Now closed: #510, #513, #527, #509, #303, #553, #932 (Convert To Bezier Path on a shape layer's parametric path) | Masks are core compositing | 10–20 h | G2 · [target-app-parity.md](target-app-parity.md) |
|
||||
@@ -266,6 +266,12 @@ Issues #41–#47 (all from the Linux AppImage 0.1.1, 5 October): panning the vie
|
||||
The second means reading another implementation of the format, which AGENTS.md rules out
|
||||
unless the clean-room scope allows it.
|
||||
- Also in this area: relative footage paths and relinking when a project moves (gap 18).
|
||||
- Placed PDF / Illustrator footage of an `.aep` is framed as After Effects places it (the ArtBox
|
||||
clipped to the crop box, `/Rotate` ignored) and drawn to fill the size After Effects stored
|
||||
(#3099), measured with hand-written PDFs named `.ai`. Not yet measured: files saved by
|
||||
Illustrator itself (artboards, bleed, art past the artboard, private data), a CropBox without
|
||||
an ArtBox, `.pdf` names, and EPS files (they keep their bounding box at their own size, not
|
||||
the placed framing).
|
||||
- Done when: the decision is recorded and, if approved, a corpus of real-world-shaped projects
|
||||
opens with its comps, layers, keyframes, effects and expressions intact.
|
||||
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ The server provides the same core conventions as FilmCraft #28:
|
||||
| `command_run` | `id`, `params?` | Engine command result |
|
||||
| `command_batch` | `steps: [{id, params?}]`, `stop_on_error?` | `completed`, `failed`, per-step `results` |
|
||||
| `doc_inspect` | none | Project overview and active composition |
|
||||
| `render_preview` | `comp?`, `time?`, `max_side?`, `transparent?` | PNG and frame information |
|
||||
| `render_preview` | `comp?`, `time?` or `times?`, `columns?`, `labels?`, `max_side?`, `transparent?` | PNG and frame information; with `times`, one contact-sheet PNG and its layout (`columns`, `rows`, `cellWidth`, `cellHeight`, `width`, `height`) |
|
||||
|
||||
`command_batch` stops at the first error by default; false continues. Each edit has its
|
||||
own undo step. The existing `batch` tool retains its atomic undo grouping and result references.
|
||||
|
||||
+34
-15
@@ -11,7 +11,7 @@ Guide):
|
||||
|
||||
| After Effects | EffectCraft | |
|
||||
|---|---|---|
|
||||
| Effect plug-ins (C/C++ SDK, `.aex` / `.plugin`) | [Effect plug-ins](#effect-plug-ins): WebAssembly modules (plug-in API v1) or Rust; the `Plug-ins` folder next to the settings loads at start-up | Different ABI: After Effects SDK plug-ins can't run (G8) |
|
||||
| Effect plug-ins (C/C++ SDK, `.aex` / `.plugin`) | [Effect plug-ins](#effect-plug-ins): WebAssembly modules (WebAssembly ABI 1 and 2) or Rust; the `Plug-ins` folder next to the settings loads at start-up | Different ABI: After Effects SDK plug-ins can't run (G8) |
|
||||
| Scripts folder: its scripts are listed in File ▸ Scripts | `Scripts/` in the settings folder (File ▸ Scripts ▸ Install Script File… copies a script there), listed in File ▸ Scripts with the bundled samples; a subfolder (one level) is a submenu named after it | Same |
|
||||
| File ▸ Scripts ▸ Run Script File… (`.jsx`, `.jsxbin`) | `.jsx` / `.js` | Gap: `.jsxbin` (an undocumented binary encoding) |
|
||||
| File ▸ Scripts ▸ Recent Script Files: the 10 most recently run scripts; Cmd+Option+Shift+D (Ctrl+Alt+Shift+D on Windows) reruns one | File ▸ Scripts ▸ Recent Script Files lists the 10 scripts run last (script files and File ▸ Scripts entries, newest first, kept in the settings); Cmd+Alt+Shift+D runs the most recent one again (`file.runScript {"recent": 0}`, a key that can be changed in Edit ▸ Keyboard Shortcuts) | Same |
|
||||
@@ -152,7 +152,7 @@ example of a keyframe tool.
|
||||
|
||||
## Effect plug-ins
|
||||
|
||||
EffectCraft loads third-party effects through a small, versioned plug-in API (**API version 2**; WebAssembly ABI 1).
|
||||
EffectCraft loads third-party effects through a small, versioned plug-in API (**API version 2**; WebAssembly ABI 1 and 2).
|
||||
A plug-in effect behaves exactly like a built-in one: it is listed in Effects & Presets (and the
|
||||
Effect menu) under its category, applied by id (`effect.apply {"effect": "org.example.posterize"}`)
|
||||
or display name, its parameters are ordinary properties (keyframes, expressions, Effect Controls,
|
||||
@@ -178,7 +178,8 @@ author, source, params}]}`.
|
||||
* **Parameters** arrive flattened to `f64`s in the order they are declared: slider, angle,
|
||||
checkbox (0 / 1) and popup (0-based index) take one value, point two (already in **buffer
|
||||
pixels**), colour four (RGBA 0–1).
|
||||
* **Time** is layer time in seconds.
|
||||
* **Time** is layer time in seconds. A WebAssembly module written for ABI 2 also gets the
|
||||
layer's neighbouring frames at other times (see [WebAssembly ABI](#webassembly-abi)).
|
||||
* Rendering must be **deterministic** and may run on several threads at once (a WebAssembly
|
||||
plug-in gets one instance per thread).
|
||||
* A failed render (an error code, a trap, running out of fuel) leaves the frame unchanged.
|
||||
@@ -216,22 +217,36 @@ Every plug-in describes itself with a JSON manifest:
|
||||
parameters; a slider needs `min ≤ default ≤ max` (and `sliderMin ≤ sliderMax`); the manifest
|
||||
is at most 1 MiB.
|
||||
|
||||
### WebAssembly ABI (v1)
|
||||
### WebAssembly ABI
|
||||
|
||||
A module with **no imports** that exports:
|
||||
A module with **no imports** exports:
|
||||
|
||||
| export | signature | |
|
||||
|---|---|---|
|
||||
| `memory` | memory | linear memory shared with the host |
|
||||
| `ec_api_version` | `() -> i32` | must return `1` |
|
||||
| `ec_api_version` | `() -> i32` | `1` for ABI 1, `2` for ABI 2 (neighbouring frames) |
|
||||
| `ec_manifest_ptr`, `ec_manifest_len` | `() -> i32` | the UTF-8 JSON manifest in memory |
|
||||
| `ec_alloc` | `(bytes: i32) -> i32` | a buffer of at least `bytes` bytes, 8-byte aligned; it may reuse the previous one |
|
||||
| `ec_render` | `(pixels: i32, width: i32, height: i32, params: i32, nparams: i32, time: f64, scale: f64) -> i32` | process the pixels in place; return 0 on success |
|
||||
| `ec_render` (ABI 1) | `(pixels: i32, width: i32, height: i32, params: i32, nparams: i32, time: f64, scale: f64) -> i32` | process the pixels in place; return 0 on success |
|
||||
| `ec_render2` (ABI 2) | `ec_render`'s arguments, then `prev: i32, prev_time: f64, next: i32, next_time: f64, flags: i32` | the same, with the neighbouring frames; return 0 on success |
|
||||
|
||||
For each frame the host calls `ec_alloc` once for a buffer holding the parameters (`nparams`
|
||||
little-endian `f64`s) followed by the pixels (`width × height × 4` little-endian `f32`s), fills it,
|
||||
calls `ec_render`, and reads the pixels back. Each call gets a fuel budget proportional to the
|
||||
frame size; a module that loops forever stops with an error instead of hanging the app.
|
||||
little-endian `f64`s) followed by the pixels (`width × height × 4` little-endian `f32`s,
|
||||
premultiplied RGBA, row-major), fills it, calls the module's render export, and reads the pixels
|
||||
back. An ABI 2 module gets two more pixel buffers after the current frame: the previous and next
|
||||
frames, resampled onto the current buffer's grid. At a frame boundary they are the frames before
|
||||
and after it (`time ± 1/fps`); between frames they are the two frames that bracket `time`.
|
||||
|
||||
The neighbour buffers are always allocated (zeroed when the layer has no pixels there, so a
|
||||
module can read both without a null check). `flags` bit 0 is set when `prev` holds a real frame,
|
||||
bit 1 when `next` does; `prev_time` / `next_time` are those frames' layer times in seconds (0
|
||||
when absent). The ABI version is the module's `ec_api_version` and is independent of the
|
||||
manifest's `api` (the native plug-in API): a module can use ABI 2 while its manifest says
|
||||
`"api": 1`. Because an ABI 2 module reads other times, its layer is keyed on the layer time in
|
||||
the render cache; ABI 1 modules keep the time-independent key.
|
||||
|
||||
Each call gets a fuel budget proportional to the frame size; a module that loops forever stops
|
||||
with an error instead of hanging the app.
|
||||
|
||||
#### Example
|
||||
|
||||
@@ -324,8 +339,10 @@ register_plugin(Arc::new(Swap(manifest)))?; // before the UI builds its menus
|
||||
## Plug-in API 2 (native plug-ins)
|
||||
|
||||
API 2 is for Rust plug-ins and for hosts that bring effects from elsewhere (such as
|
||||
[external effect helpers](external-helpers.md)); the WebAssembly ABI stays at 1. Manifests with `"api": 1` or `2` are accepted,
|
||||
`effect.plugins.list` reports `api: 2`, and up to 1024 plug-in effects can be registered.
|
||||
[external effect helpers](external-helpers.md)); the WebAssembly ABI is versioned separately
|
||||
(ABI 2 adds the neighbouring frames, see [WebAssembly ABI](#webassembly-abi)). Manifests with
|
||||
`"api": 1` or `2` are accepted, `effect.plugins.list` reports `api: 2`, and up to 1024 plug-in
|
||||
effects can be registered.
|
||||
|
||||
* **New parameter types**: `point3` (`default` `[x, y, z]`; x, y arrive in buffer pixels, z as is),
|
||||
`layer` (another layer of the composition; flattens to its id, -1 for none, read with
|
||||
@@ -354,9 +371,11 @@ API 2 is for Rust plug-ins and for hosts that bring effects from elsewhere (such
|
||||
|
||||
### Compatibility
|
||||
|
||||
The API version is checked when a plug-in loads: a manifest or module for another version is
|
||||
refused with a message. Within a version the contract above does not change; additions (new
|
||||
parameter types, new optional exports) bump the version.
|
||||
The API version is checked when a plug-in loads: this build implements plug-in API 1 and 2 and
|
||||
WebAssembly ABI 1 and 2; a manifest or module for another version is refused with a message.
|
||||
ABI 1 modules load and render unchanged; ABI 2 modules add `ec_render2` and the neighbouring
|
||||
frames. Within a version the contract above does not change; additions (new parameter types,
|
||||
new optional exports) bump the version.
|
||||
|
||||
## Plug-in Library
|
||||
|
||||
|
||||
+40
-3
@@ -37,6 +37,7 @@ Commands (CLI, MCP, control channel):
|
||||
- `general.showToolTips`: Show Tool Tips
|
||||
- `general.viewerWheelScrubbing`: Scrub Composition Frames with Mouse Wheel (off by default). On: an unmodified mouse wheel over the Composition viewer steps frames, and the smoothed tail of that notch does not zoom or pan. Ctrl/Cmd+wheel and a trackpad still zoom and pan.
|
||||
- `general.createLayersAtCompStart`: Create Layers at Composition Start Time
|
||||
- `general.fitNewLayersToWorkArea`: Fit New Layers to Work Area (off by default). On: new text, solid, shape, null and adjustment layers start at the work area start and end at its end, ahead of Create Layers at Composition Start Time. Footage and compositions keep their own length.
|
||||
- `general.defaultSpatialLinear`: Default Spatial Interpolation to Linear
|
||||
- `startup.showHomeOnLaunch`: Show Home Screen When Launching
|
||||
- `startup.offerCrashRecovery`: Offer to Open the Latest Auto-Save After a Crash
|
||||
@@ -52,10 +53,20 @@ Commands (CLI, MCP, control channel):
|
||||
- `autoSave.saveOnRenderStart`: Save When Starting Render Queue
|
||||
- `composition.showRenderingProgress`: Show Rendering Progress in Info Panel and Flowchart
|
||||
- `previews.adaptiveResolutionLimit`: Adaptive Resolution Limit (the lowest resolution Fast Previews ▸
|
||||
Adaptive Resolution drops to while you drag; a playing preview keeps the viewer's resolution)
|
||||
Adaptive Resolution drops to while you drag, and the lowest a playing preview steps down to)
|
||||
- `previews.adaptivePlayback`: Lower Resolution When Playback Can't Keep Up. Off by default (without it a first play renders
|
||||
at the viewer's resolution at the rate frames arrive, and a looping preview plays its later loops
|
||||
from the cache in real time, as in After Effects; with it the play stays lowered). A
|
||||
preview at Resolution Auto that plays under 80 % of its frame rate (frames render slower than
|
||||
that, so it plays them at the rate they arrive rather than freezing between them) steps
|
||||
down a resolution (half the scale after two seconds, a quarter two seconds later if still slow,
|
||||
no lower than the Adaptive Resolution Limit) for the rest of that play; stopping goes back to the viewer's resolution, and Cache
|
||||
Frames When Idle fills it in. An explicit Resolution (the comp's or the Preview panel's) is
|
||||
kept as set
|
||||
- `previews.cacheFramesWhenIdle`: Cache Frames When Idle (Composition ▸ Preview): after a second
|
||||
without input or edits, the viewer renders the work area into the RAM preview in the
|
||||
background, from the current time on, until it is cached or the budget is full
|
||||
background, from the current time on, until it is cached or the budget is full. On by default,
|
||||
as in After Effects, so the first playback of a comp left alone for a second is already cached
|
||||
- `previews.fastPreviews`: Fast Previews (Draft 3D, Faster Effects)
|
||||
- `appearance.appearanceMode`: Appearance Mode (`auto` / `dark` / `light`). `auto` (Sync with System) follows the operating system's light or dark appearance while the app runs: on Linux the desktop app reads the XDG desktop portal (`org.freedesktop.appearance` `color-scheme`) once and then listens for its `SettingChanged` signal on a worker thread, repainting only when the value changes (nothing polls); `gsettings` runs at most once at start-up, by absolute path, when the portal gives no answer. Elsewhere it uses what the window system reports. With no answer it shows the dark theme. New installs stay `dark`; Sync with System is opt-in. The Tools bar's appearance button (a monitor, sun or moon) cycles Sync with System, Light and Dark (`view.theme.toggle`, Next Appearance Mode).
|
||||
- `appearance.darkTheme`: Dark Theme (`dark` / `darker`), shown in dark appearance
|
||||
@@ -82,6 +93,27 @@ Commands (CLI, MCP, control channel):
|
||||
- `memory.layerCacheMb`: Layer Cache (in the browser at most 512 MB, see [web.md](web.md))
|
||||
- `memory.mediaCacheMb`: Footage Frame Cache (in the browser at most 256 MB)
|
||||
- `memory.previewCacheMb`: Preview (RAM) Cache
|
||||
- `memory.gpuFramesInFlight`: GPU Frames Rendered at Once (`auto`, `1`, `2`, `3`): how many
|
||||
frames the Render Queue renders on the GPU at once. Auto measures the GPU memory after the
|
||||
job's first GPU frame and fits up to 3 frames into the GPU memory cap (by default a
|
||||
conservative 3 GiB on a discrete card, less when its memory is known, 2 GiB on an integrated
|
||||
one; `EFFECTCRAFT_GPU_MEMCAP_MB` sets it), keeping room for the upload cache to fill. It
|
||||
renders one frame at a time where the memory can't be measured (Metal, WebGPU, GL, or the
|
||||
cap turned off) and once frames have had to wait for memory. The render log names the count.
|
||||
`EFFECTCRAFT_GPU_INFLIGHT` overrides it. CPU renders have no such setting. A job on GPU
|
||||
Acceleration that renders on the CPU (it measured the CPU faster, or the GPU stopped) fits
|
||||
its CPU frames in flight into half the memory the system has free, at the size a GPU frame
|
||||
measured, from 1 to one per thread; the app log names the count. Other CPU renders (Software
|
||||
Rendering projects) keep one frame per thread in flight, so their memory grows with the core
|
||||
count. Set `EFFECTCRAFT_CPU_INFLIGHT` to a whole number of frames (at least 1) to cap the
|
||||
frames in flight of every Render Queue render and export, `effectcraft-cli render` included
|
||||
([#1615](https://github.com/storytold/effectcraft/issues/1615)); previews don't read it. One
|
||||
exception: while a job on GPU Acceleration times both compositors on its first frames, it
|
||||
renders them under the cap but keeps them all (up to 12) until it has chosen. That timing
|
||||
picks the compositor that a job of 12 frames or more renders every frame on (the render log
|
||||
names it and why). `EFFECTCRAFT_RQ_RENDERER` set to `gpu` or `cpu` skips it and renders on
|
||||
that compositor; `auto` (the default) measures. A queue item's later output modules reuse its
|
||||
first one's choice, and Software Rendering projects always render on the CPU.
|
||||
- `threeD.defaultRenderer`: Default 3D Renderer
|
||||
- `labels.N.name` / `labels.N.color`: the 16 label names and colours, used by the Label menu,
|
||||
the timeline, project panel, render queue and viewer handles
|
||||
@@ -142,7 +174,12 @@ Commands (CLI, MCP, control channel):
|
||||
it supports it)
|
||||
- `disk.diskCacheEnabled`, `disk.diskCacheMaxGb`, `disk.diskCacheFolder`: the persistent disk
|
||||
cache of processed layers and frames (in the browser: frames in the Origin Private File System,
|
||||
at most half the origin's quota; the folder setting does not apply there). The Disk page also
|
||||
at most half the origin's quota; the folder setting does not apply there). On the desktop the
|
||||
cache never fills its disk: it leaves 5% of the disk free, at least 2 GB and at most 10 GB, and
|
||||
is smaller than `disk.diskCacheMaxGb` when free space requires it. The Disk page shows the limit
|
||||
in effect in MB under Maximum Disk Cache Size, with a note naming the space kept free when free
|
||||
disk space lowers it (with less than 64 MB of room nothing is written, and frames and layers
|
||||
are not compressed for it). The Disk page also
|
||||
shows **Browser Storage** in the web app: usage and quota, persistent storage, Clear buttons
|
||||
(`storage.info` / `storage.persist` / `storage.clear`, see [web.md](web.md))
|
||||
- `disk.mediaCacheFolder`: audio waveform summaries kept between sessions (`Peaks/`)
|
||||
|
||||
+108
-1
@@ -2,7 +2,8 @@
|
||||
|
||||
```sh
|
||||
cargo test --workspace # everything
|
||||
cargo xtask ci # fmt, clippy -D warnings, tests, pdf-deps, layers, assets, wasm
|
||||
cargo xtask ci # fmt, clippy -D warnings, tests, pdf-deps, lock-patches, layers, assets, wasm
|
||||
cargo xtask ci --affected # the same gates, scoped to crates your change can affect
|
||||
```
|
||||
|
||||
The After Effects test projects of `crates/aep` are not in the repository; without them the
|
||||
@@ -19,6 +20,9 @@ cargo xtask aep-corpus
|
||||
EFFECTCRAFT_AEP_FIXTURES=corpus/aep EFFECTCRAFT_AEP_FIXTURES_REQUIRED=1 cargo test -p effectcraft-aep
|
||||
```
|
||||
|
||||
The container and typed-view tests are in `craft-aep` ([storytold/craft-libs](https://github.com/storytold/craft-libs)),
|
||||
which reads the same corpus from `CRAFT_AEP_FIXTURES` (and `CRAFT_AEP_FIXTURES_REQUIRED`).
|
||||
|
||||
The `dev` profile keeps debug info to line tables (fast builds, readable backtraces). To debug
|
||||
interactively — local variables, parameters, column breakpoints — build with the `dev-full`
|
||||
profile: `cargo run --profile dev-full -p effectcraft -- --demo` (see `CONTRIBUTING.md`).
|
||||
@@ -28,10 +32,60 @@ profile: `cargo run --profile dev-full -p effectcraft -- --demo` (see `CONTRIBUT
|
||||
runs `missing_translations_report` with `--nocapture`, which prints the interface strings each
|
||||
language has no translation for. Those strings show in English; the report always passes.
|
||||
|
||||
Direct nextest runs, including archived coverage shards, serialise `effectcraft-gpu`,
|
||||
`effectcraft-ui-egui`, `effectcraft-sam2` and the CLI adapter tests through the `gpu` test group
|
||||
in `.config/nextest.toml`. Their process-local mutexes cannot coordinate nextest's separate
|
||||
processes; competing software devices can time out during readback. UI harnesses can also
|
||||
create a device lazily when rendering a screenshot. This also reduces UI and SAM2 test
|
||||
parallelism in the normal gate. `cargo xtask ci` still runs the GPU library suite in one
|
||||
process to reuse its device. Group limits apply within one nextest invocation, not across
|
||||
separate invocations, and cannot prevent timeouts from other host workloads.
|
||||
|
||||
Each crate with several integration-test files has one test binary, `tests/it/main.rs`, whose
|
||||
modules are the files in `tests/it/`. Run one file with `cargo test -p <crate> --test it <module>::`
|
||||
or `cargo nextest run -p <crate> -E 'binary(it) and test(/^<module>::/)'`.
|
||||
|
||||
## Affected-only CI
|
||||
|
||||
Use `cargo xtask ci --affected` to run the gates for the crates a change can affect. The full
|
||||
`cargo xtask ci` remains the required check before a release and when affected mode falls back
|
||||
because a workspace-wide input changed.
|
||||
|
||||
```sh
|
||||
cargo xtask ci --affected # base: the merge-base of HEAD with main
|
||||
cargo xtask ci --affected --base HEAD~3 # any git revision
|
||||
cargo xtask ci --affected --dry-run # print the plan only
|
||||
```
|
||||
|
||||
It collects files that differ from the base (committed, staged, unstaged and untracked), preserving
|
||||
Unicode and whitespace in their names. Each file maps to the workspace package whose directory
|
||||
holds it and every package whose sources point at it with a `../` literal, such as
|
||||
`include_bytes!("../../../assets/fonts/Inter-Regular.ttf")` or
|
||||
`concat!(env!("CARGO_MANIFEST_DIR"), "/../../docs/preferences.md")`. Deleted and renamed inputs
|
||||
still select their readers, including when the input's entire directory no longer exists.
|
||||
|
||||
The affected set is those packages plus everything that depends on them (transitively through
|
||||
normal and build dependencies) and every package that uses one of them as a dev-dependency.
|
||||
Clippy and tests use the same dev profile as full CI. Both modes use the same test runner:
|
||||
nextest plus doctests when installed, otherwise cargo test plus doctests. GPU tests run in one
|
||||
libtest process, and the translation report reruns with visible output when ui-egui is affected.
|
||||
The audio-backend probe runs before building affected crates.
|
||||
|
||||
Formatting, PDF dependency policy, lock-patch and layer checks always run. Assets are checked
|
||||
when an asset, an `.attribution` sidecar or `ATTRIBUTION.md` changes. The wasm check selects
|
||||
affected L0–L4 and web crates. Every step runs even when an earlier step fails; the final result
|
||||
names failed steps and includes timings.
|
||||
|
||||
Changes to the root `Cargo.toml`, `Cargo.lock`, `.cargo/`, `xtask/`, `rust-toolchain(.toml)`,
|
||||
`clippy.toml` or `.config/nextest.toml` can change every crate's build or test policy, so they run
|
||||
the full CI instead and say why. Changes no crate reads (most of `docs/`, `README.md`, `.github/`,
|
||||
`packaging/`) only get the cheap checks.
|
||||
|
||||
Limits: a crate that reads a repo file at run time through a path that is not a `../` literal in its
|
||||
sources is not detected, and building a subset of packages can unify features differently from the
|
||||
whole workspace. Non-UTF-8 Git paths stop the command rather than silently omitting checks.
|
||||
Run the full `cargo xtask ci` before a release.
|
||||
|
||||
## What the tests cover
|
||||
|
||||
- **Engine commands** (`crates/engine/src/tests*.rs`): every command family, including undo and
|
||||
@@ -112,3 +166,56 @@ and line instead of being discarded. `--fail-over PCT` requires the same run key
|
||||
successful latest runs, and at least one comparable resource metric per run. Failed latest runs,
|
||||
missing coverage, empty results and wall-time-only measurements cannot pass the gate. Omit
|
||||
`--fail-over` for an exploratory partial comparison; failed latest runs remain visible.
|
||||
|
||||
## Unexpected desktop exit diagnostics
|
||||
|
||||
The desktop starts a separate, lightweight reporter before loading fonts, projects or the GPU.
|
||||
A normal exit (including a handled startup error) sends a clean marker only after desktop
|
||||
teardown. macOS finishes at the end of its successful exit hook because its native event loop
|
||||
terminates without returning to main; other platforms finish after the event loop returns.
|
||||
A teardown panic leaves reporting armed. An abort or other unexpected process termination
|
||||
closes the pipe without that marker.
|
||||
The helper saves a local report under the session log folder's `Crash Reports` directory and
|
||||
shows a native error dialog. Control-server launches save the report without a modal dialog.
|
||||
Reports contain version/platform, UTC detection and observer-start times, observed duration,
|
||||
GUI uptime at the last target-present sample, and at most the last 64 KiB of the matching
|
||||
session's log. A separate ring retains the last 32 timestamped, fixed-vocabulary backend and
|
||||
GPU observations even if startup messages have fallen out of the log tail. `WGPU_BACKEND`
|
||||
values are normalized to known backend names; arbitrary environment text is not copied.
|
||||
Logging filters and the session file size cap can omit later events from the log tail.
|
||||
They are never uploaded automatically. Review logs for private project paths before sharing.
|
||||
|
||||
On Windows the helper acquires a process handle before acknowledging readiness and retains
|
||||
it until the GUI exits. It records the real OS exit status in unsigned hexadecimal, with a
|
||||
small lookup of known status names. These bits are **not a verified exception record**:
|
||||
software can explicitly set the same code, and an access violation does not identify the
|
||||
faulting module or prove a driver crash. Other platforms report exit status as unavailable.
|
||||
A lost pipe while the GUI is still present, or a failed/absent process refresh, is not reported
|
||||
as confirmed termination.
|
||||
|
||||
The helper samples only the target process and system RAM at roughly one-second intervals,
|
||||
retaining a count, last-attempt time, sampled resident-memory maximum, Windows private-commit
|
||||
maximum (virtual memory on other platforms), and system available-RAM minimum. This is a
|
||||
fixed-size summary, not a memory dump. OS query failure can leave stale or zero values, and
|
||||
sampling misses short spikes. System commit limits/usage and GPU VRAM budgets are not captured.
|
||||
No threshold is treated as proof of memory exhaustion. Typed wgpu OOM, internal/validation
|
||||
errors and device-loss notifications are observations; device destruction is distinguished
|
||||
from unknown loss, and neither establishes why the process later exited.
|
||||
|
||||
Run `cargo test -p effectcraft crash_report::tests` for bounded protocol, normal shutdown,
|
||||
actual subprocess abort, unrelated child handles, concurrent sessions, missing log/helper,
|
||||
log rotation, bounded retained events, live-GUI pipe loss, and report-write failure coverage.
|
||||
Windows also tests a real subprocess exit with explicit `0xC0000017` bits, without exhausting
|
||||
memory or pretending that the exit was an actual OOM exception. The three ignored tests are subprocess entry
|
||||
points launched by these tests, not manual tests.
|
||||
|
||||
These are observations, not memory recovery or a causal diagnosis: a pipe closing does not
|
||||
prove an out-of-memory failure. System-wide exhaustion, killing the whole process tree,
|
||||
or an unavailable native dialog service can also prevent the helper from reporting. Existing
|
||||
auto-save recovery remains responsible for restoring previously saved work.
|
||||
|
||||
Native exception records, faulting-module attribution and driver/TDR confirmation require
|
||||
separately collected OS event evidence or a native dump. They are deliberately not gathered
|
||||
here: dumps can contain document contents and credentials. Any future dump workflow must be
|
||||
explicitly opt-in, bounded, local-first and separately reviewed before sharing. This reporter
|
||||
must remain useful when those sources are unavailable.
|
||||
+13
-8
@@ -173,9 +173,9 @@ the same futures with `passes::block_on`; they never wait there).
|
||||
|
||||
- **Render Queue**: `effectcraft_export::export_async` renders each frame in passes (frames
|
||||
with deferred readbacks render one by one instead of in parallel batches) and encodes as
|
||||
before (`Exporter::export_async`). As on the desktop, a job renders every frame on the
|
||||
project's renderer (the worker's GPU with Mercury GPU Acceleration), not on whichever
|
||||
compositor measured faster, so runs don't differ by timing.
|
||||
before (`Exporter::export_async`). Unlike the desktop, which times both compositors on a
|
||||
job's first frames and keeps the faster, a job renders every frame on the project's renderer
|
||||
(the worker's GPU with GPU Acceleration), so runs don't differ by timing.
|
||||
- **Analyses** (Warp Stabilizer, 3D Camera Tracker, Track Motion, mask and face tracking, Roto
|
||||
Brush freeze and propagation): their work functions are `async`; started with `wait` inside
|
||||
a job they are queued and awaited (`offload::run_or_queue`), and each input frame renders in
|
||||
@@ -332,9 +332,13 @@ pixels.
|
||||
|
||||
Settings ▸ Disk ▸ Disk Cache works in the browser, backed by the **Origin Private File System**
|
||||
(`src/diskcache.rs`): viewer frames are kept under `effectcraft-cache/v1/frames/<32 hex>.ecc`
|
||||
between visits, in the desktop cache's format (`disk_cache::frame_entry`: 8-bit premultiplied
|
||||
RGBA, LZ4, a checksum), keyed by the same content hash (the comp and everything it uses, frame,
|
||||
scale, view, render options).
|
||||
between visits, in the desktop cache's format (`disk_cache::frame_entry`: the source files the
|
||||
frame read with their size and content hash, then 8-bit premultiplied RGBA, LZ4, a checksum),
|
||||
keyed by the same content hash (from memory: the comp, everything it can draw and what its
|
||||
expressions can read, frame, scale, view, render options; see
|
||||
[architecture.md](architecture.md)). The page reads an entry only while the files it lists are
|
||||
unchanged; a frame whose files can't all be named is not stored. Reload Footage has nothing to
|
||||
drop here: the file table's identities are content hashes, so a replaced file never matches.
|
||||
|
||||
- A render request to a frame worker carries the frame's disk key; the worker posts the frame,
|
||||
then writes the entry with a **synchronous access handle** into a temporary file moved into
|
||||
@@ -352,8 +356,9 @@ scale, view, render options).
|
||||
Cache / `edit.purge {what: "disk"}` clears it. `effectcraft.info().diskCache` has the same.
|
||||
|
||||
**Layer buffers (M13.30).** Slow layer buffers (≥ 20 ms to render) go to the disk cache too
|
||||
(`effectcraft-cache/v1/layers/<32 hex>.ecc`, `disk_cache::layer_entry`, keys salted per project
|
||||
and footage as on the desktop). A layer lookup happens in the middle of a synchronous render,
|
||||
(`effectcraft-cache/v1/layers/<32 hex>.ecc`, `disk_cache::layer_entry`, keyed as on the desktop:
|
||||
`disk_cache::layer_disk_key`; each entry keeps the source files its buffer was made of, with
|
||||
the file table's size and content hash, and is used only while they are unchanged). A layer lookup happens in the middle of a synchronous render,
|
||||
where the browser's file system can't be read, so it is made synchronous by fetching first:
|
||||
|
||||
1. Each frame worker backs its layer cache with a `PrefetchStore` (`effectcraft_render::cache`):
|
||||
|
||||
Reference in new issue
Block a user