Chapters, DVD subtitle palette, MKV track flags, progress total_bytes

Chapters:
- MPLS PlayList marks parsed (mark_type 1 = chapter)
- Chapter struct on DiscTitle (time_secs, name)
- MKV Chapters element with EditionEntry/ChapterAtom per mark
- 3 MPLS mark tests + 2 MKV chapter tests

DVD subtitle palette:
- IFO palette extraction (PGC offset 0xA4, 16 × YCbCr colors)
- YCbCr→RGB conversion for VobSub .idx format
- DvdSubParser codec_private returns formatted palette
- codec_data field on SubtitleStream flows through pipeline
- 5 palette tests (YCbCr conversion, formatting, overflow)

MKV track flags:
- FlagDefault: primary video/audio = 1, secondary = 0
- FlagForced: forced subtitles = 1
- Language: set from stream language code
- Already implemented, verified with 4 new tests

Progress total_bytes:
- IOStream trait: total_bytes() -> Option<u64>
- DiscStream, IsoStream: from disc_title.size_bytes
- M2tsStream, MkvStream: from file metadata on open
- NetworkStream, StdioStream, NullStream: None

316 tests total, all passing.
This commit is contained in:
MattJackson
2026-04-11 17:43:47 +00:00
parent cd575b7221
commit 96a65de3ff
21 changed files with 690 additions and 12 deletions
+14 -2
View File
@@ -51,6 +51,8 @@ pub struct MkvStream {
mode: Mode,
max_buffer: usize,
finished: bool,
/// File size in bytes, set for read mode.
file_size: Option<u64>,
}
impl MkvStream {
@@ -71,6 +73,7 @@ impl MkvStream {
}),
max_buffer: DEFAULT_MAX_BUFFER,
finished: false,
file_size: None,
}
}
@@ -90,7 +93,7 @@ impl MkvStream {
crate::disc::Stream::Subtitle(s) => (
s.pid,
MkvTrack::subtitle(s),
codec::parser_for_codec(s.codec),
codec::parser_for_codec_with_data(s.codec, s.codec_data.clone()),
),
};
let idx = ws.tracks.len();
@@ -116,6 +119,8 @@ impl MkvStream {
/// Open an MKV file for reading.
pub fn open(mut reader: impl Read + Seek + 'static) -> io::Result<Self> {
let file_size = reader.seek(SeekFrom::End(0))?;
reader.seek(SeekFrom::Start(0))?;
let disc_title = parse_mkv_header(&mut reader)?;
Ok(Self {
disc_title,
@@ -128,6 +133,7 @@ impl MkvStream {
}),
max_buffer: 0,
finished: false,
file_size: Some(file_size),
})
}
}
@@ -156,6 +162,10 @@ impl IOStream for MkvStream {
}
Ok(())
}
fn total_bytes(&self) -> Option<u64> {
self.file_size
}
}
// ── Write ──────────────────────────────────────────────────────
@@ -314,11 +324,12 @@ fn begin_streaming(ws: &mut WriteState, dt: &DiscTitle) -> io::Result<()> {
.take()
.ok_or_else(|| io::Error::other("writer already consumed"))?;
ws.muxer = Some(MkvMuxer::new(
ws.muxer = Some(MkvMuxer::new_with_chapters(
writer,
&ws.tracks,
Some(&dt.playlist),
dt.duration_secs,
&dt.chapters,
)?);
ws.phase = WritePhase::Streaming;
@@ -531,6 +542,7 @@ fn parse_track(r: &mut (impl Read + Seek), size: u64) -> io::Result<Option<crate
codec,
language: lang,
forced,
codec_data: None,
})),
_ => None,
})