Commit Graph
4329 Commits
Author SHA1 Message Date
Bar BorerandClaude Opus 5.5 6c49ef819d Tests: trim the key box tests to what #2857 changes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 05:16:27 +02:00
Bar BorerandClaude Opus 5.5 8a37ff6b43 Tests: trim the midpoint snap tests to what #2855 changes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 05:07:59 +02:00
Bar BorerandClaude Opus 5.5 325c029d06 Viewer: the Rotation tool measures its angle on screen about the anchor (#2870)
One comp-space path: the pivot is the anchor where it shows and a flipped
parent reverses the turn, so the separate parent-space frame and the moved
parent_to_comp go. Under uneven or skewed parents the layer's edge tracks the
pointer slightly less exactly. The empty-layer marker goes back to main's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 04:59:16 +02:00
Bar BorerandClaude Opus 5.5 72d0b3aa08 Viewer, Timeline: a middle-drag pans and moves no layer (#2854)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 04:57:19 +02:00
Bar BorerandClaude Opus 5.5 f1767c3349 Panels: the gutter keeps rows clear of the scroll bar (#2867)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 04:57:14 +02:00
Bar BorerandClaude Opus 5.5 fe6031a36f Panels: the panel menu stays as wide as its contents (#2864)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 04:57:08 +02:00
JonRC 34b3795c00 CLI: props prints an enum's label next to its number (#2928)
The text listing printed enum values as bare numbers, which are 0-based
where After Effects scripting counts from 1, so Range Selector Shape 2
read as Ramp Up was Ramp Down. Print the label from the property's
options, as props --json already lists them: "0 (Square)".
2026-11-02 23:52:47 -03:00
vangel76andClaude Opus 5.5 0d2153e259 test(media): review follow-ups for the aux footage set (#474)
- A watch-folder render gives the host session's set back after its own
  project replaced it on the shared footage source.
- Guard gpu fx_depth::IDS against reads_aux drifting apart.
- Count picture and channel decodes per thread instead of reading the
  shared channel cache, so parallel tests can't make the checks flaky.
- The engine test fails when no set was ever sent.
- Docs: Media Replacement items are learned at their first frame.

Refs #474

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 03:27:25 +01:00
JonRC 12a61c5cc9 Export tests: check the bitstream colour on the bare elementary stream
The bitstream check read ffprobe's first-frame tags from the MP4, which
ffmpeg can fill from the colr box when a stream says nothing itself. Copy
the video out as a bare h264, hevc or obu stream first, so only the SPS VUI
or AV1 color_config counts.
2026-11-02 23:24:09 -03:00
vangel76andClaude Opus 5.5 5b31b96cde perf(media): tell the media pool which EXR footage effects read (#474)
The pool learned that an effect reads a footage item's channels only at the
first aux() call, so the first frame was decoded twice (picture, then in
full), and the hint was never cleared after the effect was removed.

The session now gives the footage source the whole set of files read by
enabled 3D Channel effects (FootageSource::set_aux_footage) with every
project change (Session::bump: edits, undo, redo, history jumps, rollbacks,
project open), sending it only when it changed. The pool keeps that set and,
as a safety net, what aux() learns; a new set drops what was learned.

Refs #474

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 03:21:40 +01:00
JonRC 1b5c28ee1d Follow-up to #2919: Match Grain matches no grain without a clean sample box
A source with no box-sized area clear of transparent pixels (a small
layer inside a blur's padding) got no sample boxes, and
grain_stats_boxes then measured the whole image, edge included. With no
clean box Match Grain now matches no grain, as After Effects applies
none when its samples hold transparent areas. Compensate for Existing
Noise took the same route through the layer's own boxes, so a layer
with no clean box now has nothing taken off. grain_stats_boxes keeps
its empty-list fallback.
2026-11-02 23:17:04 -03:00
JonRC 583ba084b6 Fix #2917: material.set names the Material Options a layer doesn't have
A 3D model layer keeps its file's materials, so it has no Base Color,
Metallic, Roughness or Emissive. material.set refused them with "no
Material Options property matched (..., baseColor, metallic, roughness,
...)", naming the parameters it had just refused, and next to a
parameter the layer has it dropped them and reported success.

A parameter that none of the given layers has now fails the command
(nothing is set), naming it and the properties the first layer has. A
value of the wrong type is its own error. The parameter list says which
layer kinds take which.
2026-11-02 23:13:02 -03:00
Bar BorerandClaude Opus 5.5 fa0c260630 Puppet: refuse only a mesh that repeats one already there (#2865)
Cut the fix down: a click off every mesh makes nothing only when the art it resolves to
already has a mesh with that outline. The nearest-art fallback in `region` stays, so a
near miss beside separate art still meshes it. `mesh::outline` is as it was and the
`newMesh` option of puppet.addPin is back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 04:00:19 +02:00
Bar BorerandClaude Opus 5.5 5593c6e8b3 Tests: give the key box test's comp a frame rate, so its keys stay on whole seconds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 03:57:05 +02:00
JonRC c6ab02b83a Fix #2919: Match Grain takes no sample box beside transparent pixels
A box's own pixels had to be opaque, but the high-pass that measures its
grain reads 3 px around it, and a transparent pixel there is black once
unpremultiplied. A blur earlier in the stack pads the layer with
transparent pixels, so boxes along that border measured the edge as
strong, smooth colour grain, and Match Grain drew 12 px colour cells
from a clean source on the frames that picked them. Boxes now need their
3 px surround opaque too (a summed-area table keeps the check O(1) per
candidate). Remove Grain picks its boxes the same way.
2026-11-02 22:49:52 -03:00
vangel76 00a15d847e Merge remote-tracking branch 'upstream/main' into perf/exr-decode-cache-474 2026-11-03 02:38:46 +01:00
Bar BorerandClaude Opus 5.5 9431089493 Puppet: decide a marquee from where the button went down (#2865)
The drag branch asked what was under the pointer when the drag was recognised, by which
time a quick drag has already reached the art, so no gesture started at all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 03:37:31 +02:00
vangel76andClaude Opus 5.5 eedd64906c #474: keep files in use in the channel cache, cap plane memory, fall back on a missing cache entry
Review follow-ups:
- Files in use are never dropped from the channel cache: planes an effect
  holds, the files of the last 4 pictures made of their planes until their
  effects ask for the channels, and decodes in progress, which now count
  the planes they will allocate (from the header) against the budget. With
  4 frames at once over a budget of one file, each file decodes once (test).
- A decode that panics removes its entry instead of leaving it decoding.
- One decode allocates at most 8 GiB of planes (512 MiB on 32-bit and
  wasm); a header asking for more is refused with the reason.
- A shared picture whose file has no cached channels (a failed read, a
  broken part it doesn't use) is decoded from its own channels.
- Comment why waiting on a decode may block a rayon worker; drop the
  subsampling check and test (the exr crate refuses subsampled files).
- Docs: the cache is a soft bound; a single-part file still decompresses
  every channel; the first frame and the image crate's route decode twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 02:25:07 +01:00
vangel76andClaude Opus 5.5 d481671947 #474: golden OpenEXR files for tiles, mip levels, line order, uncompressed and uint RGB
Adds tiled (ZIP16, RLE), tiled mip-mapped (ZIP16, PIZ), decreasing line
order, uncompressed, a uint R/G/B picture and a multi-part file whose first
R/G/B part is depth-named (the planes can't stand in for the image crate's
picture there). The new hashes were taken with main's crates/media/src at
a646bdcc (extracted with git archive) and the same test, before the decode
rework.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 02:20:15 +01:00
vangel76andClaude Opus 5.5 2939b3769c #474: format the OpenEXR golden test
Formatting only; no hash changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 02:18:30 +01:00
vangel76andClaude Opus 5.5 8add8ac7b4 #474: decode a multi-layer EXR once per frame, and only its picture when nothing else reads it
- The channel cache decodes a file once however many callers ask for it at
  the same time (the others wait for that decode), and is bounded by the
  bytes of its planes (1 GiB, least recently used out first, a file counting
  as used when its decode finishes, the newest file always kept) instead of
  four files of any size.
- Once an effect has read a footage item's channels, its pictures are made
  of the cached planes, also for files with an unnamed RGB layer (bit for
  bit what the image crate shows, which still decodes the files the planes
  can't stand in for); footage alone decodes only its picture's channels
  (a multi-layer file's were all decoded and cached for every frame).
- Channels are decoded straight into their display-window planes, and the
  picture is converted from the planes without two interleaved copies.

Measured on a 30-frame 2560x1440 sequence of 32 channels (DWAA), one frame
at a time: footage alone 0.185 -> 0.073 s per frame and 4.4 -> 1.8 GB peak;
with an unnamed RGB layer plus Cryptomatte 0.244 -> 0.159 s; multi-part
plus EXtractoR 0.194 -> 0.127 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 02:06:02 +01:00
vangel76 05c6f5d495 Merge remote-tracking branch 'upstream/main' into fix/exr-scene-linear-output 2026-11-03 01:57:53 +01:00
vangel76andClaude Opus 5.5 406c9a9648 EXR export: raw alpha for Alpha-only files, skip_hdr_step name, more tests
- Rename RenderOpts::scene_output to skip_hdr_step (Pipe::without_hdr_step):
  the consumer decodes with Pipe::output_linear or applies the step itself.
- Alpha-only EXRs hold the alpha as it is in every output space (it is
  coverage, not encoded colour; it used to be sRGB-decoded).
- Tests: a half-opacity layer over a coloured background (premultiplied and
  RGB-over-background paths, golden hashes taken before the change), Rec.
  2100 PQ and HLG EXR outputs, raw alpha, and a visible skip of the GPU test
  without an adapter.
- docs/architecture.md: the Working Gamma curve for unmanaged projects, and
  no HDR step for unmanaged projects or PQ / HLG outputs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:57:53 +01:00
vangel76andClaude Opus 5.5 50340011f5 Follow-up to #2911: clippy in the OpenEXR round-trip test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:53:16 +01:00
vangel76 32871be132 Merge remote-tracking branch 'upstream/main' into perf/exr-output-options 2026-11-03 01:52:11 +01:00
vangel76andClaude Opus 5.5 51d7e530a0 Follow-up to #2911: B44 at float is labelled uncompressed, half holds over-range samples
B44 and B44A pack only half floats, so at 32-bit float the menu and the
render log say the samples stay uncompressed. Half-float frames hold samples
above 65 504 at that limit instead of writing infinity. The EXR parameters
move into their own helper; exr_out is public for other writers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:52:06 +01:00
vangel76andClaude Opus 5.5 684e05859f EXR export: no compand / tone mapping, and the output space's own curve
OpenEXR renders (Render Queue sequences and Save Frame As ProEXR) received
the frame after Project Settings > HDR, so Compand and Tone Map (the default
of every OCIO project) compressed highlights and changed values below 1.0
before the writer decoded them, always with the sRGB curve.

- RenderOpts::scene_output: the top-level comp converts to the output space
  without the HDR step (CPU and GPU walks, via Renderer::output_pipe).
- Pipe::output_linear: the writers decode with the output space's curve
  (the Working Gamma's when unmanaged) instead of a fixed sRGB curve.
- Render Queue EXR jobs and ProEXR set the flag; PSD layers, 8-bit stills,
  movies and the viewer keep the HDR step. The flag is part of the viewer
  frame and disk cache keys (opts_hash).

Golden hashes, taken before the change, pin PNG/TIFF/Targa bytes for
8/16/32 bpc x Clip/Compand/ToneMap, Clip-mode EXR samples with an sRGB
output, and the layered PSD bytes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:47:24 +01:00
vangel76andClaude Opus 5.5 b5fe54c08b Render Queue OpenEXR: choose compression and half or 32-bit float (#2911)
OpenEXR sequence output modules get two settings, exrCompression (none,
rle, zips, zip, piz, pxr24, b44, b44a, dwaa, dwab; scan lines in each
method's own block height) and exrBitDepth (half or float). The defaults
are ZIP and 32-bit float, so existing projects render the same bytes as
before. They go through the project file, Output Module templates, the
renderQueue commands and renderQueue.formats, the CLI (--exr-compression,
--exr-depth), the Output Module menu (lossy methods are marked) and the
render log. Half rounds each sample to the nearest half float.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 01:37:07 +01:00
Brandon ThomasandClaude Opus 5.5 7870b5916a Release 0.21.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.21.0
2026-11-02 19:26:55 -05:00
JonRC 0b59d2b8c4 Follow-up to #2907: say that a uid wins over an index for animator
animator_uid matches a uid before an index, so an animator whose uid
equals an index is picked over the one at that position. The four
commands that take `animator` (layer.addTextAnimatorProperty,
layer.addTextSelector, text.addSelector, text.animatorFontAxes) now say
so; text.addSelector also said only `uid|index` before. The test gives a
second animator uid 1 and checks that `animator: 1` picks it.
2026-11-02 21:14:22 -03:00
ljkx f6fd6de3bb Follow-up to #2841: Composition Settings settles after a width change
- `dialog.comp.view` is the scroll area's viewport (its clip rect). Cut
  by the content's max rect, it lost the scrolled amount at the bottom.
- When the dialog's width changes, the pass laid out at the old width is
  discarded and a repaint requested.
- The measured width keeps room for a vertical scroll bar. Without it,
  switching the language with the dialog open left the two scroll bars
  trading places every frame: the UI never settled and OK was clipped
  every other frame.
- `content_width` becomes `dialog_width`, which returns the dialog's
  width, so the 600-point rule is in one place.

Tests: the view keeps its place when the dialog scrolls, and switching
to French and back with the dialog open settles with OK inside it.
2026-11-03 01:03:48 +01:00
JonRC 5e8e6f829e SAM 2: the gpu_matmul example reads WGPU_POWER_PREF too (#2900) 2026-11-02 20:43:58 -03:00
JonRC 5643f39769 Fix #2907: say that text animator indexes count from 1
layer.addTextAnimatorProperty, layer.addTextSelector and
text.animatorFontAxes take `animator: uid|index`, counted from 1 as the
code and `#n` references do, but the parameters didn't say so and
`animator: 0` failed with only "no animator 0". The parameters now say
1-based, and the error says what the value can be.
2026-11-02 20:32:48 -03:00
vangel76andClaude Opus 5.5 e7c208434b #474: pin OpenEXR pictures and aux channels to exact hashes
Synthetic single-layer, multi-layer, multi-part and windowed files in
half, float and uint, with RLE, ZIP, PIZ and DWAA, decoded through the
media pool picture first and channels first. The hashes are taken on
main before the decode is reworked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:27:45 +01:00
JonRC 0b60a86bfe Fix #2904: a second layer or a copy of v-b1.mp4 is v-b1.mp4 2, not v-b1.mp 2
Comp::unique_layer_name and Project::numbered_item_name each stripped
every trailing digit before numbering. Both use one helper now,
numbered_name, which replaces a trailing number only when a space sets
it off (Red Solid 1 -> Red Solid 2); digits that end a word stay.
2026-11-02 20:20:21 -03:00
JonRC 50e5a7970e SAM 2: read the wgpu env variables and refuse adapters without compute shaders (#2900)
SAM 2's instance now reads wgpu's environment variables, as the render context's does, so a driver such as
Mesa's Dozen (WGPU_ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER=1) is visible to it, and WGPU_POWER_PREF applies.
An adapter without compute shaders is refused before a device is made on it, so the CPU takes over with one
line instead of a validation error per kernel. The refused adapter is kept for the life of the process, as
the device on it was: dropping it crashed the tests on WSL2's GL driver. Gpu::shared no longer repeats
"SAM 2 compute:" in its errors.
2026-11-02 20:18:18 -03:00
FredApps a646bdcc2e Merge pull request #1598 from danielmigueltejedor/fix/gnome-window-decorations
Restore GNOME Wayland window controls without bundled fonts
2026-11-02 19:42:57 +01:00
ljkx cab353eb5b UI: Composition Settings widens to fit a longer translation (#2841)
The dialog was a fixed 600 points wide inside a scroll area, so a row
wider than that in another language (French "Conserver la fréquence
d'images en cas d'imbrication ou dans la file d'attente de rendu", the
French and Spanish Lock Aspect Ratio row) pushed OK past the right edge,
reachable only by scrolling sideways.

The dialog now measures the widest row of its three tabs (as it already
measured their heights) and takes that width, or 600 points if that is
more, capped by the window as before. English keeps 600 points.
2026-11-02 19:40:51 +01:00
Bar BorerandClaude Opus 5.5 53dc9032c5 Viewer: the Rotation tool measures its angle in parent space (#2870)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:40:09 +02:00
Bar BorerandClaude Opus 5.5 f20dd38f94 Puppet: a click on deformed-away space creates nothing (#2865)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:39:17 +02:00
Bar BorerandClaude Opus 5.5 34b3e13d54 Timeline: box selection selects a keyframe as soon as it touches it (#2857)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:38:40 +02:00
Bar BorerandClaude Opus 5.5 39119ff9ce Viewer: dragged layers snap to other layers' midpoints and center lines (#2855)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 20:36:57 +02:00
FredApps f2aeff7e17 Merge current main into fix/gnome-window-decorations 2026-11-02 19:10:38 +01:00
arnav-hiwarkarandClaude Opus 5.5 da5bf7b45a Follow-up to #2803: document the Timewarp speed-ramp tests and test host
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:54:04 +05:30
JonRC 6d5e84e7a5 Export: H.264, HEVC and AV1 tag the frames' own colour (#2879)
Every MP4 export said BT.709 whatever the Output color space, in the
colr box and in the bitstream, so Rec. 2020, P3, PQ and HLG renders
were labelled as BT.709.

Move the ProRes colr mapping to output_codes and use it for every movie
path: the nclx colr box, FilmCraft's H.264 ColorConfig (VUI), and new
colour fields in effectcraft-hevcenc (VUI) and effectcraft-av1enc
(color_config), which wrote 1-1-1 as constants. The matrix stays BT.709,
the coefficients every path converts with. Default renders don't change.
2026-11-02 14:11:33 -03:00
f15b619a92 Follow-up to #2823: box each of several objects around its whole matte on correction frames (#2830)
* Follow-up to #2823: box each of several objects around its whole matte on correction frames

With several objects, a correction frame's box came from the model's cut
of that object's strokes on the frame alone. A stroke on part of the
object boxed only that part, and SAM 2 followed only that part from
there on. Each object's matte is now carried to the frame with its own
strokes, from its first prompted frame or its nearest tracked frame, by
the walk Chain::ensure uses (now Chain::carry). These mattes are not
cached, since object keys hold tracked results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Follow-up to #2823: carry corrections before the base from the base outwards

Prompt frames were visited in ascending order, but before the base the
carry walks towards it, so each correction there re-walked from the
root. Visit each side from the base outwards and sort the prompts back
into frame order for the tracker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Follow-up to #2823: find an object's carry root in one pass over its strokes

The root search scanned every frame's strokes from the prompt frame to
the base, for every prompt. Take the object's foreground stroke on the
way that is nearest the base in one pass instead; the root is the same.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* M13.35: reproduce an earlier object correction losing tracked matte

* M13.35: carry earlier object corrections from valid tracked mattes

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-02 18:01:39 +01:00
arnav-hiwarkarandClaude Opus 5.5 324a6326da Fix #2803: Timewarp plays an animated Speed as a rate
Timewarp's Adjust Time By = Speed showed source time t x speed(t) / 100,
using only the Speed value at the current frame. With Speed keyframed
(a speed ramp), the source jumped to another part of the footage at the
change, and while Speed eased down it ran backwards or held still.

An animated Speed (keyframes or an expression) is now integrated over
layer time from 0 through the effect host's param_at, on a fixed
sub-frame grid with a bounded sample count. A static Speed keeps the
exact previous formula, so the Timewarp goldens are unchanged.

Local: effectcraft-effects time_fx lib tests (8), timewarp golden,
clippy -p effectcraft-effects. Full cargo xtask ci deferred to GitHub CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 22:14:39 +05:30
JonRC 2913d27d6f Import: an EDL's hour on timecode is counted in its own timecode (#2832)
Which clips use an hour or more of timecode was decided in seconds, so in a
29.97 fps drop-frame list a clip cut at 01:00:00;00 (3599.996 s) was left
out, and a missing file it used went unnamed. The hour is now 01:00:00:00 at
the sequence's rate and drop-frame setting.
2026-11-02 13:42:35 -03:00
JonRC 7f6afdb51a Lottie: leave blank font families out of the embedded glyphs warning
A `chars` entry with an empty or whitespace-only `fFamily` listed an
empty name, so the warning read "of Sans, , Serif".
2026-11-02 13:42:27 -03:00
JonRC 33156f4119 G2: --save runs on one project take turns, so no edit is lost (#2851)
A run reads the project, edits it and writes the whole file back, so two
at once silently dropped the edits of whichever saved first. A run that
saves over the file it opened (--save, or --save-as the same file) now
holds an OS lock on `.<name>.lock` next to the project from before its
read until it exits. The next run says so on stderr, waits, then edits
what the first one saved. Ported from filmcraft-cli
(storytold/filmcraft#3926).

The OS drops the lock however the process ends, so a crash leaves nothing
stale. If the lock can't be taken the run warns and saves as it did
before. A missing project stops the run before it opens anything, with
the same error opening gives, so a project created in between is never
saved without the lock. The desktop app and `mcp` don't take this lock.
2026-11-02 13:00:47 -03:00