VP8 WebM showed black video: there was no VP8 decoder. New L0 crate
filmcraft-vp8, implemented from RFC 6386 sections 1-19 (tables extracted
from the RFC text, nothing from the attached reference decoder): every
coding tool, versions 0-3, hidden altref frames, size changes. Bit-exact
with libvpx on 62 libvpx VP8 test vectors, real-world FATE samples and
generated fixtures; never panics on corrupted input.
Wired into codecs for Matroska / WebM (V_VP8, including the alpha layer
of transparent WebM) and MP4 (vp08). VP8 colour is BT.601 (RFC 6386 9.2).
A hidden altref frame is a WebM block of its own with the time of the
frame that follows it: the VP8 time lookup now moves on to that shown
frame instead of repeating the previous one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A GopCache decodes with its lock held, and that decoding starts rayon work
(rotating iPhone frames, cropping, slice-threaded decoders). On the global pool
a worker that took part of it could, waiting inside it, pick up another render
job asking the same cache for a frame and block on the lock, while the lock
holder waited for the work on that worker's stack: exports of rotated iPhone
HEVC clips stopped making frames in about half the runs.
The locked decoding now runs on a separate decode pool, whose threads never wait
for a cache's lock; a caller that is a rayon worker waits on a plain thread so it
does no other rayon work while holding the lock. The cancel/catch-up/draft/
background hints travel with it (filmcraft_media::cancel::Context). Intra-only
streams crop and rotate before taking the lock again.
Second version of #662 (reverted): the regression test's stand-in decoder summed
with overflow, which panics in debug builds; it now wraps explicitly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A GopCache decodes with its lock held, and that decoding starts rayon work
(rotating iPhone frames, cropping, slice-threaded decoders). On the global pool
a worker that took part of it could, waiting inside it, pick up another render
job asking the same cache for a frame and block on the lock, while the lock
holder waited for the work on that worker's stack: exports of rotated iPhone
HEVC clips stopped making frames in about half the runs.
The locked decoding now runs on a separate decode pool, whose threads never wait
for a cache's lock; a caller that is a rayon worker waits on a plain thread so it
does no other rayon work while holding the lock. The cancel/catch-up/draft/
background hints travel with it (filmcraft_media::cancel::Context). Intra-only
streams crop and rotate before taking the lock again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Native file dialogs are requested with a continuation (FilmcraftApp::pick for
commands, pick_ui for panel buttons). On Linux the desktop host shows the rfd
dialog on a worker thread (HostHooks::run_dialog) and the result is applied
when it arrives, so the frame loop, playback and the control channel keep
running and GNOME no longer offers to force-quit the window. One dialog at a
time; a path for a dialog closed meanwhile goes nowhere. macOS / Windows keep
their synchronous pickers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The MP4 source reported the sample entry's size, only swapped for the display rotation, so two
crops were lost: the codec's own (H.264 SPS frame cropping, HEVC conformance window), which the
decoder applies but the reported size and sequences made from the clip didn't, and the clean
aperture (`clap`), parsed but never used. An iPhone clip (1920x1440 coded, 1744x1308 clean
aperture, -90 degree display matrix) showed 1440x1920 instead of 1308x1744.
H.264 / HEVC sizes now come from the stream's parameter sets; the clean aperture is a rectangle
inside that picture (ignored when it makes no sense), reported as the size and cut from every
frame by the GOP cache before the display rotation, through the new VideoFrame::cropped.
Tests: bit-exact against ffmpeg (which applies both crops) for an H.264 SPS crop, an HEVC
conformance window, centred and off-centre clean apertures and a clean aperture with a rotation;
clean apertures that make no sense are ignored without a panic; VideoFrame::cropped on every pixel
format with odd sizes and misaligned corners.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every VA-API session opened its own display and terminated it when dropped. With NVIDIA's driver
that is a CUDA context each: 0.2-0.3 s to open, 60-70 ms to terminate and about 100 MB of video
memory, on the thread creating or dropping the decoder (a frame worker at every clip change, the
UI thread when media is removed: a ~0.6 s freeze deleting five played clips, and dropped frames at
clip changes).
Sessions now take the process's shared display (the startup probe's, kept for the life of the
process like the libraries) and make only their own configuration, context and surfaces. Display
is Sync on the strength of libva's Multithreading Guide (va.h): its functions and drivers are
thread-safe, and every session uses only its own VA objects. A session that cannot be made on the
shared display is tried once on a new display, which is shared from then on if it works (a GPU
reset).
On NVIDIA (RTX 3060): deleting five played clips 650-690 ms -> 132-198 ms, dropped frames in 20 s
of playback 2-3 -> 0, peak video memory 1281 -> 1010 MiB. Intel and software unchanged.
Test: tests/vaapi_displays.rs (decoders one after another and six at once on six threads open no
display; pictures bit-exact). The VA-API hardware suite passes on NVIDIA and Intel, and the
concurrency tests 20 times each.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a hardware decoder failed, the replay of the run through the software decoder stopped at the
first sample the software decoder rejected (`?` in the replay loop): the pictures it had already
output were dropped, the rest of the run was never fed, the error was returned for the failed
sample even when the software decoder accepted it, and the replay log was not restored. That
happens when the GPU tolerated a damaged sample our decoder rejects and then fails later in the
same run; one picture went missing on a damaged HEVC file through VA-API (Intel).
The replay now behaves like software decoding from the restart point: a rejected sample is skipped
(logged when the hardware had already decoded it); if the failed sample itself is rejected its
error is this call's, and the pictures replayed before it are kept in `carry` for the next output.
Regression test (every platform, no GPU): a stand-in hardware decoder that decodes one damaged
sample the software decoder rejects and fails at that sample or later; every software picture
comes out once and in order, errors are reported where the software decoder reports them, and the
pictures from the next restart point are the software decoder's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The interface became translatable since this branch started. The edit point menu now draws its
labels (and automation labels) in the interface language like the clip menu: the trim types
through i18n::t, Apply Default Transitions and Join Through Edits with tl! (both already in
the Spanish catalog). Spanish entries for the five trim types use the catalog's existing
vocabulary (recorte de rizo, edición de rodillo, recortar), and the trim types are listed with
the clip menu's labels in the test that requires Spanish for every such name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflict in the timeline's right-click handling (crates/ui-egui/src/panels/timeline.rs):
#220 now remembers the right-clicked clip so Unlink keeps only it selected, and this branch
opens the edit point menu on a clip's edge. Both are kept: the clip is remembered on every
right-click, and an edge also stores the edit point menu's target.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Right-clicking a clip's edge on the timeline now selects that edit point and
opens Premiere's edit point menu instead of the clip menu: Ripple Trim In,
Ripple Trim Out, Roll Edit, Trim In and Trim Out (each re-selects the cut with
that trim type, greyed out when the needed side has no clip), Apply Default
Transitions, and Join Through Edits. The body of a clip still opens the clip
menu.
Join Through Edits from this menu joins only the right-clicked cut (and its
linked partner): `sequence.joinThroughEdits` takes a new `cut: [left, right]`
parameter, since `clips: [id]` also matched any other through edit touching
the same piece.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unlinking from the timeline clip menu cleared the link, but the picture and
its sound stayed selected together, so dragging the picture still moved the
sound and Unlink looked like it did nothing. The clip that was right-clicked
is now the only one selected after Unlink, and can be dragged away straight
away. Link leaves the selection as it was.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>