Skip to content

chore: release v0.0.10 - #7

Merged
MagicalTux merged 1 commit into
masterfrom
release-plz-2026-06-15T05-40-11Z
Sep 1, 2026
Merged

chore: release v0.0.10#7
MagicalTux merged 1 commit into
masterfrom
release-plz-2026-06-15T05-40-11Z

Conversation

@MagicalTux

@MagicalTux MagicalTux commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

🤖 New release

  • oxideav-mp4: 0.0.9 -> 0.0.10 (✓ API compatible changes)
Changelog

0.0.10 - 2026-08-15

Other

  • structure-aware MP4/ISOBMFF battery — 6 targets, 3 hostile-input fixes
  • §8.8.12 mfro surfacing + trailer-size cross-check
  • fuzz-campaign hardening — five unbacked-allocation shapes fixed
  • resolve_sai_aux_info — bridge the senc-less CENC carriage
  • insert_empty_time — §8.8.6.1 empty-insert write side
  • §8.8.2 mehd write side — sealed fragmented duration
  • §8.8.7 duration-is-empty track fragments
  • foreign-file elst equivalence pin + README seek-path accuracy
  • start_time from the mapped §8.6.6 timeline + seek/hostile pins
  • per-packet sample_description_index surfacing
  • explicit edit lists reach the fragmented init segment
  • explicit per-track edit lists on the muxer
  • typed §8.6.6 edit-list public surface
  • full §8.6.6 edit-list timeline mapping on demux
  • mark internal boxes module #[doc(hidden)]
  • emsg v0-delta absolute-time resolver + PIFF truncation sweeps
  • wire PIFF uuid boxes + emsg into demux surface and fragment writer
  • PIFF legacy uuid encryption boxes + DASH emsg byte layer
  • README + CHANGELOG — round 396 self-describing CENC packaging docs
  • CENC×index interplay hardening + EveryKeyframe final-sample fix
  • CencFragmentPackager — plaintext-in, protected-fMP4-out write driver
  • packager→demux→decrypt round-trip gate — every §10 scheme × fragmented shape
  • seig key-rotation signalling on write — fragment-local sgpd/sbgp
  • per-fragment senc + saiz/saio emission through write_protected_packet
  • CENC seig group-entry serialiser (§6 write side)
  • add CI / crates.io / docs.rs / MIT-license badges
  • moof-level pssh emission for per-fragment key rotation
  • CENC protected-track muxing — sinf envelope + moov-level pssh emission
  • CENC write-side foundations — tenc/pssh/senc builders + AES encrypt driver
  • muxer codec write-side coverage x12 — h265/av1/vp9/vp8/h263 + opus/alac/ac3/eac3/mp3/G.711
  • commit stranded pnot integration test
  • QuickTime track load settings atom (trak/load) read + build
  • QuickTime text sample description (text stsd entry) read + build
  • QuickTime timecode sample description (tmcd stsd entry) read + build
  • QuickTime timecode media info atom (tcmi §gmhd) read + build
  • QuickTime base media info header (gmhd/gmin §minf) read + build
  • HEIF item-properties crtt/mdft timestamps + README
  • HEIF item-properties extended set (udes/altt/iscl/rref)
  • surface per-item iloc/iref catalogue detail on metadata channel
  • HEIF entity-grouping family (grpl/EntityToGroupBox)
  • HEIF item-properties family (iprp/ipco/ipma + property boxes)
  • README — document r375 box builders (tref/trgr/kind/cprt/tsel/strk/subs/saiz/saio/pdin/prft write side)
  • saiz/saio (Sample Auxiliary Info Sizes/Offsets §8.7.8-9) public surface
  • subs (SubSampleInformationBox §8.7.7) public parse + build surface
  • prft (ProducerReferenceTimeBox §8.16.5) standalone builder
  • pdin (ProgressiveDownloadInfoBox §8.1.3) builder
  • trgr (TrackGroupBox §8.3.4) builder
  • cprt/kind/tsel track-udta selection-box builders (§8.10)
  • sub-track (strk/stri/strd/stsg §8.14) builders
  • tref (TrackReferenceBox §8.3.3) builder
  • document hint sample entries, hinf, FD item info, meco/mere
  • hinf Hint Statistics Box parse+build (§9.1.5)
  • rtcp reception + MPEG-2 TS (sm2t/rm2t) hint sample entries
  • RTP/SRTP/reception hint sample-entry family (§9.1.2 / §9.4.1.2)
  • meco/mere Additional Metadata Container family (§8.11.7/8)
  • FD Item Information family parse+build (§8.13 / §9.2.4.7)
  • pasp/clap/colr box builders (§12.1.4-5)
  • demux VisualSampleEntry pasp/clap/colr boxes (§12.1.4-5)
  • §8.11 meta-item box builders (iloc/pitm/iinf/iref/idat)
  • demux idat capture + item-byte resolution (§8.11.11 / §8.11.3.3)
  • demux file-level meta item infrastructure (iloc/pitm/iinf/iref, §8.11)
  • parse fragment-local sample groups (sgpd/sbgp/csgp in traf, §8.9)
  • wire csgp (CompactSampleToGroupBox §8.9.5) into non-fragmented muxer
  • trep + assp fragmented-mux emission (§8.8.15 / §8.8.16)
  • typed assp AlternativeStartupSequencePropertiesBox in trep (§8.8.16)
  • add build_stvi_box write side + stvi integration round-trip tests
  • parse stvi (StereoVideoBox, §8.15.4.2) from sample-entry sinf/schi
  • ssix SubsegmentIndexBox emission after each sidx (ISO/IEC 14496-12 §8.16.4)
  • leva LevelAssignmentBox emission in init mvex (ISO/IEC 14496-12 §8.8.13)
  • build_leva_box LevelAssignmentBox builder (ISO/IEC 14496-12 §8.8.13)
  • large_mdat option for 64-bit largesize mdat header (§4.2)
  • document prft muxing; drop stale "no fragmented muxing" bullet
  • prft ProducerReferenceTimeBox muxing (ISO/IEC 14496-12 §8.16.5)
  • document typed §10 sample-group description entries
  • decode_sample_group_entry dispatch + SampleGroupEntry
  • rash RateShareEntry (§10.2.2) + CHANGELOG
  • alst AlternativeStartupEntry (§10.3.2)
  • rap/tele/sap single-byte bit-packed entries
  • typed roll/prol RollRecoveryEntry (§10.1.1)
  • capture 2022-edition flags annotating what the NTP time represents
  • enforce §8.9.5 pattern/count 4-bit-width agreement constraint
  • csgp pattern → sample expansion: CsgpBox::resolve_samples (§8.9.5)
  • capture index_msb_indicates_fragment_local_description (flag bit 7) + width-aware MSB resolver
  • parse btrt BitRateBox in sample entries (ISO/IEC 14496-12 §8.5.2)
  • parse amve AmbientViewingEnvironmentBox inside VisualSampleEntry
  • csgp CompactSampleToGroupBox builder (ISO/IEC 14496-12:2020 §8.9.5)
  • demux hmhd HintMediaHeaderBox (ISO/IEC 14496-12 §12.4.2)
  • refresh to current status, drop per-round changelog cruft

Added

  • Structure-aware fuzz surface expanded from one demux-only target to six (shared oxideav_mp4_fuzz scaffolding: a Recipe byte-reader, a valid-by-construction MuxPlan, and a full-accessor demux battery). New targets: box_parsers (hostile bytes through every standalone public box parser — HEIF item catalogue, CENC/PIFF, emsg, FD, hint, sample-group blobs, QuickTime atoms, aux-info/random-access records — with parse∘build fixed-point asserts wherever a byte-exact builder dual exists); mux_roundtrip (valid recipes must mux and re-demux byte-exactly across plain / faststart / fragmented — incl. styp + sidx/mfra — layouts, with exact pts identity through the media/movie timescale rescale and start-delay elst); structured_mutate (byte flips / truncations / targeted corruption right after the sample-table, fragment, and index FourCCs the offset walkers arithmetic on); meta_graph (builder-assembled iloc/iinf/iref/ipma catalogues — every construction method, reference cycles, out-of-range property indexes — reparse identically, then survive hostile mutation of the meta body); and cenc_roundtrip (encrypt∘decrypt identity under arbitrary-but-valid §10 scheme × tenc × subsample-partition metadata, plus hostile plan_sample_cipher / senc / seig walks). The retooled demux target now drives the whole public accessor set (CENC / PIFF / emsg / HEIF item resolution / edit lists / fragment records) plus both seek paths. Bounded inline ASan campaigns (5.9M+ execs total across targets) found and fixed three defects, each pinned as a fuzz-corpus regression + covered by a unit test:

    • tcmi / text Pascal-string round-trip corruption (build_tcmi_box / build_text_sample_entry, ISO/IEC 14496-12 §12): a font_name longer than the 255-byte Pascal length ceiling — which arises when the parser lossily replaces non-UTF-8 wire bytes (each expands to a 3-byte U+FFFD) — was truncated at a raw byte index, slicing a multi-byte char and re-growing a replacement-character tail on reparse. The builders now truncate on a char boundary, so an over-long name rebuilds to a clean UTF-8 prefix.
    • fd::each_child / hint::each_child box-walker overflow (ISO/IEC 14496-12 §4.2): a hostile largesize-encoded child near u64::MAX (or an oversize size32) overflowed the pos + total bound check before it could reject the child. Both walkers now bound with checked_add, dropping the malformed child and terminating the walk cleanly.
  • Daily Fuzz workflow (fleet crate-fuzz.yml shim, 05:23 UTC) — previously absent for this crate.

  • mfro surfacing (ISO/IEC 14496-12 §8.8.12): the trailer's declared enclosing-mfra size appears as the mfro_size metadata key, with a mfro_size_mismatch key (declared=<d> actual=<a>) when it disagrees with the mfra measured on disk — validating the last-16-bytes locator shortcut without affecting the open or the seek table. Integration test covers own-muxer match + byte-corrupted mismatch

  • Hostile-input hardening from a corpus-refreshed fuzz campaign — five distinct unbacked-allocation (OOM) shapes found and fixed, each pinned as a fuzz-corpus regression + covered by tests: (1) a defaults-only trun (zero wire bytes per sample) claiming ~10^9 samples — every fragmented sample_count is now charged against a whole-file sample budget (one input byte per sample floor); (2) constant-size stsz whose sample_count × sample_size exceeds the file size; (3) next_packet allocating a forged multi-GiB sample size before reading — sizes are validated against the input length first; (4) tfra reserving its entry table before the entry-count/body validation; (5) senc zero-width entries (IV size 0, no subsample flag — syntactically legal §7.2.2) with a forged count — bounded at 2^20 entries; plus body-backed Vec::with_capacity clamps across the moov table parsers (stts / stsc / stsz / stz2 / stss / ctts / stco / co64 / elst) and a pre-allocation cap on the resolve_sai_aux_info sizes scratch vector. Fuzz target now drives open_typed + resolve_sai_aux_info + empty_duration_records; corpus gains muxer-produced fMP4 seeds (sealed mehd + empty-time gap fragment, senc-stripped CENC saiz/saio carriage)

Changed

  • Comment hygiene: reworded four code comments that named external multimedia implementations; behaviour is unchanged and the reworded rationale now cites the ISO clauses directly

  • Mp4Demuxer::resolve_sai_aux_info() — bridges the senc-less CENC carriage: for each fragment whose saiz+saio (ISO/IEC 14496-12 §8.7.8-9, §8.8.14 in-traf offsets) name mdat-resident auxiliary information but no senc was parsed, the contiguous run is fetched from the input, parsed as per-sample cells (tenc-width IV, then u16 subsample count + (u16, u32) runs when the cell exceeds the IV, exact-size discipline), and appended to senc_records() as a synthesised record — a decryption layer replaying senc_records now consumes both carriages identically. SaiRecord gains the traf's resolved base_data_offset so callers can resolve the §8.8.14-relative offsets themselves. Conservative skips (senc-present fragments, no tenc, non-§10 aux types, multi-offset saio, >16 MiB totals, past-EOF runs, unparseable cells); input cursor restored. 3 integration tests (senc-renamed-away bridge + decrypt-to-plaintext, senc-authoritative no-op, past-EOF saio skip)

  • Empty-time write side: FragmentedMuxer::insert_empty_time(stream_index, duration) emits a standalone §8.8.6.1 gap fragment — moof(mfhd + traf(tfhd(duration-is-empty | default-sample-duration-present) + tfdt)), no trun, no mdat — after flushing pending samples, and advances the track's decode timeline so later tfdts land after the gap. Rejected when explicit track_edit_lists are set (§8.8.7.1 forbids elst + empty-duration fragments). Round-trips through the new demux surface (gap position + length recovered exactly). 2 integration tests

  • mehd write side (ISO/IEC 14496-12 §8.8.2) on the fragmented muxer: FragmentedOptions::write_mehd reserves a version-1 MovieExtendsHeaderBox as the first child of the init-segment mvex and patches its fragment_duration in place at write_trailer with the sealed §8.8.2.3 total (longest track including fragments, media→movie timescale with ceiling division). A sealed file demuxes with authoritative duration_micros (mvhd stays 0) and surfaces the raw value as mehd_fragment_duration; an unreached trailer leaves the 0 placeholder — the §8.8.2.1 "compute by examining each fragment" posture. Default false keeps the init segment byte-identical. 2 integration tests (sealed round-trip incl. box shape/placement, default-off pin)

  • Empty-time track fragments (ISO/IEC 14496-12 §8.8.7 tfhd duration-is-empty, flag 0x010000): an empty-duration traf now advances the track's running decode timeline by the effective default sample duration (tfhd override, else trex), so a subsequent tfdt-less fragment lands after the gap; a tfdt inside the empty traf re-anchors the gap's start. Per §8.8.8.1 ("there are no track runs") a trun planted inside an empty-duration traf by a non-conforming producer is ignored — no fabricated samples, no double-counted interval. Gaps are surfaced flat as frag_empty_duration_<n> ("track=<t> seq=<s> duration=<d>") and typed via the new Mp4Demuxer::empty_duration_records() / demux::EmptyDurationRecord. 3 integration tests (tfdt-less continuity across the gap, tfdt re-anchor, hostile in-gap trun)

  • Per-packet sample_description_index surfacing: each demuxed sample now carries the 1-based §8.5.2 index of the stsd entry it was authored against — resolved from the covering stsc entry (§8.7.4) for moov-resident samples and from tfhd (0x000002 flag) falling back to the trex default (§8.8.7) for fragment samples (tfhd.sample_description_index was previously parsed-and-skipped). Read it after each next_packet via the new Mp4Demuxer::sample_description_index_of_last_packet (None before the first packet). Closes the detection half of the multi-description gap: a caller sees index ≥ 2, looks the entry up via the existing stsd_<n> options, and re-dispatches its decoder; automatic mid-stream re-dispatch inside next_packet stays out of scope. 3 integration tests (per-chunk stsc switch, stsd_<n> options coexistence, fragmented tfhd override + trex fallback across three moofs)

  • Explicit edit lists on the fragmented muxer's init segment: Mp4MuxerOptions::track_edit_lists now also drives open_fragmented_typed — the list is written into the init-moov trak (between tkhd and mdia), validated at open. The §8.6.6.1 zero-segment_duration form covers the whole fragmented presentation ("the edit provides the offset for the movie and subsequent movie fragments"), the idiom a CMAF packager uses to cut priming samples; integration test proves a media_time = 1024, duration = 0 trim spans fragment boundaries (pre-roll discard in fragment 1, constant delta into fragment 2)

  • Explicit per-track edit lists on the muxer: Mp4MuxerOptions::track_edit_lists (new TrackEditList record — stream_index + demux::EditListEntry entries) emits a caller-supplied edts/elst verbatim, overriding the automatic start-delay emission for that track and written even when write_edit_list is false (the flag now governs only the automatic behaviour). Serialised through demux::build_elst_box, so §8.6.6.3 round-trip violations and out-of-range stream indices fail at open, never at write_trailer. Completes demux↔mux elst parity: a remuxer feeds Mp4Demuxer::edit_list's slice straight back in. 4 integration tests (verbatim emission + timeline recovery, automatic-form override, flag independence, open-time rejection sweep)

  • Typed §8.6.6 edit-list public surface: demux::EditListEntry (segment_duration movie-timescale / media_time media-timescale / media_rate_integer / media_rate_fraction, with is_empty_edit() / is_dwell() helpers), the per-stream Mp4Demuxer::edit_list(stream) accessor, flat elst_entry_count + elst_<n> (dur=<d> media_time=<mt> rate=<int>) keys on params.options, and the standalone byte pair demux::parse_elst_box / build_elst_box (byte-exact inverses; auto v0/v1 width promotion; build rejects §8.6.6.3 violations — media_rate_integer outside {0, 1}, a final empty edit, media_time below the -1 empty-edit sentinel, an empty entry list). Lets a remuxer carry a source's elst across unchanged and a validator check the declaration without re-walking the moov. 6 unit tests + 2 integration tests

  • Full ISO/IEC 14496-12 §8.6.6 edit-list timeline mapping on the demux path (moov sample tables and moof fragments alike). Previously only the first non-empty edit's media_time was subtracted; now each edit segment contributes its own presentation delta: leading empty edits delay the track start by their movie-timescale duration (so a muxer-written start-delay elst round-trips to the original packet pts), initial trims subtract the trim point, dwells (media_rate_integer = 0) insert presentation time without consuming media, and multi-segment lists hop each mapped media range into place (movie→media segment_duration rescale with saturating 128-bit math; a zero duration is the §8.6.6.1 open-ended form). Media the list never presents — decode pre-roll before the first mapped composition time, and ranges excised between segments — is still delivered for decoding but carries the packet discard flag. Non-media-monotonic lists (media re-ordered or repeated, where per-sample deltas would break DTS monotonicity) fall back to the previous leading-shift mapping. 11 new timeline unit tests + 8 integration tests over synthetic elst shapes (trim / delay+trim across timescales / dwell / padded excision / v1 64-bit layout / hostile u64::MAX magnitudes), including a PATH-gated black-box cross-check that an independent reader reports the same start delay on a muxer-produced file

Changed

  • StreamInfo::start_time now reflects the §8.6.6 mapped presentation start: a track behind a leading empty edit reports its first presented time (the first edit segment's presentation start, media timescale) instead of the previous constant Some(0); tracks without an elst — or under the non-monotonic fallback — still report 0. Also verified seek_to operates on the mapped timeline (the landed pts and post-seek packets carry mapped timestamps), and added a hostile sweep pinning that spec-invalid stsc.sample_description_index values (0, out-of-range) surface verbatim without a panic

Other

  • Foreign-file black-box equivalence pin: a PATH-gated test generates an AAC file with an independent encoder (whose elst carries the encoder-priming trim media_time = 1024) and asserts this demuxer's mapped pts sequence matches the independent reader's packet-for-packet — including the negative pre-roll pts, which our mapping additionally flags discard. Also corrected a stale README line: sidx / mfra are not "skipped" — they are parsed and drive the documented seek fast-paths, and the per-traf sample_description_index is now honoured

  • Marked the internal boxes module #[doc(hidden)] — the low-level BMFF box-header reader and FourCC constant table (BoxHeader, read_box_header/read_box_body/skip_box_body, fourcc, ~150 FourCC constants) are implementation plumbing the README documents no stable surface for; excluding them from the documented public API keeps cargo-semver-checks bump-level detection focused on the real demux/mux/CENC/HEIF API. No signature or behaviour change.

  • Round 407 — PIFF legacy uuid encryption boxes + DASH emsg. PIFF (Protected Interoperable File Format 1.1/1.3, the pre-CENC predecessors of senc/tenc/pssh carried as ISO/IEC 14496-12 §4.2 uuid extended-type boxes): all three vendor usertypes parsed and re-emitted — schi-level TrackEncryptionBox (cenc::PiffTencBox, u24 AlgorithmID / IV_size / KID, surfaced as piff_* stream options + Mp4Demuxer::piff_tencs), moov/moof-level ProtectionSystemSpecificHeaderBox (byte-identical to a v0 pssh payload; piff_psshes / piff_moof_psshes + piff_pssh_<n> / piff_moof_pssh_<n> metadata), traf-level SampleEncryptionBox with the PIFF-only flags & 1 inline override triple (cenc::PiffSencBox / PiffSencOverride; piff_senc_records + piff_senc_<n> metadata). PIFF-only trafs bridge into the standard senc_records surface (tables are byte-compatible) and PiffTencBox::to_tenc/scheme_decision route legacy tracks into the existing CencSchemeDecision decryption path; dual-branded files keep the 4CC boxes authoritative with no double-reporting. Write side: build_piff_tenc_box / build_piff_senc_box / build_piff_pssh_box, byte-exact inverses with round-trip rejection. emsg (ISO/IEC 23009-1 §5.10.3.3 DASH Event Message Box): new emsg module with the typed EmsgBox (scheme_id_uri / value / timescale / EmsgTime Delta-v0-vs-Absolute-v1 / event_duration + unknown sentinel / id / message_data), parse_emsg_box / build_emsg_box covering both version field orders; demux captures instances keyed by the following moof's index (Mp4Demuxer::emsgs + emsg_<n> metadata) and FragmentedMuxer::set_next_segment_emsg queues write-side events per segment, emitted between styp and moof with their bytes counted into the sidx referenced_size / ssix metadata range. Mp4Demuxer::emsg_absolute_time resolves a v0 delta to an absolute presentation time by anchoring it to the earliest presentation time of the moof the box preceded (per-moof anchors captured during the fragment walk, cross-timescale-safe via rational comparison and rescaled into the emsg timescale); v1 passes through. 11 integration tests (mux→demux round trips, byte-level sidx coverage gate, v0-delta anchoring across timescales, dual-branding, hostile truncation/UTF-8/version/NUL inputs) + 20 unit tests incl. full truncation sweeps over all three PIFF bodies

  • Round 396 — self-describing CENC fragment packaging (ISO/IEC 23001-7 §7.1–7.2 + ISO/IEC 14496-12 §8.7.8–9 / §8.8.14): new FragmentedMuxer::write_protected_packet(packet, SencSample) queues each protected sample's §7.1 auxiliary information (per-sample IV + optional subsample map) alongside the payload, validated at queue time (§9.2 IV-supply discipline against the track tenc, §9.5.1 subsample-total coverage, §7.2.3 all-samples-or-none per fragment — mixing with plain write_packet on one track within a fragment is rejected both ways). Each flush emits per traf: a §7.2 senc (UseSubSampleEncryption flag iff any sample maps subsamples), a §8.7.8 saiz (constant-size shortcut when every sample's aux-info size agrees, per-sample table otherwise, aux_info_type omitted per the §7.1 SHOULD), and a §8.7.9 saio whose single offset is patched post-layout to the moof-relative position of the first senc entry (§8.8.14 default-base-is-moof), making every fragment independently decryptable (§7.2.1). Constant-IV tracks without subsample maps omit all three boxes (§7.1 empty-aux rule). EveryKeyframe detach carries the senc entry with the replayed sample. Demux side: new public demux::open_typed returns the (now-public) Mp4Demuxer so decrypt/indexing layers reach the structured senc_records / sai_records / traf_sample_groups accessors without downcasting. 9 integration tests including a byte-level gate proving each saio offset lands on its fragment's first IV

  • Round 396 — CENC seig key-rotation signalling on write (ISO/IEC 23001-7 §6 + ISO/IEC 14496-12 §8.9.4): new cenc::build_seig_entry (byte-exact inverse of parse_seig, with §6 round-trip validation: 4-bit pattern ceilings, §9.1 IV-size set, constant-IV-presence coherence) and FragmentedMuxer::write_protected_packet_grouped(packet, senc, Option<SeigEntry>) mapping each protected sample to an optional per-sample-group override (the key-rotation channel). At flush, the distinct entries used by the fragment's samples are deduplicated in first-use order into one fragment-local sgpd('seig') (§8.9.3 traf container) and the per-sample mapping run-length-encoded into one sbgp('seig') with the §8.9.4 fragment-local index numbering (0x10001 = first local description, 0 = tenc defaults). Queue-time guard: a protected override may not change Per_Sample_IV_Size (the fragment's senc stores one IV width, §7.2.3). Tests: mid-fragment KID rotation, A/B/A/B two-key dedupe order, clear-samples group (isProtected = 0), IV-width-change rejection

  • Round 396 — CencFragmentPackager (new cenc_packager module): plaintext-in, protected-fMP4-out write driver owning the crypto state the container layer doesn't hold — the KID-keyed AES-128 content-key store, per-sample IV generation (per-track 64-bit counter in IV bytes 0..8 so §9.3 CTR counter blocks never collide across samples under one key, honouring §9.4.2 unique-IV-per-sample), and the active seig override. write_packet (§9.4/§9.7 full-sample), write_packet_with_subsamples (§9.5 subsample/pattern, clear prefixes preserved), rotate_key(kid, key, constant_iv?) (constant-IV schemes take a fresh constant IV per key; per-sample-IV schemes must not; rotating back to the default KID clears the override), reset_to_default_key, set_next_segment_pssh delegate (§8.1.1 moof-scoped licence blobs). Unprotected sibling streams pass through to the plain write path

  • Round 396 — packager→demux→decrypt round-trip gate on every §10 scheme × fragmented shape: cenc full-sample (8/16-byte IVs) + subsample, cbc1 full-sample (§9.4.3 clear tail) + subsample, cens 2:1 CTR pattern, cbcs 1:9 CBC pattern under a constant IV (zero-length senc IVs) + the §9.7 whole-block full-sample shape (no senc/saiz/saio in the file at all — decrypt from tenc alone), plus a key-rotation loop whose decrypt side resolves each sample's KID purely from the file (sbgp('seig') run-length → fragment-local sgpd entry → parse_seig → KID-keyed store). 9 + 10 new integration tests across tests/cenc_frag_decrypt.rs / tests/cenc_packager.rs

  • Round 396 — EveryKeyframe final-sample fix: with FragmentCadence::EveryKeyframe, write_trailer's final flush used to run the cadence detach — popping the trailing keyframe "for the next fragment" that never comes — silently dropping the stream's last sample (plain and protected paths alike). The final flush now skips the detach (flush_fragment_inner(detach_trailing_keyframe)); regression test proves every written packet demuxes back byte-exact. Plus CENC×index interplay gates: protected fragments under full DASH chrome (sidx + styp + mfra) keep the moof-relative saio byte gate intact; two protected tracks in one moof patch independent saio offsets against the shared moof origin; the packager rotation workflow with a moof-scoped pssh decrypts across both key epochs

  • Round 389 — moof-level pssh emission (ISO/IEC 23001-7 §8.1.1): new FragmentedMuxer::set_next_segment_pssh(iter) queues one or more pssh boxes to be written inside the next fragment's moof (after mfhd, before the traf boxes), scoping a DRM header to that fragment's samples for per-fragment key rotation — the movie-fragment counterpart to the moov-level Mp4MuxerOptions::pssh init-segment boxes. Threaded through build_moof / build_moof_inner (the box size folds into the enclosing sidx referenced_size since moof_size is derived from the built bytes). Consumed per fragment; serialised through build_pssh_box. The demuxer already surfaces these on moof_pssh_<n> keyed by mfhd.sequence_number. One integration test driving open_fragmented_typed, attaching a v1 moof pssh to the first fragment and asserting it surfaces on exactly that fragment

  • Round 389 — CENC protected-track muxing (ISO/IEC 14496-12 §8.12 + ISO/IEC 23001-7 §4.1 / §8.1): new Mp4MuxerOptions::track_protection (TrackProtection { stream_index, scheme_type, scheme_version, tenc }) wraps a muxed track's sample entry into its protected encv / enca / enct / encs form with a sinf(frma + schm + schi/tenc) envelope, serialised through the new public cenc::build_sinf_box (validated at open via CencSchemeDecision::new + build_tenc_box round-trip rules — an envelope this crate's own demuxer would reject cannot be emitted, and an incoherent scheme/tenc pair fails at open, never at write time). New Mp4MuxerOptions::pssh emits moov-level pssh boxes after the trak boxes (fragmented mode: in the init-segment moov after mvex), serialised through build_pssh_box. Applies to both the plain and fragmented muxers. TrackProtection / TrackSampleGroups re-exported at crate root. Three integration tests: a full black-box encrypt→mux→demux→decrypt loop (plaintext packets encrypted per-sample via encrypt_sample_in_place, muxed as a protected enca track + v1 pssh, demuxed back asserting protection_scheme / cenc_default_* / pssh_0 and ciphertext-verbatim packets, then decrypted byte-exact via decrypt_sample_in_place), a fragmented init-segment protection surface check, and the incoherent-pair open failure

  • Round 389 — CENC write-side foundations (ISO/IEC 23001-7:2016 §7.2 / §8.1 / §8.2 / §9): new public byte-exact builders cenc::build_tenc_box / build_pssh_box / build_senc_box — each emits a complete [size][fourcc] box whose body is the inverse of the matching parser (parse_tenc / parse_pssh / parse_senc), with round-trip validation (a v0 tenc carrying a pattern pair, a >4-bit pattern component, an IV size outside {0, 8, 16}, or a constant IV whose presence disagrees with the §9.1 isProtected/IV_size rule is rejected; a v0 pssh with KIDs or >u32 counts is rejected; a senc with mixed per-sample IV widths, >u16 subsample counts, subsamples under a cleared UseSubSampleEncryption flag, or >24-bit flags is rejected). New write-side cipher duals cenc_cipher::encrypt_sample_in_place / encrypt_steps_in_place: the same plan_sample_cipher partition and §9.2 IV-supply discipline as the decrypt path, executed forward — CTR is the identical §9.3 keystream XOR, CBC encrypts with the same per-cbcs-subsample IV restart, §9.5.1 chain continuity, and §9.4.3 whole-block discipline — so encrypt→decrypt with identical arguments is the identity. 9 builder round-trip/rejection unit tests + 5 encryptor tests (all-schemes encrypt→decrypt identity across a two-subsample shape, first-principles CTR keystream equality, reference-CBC chain equality with clear tail, cbcs constant-IV per-subsample restart producing identical ciphertext halves, shared malformed-plan guards)

  • Round 389 — muxer codec write-side coverage ×12 (ISO/IEC 14496-12 §8.5.2 / §12.1.3 / §12.2.3 + ISO/IEC 14496-15 §8.4 + ISO/IEC 14496-1 §7.2.6): sample_entries::sample_entry_for now packages every remaining codec id the demuxer's sample-entry table resolves that has a well-defined write shape — video h265hvc1+hvcC, av1av01+av1C, vp9/vp8vp09/vp08+vpcC (config record = extradata verbatim, the demuxer's surfaced form; missing extradata is rejected at open), h263s263 with a d263 config child when extradata is present; audio opusOpus+dOps (the demuxer-prepended OpusHead magic is stripped on write and re-prepended on read — byte-exact both ways), alacalac entry + alac magic-cookie FullBox child, ac3ac-3+dac3 and eac3ec-3+dec3 (config body verbatim), mp3mp4a with an esds carrying objectTypeIndication = 0x6B and no DecoderSpecificInfo (the demuxer's OTI refinement resolves it back to mp3), pcm_mulaw/pcm_alawulaw/alaw plain 8-bit AudioSampleEntries. The AAC esds assembly is refactored onto a shared build_esds_body(oti, dsi) used by both aac and mp3. Demux-side symmetry additions: parse_video_sample_entry now surfaces a d263 config-box body as extradata and parse_audio_sample_entry surfaces the alac magic cookie (post-version/flags bytes, mirroring the dfLa convention). 10 new sample-entry unit tests + 2 table-driven integration tests (tests/mux_roundtrip.rs) remuxing all 12 codecs mux → demux with byte-exact packet and extradata round-trip assertions

  • Round 379 — HEIF item-properties timestamps (crtt / mdft, ISO/IEC 23008-12 §6.5.18 / §6.5.19): two more typed demux::ItemProperty variants. crtt CreationTimeProperty and mdft ModificationTimeProperty each carry a 64-bit time in microseconds since 1904-01-01 UTC, decoded by parse_ipco_box / parse_item_property and re-emitted byte-exact by build_item_property. Surfaced in the meta_iprp_property_<n> summary as crtt <us> / mdft <us>. Two new round-trip unit tests. Two new FourCC consts in boxes (CRTT / MDFT) (ISO/IEC 23008-12 §6.5)

  • Round 379 — HEIF item-properties extended set (udes / altt / iscl / rref, ISO/IEC 23008-12 §6.5.13 / §6.5.17 / §6.5.20 / §6.5.21): four more typed demux::ItemProperty variants beyond the base round-379 set. udes UserDescriptionProperty (§6.5.20) carries four NUL-terminated UTF-8 strings (lang RFC 5646 tag, name, description, comma-separated tags, any may be empty); altt AccessibilityTextProperty (§6.5.21) an HTML-alt-style alt_text plus its alt_lang; iscl ImageScaling (§6.5.13) the horizontal / vertical scaling ratios as 16-bit numerator/denominator pairs (a transformative property); rref RequiredReferenceTypesProperty (§6.5.17) the list of iref reference-type FourCCs a reader must understand to decode the item (e.g. pred for a predictively-coded image). Each is decoded by parse_ipco_box / parse_item_property and re-emitted byte-exact by build_item_property; a property truncated for its declared type (e.g. an rref whose count overruns the body) falls back to ItemProperty::Other so its ipco index slot survives. The meta_iprp_property_<n> summary tokens cover the new types (udes [<lang>] <name>, altt <alt_text>, iscl <wn>/<wd>x<hn>/<hd>, rref <type,…>). Six new round-trip unit tests (udes full / empty, altt, iscl, rref full + empty, rref overrun-to-Other). Four new FourCC consts in boxes (UDES / ALTT / ISCL / RREF) (ISO/IEC 23008-12 §6.5)

  • Round 379 — HEIF item-catalogue flat-channel enrichment (ISO/IEC 14496-12 §8.11.3 / §8.11.12): the file-level meta box's already-parsed iloc and iref records now surface per-entry detail on Demuxer::metadata(), not just a count. meta_iloc_<n> (id=<item_id> method=<construction_method> extents=<n> length=<total>) lets a HEIF consumer spot where each item lives — file (0) / idat (1) / item (2) offset, extent count, and total byte length — without downcasting to MetaItems::iloc. meta_iref_<n> (type=<fourcc> from=<item_id> to=<id,…>) exposes the relationship graph (thumbnail thmb, auxiliary auxl, derivation dimg, description cdsc, pre-derived base, predictive pred, tile-base tbas, scalable-base exbl, …) so the item relationships are queryable from the flat channel. The existing meta_iloc_count / meta_iref_count keys are unchanged; absent the boxes no keys are emitted. Catalogue integration test extended with meta_iloc_0 / meta_iloc_1 / meta_iref_0 assertions (ISO/IEC 14496-12 §8.11)

  • Round 379 — HEIF / MIAF entity-grouping family (grpl / EntityToGroupBox, ISO/IEC 23008-12 §9.4) read + write: a file-level meta box's grpl GroupsListBox is now decoded into the new public demux::EntityGroups record — every EntityToGroupBox (each a FullBox whose FourCC is the grouping_type: altr alternatives, ster stereo pair, …) becomes a typed demux::EntityToGroup carrying the grouping_type, group_id, and the entity_id list (item / track IDs). Reachable via MetaItems::grpl; EntityGroups::by_type(grouping_type) filters (e.g. all altr sets) and EntityGroups::by_id(group_id) locates a group. Public byte-exact builders demux::build_grpl_box / build_entity_to_group_box are the inverses of parse_grpl_box (FullBox version 0, flags 0 per group). An EntityToGroupBox whose num_entities_in_group overruns its body is dropped (contributes no group) without aborting the grpl walk. The demuxer surfaces meta_grpl_group_count plus meta_grpl_group_<n> (type=<fourcc> id=<id> entities=<id,…>) on Demuxer::metadata(). Absent grpl, no keys are emitted. Six standalone round-trip unit tests (altr / ster / multi-group + index helpers / empty group / empty grpl / malformed-entity drop) + one end-to-end demuxer test surfacing the meta_grpl_* keys. New FourCC const GRPL in boxes (ISO/IEC 23008-12 §9.4)

  • Round 379 — HEIF / MIAF item-properties family (iprp / ipco / ipma, ISO/IEC 23008-12 §9.3) read + write: a file-level meta box's iprp ItemPropertiesBox is now decoded into the new public demux::ItemProperties record — the ipco ItemPropertyContainerBox property list (implicitly 1-indexed) plus every ipma ItemPropertyAssociation box merged into per-item (essential, property_index) lists. Reachable via MetaItems::iprp; ItemProperties::properties_for(item_id) resolves an item to its ordered (essential, &ItemProperty) pairs (skipping index-0 / out-of-range references), and ItemProperties::property(index) looks up the 1-based ipco slot. The typed demux::ItemProperty enum models the base property set: ispe ImageSpatialExtents (§6.5.3), pixi PixelInformation (§6.5.6), rloc RelativeLocation (§6.5.7), auxC AuxiliaryType (§6.5.8, NUL-terminated URN + subtype tail), irot ImageRotation (§6.5.10), imir ImageMirroring (§6.5.12), lsel LayerSelector (§6.5.11), plus pasp / clap / colr reusing this crate's existing 14496-12 records (§6.5.4 / §6.5.9 / §6.5.5); any unrecognised property box is preserved verbatim as ItemProperty::Other so its index slot survives a round-trip. Reserved high bits on irot / imir are masked on read. Public byte-exact builders demux::build_iprp_box / build_ipco_box / build_ipma_box / build_item_property are the inverses of the parsers (parse_iprp_box / parse_ipco_box / parse_ipma_box); build_ipma_box auto-selects the narrowest item-ID width (v0 16-bit / v1 32-bit) and property_index width (flags & 1 7-/15-bit), sorts entries by ascending item_ID (§9.3.1), and rejects an index past the 15-bit ceiling. The demuxer surfaces a compact summary on Demuxer::metadata(): meta_iprp_property_count, meta_iprp_property_<n> (a <fourcc> <decoded> token), and meta_iprp_item_<n> (id=<id> props=<idx[,idx*…]>, * marking an essential association). Absent iprp, no keys are emitted. 19 standalone round-trip unit tests (every property box, narrow / wide-index / wide-id / sorted / truncated ipma, unknown-property index preservation, properties_for resolution) + one end-to-end demuxer test surfacing the meta_iprp_* keys. Ten new FourCC consts in boxes (IPRP / IPCO / IPMA / ISPE / PIXI / RLOC / AUXC / IROT / IMIR / LSEL) (ISO/IEC 23008-12 §9.3 / §6.5)

  • Round 375 — saiz / saio (Sample Auxiliary Information Sizes / Offsets, ISO/IEC 14496-12 §8.7.8 / §8.7.9) public parse + build surface: the demuxer's internal saiz / saio parse paths are now exposed as public demux::parse_saiz_box / parse_saio_box plus new byte-exact demux::build_saiz_box / build_saio_box, and the carried records demux::SaizBox / SaioBox are now public with public fields. build_saiz_box writes the (aux_info_type, aux_info_type_parameter) key only when present (setting flags & 1), then the 8-bit default_sample_info_size, 32-bit sample_count, and — for a variable-size table — the per-sample size array (§8.7.8.2); it rejects a constant-size record carrying a per-sample table or a variable table whose sample_count disagrees with per_sample.len(). build_saio_box writes the same optional aux key then the 32-bit entry_count and the offset array as 32-bit (v0) / 64-bit (v1) fields (§8.7.9.2); it rejects an undefined version or a v0 offset above u32::MAX. CENC/DRM tooling can now decode and re-emit the auxiliary-info size/offset tables (the senc-companion boxes) without re-running open(). Five round-trip unit tests (saiz constant + variable-with-key + inconsistent rejection; saio v0/v1 + inconsistent rejection) (ISO/IEC 14496-12 §8.7.8 / §8.7.9)

  • Round 375 — subs (SubSampleInformationBox, ISO/IEC 14496-12 §8.7.7) public parse + build surface: the demuxer's internal subs parse path is now exposed as public demux::parse_subs_box plus a new byte-exact demux::build_subs_box, and the carried records demux::SubsBox / SubsEntry / SubSampleEntry are now public with public fields. build_subs_box serialises the FullBox preamble (record version 0/1 + codec-owned 24-bit flags verbatim), the 32-bit entry_count, and per entry the sparse sample_delta, 16-bit subsample_count, and per sub-sample the subsample_size (16-bit at v0, 32-bit at v1), subsample_priority, discardable and codec_specific_parameters (§8.7.7.2). Rejects records that cannot round-trip: an undefined version, an entry_count / subsample_count exceeding its on-wire field, or — at version 0 — a subsample_size above u16::MAX. A codec-aware layer (e.g. an AVC NAL splitter reading codec_specific_parameters) can now both decode and re-emit a subs table without re-running open(). Four round-trip unit tests (v0 multi-entry, v1 wide size, empty + degenerate zero-count, inconsistent-record rejection) (ISO/IEC 14496-12 §8.7.7)

  • Round 375 — prft (ProducerReferenceTimeBox, ISO/IEC 14496-12 §8.16.5) standalone builder: new public demux::build_prft_box serialises a PrftRecord into a prft box — the byte-exact inverse of parse_prft_box. The FullBox preamble carries the record's version (0/1) and its 24-bit flags verbatim (the 2022-edition NTP-annotation bits), then the 32-bit reference_track_ID, the 64-bit NTP timestamp, and media_time as a 32-bit field for v0 / 64-bit for v1 (§8.16.5.2). Rejects a v0 record whose media_time exceeds u32::MAX (would not round-trip — bump to v1) or an undefined version. This complements the fragmented muxer's existing per-segment prft emission with a stateless box builder usable outside the writer. Three round-trip unit tests (v0 with flags, v1 64-bit media_time, inconsistent-record rejection) (ISO/IEC 14496-12 §8.16.5)

  • Round 375 — pdin (ProgressiveDownloadInfoBox, ISO/IEC 14496-12 §8.1.3) builder: new public demux::build_pdin_box serialises a PdinRecord into a pdin box — the byte-exact inverse of parse_pdin_box (FullBox version 0, flags 0). The body is the 4-byte preamble followed by one big-endian (rate, initial_delay) u32 pair per entry (§8.1.3.2), written in supplied order (the builder does not re-sort by ascending rate). An empty entry list yields a preamble-only box. Two round-trip unit tests (two-entry table, empty) (ISO/IEC 14496-12 §8.1.3)

  • Round 375 — trgr (TrackGroupBox, ISO/IEC 14496-12 §8.3.4) builder: new public demux::build_trgr_box serialises a list of (track_group_type, track_group_id) pairs into a trgr container box — the byte-exact inverse of parse_trgr. The outer trgr is a plain container; each pair becomes one TrackGroupTypeBox whose FourCC is the track_group_type and whose body is the 4-byte FullBox preamble (version 0, flags 0) + the 32-bit track_group_id (§8.3.4.2). Pairs are emitted in supplied order; the per-track_group_type extension tail is not modelled. Two round-trip unit tests (multi-type, empty set) (ISO/IEC 14496-12 §8.3.4)

  • Round 375 — track-udta selection-box builders (cprt / kind / tsel, ISO/IEC 14496-12 §8.10): new public demux::build_cprt_box / build_kind_box / build_tsel_box are the write counterparts to the existing parse path. build_cprt_box serialises a CopyrightBox (§8.10.2): FullBox v0 + the 16-bit packed language word (three 5-bit ISO 639-2/T characters, each ASCII - 0x60) + the NUL-terminated UTF-8 notice — rejecting a non-lowercase language byte (unrepresentable in 5 bits) or an embedded NUL in the notice. build_kind_box serialises a KindBox (§8.10.4): FullBox v0 + the schemeURI and value NUL-terminated UTF-8 strings (value terminator always written, even when empty) — rejecting an empty URI (§8.10.4.3) or an embedded NUL. build_tsel_box serialises a TrackSelectionBox (§8.10.3): FullBox v0 + signed switch_group + the FourCC attribute_list. Each is the byte-exact inverse of its parser. Seven round-trip unit tests (cprt full / empty / bad-input rejection, kind full / empty-value / bad-input rejection, tsel signed + attributes) (ISO/IEC 14496-12 §8.10)

  • Round 375 — Sub-track (strk / stri / strd / stsg, ISO/IEC 14496-12 §8.14) builders: new public demux::build_stsg_box / build_stri_box / build_strk_box are the write counterparts to the existing parse path. build_stsg_box serialises a SubTrackSampleGroupBox (§8.14.6, FullBox v0: grouping_type FourCC + 16-bit item_count + the group_description_index array; rejects an index list exceeding the 16-bit count). build_stri_box serialises a SubTrackInformationBox (§8.14.4, signed switch_group / alternate_group, sub_track_ID, then the FourCC attribute_list to the end of the box). build_strk_box composes a complete SubTrackBox (§8.14.3): the mandatory stri followed — when any sample groups are supplied — by a single strd (SubTrackDefinitionBox, §8.14.5) wrapping one stsg per (grouping_type, indices) pair; with no sample groups the content-free strd is omitted. Each builder is the byte-exact inverse of its parser. Five round-trip unit tests (stsg full + empty indices, stri with signed groups + attribute list, full strk re-parse, stri-only strk omits strd) (ISO/IEC 14496-12 §8.14)

  • Round 375 — tref (TrackReferenceBox, ISO/IEC 14496-12 §8.3.3) builder: new public demux::build_tref_box serialises a list of (reference_type, track_IDs) pairs into a tref container box — the byte-exact inverse of parse_tref. The outer tref is a plain container (no FullBox preamble); each pair becomes one TrackReferenceTypeBox whose FourCC is the reference_type and whose body is the packed big-endian track_ID array (§8.3.3.2). Pairs are emitted in supplied order; a zero track_ID is rejected (§8.3.3.3 "never zero" — it would be silently dropped by the parser, breaking round-trip), while an empty track_IDs array for a type is permitted. Four round-trip unit tests (multi-type, empty-id array, zero-id rejection, empty set) (ISO/IEC 14496-12 §8.3.3)

  • Round 372 — hinf Hint Statistics Box (ISO/IEC 14496-12 §9.1.5) parse + build: a container in a hint track's udta holding optional delivery-statistic sub-boxes summarising the packetised stream a server would generate. New public hint::HintStatistics record (with MaxRate / PayloadId helpers) + byte-exact parse_hinf_box / build_hinf_box covering all §9.1.5 sub-boxes: trpy / nump / tpyl (u64 bytes-with-RTP / packets / bytes-without-RTP), the totl / npck / tpay u32 variants, zero-or-more maxr max-rate windows (period + bytes), dmed / dimm / drep (media / immediate-mode / repeated bytes, u64), tmin / tmax (signed relative-time extremes, ms), pmax / dmax (largest-packet bytes / longest-packet ms), and zero-or-more payt payload-ID + length-prefixed rtpmap entries. Every field is None / empty when its sub-box is absent ("not all these sub-boxes may be present", §9.1.5); the builder emits only present fields, in declaration order. The track-udta walk now decodes the first hinf into Track::hint_stats and surfaces hinf_bytes_sent / hinf_packets_sent / hinf_payload_bytes / hinf_media_bytes / hinf_immediate_bytes / hinf_repeated_bytes / hinf_largest_packet / hinf_maxr_count / hinf_payload_count on params.options (the u32 variants fall back to the u64 forms; absent keys omitted). Four hint module round-trip tests (full, partial, empty, multi-maxr) + three demux tests (parse_track_udta pickup, surfacing, absence). New FourCC const HINF in boxes (ISO/IEC 14496-12 §9.1.5)

  • Round 372 — rtcp reception + MPEG-2 TS (sm2t / rm2t) hint sample entries (ISO/IEC 14496-12 §9.4.2.3 / §9.3.3.2): the RTCP reception hint sample entry (rtcp) is structurally identical to rtp (§9.4.2.3, no defined additional-data boxes), so it now routes through the same hint::RtpHintSampleEntry parse path and Track::rtp_hint surface. The MPEG-2 Transport Stream hint sample entries — sm2t (server) and rm2t (reception), §9.3.3.2 — get a new public hint::Mpeg2TsHintSampleEntry record + byte-exact parse_mpeg2ts_hint_sample_entry / build_mpeg2ts_hint_sample_entry: the MPEG2TSSampleEntry body (hinttrackversion / highestcompatibleversion + precedingbyteslen / trailingbyteslen per-TS-packet wrapper byte counts + the precomputed_only_flag top bit, with the 7 reserved low bits masked, then opaque additionaldata bytes preserved verbatim). The stsd walk dispatches sm2t / rm2t into Track::mpeg2ts_hint and surfaces m2t_hint_format / m2t_hint_preceding_bytes / m2t_hint_trailing_bytes / m2t_hint_precomputed on params.options — a de-hinter strips the wrapping bytes before reassembling the 188-byte TS. Four hint module round-trip tests (rtcp uses rtp body, sm2t server, rm2t with additionaldata, truncation rejection) + two demux build_stream_info tests. New FourCC consts RTCP_HINT/SM2T/RM2T in boxes (ISO/IEC 14496-12 §9.4.2.3 / §9.3.3.2)

  • Round 372 — RTP / SRTP / reception hint sample-entry family (ISO/IEC 14496-12 §9.1.2 / §9.4.1.2) parse + build in a new hint module: the sample-description entry format for an RTP server hint track (rtp ), an SRTP secure hint track (srtp), and an RTP reception hint track (rrtp) — all sharing one body (hinttrackversion, highestcompatibleversion, maxpacketsize, then an additionaldata box set). New public hint::RtpHintSampleEntry record + byte-exact parse_rtp_hint_sample_entry / build_rtp_hint_sample_entry decoding the §9.1.2 additional-data boxes: tims (timescale entry, required), tsro (signed time offset, optional), snro (signed sequence offset, optional), and — for srtp — the srpp SRTPProcessBox (§9.1.2.1, the four SRTP encryption/integrity algorithm 4CCs plus a verbatim schm/schi tail, modelled as hint::SrppBox with its own parse_srpp_box / build_srpp_box). The demuxer's stsd walk dispatches a hint track's active entry (hint handler ⇒ MediaType::Data, so dispatched by FourCC) into Track::rtp_hint and surfaces rtp_hint_format / rtp_hint_max_packet_size / rtp_hint_timescale / rtp_hint_time_offset / rtp_hint_sequence_offset (signed; offset keys omitted when absent) / rtp_hint_srtp on params.options. A streaming server reconstructs the RTP packetisation parameters without re-walking stsd. Six hint module round-trip unit tests (tims-only, all-offsets, srtp+srpp, srpp empty-tail, truncated-preamble rejection, unknown-additional-data tolerance) + three demux build_stream_info tests (rtp surfacing, srtp marker, absence). New FourCC consts RTP_HINT/SRTP_HINT/RRTP_HINT/TIMS/TSRO/SNRO/SRPP in boxes (ISO/IEC 14496-12 §9.1.2 / §9.1.2.1 / §9.4.1.2)

  • Round 372 — meco / mere Additional Metadata Container family (ISO/IEC 14496-12 §8.11.7 / §8.11.8) demux: a file-level meco AdditionalMetadataContainerBox holds one or more additional meta boxes (each with a handler type distinct from the primary meta) that complement it, plus zero or more mere MetaboxRelationBoxes describing how two same-level meta boxes relate. The top-level walk now parses the first file-level meco into a new public demux::MecoBox (metas: Vec<MetaItems> — each additional meta fully decoded through parse_meta_items, capturing its handler type + any §8.11 item infrastructure — and relations: Vec<MereRelation>). MereRelation carries the two 32-bit handler-type codes and the §8.11.8.3 relation enum (1 unknown … 5 second-is-subset-of-first), preserved verbatim. New public stateless parsers demux::parse_meco_box / parse_mere_box + builder demux::build_mere_box (byte-exact inverse; the meco body is a plain box sequence so no dedicated meco builder is needed). Records reachable via the new Mp4Demuxer::meco() accessor; a flat summary is surfaced on Demuxer::metadata() as meco_meta_count / meco_meta_<n> (handler FourCC) / meco_relation_count / meco_relation_<n> ("<first>-<second>=<relation>"). MetaItems now derives PartialEq/Eq. Four demux unit tests (mere round-trip + truncation, meco walk collecting 2 metas + 1 relation, empty body) + one tests/meta_items.rs integration case (file-level meco spliced after ftyp, real audio track undisturbed) (ISO/IEC 14496-12 §8.11.7 / §8.11.8)

  • Round 372 — FD (File Delivery) Item Information family (ISO/IEC 14496-12 §8.13) parse + build in a new fd module: the §8.13 boxes that record how a source file carried in a top-level meta is partitioned into source blocks and FEC/File reservoirs for ALC/LCT / FLUTE transmission. New public typed records + byte-exact parser/builder pairs for fpar FilePartitionBox (§8.13.3, v0/v1 16-/32-bit item_ID/entry_count, the FEC-OTI scheme-specific NULL-terminated string, and the (block_count, block_size) partitioning list), fecr FECReservoirBox + fire FileReservoirBox (§8.13.4 / §8.13.7, the shared versioned (item_ID, symbol_count) list — one parse_reservoir_box/build_reservoir_box pair serves both, dispatched by FourCC), paen PartitionEntry (§8.13.2, one mandatory fpar + optional fecr + optional fire), segr FDSessionGroupBox (§8.13.5, session groups each carrying a file-group-ID set + an FD-hint-track-ID channel list), gitn GroupIdToNameBox (§8.13.6, (group_ID, group_name) UTF-8 mappings), fiin FDItemInformationBox (§8.13.2, the partition-entry array + optional segr + optional gitn; the parser walks the actual paen children rather than trusting the leading entry_count, so a corrupt count cannot desync the walk), and feci FECInformationBox (§9.2.4.7, the fixed 7-byte FEC encoding/instance/source-block/symbol descriptor carried in an FD hint sample's extr). The file-level meta walk now decodes a fiin into MetaItems::fiin and surfaces meta_fiin_partitions / meta_fiin_session_groups / meta_fiin_group_names on Demuxer::metadata(). 14 fd module round-trip unit tests + one demux integration test driving a metafiin through parse_meta_items. New FourCC consts MECO/MERE/FIIN/PAEN/FPAR/FECR/SEGR/GITN/FIRE/FECI in boxes (ISO/IEC 14496-12 §8.13 / §9.2.4.7)

  • Round 369 — pasp / clap / colr box builders (ISO/IEC 14496-12 §12.1.4–5): public demux::build_pasp_box / build_clap_box / build_colr_box serialise the picture-geometry / colour descriptor boxes, each the byte-exact inverse of its parser (an nclx box's seven reserved low bits are written as 0, matching the parser's mask; rICC / prof / Other colour types preserve their raw payload). Completes parse/build symmetry for the VisualSampleEntry descriptor family. One round-trip unit test covering pasp, clap, and all four colr variants (ISO/IEC 14496-12 §12.1.4 / §12.1.5)

  • Round 369 — VisualSampleEntry pasp / clap / colr boxes (ISO/IEC 14496-12 §12.1.4–5) demux: the video sample-entry sub-box walk previously decoded amve / btrt / sinf but skipped the three core picture-geometry / colour descriptor boxes. It now parses pasp (PixelAspectRatioBox §12.1.4, hSpacing / vSpacing), clap (CleanApertureBox §12.1.4, the eight-u32 width/height/horiz-off/vert-off N/D fractions) and colr (ColourInformationBox §12.1.5, the colour_type-discriminated payload — nclx on-screen colours with the three ISO/IEC 23091-2 16-bit codes + full_range_flag, rICC / prof ICC profiles kept as raw bytes, and an Other fall-back preserving an unknown type + payload). Typed PaspRecord / ClapRecord / ColrRecord records on the track; first one on the active entry wins (the spec permits several colr, most-accurate-first). Surfaced on params.options as pasp_h_spacing / pasp_v_spacing; clap_width / clap_height / clap_horiz_off / clap_vert_off (each "<N>/<D>"); and colr_type plus, for nclx, colr_primaries / colr_transfer / colr_matrix / colr_full_range, or for an ICC profile colr_icc_len. New public standalone parsers demux::parse_pasp_box / parse_clap_box / parse_colr_box. Six new unit tests (body parsing of all three incl. nclx/prof/Other + short-body rejection; sample-entry pickup; params.options surfacing). A renderer or HDR-aware pipeline now reads the display aspect, crop rectangle, and colour signalling straight from the container without touching the codec bitstream (ISO/IEC 14496-12 §12.1.4 / §12.1.5)

  • Round 369 — §8.11 meta-item box builders (write counterparts to the demux parsers): new public demux::build_iloc_box / build_pitm_box / build_iinf_box / build_iref_box / build_idat_box serialise the HEIF/MIAF item infrastructure, each the byte-exact inverse of its parser so a parse_* record re-emits identically. build_iloc_box honours the record's field-width selectors verbatim (so an arbitrary v0/v1/v2 IlocBox round-trips) and rejects records that would not round-trip — a *_size outside {0,4,8}, a version-0 item carrying a non-zero construction_method (the field is absent on the wire for v0), or index_size > 0 on a version-0 box. build_pitm_box auto-selects version 0 (16-bit) / 1 (32-bit) by ID magnitude; build_iinf_box auto-selects the entry_count width and serialises each infe (versions 0/1/2/3, with the v2/3 mime/uri tails) — rejecting a name/type string with an embedded NUL that would corrupt the framing; build_iref_box emits the typed SingleItemTypeReferenceBox groups at the record's 16-/32-bit width. Six new round-trip unit tests (iloc v0/v1/v2 + inconsistent-record rejection, pitm, mixed-version iinf, iref v0/v1, and a built-iloc+built-idat assembly read back through item_data_from_idat). Completes parse/build symmetry for the §8.11 item family (ISO/IEC 14496-12 §8.11.3 / §8.11.4 / §8.11.6 / §8.11.11 / §8.11.12)

  • Round 369 — idat (ItemDataBox, ISO/IEC 14496-12 §8.11.11) capture + item-byte resolution: a file-level meta's idat box bytes are now captured into MetaItems::idat and the §8.11.3.3 data-origin rules are honoured by new resolution helpers. MetaItems::item_byte_ranges(item_id) returns each extent's (offset, length) with the item's base_offset folded in (relative to the data origin selected by the item's construction_method — absolute file offset for method 0, idat offset for method 1). MetaItems::item_data_from_idat(item_id) materialises the concatenated bytes of an item whose construction_method == 1 (idat-resident, e.g. a small Exif/XMP blob stored inline in the meta), with the §8.11.3.3 "entire source" (extent_length == 0, single extent) convention and bounds-checking that rejects any extent overrunning the idat. File-offset items (method 0) are deliberately not re-read from the input here (the demuxer does not re-read untimed items); their absolute ranges come from item_byte_ranges. Convenience MetaItems::iloc_item(id) / item_info(id) look up a single item's iloc location / iinf info by ID. idat length is surfaced on Demuxer::metadata() as meta_idat_len. Two new unit tests (idat resolution + lookups; overrun rejection) (ISO/IEC 14496-12 §8.11.11 / §8.11.3.3)

  • Round 369 — file-level meta box item infrastructure (HEIF / MIAF item catalogue, ISO/IEC 14496-12 §8.11) demux: the top-level walk previously parsed a meta box only inside moov/trak for iTunes-style ilst metadata and ignored a file-level meta entirely — that is the home of the HEIF/MIAF still-image item catalogue. The opener now walks a file-level meta and decodes its four §8.11 child boxes into a new public demux::MetaItems record: pitm (PrimaryItemBox §8.11.4, primary item_ID, v0 16-bit / v1 32-bit), iloc (ItemLocationBox §8.11.3, the full v0/v1/v2 layout — the box-global offset_size/length_size/base_offset_size/index_size nibble selectors from the set {0,4,8}, per-item construction_method (file/idat/item) + data_reference_index + base_offset, and the per-extent extent_index/extent_offset/extent_length loop with v2's 32-bit item_ID/item_count widening), iinf/infe (ItemInfoBox §8.11.6, every infe version 0/1/2/3 — v0/1 (name, content_type, content_encoding) and v2/3's 32-bit item_type FourCC with mime/uri tails), and iref (ItemReferenceBox §8.11.12, the typed SingleItemTypeReferenceBox groups, v0 16-bit / v1 32-bit from/to item IDs). The meta's hdlr handler_type (e.g. pict) is captured too. New public stateless parsers demux::parse_iloc_box / parse_pitm_box / parse_iinf_box / parse_iref_box / parse_meta_items decode each box from its body; malformed/truncated boxes are dropped gracefully rather than aborting the open, and invalid field widths (a *_size outside {0,4,8}) are rejected. Records reachable via the new Mp4Demuxer::meta_items() accessor; a flat summary is surfaced on Demuxer::metadata() as meta_handler / meta_primary_item / meta_item_count / meta_item_<n> (id=<id> type=<fourcc> name=<name>) / meta_iloc_count / meta_iref_count. A plain iTunes meta (just hdlr + ilst) yields an empty MetaItems (MetaItems::is_empty()) and emits no item keys. New tests/meta_items.rs integration case (HEIF meta spliced after ftyp, two items + thumbnail iref, real audio track undisturbed) + 11 src/demux.rs unit tests covering iloc v0/v1/v2 + bad-width rejection, pitm v0/v1, iinf with infe v0/v2, iref v0/v1, and the full parse_meta_items assembly (ISO/IEC 14496-12 §8.11)

  • Round 364 — fragment-local sample groups (sgpd / sbgp / csgp inside traf, §8.9.2 / §8.9.3 / §8.9.5) demux: the traf walk previously skipped sample-group boxes; it now parses each fragment's sgpd description table plus sbgp / csgp per-sample maps and collects them into a new public demux::TrafSampleGroupRecord keyed by (track_idx, moof_sequence). This is where the csgp bit-7 index_msb_indicates_fragment_local_description flag is actually legal (§8.9.5) — a fragment can declare fragment-local group descriptions and map samples into either those or the trak-level global ones (most commonly CENC seig key rotation across a fragment's samples). A per-traf summary is surfaced via Demuxer::metadata() as frag_sample_group_<n> ("track=<t> seq=<s> sgpd=<n> sbgp=<m> csgp=<k>"); structured records reachable via the new Mp4Demuxer::traf_sample_groups() accessor. The demuxer-internal SbgpBox / SgpdBox parse structs are now public (their fields preserve on-wire values verbatim). Sample-group boxes don't disturb the trun sample walk. New tests/fragmented.rs integration case (metadata + sample-walk) + two src/demux.rs unit tests (structured-record values incl. fragment-local MSB resolution, and the no-groups path) (ISO/IEC 14496-12 §8.9 inside traf)

  • Round 364 — csgp (CompactSampleToGroupBox, §8.9.5) non-fragmented mux emission: TrackSampleGroups gains a csgp: Vec<sample_groups::CompactSampleToGroup> field (empty by default) alongside the existing sbgp/sgpd. When non-empty, the muxer writes each track's csgp boxes into its stbl after the chunk-offset table and after any sbgp (so the sgpd description tables they reference are declared first). sbgp and csgp are alternative encodings of one per-sample → group mapping — §8.9.5 permits at most one form per grouping_type, so a caller picks one per type while distinct types may each pick their own form. This wires the already-public sample_groups::build_csgp builder into the muxer, closing the write half of csgp (the demuxer already parsed it via parse_csgp_box); a muxer-emitted csgp round-trips byte-exact through the demuxer's csgp_<n> metadata surface (grouping type, optional param=, optional fraglocal marker, per-pattern count*i0,i1 tokens) including the width-code auto-selection, the grouping_type_parameter, and the fragment-local MSB flag. Default (empty csgp) output is byte-identical to before. New tests/sample_groups_mux.rs cases cover single-pattern, multi-pattern + grouping_type_parameter, sbgp/csgp coexistence on distinct grouping types, and fragment-local-flag round-trips (ISO/IEC 14496-12:2020 §8.9.5)

  • Round 360 — trep (TrackExtensionPropertiesBox, §8.8.15) fragmented-mux emission: new FragmentedOptions::treps: Vec<demux::TrepRecord> (empty by default). When non-empty, the fragmented muxer writes one trep per record after the trex boxes (and after any leva) inside the init-segment mvex, each documenting characteristics of one track in the subsequent fragments (the one base-spec-defined child, assp §8.8.16, is serialised from its typed AsspRecord when present). Default (empty) output is byte-identical to before. New public demux::build_trep_box(&TrepRecord) builder — the write counterpart to parse_trep_box, serialising the FullBox preamble + track_id + children (a typed assp child via build_assp_box, any other child as an empty placeholder box; a non-assp child carrying an AsspRecord, or an opaque child with a non-zero payload_len, is rejected). A muxer-emitted trep+assp reads back through the demuxer's mvex walk (trep_<n> metadata). Closes the write/read symmetry for trep (ISO/IEC 1449

@MagicalTux
MagicalTux force-pushed the release-plz-2026-06-15T05-40-11Z branch 13 times, most recently from e320d12 to 112d7ef Compare June 22, 2026 06:15
@MagicalTux
MagicalTux force-pushed the release-plz-2026-06-15T05-40-11Z branch 17 times, most recently from acec326 to 33b73f0 Compare June 28, 2026 21:40
@MagicalTux
MagicalTux force-pushed the release-plz-2026-06-15T05-40-11Z branch 10 times, most recently from 537be3d to 216aa12 Compare July 3, 2026 22:00
@MagicalTux
MagicalTux force-pushed the release-plz-2026-06-15T05-40-11Z branch 6 times, most recently from 2ffd841 to c302d72 Compare July 10, 2026 07:22
@MagicalTux
MagicalTux force-pushed the release-plz-2026-06-15T05-40-11Z branch 8 times, most recently from 9483b41 to eaccd7b Compare July 17, 2026 14:11
@MagicalTux
MagicalTux force-pushed the release-plz-2026-06-15T05-40-11Z branch 3 times, most recently from 6c83ab1 to 839c4c9 Compare August 6, 2026 07:38
@MagicalTux
MagicalTux force-pushed the release-plz-2026-06-15T05-40-11Z branch from 839c4c9 to d6aa522 Compare August 15, 2026 06:22
@MagicalTux
MagicalTux merged commit 660c9ce into master Sep 1, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant