mux: add video:// sink (video-only per-track elementary streams)

Completes the per-track-class trio with audio:// / sub://. video:// is
the demux path with a TrackKind::Video kind filter — each video track to
its own native elementary stream (.hevc/.h264/.vc1/.m2v/.obu), no audio
or subtitles. Reuses the existing kind_filter machinery and extension
map; scheme/parse/output/write-only-guard arms added symmetrically.
This commit is contained in:
Matthew Jackson
2026-07-18 19:24:59 -07:00
parent 281d8baed6
commit 489545c865
3 changed files with 26 additions and 5 deletions
+5 -1
View File
@@ -4,8 +4,12 @@
### Added
- **Four extraction sinks — dissect a title, don't just rip it.** New write-only
- **Five extraction sinks — dissect a title, don't just rip it.** New write-only
destinations that pull one facet of a title out on its own:
- **`video://dir/`** — every video track to its own file, as a raw elementary
stream in the codec's native form (`.hevc`, `.h264`, `.vc1`, `.m2v`, `.obu`).
No audio, no subtitles. Completes the per-track-class trio with `audio://` /
`sub://`.
- **`audio://dir/`** — every audio track to its own file in a directory, each in
its native container (`.thd`, `.dts` / `.dtshd`, `.ac3`, `.eac3`, `.aac`,
`.flac`). No video, no subtitles. BD/DVD LPCM has no container of its own and is
+1
View File
@@ -159,6 +159,7 @@ mod tests {
assert_eq!(parse_url("iso://f").scheme(), "iso");
assert_eq!(parse_url("null://").scheme(), "null");
assert_eq!(parse_url("demux://out/").scheme(), "demux");
assert_eq!(parse_url("video://out/").scheme(), "video");
assert_eq!(parse_url("audio://out/").scheme(), "audio");
assert_eq!(parse_url("sub://out/").scheme(), "sub");
assert_eq!(parse_url("chapters://c.xml").scheme(), "chapters");
+20 -4
View File
@@ -62,6 +62,10 @@ pub enum StreamUrl {
/// chapters + delay metadata). Like `dir://` it targets a directory; the
/// CLI constructs the `DemuxSink` with full options before the mux loop.
Demux { dir: PathBuf },
/// Video-only per-track output directory (`video://`) — a `demux://`
/// restricted to video tracks (native elementary streams: `.hevc`, `.h264`,
/// `.vc1`, `.m2v`, …). One file per video track; no audio/subtitles.
Video { dir: PathBuf },
/// Audio-only per-track output directory (`audio://`) — a `demux://`
/// restricted to audio tracks (native containers: `.thd`, `.dts`, `.ac3`,
/// `.eac3`, `.pcm`, …). One file per audio track; no video/subtitles.
@@ -100,6 +104,7 @@ impl StreamUrl {
StreamUrl::Dir { .. } => "dir",
StreamUrl::Null => "null",
StreamUrl::Demux { .. } => "demux",
StreamUrl::Video { .. } => "video",
StreamUrl::Audio { .. } => "audio",
StreamUrl::Sub { .. } => "sub",
StreamUrl::Fvi { .. } => "fvi",
@@ -119,6 +124,7 @@ impl StreamUrl {
| StreamUrl::Iso { path }
| StreamUrl::Dir { path }
| StreamUrl::Demux { dir: path }
| StreamUrl::Video { dir: path }
| StreamUrl::Audio { dir: path }
| StreamUrl::Sub { dir: path }
| StreamUrl::Fvi { path }
@@ -196,6 +202,11 @@ pub fn parse_url(url: &str) -> StreamUrl {
dir: PathBuf::from(rest),
};
}
if let Some(rest) = url.strip_prefix("video://") {
return StreamUrl::Video {
dir: PathBuf::from(rest),
};
}
if let Some(rest) = url.strip_prefix("audio://") {
return StreamUrl::Audio {
dir: PathBuf::from(rest),
@@ -493,6 +504,7 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::S
StreamUrl::Null => Err(crate::error::Error::StreamWriteOnly.into()),
// `demux://` is an output-only sink (per-track ES files); never a source.
StreamUrl::Demux { .. }
| StreamUrl::Video { .. }
| StreamUrl::Audio { .. }
| StreamUrl::Sub { .. }
| StreamUrl::Chapters { .. }
@@ -575,11 +587,15 @@ pub fn output(
dir, title, &opts,
)?))
}
// `audio://` and `sub://` are `demux://` restricted to one track class —
// audio in native containers, or subtitles as `.sup`/`.idx+.sub`/`.srt`.
// No chapters sidecar (that's a `demux://` / `chapters://` concern).
StreamUrl::Audio { ref dir } | StreamUrl::Sub { ref dir } => {
// `video://`, `audio://`, and `sub://` are `demux://` restricted to one
// track class — video as native elementary streams, audio in native
// containers, or subtitles as `.sup`/`.idx+.sub`/`.srt`. No chapters
// sidecar (that's a `demux://` / `chapters://` concern).
StreamUrl::Video { ref dir }
| StreamUrl::Audio { ref dir }
| StreamUrl::Sub { ref dir } => {
let (scheme, kind) = match parsed {
StreamUrl::Video { .. } => ("video", super::demux_sink::TrackKind::Video),
StreamUrl::Audio { .. } => ("audio", super::demux_sink::TrackKind::Audio),
_ => ("sub", super::demux_sink::TrackKind::Subtitle),
};