mirror of
https://github.com/freemkv/libfreemkv.git
synced 2026-10-07 18:13:10 +00:00
Security - Remove the unsafe Vec::set_len over recycled sector buffers — UB reachable from safe code (GHSA-j8ww-f5fg-9pmh, low). Uses Vec::resize; pooling unchanged. CI / tooling - clippy on stable with the MSRV single-sourced from Cargo.toml; cargo-deny dependency-audit gate with first-party git sources allowed (#15); audience-based comment-guard (#17). Docs - README (keydb ownership, BD-J parser count), drive-access transport/reset notes, mux timeline module-doc links.
75 lines
3.9 KiB
Markdown
75 lines
3.9 KiB
Markdown
# `demux://` sink — per-codec ES output
|
|
|
|
The `demux://` sink is a write-only `crate::pes::Stream`. Instead of muxing
|
|
the per-track `PesFrame` stream into a single container (as `MkvStream`
|
|
does), it routes each frame's payload to the file for `frame.track`,
|
|
post-processing where the internal codec `Frame` form differs from the
|
|
standalone on-disk ES form:
|
|
|
|
- **HEVC / H.264**: the codec parsers emit hvcC/avcC-style 4-byte
|
|
length-prefixed NALs (start codes stripped) and carry the parameter sets
|
|
out-of-band in `codec_private`. A standalone `.hevc`/`.h264` needs Annex-B
|
|
framing with the parameter sets prepended — see `AnnexBWriter`.
|
|
- **PGS**: the parser collapses a display/clear PCS pair into one
|
|
duration-bearing `Frame` with the raw segment payload but no `PG` magic /
|
|
timestamp header. A `.sup` needs the HDMV segment framing rebuilt — see
|
|
`PgsSupWriter`.
|
|
- **VobSub**: the `.sub` is the raw SPU stream; the `.idx` sidecar is
|
|
synthesized from per-SPU PTS + byte offsets — see `VobSubWriter`.
|
|
|
|
Every other codec writes `frame.data` verbatim (`PassthroughWriter`).
|
|
|
|
The sink does NOT touch the MKV mux path; it is purely additive.
|
|
|
|
## `PgsSupWriter`: rebuilding HDMV segment framing
|
|
|
|
The parser hands the demux sink the concatenated PGS segments of a display
|
|
set in `frame.data` (segment_type + segment_size + payload, repeated), with
|
|
no `PG` magic and no PTS/DTS. A `.sup` prefixes each segment with a 13-byte
|
|
header: `0x50 0x47` ("PG") | PTS u32 BE | DTS u32 BE | (segment_type|size
|
|
already present in the payload). When the parser folded a trailing clear
|
|
(`duration_ns` set), the writer re-emits it as an empty composition at
|
|
`pts + duration` so players time the subtitle out.
|
|
|
|
## `synthetic_clear_display_set`: synthesizing the clear PCS
|
|
|
|
Builds a synthetic "clear" display set: an empty PCS (0 composition objects)
|
|
followed by an END segment. The parser folds the original clear/end PCS
|
|
pair's wipe time into the display frame's `duration_ns` and drops the clear
|
|
bytes, so a faithful `.sup` re-emits one here at `display_pts + duration`;
|
|
without it every subtitle lingers to EOF. `width`/`height` are carried from
|
|
the display set's PCS so the clear PCS advertises the same video geometry —
|
|
they don't affect the wipe but keep the segment well-formed. Returned bytes
|
|
are concatenated `type(1)+size(2 BE)+payload` segments, the same shape
|
|
`emit_segments` consumes.
|
|
|
|
## Test: `epoch_driver_follows_ref_video_not_track_zero`
|
|
|
|
Regression for the hardcoded `frame.track == 0` epoch driver: on an M2TS/PMT
|
|
title the PMT can list an AUDIO ES before the VIDEO ES, so the video lands
|
|
at stream index 1. The sink already resolves the video reference
|
|
dynamically (`ref_video_track`); the epoch driver must use that SAME
|
|
reference, not the literal 0. In the test, track 0 is audio and track 1 is
|
|
video; a video-only clip boundary (track 1) must open a new epoch (bump
|
|
`offset_ns`). With the bug (only `frame.track == 0` drives epochs) the
|
|
video back-jump would be treated as a passive rider and `offset_ns` would
|
|
stay 0 — corrupting every track's rebased timeline.
|
|
|
|
## Test: `a_demux_export_the_seam_plan_emptied_fails`
|
|
|
|
Audit finding: this sink builds its timeline with
|
|
`TimelineContinuity::with_clips` but no test ever gave it a title with
|
|
usable PlayItem marks, so `frames_mapped` and `dropped_total()` were always
|
|
zero and BOTH gates in `finish` were unreachable. Deleting either left the
|
|
whole suite green while a `demux://` export of a seamless-branching title
|
|
wrote zero-byte track files beside a populated chapters document, at exit 0.
|
|
|
|
## `sanitize`: path-hostile filename characters
|
|
|
|
Control characters are replaced, NUL above all. Every string this touches is
|
|
disc bytes, and a language code is three raw STN bytes run through
|
|
`from_utf8_lossy` with no validation — `00 00 00` is the ordinary
|
|
"undefined" encoding on real Blu-rays. A NUL in a path aborts
|
|
`File::create` with `InvalidInput`, which took the whole demux export down
|
|
before a single track file was opened.
|