Fix batch overflow in Disc::copy(), DVD PGC parsing, demuxer flush at EOF

- Disc::copy() hardcoded batch=64 sectors, exceeding BU40N's 60-sector
  hw limit. Now accepts batch_sectors param, defaults to 60.
- IFO PGC: playback time at offset 0x04 not 0x02, cell time at cell+4
- DiscStream: set demuxer from content_format (TS for BD, PS for DVD)
- Flush TS/PS demuxers at EOF to avoid losing last PES frame
- M2tsStream: flush demuxer at EOF
- StdioStream: FMKV metadata header for roundtrip compatibility
This commit is contained in:
MattJackson
2026-04-15 22:26:07 +00:00
parent 937bb038c1
commit c11d2bea31
6 changed files with 182 additions and 26 deletions
+51 -2
View File
@@ -1,13 +1,21 @@
//! StdioStream — PES frames via stdin/stdout.
//! StdioStream — PES frames via stdin/stdout with FMKV metadata header.
//!
//! The FMKV header carries stream metadata (PIDs, codecs, languages, codec_privates)
//! so the receiving end can set up muxing without scanning the content.
use super::meta;
use crate::disc::DiscTitle;
use std::io::{self, Write};
/// Stdio stream — reads PES from stdin, writes PES to stdout.
/// FMKV metadata header is written/read automatically.
pub struct StdioStream {
disc_title: DiscTitle,
reader: Option<io::Stdin>,
writer: Option<io::BufWriter<io::Stdout>>,
header_written: bool,
header_read: bool,
stored_codec_privates: Vec<Option<Vec<u8>>>,
}
impl StdioStream {
@@ -17,6 +25,9 @@ impl StdioStream {
disc_title: DiscTitle::empty(),
reader: Some(io::stdin()),
writer: None,
header_written: false,
header_read: false,
stored_codec_privates: Vec::new(),
}
}
@@ -26,12 +37,32 @@ impl StdioStream {
disc_title: title.clone(),
reader: None,
writer: Some(io::BufWriter::new(io::stdout())),
header_written: false,
header_read: false,
stored_codec_privates: Vec::new(),
}
}
/// Read the FMKV metadata header from stdin on first read.
fn ensure_header_read(&mut self) -> io::Result<()> {
if self.header_read {
return Ok(());
}
self.header_read = true;
if let Some(ref mut r) = self.reader {
if let Ok(Some(m)) = meta::read_header(r) {
let title = m.to_title();
self.stored_codec_privates = title.codec_privates.clone();
self.disc_title = title;
}
}
Ok(())
}
}
impl crate::pes::Stream for StdioStream {
fn read(&mut self) -> io::Result<Option<crate::pes::PesFrame>> {
self.ensure_header_read()?;
match &mut self.reader {
Some(r) => crate::pes::PesFrame::deserialize(r),
None => Err(crate::error::Error::StreamWriteOnly.into()),
@@ -39,7 +70,16 @@ impl crate::pes::Stream for StdioStream {
}
fn write(&mut self, frame: &crate::pes::PesFrame) -> io::Result<()> {
match &mut self.writer {
Some(w) => frame.serialize(w),
Some(ref mut w) => {
if !self.header_written {
if !self.disc_title.streams.is_empty() {
let m = meta::M2tsMeta::from_title(&self.disc_title);
meta::write_header(w, &m)?;
}
self.header_written = true;
}
frame.serialize(w)
}
None => Err(crate::error::Error::StreamReadOnly.into()),
}
}
@@ -48,4 +88,13 @@ impl crate::pes::Stream for StdioStream {
Ok(())
}
fn info(&self) -> &DiscTitle { &self.disc_title }
fn codec_private(&self, track: usize) -> Option<Vec<u8>> {
self.stored_codec_privates.get(track).and_then(|c| c.clone())
}
fn headers_ready(&self) -> bool {
// After first read(), header is parsed and codec_privates populated
self.header_read || self.writer.is_some()
}
}