Commit Graph
4 Commits
Author SHA1 Message Date
c31fc02ab9 craft-media: MXF writer identification and metadata budget; ports of FilmCraft container PRs (#1794, #4100, #4497, #2303) (#989)
* craft-media: MXF identification on every writer entry point, and a total metadata budget

write_opatom_pcm_as takes the company, product and version that WriterConfig
already has, and the product UID is derived from the product name, so an app
that names itself writes the bytes it wrote before the move.

Everything read while opening shares a 256 MiB budget: a file made of metadata
sets, each under the 64 MiB value limit, is refused instead of read whole.

* craft-media-testkit: hostile-input hammer

Truncations, bit flips, extreme-value overwrites in both byte orders and
deletions of a valid input, each run under catch_unwind with a time budget.
CRAFT_MEDIA_HOSTILE_MULT multiplies the iterations for soak runs.

Ported from storytold/filmcraft#1794 (filmcraft-testkit's hostile module).

* craft-media: MXF hostile-input hammer

The hammer over three synthetic OP1a files and over an index segment, a primer
pack and a metadata set parsed directly, and the regression test for
sync_before on a track without samples (the fix is already in the module).

Ported from storytold/filmcraft#1794 (crates/mxf/src/tests/hostile.rs).

* craft-media: MXF picture descriptors give the H.273 code points of their colour labels

PictureInfo::color_codes maps the colour primaries, transfer characteristic
and coding equations labels the writer uses (Rec. 709, BT.2020, PQ, HLG) to
their code points, whatever the label's registry version byte.

Ported from storytold/filmcraft#4100 (the crates/mxf part).

* craft-media: MXF writer describes interlaced pictures, and the reader gives the field dominance

PictureDesc::interlaced writes the SeparateFields frame layout with a field's
stored and sampled height, the 1080i video line map and the field dominance
(bottom_field_first: 2). PictureInfo::field_dominance reads it back.

Ported from storytold/filmcraft#4497 (the crates/mxf part).

* craft-media: AVI hostile-input hammer

The hammer over the four synthetic layouts (idx1 relative and absolute, rec
lists, no index, OpenDML), opened with and without the OpenDML index.

Ported from storytold/filmcraft#1794 (crates/avi/src/tests/hostile.rs).

* craft-media: AVI says how an H.264 or HEVC stream frames its NAL units

The bytes after a video stream's format header are nothing, Annex B parameter
sets (ffmpeg with global headers) or an avcC / hvcC record (a stream copied
from MP4, with length-prefixed chunks). Stream::nal_framing tells them apart
and gives the length-prefix size, so a caller hands the decoder the header's
parameter sets and turns length prefixes into start codes. codec_params said
the bytes were never a record.

Ported from storytold/filmcraft#2303 (the container part of NalSetup in
filmcraft-codecs; the decoder wiring stays in the app).

* craft-media: MPEG-TS / PS hostile-input hammer

The hammer over transport streams (188- and 192-byte packets, six stream
types), program streams (MPEG-2 and MPEG-1 packs, private stream 1) and the
frame-header helpers for every codec. Fewer rounds than in FilmCraft, where
tests build with optimisation.

Ported from storytold/filmcraft#1794 (crates/mpegts/src/tests/hostile.rs).

---------

Co-authored-by: Logan Jennings <loganwjennings@gmail.com>
Co-authored-by: JonRC <jonarc06@gmail.com>
Co-authored-by: Michael de Meyer <249562202+oblivion8282-1337@users.noreply.github.com>
Co-authored-by: tylerjenningsw <tylerjennings2@outlook.com>
2026-11-11 19:46:48 -05:00
Dylan M. Taylor b16a2584fe craft-media tests: scratch files live in a directory only the test created (#839) 2026-11-11 15:00:02 -05:00
el-jandFredApps 41ff22dcd4 craft-media-testkit: shared media test helpers and ffmpeg oracle discovery (#701)
* media-testkit: shared test helpers and ffmpeg oracle discovery

* media-testkit: report fixture rename failure via skip; media: unlink relative craft-video path

* media-testkit: failed fixture generator honours CRAFT_MEDIA_REQUIRE_ORACLES

generate() returned None directly when the generator reported failure, so a
required-oracle run could pass with a missing fixture. Route it through skip,
like run_generator, and cover both policies with tests.

---------

Co-authored-by: FredApps <153173854+FredApps@users.noreply.github.com>
2026-11-10 18:56:30 +01:00
Aleksander HelgakerandClaude Opus 5.5 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