Commit Graph
3533 Commits
Author SHA1 Message Date
FredApps 3bb3d2ba67 Merge origin/main into Drag-and-drop-folders-in-project-media 2026-11-05 06:12:13 +01:00
fafdb22e43 docs: explain video fades and audio volume keyframes (#2973) (#3090)
Co-authored-by: Zaxaerith <Zaxaerith@users.noreply.github.com>
Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-05 05:30:35 +01:00
FredApps 6eaf6b966b Merge origin/main into Drag-and-drop-folders-in-project-media 2026-11-05 05:28:20 +01:00
FredApps d4c9f6c17c Restore packaging script executable bits and drop the unrelated .gitignore entry
The branch had changed nine packaging scripts from 100755 to 100644 (FredApps' review) and added an .andromity/ ignore line unrelated to this PR.
2026-11-05 04:34:05 +01:00
FredApps a4278d9783 i18n: update the drop hint in bs, cs, pl, ru, sv and tr
The English key now mentions folders; these six catalogs still had the old key.
2026-11-05 04:34:04 +01:00
FredApps 1672bc4f7b Import dropped folders as bins from app/input.rs
Main moved handle_drops into app/input.rs (import_dropped); re-apply this PR's change there: mediaBrowser.import with imageSequence:false, with errors shown in the status bar. Add an engine test for files dropped with an empty folder.
2026-11-05 04:34:02 +01:00
FredAppsandClaude Opus 5.5 532d99e119 About: credits tables fit without side-scrolling and their columns resize (#4234)
* About: credits tables fit without side-scrolling and their columns resize

The Contributors table and the Models table were egui Grids inside a
two-way scroll area: they opened wider than the dialog, so they scrolled
sideways, and Company and Model sat almost touching. Both are now
egui_extras tables. Each column opens at the width of its header and widest
cell, the spare width goes to the text columns (Name; Company and Model),
and every column edge can be dragged. Widening a column grows the table
like a spreadsheet, and only then does it scroll sideways; the header stays
put while the rows scroll. Counts and dates are right-aligned and every
cell is vertically centred.

The sorted column stays highlighted and its direction shows on the
toolbar's sort-order button, so headers no longer reserve room for an
arrow. The opening widths come from fit_widths, a pure function with unit
tests that a fitted table fills its room exactly and that only the text
columns give way when room is short.

The About dialog opens 820 points wide, so every column fits at its natural width.

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

* Let every credits column be dragged, and refit changed tables

The last column was egui's remainder column, which has no drag handle, so
it could only be resized from the edge before it. It is now an ordinary
column with its own handle.

A table keeps its column widths, so switching to a longer name left the
Name column at its old width and clipped the text. The table now goes
back to the opening fit whenever that fit changes: a longer widest cell
or a resized window. Dragged widths are dropped at that point.

fit_widths could overflow the room when one column hit the minimum,
because the other columns did not take up its share. Columns now give
way in proportion to their width above the minimum.

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

* Refit credits tables on what they measure, not the fitted widths

Comparing the fitted widths missed the case it was meant for. Name is the
only growing column, so a longer name takes its extra width from the
spare room and the fit comes out the same: a dragged Name width stayed,
clipping the longer name.

It could also drop dragged widths by accident. The fit comes from the
available width inside the scroll area, which scrolling can nudge, so
scrolling a widened table could reset it.

The table now compares the natural widths and the clip rect's width,
which scrolling does not move.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 04:33:40 +01:00
FredApps 34a63ca4f6 Merge origin/main into Drag-and-drop-folders-in-project-media
main split handle_drops out of lib.rs into app/input.rs and fixed the bin-card overlap with elided_name; take main's lib.rs and project_views.rs. The drop-import change is re-applied to app/input.rs in the next commit.
2026-11-05 04:33:14 +01:00
7322aad724 Preserve same-second autosave snapshots (#3844)
* Preserve same-second autosave snapshots

Found while testing crates/format/src/autosave.rs:133

Co-Authored-By: GPT-5.6-Luna <noreply@openai.com>
Co-Authored-By: GPT-6-Luna <noreply@openai.com>

* test: cover autosave collision rotation

Co-Authored-By: GPT-5.6-Luna <noreply@openai.com>

* autosave: number a same-second save after the highest collision on disk

Rotation removes the oldest collisions first, so the lowest free number can sit
below a newer save. Reusing it made the new save sort as the oldest and the
next rotation deleted it straight away; continue after the highest instead.

---------

Co-authored-by: GPT-5.6-Luna <noreply@openai.com>
Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-05 04:03:10 +01:00
Romain PINSOLLE 85448940ef render: turn display-rotated video on the GPU (#3467) (#3484)
* render: turn display-rotated video on the GPU (#3467)

A portrait phone clip (4K60 HEVC with a 90° display matrix) dropped ~97 %
of its frames: every decoded 4K frame was turned on the CPU, which is too
slow at 60 fps, and with the GOP cache's lock held.

- GopCache caches decoded frames raw when they need a crop or a rotation;
  `frame_late` crops and turns them off the lock, and the new
  `frame_late_unturned` hands them over as decoded with the quarter turns
  still to apply.
- MediaSource gains `video_frame_unturned` (default: the upright frame, 0
  turns). MP4 and Matroska implement it; the pool's wrapping sources and
  the frame server's timed source pass it on.
- The render plan folds the turns into the layer matrix of plain layers,
  and into the GPU effect stage (`LayerFx::turns`): `fs_fx_source` reads
  the source on its decimated grid through the turn, and the CPU reference
  decimates then turns, so effects still see the upright picture.

Other paths (CPU-only effects, log media, a clean aperture, export) still
turn on the CPU as before.

* codecs: cache intra-only frames raw too (#3467)

Intra-only decoders (ProRes, DNx) cropped and turned every frame before
caching it, so frame_late_unturned found it already turned and the GPU
had nothing left to do. Their frames are now cached raw like long-GOP
output: frame_late still crops and turns them off the lock, and
frame_late_unturned hands them over as decoded.

* docs: document the unturned-frame entry points (#3467)

* codecs: follow main's frame lookup in the unturned paths (#3467)

main changed `from_tick` (Matroska) to take the frame rate and `snap`, and
MP4 looks frames up with `lookup_time(.., rate, snap)`. `video_frame_unturned`
still used the old calls: the Matroska one no longer compiled, and the MP4 one
could pick another sample than `video_frame` when `snap` is on. Both now use
the same lookup as their `video_frame`.

Also repairs the merge of main into this branch: a closing brace was lost in
the #3467 test (the test module did not compile) and a blank line was doubled
(cargo fmt --check failed).

* codecs: test every display turn, crop with turn, drafts and eviction of raw frames

* gpu: give the neutral-corrector test LayerFx its turns field

Merging main added a LayerFx initializer without the turns field, which
breaks compilation of the gpu tests (E0063).
2026-11-05 03:19:24 +01:00
skylar taylor-barrick 0b780f6f11 Merge pull request #4066 from vangel76/fix/ogg-packet-budget
ogg: stop reading when the packet list passes 256 MiB
2026-11-05 00:28:58 +01:00
Dylan M. Taylor 2508f5420e Pass the Linux signing key to the release workflow (#4303) 2026-11-04 17:45:08 -05:00
Dylan M. Taylor c9be14245a interchange: refuse repeated AAF storage ownership and bound weak paths, Bento names and FCP7 link lists (#4216)
* interchange: refuse repeated AAF storage ownership and bound weak paths, Bento names and FCP7 link lists

* address review: leave clips cut from an over-large link group unlinked

* address review: assert the truncation warning on the export report
2026-11-03 23:50:31 -05:00
Dylan M. Taylor d184681882 media: validate offline slate audio before allocating silence (#4199)
* media: validate offline slate audio before allocating silence

The offline slate sources (OfflineSource in media, SlateSource in the
media pool) allocated silence for any requested length at the channel
count stored in the project, with no check. Transcription asks for a
whole item's stored duration, so an offline item with a huge duration or
channel count requested gigabytes. Both now validate the buffer shape
first, like the file readers.

* address review: compute the oversized frame count as u64 in the offline-source test
2026-11-03 23:50:05 -05:00
Dylan M. Taylor ece831b619 render: cap Long Shadow and Glint lengths at the picture extent (#4200)
* render: cap Long Shadow and Glint lengths at the picture extent

Long Shadow doubled a step until it passed twice the stored length: an
infinite length never ended the loop, each pass cloning and scanning the
image. Glint ran one full-image pass per doubling up to three times its
length. Both lengths are now capped at the picture's width plus height,
beyond which a longer shadow or streak looks the same.

* address review: Glint and Long Shadow keep the requested length for weighting and fade; the extent bounds only the passes

* address review: assert the long shadow fade still depends on length
2026-11-03 23:24:17 -05:00
Dylan M. Taylor 234e5e8991 logging: escape control characters in a log message so it stays one record (#4255) 2026-11-03 23:21:27 -05:00
Dylan M. Taylor 8ee7c59993 export: pick the cheaper resample axis order and refuse short sources in the 8-bit conversions (#4245) 2026-11-03 23:21:01 -05:00
Dylan M. Taylor be73f74939 edit: search transcript phrases in one pass (#4224)
* edit: search transcript phrases in one pass

* address review: drop the wall-clock assertion from the search test
2026-11-03 23:20:36 -05:00
Dylan M. Taylor cc3b8267b0 media: bound still pixel count and YCbCr TIFF strips before allocating (#4214)
* media: bound still pixel count and YCbCr TIFF strips before allocating

* address review: reserve the YCbCr TIFF buffer fallibly
2026-11-03 23:20:11 -05:00
Dylan M. Taylor b316cc708b engine: keep a shortcut preset named "active" from replacing the active set (#4201)
* engine: keep a shortcut preset named "active" from replacing the active set

Custom shortcut presets are saved as "<name>.json" in the same folder as
active.json, the set restored at startup. A preset named "active" (for
example an imported file, even with activation off) therefore overwrote
the active set. Such a preset is now saved as "active (preset).json".

* address review: redirect an 'active' preset to a collision-free file and keep legacy mixed-case presets reachable
2026-11-03 23:19:45 -05:00
Dylan M. Taylor cefb980e6c speech: validate the Whisper attention head count (#4195)
* speech: validate the Whisper attention head count

The Whisper attention layer took its head count from config.json without
checking it: zero heads divided by zero in forward, and a huge count sized
every attention-logit tensor. A count must now be 1..=64 and divide
d_model.

* address review: separate head-count and divisibility errors
2026-11-03 23:19:06 -05:00
Dylan M. Taylor d9dad4a511 flac: consume encoder batches by offset instead of draining per frame (#4190)
* flac: consume encoder batches by offset instead of draining per frame

encode_frames drained each block from the front of the pending buffers,
moving the whole remainder per frame, so one large batch cost quadratic
copying. Blocks are now read at an advancing offset and the buffers are
compacted once.

* address review: drop the wall-clock assertion from the large-batch FLAC test
2026-11-03 23:18:41 -05:00
Dylan M. Taylor 9731ad3fcc tts: look for a pause marker's closing bracket only a marker's length ahead (#4189)
* tts: look for a pause marker's closing bracket only a marker's length ahead

script::parse searched the whole remaining text for a closing bracket after
every '[' and lowercased the candidate, so text with many unclosed or
distant brackets parsed in quadratic time (the speech panel re-parses on
every redraw). A pause marker is short; the search now looks at most 64
characters ahead.

* address review: drop the wall-clock assertion from the linear parse test
2026-11-03 23:18:16 -05:00
Dylan M. Taylor 5d4e4aa692 security(mcp): bound queued parse-error replies so unread output stalls input (#4115)
* mcp: parse errors wait for the writer instead of queuing without bound

* address review: backpressure test floods 64K lines and polls the write alongside the reads

* address review: stop the MCP reader when output fails; bound the backpressure test drain
2026-11-03 23:17:49 -05:00
Dylan M. Taylor 100577c625 mxf: resolve a source package's repeated track references once (#4212) 2026-11-03 22:32:56 -05:00
Dylan M. Taylor 7ae7c760c6 captions: bound caption outline dilation work (#4110)
* captions: bound caption outline dilation work

The outline max-filter visited every disc offset for every pixel of the
caption overlay, so a large outline radius over a frame-sized caption cost
area * r^2 work (around 10^11 visits at 4K with the 64 px radius cap).

Spread only covered pixels over the disc, which gives the same maximum
because the disc is symmetric, and shrink the radius until covered pixels
times disc offsets fit a fixed work budget.

* captions: test outline radius reduction on a mixed mask
2026-11-03 22:32:08 -05:00
Dylan M. Taylor 9bd7250bfc engine: leave unselected items' source graphics out of a selection export (#4183)
project_subset filtered items, transcripts and narrations to the selected
closure but kept every source_graphics entry, so File > Export Selection as
Project wrote other items' graphic text and metadata into the new file.
2026-11-03 22:31:18 -05:00
Dylan M. Taylor 6d85508e5f render: bound subclip recursion in the preview cost estimate (#4186)
decode_cost_ms followed Subclip parents with no guard (only nested
sequences checked depth), so a subclip that names itself, or a cycle of
subclips, recursed until the stack ran out while drawing the render bar.
The depth bound now applies to every source kind.
2026-11-03 22:30:28 -05:00
Dylan M. Taylor 405d9aa958 hevc: do not carry the previous segment's entry points into dependent slices (#4188)
A dependent slice segment inherited its header by cloning the previous one,
entry-point table included, and kept that allocation after clearing it, so
many short dependent segments each retained a copy of one large table.
The inherited header now starts with an empty table.
2026-11-03 22:29:36 -05:00
Dylan M. Taylor 551155a92d dng: reject zero or over-16-bit sample depths before unpacking (#4192)
unpack accepted a bit depth of 0: the input requirement became zero bytes,
so one source byte produced a header-sized buffer of zero samples. Depths
outside 1..=16 (the u16 output range) are now rejected up front.
2026-11-03 22:28:45 -05:00
Dylan M. Taylor c42e803e65 h264: refuse slices that overlap macroblocks the picture already decoded (#4204) 2026-11-03 22:27:56 -05:00
Dylan M. Taylor 073ef5549f captions: cap the CEA-708 text an MCC import retains (#4121)
* captions: cap the CEA-708 text an MCC import retains

The 708 decoder retains a full snapshot of every visible window each time a
packet changes the display. Window dimensions are honoured, but a packet of
a few bytes (hiding and showing all windows) still retains up to eight full
windows of text, so an MCC file could expand by hundreds of times into
retained strings.

Track the cue text kept and stop decoding 708 data once 16 MiB is reached,
with an import warning.

* address review: stop the 708 import once an open cue exceeds the text budget
2026-11-03 17:59:48 -05:00
Dylan M. Taylor afa650b3df engine: merge imported presets through a name index (#4168)
* engine: merge imported presets through a name index

presets.import and export.presets.import removed same-named entries with a
retain over the whole user library for every imported preset, so a file of
many distinct names made the import quadratic on the command thread. Both
now go through presets::merge_by_key, which replaces a same-keyed entry in
place and appends new ones, using a map built once.

* address review: keep one library entry per imported preset key; drop wall-clock assert
2026-11-03 17:59:36 -05:00
Dylan M. Taylor 3fcff6bea1 engine: do not keep opened sources in relink search results (#4198)
Relink search kept every candidate's opened media source until the search
finished, so many matching files (or many aliases of one image, each
decoded separately) stayed in memory at once. Search results drop the
source; relinking already reopens the chosen file.
2026-11-03 17:59:24 -05:00
Dylan M. Taylor ce3db76e8c codecs: cap the decoded samples a standalone file or Vorbis stream keeps in memory (#4203) 2026-11-03 17:59:12 -05:00
Dylan M. Taylor 8c7d0003be text: lay out overlapping runs, visual runs and wrapped lines without rescanning (#4221) 2026-11-03 17:28:31 -05:00
Dylan M. Taylor 9ac1de570e project: cut the speaker name copied into each derived transcript word (#4223) 2026-11-03 17:28:19 -05:00
Dylan M. Taylor 9463ed6f98 codecs: bound AVI window copying, packet reads, H.264 head collection and decoded-packet caches (#4228) 2026-11-03 17:28:07 -05:00
Dylan M. Taylor 66487b0ac3 h264, hevc: refuse a decode call that starts an absurd number of pictures (#4231) 2026-11-03 17:16:18 -05:00
Dylan M. Taylor 57dc82ea20 project: bound the speaker index a transcript word may carry (#4140)
Transcript::normalize sized the speaker label table from the largest word
speaker index, so one word with a u32::MAX index made transcript.set allocate
billions of labels before check() could run. Indices at or past MAX_SPEAKERS
(1024) now lose their speaker instead.
2026-11-03 06:02:57 -05:00
Dylan M. Taylor b37811ec53 edit: look up higher-track transcript coverage by binary search (#4123) 2026-11-03 06:02:49 -05:00
Dylan M. Taylor 3a2ef6fe90 dng: budget SubIFD references before reading and queueing them (#4108) 2026-11-03 06:02:41 -05:00
32749e34f6 macOS: keep AppKit's Touch Bar observers when AccessKit drops (no crash on quit) (#4002)
* macOS: keep AppKit's Touch Bar observers when AccessKit drops (no crash on quit)

accesskit_macos 0.27.1 reset the window's view to WinitView when its adapter
dropped, discarding the key-value observation AppKit's Touch Bar finder had put
on the view's nextResponder. On quit, AppKit's removal of that observer raised
and _crashOnException: ended FilmCraft with SIGILL on Touch Bar Macs. 0.27.2
resets the class only while it is still AccessKit's own subclass.

Lockfile-only bump (accesskit 0.25.2, accesskit_consumer 0.39.2, accesskit_macos
0.27.2), with a test that fails below 0.27.2.

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

* filmcraft: accesskit lock test in the it binary; leave tempfile's getrandom as it was

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-03 00:10:12 -05:00
19fb3366ce fix(timeline): the Mix row's level sets the Mix fader (#3765) (#3954)
* fix(timeline): the Mix row's level sets the Mix fader (#3765)

The Timeline's Mix track header painted the master level as hot text but
nothing could change it, so the Mix volume seemed to do nothing. It is now a
hot number, as on an audio track: drag or type to set the Mix fader. It
shows the fader at the playhead (its automation, or a control held in the
Audio Track Mixer) and changes it through mixer.setValue, which writes a
keyframe where the Mix fader is automated.

mixer.setValue takes `merge` / `begin`, as clipMixer.set does, so a drag
is one undo step; a typed value is its own. Without `merge` nothing changes.

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

* fix(mixer): a merged Mix drag stays in its own sequence (#3765)

The Mix strip has the same id in every sequence, so the merge key of
mixer.setValue now names the active sequence too. Otherwise one undo
could revert Mix drags in two sequences.

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

* fix(timeline): drag the Mix level as a held gesture (#3765)

During playback an automation pass runs, and mixer.setValue touches and
releases the control on every call, so each drag update of the Timeline's
Mix level was its own undo step (and in Touch the return started from the
last update). The field now does what the Audio Track Mixer's fader does:
mixer.touch while dragging and mixer.release when the drag ends; a typed
value still goes through mixer.setValue. Outside playback the release
commits the value once, so the merge/begin params of mixer.setValue are
no longer needed and are removed.

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

* test(timeline): the Touch return starts at the Mix drag's release (#3765)

The drag test helper calls `between` once more before the release, so the
playhead moves after the last drag update; the Touch test checks that the
keyframe at the release time holds the released value.

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

* timeline: empty_state keeps its doc comment

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>
2026-11-03 00:10:07 -05:00
95758658bb gpu: the off-screen instance reads wgpu's flag variables (#4084) (#4087)
* gpu: the off-screen instance reads wgpu's flag variables (#4084)

native_instance() applies with_env() to its flags and backend options, as the window's instance does, so
WGPU_ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER=1 lets GPU exports use Mesa's Dozen on WSL2, and
WGPU_VALIDATION, WGPU_DX12_COMPILER and the others apply to them. Its backends are unchanged: no GL on
Windows (#3418), and ExportRenderer::new still narrows the adapters by WGPU_BACKEND.

The playback bench example reads the same variables now, and WGPU_POWER_PREF when set.

* bench: keep the default adapter options, not WGPU_POWER_PREF

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

---------

Co-authored-by: Brandon Thomas <echelon@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:10:01 -05:00
Tex JerniganandClaude Opus 5.5 d781e09cfd fix(ui): shortcuts need exactly their modifiers (#4016)
The key loop matched bindings with egui's consume_key / matches_logically,
which ignores extra Shift and Alt, and on a Mac also an extra Control, so
Ctrl+Opt+Cmd+N ran File > New > Project (Cmd+Opt+N) and Cmd+Shift+N ran
New Sequence. The native macOS menu bar matches key equivalents exactly
(checked against NSMenu), so these presses reached the egui bindings.

menus::chord_held now requires the held modifiers to be exactly the
chord's: Cmd and Control are two keys on a Mac and one elsewhere (#245);
Shift and Alt are exact on letters and named keys, while punctuation and
digits keep the Shift/Alt a layout needs to type them (#2253, Shift+; as
`:`). Most-specific-first ordering, panel scoping and key-repeat handling
are unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:09:56 -05:00
JohnandClaude Opus 5.5 26bcdbb9d7 fix(ui): Remove Attributes says that ticked attributes are removed (#3789) (#4003)
* fix(ui): Remove Attributes says that ticked attributes are removed (#3789)

The report's effect stayed because it was unticked in the dialog to turn
it off; a ticked box means "remove". The dialog now says so above the
attributes. The test covers the hint, an unticked effect being kept, and
an effect offered ticked and removed with the defaults.

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

* fix(ui): Paste / Remove Attributes offer only the effects they act on (#3789)

The dialogs listed Essential Sound's effects (Dialogue's noise reduction,
EQ…), which the commands keep, so a ticked box promised a removal that
did not happen. The engine's rule is now public as
clip_ops::is_attribute_effect and the dialogs use it.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:09:51 -05:00
Kathir DevandClaude Opus 5.5 281b3f4c1d fix(#3653): Timeline clicking it brings the Program monitor to the front of its group (#4012)
* Timeline: clicking it brings the Program monitor to the front of its group (#3653)

When the Program monitor shares a tab group with other panels and another
tab is in front, a click on the Timeline (its body or its header) brings
Program forward, as in Premiere; the focus stays on the Timeline. Program
is left alone when it is floating, closed, already in front, in a collapsed
group, accordion or icon-only dock column, or in the Timeline's own group.

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

* Timeline: test that Program is not fronted from a collapsed group (#3653)

A unit test covers tab_group's skips (minimized group, icon-only dock
column, accordion), so a Timeline click never expands what the user
collapsed. Node is imported with the other docking types, and the doc
comment names the collapsed cases.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:09:47 -05:00
IvanovichandClaude Opus 5.5 d5b2e787b1 fix(timeline): a marquee takes only the track rows its own pane shows (#2978) (#4118)
#2533 made a press hit only the rows of its own pane, but the marquee still
tested each row's full rect. A video row scrolled down past the divider lies
under the audio tracks, so a box drawn there also selected the hidden video
clips. Layout::shown gives the part of a row its pane shows; row_at and
marquee_clips both use it.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:09:41 -05:00
IvanovichandClaude Opus 5.5 eec7df93b3 gpu: export draws Push transitions on the GPU (#4122)
plan_frame plans a Push as a GPU composite (#308) and is_cpu_frame gives
such a frame a pooled renderer, but render_export_prepared and
ExportRenderer::render both refused every composite, so each frame inside
a Push went back to the CPU renderer. plan_frame composites only
transitions the GPU draws, so the export path now runs composites like
layer plans.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:09:35 -05:00