A PC whose GPUs were installed from different driver packages of one vendor
(an ASUS G513QY: the integrated Radeon on the 2021 package, the RX 6800M on a
2026 one) gives the Vulkan loader two versions of that vendor's driver, and
the RX 6800M is listed once through each. wgpu's request_adapter takes the
first discrete entry, the 2021 driver's, and the window drawn through it dies
inside amdvlk64.dll (0xc0000005) before it paints: the main thread is in the
2021 driver, the fault on a thread of the 2026 one, no Rust frame anywhere.
Left to the #820 ladder, two crashed launches end in DirectX 12 only.
filmcraft_gpu::adapter::choose is wgpu's own rule (the adapters that can draw
to the surface, by device type for WGPU_POWER_PREF, then as enumerated) with
one addition: when the adapter that rule picks is listed again by the same
driver (same backend, device type, PCI vendor and device and driver name; the
same bus id when both report one), the entry exposing the most wgpu features
is used, i.e. the newer version (68 against 60 here, a strict superset).
Without such a duplicate the choice is wgpu's own, and entries of one GPU from
different drivers (RADV and AMDVLK, a vendor's driver and Dozen) are left
alone.
graphics::apply installs it as egui-wgpu's native_adapter_selector in the
default mode on Windows, as #609 was asked to be rebased onto the #827
start-up. DirectX 12 only, software mode and the other platforms keep their
adapter choice, and the ladder is untouched. The adapters and the choice go
to the log.
Tests: the laptop's listing (wgpu's rule picks the entry that crashed, choose
the other one), listings without two versions of one driver over every power
preference (choose equals wgpu's rule), surface, bus id and driver cases, and
one against the adapters of the machine running the tests (the rule is what
request_adapter returns there).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Problem: Premiere reads Radiance HDR (.hdr) pictures; FilmCraft listed
the extension nowhere and could not open them.
Fix: clean-room RGBE decoder in filmcraft-media from the public format
description: header with FORMAT, all eight axis orders, flat, old
run-length and new per-channel run-length scan lines. Frames are linear
float (Rec.709), like EXR. Allocations are bounded by the data present;
hostile headers and truncated data return errors. "hdr" joins the
importable still extensions; stills and image sequences share the decoder.
Output equals ffmpeg's decode exactly for flat and RLE files; EXPOSURE and
PRIMARIES lines are ignored and XYZE is refused.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Problem: Shift snapped pen segments (#853) but dragging an existing mask
vertex or the whole mask still moved freely.
Fix: while Shift is held the movement since the press is constrained to
horizontal/vertical/diagonal steps (accumulated raw vs applied totals per
gesture, so each frame's delta stays a single undo step). Unit test for
the pure step function; gaps.md G18 updated.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Problem: OpenEXR files with PXR24 compression (a common lossy 24-bit
float format from renderers and compositors) were refused with "not
supported yet".
Fix: decode PXR24 chunks (16 lines, deflated byte planes of per-line
sample differences; floats keep their top 24 bits) into the existing
scan-line pipeline. Bounds-checked, no panics on damaged data. Tests:
hand-built chunks with half/float/uint channels and a partial last
chunk, damaged-file cases, and an OpenImageIO-written oracle comparing
our decode with OpenEXR's own (skips without oiiotool). Docs updated;
stale AVI / FLAC-in-MP4 entries removed from G10.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Problem: the Matroska demuxer mapped A_MPEG/L2 and A_MPEG/L3 only, so a
Layer I track was reported as an unsupported codec even though the MP1/MP2
decoder (#809) reads Layer I.
Fix: add Codec::Mp1 for A_MPEG/L1 and route it through the same packet
duration and decoder path as Mp2. Test remuxes synthetic Layer I frames
into Matroska with ffmpeg and compares our decode (max error 3e-5).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
A tiny header naming ~1000 channels on a 64M-pixel line sized each chunk
buffer at hundreds of GB and Vec::with_capacity aborted the process.
Chunks over 256 MB are refused, and the RLE / zlib buffers reserve with
try_reserve_exact. Regression test: huge_scan_lines_are_refused_before_allocating.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The interface had no face for Arabic or Hebrew, so caption text, track and media names showed
missing-glyph boxes in the Text panel and on the timeline while the Program monitor drew them.
crates/ui-egui/src/rtl.rs adds the craft-fonts Arab / Hebr faces, else one installed face per
script (Noto Sans Arabic, SF Arabic, Segoe UI...), as the last fallbacks of every UI font family,
as cjk.rs does for Chinese.
egui already shapes each font run with harfrust, so the letters join, but it lays runs out left to
right with no bidi pass and multi-word Arabic read backwards. rtl::Lines lays captions out in
reading order: lines are broken on the logical text as egui wraps it, each line is reordered with
unicode-bidi (UAX #9, as crates/text does for titles) and every right-to-left word is its own
layout section for egui's shaper. Caption blocks on the timeline and the Text panel caption list
use it; a caption being edited keeps egui's logical order, since TextEdit places the caret by
character index. Left-to-right text is laid out exactly as before.
Problem: .dng files were not importable, and a DNG (a TIFF) would have
gone to the TIFF decoder, which reads at best the embedded preview. A
CinemaDNG shot (a folder of numbered DNG frames) could not be edited.
Fix: "dng" joins the importable still extensions. filmcraft-media
develops DNGs with filmcraft-dng into scene-linear Rec. 709 float
frames (transfer Linear, like OpenEXR), so highlights above 1.0 reach
the compositor. A single DNG is a still; a numbered folder imports
through the existing image-sequence path as one clip, whose frame rate
and start time code come from the first frame's CinemaDNG FrameRate and
TimeCodes tags (else Settings > Media > Indeterminate Media Timebase).
Lossy and JPEG XL DNGs are refused as unsupported by name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Problem: the post stage of a VP9 frame (band assembly, loop filter,
publication) ran on one thread. Later frames wait per band for their
references, so the serial loop filter (~30 % of the profile) set the
pace of every inter frame; 16 threads were only ~1.8x one thread.
Fix: rows of a frame's loop filter run as a wavefront, row r two
superblocks behind row r - 1 (what the raster order of 8.8.1 allows),
taken by the post thread and helper jobs on the decoder's pool. No lock
is held while a superblock is filtered: each row filters its own band
and rows exchange copies of the 8 rows the filter reaches across a band
boundary. Output stays bit-identical; one thread / wasm run the rows in
order as before. A row is only taken by a running thread, so waits
always make progress, and helpers run under catch_unwind (a failure
publishes placeholder bands as before). New conformance fixture
narrow_tall (2 superblock columns) and a repeated multi-thread 1080p
decode test. 1.2-1.5x faster with 16 threads on a shared machine.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review on #827: a start marker left pending by any early exit (force-quit,
hang, unrelated crash, two launches racing) moved the user down a rung for
good, with no way back but deleting a file.
- Each start writes its own attempt file (graphics-start/<pid>-<n>.json)
that is removed after two painted frames, on a normal exit, or 15 s after
launch whatever happened. A later launch only counts attempts that are
older than that window, so a hang, a later crash or force-quit, and an
instance that is still starting are never failures.
- graphics-start.json now holds {mode, failures}: two failed starts in a row
in the same mode move on to the next one; a start that paints resets the
count. A wgpu start-up error still moves on at once.
- All files are written to a per-process temp name and renamed into place,
so concurrent launches never see or leave half a file.
- A fallback mode shows a notice (graphics.notice) with Use Default Graphics
(graphics.notice.reset) and Close; es, fr, ja and zh-cn strings.
- --graphics default|dx12|software picks a mode by hand and keeps it;
`--graphics default` undoes a fallback. Modes this platform lacks are
refused.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ffmpeg's CLI handles Matroska priming differently by version: 9.x trims
CodecDelay, 2023-24 static builds keep the priming samples (AAC 1024,
AC-3 / E-AC-3 256, MP3 1105), so a +-40-sample best-lag search against
its decode landed on a spurious minimum (4 or 24 samples).
matroska_audio_oracle now has ffmpeg encode a reference signal we
generate (low-passed xorshift noise) and asserts our decode lines up
with that signal to the sample, the stream's own CodecDelay trimmed:
version-independent, and it fails on the old ms-rounded trim (-16).
ffmpeg's decode is compared after a best-lag search bounded by the
CodecDelay; its lag is reported, the waveform must match (< 1e-3).
E-AC-3 joins the cases. eac3_oracle's .mkv case accepts lag 0 or the
256-sample priming; MP4 / MOV / TS stay strictly at lag 0.
Verified with ffmpeg 9.0.2 and the static N-71064 (2024-06) build.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Conflict in apps/filmcraft/src/main.rs: kept the graphics start loop and
app_creator, and carried over --control-input-only (#881) into them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Problem: the AV1 in-loop filters ran on one thread per frame (postfilter::apply
had an unused pool). On a single-tile stream they are a quarter to a third of a
frame, and since references are waited for as whole frames, that serial tail
sat on the critical path of every frame that references it.
Fix: the three filters run in bands of 64 luma rows on the decoder's existing
rayon pool, one join per filter per frame. CDEF and loop restoration write only
their own rows of a separate output. The loop filter's edges never touch the
same sample, and only a band's top horizontal edges reach into the band above:
each band refilters the vertical edges of a copy of the 8 rows above it, runs
those edges against the copy, and the samples they changed are written back.
With one thread the code path is the old one. Output stays bit-exact (1 vs 16
threads vs libdav1d; new 4:2:2 / 4:4:4 libaom fixtures). perf.rs gains a
low-delay 1080p and a single-tile 2160p fixture and the all-core stage
breakdown. 16 threads: 1080p GOP 1.16-1.30x, low delay 1.28x, 2160p 1.14x.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Problem: OpenEXR sequences (#806) were written uncompressed only, because the
workspace had no deflate encoder; the files are 2-4x larger than necessary and
Premiere offers an EXR compression choice.
Fix: a small clean-room DEFLATE (RFC 1951) encoder in a zlib (RFC 1950)
wrapper in filmcraft-export (greedy LZ77 with bounded hash chains; stored,
fixed or dynamic Huffman per block, whichever is smallest; length-limited
codes). The EXR writer gains RLE, ZIPS (1 line) and ZIP (16 lines) chunks with
the format's byte interleave + delta predictor; chunks that do not shrink are
stored raw. New ExportSettings::exr_compression (default None, as before),
`exrCompression` param on file.exportMedia, Compression combo in Export mode
(es/fr/ja/zh-cn strings).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Problem: Indonesian, one of the 12 key languages in
docs/localization-parity.md, had no catalog (0%), so Indonesian users got
the interface in English and System Language never picked Indonesian.
Fix: add crates/ui-egui/src/i18n/id.tsv, clean-room translations of every
entry in the Spanish catalog plus the native macOS Settings menu (3,541),
with a glossary in its header. Language::Id ("Bahasa Indonesia") is
persisted as `id`; it appears in Edit > Language (also the native macOS
menu), Settings > General, and System Language picks it for `id-*` locales
and the legacy `in-*` code older Java/Android runtimes report. Tests cover
the core menus, reject entries without a Spanish counterpart, check glyph
coverage with the bundled fonts, switching/persistence and locale
matching. Attribution sidecar, ATTRIBUTION.md row, docs (gaps.md G14,
localization-parity, ROADMAP, ui-egui README) updated.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Problem: the Portuguese (Brazil) catalog, one of Premiere's interface
languages and one of the 12 key languages in docs/gaps.md G14, covered
only the menus (348 of ~3,540 strings); panels, dialogs, Settings pages,
effect names, the Keyboard Shortcuts dialog and status messages showed
in English.
Fix: pt-br.tsv now has every entry Spanish has plus the native macOS
Settings item, written from the plain meaning of the English text with
ordinary Brazilian video-editing vocabulary (infinitive commands,
polite imperative instructions, sentence case); the header records the
conventions and a term glossary. Like Japanese and French, tests
require only the menus (now every menu label, not just the core set),
so later strings show in English until translated; a Portuguese entry
without a Spanish one fails, and a new test checks the bundled fonts
cover every Portuguese letter. The core-menu set that Ukrainian,
German, Russian and French must cover moves from pt-br.tsv (no longer
menus-only) to the menus-only Ukrainian catalog, unchanged in content
for those tests. Attribution sidecar and ATTRIBUTION.md entry added.
Docs: G14, localization-parity, ROADMAP language row, ui-egui README.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Problem: the pen mask tool ignored Shift, so straight horizontal,
vertical or diagonal mask edges could not be drawn precisely (Premiere
26.0 constrains mask lines with Shift). Separately, path_from_json
accepted the `{"p","in","out"}` vertex form through serde first, which
silently dropped the `in`/`out` tangents, so a pen mask's dragged-out
curves were committed as corners.
Fix: with Shift held, a new pen point snaps onto the nearest 45° ray
from the previous point (in screen space), the rubber-band preview
follows, and a Shift-drag constrains the Bézier handle it pulls out.
path_from_json now parses the vertex forms before falling back to the
serialized MaskPath.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Problem: the desktop app picked the project among its launch files with a
case-sensitive ends_with(".fcproj"), so CUT.FCPROJ opened through a Windows
file association or the Linux MIME glob was imported as media. Relative
launch paths were stored as given and went missing from another folder.
std::env::args panicked on a non-Unicode argument. filmcraft-cli import
used canonicalize, which stores \\?\ verbatim paths on Windows.
Fix: args::project_and_media finds the project by a case-insensitive
extension and makes every launch path absolute with std::path::absolute
(no verbatim prefix, symlinks kept). Both apps read args_os; the desktop
app keeps --data-dir exactly. filmcraft-cli import uses std::path::absolute.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Problem: masks.roundCorners (#813) rounded rectangle/polygon mask corners only from the
command line, control channel or MCP; the Program monitor had no Premiere 26-style
corner-roundness handle.
Fix: a selected mask with sharp corners between straight edges shows a roundness handle
just inside its top-left corner (automation id program.mask.roundness). Dragging it into
the shape moves it along the corner bisector, previews the rounded outline live with the
radius, and on release dispatches one masks.roundCorners (one undo step per drag). The
radius is capped where no corner can grow further. Ellipses, pen masks and rounded corners
show no handle. Tooltip translated (es/fr/ja/zh-cn). New headless UI test
mask_roundness_ui; docs/gaps.md G18 updated.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Problem: the cpal error callback marked the stream failed on its first
error of any kind, so the UI stopped audio and played the rest of the
playback without sound. cpal's ALSA backend reports transient errors
(POLLERR after an xrun, spurious poll returns, short writes) that way
and keeps the stream alive; after POLLERR it no longer calls back.
Reports: a second or two of audio, then silence while meters move (#23).
Fix: AudioOut::recover, called every playback frame, lets CpalOut
restart the stream: at once when the device is gone, or when an error
was reported and no samples were consumed for 300 ms. The restarted
stream reuses the shared play-ahead mixer and played-frame clock and
must keep the sample rate and channel count; at most 3 restarts in
10 s, then playback falls back to the wall clock as before. Errors
while samples keep flowing no longer silence playback. The device
callback runs the mixer under catch_unwind: a panic plays silence.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Problem: no test did a real editing job end to end. Codec and container
oracles exist, but nothing imported phone- or camera-like media, edited
it through the command layer and checked the exported file (G6).
Fix: apps/filmcraft-cli/tests/workflows.rs drives the `filmcraft-cli mcp`
binary over stdio on media generated with ffmpeg into target/fixtures:
- a VFR H.264 + AAC MP4: sequence from clip, extract, -6 dB audio gain,
title, H.264 MP4 export;
- a long-GOP H.264 MKV with AAC + AC-3 tracks: ripple trims, colour matte,
centred cross dissolve, save; a new CLI process exports ProRes.
ffprobe / ffmpeg check streams, frame count, duration, which source frame
lands where, A/V sync across the cut, title pixels, the dissolve mix and
audio levels. The tests skip without ffmpeg (testkit oracles).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Problem: DPX import rejected the 10/12-bit packed layout (packing 0)
and the Y'CbCr descriptors (100 CbYCrY, 101 CbYACrYA, 102 CbYCr,
103 CbYCrA) with an "unsupported" error.
Fix: unpack packed components back to back, low bits first, each scan
line on a 32-bit boundary (as ffmpeg reads 12-bit packed files), and
convert Y'CbCr to RGB with the matrix the colorimetric field names
(BT.709; BT.601 for the 601 and composite codes; by picture size when
unspecified), in video range unless the reference codes span the full
code range; 4:2:2 chroma is shared by each pixel pair. Unit tests for
every new layout and hostile headers; an ffmpeg oracle test builds
packed 12-bit RGB and 8-bit 4:2:2 / 4:4:4 / 4:4:4:4 files from
ffmpeg's test picture and matches its decode within one 8-bit step.
Co-Authored-By: Claude Opus 5.5 <[email protected]>