mux: add high-level mux_stream driver (layering step 4a)
Add the shared decrypt+mux driver both consumers hand-roll today, as a
library-only API. `mux_stream(input, dest_url, opts, halt, events)` runs
the construct -> headers gate -> open sink -> pump -> finish pipeline:
- MuxInput::{Session, Iso, Url} selects the source. The file/ISO path
calls the untouched build_iso_pipeline highway (zero added copies —
the driver reads frames exactly where consumers call stream.read());
the live path builds a DiscStream; a URL source goes through input().
- chapters:// / json:// metadata sinks short-circuit BEFORE the header
pump/gate. They write their whole file from the scanned title and need
no codec headers; the CLI placed this after the gate, so a metadata
export on a title whose video headers never resolved failed with
MkvInvalid. Fixed by construction.
- The header gate refuses a stream with no resolved codec_private
(MkvInvalid); the zero-output gate refuses an empty/undecryptable drain
(NoStreams); a halt mid-pump yields completed=false, never a success.
- Frames are written through a WRITE_PIPELINE_DEPTH consumer pipeline so
the latency-bound sink write overlaps the next read.
Add error::is_skippable_title_stub(&io::Error) so consumers can drop the
E7023/E6008 string-match, and DiscSession::take_reader for the live arm.
Driver body unit-tested in isolation via a synthetic Stream against
null:// / chapters:// / json:// sinks (short-circuit, header gate,
zero-output gate, halt, happy path); each gate mutation-verified.
Consumers are NOT migrated yet (steps 4b/4c).
This commit is contained in:
@@ -321,6 +321,17 @@ impl DiscSession {
|
||||
pub fn into_reader(self) -> Option<Box<dyn SectorSource>> {
|
||||
self.reader
|
||||
}
|
||||
|
||||
/// Take the staged sector source out of the session by mutable borrow,
|
||||
/// leaving `None` behind. Used by [`crate::mux::mux_stream`]'s
|
||||
/// [`MuxInput::Session`](crate::mux::MuxInput::Session) arm, which drives
|
||||
/// the mux from `&mut DiscSession` and so cannot consume the whole session.
|
||||
/// A second call (or a call before the reader is staged) returns `None`, and
|
||||
/// the driver maps that to a clean error rather than a panic (see Q2 of the
|
||||
/// boundary-audit contract).
|
||||
pub fn take_reader(&mut self) -> Option<Box<dyn SectorSource>> {
|
||||
self.reader.take()
|
||||
}
|
||||
}
|
||||
|
||||
/// Scan an ISO image's structure from a file path, returning the scanned
|
||||
|
||||
Reference in New Issue
Block a user