Commit Graph
935 Commits
Author SHA1 Message Date
Valentin KleinandClaude Opus 5.5 0f94a1f983 Skip the startup update check when started for an agent (#3097)
* Skip the startup update check when started for an agent

An app started with --control or EFFECTCRAFT_CONTROL_PORT still checked
for updates at start, and the modal "newer version available" prompt
blocked agent runs until a person dismissed it. Agent launches already
skip crash recovery and the Home screen; now they skip the startup update
check too. Help > Check for Updates is unaffected (it goes through
updates_ui::check / check_requested, independent of the startup flag).

Documents what a control-port launch skips in docs/control-protocol.md and
the main.rs module doc, with a unit test for the startup decision.

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

* Control protocol docs: a window-start failure still shows a blocking message box

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 17:18:52 +01:00
Valentin Klein ce9eaec650 Add After Effects project export (#2594)
Add After Effects project export with chunks built in code, explicit loss reporting, engine and UI integration, and regression coverage.
2026-11-06 09:23:16 +01:00
FredApps 3102b6e632 Merge pull request #2915 from vangel76/perf/exr-output-options
Render Queue OpenEXR: choose compression and half or 32-bit float
2026-11-06 02:26:23 +01:00
vangel76andClaude Opus 5.5 4934de892d Merge upstream/main into cryptomatte-matte-only
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 00:53:40 +01:00
vangel76andClaude Sonnet 5.5 f9b860838c Merge upstream/main into perf/exr-output-options
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-06 00:49:29 +01:00
FredApps 3768ff40a3 Merge pull request #3134 from vangel76/aep-tdsb3-values
.aep import: read effect values stored as default-state records
2026-11-06 00:20:44 +01:00
FredApps 8b63d4308c Merge pull request #2916 from vangel76/fix/exr-scene-linear-output
EXR export: no compand / tone mapping, and the output space's own curve (#2913)
2026-11-06 00:12:32 +01:00
FredApps 39af86e9f4 Merge pull request #2929 from vangel76/perf/aux-footage-hint-474
perf(media): tell the media pool which EXR footage effects read (#474)
2026-11-06 00:12:06 +01:00
FredApps 35ce9b2694 Merge pull request #2920 from vangel76/perf/exr-decode-cache-474
Multi-layer EXR: one decode per frame shared by the picture and EXtractoR / Cryptomatte, picture-only decode for footage alone (#474)
2026-11-06 00:10:05 +01:00
skylar taylor-barrick a408d990b2 Export: use conformant ProRes alpha run termination (#2225)
* #293: use conformant ProRes alpha run termination

Pin the direct ProRes encoder to FilmCraft e4be1577 while preserving all other codec pins. Add a full MOV export regression for final alpha runs across both 4444 profiles, alpha modes and blur settings.

* #293: document ProRes alpha export regression

* export: repin FilmCraft to the merged ProRes alpha fix and move the round-trip regression into roundtrip.rs

All FilmCraft deps pinned to d5ead4ba (squash merge of filmcraft #2991). H.264 pinned movie bytes move 104341 -> 104342: only the encoder version string in the SEI (0.8.0 -> 0.12.0) and five size fields change.
2026-11-05 22:45:23 +01:00
vangel76andClaude Opus 5.5 e25025545a AEP import: an effect record is read only in the written or the default state
A record in another tdsb state is reported as damaged data again, as before the
default-state change. The synthetic test checks Levels' recorded popup directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 19:28:55 +01:00
vangel76andClaude Opus 5.5 e730da1435 AEP import: read effect values recorded in the default state (tdsb 3)
After Effects records some effect parameters as value records in the
default state (tdsb 3) that still hold the value it uses. A survey of 839
corpus and sample files finds 25 such effect records, all in Lumetri
Color (its hidden controls) and Cryptomatte (Selection Mode on); every one
differs from its pard default and reads back in After Effects as recorded.
No effect record in the default state holds a value that does not count.

The general reader now takes them: EffectParam::recorded_value (the same
name, signature and body as in the Lumetri import) reads a record whatever
its state, value() and is_default() use it, and the importer's mapped
parameters read it, so such a record imports like a written one instead of
being reported as an unreadable value with the default used.
stored_value keeps to written records.

Layer, camera, light, shape and text properties in the default state all
hold After Effects' defaults or values with no effect on that layer
(the point light cone, a polygon's inner radius, a camera's opacity), so
their readers are unchanged. docs/aep-format.md records both findings.

Closes #3126

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 16:51:53 +01:00
vangel76andClaude Opus 5.5 50cbbfe310 Cryptomatte: Matte Only puts the matte in RGB over alpha 1 (#3127)
With Matte Only on, Cryptomatte wrote the matte into all four channels:
a premultiplied white only as opaque as the matte. After Effects'
Cryptomatte plug-in shows Matte Only as an opaque grey picture, the
matte in RGB with alpha 1 (its Matte Only render of 2026-11-05 is opaque
black where nothing is selected). The CPU effect and the GPU shader now
both give (m, m, m, 1).

Projects saved before keep their render: a hidden Matte Only In Alpha
switch (off for new instances, presets and AEP imports) is turned on by
migrate::legacy_default when an older instance is upgraded on open, and
then keeps the matte in every channel on CPU and GPU.

Tests: the CPU path for a partly covered pixel, an unselected one and
outside the aux area, in both forms; the upgrade of a saved instance;
the GPU parity test over every display with Matte Only off, on and on
with the legacy switch; the host pick tests read whole pixels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 15:38:36 +01:00
a439b1bb19 Effect ▸ Install Effect Plug-in… and plug-in bundles (wasm + ScriptUI panel in one go) (#2749)
* plugins: install effect plug-in bundles (wasm + ScriptUI panels) in one go

* plugins: address review - keep listing errors, refuse replacing an installed plug-in

* plugins: report unreadable bundle entries when their metadata cannot be read

* plugins: surface partial install errors in the menu bar, cover the install flow with tests

* plugins: surface partial install errors for async dialog answers too

* plugins: show result errors as compact status text

* plugins: harden installs - confined automation, no panel overwrite, size cap, cleanup errors

* plugins: bound install reads from one handle, detect unreadable panels, clarify install docs

* plugins: drop an unneeded mut in the bounded read

* plugins: saturate the bounded read limit

* plugins: install into the session's Plug-ins folder, refuse hidden single installs and case-variant panels

- Install .wasm files into Session::plugins_dir() (the folder the Plug-in Library
  from #2524 uses), not a path rebuilt from the settings folder; the host tests
  now point both at their temp folder so they never touch the real settings.
- A single install of a hidden file (".x.wasm") was skipped and reported as a
  success that installed nothing: it is now an error.
- Installed names must be bare file names (and not Windows device names on
  Windows), as file.installScript requires, so every installed panel is listed
  and can be uninstalled.
- A panel whose name differs from an installed one only in case replaced it on
  Windows and macOS: compare ignoring case and check the file on disk too.
- The loaded plug-in's source is the installed copy, not the file it came from.
- Document the `bundle` menu parameter, so checked execution accepts it.

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

* plugins: drop needless to_vec in the bundle install test (clippy)

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

* plugins: keep the loader test's byte vectors, drop to_vec in the install test (clippy)

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

---------

Co-authored-by: STAR Web Studio <hello.starwebstudio@gmail.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 11:35:54 +01:00
611a282bea Plug-ins: external effect helpers (safe, out-of-process native effects) + plug-in API 2 (#889)
* Plug-ins: API 2 (PluginHost, padding, layer_span)

Split out of the OpenFX work so it can be reviewed on its own. Additive; the WebAssembly ABI
stays at 1.

- point3 / layer / text / hidden parameters, integer sliders, parameter groups.
- EffectPlugin::padding (transparent pixels around the buffer, at most 4096 per side).
- EffectPlugin::render_v2 with a PluginHost: the input at other times, other layers, parameters
  at other times, frame rate, per-instance key, the layer's source range.
- EffectEnv::layer_span from the renderer; MAX_PLUGINS 1024.
- plugin_error: a plug-in effect's last render error, for the UI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxacwTpUwqyfpcLgKW8yjS

* Plug-ins: external effect helpers (crates/helper-host)

Native plug-in formats run in separate helper programs, never in EffectCraft's process
(docs/adr/0001-external-effect-helpers.md). New safe-Rust crate effectcraft-helper-host (L3,
native only, unsafe forbidden): it finds helper programs, starts each as a child process and
registers its effects through plug-in API 2, so the UI and command surface need nothing new.

- Protocol v1 (docs/external-helpers.md): JSON lines over stdin / stdout (helper replies are
  prefixed so plug-in chatter is ignored); pixels go through a frame exchange file (/dev/shm on
  Linux), never through the pipe. Render-time callbacks for the input at other times, other layers
  and parameters at other times, with nested renders through the same helper.
- A helper that exits, hangs past its timeout or breaks the protocol is stopped. The render fails
  with a message, the frame is left as it was, and the helper restarts for the next render. An
  effect that stops its helper 3 times is turned off (helper_blocklist.txt).
- Opt-in: nothing starts helpers unless the user asks (Effect > Scan for Effect Helpers), or opts
  in to starting them at launch with EFFECTCRAFT_HELPERS=1, an environment variable, so writing
  to the settings folder alone can't run code. effect.helpers.scan / load are refused for
  confined agent sessions (confine.rs), like file.executeFile.
- Discovery: helpers/ next to the app (Contents/Helpers on macOS), helpers/ in the settings
  folder, EFFECTCRAFT_HELPER_PATH.
- Commands effect.helpers.list / scan / load; Effect menu entries (translated); the last render
  error of an effect shows as a warning sign after its name in Effect Controls.
- serve(): the helper side for Rust helpers. tests/it/main.rs runs the whole protocol (render,
  padding, callbacks, nesting, threads, crash, restart, block list, hang) against a fake helper,
  with nothing to install.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxacwTpUwqyfpcLgKW8yjS

* Effect helpers: address review (start failures, frame file mode, nextest listing)

- A helper that can't start (not installed any more, restarting too often, no frame exchange
  file) is reported but no longer charged to the effect being rendered: it is Failure::Refused,
  not Failure::Broken, so it never counts toward turning that effect off. New check in
  tests/it: once the helper is out of restarts, renders of an effect fail without blocking it
  (fails without the fix: the effect was turned off after 3 such renders).
- The frame exchange file is created owner-only (0o600) on Unix: it holds the user's frames and
  /dev/shm is shared. Asserted in the frames unit test.
- tests/it (harness = false) speaks the libtest listing protocol cargo-nextest uses:
  `--list --format terse` prints `helper_protocol: test` (nothing with `--ignored`), and
  `helper_protocol --exact` runs the checks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxacwTpUwqyfpcLgKW8yjS

* Effect helpers: bound writes to a helper, drain stderr, keep callbacks in step

- Writes to a helper go through a writer thread with the request timeout: a
  helper that stops reading its stdin (hung with a large callback answer or
  render request pending) no longer blocks the render thread forever while
  holding the helper.
- stderr is drained as bytes (lossy, in 64 KiB pieces) to the end: output that
  isn't UTF-8 stopped the reader, closing the pipe under the helper (EPIPE /
  SIGPIPE on its next write); lines were unbounded.
- Callback images that can't be written are answered with `null` instead of
  failing the render while the helper waits (which left the protocol out of
  step); a frame that can't be written before the request is sent is not
  charged to the effect.
- Mid-render calls use the render's own process: no respawn or heartbeat ping
  while the helper is waiting for an answer.
- Waiting for a busy helper is bounded (load timeout) instead of forever;
  status() no longer blocks on a running render.
- Reap the child when start-up fails after spawning; cap the block list read.
- Scripts start helpers only with Allow Scripts to Write Files and Access
  Network on (they run native code), as for File.execute().
- helper-host declares the workspace's no-panic crate lints; the desktop
  main() keeps its doc comment.

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

* Effect helpers: rustfmt the fake provider, build the test session in its initializer (clippy)

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

* Plug-in Library: offer WebAssembly API 1 releases only; a helper already stopped mid-render isn't charged again

- WasmPlugin loads only modules at the WebAssembly ABI version, which stays 1 while
  PLUGIN_API_VERSION is 2: share it as effects::plugin::WASM_API_VERSION and have the
  library's compatibility check use it, so it never installs an API 2 release that
  can't load.
- with_running: a process that is already gone was stopped by what broke it (a nested
  render, charged there), so this render fails without charging its own effect.

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

* frames: plug-in effects count as reading other frames (plug-in API 2)

With main's narrowed frame invalidation (#2795), an edit inside a precomp under an
effect that reads its layer at other times must reach the layer's whole span.
Plug-in API 2 gives every plug-in effect the input at other times, so any effect
outside ec. counts.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-05 11:11:57 +01:00
4e8398f8ca G8: Plug-in Library: install, preview and update community WebAssembly effects (#2524)
* G8: plug-in library

Community Plug-in Library for WebAssembly effect plug-ins (#376): one
reviewed catalog (index.json), install / update / remove from Settings >
Plug-in Library (also Effect > Plug-in Library...), library.* commands,
start-up loading of installed packages with SHA-256 checks. Downloads go
through the system curl (https, GitHub hosts), as trained models do.

Preview before installing (library.preview): the release is downloaded,
checked like an install, and rendered in the sandbox on a built-in sample
frame at default settings (2 s at 12 fps) with a Before/After split, plus
the effect's parameters, "Open on GitHub" and an optional author video
link (catalog field preview.video). Authors need to do nothing for it.

Hardening: a module that fails to load leaves the installed copy alone,
Update All updates every package in one job, minApp also applies to an
explicit version, curl ignores ~/.curlrc and URL globbing, unique temp
files, percent-decoded file URLs. Plug-in effect slots grow from 64 to
256 with a clearer overflow error.

Docs: plugins.md "Plug-in Library", publishing-plugins.md (author guide
from zero to listed), gaps.md; examples/plugin-catalog is the starting
content of the catalog repository (schema, validation workflow).

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

* contributors: credit iseemhlyfta

* G8: plug-in library: doc comments on the library's functions

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

* G8: plug-in catalog check: require one object with schema 1 and a packages array

An empty file or several JSON values passed jq empty, and with
SKIP_SCHEMA=1 nothing else checked the catalog's shape.

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

* G8: plug-in library: one preview texture set; catalog schema requires effects

The preview kept a texture set per package version previewed (about
6 MB each) for the whole session; it now keeps one, replaced when
another package is previewed. Releases in the catalog schema must list
at least one effect id, as the author guide says.

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

* G8: plug-in catalog schema: effect ids follow the manifest's id rules

Letters, digits, '.', '_' and '-', and not the reserved 'ec.' prefix,
so library.missing can match them.

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

* G8: refuse Plug-in Library jobs while automation file access is confined

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

* G8: split the Plug-in Library page into toolbar, card, links and actions

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

* G8: show Plug-in Library toasts without a Roto job; cache preview frame checks

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

* G8: log Plug-in Library packages that fail to load at start-up

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

* G8: check an update before replacing a loaded package; Update All past failures

- An update to a package that is already loaded is now compiled and its manifest checked
  (through the previewer, without registering it) before the new file is written and the old
  version's folder deleted. Before, a release with the right SHA-256 that didn't load replaced
  the working version and failed at the next start.
- Package ids that differ only in case, or end in a dot, would share one folder on Windows and
  macOS: the catalog keeps the first of them, and ids can't end in a dot (schema too).
- Update All: a package that can't be picked or installed no longer stops the others; the
  result names what was updated and what wasn't.
- Downloads: at most 5 redirects, and one that ends off GitHub's hosts is refused, as the module
  docs say.
- A job that panics no longer leaves the library busy; poisoned status locks are used, not
  dropped.

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

* G8: rustfmt the update test

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-05 05:51:17 +01:00
Valentin Klein bb78d55825 After Effects project import (.aep / .aepx); export follows (#2558)
Add After Effects project import with bounded parsing, explicit loss reports, and regression coverage.

Fetch the cleaned external test corpus at commit 97262276f625afdeeb61787e4cbb74b3f17af510 and verify each fixture against the pinned SHA-256 manifest.
2026-11-04 12:06:08 +01:00
vangel76 06438a1b9f Merge remote-tracking branch 'upstream/main' into HEAD 2026-11-04 00:14:35 +01:00
vangel76andClaude Sonnet 5.5 653157532b Merge upstream/main into fix/exr-scene-linear-output
Conflict: crates/gpu/src/tests.rs (both sides added a test; kept both).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-04 00:04:11 +01:00
gmrmad1andFredApps 389894e69b gpu: run Content-Aware Fill's PatchMatch and flow on the GPU (#1856)
* gpu: run Content-Aware Fill's PatchMatch on the device

Content-Aware Fill's synthesis is the stage that makes the tool slow: at
1920×1080 with a 300×200 hole, PatchMatch costs ≈ 1.1 s per frame on the CPU,
and it is what Object, Surface and every refining frame spend their time in.

Add a `FillBackend` trait to `effectcraft-raster` whose stages (the membrane
solve, the flow fields, PatchMatch) default to the CPU reference, and
`Accelerator::inpaint()` on the compositor, which is how the engine asks for a
device backend. `effectcraft_gpu::inpaint` implements PatchMatch: the
multi-scale pyramid, the EM rounds, the propagation (a two-pass checkerboard
instead of the CPU's scan order), the random search, the SSD, the
histogram-based σ² and the weighted vote, as compute kernels in
`shaders/inpaint.wgsl`.

The fill's frames are read back to the CPU before it runs (analyses composite
on the CPU by design), so the backend is about the fill's own work; the
orchestration, the flow-guided propagation and the per-frame flow control stay
on the CPU. Only the hole's bounds cross back to the CPU, which is what makes
the offload pay.

Measured on an Intel Arc (MTL) iGPU, 1920×1080, 300×200 hole:

  CPU 1087 ms   GPU 397 ms   (2.7×, 41.0 dB PSNR against the CPU's synthesis)

The membrane (harmonic) solve deliberately stays on the CPU: the same
measurement has it at ≈ 90 ms on the device against ≈ 55 ms on the CPU,
because a fill's full-frame data movement (upload, the known pyramid, a copy)
does not shrink with the hole, while the relaxation does. The device takes only
the stage where its parallel search pays; `membrane_tex` still runs on the
device inside PatchMatch, where it is the coarsest level's start and never
crosses back.

Closes #1839.

* gpu: fix the fill's vote histogram and run its flow on the device

Review follow-ups on the Content-Aware Fill GPU port, plus the fill's flow.

Fixes:

* imp_hist kept about one sample in 256: each of the 256 threads scanned a
  strided subset of the pixels and retained only those whose bin selected it,
  so the vote's sigma² came from a sample correlated with the pixel index (and
  a small hole usually fell back to 1.0, making the weights near-uniform). One
  workgroup now accumulates the whole population through shared atomic bins,
  with a test that pins the sample count.
* imp_pm_prop and imp_pm_rand were dispatched over grow(near, 1) but with
  near's origin, so the ring's left column and top row were never written
  (NaN under test, stale pooled data in production) while a strip past it was.
  `over` now derives the shader's origin from the box it sizes the dispatch
  from, so a dispatch's region and its origin can no longer disagree.
* Gpu::inpaint() requires its kernels to have built. Otherwise every stage
  paid an upload and a failed dispatch to fall back to the CPU, while the fill
  still reported the GPU path.
* imp_rnd's XOR chain parenthesises its multiplicative terms: WGSL's grammar
  makes each XOR operand a unary expression, so `a * b ^ c * d` is rejected
  by spec-compliant parsers (naga accepts it).
* FillBackend documents that its stages may be called concurrently.
* The coarsest membrane's sweep count (64, against the CPU's 200) is now
  justified: matching 200 measured ~6% slower over the fill with no change in
  the result.

And the flow: Gpu::flow runs block_flow's coarse-to-fine search, sub-pixel fit
and vector median on the device. The luma is the CPU's (flow::luma_pair), so
the field matches block_flow's to a float rounding and the propagation sees
the same motion; the hole completion stays on the CPU with the membrane. A
four-frame 1920x1080 fill goes from 1.27x to 1.42x against the CPU, and the
device path is covered for holes from 16x16 to 300x200.

* gpu: keep the fill vote's histogram range on HDR input

imp_hist binned every distance at a fixed 256 bins per unit and clamped to
1023, so the vote's sigma2 topped out at 3.998. A 32-bpc frame's channels can
exceed 1, and masked_frame forwards them unclamped: a patch distance of 1600
then reported sigma2 ~ 4, exp(-1600/(2*sigma2)) rounded to zero in f32, and
imp_pm_vote kept the prior image instead of synthesising.

The histogram now reports the largest finite distance it saw (atomicMax on the
float bits, carried in bin 0's unused .g channel). SDR input keeps the fixed
0..4 range with identical bins; HDR input is re-binned over the observed range
and percentile() maps the bin centre back through it. Nothing clamps the input.

Also group over/sweeps/pm_level's arguments into small structs: the workspace
denies too_many_arguments and the lint policy (#2496) does not allow new
#[allow]s. No behaviour change on those paths.

* engine: don't report Edge Blend fills as device fills

Edge Blend calls only the membrane, which the GPU backend leaves to the CPU
reference, so run_plan's "Filling (GPU)" status was wrong for it whenever a
backend was present. Object and Surface still run the flow and PatchMatch on
the device and keep the label.

CodeRabbit's #1856 finding.

* gpu: run the fill's device stages inside error scopes

An allocation or validation failure inside patch_image/flow_image left an
invalid texture whose later use raised an uncaptured error, retiring the
shared compositor instead of falling back to the CPU for that frame. Wrap both
stages in Gpu::within_memory, whose scopes are native thread-local, so the
fill's rayon workers do not share them.

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-03 22:24:42 +01:00
vangel76 e3ab397a7e Merge remote-tracking branch 'origin/perf/exr-decode-cache-474' into m2929-merge 2026-11-03 19:20:26 +01:00
vangel76andClaude Sonnet 5.5 2b3d76c5fd Merge upstream/main into perf/exr-decode-cache-474
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-03 19:18:12 +01:00
vangel76 4aaf9c4bb4 Merge remote-tracking branch 'upstream/main' into m2915-merge 2026-11-03 19:08:37 +01:00
Brandon ThomasandClaude Opus 5.5 d16c720e59 Follow-up to #2981: a missing Chinese effect row draws English instead of failing the build, the font probe sees effect and category characters, coverage totals recounted
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 09:52:40 -05:00
Brandon Thomas 3ae9c8dbc2 Merge PR #2981 2026-11-03 09:41:24 -05:00
SHXJ b77db389af #2037: complete the Traditional Chinese effect-name catalog
Add 308 Traditional Chinese effect names and 21 category labels, written from the English source with Taiwan creative-software terms. Update the completeness tests to require the zh-hant table and recount the localization docs.
2026-11-03 22:34:55 +08:00
Brandon Thomas f5e81cd1f9 Merge PR #2957 2026-11-03 09:08:08 -05:00
Brandon Thomas 6334d07fe1 Merge PR #2872
# Conflicts:
#	apps/effectcraft-cli/tests/it/cli.rs
2026-11-03 09:05:01 -05:00
Brandon Thomas 3170c0e34f Merge PR #2905 2026-11-03 09:04:45 -05:00
Matej Kozlovský 0d1c5c08bf Docs: the Cryptomatte Selection is picked in the viewer (#473)
effects-parity.md and gaps.md: the Selection is picked in the viewer. control-protocol.md: the
viewer.cryptoPick element ids. footage.md: a paragraph on the eyedropper.
2026-11-03 07:37:53 +01: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
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
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 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
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 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
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
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
JonRC a2f6cd3ed5 3D Camera Tracker: a partial solve is not reported as solved (#2822)
camera.solveStatus said `solved: true` for any stored solve, but the solver
keeps the frames it cannot resect as unsolved and interpolates the camera
there. `solved` now needs every frame, the same check camera.exportBlender
makes, and `partial` marks a solve with gaps, next to `solvedFrames`.
2026-11-02 09:15:05 -03:00
Brandon Thomas a658ec736a Merge PR #1266 2026-11-01 21:50:53 -05:00
Brandon Thomas 83b4e36e9b Merge PR #2343
# Conflicts:
#	crates/ui-egui/src/i18n/effects.rs
#	crates/ui-egui/src/i18n/ui.rs
2026-10-31 10:55:46 -04:00
Brandon Thomas d3001efade Merge PR #2731 2026-10-31 10:54:52 -04:00
Brandon Thomas 738f9a3055 Merge PR #1541 2026-10-31 10:54:51 -04:00
Brandon Thomas d3897efd86 Merge PR #2719 2026-10-31 10:54:51 -04:00
dotCooCoo 1fe1058d34 Scripting: Offset Paths and Zig Zag properties resolve by their match names
Offset Paths' Amount, Line Join, Miter Limit, Copies and Copy Offset,
and Zig Zag's Size, Ridges per segment and Points reported their
internal ids as matchName, so property("ADBE Vector Offset Amount") and
the other documented names returned null. They now report and resolve
by ADBE Vector Offset Amount, Line Join, Miter Limit, Copies and Copy
Offset, and ADBE Vector Zigzag Size, Detail and Points. Lookups by
display name and internal id still work.
2026-10-31 02:01:29 -07:00
adikshan11andClaude Opus 5.5 45b737455a automation: a confined project can't show footage from outside the roots (#2131 F-11, "Indirect reads")
The command checks look at the paths an agent gives, but a project it opens or
imports from a read root names its own footage, which was read wherever it is
when the project rendered: render_frame returned its pixels.

Backend::confine now wraps a headless session's footage and services:
ConfinedFootage reads frames, audio, aux channels, models and vector frames
only for footage inside the read roots (problem() says why the rest shows
nothing), and ConfinedServices checks read_file, exists, stamp, list_dir and
is_dir the same way. Writes are left to the command checks.

Test: automation_roots_confine_the_footage_a_project_names covers an absolute
path, `../`, a linked file and a linked folder, each opened and imported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 11:16:36 +05:30
aiongg 23a38b6627 docs: tidy wording 2026-10-31 04:25:49 +00:00
aiongg d4ddedddaf docs: tidy agent, contributor and asset docs 2026-10-31 04:17:22 +00:00