Files
Aleksander Helgaker 006b50d2ae craft-media: shared codec identifiers, one ByteSource, the testkit (1/10) (#439)
* craft-media: shared codec identifiers, one ByteSource, the testkit

The types a codec crate needs to describe a file and configure a
decoder, ahead of craft-audio:

- `Container`, `CodecId`, `PcmFormat` and `CodecParams` (`id.rs`),
  always built, with optional serde.
- `codec_params()` on `isobmff::Track` and `matroska::Track`, both
  returning `CodecParams` in the same form; `extradata` follows
  Matroska's `CodecPrivate`.
- One `ByteSource` trait (`source.rs`), with `ReadSeekSource`;
  `isobmff::ByteSource` and `matroska::ByteSource` are that trait.
- craft-media-testkit (`publish = false`): the ffmpeg oracle helpers
  from craft-media's tests, so the codec crates share one copy.

The existing tests prove the refactor; the new types have their own.

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

* craft-media: APV extradata is the bare record; reseek after a poisoned lock

An MP4 apvC track gave CodecParams.extradata with the 4-byte FullBox header
in front, where Matroska's V_APV CodecPrivate is the bare
APVDecoderConfigurationRecord. ApvConfig::record_bytes serializes the record
alone and extradata uses it.

ReadSeekSource kept its tracked position after recovering a poisoned lock or
a failed seek, so a reader moved by a panic was read from the wrong offset
with no error. Both now force a seek on the next read.

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

* craft-media: MP4 codec params per sample description

Track::codec_params always described the first stsd entry, while each sample
selects its own through description_index. It now describes the first
sample's entry; codec_params_for gives the parameters of any entry, and
has_mixed_descriptions says whether one set covers every sample.

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

* craft-media: test file-backed ByteSources; a File read past i64::MAX is an EOF

A File and a ReadSeekSource over a file now have the buffer's test: the same
bytes at the same offsets, and UnexpectedEof past the end. The test found
that a File read at an offset past i64::MAX failed with the OS's
InvalidInput instead of the UnexpectedEof the trait promises; it now
reports the end of the source like the other implementations.

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

* craft-media: Matroska codec params match MP4 for ALAC, DNx and Opus

Matroska's codec_params gave Unknown for A_ALAC and for DNxHD/DNxHR stored
as V_MS/VFW/FOURCC (AVdn, AVdh), where MP4 gives Alac and Dnxhd; ALAC's
extradata is the CodecPrivate, the ALACSpecificConfig MP4's alac box holds.
Opus now reports 48 kHz from Matroska as from MP4, whatever rate the muxer
declared.

ReadSeekSource clears the poison when it recovers the lock, so one
panicked read costs one recovery instead of a seek on every later read.

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

* craft-media: MP4 reports the MPEG audio layer the first frame names

An MP4 track with MPEG audio reported CodecId::Mp3 whatever the layer, since
mp4a + 0x69/0x6B names none (ffmpeg stores MP2 that way), while Matroska
reports Mp1/Mp2/Mp3 from its CodecID. open() now reads the frame header of
each such entry's first sample and codec_params() reports the layer it
names; without a valid header, QuickTime's .mp2 gives Mp2 and anything else
Mp3 as before. CodecConfig::Mp3 is unchanged.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 14:18:36 +01:00
..