mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-08 11:31:25 +00:00
dbcae60e1911adb286b28b0f77e99d345ac1be39
952
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dbcae60e19 |
Release: refresh contributor credits for each release (#3189) (#3190)
About > Contributors still showed an old snapshot, because nothing regenerated contributors/contributors.json before a release. Opt in to the shared refresh from craft-common-actions: a read-only credits job regenerates the snapshot for the release commit, and the shared release workflow installs it before every source build. The snapshot is not committed back. |
||
|
|
84382487bb |
Disk cache: entries keep the files they read, keys from memory (#3184) (#3191)
* Disk cache: record the source files each frame reads Part of #3184 (step 1): renders now report which footage files they read, each with its identity (size and modification time) as read. Nothing uses the list yet: images, cache keys and cache behaviour are unchanged. - render: `reads` module with `FileStamp`, `ReadFile`, `ReadFiles` (sorted, deduplicated, bounded; marked incomplete when it can't vouch for every file) and the thread-safe `ReadLog`. `Renderer::reads` collects a frame's files; `FootageSource::recording` lets a source note what it reads. `file_stamp` uses the same identity. - Layer cache entries keep the files their buffer was made of and report them on every hit; derived entries (styles, blend space, flattened) keep their base's. Buffers from the disk cache or the browser store don't know their files yet and mark the list incomplete. - Media pool: every file read notes its identity, taken from the file it opens. Decoded frames, opened movies, GIFs, models and EXR channels keep it with the cache entry, so a frame served from memory reports its file without touching the disk. - Viewer frame worker collects each frame's list and carries it to where the disk entry is written (step 2 stores it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Automation: confined footage passes on the files it reads Part of #3184: ConfinedFootage forwards FootageSource::recording to the source it wraps, so renders of an automation session list their files instead of coming back incomplete. The read tests also render through the viewer's CancellableFootage wrapper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Disk cache: frame entries keep their source files, comp key from memory Part of #3184 (step 2). Fixes #3175: opening a comp no longer stamps every footage file of the project before its first frame can show. - The comp's content key (`disk_cache::comp_content_key`) is taken from the project in memory alone: no footage or proxy file is stamped, no `VirtualFileId` is asked. `comp_content_key_with` is gone; `frame_key` hashes a new entry version, so entries of the earlier format are never looked up again (LRU evicts them). - Frame entries (formats 2 and 3) store the files the frame read (`EntryFiles`: size and modification time as read; size and content hash through `VirtualFileId` for files from memory; missing files), then the pixels. Formats 0 and 1 read as misses and are deleted. - Check on hit: `get_frame`/`get_frame_f16` look only at the listed files (one metadata call each) on the frame worker. A changed, back or unreadable file, or a damaged entry, is a miss: the entry is deleted and the frame rendered and stored again. - A frame whose file list is incomplete is shown but not stored. The RAM preview keeps each frame's files, so `persist_missing` writes only frames with a complete list. - Remote frame worker: `FrameServer` records each frame's reads (`take_files`); the web worker stores them with the entry and the page checks them through the file table before using it. The browser's media epoch existed only for the old key and is removed. Export does not use frame entries. - The cache indicator still only asks whether an entry exists. Tests: entry format round trip and damage, the check per file, and in the viewer: a changed file re-renders exactly its frames (and a reopen reads the rest from disk); a frame checks only its own files in a project with a large unused sequence and the key looks at no file; missing, unreadable files and damaged entries never serve a stale frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Disk cache: layer entries keep their source files, no footage salt Part of #3184 (step 3 of 4). Layer buffers on disk (and in the browser's layer store) now carry the files they were made of, like frame entries since step 2, and are checked against exactly those files when used. The project-wide footage salt is gone: nothing stamps every footage file any more. - Layer entry format 4: the buffer's EntryFiles, then the pixels. A hit checks only the listed files (metadata, or the VirtualFileId hook for files from memory; Missing must still be missing); a change, a damaged entry or the old format 0 is a miss and the entry is deleted, so the re-rendered buffer replaces it. A hit reports its file list, so frames made of buffers from disk are complete and get stored again. - Buffers whose files can't all be named are not written. - Layer disk keys: layer_disk_key = hash of the layer entry version, the renderer version and the layer cache key (which names the footage items with their paths and settings, from memory). - PrefetchStore checks on use; an entry made of changed files is dropped and forgotten, so the new buffer is queued over it. FrameServer passes its virtual-file hook to its layer cache. - Removed: footage_salt(_with), file_stamp, footage_stamp, set_disk_salt, set_store_salt, Session::refresh_disk_salt, poll_disk_salt, footage_fingerprint, disk_salt, disk_salt_background, disk_salt_job, DiskSaltJob, the UI's per-frame poll, the salt in FrameServer and in the availability worker (with its cache_enabled plumbing). The disk cache is attached as soon as it is configured. - Docs: architecture.md, web.md. Tests: layer entries check their files (size, time, missing/back, folder, damaged, old format, incomplete); virtual files; prefetch store; media tests with a real pool across restarts (only the changed file is read, projects with the same item ids never share an entry, missing, unreadable and damaged never serve stale buffers); engine tests that the cache is attached at once and edits do no file work (#1926, #3106). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Disk cache: no-salt worker test, sequence paths in the frame-blend key Part of #3184 (step 3 of 4). - A frame-blended footage frame's layer-cache key now hashes the sequence's file list: with no project-wide salt, two projects whose items share an id and a first frame must not share its disk entry. - Test: a frame worker syncing, patching and rendering a project with an unused 5000-frame sequence asks its file hook only about the file its buffer reads (the salt asked about every file at every sync). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Disk cache: the comp key covers what its expressions can read With an expression anywhere in the project, every comp's disk key hashed every item of the project, so any edit (another comp, an unused footage item) threw away every comp's frames on disk. The key now covers what the comp can draw plus what the expressions among it can reach (Project::pixel_reach): - without expressions: the items the comp draws, without silent layers, plus the footage its precomp layers swap in (Media Replacement); - with expressions: every layer of the comps they reach, their markers, the comps and footage they name with string literals (comp("Name"), footage("Name")) together with which items have those names now, and the footage named like a reached source (its data is read by name); - the whole project when an expression computes a name, uses the object model's constructors or internals, dynamic code, computed keys or writes, or text the scanner can't read for sure (regex literals, template strings, escapes): expression_reach errs towards reaching more. Markers count only where an expression can read them (#1083). Memory only, no file is looked at. Tests: scanner and reach unit tests in the project crate; engine tests for the key (own-comp expressions keep the key through edits elsewhere, comp("Other") follows Other and not a third comp, a second comp taking the name counts, a computed name counts every item, markers read from another comp); a ui-egui test that the disk frames outside an expression layer are carried when the comp it names changes. Part of #3184. Part of #1492. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Disk cache: Reload Footage drops the entries of its files, docs File > Reload Footage now deletes the frame and layer entries whose file lists name the reloaded item's files (DiskCache::drop_files), so a file replaced by one with the same size and modification time, which the check on hit can't tell apart, is read again. The index knows keys, not files: the writer thread reads the files section at the start of each frame and layer entry (not the pixels) after the writes queued before it, and deletes the entries that list one of the files. Until it is done, an entry made of one of them is a miss and frames aren't linked to new keys. The entries are gone for good, so they don't come back after a restart. Automatic reloads don't drop anything: they follow a changed stamp, which the check on hit catches. The browser needs nothing (its file identities are content hashes). Removed: disk_cache::seal, which steps 2 and 3 left without callers (the tests use seal_format). Updated the module, seal_format and forget_media docs, and docs/architecture.md / docs/web.md describe the final design: what is cached, keys from memory, file lists checked on hit, nothing scans the project, incomplete lists stay in memory, Reload Footage drops entries, edits keep the frames they can't change. Tests: render unit tests (same size and time passes the check but not the drop; other files' entries stay; an entry queued before the drop is dropped; a layer entry too; written again afterwards it is used; the drop survives a restart; a pending drop is a miss), and an engine test through file.reloadFootage. Closes #3184. Part of #1492. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Disk cache: file stamps keep the whole modification time A file's modification time was nanoseconds since 1970 as u64: a time before 1970 read as "no time" (two such files of one size looked alike) and times after 2554 were truncated. It is now i128 nanoseconds, negative before the epoch; None only means the file system has no time. The content-key hash writes a presence tag and the full value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Automation: a refused footage read leaves the render's file list incomplete Footage outside the read roots shows nothing and was noted as no file at all, so a picture made without it had a complete, empty list and could be kept on disk for a later session with other roots. A refused read during `recording` now marks the list incomplete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Render: a buffer kept by another worker comes with its own files LayerCache::insert returns the files of the buffer it keeps, and Renderer::keep notes them in the render's log and returns them, so a render that uses another worker's buffer lists that buffer's files (a file changed between the two reads then has two identities and the list is incomplete). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Render: sampleImage and text fonts note the files they read EvalCtx carries the render's read log. A sampleImage render records its footage reads, keeps them with its per-thread cached buffer and notes them in the evaluating render's log on every use. Text notes the font files it is drawn with, with the identity their bytes were read with; a family that isn't installed, a fallback or missing glyph, or a font from memory leaves the list incomplete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Disk cache: frame entries store the whole modification time A frame entry's file list stores a file's modification time as i128 nanoseconds since 1970 (negative before), as the file stamps now keep it; the frame entry format version goes to frame-entry-files-2 so entries in the old layout are not read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Disk cache: layer entries store the whole modification time Layer entries share the frame entries' file list layout, which now stores the full i128 modification time: the layer entry format version goes to layer-entry-files-2. The module doc names the font files text is drawn with among an entry's source files, and what keeps a list incomplete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Layer cache: buffers that read other layers are keyed by the switches passed down A layer whose effect reads another layer (Gradient Wipe, Set Matte, Blend…) gets that layer drawn by its own renderer. Through a precomp layer, Switches Affect Nested Comps, the switches inherited from the precomp layers above and motion blur on change those pixels, but the key held only the layers' own switches: a buffer made with the setting on was reused (from memory or the disk cache) with it off. Such layers now derive their key with what the renderer passes down. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Layer cache: masked buffers are keyed by the mask motion blur gate A mask with Mask Motion Blur On blurs even when the layer's own switch is off, as long as the render's gate is open (Render Settings motion blur, and with Switches Affect Nested Comps the precomp layers above). The key held the layer's own motion blur state only, so a buffer blurred with the gate open was reused with it closed (and the other way round), also from the disk cache. Masked layer, input and footprint keys now tell a closed gate apart. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Render: cached buffers list what their expressions and substituted characters read - Buffers made under `Renderer::recorded` (layer content, a Time effect's input, an adjustment layer's footprint) evaluate their masks, shapes and effects with the context that notes into the buffer's own read log, so a file an expression reads (`sampleImage`, also at shutter times mask motion blur adds and the cache key doesn't see) is in the buffer's files. - A styled layer served from the cache lists the files of the cached body and passes too, so the buffers made of it (flattened, blending space) don't get a complete list when one of them came with an incomplete one. - A character put in by Character Offset or Character Value without a relayout notes the font it is drawn with; a fallback face (or none) leaves the list incomplete. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Render: text lists the font file of a run that draws no glyph; tests clean their temp dirs An empty first paragraph draws no glyph, but in a box its face sets the first baseline that places every later line. The read list took faces only from glyphs, so it could be complete without that file. Note each style run's resolved face as well. The font-reads and confine tests use tempfile::tempdir(): a fresh directory per run, removed on panic too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Disk cache: Reload Footage closes the gaps around its drop (#3184) - A reload while the disk cache is off is kept on the session (pending_disk_drops) and applied when the cache is switched on, before anything is read from it. - Each render takes the cache's drop generation before it reads (frame worker; layer cache at the miss). An entry listing a file dropped after that is refused under the queue's lock, so a render that was under way during the reload can't store pixels from before it. Such a frame is shown with an incomplete file list, so persist_missing skips it too. The per-file record is bounded (past it, everything older is refused). - An entry read while its drop was pending is a miss even when the writer ends the sweep between the read and the check (a stale hit found under load): the pending drops and the generation are taken before the entry is read. - Test: a reopened project serves its frame from disk with no source file opened or decoded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Disk cache: RAM frames keep the drop generation their render started at Reload Footage empties the RAM preview, but an idle pass of persist_missing may already hold a frame from before the reload. It took the cache's drop generation when it ran, after the drop, so the old pixels could be written back and, with the file's size and time unchanged, served again after a restart. Each RAM frame now keeps the disk cache its render started with and that cache's drop generation then. persist_missing writes a frame only to that cache, with that generation, and skips it once one of its files was dropped since; a frame rendered without that cache is not written from there. Tests use tempfile directories (the layer drop test, the frame_files folders under target/). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Doc comments for the functions this PR touches * Disk cache: cap the free-space reserve at 10 GB, skip encodes the limit refuses The reserve the cache leaves free on its disk was max(2 GB, 5% of the disk) (#2551). On a 4 TB disk with 190 GB free that is 200 GB, so a cache set to 20 GB had a limit of 0: every write was refused, the cache never held anything, and only a log warning said so. The reserve is now clamp(5% of the disk, 2 GB, 10 GB). It is there to keep the cache from filling the disk, not to keep a share of a large disk free: 10 GB leaves the system room to work on any disk, a small disk still keeps 2 GB, and a 100 GB disk keeps 5 GB as before. After Effects has only a maximum cache size and no reserve; a clamp keeps the floor for small disks, where a fixed 10 GB would take most of the space. Writes the limit refuses cost no work: DiskCache::accepts_writes (one atomic read) is checked before a frame is converted (put_disk_frame) and before frames and layers are compressed (put_frame, put_frame_f16, put_layer). The frame workers spent about 8 ms per frame and 14 ms per layer on encodes that were then refused. persist_missing returns at once at limit 0 instead of converting the RAM frames on every idle pass. Settings > Disk shows the limit in effect in MB under Maximum Disk Cache Size, with a note when free disk space lowers it (settings.disk.cacheLimit). * Disk cache: refuse writes below 64 MB of room, show this disk's reserve Review follow-ups to the reserve fix: - accepts_writes also refuses while free disk space leaves less than 64 MB of room (a limit the setting asks for is always used): a few frames would fill it, and the rest would be compressed only to be refused. - While writes are refused the idle passes ask for no repaint, so the UI keeps a frame coming every 5 s: the free space is read again and the RAM frames are written once there is room. - Settings > Disk names the space kept free on this disk (the reserve the last reading applied; the rule from MIN/MAX_DISK_RESERVE when it is unknown), and the "no room" note follows accepts_writes. - The settings.disk.cacheLimit automation label is machine-readable and the same in every language: limitMb, maxMb, reserveMb, spaceLimited, writes. - cargo fmt. * Disk cache: follow 3D model sources in the comp's reach The reach walk followed comp, footage and solid sources but not LayerSource::Model. An expression reading a model layer's source (thisComp.layer("Model").source.name) left the model item out of the comp's content key, so renaming the model kept the key and the old frames on disk were served. Every source item is followed now (LayerSource::item), which also counts the model when no expression reads it (Advanced 3D draws it). The same omission is fixed in the RAM preview's content walk (frames.rs) and in the layer key's walk of referenced layers (cache/dependencies.rs). * Disk cache: a render's drop generation travels with its reads; no disk entries for Expression Selectors Drop generation per render: the layer cache recovered a buffer's drop generation from the latest miss of its key, so a second render of the key that started after File > Reload Footage lent its generation to a render still running from before, and that render's buffer (the file as it was) was written after the sweep. Now Renderer::recorded starts each buffer's read log at the store's drop generation before anything is read (ReadLog::starting_at); ReadFiles carries the earliest generation through merges, so derived buffers (styles, the blend-space copy, the flattened layer) and frames that use a buffer from memory carry it too. LayerCache::insert keeps a buffer made of a file dropped since its reads began neither on disk nor in memory; a list that doesn't say counts as made before every drop. DiskCache::dropped_since takes the list's own generation too. missed_at keeps only the miss time. Expression Selectors: the layer key evaluates a text Expression Selector's amount for the first character only, so a per-character dependency such as textIndex == 1 ? 0 : footage("ref.png").width was not in the key, and after the footage item's width changed a reopened session reused the old layer entry. A text layer with an Expression Selector now marks its reads unkeyed (ReadFiles::is_unkeyed), which merges like the generation; such buffers stay in memory and are never written to disk. Frame entries are unaffected: the comp's frame key hashes what the expressions name. The equality and hash of ReadFiles ignore both new fields. * Disk cache: never serve a RAM layer buffer made before a drop of its files A render that passed the drop check could sit in put_layer's encode while Reload Footage cleared the layer cache and dropped the file, then publish its old pixels to memory after the purge; get, and a fresh render's insert, then served them. The disk-hit promotion went through the same insert_mem. Every way the cache hands out a RAM buffer (get, the kept-first path of insert, an existing entry in insert_mem) now checks the entry's files against the store's drops from their own generation (ReadFiles::since): an entry made before a drop of one of its files is forgotten and the lookup misses, so a fresh render's buffer replaces it. insert_mem also re-checks a buffer just before publishing it. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
514e16c027 |
.aep import: effects without definitions borrow them from another instance (#3223)
Refs #3170 (the Color Control part). Some files have no project-level effect definition list (EfdG); it was seen in 17 projects, all from inside After Effects-made Motion Graphics templates. In those, one instance per effect type keeps the full parameter definitions (parT) and every other instance of the type has an empty parT. The others were reported "without readable parameter definitions" and dropped (129 Color Controls of one template). The definition index now falls back to the parT of the first instance of the same match name that has its own (DefinitionSource::OtherInstance). Values still come from each instance's own records; a value record that borrowed definitions do not name is reported instead of vanishing. The "Color ... holds an unreadable value" report came from 0.21.1, which read only written records (tdsb 1). Reading default-state records (tdsb 3, #3126) probably fixes it, but that is not confirmed for Color Control; a synthetic test now pins a default-state colour record. |
||
|
|
99e24f5867 |
.aep import: image sequences without a filename prefix or with Force alphabetical order (#3213)
* AEP import: read image sequences without a filename prefix and with Force alphabetical order (#3174) After Effects leaves out an empty filename prefix, so only one Utf8 (the suffix, such as `.png`) follows the alias. The single part is now placed by the first file name stored in `opti`: a name starting with it has it as the prefix, one ending with it as the suffix; without that evidence a part starting with `.` is the suffix and anything else is not read. A sequence imported with Force alphabetical order stores its files in `LIST StVc` (`StVS` with the count as a little-endian u32, then one Utf8 per file name) instead of prefix and suffix; that list now replaces the numbering. A list with a name that is not a plain file name (a separator, a colon, `.` or `..`) is not read, since the names are joined to the folder. A folder alias whose naming still cannot be read is reported as an import loss instead of silently linking the folder. docs/aep-format.md describes the empty prefix and StVc/StVS. * AEP import: a listed sequence file name with a control character such as NUL is not read (#3174) A NUL or another control character in a Force alphabetical order file name now makes the whole list unreadable, like a separator or a dot directory, so the import reports the naming instead of linking files that cannot exist. * AEP import: a sequence file list without a readable count is not read (#3174) After Effects writes the StVS count before the names. A StVc list without StVS, or with one shorter than its u32, is now reported as unreadable naming instead of taking every listed name. * Doc comments for the functions this PR touches |
||
|
|
bb2d2005ce |
Layer: Un-precompose a selected pre-comp layer back into its parent composition (#3081)
* Layer: Un-precompose a selected pre-comp layer back into its parent composition (#2918) layer.unprecompose copies the nested composition's layers into the parent at the pre-comp layer's place with timing and position carried over, and refuses what it can't keep exactly. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Un-precompose: refuse a pre-comp layer with Solo or Guide on A soloed pre-comp layer hides the comp's other layers and a guide layer is left out of renders; the expanded copies carry neither switch, so the picture would change. Listed with the other blockers, in the command description and the agents.md Cookbook entry; the refusal test covers both switches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Un-precompose: warn instead of refusing, only a locked layer stops it A locked pre-comp layer is still refused, with or without `force`. Every other former refusal reason is a warning: without `force`, `layer.unprecompose` returns an error that lists the reasons and changes nothing. `dryRun` returns `{dryRun, warnings}`. With `force` the layer is unpacked in one undo step and the result carries `warnings`. Whatever the pre-comp layer itself carried is dropped, and other layers' parent, track matte and layer-property links to it are removed. Layer > Un-precompose in the UI lists the reasons in a prompt with Unpack Anyway / Cancel (`dialog.unprecompose.force` / `dialog.unprecompose.cancel`). A layer without reasons unpacks at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Un-precompose: warn about expressions that refer to the pre-comp layer An expression on another layer of the comp that names the pre-comp layer would no longer find it once the layer is unpacked. The expression can name it in quotes or call `layer(<index>)` with its index, bare or on thisComp; another comp's comp("…").layer(…) is not counted. Each such property is now listed once as a warning. Like the other warnings, `force` unpacks and `dryRun` lists it. The expressions are left as written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Un-precompose: warn about lost frame sampling and Position expressions, keep markers in order Review follow-up (CodeRabbit on #3081): - A nested comp that preserves a frame rate other than the parent's samples its layers on its own frames, and the expanded copies would lose that. Motion-blurred nested layers under a different shutter would also blur with the parent's shutter. Both are now warnings. - When the pre-comp layer's Position differs from its Anchor Point, the move is added to the stored Position of the unparented nested layers. An unparented layer whose Position, or a separated axis, has an expression may ignore that move, so it is now a warning. - Copied comp markers are sorted into time order with the comp's own. - New tests: a 10 fps nested comp with Preserve Frame Rate in a 30 fps comp, a different shutter, a Position expression with and without a move, and nested matte and layer-value remapping with shifted markers merged in order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Un-precompose: warn when an expression's layer numbers shift; the prompt keeps its target Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Un-precompose: a shorter renumbering warning Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com> |
||
|
|
52bca964f5 |
Image sequences: a gap in the numbering holds the previous frame, as After Effects (#3137)
* Image sequences: a gap in the numbering holds the previous frame, as After Effects
Split out of #1876 (originally
|
||
|
|
719b9170dd |
3D rotation order, keyed Orientation, linear-to-eased keyframes and Timewarp behave as in After Effects (#1876)
* 3D: Orientation and X/Y/Z Rotation turn Z first, keyed Orientation slerps, as After Effects After Effects composes a 3D layer's (and a camera's) Orientation and its X/Y/Z Rotation as Rx * Ry * Rz each, Orientation before the axis rotations; EffectCraft used Rz * Ry * Rx. Measured on synthetic AE 2026 cases: Orientation [10,20,30] (488) was 5.2 % of the frame off and now matches to the anti-aliased edge (0.03 %); X/Y/Z Rotation 10/20/30 (800), Orientation + X Rotation (801) and one-node cameras (804, 805) likewise. Keyed Orientation moves along the shortest arc between the keys (quaternion slerp, eased in time, angles written in [0, 360)): keys [0,0,0] -> [10,20,30] read back [6.2643, 9.3531, 15.2190] half-way where EffectCraft gave [5,10,15]; [0,0,0] -> [60,80,40] linear and eased match AE's read-backs within 0.002 deg. lookAt() returns AE's angles (z axis on the target, x axis level; two AE read-backs within 1e-3 deg). The camera tracker, VR editor, Look At and orbit commands use the new decomposition. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Keyframes: a linear side next to an eased side has 16.67 % influence, as After Effects In a segment that is Bezier on one side and linear on the other, the linear side keeps the segment's average speed, but with the 16.67 % influence After Effects shows for linear keys in the Keyframe Velocity dialog (and reports to scripts), not 33.3 %. Measured on an After Effects 2026 time-remap render (a square at x = 50 + 455 * remap): out of a key at 2 s (3.5, speed 0, influence 50 %) into a linear key at 4 s (0.5), AE plays 3.290 at 2.5 s and 2.657 at 3 s; EffectCraft played 3.308 and 2.629 (8 and 13 px off), now the same square positions to the pixel. LINEAR_SIDE_INFLUENCE is used by evaluation (scalar, per-dimension, paths and spatial), side_ease (Graph Editor, velocity dialog, keyIn/OutTemporalEase) and the Lottie export, so exported easing plays as EffectCraft does. Segments linear on both sides stay straight lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Timewarp: retime the layer's source without the effects above it, as After Effects After Effects' Timewarp reads the layer's source (with its masks) at the retimed times and ignores the effects applied before it: a red solid with a Gradient Ramp and then Timewarp (Speed 25) renders as the red solid in AE 2026 (EXPORTER e2 read-back). EffectCraft retimed the layer with its effects (mean difference 127.5/255 over the comp), now identical (0, CPU and GPU). Echo, Posterize Time and the other time effects keep reading the effects before them, as measured before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Image sequences: a gap in the numbering holds the previous frame, as After Effects After Effects 2026 shows the last existing file across a gap in a sequence's numbering (case 860: files 1, 2, 4, 5 show frame 2 at frame 3; VERIFY read-back). EffectCraft drew colour bars there. The media pool now holds the previous file (Footage::sequence_file_held); colour bars remain only where no file precedes the frame. Missing-frame reports on import are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Review: keyed Orientation uses LINEAR_SIDE_INFLUENCE for linear sides; real NaN assert evaluate_orientation still gave a linear side (and the unreachable missing-key fallback) 1/3 influence while segment_value, side_ease and the Lottie export use LINEAR_SIDE_INFLUENCE (1/6), so a keyed Orientation next to an eased key moved on a different curve than every other property (CodeRabbit on #1876). New test: orientation_linear_side_next_to_an_eased_side_matches_a_scalar (both directions). The always-true NaN assert in keyed_orientation_slerps_as_after_effects now checks that a non-finite key comes back as given; Property::value_at says why the match_id "orientation" routing is safe. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Format keyframe orientation test (cargo fmt) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Move the image-sequence gap change out of this branch The gap change (hold the previous file instead of colour bars) reverses #297's documented behaviour and goes to its own PR, as requested in review. pool.rs, sequence.rs, the footage test and docs/footage.md / docs/agents.md are back to main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Codex <noreply@openai.com> Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com> |
||
|
|
ca1ff9cfc2 |
.aep import: map the Cryptomatte plug-in to EffectCraft's Cryptomatte (#3109)
* AEP import: map the Cryptomatte plug-in to EffectCraft's Cryptomatte The importer reported every instance of the free Cryptomatte plug-in (match name `Cryptomatte`) as a lost effect. It now becomes `ec.3d.cryptomatte`: - CryptomatteData decodes the plug-in's Settings data (its own little-endian layout, documented in docs/aep-format.md 3.7): the Cryptomatte layer name and the selection text, both carried verbatim. - Output maps to Display / Matte Only (Colors -> Colors, Matted RGBA -> Matte, Matte Only -> Matte Only). Matted Colors, Selection Mode when on, keys or expressions and anything else stored are reported, and unreadable data is reported as damaged. - EffectCraft's Cryptomatte gains a Layer Name text parameter. A layer the footage has under exactly that name wins; otherwise the Layer popup decides as before. The CPU, GPU and pick paths share one resolver (AuxChannels::resolve_crypto_layer_named). Without it, plug-in layer names such as Crypto_Material would silently fall back to the first layer. On a real production project this takes the effect losses from 132 to 44. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * AEP import: check Cryptomatte against an After Effects 2026 sample of our own A sample made in After Effects 2026 (plug-in 2.6.6: a fresh effect, Selection Mode on, each Output item, with read-backs and renders) corrected three points of the mapping: - Selection Mode on was dropped silently: After Effects writes it as a default-state record (tdsb 3) holding 1, which the general reader does not take. The Cryptomatte step reads that record itself and reports it. - No Output record is now the plug-in's default Colors, also for an instance that borrows the project's definition (every instance after the first), which was reported as having no reliable default and fell back to EffectCraft's Preview. Keyed or unreadable Output values fall back to Colors too, still reported. - Matted Colors imports as Display Matte (the same matte over the layer's own colours) instead of Preview, still reported as an approximation. docs/aep-format.md 3.7 gets the sampled Output default, the tdsb 3 Selection Mode record and the real fresh Settings data (an empty text of length 1 and its MurmurHash3, twice, not cry1 and zeros); docs/aep-import.md the render comparison with After Effects (Matted RGBA matches; Matte Only differs in alpha; Colors with no layer chosen shows another layer). Tests: the exact fresh Settings bytes, tdsb 3 Selection Mode, borrowed definitions, and an opt-in test against the sample once it is in the corpus (aep/cryptomatte.aep, ignored until the corpus pin includes it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * AEP import: write Cryptomatte's popup items in the test as After Effects does The injected Cryptomatte definition wrote `pdnm` and `fnam` as `LIST` chunks. After Effects writes them as plain chunks whose body is chunks (`NESTED_IDS`), and the reader only looks there, so the tests never read Output's item names: they passed through the fallback for a definition with four items and no `pdnm`, which uses the plug-in's own list. The harness now writes both as After Effects does. New tests check that the injected definition reads back with its items and instance name, that Output is mapped by the item the stored index names (items in another order, case, an unknown item), and that the fallback without a `pdnm` still works. With the old `LIST` form, or with the mapping ignoring the items, the new tests fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * AEP import: check Cryptomatte's layer and selection against presets of our own The After Effects 2026 sample 1400 now has two preset compositions that choose a layer and pick one object (box_red on CryptoObject) or one material (mat_paint on CryptoMaterial). CryptomatteData reads both as they are; the picked tokens are the EXR manifest's hashes, which EffectCraft's selection grammar reads the same as the names. - The u32 at 108 is MurmurHash3 of the selection text with its NUL (three texts). - A unit test pins the 127-byte preset data; the sample test now covers all eight compositions with their layer and selection. - Render check: EffectCraft's matte covers exactly the pixels After Effects draws as selected, edge coverage within 0.0002. docs/aep-format.md 3.7 and docs/aep-import.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cryptomatte import: report damaged Settings and Selection Mode records Review follow-ups for #3109: - CryptomatteData refuses a selection text without its NUL and checks the zero byte at 112 for an empty text too. - A Settings record without its data, and a Selection Mode record whose value cannot be read, are reported as damaged data, not taken as the default. - Layer Name is the effect's last parameter, so the earlier parameters keep their indexes and upgraded instances have the same order as new ones. - A Cryptomatte layer name is matched as given before it is trimmed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cryptomatte settings: require the selection's NUL to end its declared length Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Cryptomatte: match rank planes by the exact layer name crypto_rank_planes looked planes up with a case-insensitive whole-name match, so layers Crypto_A and crypto_a shared planes. The layer prefix now matches case-sensitively; component letters still match in either case. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Format Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * AEP: the exporter knows Extra::Cryptomatte; rustfmt 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> |
||
|
|
48c8137835 |
Accessibility (gap 25), slice 2: Info, Flowchart, Script Console and the 3D Camera Tracker block named, described and keyboard-operable (builds on #1339) (#1341)
* #1335: accessibility slice 2: Info, Flowchart, Script Console and the 3D Camera Tracker block named, described and keyboard-operable The audit gets read-outs, graphics and logs as described items (a name is required in a fixed panel), dedicated maximized scenarios for the panels the shared loop squeezes, and minimum counts for the new zero panels. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Review: Enter/Space on a favourite star only toggles it; keyboard activation of a property label; settings page scrolls to focus; sharper UI test assertions Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Refresh the accessibility audit baseline after merging upstream/main Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * a11y audit: hold the machine-dependent Media Browser scenario to its upper bound The Media Browser opens on the home folder, so a runner with fewer files finds fewer controls and the 'improved, lower the entry' check failed in CI. The entry stays an upper bound. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Merge upstream/main and updated feat-gap25-a11y-audit into feat-gap25-a11y-panels-2 Brought in the updated feat-gap25-a11y-audit (which already contains upstream/main). Conflict: crates/ui-egui/src/menus.rs, which upstream split into menus/*.rs. Took the slim module and re-applied this branch's Flowchart hunks where the code now lives: nudge() skips the Flowchart (menus/invoke.rs) and Enter is left to the Flowchart in handle_shortcuts (menus/shortcuts.rs). Also removed a duplicate MACHINE_DEPENDENT const in tests/ui_accessibility_audit.rs that the two branches both added. The audit BASELINE still matches the measured findings. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Flowchart: split show() to pass the stricter clippy limits from main Main now denies cognitive_complexity (30) and too_many_arguments; the merged flowchart panel exceeded it. The keyboard navigation, the key-to-node mapping, pan-into-view, node commands, the accessible node name and the option params move into small helpers with unchanged behaviour. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Camera tracker a11y test: wait for the analysis result instead of one frame The test pressed Enter, ran one frame and expected to see the background job. On a fast runner the job starts, ends and is written back inside that frame, so the state was never seen. Undo the first analysis, press Enter, and wait (bounded) for the new analysis step in the undo history. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Accessibility: one hours range for the accessibility pass in ui-parity.md; test that Flowchart automation labels stay English Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Flowchart toolbar: keep the English label for the automation registration The translated text is shown and spoken; the automation label stays English. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> Co-authored-by: OpenAI Codex <noreply@openai.com> Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com> |
||
|
|
519769299f |
Cryptomatte: the Layer popup lists the footage's Cryptomatte layers (#473) (#3020)
* Cryptomatte: the Layer popup lists the footage's Cryptomatte layers (#473) The popup was the constant CryptoObject / CryptoMaterial / CryptoAsset. It now lists the Cryptomatte layers of the layer's OpenEXR footage, and those three when there are none. The stored value keeps its meaning (0-2 the standard layers, 3 and up the file's other layers in name order), so older projects render the same. Refs #473 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Cryptomatte Layer popup: timeline editor, one menu helper, probe and fallback tests (#473) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Cryptomatte Layer popup: give further layers of a standard kind a custom slot A.CryptoObject keeps slot 0; B.CryptoObject and later ones get 3+ instead of being dropped, so they can be selected, picked and rendered. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Format; note same-kind layers in the footage docs Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Format the merged code with rustfmt Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
fdcc715829 |
Viewer exposure on the full float range of 32 bpc frames (#2937)
* Viewer exposure on the full float range of 32 bpc frames (#2797) 32 bpc viewer frames on the desktop are rendered without the HDR step (skip_hdr_step, #2916) and kept as half floats in the RAM preview. A display pass on egui-wgpu's device turns each frame into one RGBA8 texture with mips per viewer, in this order: decode, exposure in linear light, Compand / Tone Map, encode, display colour, Show Channel, rounding. Before, exposure was an 8-bit lookup applied after tone mapping, clamping and the display conversion, so -2 EV could not bring values above 1.0 back. - engine: viewer::DisplayPass describes the pass as data; its pixel() is the CPU fallback (no device) and the shader's reference. It replaces exposure_lut / display_transform. FrameEncoding records a frame's curve and the HDR step it still needs. 32 bpc snapshots are taken without the HDR step; snapshot_rgba8 applies it. - ui-egui: FrameImage::CpuF16 (half crate, F16C at run time), byte accounting at 8 bytes a pixel in the cache and the prefetch estimates; display_gpu.rs + display_gpu.wgsl; main viewer, snapshot and passive viewers draw through the pass; the 8-bit image other panels read is made on demand with the HDR step; the Info panel shows float values. - Half-float frames are not written to the disk cache yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Viewer display pass: f16 disk cache entries, window-device gating, review fixes - Disk cache: half-float frame entries (format byte in the entry header, byte planes + LZ4); 8-bit entries keep format 0 and stay valid. 32 bpc viewer frames are written and read again, and the timeline's disk bar sees them. A damaged half-float entry is deleted. - The display pass runs on the window's egui-wgpu device, with or without the GPU compositor (still building, failed, GL fallback). Its pipelines are made inside error scopes; a backend that rejects them gets the CPU pass instead of a blank viewer. - A new LUT of the same size (another ICC profile, Preserve RGB, another display) runs the pass again. - f32 to f16: one pass per L1-sized block, the NaN / infinity fix-up on the bits, no fill of the buffer (4K: 4.7 ms, as the 8-bit conversion). - Mips of odd-sized levels include the last column / row; the snapshot's half floats and texture go with the snapshot; no placeholder texture ids; u64 size checks. - Tests: LUT profiles and negative values against the shader, Bake reruns, the passive viewer's colour, f16 disk round trips; exact comparisons allow 1 LSB on software rasterizers. Docs: the display pass and the f16 disk entries in architecture.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Viewer f16 disk-cache test: temp dir removed on failure too, wait for the queued disk write Review points: the test's directory was only removed by its last statement (now tempfile::TempDir), and the frame is released before its disk write is queued, so flush() could return early (now waits for the entry). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Viewer: oversized half-float frames use the CPU display pass, so exposure/HDR come before the averaging down Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Viewer: fmt and a debug assert for the half-float upload path Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Disk cache: persist_missing also writes half-float frames Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Passive viewer: oversized half-float frames take the CPU display pass instead of leaving a stale texture Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8948a7cdc9 |
Masks: set the corner roundness of a Rounded Rectangle mask (#3077)
* Masks: set the corner roundness of a Rounded Rectangle mask (#3067) layer.addMask takes a roundness (corner radius in px) for shape rounded, and the new mask.setRoundness changes it afterwards on a rectangular mask. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Mask roundness: refuse ellipses, keep feather points and key correspondence mask.setRoundness accepted any closed path whose vertices lie on its bounding box, so an ellipse was replaced by a rectangle. It now accepts only a plain rectangle (four corner vertices, straight sides) or a rounded one (eight vertices, straight sides alternating with circular corners). The rebuilt path keeps the old start vertex and direction, and its feather points, so an animated path's other keys and the feather points still line up. A change between square and rounded corners changes the vertex count; it is refused when the path is keyed or has feather points instead of leaving keys with different counts or dropping the points. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
be8b5fd8d4 |
Layer: Quick Set Anchor, nine anchor points with Position compensation (#3080)
* Layer: Quick Set Anchor, nine anchor points with Position compensation (#2967) layer.quickSetAnchor moves the Anchor Point of 2D layers to a corner, edge midpoint or the centre of their content and shifts Position and its keys so nothing moves. Layer > Transform > Quick Set Anchor lists the nine points. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Quick Set Anchor: skip layers with an animated Anchor Point, Scale or Rotation The anchor is set at the current time and the Position shift is worked out from the current Anchor Point, Scale and Rotation, so on a layer where any of them is keyed or has an expression the layer would move at other frames. Such layers are skipped like 3D and locked ones (an error when none is left). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Quick Set Anchor: skip layers with a Position expression or Auto-Orient along the path An expression on Position (or the separated X/Y) overrides the shifted value, and Auto-Orient along the path adds a rotation the shift does not account for; both would make the layer jump, so they are skipped like 3D layers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Quick Set Anchor: error when no selected layer has content bounds Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Quick Set Anchor: test separated, keyed Position keeps the layer in place Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
9a32a51793 |
Viewer: Alt-drag duplicates the dragged layers (#3076)
* Viewer: Alt-drag duplicates the dragged layers (#2968) Alt/Option-drag of the selected layers in the Composition viewer duplicates them and drags the copies; the copy and the drag are one undo step. edit.duplicate takes a merge key for it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Alt-drag: duplicate from a 3D gizmo arrow, no double move of a selected child Alt-dragging a gizmo arrow moved the original; it now duplicates and moves the copy along the axis. A selected layer whose parent is dragged too follows the parent, so its own Position no longer gets the drag a second time (with or without Alt). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
93cce09f3c |
.aep import: name the After Effects version in the report; 25.x and 24.x copies import like 2026 (#3136)
* AEP import: the report names the After Effects version of the file (#3115) svap holds the version of the After Effects that wrote a project, head bytes 4 to 7 the version the file is written for; Save a Copy As 25.x / 24.x of After Effects 2026 keeps svap and writes the older version into head. AeVersion decodes the packed word (26.2.1x2), AepFile gains file_version, saved_by_version and head_revision, and ImportReport gains aeVersion (fileVersion, savedBy, tested; "unknown" when the file does not say). A version outside 24 to 26 is imported as usual and gets a limitation line, not a loss. The 12 corpus families saved as 2026, 25.x and 24.x copies import to the same project and report; docs/aep-format.md lists what the copies change in the container. The fixture tests are ignored until the corpus holds the files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * AEP import: doc comments on the version tests and helpers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * AEP import docs: tested is a major-version classification from copies saved by After Effects 2026 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * AEP import docs: the importer reports detected losses, not every loss Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * aep tests: compare the imported settings across version copies too Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ab3674cc2b |
.aep import: map HDR Highlight Compression, Echo, Linear Wipe, Offset and Color Stabilizer (#3119)
* AEP import: map HDR Highlight Compression, Echo, Linear Wipe, Offset and Color Stabilizer The importer reported these five effects as lost. They now become EffectCraft's effects of the same name with every parameter After Effects stores (ranges, defaults and popup items from our own After Effects 2026 project, documented in docs/aep-format.md 3.7): - ADBE HDR ToneMap -> ec.utility.hdrcompression (Amount) - ADBE Echo -> ec.time.echo (Echo Time, Number Of Echoes, Starting Intensity, Decay, Echo Operator by item name) - ADBE Linear Wipe -> ec.transition.linearwipe - ADBE Offset -> ec.distort.offset (Blend With Original is a stored 0..1 fraction, a percentage here) - ADBE Deflicker -> ec.color.colorstabilizer (fresh Stabilize default Levels, points, Sample Size) A table row can name the range EffectCraft's effect works in (ParamMap::limit): Echo values or keys beyond 255 echoes or a Decay of 1 are imported as stored and reported. Tests build each effect into the committed synthetic.aep (fx_inject.rs, which also checks its pard layout reads back). On the sample project the effect losses go from 75 to 3 (the out-of-range Echo values). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * AEP import: doc comments on the quick-win test helpers and tests Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * AEP import: Echo keyframe regression for the range loss Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ae068ff931 |
EXR export: write the output space's chromaticities attribute (#3038)
* EXR export: write the output space's chromaticities attribute (#3037) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * EXR chromaticities: tests for unmanaged and PQ ProEXR, every space once Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * EXR export: note why the comp background is added before decoding (review point) CodeRabbit asked to convert Comp.background to the output space before compositing. Every output path (Image::to_rgba8_over) adds the background to the output-encoded frame as it is, so the EXR now matches the other outputs; converting would make the EXR colour differ from the PNG of the same comp. Comment only. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> * Format the merged code with rustfmt Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
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. |
||
|
|
3102b6e632 |
Merge pull request #2915 from vangel76/perf/exr-output-options
Render Queue OpenEXR: choose compression and half or 32-bit float |
||
|
|
4934de892d |
Merge upstream/main into cryptomatte-matte-only
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f9b860838c |
Merge upstream/main into perf/exr-output-options
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
3768ff40a3 |
Merge pull request #3134 from vangel76/aep-tdsb3-values
.aep import: read effect values stored as default-state records |
||
|
|
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) |
||
|
|
39af86e9f4 |
Merge pull request #2929 from vangel76/perf/aux-footage-hint-474
perf(media): tell the media pool which EXR footage effects read (#474) |
||
|
|
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) |
||
|
|
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. |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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. |
||
|
|
06438a1b9f | Merge remote-tracking branch 'upstream/main' into HEAD | ||
|
|
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> |
||
|
|
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> |
||
|
|
e3ab397a7e | Merge remote-tracking branch 'origin/perf/exr-decode-cache-474' into m2929-merge | ||
|
|
2b3d76c5fd |
Merge upstream/main into perf/exr-decode-cache-474
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
4aaf9c4bb4 | Merge remote-tracking branch 'upstream/main' into m2915-merge | ||
|
|
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> |
||
|
|
3ae9c8dbc2 | Merge PR #2981 | ||
|
|
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. |
||
|
|
f5e81cd1f9 | Merge PR #2957 | ||
|
|
6334d07fe1 |
Merge PR #2872
# Conflicts: # apps/effectcraft-cli/tests/it/cli.rs |
||
|
|
3170c0e34f | Merge PR #2905 | ||
|
|
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. |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |