Commit Graph

155 Commits

Author SHA1 Message Date
Brandon Thomas e61900cd3a Merge pull request #1302 from LloydNicholson/fix-1291-adts-aac-import
codecs: import raw ADTS .aac files (#1291)
2026-10-13 00:03:21 -04:00
Brandon Thomas 6c06ab14ca Merge pull request #1492 from marcoarnulfo/fix/hevc-pps-trailing-zero
fix(codecs): in-band parameter sets compare without trailing zero bytes
2026-10-13 00:03:15 -04:00
Brandon Thomas 2d2e701078 Merge pull request #1403 from vangel76/gap-mkv-mp1
#1402: import MP1 (A_MPEG/L1) audio from Matroska
2026-10-13 00:03:10 -04:00
Marco Arnulfo c1b1e86046 fix(codecs): in-band parameter sets compare without trailing zero bytes (fixes #1483)
ffmpeg keeps a trailing zero byte after the hvcC PPS that the in-band PPS lacks, so the byte-for-byte comparison read it as a parameter-set change and sent the stream to the software decoder from its first sample. A NAL unit never ends with 0x00 (H.264 7.4.1, H.265 7.4.2): those bytes are trailing_zero_8bits, so both sides are compared without them. The platform fallback test used a trailing zero byte as its changed SPS; it now changes nal_ref_idc.
2026-10-13 01:16:52 +02:00
vangel76 3516207de7 #1402: import MP1 (A_MPEG/L1) audio from Matroska
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>
2026-10-12 20:55:35 +02:00
Marco Arnulfo 3f61c036ff codecs: MP4 frames stamped just after the frame grid show on their own frame (fixes #1282)
Phone footage stamps its frames up to a millisecond after the frame grid
(a first frame 34.1 ms long at 30 fps). The frame shown at a time is the
last one starting at or before it, so a sequence frame asked at its exact
start got the picture before: the clip ran one frame behind and showed
frame 0 twice. The request is already rounded to the track timescale
(#73, as WebM in #83), but at 90 kHz a millisecond is 90 units.

When opening an MP4, the video samples of a file that is a constant rate
gone slightly off its grid are put back on it: the grid starts at the
first picture and steps by the nominal frame duration, and the file is
snapped only when every picture is less than a quarter of a frame from
its own grid point and no two pictures share a grid point. Anything
further off is variable frame rate and keeps all its times; a picture one
unit off is left where it is, since that can't be told from timescale
rounding. Only the video sample times move: the audio, the first picture
and the clip duration are unchanged.
2026-10-12 16:50:49 +02:00
lloyd 52b36f9764 codecs: import raw ADTS .aac files (#1291)
File ▸ Import and the Media Browser offer `.aac` (it is in AUDIO_EXTENSIONS),
and Export ▸ AAC Audio writes ADTS `.aac` (#814), but no opener read them:
`audio::opener` only accepted mp3/mp2/flac/ogg/aif by extension, so every
`.aac` failed with "unrecognised format".

`AudioFileSource` now decodes a standalone ADTS stream with our AAC decoder
(ISO/IEC 13818-7 §6.2): frame after frame from the first header (after an
ID3v2 tag), resyncing past damage, skipping frames with another
configuration, keeping a frame that doesn't decode as silence, and
upsampling HE-AAC to its output rate. Decoding stops with an error past
4 GiB of samples instead of allocating without bound.

Tests encode a stereo tone with our encoder's ADTS output and import it
(rate, channels, duration, level), and run truncated, bit-flipped and
bad-length streams under catch_unwind.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:01:33 +02:00
Brandon Thomas 85357ede03 codecs: gapless trimming for MP3 files only
Other formats' start and priming (AAC edit lists, …) are handled and
oracle-tested elsewhere; enable_gapless for them could move their audio.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:26:15 -04:00
Brandon Thomas 812a7108d0 Merge remote-tracking branch 'origin/main' into fix-1165 2026-10-11 22:25:08 -04:00
Brandon Thomas f704b46906 Merge pull request #1064 from MrCloudy2/feat/vp8-decoder-432
#432: VP8 video decodes (clean-room pure-Rust VP8 decoder), so VP8 WebM no longer shows black
2026-10-11 21:02:52 -04:00
Brandon Thomas 5ce44e2bd8 Merge pull request #650 from JordyUSA/feat/598-avi-import
#598: import AVI files (clean-room AVI / OpenDML demuxer)
2026-10-11 21:02:51 -04:00
Brandon Thomas 8ec1a36b9f Merge pull request #613 from JordyUSA/feat/flac-export
Export: FLAC (clean-room encoder; .flac files and FLAC audio in MP4/MOV)
2026-10-11 21:02:50 -04:00
Brandon Thomas 06e5ac6651 Merge pull request #819 from vangel76/gap-mp2-decoder
#809: own MPEG audio Layer I / II decoder plays MP2 at ffmpeg's default bit rates
2026-10-11 21:02:50 -04:00
Brandon Thomas 47b0688483 Merge main into #650; AVI frames pass an unspecified transfer to vui_color
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 21:02:34 -04:00
Brandon Thomas c7f6eccb18 Merge remote-tracking branch 'origin/main' into fix-650 2026-10-11 21:00:53 -04:00
Brandon Thomas dab9630d68 Merge pull request #846 from MrCloudy2/fix/export-hevc-stall-480-v2
#480: exports no longer stop for good when a clip's decoding uses rayon (rotated iPhone HEVC clips)
2026-10-11 20:29:56 -04:00
Brandon Thomas 388bef268c Merge pull request #984 from JordyUSA/fix/922-aac-8ch
#922: eight-channel AAC reports and returns eight channels
2026-10-11 20:24:47 -04:00
Brandon Thomas 3f52a705e8 Merge pull request #989 from jahvari/fix/windows-baseline-win01
WIN: stabilize Windows verification prerequisites
2026-10-11 19:26:18 -04:00
Brandon Thomas cc57068acc Merge pull request #988 from JordyUSA/fix/924-frame-primaries
#924: decoded H.264 / HEVC frames keep the signalled colour primaries
2026-10-11 19:18:36 -04:00
Brandon Thomas 1661344ba6 Merge pull request #891 from vbasky/fix/823-multi-audio-import
Import every MPEG audio stream, not only the first PID
2026-10-11 19:18:35 -04:00
Brandon Thomas d89b2788ef Merge pull request #876 from vbasky/fix/848-longgop-seek-preroll
LongGOP seek / time-to-first-frame, not the H.264 throughput gap
2026-10-11 19:18:35 -04:00
Brandon Thomas a470620b8d Merge pull request #842 from vangel76/gap-dpx-packed-ycbcr
#841: import packed 10/12-bit and Y'CbCr DPX stills
2026-10-11 19:18:33 -04:00
Brandon Thomas 22fe1017bb Merge pull request #986 from JordyUSA/fix/923-mkv-reordered-rate
#923: reordered Matroska video without DefaultDuration keeps its frame rate
2026-10-11 18:21:37 -04:00
Brandon Thomas c3e28a08e4 Merge pull request #792 from vangel76/gap-mkv-codec-delay
#790: Matroska AAC / MP3 / AC-3 trims the exact CodecDelay
2026-10-11 18:21:36 -04:00
MarekSpodBiedry bc0ef5ad94 Trim MP3 encoder delay when decoding audio files (#1137) 2026-10-11 23:41:55 +02:00
MrCloudy2 53a0d959fe #432: VP8 video decodes (clean-room pure-Rust VP8 decoder)
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>
2026-10-11 15:13:07 +02:00
JordyUSA 0166dc1d6c #924: decoded H.264 / HEVC frames keep the signalled colour primaries
vui_color took the VUI matrix, transfer and range but not the primaries,
so every decoded H.264 / HEVC frame reported BT.709 primaries. Stream info
read them, so a BT.2020 file without a colr box said BT.2020 in its info
and BT.709 on its frames (Rec709 instead of Rec2020 for the renderer).
Pass colour_primaries through, for the software and hardware decoders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:35:06 +07:00
JordyUSA 424ec6014b #923: reordered Matroska video without DefaultDuration keeps its frame rate
Without DefaultDuration the rate came from the median step between
neighbouring blocks in file order. Blocks are in decode order, so with
B-frames neighbours are two or more frames apart in presentation time and
a 25 fps clip reported 12.5. Step between the sorted presentation times
instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:28:42 +07:00
JordyUSA d348dccd21 #922: eight-channel AAC reports and returns eight channels
The MP4 reader took the AudioSpecificConfig's channel configuration as a
count, but it is an index into ISO/IEC 14496-3 Table 1.19: configuration 7
(7.1) reported seven channels and dropped the last decoded one, and 12
(7.1 back, what ffmpeg 9 writes for 7.1) reported twelve. Count from the
table (with Amd 4: 11 → 7, 12 and 14 → 8, 13 → 24), and decode
configurations 11 and 12, reordered to the WAV/SMPTE order used for 5.1.
Checked against ffmpeg's decode channel by channel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:26:28 +07:00
vangel76 a147e2551e #809: MP2 Matroska oracle robust to older ffmpeg; bounded ID3 skip and subbands
ffmpeg's CLI handles Matroska priming differently by version: 9.x trims
the 481-sample CodecDelay, 2023-24 static builds keep the priming
samples, so comparing from sample 2 put the priming ramp against our
tone (max error = peak 0.125). The .mkv case now searches lags within
two samples of 0 (trimmed) and of the stream's own CodecDelay (kept),
over the interior of both decodes; the plain .mp2 file stays at lag 0.

Review nits: the ID3v2 skip offset uses saturating_add (test with the
largest tag size); every Layer II table's sblimit is checked <= 32 at
compile time and clamped to SUBBANDS before indexing the per-subband
arrays (row() is a match, so it cannot index out of range).

Verified with ffmpeg 9.0.2 and the static N-71064 (2024-06) build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 08:56:31 +02:00
vangel76 c2c7f1206c #790: Matroska audio oracle checks the encoded signal, robust to older ffmpeg
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 <noreply@anthropic.com>
2026-10-11 08:53:27 +02:00
vangel76 3b48565ecb Merge remote-tracking branch 'upstream/main' into gap-mkv-codec-delay 2026-10-11 08:48:22 +02:00
JordyUSA 773e1a3c72 Merge origin/main into feat/flac-export
Keep FLAC beside main's new AAC Audio format (21 formats, both audio-only
presets), use is_mp4_with / is_mp4_video in audio_codec() and the Audio
Format picker so MP4 (incl. AV1) offers AAC or FLAC, and add flacLevel to
main's export.media params.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:32:13 +07:00
Mara Johnson e283f4dee7 fix(media): RF64 trailing metadata stays outside audio duration
Use ds64.dataSize for an RF64 data-size sentinel instead of the rest of
the file, so chunks after the audio are no longer decoded as samples.

Fixes #925
2026-10-10 23:57:14 -04:00
jahvari 61df55ff43 WIN-02: Make parallel GOP cache test deterministic
Co-Authored-By: Codex (GPT-6) <noreply@openai.com>
2026-10-10 22:39:51 -05:00
Vikram Bhaskaran f733230b9f
#823: import every MPEG audio stream, not only the first PID
Transport and program streams listed a single audio stream, so file.import
and New Sequence From Clip kept one clip. Probe every audio PID (capped),
decode each independently, and place linked clips per stream. MP4/MKV
already did this; MXF same-rate PCM is still interleaved as one stream.
2026-10-11 13:01:20 +11:00
vangel76 b74202696d Merge remote-tracking branch 'upstream/main' into HEAD 2026-10-11 03:00:38 +02:00
Vikram Bhaskaran f9f7ea4666
#848: LongGOP seek / time-to-first-frame, not decoder throughput
Pin GOP-cache eviction on the requested pts so a burst past the playhead
does not drop the target and walk the GOP again. Preroll only schedules
the frames that gate clock start. bench_playback no longer meters preroll
jobs as on-clock latency.

This is the seek stall from #848, not the ~3× per-core gap vs ffmpeg.
2026-10-11 11:35:59 +11:00
vangel76 42b990b72b #841: import packed 10/12-bit and Y'CbCr DPX stills
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 <noreply@anthropic.com>
2026-10-11 01:26:31 +02:00
MrCloudy2 f79a51dd24 #480: exports no longer stop for good when a clip's decoding uses rayon
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>
2026-10-11 01:16:43 +02:00
vangel76 bad190d234 Merge remote-tracking branch 'upstream/main' into gap-mp2-decoder
# Conflicts:
#	docs/codec-parity.md
#	docs/gaps.md
2026-10-11 01:07:54 +02:00
vangel76 fe52bc0b2c #821: import Targa and DPX stills and image sequences
Problem: Premiere imports Targa and DPX as stills and numbered sequences.
FilmCraft exports both (#779) but could not read either: the extensions
were not importable and the image crate has no DPX reader, so even our
own Targa / DPX renders could not come back in.

Fix: clean-room decoders in filmcraft-media. Targa from the TGA 2.0 spec:
image types 1/2/3 and RLE 9/10/11, colour maps, 15/16/24/32-bit, 8/16-bit
grayscale, any origin. DPX from SMPTE ST 268: first element, luma / RGB /
RGBA / ABGR, 8/16-bit, filled 10/12-bit (methods A and B), either byte
order, padded lines, orientations 0-3. Both bound allocations by the
data present and reject hostile headers with errors. Stills and image
sequences share one decode_raster entry point; "tga" and "dpx" join the
importable still extensions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:58:15 +02:00
vangel76 39afd2f6c3 #809: own MPEG audio Layer I / II decoder plays MP2 at ffmpeg's default bit rates
Problem: symphonia rejects every Layer II frame whose bit rate / mode
combination ISO/IEC 11172-3 does not allow (mono above 192 kbit/s, stereo
below 64 kbit/s). ffmpeg writes mono MP2 at 384 kbit/s by default, so such
tracks played as silence in MPEG-TS / PS, Matroska and raw .mp2 files.

Fix: new L0 crate filmcraft-mpa, a clean-room Layer I / II decoder written
from ISO/IEC 11172-3 and 13818-3 (all modes incl. joint stereo, MPEG-1/2/2.5
rates, the disallowed combinations decoded with the allocation table their
bit rate per channel selects). PacketDecoder::mpeg_audio uses it for layers
I and II (packets of several frames too); raw .mp2 files decode with it,
resyncing after damage. Oracle tests vs ffmpeg: 73-84 dB SNR on ffmpeg /
twolame streams and synthetic Layer I / joint stereo frames; fuzzed damaged
frames never panic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:48:45 +02:00
vangel76 c9b9bf3aae #800: MP2 audio in MP4 and MOV decodes instead of playing silent
Problem: MP4 and QuickTime name MPEG audio without its layer (ffmpeg
stores MP2 as `mp4a` + esds object type 0x6B), and we always made the
layer III decoder for it. symphonia's decoder reads only the layer it
was made for, so every MP2 frame was rejected and the track played as
silence; MOV's `.mp2` sample entry was not recognised at all.

Fix: the MPEG audio decoder for MP4 / MOV is made on the first packet,
for the layer its frame header names (ISO/IEC 11172-3 2.4.2.3);
`.mp2` maps to the MPEG audio codec like `.mp3`; the track is labelled
"MPEG Audio" for layers I/II, as Matroska's is. Decoded tracks now match
ffmpeg sample-exactly (edit list trims the 481-sample encoder delay).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:26:39 +02:00
vangel76 3b1fa29663 #790: Matroska AAC / MP3 / AC-3 trims the exact CodecDelay
Problem: ffmpeg stores the encoder priming as CodecDelay in ns (1024 AAC
samples at 48 kHz = 21.333 ms, 256 AC-3 samples at 44.1 kHz = 5.805 ms)
and block timestamps in ms. The demuxer subtracts the delay rounded to
1 ms, and fixed-length packets were anchored on that rounded stamp, so
the whole track played up to 16 samples late against ffmpeg's decode.
Only Opus undid the rounding.

Fix: one stamp-to-samples conversion for every Matroska audio codec
that undoes the demuxer's rounding and removes the exact CodecDelay
(rounded to the nearest sample; the element holds whole ns), with
checked math against hostile timebases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:59:15 +02:00
vangel76 be98b9d7f7 #647: decode E-AC-3 (Dolby Digital Plus) audio
Problem: files with E-AC-3 audio (MP4 ec-3, MOV, Matroska A_EAC3, MPEG-TS)
imported silent: filmcraft-ac3 refused bsid 11-16.

Fix: clean-room E-AC-3 decoding from ATSC A/52:2012 Annex E in filmcraft-ac3:
bsi and audfrm syntax, 1/2/3/6-block syncframes, reduced sample rates,
frame-based exponent strategies (exponents reused across short syncframes),
SNR offset strategies, default band structures, spectral extension
(translation, notch filter, noise blending). Independent substream 0 is
decoded; dependent substreams and other programs are skipped (7.1 plays as
its 5.1 core). AHT and enhanced coupling are refused as unsupported. The
audio blocks share the AC-3 path; AC-3 output is unchanged. filmcraft-codecs
routes E-AC-3 in MP4/MOV, Matroska and TS to the decoder; the TS splitter
joins dependent substream frames to their access unit.

Tests: ffmpeg oracle for raw E-AC-3 (64.8-91.0 dB SNR) and in MP4, MOV,
Matroska and TS (79.5-91.3 dB, random access identical), mutation fuzz,
synthetic frames for headers, substream skipping, refusals and exponent
reuse across 1-block syncframes, spectral extension unit tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:49:03 +02:00
Brandon Thomas c4bb63bb6d
Merge pull request #772 from alex-brazh/fix/open-gop-leading-pictures
codecs: an open GOP's leading pictures decode from the GOP before, not as black frames
2026-10-10 17:01:42 -04:00
Brandon Thomas 47fc0677c7
Merge pull request #762 from vangel76/fix-626-unsupported-video-slate
#626: undecodable video shows the MEDIA UNREADABLE slate, not black
2026-10-10 17:01:19 -04:00
Alex Brazh 45f2b8e297 codecs: an open GOP's leading pictures decode from the GOP before, not as black frames
In an open GOP (iPhone HEVC; x265 `open-gop`) each CRA after the first is
followed in the file by RASL pictures shown before it, predicted from the
GOP before. GopCache started a decoding run at `sync_before(i)`, the sync
sample at or before the wanted sample in decoding order: for a RASL
picture that is the CRA after it, and the decoders (software and
VideoToolbox) rightly leave a run's leading RASL pictures out. The frame
was never produced, so the request failed ("frame not produced") and the
clip's layer was left out: a black frame (or the track below) wherever a
seek landed on one of the 3-in-24 RASL frames of iPhone video, such as a
clip trimmed to start on one, the parked playhead, and parallel export.

- `decode_start`: a sample shown before its sync sample starts the run at
  the sync sample before (the MPEG and MXF readers already did this in
  their own `sync_before`; this covers MP4/MOV and Matroska too).
- A running decoder that started after that point is not continued for
  it: it left the picture out.

Tests: a synthetic open-GOP stream in gop.rs, and an x265 open-GOP oracle
test (MP4 and Matroska, every frame from a fresh source and each CRA
before its leading pictures) against ffmpeg. Both fail without the fix.
2026-10-10 23:14:29 +03:00
Brandon Thomas 4768d1a582
Merge pull request #745 from vangel76/fix-715-mkv-timestamp-origin
#715: a Matroska remux that keeps its timestamps starts at its first picture
2026-10-10 15:52:43 -04:00