- Default disk cache size 20 GB (was 100 GB); settings version 3 migrates the stored old
default and keeps a size the user chose.
- The cache keeps max(2 GB, 5% of the disk) free: sysinfo::disk_space (df -Pk / PowerShell
DriveInfo) is read in the background from memory_tick and caps the limit
(DiskCache::set_disk_space); cache.diskStats reports limitBytes.
- Eviction goes down to 90% of the limit, so the LRU sort runs once per tenth of the limit
written instead of on every write at the limit.
- Failed writes are counted (cache.diskStats failed) and the first is logged.
Also fixes#2063.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
effect.cryptomatteAt rendered the effect's input on every call to learn its pixel grid (up
to ~1 s per hover on a 3D precomp). The aux channels and that grid are now kept in a
CryptoProbe on the Session, keyed by the project revision, layer, effect, frame, scale and
render prefs (nested switches, draft shadows). On footage with nothing animated before the
Cryptomatte effect the frame is the source frame, so a still or a held frame keeps one
probe for every comp time. The effect's own Selection doesn't count: the probe never reads
it, so an animated Selection keeps the probe too.
Session::crypto_probe_ready says whether a query answers from the kept probe and changes
nothing. Session::crypto_probe_job builds it on another thread for the viewer; asking for a
job is starting it. Each started job has a generation: it stores its result only while it
is the newest, a newer job or an edit cancels it, and a panic in the build becomes the
probe's error. A cancelled build checks its generation between its stages (reading the IDs,
rendering the effect's input), so it stops early and lets go of what it read. `probeOnly`
makes cryptomatteAt / pickCryptomatte answer {pending} instead of building on the caller.
A pick re-keys the probe (it only edits the Selection), and undo, redo and history jumps
over picks keep it too (each pick is labelled "Pick Cryptomatte"). Any other edit, footage
changes, Open / New and Session::drop_crypto_probe (the eyedropper disarmed) let it go. The
slot's type is crate-private (the Session field stays public, doc(hidden), so other crates
can still build a Session); crypto_probe_stats and crypto_probe_jobs (jobs run, cancelled
ones included) are doc(hidden), for tests.
Reconcile main 991904888a and preserve the reviewed lifecycle, IME, rendering and lockfile changes.
Formatting, strict lint, layers, assets and WASM passed. Full CI retained 3,961 passes, 14 baseline-matching GPU failures and 31 ignored executions. The local review commit has explicit approval to retain these documented failures; native/browser acceptance and paired app performance remain open.
A table of After Effects' index and unit for the effect parameters its keyframe text gives differently from EffectCraft's rules, read from After Effects 2019's text for 230 effects. All 1,530 parameters the two share now match; 949 did before.
Session::bump took the disk cache's footage salt at every edit, which reads the size and time of
every footage file, each frame of an image sequence included. With three 5000-frame sequences a
caret move or a typed character in a text field took ~0.6 s on the UI thread (9 ms with the disk
cache off). The salt is now taken again only when the footage items changed, or when a project
is opened, footage is reloaded or the disk cache attached.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Alt/Option-drag a Project item onto a Timeline layer, or press Ctrl+Alt+/ (Cmd+Opt+/) with layers and an item selected, to swap the layer's source. The layer keeps its name, timing, transforms, keyframes, expressions, effects, masks, switches, parenting and track matte; a source with audio adds Audio properties when missing. Precompositions can't be nested inside themselves.
AVLayer.replaceSource now runs the same engine command and closes the replaceSource part of #813.
Copying keys or unanimated properties of one layer records them as After Effects keyframe text: its Transform properties, one row per key with the frame in comp time (start offset and stretch applied) or a frameless row for a static value. EffectCraft's own keyframe clipboard is unchanged.
Engine part of viewer picking: replace / add / remove the top-coverage ID at a
layer-space point in one undo step, at the time the Selection was read. The
aux channels come from the render's own path (Renderer::layer_aux, shared with
the effect host; footage at its source time, 3D precomps) through a renderer
set up like Session::render (expressions, render prefs, layer cache). An
optional scale snaps the point to the pixel a preview at that resolution
samples. New effects::crypto_select quotes names and falls back to <hex> for
unnamed, wildcard-looking or ambiguous names. effect.cryptomatteAt is the
read-only hover query.
Layer times were already saved and read back as exact ticks; the values in
the issue are 34f, 315f and 3033f at 59.94 fps. The viewer was black because
the playhead wasn't saved: a reopened comp started at 0, before its layers.
The project file's view (`activeComp`, `openComps`) now also records each
comp's current time (`compTimes`), restored on open within the comp's frames.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Footage paths stored in a project are resolved through craft-docpath against
the project file when it is opened or imported: relative paths are taken from
the project's folder, and an item whose path is on another computer, names a
device or is a non-file URL is marked missing without being looked at. The
footage check, the changed-on-disk scan, Reload Footage, Relink, Edit Original
and Reveal in Finder resolve the path again before touching the file, and the
Project panel, Metadata panel and waveforms read no file details of missing
footage.
Lottie image assets are resolved against the Lottie file; one on another
computer is skipped with a warning. Files a 3D model names are read only when
local or on the model's own share.
Reveal in Finder refuses a folder that would open as an app or other bundle.
Files imported or relinked in this session are opened as given.
keys.delete returned the number of selected keys even when some of them
no longer existed, so a delete that removed nothing still reported success.
It now returns the number of keys actually removed, and a key that is gone
or listed twice is not counted.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JaA7tHy731qtvscjulrcfz
Merge each offloaded property-group result against the snapshot the job started from. Preserve disjoint live edits, group identity and child changes; refuse conflicting results without changing the document or undo/redo history.
Record an apply rejection on its originating job so completion logs and waiting control commands report failure. Keep concurrent jobs' outcomes separate and retain the correct completion position when the bounded log rolls over.
Add actual-worker regressions for conflicts, disjoint edits, concurrent analyses and undo/redo, plus control completion/error regressions.
Fixes#1249
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A single selected_layers list kept ids from every comp, so the active comp looked selected whenever another comp was. Opening a comp and running a script against a different comp now park and restore that selection.
sanitize_state dropped every selected layer not in the active comp at the
end of each script run, so a layer selected by script in a comp that was not
open was gone by the next script call. Every comp keeps its own selection as
in After Effects; only ids gone from every comp are dropped now. UI and
engine lookups resolve selections per comp and tolerate foreign ids.
After Effects 25.4's Quick Offset staggers selected layers, or each
layer's selected keyframes, in the order they were selected: the first
stays, the last moves by the drag and the ones between by equal steps,
landing on subframes.
layer.quickOffset {delta, layers?} moves each layer's start, in and out
point by total * i / (n - 1), worked out once in ticks with integer
arithmetic, so 1 frame over 4 layers gives 0, 1/3, 2/3 and 1 frame
exactly. keys.quickOffset {delta} does the same per layer for the
selected keys, the layers in the order their first key was selected;
every selected key of a layer, across its properties, moves by that
layer's comp-time offset through its start and stretch (reversed too),
keeping values, interpolation, easing and labels. Locked layers are
skipped and at least two layers are needed. With merge and fromStart,
delta is the whole drag and each step starts again from before it, so
the drag is one undo step and a key passed over survives. A delta or a
destination out of the range of times is an error, not a clamped move.
Docs: agents.md; architecture.md notes Quick Offset as the exception to
times snapping to frames.
comp.workAreaToSelection (Cmd+Alt+B) spans the selected layers, or the
whole composition with none selected. comp.activateRecent (Shift+Esc)
goes back to the composition that was active most recently within the
active one's nesting hierarchy; EditorState keeps recent_comps for it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
time.workAreaStart / time.workAreaEnd (Shift+Home / End) and
time.previousInOut / time.nextInOut (Cmd+Alt+Shift+Left / Right): the In
and Out points of the selected layers, of every layer with none selected.
time.go takes nextInOut and prevInOut.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The media layer keys decoded frames by path, and nothing dropped them when a
new project (or a new import) named a file that another app had overwritten
since, so a same-size overwrite showed the old pixels. Importing footage and
replacing the project now make the media layer and the layer cache let go of
the files involved.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
File > Import > EffectCraft Project (and .ecproj/.ecprojx given to File > Import)
merges the project into a folder named after the file, with every id shifted past
the open project's, in one undo step. Relative footage resolves next to the file;
the result lists comps, root comps, primaryComp and missing media.
offset_ids now shifts tracker Motion Targets and saturates instead of overflowing;
fix_next_id saturates too, so a damaged file can't overflow on open or import.
Menu entry translated in all ten menu catalogues.
Rebased onto current upstream main for the split of #858 (test module list in
engine lib.rs; es/pt-br/ru/fr/de/cs menu translations).
(cherry picked from commit 19c653fccc95133fb35e978a7f7f20a18c0e8c89)
(cherry picked from commit 4e751ad2)
(cherry picked from commit ceed706cc3b5f3755769738a1ddb3d965a94ab6b)
Viewer: each composition keeps its Resolution in the project, as in After Effects (#417)
# Conflicts:
# crates/ui-egui/src/control.rs
# crates/ui-egui/src/menus.rs
# crates/ui-egui/src/panels/viewer_tools.rs