mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-31 21:48:20 -06:00
Merge main into fix/particle-birth-history
This commit is contained in:
+36
-16
@@ -10,15 +10,6 @@ user can do, an agent can do too. There are three ways in:
|
||||
| **CLI** (`effectcraft-cli exec/get/set/...`) | one-shot scripting and CI; JSON output with `--json` | no |
|
||||
| **Control channel** (`effectcraft --control 9877`) | driving and seeing the live UI; see [control-protocol.md](control-protocol.md) | yes |
|
||||
|
||||
## Find the presets folder
|
||||
|
||||
The native Effects & Presets panel's options menu includes **Copy Presets Folder Path**. It copies
|
||||
the folder the panel scans: `EC_PRESETS_DIR` when set, otherwise `~/Documents/EffectCraft/Presets`
|
||||
using `HOME` or `USERPROFILE`. Relative paths are resolved to an absolute path. The folder may
|
||||
not exist yet; this action does not create it.
|
||||
Over the live UI control channel, `ui.menu.invoke {"id":"effects.copyPresetsFolderPath"}` copies
|
||||
the same path and returns `{path}`. The web app reports that it has no local presets folder.
|
||||
|
||||
## MCP setup
|
||||
|
||||
Build once with `cargo build --release -p effectcraft-cli`, then register the server. For Claude
|
||||
@@ -52,6 +43,11 @@ Desktop, Cursor and the like) take the same `command` and `args`.
|
||||
- **Bridge** (`["mcp", "--bridge", "9877"]`): drives a running `effectcraft --control 9877`, so you
|
||||
see every change live. Bridge mode adds `screenshot` and the `ui_*` tools.
|
||||
|
||||
After a broken connection, the bridge retries explicitly read-only control queries once.
|
||||
Engine commands, UI edits and unknown methods whose transmission or reply fails report an
|
||||
unknown outcome: the app may already have applied them. Inspect the app before explicitly
|
||||
retrying. A later call reconnects; timeouts and malformed replies are never automatically resent.
|
||||
|
||||
### From an installed release
|
||||
|
||||
The release packages ship `effectcraft-cli` alongside the desktop app, so no build is needed:
|
||||
@@ -184,6 +180,7 @@ Short recipes; every step is one tool call.
|
||||
| Animate | `add_keyframe {"layer":2,"path":"transform/scale","keys":[{"time":0,"value":[0,0]},{"time":0.5,"value":[100,100]}],"interpolation":"easyEase"}` |
|
||||
| Effect with settings | `list_effects {"filter":"glow"}` → `add_effect {"layer":2,"effect":"Glow","values":{"threshold":40,"radius":30}}` → `set_property {"layer":2,"path":"effects/#1/intensity","value":2}` |
|
||||
| Key a green screen | `add_effect {"layer":2,"effect":"Keylight (1.2)"}` → `execute_command effect.pickColor {"layer":2,"effect":1,"param":"screenColour","x":20,"y":20,"average":true}` (a screen pixel, layer space; samples the effect's input) → `set_property {"layer":2,"path":"effects/#1/screenMatte/clipBlack","value":10}` → `render_frame {"transparent":true}` |
|
||||
| Key objects by Cryptomatte | `add_effect {"layer":2,"effect":"Cryptomatte"}` → `execute_command effect.pickCryptomatte {"layer":2,"effect":1,"x":120,"y":80}` (the ID under that layer pixel replaces the Selection; `"mode":"add"` / `"remove"` edit it; `effect.cryptomatteAt` with the same point only reports the name) → `render_frame` |
|
||||
| Drive with an expression | `set_property {"layer":2,"path":"transform/rotation","expression":"time*90"}` |
|
||||
| Inspect a layer | `get_layer {"layer":2,"flat":true}` (every property with its `path`, value, key count) |
|
||||
| Check the result | `render_frame {"time":1.5,"max_side":640}`; in bridge mode `screenshot {"panel":"Timeline"}` |
|
||||
@@ -220,7 +217,7 @@ Things that tripped up a long agent-driven session (a multi-scene 3D piece built
|
||||
|
||||
### Cookbook (from end-to-end QA)
|
||||
|
||||
The scenarios in `crates/automation/tests/qa/` and `apps/effectcraft-cli/tests/qa_template.rs`
|
||||
The scenarios in `crates/automation/tests/qa/` and `apps/effectcraft-cli/tests/it/qa_template.rs`
|
||||
build real projects through these interfaces; they are worked examples of everything below.
|
||||
|
||||
* **Name things when you create them**: `layer.newText`, `layer.newShape`, `layer.newSolid`,
|
||||
@@ -307,7 +304,9 @@ build real projects through these interfaces; they are worked examples of everyt
|
||||
The CLI also renders an exported template directly:
|
||||
`effectcraft-cli render --template lt.ectemplate --values values.json --out 'title_[#####].png'`.
|
||||
`values.json` is an object such as `{"Title":"John Smith","Bar Color":"#00c080","Bar Opacity":50}`;
|
||||
names follow `essential.set`, including its case-insensitive matching. Values override one
|
||||
names follow `essential.set`, including its case-insensitive matching. A Media Replacement
|
||||
control takes a footage file path (`{"Clip":"/in/clipB.mp4"}`, or `{"path": …}`), which is
|
||||
imported and set as the control's `item`. Values override one
|
||||
imported instance; the template file and its source controls keep their defaults. Omit
|
||||
`--values` to render those defaults, or use the same options with `render-frame` for a PNG.
|
||||
Values must fit in 1 MiB. Invalid JSON, unknown controls and invalid values fail before rendering.
|
||||
@@ -325,8 +324,8 @@ build real projects through these interfaces; they are worked examples of everyt
|
||||
### Effect positions
|
||||
|
||||
Effect points, Puppet pins (`puppet.addPin`, `puppet.recordPin`), Paint, Liquify and Roto Brush
|
||||
strokes and `effect.pickColor` are in **effect space**: layer pixels from the top-left of the
|
||||
layer's effect bounds. For footage, solids and precomps that is the layer's own pixel grid. Shape
|
||||
strokes, `effect.pickColor` and `effect.pickCryptomatte` are in **effect space**: layer pixels
|
||||
from the top-left of the layer's effect bounds. For footage, solids and precomps that is the layer's own pixel grid. Shape
|
||||
and text layers have comp-sized bounds centred on their content's origin, so in a `w` × `h` comp
|
||||
the point `[w / 2, h / 2]` is that origin (a new effect's default point). `prop.get` / `prop.set`
|
||||
read and write these values as they are.
|
||||
@@ -534,7 +533,10 @@ indices): character attributes
|
||||
starts editing (`position` instead of `box` makes point text; `vertical: true` vertical type).
|
||||
2. `text.insert {"text":"Hello world"}` types at the caret (replacing the selection);
|
||||
`text.setSelection {"start":6,"end":11}` selects "world"; `text.moveCaret {"to":"wordLeft",
|
||||
"extend":true}` moves like the arrow keys; `text.delete {"word":true}` is Alt+Backspace.
|
||||
"extend":true}` moves like the arrow keys; `text.delete {"word":true}` is Alt+Backspace;
|
||||
`text.deleteSurrounding {"before":1,"after":2,"bytes":true}` deletes the characters around the
|
||||
selection without deleting it (what an input method's replacement asks for: the selection stays
|
||||
for the commit that follows; a byte count ending inside a character leaves that character).
|
||||
3. `layer.setText {"range":[6,11], "size":40, "fill":"#ff5500"}` styles the selection (the
|
||||
Character panel does the same); with an empty range it sets the style the next typed text takes.
|
||||
4. `edit.copy`, `edit.paste`, `edit.pasteTextMatchFormatting` and `edit.pasteTextFormattingOnly`
|
||||
@@ -544,8 +546,10 @@ Fonts: `text.fonts` (MCP `list_fonts`) lists every family a text layer can use,
|
||||
styles, origin (`bundled`, `system`, `user`), `variable` (it has variation axes) and own-language name
|
||||
(`{"query":"gothic"}` filters by name, `{"variable":true}` keeps only variable fonts, like the font
|
||||
menu's Variable Fonts filter; `{"rescan":true}` first picks up fonts installed since launch). Pass a listed `family` and
|
||||
one of its `styles` to `layer.setText {"font", "style"}`; `text.fontFeatures` says which OpenType
|
||||
options a font draws with its own glyphs.
|
||||
one of its `styles` to `layer.setText {"font", "style"}`. A variable font's named instances (`Light` …
|
||||
`Black`) are styles of its family; a style or an instance's PostScript name (`"font": "Figtree-Black"`)
|
||||
sets the instance's axis values, and `variations` set on the text override them axis by axis.
|
||||
`text.fontFeatures` says which OpenType options a font draws with its own glyphs.
|
||||
A tool with its own font library can hand it over without installing it: folders listed in
|
||||
`EFFECTCRAFT_FONT_DIRS` (a path list, `:`-separated, `;` on Windows) are scanned like the system font folders.
|
||||
On Linux and the BSDs the system font folders are `/usr/share/fonts`, `/usr/local/share/fonts`,
|
||||
@@ -595,6 +599,12 @@ Extension points and the bundled extensions: [plugins.md](plugins.md).
|
||||
properties, FF missing effects, EE Expressions, LL Waveform, UU everything changed from its
|
||||
default. Shift+key adds or removes a set; the same key again hides it; Ctrl/Cmd+` twirls the
|
||||
selected layers open or closed. With no layer selected they apply to every layer.
|
||||
* A row control clicked on one of several selected layers applies to every selected layer, as in
|
||||
After Effects: the A/V switches (video, audio, solo, lock), the Switches column (shy, collapse,
|
||||
quality, fx, frame blending, motion blur, adjustment, 3D), the label swatch and the Modes column
|
||||
(blending mode, Preserve Transparency), one undo step, with the layers the switch doesn't apply
|
||||
to left out. A control on an unselected row changes that layer alone; Alt-clicking an A/V feature
|
||||
still sets the selected layers apart from the rest.
|
||||
* `keys.toggleTransform {"layers"?: [...], "prop": "position"}` (Alt+Shift+A/P/S/R/T) adds or
|
||||
removes a keyframe at the current time on that Transform property of each layer, in one undo
|
||||
step (separated X/Y/Z Position; Orientation and X/Y/Z Rotation on 3D layers).
|
||||
@@ -667,6 +677,7 @@ effectcraft-cli render-frame main.ecproj --time 1 --max-side 640 --out f.png --j
|
||||
effectcraft-cli exec layer.newNull --bridge 9877 # same commands, against the live app
|
||||
effectcraft-cli exec file.exportLottie '{"comp":"Main","path":"main.json","includeExpressions":true}' main.ecproj --json
|
||||
effectcraft-cli exec file.importLottie '{"path":"anim.json"}' main.ecproj --save
|
||||
effectcraft-cli render main.ecproj --comp Main --out main.mov --format prores --progress # progress lines on stderr for a program that reads them
|
||||
```
|
||||
|
||||
Scripts written against After Effects' documented scripting API run with `script`:
|
||||
@@ -714,3 +725,12 @@ open the demo project; without it the app starts with an empty project) and use
|
||||
mode or the raw control channel. A good loop is: `ui_elements` to find an id, `ui_click` or `ui_drag`
|
||||
to act, then `screenshot {"panel":"Timeline"}` to check the result. `render_frame` shows the
|
||||
composition itself at any zoom.
|
||||
|
||||
### Effects & Presets
|
||||
|
||||
The native Effects & Presets panel's options menu includes **Copy Presets Folder Path**. It copies
|
||||
the folder the panel scans: `EC_PRESETS_DIR` when set, otherwise `~/Documents/EffectCraft/Presets`
|
||||
using `HOME` or `USERPROFILE`. Relative paths are resolved to an absolute path. The folder may
|
||||
not exist yet; this action does not create it.
|
||||
Over the live UI control channel, `ui.menu.invoke {"id":"effects.copyPresetsFolderPath"}` copies
|
||||
the same path and returns `{path}`. The web app reports that it has no local presets folder.
|
||||
|
||||
@@ -356,7 +356,7 @@ to the preview cache. Scoped allocation failures retain the adaptive budget/CPU
|
||||
|
||||
**Recovery boundary:** the host installs handlers through eframe's CreationContext, after egui's
|
||||
presentation pipelines but before EffectCraft's pipelines. This does not cover that earlier egui
|
||||
startup window. eframe 0.36.2 supports lost-surface recreation, not replacing its live device and
|
||||
startup window. eframe 0.37 supports lost-surface recreation, not replacing its live device and
|
||||
all presentation resources. Device loss therefore requires restarting the presentation backend;
|
||||
CPU compositing alone cannot restore that display. No automatic restart or persistent safe-mode
|
||||
sentinel is implemented here. Browser scope futures are not synchronously awaited; the browser page
|
||||
@@ -728,3 +728,5 @@ JSON parsing is now the bulk of open), and rendering 5,000-layer comps faster.
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-10 | minor | Status line added; crate table checked against `crates/` (every crate listed); effect count and VP9 encoder description corrected |
|
||||
|
||||
PDF document structure uses the `craft-pdf` read feature through an app-owned bounded object adapter. EffectCraft retains its drawing tree, fonts, content/image filters and EPS interpreter; ordinary object/xref limits remain 256 MiB on 64-bit and 64 MiB on 32-bit/WASM, including repair.
|
||||
|
||||
@@ -37,7 +37,8 @@ printf '{"id":1,"method":"engine.execute","params":{"command":"comp.new","params
|
||||
|---|---|---|
|
||||
| `engine.execute` | `{command, params?}` | The command's result. Every engine command (`effectcraft-cli commands`) runs exactly as it does headless, so it never opens a dialog: `comp.new {}` creates a default comp. UI-only ids (`tool.*`, `view.*`, `window.*`, `playback.*`, `timeline.*`, `app.*`) fall through to the menu dispatcher. Unknown top-level params are rejected, and the error lists the accepted keys. |
|
||||
| `ui.menu.invoke` | `{id, params?}` | Like a menu click: commands with empty params may open their dialog (New Composition, Solid Settings, file pickers). |
|
||||
| UI commands (via either) | | `timeline.column {column, visible?}` (av, keys, label, num, comment, switches, modes, parent, in, out, duration, stretch), `timeline.sourceName {value?}`, `timeline.search {query}`, `timeline.reveal.<kind>` / `timeline.revealAdd.<kind>` (the property shortcuts and Shift+shortcut: position, scale, rotation, opacity, anchor, effects, masks, feather, levels, animated, and the double-press sets maskPath, maskOpacity, timeRemap, expressions, material, paint, selected, missingEffects, waveform), `timeline.keyAt.<anchor\|position\|scale\|rotation\|opacity>` (Alt+Shift+A/P/S/R/T), `timeline.twirlSelected` (Ctrl/Cmd+`), `timeline.collapseAll`, `flowchart.options {layers?, effects?, solids?, direction?: lr\|tb, comp?}`, `flowchart.graph {comp?}` → `{nodes, edges}`, `window.workspace {name}` (all After Effects workspaces). |
|
||||
| Copy a Media Browser source path | `ui.menu.invoke {id: "mediaBrowser.copySourcePath", params: {path}}` | Copies the exact non-empty path string to the clipboard and returns `{path}`. Supports file/folder paths and virtual source identifiers. Missing, empty or non-string paths return an error. |
|
||||
| UI commands (via either) | | `timeline.column {column, visible?}` (av, keys, label, num, comment, switches, modes, parent, in, out, duration, stretch), `timeline.sourceName {value?}`, `timeline.search {query}`, `timeline.reveal.<kind>` / `timeline.revealAdd.<kind>` (the property shortcuts and Shift+shortcut: position, scale, rotation, opacity, anchor, effects, masks, feather, levels, animated, and the double-press sets maskPath, maskOpacity, timeRemap, expressions, material, paint, selected, missingEffects, waveform), `timeline.keyAt.<anchor\|position\|scale\|rotation\|opacity>` (Alt+Shift+A/P/S/R/T), `timeline.twirlSelected` ``(Ctrl/Cmd+`)``, `timeline.collapseAll`, `flowchart.options {layers?, effects?, solids?, direction?: lr\|tb, comp?}`, `flowchart.graph {comp?}` → `{nodes, edges}`, `window.workspace {name}` (all After Effects workspaces). |
|
||||
| `engine.commands` | `{filter?, enabledOnly?}` | `[{id, label, menu, shortcut, params, enabled, why}]` |
|
||||
| `ui.menu.list` | `{}` | The menu tree with enablement. |
|
||||
| `render.frame` | `{comp?, time?, max_side?, path?, base64?}` | Renders a comp frame through the session, independent of the viewer zoom or resolution. `comp` is an id or name (default: the active comp), `time` is in comp seconds (default: the CTI), `max_side` caps the longest side (0 = full size). Writes a PNG to `path` (default: a temp file) and returns `{comp, time, width, height, path}`. With `base64: true` it returns `{…, png: "<base64>"}` and writes no file. |
|
||||
@@ -50,7 +51,7 @@ export formats, parity summary), `jobs.list` (Progress panel jobs, e.g. the back
|
||||
`footage.check` after File ▸ Open).
|
||||
|
||||
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/ui_agent_audit.rs`.
|
||||
panel registers an element id; both are enforced by `crates/ui-egui/tests/it/ui_agent_audit.rs`.
|
||||
|
||||
## UI state
|
||||
|
||||
@@ -125,7 +126,7 @@ frames and can be assigned through `shortcuts.set`.
|
||||
|
||||
| Method | Params | Result |
|
||||
|---|---|---|
|
||||
| `ui.screenshot` | `{path?, panel?, id?}` | `{path, width, height}`: a PNG of the window, or cropped to one panel or element. |
|
||||
| `ui.screenshot` | `{path?, panel?, id?}` | `{path, width, height}`: a PNG of the window, or cropped to one panel or element. The panel or element rectangle is resolved when the capture is issued (after the UI settles), so it follows a panel that moved; a panel or id that is not on screen is retried and then reported, never replaced by a whole-window shot. |
|
||||
|
||||
Element ids are stable, for example `tools.Selection`, `panel.Timeline`, `panel.tab.EffectControls`,
|
||||
`panel.tab.Timeline.<comp>` (one Timeline tab per open comp; `panel.tab.Timeline` is the shown
|
||||
|
||||
+16
-5
@@ -15,7 +15,7 @@ After Effects', and does the preset library exist. The per-effect catalogue is t
|
||||
| Parameter names, order, popups, units, defaults | aligned in M9.12 | Agent-checked against After Effects' documentation, not against a live dump |
|
||||
| On the GPU | **280** (92%) | Measured: registry flags; GPU vs CPU agreement tested per effect |
|
||||
| 32-bit float, no clamping | 306 | Measured: registry flags |
|
||||
| **Rendered output matches After Effects** | **unmeasured** | No effect is compared with an After Effects render yet. `effectcraft_effects::fidelity::compare_frames` exists for the corpus ([fidelity/README.md](fidelity/README.md)); simulation effects are pinned by golden hashes of our own output (M13.27) |
|
||||
| **Rendered output matches After Effects** | **partly measured** | Some colour effects are checked against After Effects 2026 renders of our own test projects: Tint, Tritone, Black & White, Exposure, Levels, Curves, Luma Key, Extract, 4-Color Gradient and Fill within 1–3/255, Brightness & Contrast up to 6.2/255 (Color Correction, Generate and Keying rows below); every other effect is unmeasured. `effectcraft_effects::fidelity::compare_frames` exists for the corpus ([fidelity/README.md](fidelity/README.md)); simulation effects are pinned by golden hashes of our own output (M13.27) |
|
||||
| Animation presets | **54** original presets (8 text-animator, 23 Transitions, 23 Backgrounds) against After Effects' **621** `.ffx` presets | Measured: `crates/engine/presets/text_animators.json`, `crates/engine/presets/transitions.json`, `crates/engine/presets/backgrounds.json`; the After Effects `Presets` folder |
|
||||
| 26.x additions | Curl Noise (26.3) present (#463 open); effect colour labels (26.5) present in Effect Controls only, not in the Timeline; the rebuilt Effect Controls (26.5) missing | Release notes |
|
||||
|
||||
@@ -30,15 +30,15 @@ corpus compares renders, each effect is assumed to differ somewhere, and users k
|
||||
| Audio | 13 | 0 | CPU by design (applied in the mixdown) |
|
||||
| Blur & Sharpen | 16 | 15 | Gaussian Blur interaction and output to be measured first ([fidelity/ae-2026-acceptance.md](fidelity/ae-2026-acceptance.md)) |
|
||||
| Channel | 13 | 13 | — |
|
||||
| Color Correction | 40 | 40 | Tint has Swap Colors (#963); ACES 2.0 / OCIO 2.5 (26.5); Apply Color LUT (#514) and 32-bpc blending (#494) are closed |
|
||||
| Color Correction | 40 | 40 | Tint, Tritone and Black & White (sliders and Tint) match After Effects 2026 renders (Rec. 601 luminance); Levels (Individual Controls) matches After Effects 2026 in 32 bpc (channel controls before RGB, "Off for 32 bpc Color" unclipped; plain Levels' channel order not measured); Brightness & Contrast (non-legacy) close to After Effects 2026 ramps, not identical (up to 6.2/255 off; the test allows 7/255) (negative settings invert the positive curves); Curves interpolates a natural cubic spline through its points, as After Effects 2026 (exact on its grey steps); Tint has Swap Colors (#963); ACES 2.0 / OCIO 2.5 (26.5); Apply Color LUT (#514) and 32-bpc blending (#494) are closed |
|
||||
| Distort | 39 | 35 | Liquify has no viewer brush; #303 (Mesh Warp drag) and #534 (CC Power Pin Unstretch) are closed |
|
||||
| Expression Controls | 9 | 9 | — |
|
||||
| Generate | 26 | 25 | Fractal on the CPU (needs f64); #526 (Reset) and #536 (Write-on marks) are closed |
|
||||
| Generate | 26 | 25 | 4-Color Gradient's Blend matches After Effects 2026 (weights 1 / (d² + Blend²)); Fill's Opacity is the filled layer's opacity, as After Effects 2026 renders it (with a Fill Mask it still mixes with the original, not measured); Fractal on the CPU (needs f64); #526 (Reset) and #536 (Write-on marks) are closed |
|
||||
| Immersive Video | 12 | 12 | — |
|
||||
| Keying | 12 | 12 | Key Cleaner approximated; #533 (wire-removal Frame Offset) is closed |
|
||||
| Keying | 12 | 12 | Luma Key and Extract key on Rec. 601 luminance, as After Effects 2026; Key Cleaner approximated; #533 (wire-removal Frame Offset) is closed |
|
||||
| Matte | 6 | 4 | Mocha shape reads our documented JSON, not Mocha's format |
|
||||
| Noise & Grain | 13 | 12 | Curl Noise (#463); Match Grain on the CPU |
|
||||
| Obsolete | 9 | 9 | — |
|
||||
| Obsolete | 9 | 9 | Luma Key matches After Effects 2026 on a grey ramp (hard cut, Tolerance only for Similar / Dissimilar) |
|
||||
| Paint | 1 | 0 | Tablet pressure not read ([hardware-parity.md](hardware-parity.md)) |
|
||||
| Perspective | 10 | 9 | Chromatic Aberrations pan/tilt crash (#580) |
|
||||
| Simulation | 18 | 18 | Particle Playground lacks Particle Exploder, text particles, ephemeral mapper; CC Particle World camera fixed (#397) |
|
||||
@@ -64,6 +64,17 @@ the GPU is performance, not parity.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-19 | minor | Tritone uses Rec. 601 luminance, measured against After Effects 2026 renders of a red solid and a grey ramp (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Black & White's Tint keeps the gray's luminosity (Color blend), measured against After Effects 2026 renders (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Levels: "Off for 32 bpc Color" no longer clips in 32 bpc projects, unclipped values extrapolate below Output Black; Levels (Individual Controls) applies the channel controls before the RGB ones, measured against After Effects 2026 (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Brightness & Contrast: negative Brightness / Contrast are the inverse curves of the positive ones (Brightness −40: 20/255 → 6/255 off After Effects 2026; still up to 6.2/255 off, not the same pixels), CPU and GPU |
|
||||
| 2026-10-19 | minor | Luma Key: Key Out Brighter / Darker cut hard at Threshold, measured against After Effects 2026 renders of a ramp (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Luma Key and Extract: Rec. 601 luminance (was Rec. 709), measured against After Effects 2026 (CPU and GPU) |
|
||||
| 2026-10-19 | minor | 4-Color Gradient: each point weighs 1 / (d² + Blend²), measured against After Effects 2026 at Blend 10 / 50 / 100 / 300 (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Fill without a mask: the layer becomes the fill colour at its alpha × Opacity (was a mix with the original), measured against After Effects 2026 (CPU and GPU) |
|
||||
| 2026-10-19 | minor | Curves: natural cubic spline through the points, clamped to 0..1 (was a monotone spline), measured against After Effects 2026 (CPU and GPU); Lumetri's curves and Glow's Arbitrary Map keep the monotone spline |
|
||||
| 2026-10-18 | minor | Tint uses Rec. 601 luminance weights, measured against After Effects 2026 renders of red, green, blue and grey solids (CPU and GPU) |
|
||||
| 2026-10-18 | minor | Exposure converts to linear light with a pure 2.2 power curve, as measured in After Effects (Offset 0.0005 / 0.002 / 0.02 / 0.1 on black: 8 / 15 / 43 / 90), not the piecewise sRGB curve; its sliders take After Effects' ranges (Exposure −100..100, slider −4..4; Offset −2..2, slider −0.5..0.5; Gamma Correction 0.1..10) |
|
||||
| 2026-10-12 | minor | Parameters 3,014 to 3,017 (effects.md); #303, #514, #526, #533–#536 marked closed instead of pending |
|
||||
| 2026-10-12 | minor | Effect colour labels (26.5) added in Effect Controls |
|
||||
| 2026-10-12 | minor | Tint's Swap Colors button and agent-drivable command (#963); undo/redo, instance targeting, animated layer-time edits and invalid-input tests |
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ Effects are addressed by id (`ec.<category>.<name>`) or display name in commands
|
||||
| Corner Pin | `ec.distort.cornerpin` | 4 | GPU | 32 | Implemented |
|
||||
| Detail-preserving Upscale | `ec.distort.upscale` | 4 | | 32 | Implemented |
|
||||
| Displacement Map | `ec.distort.displacementmap` | 8 | GPU | 32 | Implemented |
|
||||
| Liquify | `ec.distort.liquify` | 12 | 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 |
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@ These checks use original compositions and public interaction behavior. No Adobe
|
||||
implementation code are included. Adobe's [selection documentation](https://helpx.adobe.com/after-effects/desktop/work-with-layers/select-and-arrange-layers/selecting-arranging-layers.html)
|
||||
describes Timeline range selection and Composition Shift-click/Shift-marquee selection.
|
||||
|
||||
`crates/ui-egui/tests/ui_workflow_selection.rs` exercises actual pointer and keyboard events:
|
||||
`crates/ui-egui/tests/it/ui_workflow_selection.rs` exercises actual pointer and keyboard events:
|
||||
|
||||
- Timeline Shift-click selects the visible range, skipping locked layers; Ctrl/Command-click toggles a layer.
|
||||
- A drag beginning in the empty Timeline outline selects the intersected layer rows without reordering them.
|
||||
|
||||
@@ -43,7 +43,7 @@ trips and renders, checked by tests, not against After Effects), **partial** (na
|
||||
| TIFF | R/W | yes | yes (sequence) | good |
|
||||
| OpenEXR (multi-layer, Cryptomatte) | R/W | yes | yes (32-bit sequence) | good; HTJ2K compression fails (#482); EXRs without an unnamed RGB layer decode twice (#474) |
|
||||
| Photoshop PSD / PSB | R (footage, composition) / W (layers, sequence) | yes (layers, groups, masks, styles, smart objects) | Save Frame As ▸ Photoshop Layers; no Photoshop sequence output | good, from Adobe's published spec |
|
||||
| Illustrator `.ai`, PDF, EPS | R | yes (vector, Continuously Rasterize, Create Shapes) | — | partial: no JPX / JBIG2 images, no Illustrator procset EPS |
|
||||
| Illustrator `.ai`, PDF, EPS | R | yes (vector, Continuously Rasterize, Create Shapes; Composition and Composition - Retain Layer Sizes, #1536) | — | partial: no JPX / JBIG2 images, no Illustrator procset EPS |
|
||||
| SVG | R | yes | — | good; SVG markup on the clipboard pastes as a shape layer (26.3, #738); Illustrator's own clipboard data (PDF/AI) doesn't |
|
||||
| GIF | R/W | yes | yes (animated) | good |
|
||||
| BMP | R | yes | — | good |
|
||||
|
||||
+24
-1
@@ -70,6 +70,16 @@ 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.
|
||||
|
||||
## 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
|
||||
Project** (also Layer ▸ Reveal ▸ Reveal Layer Source in Project). The Project panel opens, its
|
||||
search is cleared, the folders holding the source open, and the source item is selected.
|
||||
|
||||
Layers whose image is generated inside the composition — text, shape, null, camera, light and
|
||||
3D primitive layers — have no Project item, so the command is not offered for them. Adjustment
|
||||
layers do have one: the solid that backs them.
|
||||
|
||||
## Interpret Footage
|
||||
|
||||
Select a footage item in the Project panel and choose **File ▸ Interpret Footage ▸ Main…**
|
||||
@@ -107,6 +117,18 @@ vectors, positions and IDs are never the picture. EXtractoR reads every other la
|
||||
|
||||
## Replacing and reloading
|
||||
|
||||
Importing a layered file as a composition refreshes the decoded sources used by its layers,
|
||||
including files overwritten at the same path and byte size.
|
||||
|
||||
- **Replace a layer's source** without rebuilding the layer: Alt-drag (Option-drag on macOS) a
|
||||
footage item, composition or solid from the Project panel onto a layer in the Timeline. The
|
||||
layer keeps its name, timing, transforms, keyframes, expressions, effects, masks, switches,
|
||||
parenting and track matte; only its source changes. A source with audio adds Audio properties
|
||||
when the layer has none. To replace several selected layers with the selected Project item, press
|
||||
Ctrl+Alt+/ (Cmd+Opt+/). Compositions, footage and solids are interchangeable; folders, data files
|
||||
and 3D models are not sources, and a composition can't be nested in itself. Alt-dragging onto a
|
||||
layer is one layer at a time, as in After Effects.
|
||||
Agents: `layer.replaceSource {"item": id, "layers": [ids]}`.
|
||||
- **File ▸ Replace Footage ▸ File…** (Ctrl+H) points the item at another file. Pick a numbered
|
||||
file to replace it with that file's whole sequence (`file.replaceFootage {"path": …,
|
||||
"sequence": false}` for one frame).
|
||||
@@ -126,7 +148,8 @@ vectors, positions and IDs are never the picture. EXtractoR reads every other la
|
||||
File ▸ Open tries each missing file at the same place under the project's new folder, without
|
||||
marking the project modified.
|
||||
- **File ▸ Reload Footage** (Ctrl+Alt+L) reads the files again. A sequence picks up frames
|
||||
added to its folder (a render still in progress) and keeps its interpretation.
|
||||
added to its folder (a render still in progress) and keeps its interpretation; a still is
|
||||
read as its one file, even when numbered files sit next to it.
|
||||
|
||||
**File ▸ Dependencies ▸ Relink Missing Footage…** searches a chosen folder and its subfolders
|
||||
for missing source files on the desktop. It relinks unique exact filenames whose decoded type,
|
||||
|
||||
+22
-7
@@ -1,6 +1,6 @@
|
||||
# Where EffectCraft falls short
|
||||
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (issue, PR and catalog references gap 21: percentage guides and effect colour labels in; earlier change: re-audited against `upstream/main` 470ea7a3; ranked gap list from the 10 October re-measure otherwise unchanged) · **Target:** Adobe After Effects 2026 (26.5.0.89)
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-13 · **Change:** minor (path-editing layer bounds, #1220; issue, PR and catalog references gap 21: percentage guides and effect colour labels in; earlier change: re-audited against `upstream/main` 470ea7a3; ranked gap list from the 10 October re-measure otherwise unchanged) · **Target:** Adobe After Effects 2026 (26.5.0.89)
|
||||
|
||||
An honest assessment of how far EffectCraft is from being a real replacement for After Effects,
|
||||
and the work that closes the gap. This document is meant for contributors and agents choosing
|
||||
@@ -64,7 +64,7 @@ are grouped by area; the issue tracker has the rest.
|
||||
| 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) |
|
||||
| 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: 54 against 621** | After Effects ships 621 `.ffx` presets (Behaviors, Backgrounds, Shapes, Synthetics, Text, Transitions…); we ship 54 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) and read only our `.ecpreset`. Missing: Behaviors, 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, #932 Convert to Bezier Path, #959 / #960 shape group toggles and operators. Now closed: #510, #513, #527, #509, #303, #553 | Masks are core compositing | 10–20 h | G2 · [target-app-parity.md](target-app-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) |
|
||||
| 9 | **Effect behaviour bugs** | #580, #463 Curl Noise, #369 Motion Tile output size, #943 Turbulent Displace Evolution, #473 Cryptomatte selection (quoted names with commas fixed, 3b7f1ace; UI and GPU time open), #474 multi-layer EXR (decode shared, faffad3a; f16 storage and cache sizing open). Closed: #526, #514, #533–#536 | Each one breaks a look someone built | 10–20 h | G1/G2 · [effects-parity.md](effects-parity.md) |
|
||||
| 10 | **Colour pipeline differences** | #340 ProRes 4444 tagged bt709 but sRGB-encoded (open); ACES 2.0 / OCIO 2.5 configs (26.5) missing; now closed: #494 32-bpc blend modes, #495 and #750 Working Gamma 2.4, #546 / #547 viewer colour | Wrong colour in delivery is a client-visible failure | 15–30 h | G1 · [file-format-parity.md](file-format-parity.md) |
|
||||
| 11 | **Text rendering and layout** | Open: #453 CJK text in comps, #316 paragraph alignment inverted (maintainers' tests show Left/Right correct; asked for a project), #417 soft at Fit (mostly fixed by #444), #550 toolbar font options. Closed: #483, #583 | Text is in almost every motion graphic | 10–20 h | G2 · [ui-parity.md](ui-parity.md) |
|
||||
@@ -72,12 +72,12 @@ are grouped by area; the issue tracker has the rest.
|
||||
| 13 | **3D model formats and materials** | After Effects 26 imports FBX, USD/USDZ, STL besides glTF and OBJ (`usd_plugins`), and Substance 3D materials with mesh displacement (26.0–26.2); we read glTF, OBJ, STL (binary and ASCII, with facet colours) and FBX 7.x (binary and ASCII: meshes, hierarchy, Lambert/Phong materials and textures; no FBX animation or skinning yet) | Product shots and 3D scenes come as FBX/USDZ | 25–45 h | — · [file-format-parity.md](file-format-parity.md) |
|
||||
| 14 | **Object Matte** (26.2, disk cache in 26.5) | First slice (#783): the Object Matte effect (Effect ▸ Matte) and `objectMatte.apply` turn a point (and optional box) on a reference frame into a matte and follow the object up to 600 frames each way, or segment every frame on its own; Roto Brush's engine, cache and model (MobileSAM from Settings ▸ Roto Brush, classical graph cut without it). Since then: the Object Matte tool in the viewer (click / drag a box, hover crosshair), the box as the model's box prompt, background tracking with progress and cancel, Freeze, and segmentations kept in the disk cache (26.5). Missing: a comparison with After Effects ([plan](fidelity/object-matte.md)) | The new one-click roto | 10–20 h | G7 · [target-app-parity.md](target-app-parity.md) |
|
||||
| 15 | **Hardware video decode and encode** | No VideoToolbox, NVENC, Quick Sync or VA-API; #498 asks for it (answered: CUDA / NVENC need Nvidia's proprietary SDK through FFI, which the no-`unsafe` rule forbids); open PR #819 adds hardware decoding via `filmcraft-platform`; FilmCraft has VideoToolbox / NVENC / VA-API paths to borrow | 4K playback and export speed | 25–45 h | G5/G6 · [hardware-parity.md](hardware-parity.md) |
|
||||
| 16 | **Localization depth** | Menus in 10 languages (ja, zh-hans, zh-hant, uk, es, pt-br, ru, fr, de, cs: 624 rows each); panels in 6 (ja, zh-hans, zh-hant, uk, pt-br, cs); effect names translated in Simplified Chinese (306, plus the generated Effect menu), their ~1,650 parameter names untranslated; Korean and Italian (After Effects ships them) absent; Hangul probe added (#599 closed) | After Effects is used heavily in Japan, China, Korea, Brazil, Europe | 60–110 h + native review | G9 · [localization-parity.md](localization-parity.md) |
|
||||
| 16 | **Localization depth** | Menus in 11 languages (ja, zh-hans, zh-hant, uk, es, pt-br, ru, fr, de, cs, ka: 628 rows each); panels in 7 (ja, zh-hans, zh-hant, uk, pt-br, cs, ka); effect names translated in Simplified Chinese and Georgian (307, plus the generated Effect menu), their ~1,650 parameter names untranslated; Korean and Italian (After Effects ships them) absent; Hangul probe added (#599 closed) | After Effects is used heavily in Japan, China, Korea, Brazil, Europe | 60–110 h + native review | G9 · [localization-parity.md](localization-parity.md) |
|
||||
| 17 | **Media Browser lists files import refuses** | `media_browser.rs` lists AVI; `media::is_importable` has none of them (Targa and Radiance HDR import since #745; DPX and Cineon since #756, log shown as stored: no Cineon Settings conversion yet; MXF OP1a / OP-Atom since #767; FilmCraft has no AVI demuxer); #431 and #324 closed | Confusing failures on common VFX formats | 15–30 h | G6 · [file-format-parity.md](file-format-parity.md) |
|
||||
| 18 | **Relative paths and relinking** | Projects store absolute footage paths; no relink when a project moves | Moving or sharing a project breaks it | 6–12 h | G4 · [file-format-parity.md](file-format-parity.md) |
|
||||
| 19 | **Plug-in ecosystem** | No After Effects SDK hosting (#600, #327 OpenFX, #334, #376 plug-in manager); our WebAssembly API has no third-party plug-ins | Many pro projects depend on Trapcode, Element, Saber… | 40–80 h for API growth; SDK hosting needs an owner decision | G8 · [scripting-parity.md](scripting-parity.md) |
|
||||
| 20 | **Multi-monitor and video output** | #486: any panel can move into a native window of its own (panel menu ▸ Move Panel to New Window, `window.panelWindow`) and so onto another screen, restored with the workspace; still missing: dragging tabs between those windows, app shortcuts while one has the focus, named workspaces remembering them, Mercury Transmit to video hardware (we have a second preview window only) | Two-screen setups are standard for professionals | 10–20 h | G2 · [hardware-parity.md](hardware-parity.md) |
|
||||
| 21 | **Small 26.x features** | Proportional Scrubbing (26.2), pasting Illustrator's own clipboard data as shapes (26.3; SVG markup on the clipboard pastes as a shape layer, #738), variable-font filter (26.3), the rebuilt Effect Controls (26.5). Percentage guides (View ▸ Add Percentage Guide, Convert Guides to Percentage / Pixels) and effect colour labels (Effect Controls header bar and right-click Label menu, `effect.setLabel`) are in (#1095), built from the release notes' one-line descriptions and never compared with After Effects: the Timeline shows no effect label, and the labels' exact UI there is unknown | Users of the current version expect them | 10–20 h left | — · [ui-parity.md](ui-parity.md) |
|
||||
| 21 | **Small 26.x features** | Proportional Scrubbing (26.2), pasting Illustrator's own clipboard data as shapes (26.3; SVG markup on the clipboard pastes as a shape layer, #738), variable-font filter (26.3), the rebuilt Effect Controls (26.5). Percentage guides (View ▸ Add Percentage Guide, Convert Guides to Percentage / Pixels) and effect colour labels (Effect Controls header bar and right-click Label menu, `effect.setLabel`) are in (#1095), built from the release notes' one-line descriptions and never compared with After Effects: the Timeline's effect rows wear the label as a colour bar on the left edge (#1509), but After Effects' exact UI there is unknown | Users of the current version expect them | 10–20 h left | — · [ui-parity.md](ui-parity.md) |
|
||||
| 22 | **Encoder efficiency** | HEVC / AV1 have no B-frames, multi-reference or SAO / CDEF; Opus no FEC / DTX; single-file intermediates are 8-bit uncompressed QuickTime only (#372 closed by #778: no 16-bpc, AVI or lossless-compressed codec); #325 render over existing file | Larger files than After Effects + Media Encoder | 20–40 h | G6 · [file-format-parity.md](file-format-parity.md) |
|
||||
| 23 | **Essential Graphics interchange** | Templates are `.ectemplate`, not `.mogrt`; #338 and #611 closed | Premiere editors can't use our templates | 15–30 h | — · [file-format-parity.md](file-format-parity.md) |
|
||||
| 24 | **Timeline depth requests** | #601 expand precomps in place, #602 reorderable render stack, #604 guide-only effects, #425 keyframe confusion (#355 and #428 closed) | Power-user speed | 15–30 h | G2 · [ui-parity.md](ui-parity.md) |
|
||||
@@ -114,10 +114,14 @@ The most important missing piece: it turns every other estimate here into a meas
|
||||
- Run the headless snapshot and control-channel checks on Linux (X11 and Wayland) and Windows, not
|
||||
only macOS. Interactions that only fail with real input (docking drags, viewer pan, drag-and-drop
|
||||
import, inline rename) need scripted input tests.
|
||||
- Path editing (#1220): selected shape and mask paths suppress the layer transform box while
|
||||
their points are edited. `ui_viewer` regressions cover Pen-family tools, Selection vertex
|
||||
dragging and undo, restoration of layer controls, and layers without visible paths.
|
||||
- Regression evidence for [#66](https://github.com/storytold/effectcraft/issues/66): scripted
|
||||
pointer tests in `ui_viewer` and `ui_text_edit`, checked headlessly on Linux, cover viewer clicks,
|
||||
marquee selection and text picking when layers are hidden or excluded by solo. They also check
|
||||
hidden/locked solo layers and solo layers outside their active time range.
|
||||
hidden/locked solo layers, and that solo still excludes the other layers while the soloed
|
||||
layer is outside its active time range (as in After Effects, so nothing is picked there).
|
||||
- Regression evidence for [#67](https://github.com/storytold/effectcraft/issues/67): scripted
|
||||
input tests in `ui_project_delete`, checked headlessly on Linux, cover Delete/Backspace on
|
||||
Project items, multiple selection and undo. They check that an unrelated Timeline layer stays,
|
||||
@@ -145,6 +149,15 @@ The most important missing piece: it turns every other estimate here into a meas
|
||||
tests, checked headlessly on Windows, drag a selected Gradient Fill's start and end points in
|
||||
the Composition viewer: one undo step, the layer stays, a point inside a moved and scaled group
|
||||
converts to the group's space, and the handles show only for a selected gradient.
|
||||
- Regression evidence for [#1011](https://github.com/storytold/effectcraft/issues/1011): the
|
||||
`ui_text_range_handles` input tests, checked headlessly on macOS, drag a text Range Selector's
|
||||
End bar in the Composition viewer: one undo step, Start and the layer stay, and on an animated
|
||||
End a keyframe at the current time. The bars show only for a selected selector or a selected
|
||||
animator (or a property in it), with the Selection tool and layer controls on. Unit tests in
|
||||
`text` place the bars on unit boundaries (words and Characters Excluding Spaces skip spaces,
|
||||
inside a ligature, on the second line, right to left) and invert them; `render` covers the
|
||||
value a drag writes (less the Offset, whole units in Index). Not done: dragging Offset, bars on
|
||||
vertical text and on text on a path.
|
||||
- Regression evidence for the toolbar's gradient swatches (#612, reported on Discord): the
|
||||
`ui_toolbar_gradient` input tests, checked headlessly on Windows, click a Linear Fill swatch and
|
||||
a Radial Stroke swatch on a selected shape layer and get the Gradient Editor on its Gradient Fill
|
||||
@@ -300,8 +313,8 @@ Issues #41–#47 (all from the Linux AppImage 0.1.1, 5 October): panning the vie
|
||||
|
||||
- Localisation of the interface ([localization-parity.md](localization-parity.md)): since 6–12
|
||||
October the menus are translated into Japanese, Simplified and Traditional Chinese, Ukrainian,
|
||||
Spanish, Brazilian Portuguese, Russian, French, German and Czech, and the panel catalog covers
|
||||
Japanese, both Chinese, Ukrainian, Brazilian Portuguese and Czech. Next: effect and parameter
|
||||
Spanish, Brazilian Portuguese, Russian, French, German, Czech and Georgian, and the panel catalog covers
|
||||
Japanese, both Chinese, Ukrainian, Brazilian Portuguese, Czech and Georgian. Next: effect and parameter
|
||||
names, the remaining panel strings (Spanish, Russian, French, German), Korean and Italian, and
|
||||
native-speaker review of all of them.
|
||||
- Accessibility (keyboard navigation, screen readers, contrast), user documentation and
|
||||
@@ -322,6 +335,8 @@ Issues #41–#47 (all from the Linux AppImage 0.1.1, 5 October): panning the vie
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-16 | minor | Gap 8: #932 closed (Convert To Bezier Path converts a shape layer's parametric path, not only mask RotoBezier) |
|
||||
| 2026-10-13 | patch | G2: layer bounds no longer compete with editable shape/mask path points (#1220) |
|
||||
| 2026-10-12 | minor | Audited against `upstream/main` 470ea7a3: open-issue count and crash list, closed issues removed from rows 1, 3, 5, 6, 8–12, 15–17, 22–24, G8 and G9 (#759 `#include`), localization catalogs recounted (≈ 12% to ≈ 18%); rows 7, 14 and 21 left to open PRs #1102, #792 and #1098 |
|
||||
| 2026-10-10 | minor | Alpha gate checked: no alpha blockers |
|
||||
| 2026-10-12 | minor | Gap 21: percentage guides and effect colour labels added (#1095) |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Localization parity
|
||||
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (catalogs recounted at `upstream/main` 470ea7a3: menus in 10 languages, panels in 6, Hangul probe (#599)) · **Target:** Adobe After Effects 2026
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-23 · **Change:** minor (Georgian: menus, panels and effect names; missing strings show in English; `missing_translations_report` lists them) · **Target:** Adobe After Effects 2026
|
||||
|
||||
How much of EffectCraft's interface is translated, language by language, compared with After
|
||||
Effects. [gaps.md](gaps.md) gap 16 and workstream G9 hold the work;
|
||||
@@ -17,16 +17,17 @@ interface; Arabic and Hebrew text *in layers* is supported through its Middle Ea
|
||||
## How EffectCraft translates
|
||||
|
||||
- **Menus:** `crates/ui-egui/src/i18n.rs`, one catalog per language keyed by
|
||||
`(command, English label)`. A test (`every_translation_key_exists_in_the_actual_menu_tree`)
|
||||
fails if any fixed menu entry is untranslated or a row is stale, so every catalog covers all
|
||||
fixed entries (624 catalog rows each at 470ea7a3, counted). The generated Effect menu is not part of that fixed tree: its entries
|
||||
`(command, English label)` (624 catalog rows each at 470ea7a3, counted). A test
|
||||
(`menu_catalogs_have_no_stale_or_duplicate_rows`) fails on a row whose entry left the menu tree
|
||||
or on a repeated row. The generated Effect menu is not part of that fixed tree: its entries
|
||||
and category submenus take their labels from the effect catalog below.
|
||||
- **Panels, dialogs, buttons, tooltips:** `crates/ui-egui/src/i18n/ui.rs`, keyed by the English
|
||||
source string, with English fallback. Catalogs exist for Japanese (410 rows), Simplified and
|
||||
Traditional Chinese (506 each), Ukrainian (505), Brazilian Portuguese (400) and Czech (501);
|
||||
counted at 470ea7a3.
|
||||
counted at 470ea7a3. Georgian has every row the Chinese catalogs have at this revision (695, as
|
||||
both Chinese catalogs now have), plus 191 for panels converted to the catalog.
|
||||
- **Effect names:** `crates/ui-egui/src/i18n/effects.rs`, keyed by **effect id**, in Simplified
|
||||
Chinese (306 rows, one per effect in the registry, plus its 21 categories). The registry's
|
||||
Chinese and Georgian (307 rows, one per effect in the registry, plus its 21 categories). The registry's
|
||||
English name stays the effect's identity: `effect.list`, `effect.apply {"effect": …}`, scripts
|
||||
and expressions match on it, and the property tree stores it as the effect group's name. Only
|
||||
the drawing — the Effect menu, Effect Controls, the Timeline and Effects & Presets — goes
|
||||
@@ -38,6 +39,12 @@ interface; Arabic and Hebrew text *in layers* is supported through its Middle Ea
|
||||
slot is for). Each language decides for itself which names stay English — Adobe's Japanese
|
||||
interface leaves the `CC`, `VR` and `OCIO` sets in English while its Chinese one translates
|
||||
them — so a row repeating the English name is a choice, not a missing row.
|
||||
- **Missing strings:** a string with no row in the interface language shows in English, and no
|
||||
test, build or release fails on a missing translation. The `missing_translations_report` test
|
||||
(`crates/ui-egui/src/i18n.rs`) prints, per language, the fixed menu entries, panel strings, effect
|
||||
names and effect categories that have no row. It always passes; `cargo xtask ci` runs it with
|
||||
`--nocapture`, so the list is in the gate log. Locally:
|
||||
`cargo test -p effectcraft-ui-egui --lib missing_translations_report -- --nocapture`.
|
||||
- **Not translated anywhere yet:** effect *parameter* names (≈ 1,650, `crates/effects`),
|
||||
engine-generated text (property names, errors, undo labels), the Home screen's unwrapped
|
||||
literals (#574), Learn tutorials and documentation.
|
||||
@@ -76,13 +83,14 @@ the true percentages are slightly lower.
|
||||
| Korean | `ko` | 0 | — | Hangul fallback probe added (#599, closed); not checked on macOS here | no | none | 9–13 h + review |
|
||||
| Vietnamese | `vi` | 0 | — | Latin with stacked diacritics; untested | no | none (After Effects: none) | 8–12 h + review |
|
||||
|
||||
Other languages EffectCraft ships (3):
|
||||
Other languages EffectCraft ships (4):
|
||||
|
||||
| Language | Code | UI strings translated | Status | To `full` |
|
||||
|---|---|---|---|---|
|
||||
| Traditional Chinese | `zh-hant` | 1,130 (≈ 29%): menus 624 rows, panels 506 | partial | 6–9 h + review |
|
||||
| Ukrainian | `uk` | 1,129 (≈ 29%): menus 624 rows, panels 505 | partial | 6–9 h + review |
|
||||
| Czech | `cs` | 1,125 (≈ 29%): menus 624 rows, panels 501 | Existing catalog entries covered; unwrapped UI, effects and engine text still in English; AI-assisted, not yet native-speaker proofread | Remaining strings + independent native review |
|
||||
| Georgian | `ka` | 1,514 (≈ 39%): menus 628 rows, panels 886 (the Chinese catalogs' 695 plus 191 Georgian-only rows: form dialogs, context menus, column headers), effect names 307 + 21 categories; native-speaker reviewed | partial | 5–8 h |
|
||||
|
||||
Russian (`ru`) has a menu catalog (624 rows, ≈ 16%); After Effects also ships Italian, which EffectCraft does not (8–12 h).
|
||||
|
||||
@@ -97,13 +105,14 @@ needs ≈ 2,900 more strings (≈ 5–9 h per language at the pace of #398 and #
|
||||
Effects' 10 non-English languages: **≈ 60–110 h**; the four extra languages of the standard list
|
||||
(Hindi, Arabic, Indonesian, Vietnamese) add ≈ 50–80 h, most of it right-to-left layout and
|
||||
complex shaping in the interface. Every language needs native-speaker review, which only a human
|
||||
can give; none has had it.
|
||||
can give; only Georgian, contributed by a native speaker (#1843), has had it.
|
||||
|
||||
## Revision history
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-12 | minor | Recounted: Russian, French, German and Czech menus (#873, #987), Brazilian Portuguese and Czech panels (#830, #987), Hangul probe (#599); overall ≈ 12% to ≈ 18%; Korean and Italian still absent |
|
||||
| 2026-10-14 | minor | Georgian (`ka`): menus 628, panels 886 (191 Georgian-only, in `GEORGIAN_PANELS`), effect names and categories in `effects.rs` |
|
||||
| 2026-10-10 | major | First localization parity document: catalog rows counted for all six languages, the twelve-language table, After Effects' 11 shipped languages from its bundle |
|
||||
| 2026-10-11 | minor | Ukrainian panel catalog brought level with the Chinese ones (495 of 496 rows, counted at this commit); its share rises to ≈ 29%. The other languages' panel counts are the 10 October measurement |
|
||||
| 2026-10-13 | minor | Effect names and the generated Effect menu leave English: `i18n/effects.rs` adds 306 rows keyed by effect id (Simplified Chinese, from After Effects 2026's interface and Adobe's Chinese documentation for the `CC` series) and the 21 category names. Parameter names are still English; no other language has rows |
|
||||
|
||||
+13
-6
@@ -103,17 +103,24 @@ name in your Scripts folders takes their place.
|
||||
|
||||
### Ease Presets (`extensions/scriptui-panels/Ease Presets.jsx`)
|
||||
|
||||
Window ▸ Ease Presets.jsx: easing curves kept by name and applied to pairs of neighbouring
|
||||
selected keyframes (#254; it was a core panel in v0.6.0). A curve is the out side of a segment's
|
||||
Window ▸ Ease Presets.jsx: easing curves kept by name and applied to the sides of selected
|
||||
keyframes (#254; it was a core panel in v0.6.0). A curve is the out side of a segment's
|
||||
first key and the in side of its second, in the Keyframe Velocity dialog's terms: influence in
|
||||
percent, and speed relative to the segment's average speed (1 = as fast as a straight line, 0 = at
|
||||
rest), so one curve fits any duration and change of value.
|
||||
|
||||
* Click a preset to apply it to every pair of neighbouring selected keyframes of each selected
|
||||
property, in one undo step (per dimension, and along the motion path for spatial properties;
|
||||
the keys' other sides keep playing as they did). The keys stay selected, so you can try another.
|
||||
* Click a preset to apply it to the selected keyframes of each selected property, in one undo
|
||||
step. **Ease** chooses the sides (#1329): **In** eases each key's incoming side with the curve's
|
||||
in side, **Out** its outgoing side with the curve's out side, **In-Out** (the default) both. A
|
||||
side is eased when it has a neighbouring keyframe, selected or not (the neighbour doesn't
|
||||
change), with its speeds scaled to the segment on that side: per dimension, and along the
|
||||
motion path for spatial properties. So a single keyframe works, a selected pair gets the whole
|
||||
curve, and the first and last keys ease the side that has a segment. The side not eased keeps
|
||||
playing as it did (a Hold stays a Hold). Roving keyframes are skipped, and the status says how
|
||||
many: they pass speed straight through, so an ease on one wouldn't play (turn off Roving to
|
||||
ease it). The keys stay selected, so you can try another.
|
||||
* The drawing shows the working curve; type its numbers (Out % and Speed, In % and Speed) and
|
||||
click Apply, or click From Keys to read the curve between the first selected pair.
|
||||
click Apply, or click From Keys to read the curve between the first selected pair of neighbouring keys.
|
||||
* Save keeps the working curve under the typed name (replacing a preset of that name); Rename
|
||||
and Delete act on the selected preset of your own. The twelve built-in presets can't change.
|
||||
* User presets are kept with `app.settings` (section `Ease Presets`, key `userPresets`, in
|
||||
|
||||
+3
-2
@@ -30,7 +30,7 @@ Commands (CLI, MCP, control channel):
|
||||
|
||||
### Settings that change behaviour
|
||||
|
||||
- `general.language`: interface language (`system` / `en` / `ja` / `zh-hans` / `zh-hant` / `uk` / `es` / `pt-br`), Settings ▸ General ▸ Language; menu labels change immediately. `system` (Match System, the default) follows the operating system's interface language where EffectCraft has a translation and is English otherwise: a Simplified Chinese locale (`zh`, `zh-CN`, `zh_CN.UTF-8`, `zh-Hans-CN`, `zh-SG`) selects `zh-hans`, a Traditional one (`zh-TW`, `zh-HK`, `zh-Hant`, `zh_MO`) selects `zh-hant`, a Ukrainian one (`uk`, `uk-UA`, `uk_UA.UTF-8`) selects `uk`, a Spanish one (`es`, `es-ES`, `es_MX.UTF-8`) selects `es`, a Portuguese one (`pt`, `pt-BR`, `pt_PT.UTF-8`) selects `pt-br`; the browser build stays in English with it. Choose **Español** or **Português (Brasil)** for Spanish or Brazilian Portuguese menus (panels stay in English for now); choose **Українська** for Ukrainian menus and cataloged panel, dialog, button and tooltip text; Ukrainian uses the bundled UI fonts. Native Japanese, Simplified and Traditional Chinese UI uses installed system fonts; the web host must supply a Japanese font. The same setting picks the CJK fallback for the interface *and* the Han preference of the text engine, so the composition's own text is drawn with the same language's glyph forms (the text engine's preference is process-global); swapping the language reinstalls the fonts, so it applies without a restart. Two catalogs back the text: the menus are keyed by `(command, English label)` (`crates/ui-egui/src/i18n.rs`), and the rest of the interface - panels, dialogs, buttons, tooltips - is keyed by its English source string (`crates/ui-egui/src/i18n/ui.rs`, with rows for Japanese, Simplified and Traditional Chinese and Ukrainian), where a panel writes `tr("Rename")`, or `tr_args("Proxy Use: {}", &[&quality])` when the row carries a value. A row that is missing falls back to English, so a new string shows up untranslated rather than empty; the tests in that module fail on an unused row and on a hard-coded string in a converted panel.
|
||||
- `general.language`: interface language (`system` / `en` / `ja` / `zh-hans` / `zh-hant` / `uk` / `es` / `pt-br` / `ka`), Settings ▸ General ▸ Language; menu labels change immediately. `system` (Match System, the default) follows the operating system's interface language where EffectCraft has a translation and is English otherwise: a Simplified Chinese locale (`zh`, `zh-CN`, `zh_CN.UTF-8`, `zh-Hans-CN`, `zh-SG`) selects `zh-hans`, a Traditional one (`zh-TW`, `zh-HK`, `zh-Hant`, `zh_MO`) selects `zh-hant`, a Ukrainian one (`uk`, `uk-UA`, `uk_UA.UTF-8`) selects `uk`, a Spanish one (`es`, `es-ES`, `es_MX.UTF-8`) selects `es`, a Portuguese one (`pt`, `pt-BR`, `pt_PT.UTF-8`) selects `pt-br`, a Georgian one (`ka`, `ka-GE`, `ka_GE.UTF-8`) selects `ka`; the browser build follows `?lang=` in the address, then the browser's languages (`navigator.languages`). Choose **Español** or **Português (Brasil)** for Spanish or Brazilian Portuguese menus (panels stay in English for now); choose **Українська** for Ukrainian menus and cataloged panel, dialog, button and tooltip text; Ukrainian uses the bundled UI fonts. Native Japanese, Simplified and Traditional Chinese UI uses installed system fonts; the web host must supply a Japanese font. The same setting picks the CJK fallback for the interface *and* the Han preference of the text engine, so the composition's own text is drawn with the same language's glyph forms (the text engine's preference is process-global); swapping the language reinstalls the fonts, so it applies without a restart. Two catalogs back the text: the menus are keyed by `(command, English label)` (`crates/ui-egui/src/i18n.rs`), and the rest of the interface - panels, dialogs, buttons, tooltips - is keyed by its English source string (`crates/ui-egui/src/i18n/ui.rs`, with rows for Japanese, Simplified and Traditional Chinese and Ukrainian), where a panel writes `tr("Rename")`, or `tr_args("Proxy Use: {}", &[&quality])` when the row carries a value. A row that is missing falls back to English, so a new string shows up untranslated rather than empty; the `missing_translations_report` test lists what each language lacks, and the tests in `ui.rs` fail on an unused row.
|
||||
- `general.undoLevels`: Levels of Undo
|
||||
- `general.pathPointSize`: Path Point and Handle Size
|
||||
- `general.recentItems`: Recent Projects Shown
|
||||
@@ -91,7 +91,8 @@ Commands (CLI, MCP, control channel):
|
||||
one and in the comps that nest it (through the precomp layers' timing)
|
||||
- `general.expressionPickWhipCompact`: off, the pick whip writes match names
|
||||
(`thisComp.layer("A")("transform")("opacity")`)
|
||||
- `general.createSplitLayersAbove`: where Edit ▸ Split Layer puts the new layer
|
||||
- `general.createSplitLayersAbove`: where Edit ▸ Split Layer puts the new layer (on by default,
|
||||
as in After Effects)
|
||||
- `startup.showHomeOnOpenProject`: the Home screen comes up after File ▸ Open / Open Recent
|
||||
- `composition.motionPath`, `composition.motionPathSeconds`, `composition.motionPathKeyframes`:
|
||||
how much of a position motion path the viewer draws (all, none, N seconds or N keyframes
|
||||
|
||||
@@ -15,7 +15,7 @@ our own scripts; no real-world script library has been run against both apps.
|
||||
| Feature | After Effects 2026 | EffectCraft | Status | Evidence / gap |
|
||||
|---|---|---|---|---|
|
||||
| Expression language | JavaScript engine (default) and Legacy ExtendScript | JavaScript (boa) with legacy array maths (`value + [10, 0]`) | good | `crates/expr` tests; no separate Legacy ExtendScript engine mode |
|
||||
| Expression object model | `thisComp`, `thisLayer`, `wiggle`, `loopOut`, `valueAtTime`, `sampleImage`, `footage()`, text style API… | Same members, from the reference | good | `crates/expr/src/tests.rs`; behaviour of edge cases unmeasured (G1) |
|
||||
| Expression object model | `thisComp`, `thisLayer`, `wiggle`, `loopOut`, `valueAtTime`, `sampleImage`, `footage()`, text style API… | Same members, from the reference, including `propertyGroup(countUp)` and a group's `propertyIndex` for name-independent rigs (#1619) | good | `crates/expr/src/tests.rs`, `tests/property_group.rs`; behaviour of edge cases unmeasured (G1) |
|
||||
| Expression editing | Inline field, error bar, pick whip, Expression Language menu, external editors | Inline field that grows, error bar, pick whips (property and value), language menu | partial | External editor support (#364) |
|
||||
| Expression performance | Cached, multi-frame | Per-thread boa contexts | unknown | No benchmark |
|
||||
| Scripting object model | `app`, `Project`, items, layers, properties, keys, eases, render queue, `TextDocument`, `Shape`, markers, `File`/`Folder`, `Socket`, `app.settings` | Same classes, edits run through undoable engine commands; match names table; layers, groups and properties are callable (`layer("Effects")(1)` is `.property(...)`) | good | `crates/script` tests (≈ 10 test files) |
|
||||
@@ -42,5 +42,6 @@ performance benchmark 4–8 h.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-16 | minor | Expression object model row names `propertyGroup()` and a group's `propertyIndex` (#1619) |
|
||||
| 2026-10-12 | minor | Estimate no longer counts `#include` / `#target` (#759, `tests_include.rs`); the table row was already current |
|
||||
| 2026-10-10 | major | First expressions and scripting checklist |
|
||||
|
||||
@@ -426,7 +426,7 @@ the disk cache in the formats wave; the 3D Camera Tracker in M12.6.)
|
||||
| Layers | 88% | 4.8 | motion blur of collapsed precomps (their own motion and the parent camera's) and of animated layer content (lock-aware layer commands, delete keeping children in place, step-by-step Arrange, Transform keeping depth and separated dimensions and links kept on duplicate / paste landed in M3.11; frame blending, collapse transformations and slip edit in M4.3–M4.4) |
|
||||
| Output | 97% | 0.8 | Render Settings complete (field render + 3:2 pulldown, effects/solo/guide/depth/blending/blur overrides, time sampling, storage overflow), Output Module crop/ROI/resize, alpha modes, post-render actions, PCM formats, templates with defaults, render logs, Notify (M10.2); WebM VP9 key + inter frames with motion search, loop filter and rate control; M13.4: HEVC export (MP4 `hvc1`, `effectcraft-hevcenc`: Main / Main 10, IDR + P slices, quarter-pel motion, deblocking, bit-exact with ffmpeg) and AV1 export (MP4 `av01` and WebM, `effectcraft-av1enc`: 8/10-bit key + inter frames, quarter-pel motion, loop filter, bit-exact with libdav1d) with profile, level, bitrate / constant-quality and key-frame interval options; Opus SILK (NB/MB/WB, mid/side stereo) and hybrid (SWB/FB) modes chosen by bitrate and Audio/Voice tuning, with the Opus bitrate in the Output Module. HEVC/AV1 import decodes through FilmCraft (MP4/MOV/MKV/WebM); WebM VP9 alpha (BlockAdditions) imports as straight alpha. Left: HEVC/AV1 have no B-frames, multi-reference, SAO/CDEF/restoration or alpha (compression below mature encoders); Opus has no FEC/DTX and only 20 ms frames; Photoshop sequence output, overflow for movies only checks at file creation |
|
||||
| Audio | 85% | 0.5 | audio to keyframes |
|
||||
| Import | 97% | 0.5 | JPX / JBIG2 images and predefined non-Identity CJK CMaps inside PDF/AI files, Illustrator EPS relying on Adobe procsets, PSD 3D layers (M13.12: mesh shadings — free-form / lattice Gouraud triangles, Coons and tensor patches — and function-based shadings, CCITT G3 / G4 images, knockout / isolated transparency groups with group alpha and soft masks on the group's result, `/W2` vertical metrics, EPS text with embedded Type 1 / CFF or bundled fonts, and Create Shapes from Vector Layer keeping images as parented footage layers in paint order; M13.6: PDF/AI text with embedded TrueType / CFF / Type 1 / Type 3 fonts and standard-14 fallback to the bundled fonts, Flate / DCT / inline / stencil images with soft masks, Indexed and ICC alternates, luminosity / alpha soft masks, the 16 blend modes, tiling patterns, calculator functions, any page via `file.import page` and the Import dialog, clip groups kept by Create Shapes from Vector Layer as layer masks / Merge Paths; PSD smart-object perspective quads and placed-layer warps — named styles and custom quilt meshes — baked as placed; PDF / PDF-compatible AI / EPS vector footage with Continuously Rasterize, layered composition import and Create Shapes from Vector Layer, and PSD smart objects with embedded files landed in M13.2; PSD as footage/composition/retain layer sizes, SVG footage earlier) |
|
||||
| Import | 97% | 0.5 | JPX / JBIG2 images and predefined non-Identity CJK CMaps inside PDF/AI files, Illustrator EPS relying on Adobe procsets, PSD 3D layers (M13.12: mesh shadings — free-form / lattice Gouraud triangles, Coons and tensor patches — and function-based shadings, CCITT G3 / G4 images, knockout / isolated transparency groups with group alpha and soft masks on the group's result, `/W2` vertical metrics, EPS text with embedded Type 1 / CFF or bundled fonts, and Create Shapes from Vector Layer keeping images as parented footage layers in paint order; M13.6: PDF/AI text with embedded TrueType / CFF / Type 1 / Type 3 fonts and standard-14 fallback to the bundled fonts, Flate / DCT / inline / stencil images with soft masks, Indexed and ICC alternates, luminosity / alpha soft masks, the 16 blend modes, tiling patterns, calculator functions, any page via `file.import page` and the Import dialog, clip groups kept by Create Shapes from Vector Layer as layer masks / Merge Paths; PSD smart-object perspective quads and placed-layer warps — named styles and custom quilt meshes — baked as placed; PDF / PDF-compatible AI / EPS vector footage with Continuously Rasterize, layered composition import and Create Shapes from Vector Layer, and PSD smart objects with embedded files landed in M13.2; PSD as footage/composition/retain layer sizes, SVG footage earlier; PDF / AI Composition - Retain Layer Sizes crops each layer to its artwork in place, #1536) |
|
||||
| Automation | ≈ 99% | 0.1 | `.jsxbin` (AUT-2: concave / self-intersecting `onDraw` fills, real images in `drawImage`, image controls and icon buttons landed in M13.11; ScriptUI resource strings, `onDraw`/ScriptUIGraphics, live `onChanging`, `Socket`, Essential Graphics hooks — `addToMotionGraphicsTemplate(As)`, `canAddToMotionGraphicsTemplate`, `exportAsMotionGraphicsTemplate`, `motionGraphicsTemplateName`, controller count/names — and Watch Folder landed in M13.7; the core object model landed in M14.4; ScriptUI windows/dialogs/dockable panels with `scriptui.*` agent commands, File ▸ Scripts install + sample scripts, and the effect plug-in API (EFF-6, WebAssembly) landed in M13.1) |
|
||||
| Shapes | ≈ 80% | 1.5 | Lottie can't carry stroke taper/wave (stroke Taper and Wave, Dash 2/Gap 2/Dash 3/Gap 3 and radial-gradient Highlight Length/Angle landed in M13.5; pen tool for shape paths and vertex editing in M6.5) |
|
||||
| Compositions | ≈ 81% | 3.3 | Mocha-style planar tracks for templates; nested comp markers on the precomp layer bar (Preserve frame rate / resolution when nested and nested comps showing nothing past their end landed in M4.9–M4.10; Pre-compose keeping the comp's settings and New Comp from Selection's dialog in M3.12; CMP-7: Essential Graphics mirrored and linked properties landed in M13.11; Font and uniform Scale controls, Composition ▸ Open in Essential Graphics, Save Frame As ▸ Photoshop Layers / ProEXR and the VR Comp Editor landed in M13.7; the marker dialog, Composition Flowchart, Essential Graphics with master properties, `.ectemplate` templates and Responsive Design — Time landed: CMP-6, CMP-7) |
|
||||
@@ -688,8 +688,10 @@ Fixes from measured reports:
|
||||
step (undo and redo keep it) and keeps the comp's cached frames; projects saved before open
|
||||
with Auto. View ▸ Resolution and the panel's popup run `view.res.full|half|third|quarter|auto`
|
||||
and `view.res.custom {factor}`, which now work headless too; `comp.info` reports `resolution`
|
||||
and `resolutionFactor` (#417). Not yet: the magnification is not saved per comp, and the
|
||||
Composition Settings dialog has no Resolution popup.
|
||||
and `resolutionFactor` (#417). Composition Settings ▸ Basic has the same Resolution menu, with
|
||||
the frame size and memory per 8bpc frame at that resolution beside it; OK sets it as the popup
|
||||
does, outside the undo step of the other settings (#2167). Not yet: the magnification is not
|
||||
saved per comp.
|
||||
|
||||
### Update: plug-ins and extensions as in After Effects; ease presets become a ScriptUI panel
|
||||
|
||||
@@ -746,6 +748,9 @@ them: scripts, ScriptUI panels, effect plug-ins, with a guide to writing a panel
|
||||
way and move them however slowly they are dragged; the same Shift / Ctrl / Ctrl+Alt clicks.
|
||||
- **Paste** (M5.13): keys copied from several layers paste onto as many layers in order; Paste
|
||||
Reversed Keyframes follows Paste's rules (stretch, target property, Hold-only properties).
|
||||
A Mask Path or shape Path (its value or a key) pasted onto Position becomes a motion path: a
|
||||
key per vertex with the path's tangents, over 2 s from the CTI, the inner keys roving, the
|
||||
vertices pasted as they are (layer space, as in After Effects) (#1425).
|
||||
- **J / K and Info** (M5.14): J / K also stop at the work area; the Info panel shows a selected
|
||||
key's property, time and value.
|
||||
|
||||
@@ -1065,7 +1070,7 @@ resolution, adjustment) and composited at 8 and 32 bpc:
|
||||
Bucket's flood-filled region, Eyedropper Fill's sample); segments and convex polygons are
|
||||
binned into 16 × 16 tiles and rasterised on the GPU with the CPU's coverage functions, glows
|
||||
and compositing included. The CPU effects were refactored to draw from the same plans with
|
||||
bit-identical output (`crates/effects/tests/gen_golden.rs`: 288 frame hashes pinned before
|
||||
bit-identical output (`crates/effects/tests/it/gen_golden.rs`: 288 frame hashes pinned before
|
||||
the refactor). Exception: CC Threads decides band membership and fibre shade per pixel, and at
|
||||
round parameter values (the defaults) pixel centres sit exactly on band edges, where f32 and
|
||||
the CPU's f64 can fall on either side: up to 5 % of pixels allowed (measured ≤ 3.6 % at half
|
||||
@@ -1079,7 +1084,7 @@ resolution, adjustment) and composited at 8 and 32 bpc:
|
||||
7 more effects run on the GPU (280 in all, with M13.28's 37), and five CPU fallbacks are gone. Each case is
|
||||
checked against the CPU oracle (≤ 1/255 at 8 bpc, ≤ 1e-3 at 32 bpc) directly on a buffer (full
|
||||
and half resolution) and composited at 8 and 32 bpc; the CPU refactors behind the shared plans
|
||||
are pinned bit for bit by golden hashes (`crates/effects/tests/particle_golden.rs`,
|
||||
are pinned bit for bit by golden hashes (`crates/effects/tests/it/particle_golden.rs`,
|
||||
`timewarp_golden.rs`, `sim_golden.rs`).
|
||||
|
||||
- **Particle render passes** (`gpu::fx_particles`): CC Particle World and CC Particle Systems II
|
||||
|
||||
+25
-1
@@ -5,6 +5,15 @@ cargo test --workspace # everything
|
||||
cargo xtask ci # fmt, clippy -D warnings, tests, layers, assets, wasm
|
||||
```
|
||||
|
||||
`cargo xtask ci` builds in the dev profile and runs the tests with `cargo nextest run` (plus
|
||||
`cargo test --doc`) when `cargo-nextest` is installed, and with `cargo test` otherwise. It then
|
||||
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.
|
||||
|
||||
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>::/)'`.
|
||||
|
||||
## What the tests cover
|
||||
|
||||
- **Engine commands** (`crates/engine/src/tests*.rs`): every command family, including undo and
|
||||
@@ -16,7 +25,7 @@ cargo xtask ci # fmt, clippy -D warnings, tests, layers, assets, was
|
||||
- **Effects** (`crates/effects`): each effect has a determinism test and at least one behaviour
|
||||
test (identity at neutral settings, known pixel results, or simulations giving the same frame
|
||||
whether you seek straight to it or play up to it). A registry-wide test checks that effects
|
||||
which read the clock are declared time-dependent. `crates/effects/tests/sim_golden.rs` pins
|
||||
which read the clock are declared time-dependent. `crates/effects/tests/it/sim_golden.rs` pins
|
||||
exact pixel hashes of the simulation effects the GPU shares plans with (CC Rainfall … Card
|
||||
Wipe) at several settings, times, bit depths and resolutions, so any change to their CPU output
|
||||
fails (re-pin with `SIM_GOLDEN_PRINT=1` after an intended change; pinned for aarch64 macOS,
|
||||
@@ -68,5 +77,20 @@ instructions retired; `/usr/bin/time -v` on Linux gives max RSS and CPU; elsewhe
|
||||
It prints a table and appends one JSON line per run to `target/bench/results.jsonl` (git rev,
|
||||
date, load average, scenario, settings, metrics); `--compare OLD.jsonl` prints the % change against
|
||||
the latest matching runs there, `--compare OLD --with NEW` compares two files without rendering.
|
||||
`--repeat N` runs every scenario N times; with N > 1, one `"kind": "summary"` line per key follows
|
||||
the runs. Its `metrics` are the medians of the successful runs (byte and instruction counts
|
||||
rounded), `summary` holds `runs`, `failed` and median/p90/min/max per metric (p90 is the nearest
|
||||
rank, so it equals the max for N < 10), and its other fields (`date`, `load`, ...) are those of the
|
||||
key's last run; scripts that average runs should skip `kind: summary` lines. One failed run marks
|
||||
the summary failed. Comparisons use the latest line per key, so a key is a median only if its
|
||||
summary is the latest line (a later single run, or a batch that aborted before its summaries,
|
||||
leaves a raw run latest). The table labels each side `median of N` or `1 run` and warns when they
|
||||
differ; `--fail-over` refuses such keys.
|
||||
The table notes the 1-minute load average: on a busy machine wall time is noise, so compare
|
||||
peak memory and instructions retired, and rerun when the load differs a lot.
|
||||
|
||||
Results files allow blank lines; corrupt records or invalid comparison fields report the file
|
||||
and line instead of being discarded. `--fail-over PCT` requires the same run keys on both sides,
|
||||
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.
|
||||
|
||||
+12
-4
@@ -1,6 +1,6 @@
|
||||
# UI parity
|
||||
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-12 · **Change:** minor (closed issue references re-audited against `upstream/main` 470ea7a3) · **Target:** Adobe After Effects 2026
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-18 · **Change:** minor (shape group Contents row elided; path-editing layer bounds #1220; closed issue references re-audited against `upstream/main` 470ea7a3) · **Target:** Adobe After Effects 2026
|
||||
|
||||
How closely EffectCraft's interaction matches After Effects: tools, handles, snapping, nudging,
|
||||
modifier keys, shortcuts, drag behaviour, numeric entry, panels, context menus and feel. Evidence
|
||||
@@ -17,13 +17,13 @@ feel and platform differences, and the 26.x interface changes are not in yet.
|
||||
|---|---|---:|---|
|
||||
| Menu bar | After Effects' menus, order, submenus and labels; native on macOS; keyboard-operable in-window bar | 95% | 587 entries in `crates/engine/src/menus.rs`; Adobe-service entries omitted by design; `ui_menus` tests (#279) |
|
||||
| Panels and workspaces | Every After Effects panel; all standard workspaces; drag-to-dock, tabs, floating panels, maximise | 75% | `ui_tabs`, `ui_panels`; panels move into their own native windows on any screen (#486 open: no dragging tabs between those windows); #302, #301 and #320 (docking, stacked-panel headers, workspace persistence) are closed |
|
||||
| Timeline | Switches, modes, parent & link, twirl-downs, reveal shortcuts (U, UU, P, S, R, T, A, E, M, F, Alt+Shift…), pick whips, graph editor, work area, markers | 75% | `ui_keyframes`, `ui_timeline_*`; expand precomps in place (#601); #456, #457, #569 and #355 (Graph Editor jitter and buttons, negative rotation, bulk keyframing) are closed; Proportional Scrubbing (26.2: Ctrl+Alt / Cmd+Opt scrub of hot text spreads the change over the selected layers by selection order, #770) matched for layers selected as a whole, not yet for individually selected properties |
|
||||
| Timeline | Switches, modes, parent & link, twirl-downs, reveal shortcuts (U, UU, P, S, R, T, A, E, M, F, Alt+Shift…), pick whips, graph editor, work area, markers | 75% | `ui_keyframes`, `ui_timeline_*`; expand precomps in place (#601); #456, #457, #569 and #355 (Graph Editor jitter and buttons, negative rotation, bulk keyframing) are closed; Proportional Scrubbing (26.2: Ctrl+Alt / Cmd+Opt scrub of hot text spreads the change over the selected layers by selection order, #770) matched for layers selected as a whole, not yet for individually selected properties; every layer has the Video switch, nulls, cameras and lights included (a hidden null, camera or light has no box or handles in the viewer and isn't picked there; other selected hidden layers keep their layer controls and paths, #1769); a row control (a switch, the label swatch, a Modes cell) clicked on one of several selected layers acts on the whole selection, with the layers the switch doesn't apply to left out (#1849); a shape group's inner Contents (After Effects' elided Vectors group) draws no row — the group's path, fill/stroke and Transform list directly under it |
|
||||
| Composition viewer | Selection, handles with direction-aware cursors, rulers, guides, grids, snapping, channels, exposure, region of interest, snapshots, 3D views, Spacebar hand | 70% | `ui_viewer`, `ui_viewers`; Cmd/Ctrl +/− doesn't zoom (#570); anchor point grab on Linux (#492); #352 (snapping) is closed; no 3D transform gizmos (#392, #347); percentage guides (26.5) exist as View ▸ Add Percentage Guide and Convert Guides to Percentage / Pixels (a guide stores a percent of the comp, so it follows comp-size changes; behaviour inferred from the release note, not compared with After Effects); Copy Frame to Clipboard (26.3) is in the Composition menu and the viewer's context menu |
|
||||
| Tools panel | All After Effects tools; long-press flyouts; Tool Creates Shape / Mask; Fill / Stroke options | 80% | #213, #227 fixed; #553 (pen and vertex tools on motion paths) is closed |
|
||||
| Nudging and modifiers | Arrow nudge (1 px, Shift 10 px), Alt/Option duplicates and slips, Shift constrains | 75% | #290 nudging fixed; #491 (Linux multi-layer edits) is closed |
|
||||
| Numeric entry | Scrubbable values, typed entry with Tab / Shift+Tab, angle revolutions, Escape cancels, non-finite rejected | 85% | `ui_angles`, `ui_inline_edit` (#93, #141, #361); rename arrows on Linux (#488) |
|
||||
| Effect Controls | After Effects' twirl-downs, angle dial, point crosshair, eyedropper, curves/levels editors, effect menu on right-click | 70% | `ui_effect_controls`, `ui_fx_editors`; 26.5 rebuilt this panel with icon controls and effect colour labels: labels exist (a colour bar on the effect header, right-click Label menu, `effect.setLabel`; not in the Timeline), the rebuilt controls are not matched |
|
||||
| Context menus | Layer, property, Project, Timeline empty area, work area, labels with colours | 80% | `ui_blank_context_menus`, `ui_project_rows`; #227, #290, #407 fixed; a comp's Project row menu has Composition Settings… (#1058) |
|
||||
| Effect Controls | After Effects' twirl-downs, angle dial, point crosshair, eyedropper, curves/levels editors, effect menu on right-click | 70% | `ui_effect_controls`, `ui_fx_editors`; 26.5 rebuilt this panel with icon controls and effect colour labels: labels exist (a colour bar on the effect header and on the effect's Timeline row, right-click Label menu, `effect.setLabel`), the rebuilt controls are not matched |
|
||||
| Context menus | Layer, property, Project, Timeline empty area, work area, labels with colours | 80% | `ui_blank_context_menus`, `ui_project_rows`; #227, #290, #407 fixed; a comp's Project row menu has Composition Settings… (#1058); layer context menu has Layer Styles (#1611) |
|
||||
| Keyboard shortcuts | After Effects' defaults; Keyboard Shortcuts editor that rebinds and saves sets | 75% | Shortcut editor in Settings; #467 (adding shortcuts on Linux), #428 and #336 are closed; no shortcuts for scripts |
|
||||
| Drag and drop | Project → Timeline / viewer / New Comp / Render Queue; effects onto layers; files from the OS | 65% | `ui_drag_drop`; OS drops fail on Wayland (winit) and some Linux setups (#268); #552, #351 (multi-file drops) and #511 (crash dragging over the import dialog) are closed |
|
||||
| Text editing on canvas | Point and paragraph text, per-character styles, Character / Paragraph panels, OpenType and variable axes | 70% | `ui_text_edit`, `ui_font_search`; paragraph alignment inverted (#316); toolbar font options (#550); font search by typing (#583) is closed; the font menus' variable-font filter (26.3) landed (#741) |
|
||||
@@ -33,6 +33,12 @@ feel and platform differences, and the 26.x interface changes are not in yet.
|
||||
| Accessibility | AccessKit enabled; focus labels tested; Project panel keyboard navigation and accessible names (#776) | 20% | No screen-reader pass or keyboard-navigation audit of the other panels; no contrast check |
|
||||
| UI scale | Settings ▸ Appearance ▸ UI Scale 75–200% | beyond | After Effects follows the system's scaling (#284) |
|
||||
|
||||
Path editing (#1220): the Composition viewer suppresses a selected layer’s transform box while
|
||||
using a Pen-family tool on a visible path, or while selecting its vertices with the Selection
|
||||
tool. Path points, Bézier handles and the path’s free-transform controls remain available.
|
||||
Layer transform tools and ordinary layer selection restore the box. Headless `ui_viewer` tests
|
||||
cover shape paths and masks, corner-vertex dragging and undo, and layers without visible paths.
|
||||
|
||||
## Estimate
|
||||
|
||||
**≈ 60–120 h**: the open interaction issues above (25–45 h, about 0.2–0.5 h each with a scripted
|
||||
@@ -44,6 +50,8 @@ Effects running; most of the rest parallelises.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-18 | minor | A shape group's inner Contents (After Effects' elided Vectors group) draws no row: the group's path, fill/stroke and Transform list directly under the group |
|
||||
| 2026-10-13 | patch | Layer bounds are suppressed during path-point editing (#1220); shape/mask interaction regressions |
|
||||
| 2026-10-12 | minor | Closed issues (#302, #320, #491, #511, #553 and others) marked as closed instead of open; panels in their own windows (#486) |
|
||||
| 2026-10-12 | minor | Percentage guides and effect colour labels (26.5) added; rows updated |
|
||||
| 2026-10-10 | major | First UI parity checklist |
|
||||
|
||||
+17
-8
@@ -156,8 +156,9 @@ of the project:
|
||||
|
||||
Frames without effects that the WebGPU compositor can finish alone still render on the page
|
||||
(they stay on the GPU, no readback); everything else (frames with effects, 3D runs, the Software
|
||||
Only renderer, WebGL2) goes to the workers. If no worker is usable, frames render on the page's
|
||||
thread as before. `effectcraft.info().frameWorkers` reports `{workers, alive, busy, rendered,
|
||||
Only renderer, WebGL2) goes to the workers. If no worker is usable (one that failed to start, or
|
||||
one the browser refused to create, as in a sandboxed iframe), frames render on the page's thread
|
||||
as before. `effectcraft.info().frameWorkers` reports `{workers, alive, busy, rendered,
|
||||
failed, lastMs, syncs, syncBytes, gpu, gpuFrames, lastPasses, diskHits}` (`gpu`: each worker's
|
||||
adapter, `{cpu: why}` without one, `null` until it reported).
|
||||
|
||||
@@ -413,7 +414,8 @@ no npm packages): load, the GPU path (WebGPU → GPU compositor, viewer on the G
|
||||
in the viewer, `render.frame`, Render Queue GIF and PNG sequence (`.zip`) downloads, a background
|
||||
render in a worker while measuring the page's event-loop gaps (must stay under 400 ms) and its
|
||||
progress, the AudioContext starting on a (simulated) key press and a tone playing through it
|
||||
with the meters moving, Save As and re-opening the saved project, persistence across a reload
|
||||
with the meters moving, Save As and re-opening the saved project, a script opening a file from
|
||||
browser storage and the footage check finding stored media, persistence across a reload
|
||||
(session snapshot, a stored project, imported media, a setting, Open Recent) and an offline
|
||||
reload through the service worker. It writes screenshots and `report.json`:
|
||||
|
||||
@@ -424,14 +426,21 @@ node apps/effectcraft-web/tests/smoke.mjs --url http://127.0.0.1:8765/ --out tar
|
||||
|
||||
`apps/effectcraft-web/tests/workers.mjs` checks the job-worker plumbing (`js/host.js`,
|
||||
`web/worker.js`) under Node with a fake `Worker`, without a build: a replaced file is sent to a
|
||||
reused worker again even at the same size, and a worker that fails to start fails its job and is
|
||||
terminated (`node apps/effectcraft-web/tests/workers.mjs`).
|
||||
reused worker again even at the same size, a worker that fails to start fails its job and is
|
||||
terminated, and a `new Worker` that throws (a sandboxed iframe) fails the job and reports the
|
||||
frame worker dead instead of stopping startup (`node apps/effectcraft-web/tests/workers.mjs`).
|
||||
|
||||
`apps/effectcraft-web/tests/page.mjs` checks page-side helpers of `js/host.js` the same way with
|
||||
a fake DOM: a picked file that can't be read is reported while the others are added, and the
|
||||
device-lost notice saves, flushes before reloading and reports a failed flush
|
||||
(`node apps/effectcraft-web/tests/page.mjs`).
|
||||
|
||||
`apps/effectcraft-web/tests/storage.mjs` checks the IndexedDB storage backend of `js/host.js`
|
||||
with a fake IndexedDB: a put or delete resolves only once its transaction has committed, and one
|
||||
whose transaction aborts after its request succeeded (or whose request fails or throws) rejects
|
||||
and leaves the stored entry as it was, so the flush keeps it for a retry (#1242)
|
||||
(`node apps/effectcraft-web/tests/storage.mjs`).
|
||||
|
||||
It also checks the M13.10 paths: viewer frames rendered in frame workers while scrubbing with the
|
||||
CPU renderer (project synced as diffs, event-loop gaps under 400 ms), a `wait: true` render that
|
||||
replies when done while the page keeps running, and the Media Browser over browser storage; and
|
||||
@@ -453,19 +462,19 @@ every request and reply, a render through an in-process offload; `tests_roto.rs`
|
||||
propagation through an offload streaming segmentations; `tests_mask_warp.rs`: Warp Stabilizer
|
||||
plans solved in the job, never by `warp.status` on the page), the frame-worker protocol
|
||||
(`crates/engine/src/remote.rs`: diff / patch, a replica following edits renders the same pixels;
|
||||
`crates/ui-egui/tests/ui_web_offload.rs`: the frame cache through a remote renderer, `wait: true`
|
||||
`crates/ui-egui/tests/it/ui_web_offload.rs`: the frame cache through a remote renderer, `wait: true`
|
||||
deferred until the offloaded job ends) and the Media Browser's virtual tree (`media_browser.rs`,
|
||||
`tests_panels.rs`). M13.24: deferred readbacks on a real device (`crates/gpu/src/tests_deferred.rs`:
|
||||
whole-frame GPU and CPU-composited frames in passes match the CPU, placeholders never reach the
|
||||
layer cache; `deferred.rs`: keys, late readbacks of an older frame dropped), the frame server's
|
||||
passes and Auto timing (`remote.rs`, a deferred stand-in accelerator), the browser's routing and
|
||||
disk cache (`crates/ui-egui/tests/ui_web_gpu_storage.rs`: a GPU frame worker renders effect frames,
|
||||
disk cache (`crates/ui-egui/tests/it/ui_web_gpu_storage.rs`: a GPU frame worker renders effect frames,
|
||||
the page's GPU keeps frames without effects, frames stored under their disk keys come back after a
|
||||
"reload" without rendering, the blue cache bar sees them; the storage manager's widgets and
|
||||
commands), the disk index (`disk_cache.rs`), the store's usage by kind and clearing (`store.rs`),
|
||||
`storage.*` (`storage.rs`), and every WGSL float literal fitting f32 (browsers reject the whole
|
||||
module otherwise; `tests.rs`). M13.30: job workers on a deferred device
|
||||
(`crates/ui-egui/tests/ui_web_job_gpu.rs`: a Render Queue job and a Warp Stabilizer analysis
|
||||
(`crates/ui-egui/tests/it/ui_web_job_gpu.rs`: a Render Queue job and a Warp Stabilizer analysis
|
||||
render their frames in passes on the device, with the CPU worker's pixels / result),
|
||||
particles, Advanced 3D and the job pass driver in passes (`crates/gpu/src/tests_deferred_jobs.rs`),
|
||||
layer buffers through the disk store and the prefetch plan (`remote.rs`
|
||||
|
||||
Reference in New Issue
Block a user