#630 shipped German for the menus. This adds the other 3,213 strings, so
de.tsv has every entry of the Spanish catalog (3,590): panels, dialogs,
effects and their parameters, transitions, presets, Settings, shortcut
commands, export presets, tooltips and status messages. Same style as the
menus (du, infinitives, placeholders, ellipses and escapes kept); the
glossary in the header is extended. The 377 menu entries are unchanged.
german_entries_cover_the_original_menu_catalog required every entry to be
a menu label. It becomes german_covers_the_menus_and_has_no_dead_entries,
like the French and Italian tests: every entry needs a Spanish counterpart,
the core menus and the native macOS app menu are required, later strings
may stay English. german_renders_with_bundled_fonts checks the umlauts,
ß and German quotation marks against the bundled fonts.
Docs: ROADMAP.md, docs/localization-parity.md, docs/gaps.md, ui-egui README.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KJhvSJ5odpWzFw9HhPzAix
Letter spacing (Tracking) does animate in the engine — keyframes on the text
layer interpolate and the rendered width follows them — but the Essential
Graphics panel had no way to start that: its number and point rows had no
stopwatch, so a change there always set the value for the whole clip.
Every animatable number/point row in the panel (Tracking, Leading, Font
Size, Baseline Shift, Position, Anchor, Scale, Rotation, Opacity…) now has
the same stopwatch as Effect Controls. It calls `effects.toggleAnimation` on
the layer; once a property animates, a change in the panel adds or updates a
keyframe at the playhead (graphics.set was already keyframe-aware). A
Type-tool character selection edits character styles, which have no
keyframes, so those rows show no stopwatch while it is active.
Automation id: `graphics.prop.<id>.stopwatch`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L8DvNHjchh5Gf2q24UsWQn
Media and proxy paths read from a project file, a project template or an
interchange file are opened only when they name a local file or a file on
the network share the document itself is on (craft-docpath); a relative
path is taken from the document's folder. Other paths count as missing:
the item renders the offline slate with the reason, the open says how many
were not opened, and Link Media lists them. This covers the open-time
scan, rendering and proxies, the growing-files look and interchange
import. Files imported or linked in this session are opened as given.
The Scratch Disks folders use the same check, and Captured Audio and Video
(voice-over takes, pasted images, the default proxy folder) and Audio
Previews follow the rule Auto Save and Video Previews already follow.
Project Auto Save and Video Previews folders named in a project file are used
only when they are inside the project's own folder or were chosen in Project
Settings > Scratch Disks on this computer (remembered in the preference
autoSave.scratchFolders). Other folders fall back to the default location,
with a log line and a message when the project opens. Network-share and
device paths are refused for these folders.
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 <noreply@anthropic.com>
Adds Korean as an interface language: Edit ▸ Language ▸ 한국어, Settings ▸
General ▸ Interface Language, and System Language for ko / ko-KR locales.
- crates/ui-egui/src/i18n/ko.tsv: 3,573 entries, the Spanish catalog's sources
plus the macOS Settings item, written clean-room from the plain meaning of
the English text (sidecar ko.tsv.attribution, ATTRIBUTION.md row).
- Hangul is drawn with the installed system face cjk.rs already adds (Malgun
Gothic, Apple SD Gothic Neo, Noto Sans CJK KR…); switching is refused with a
message when there is none, like Chinese.
- Test: korean_covers_the_menus_and_has_no_dead_entries.
- Docs: localization-parity, ROADMAP, ui-egui README.
Native-speaker review welcome.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013XWUReif4gqcjU5GsjYioc
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
The APV demuxer is new in ffmpeg 8.0, so ubuntu-24.04 runners failed instead of skipping. testkit gains ffmpeg_with_format_or_skip: when ffmpeg -formats does not list apv it SKIPs naming the detected version, failing only under FILMCRAFT_REQUIRE_ORACLES=1. The apv oracle uses it; docs/testing.md notes ffmpeg >= 8.0.
Fixes#1249.
- AudioSettings gains `float`; `bits` takes 16, 24 or 32. `SampleFormat` (Int16 / Int24 /
Int32 / Float32) and `ExportSettings::supports_sample_format` decide what each codec and
container stores: WAV, AIFF and QuickTime PCM all four, MXF integers, FLAC integers.
`validate` refuses float without 32 bits and float in FLAC or MXF.
- WAV float is WAVE_FORMAT_IEEE_FLOAT (EXTENSIBLE for more than two channels) with a `fact`
chunk; AIFF float is AIFF-C `fl32`; QuickTime float is `lpcm` with the float flag; MXF writes
32-bit integers. Float keeps the mix as it is, peaks above 0 dBFS included; 16 and 24 bit
write the same bytes as before.
- FLAC encoder: 8-32 bits. 32-bit stereo is coded independently (a side channel would need 33
bits), the sample range check no longer overflows, and 32-bit frames point at STREAMINFO for
the size, which symphonia 0.5 (FilmCraft's own FLAC import) and ffmpeg both read.
- Export mode: Sample Size shows 16 bit, 24 bit, 32 bit and 32 bit float like Audio Format
does, greying out what the format cannot store with the reason on hover; a size that stops
being available (float after switching to FLAC) becomes 32-bit integer. es, fr, pt-br, id,
ja and zh-cn strings.
- Tests: writer layouts and ffprobe codecs for every new format, round trips through ffmpeg
and FilmCraft's own readers in WAV, AIFF, MOV, MXF and FLAC, headroom of a +6 dB tone,
refused combinations, full-scale 32-bit FLAC through ffmpeg (MD5 too) and symphonia, and a
UI test of the selector.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015nzevSsTNZxHPpUngfGTAw
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 <noreply@anthropic.com>
Split from #728 on current main. Full validation is recorded in the draft PR; the trim and mask-modifier failures reproduce on unchanged main.
Co-Authored-By: Codex <noreply@openai.com>