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.
After Effects draws a Rectangle Path clockwise from its top-right corner, or
with round corners from the end of the top-right arc on the right edge;
Trim Paths measures from there (AE 2026, case 930: 400 x 300 rectangles,
Trim 0-25 % and 70 / 30). EffectCraft built the path with kurbo, starting at
the top-left corner, so trimmed rectangles drew another segment: case 930
was 86 of 5184 grid points off (max 255), now 5 at stroke edges. Start > End
was already drawn swapped, as in AE. Convert to Bezier now gives AE's vertex
order (top-right first); the engine test follows. Lottie rectangles share
the path and now start where lottie players start them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Timeline draws the Video switch (the eye) on every layer's row, nulls,
cameras and lights included, but set_switch_others and hide_other_video
skipped cameras and lights: Alt-clicking the eye of a selected layer neither
set them apart with the selection nor hid them with the rest.
Both now use the rule the eye cell shows: every layer but track mattes.
At preview scales from 0.75 to 1 footage keeps native pixels, a proxy sets
its own buffer scale at any render scale, a Preserve Resolution precomp is
drawn at full size, and earlier effects can pad or offset the buffer, so
snapping to the preview grid could pick a different ID than the rendered
matte. Use the scale and offset of the buffer the Cryptomatte effect
receives (layer_input, rendered once and then cached).
The pixel centre goes to an aux point through effectcraft_effects::aux_point,
which index_map_at now calls too, so the two round identically (a division
vs the reciprocal picked the other ID on a 104 px proxy of 1920 px footage).
An unknown effect uid errors "no such effect" and a layer without pixels
"this layer has no pixels", as effect.pickColor does.
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.
Replacing a virtual file bumps a counter the UI lookup reads. The content hash runs on that miss, from a callback, so the frame path does not stat every footage item or hash on every store write.
The Roto/Object Matte segment store is one per process (roto::set_seg_store),
and every Session::configure_disk_cache that opens a cache takes it over. The
roto and disk-cache tests already serialise on hold_roto_cache_test_lock(), but
many other engine tests build a Session with a settings store, which opens the
disk cache at the platform folder and takes the store; once that session drops,
the weak store is dead and object_matte_mattes_persist_in_the_disk_cache sees
no disk writes. It failed 3 of 3 in the full parallel suite here.
configure_disk_cache now takes that lock under cfg(test) before opening a
cache, so every such test runs apart from the roto tests. Re-entry on the same
thread is a no-op. Product code is unchanged: only the app's one session ever
opens a disk cache, so users cannot hit the race.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Exporting still frames via comp.saveFrameAs previously flattened the
rendered frame against the composition background color, stripping
the alpha channel. Single still frames queued in the Render Queue also
defaulted to an RGB-only PNG sequence output module.
Preserve straight alpha by default in comp.saveFrameAs (while honoring
transparent: false if requested), and set TemplateSlot::Still to default
to 'PNG Sequence with Alpha'.
Fixes#1815
`Stills` is only used by the `#[cfg(unix)]` part of
`footage_on_other_computers_is_never_looked_at`. On Windows it was dead
code, so `cargo xtask ci` stopped at clippy (`-D warnings`).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A WAV's markers were never read, so its audio layer showed none. The importer
now reads the file's cue points (`cue ` chunk named by `LIST/adtl` `labl` and
`note`, sized by `ltxt`) and Adobe's XMP markers (`_PMX` chunk, `xmpDM:markers`
entries as elements or attributes, in frames of the track's `xmpDM:frameRate`).
A marker present in both encodings is listed once. The reader is bounds-checked
and caps the marker count and XMP size.
- `Footage.markers` (optional in the file format) holds them at footage time; a
layer made from the footage starts with them.
- Layer ▸ Markers ▸ Update Markers From Source also works on footage layers.
- Markers from a file have no label colour.
Tests: reader unit tests (cue, note, ltxt, both XMP forms, both encodings,
truncated and hostile files), a probe test, and an end-to-end test through the
fully wired session. Not checked against a WAV written by Adobe's tools.
Claude-Session: https://claude.ai/code/session_013fTnVxnXTv2MBW6uj2PPyU
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The picture comes from an item's proxy while Use Proxy is on, but the
Project panel, the Viewer, project.summary and footage.info asked only
about the item's own file: a proxy that couldn't be decoded (an AVI with
an unsupported codec, say) left the layer empty with no reason given.
They now ask FootageSource::item_problem, which gives the proxy's reason,
said to be the proxy's, for footage and compositions alike.
Pasting Trim Paths, Wiggle Paths or a paint into a shape layer with nothing
selected put it on top of Contents, where it acts on no shapes. It now goes
to the bottom, as Add places it. Also fixes#1629.
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>