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.
115 lines
3.1 KiB
Rust
115 lines
3.1 KiB
Rust
//! StdioStream — raw byte pipe via stdin/stdout. Format-agnostic.
|
|
|
|
use super::IOStream;
|
|
use crate::disc::DiscTitle;
|
|
use std::io::{self, Read, Write};
|
|
|
|
/// Stdio stream — reads from stdin, writes to stdout.
|
|
///
|
|
/// No headers, no metadata, no format opinions. Just bytes.
|
|
/// The format is determined by whatever is on the other end.
|
|
pub struct StdioStream {
|
|
disc_title: DiscTitle,
|
|
reader: Option<io::Stdin>,
|
|
writer: Option<io::Stdout>,
|
|
}
|
|
|
|
impl StdioStream {
|
|
/// Create a stdio stream for reading (stdin).
|
|
pub fn input() -> Self {
|
|
Self {
|
|
disc_title: DiscTitle::empty(),
|
|
reader: Some(io::stdin()),
|
|
writer: None,
|
|
}
|
|
}
|
|
|
|
/// Create a stdio stream for writing (stdout).
|
|
pub fn output() -> Self {
|
|
Self {
|
|
disc_title: DiscTitle::empty(),
|
|
reader: None,
|
|
writer: Some(io::stdout()),
|
|
}
|
|
}
|
|
|
|
/// Set metadata (for output — passed through from input side).
|
|
pub fn meta(mut self, dt: &DiscTitle) -> Self {
|
|
self.disc_title = dt.clone();
|
|
self
|
|
}
|
|
}
|
|
|
|
impl IOStream for StdioStream {
|
|
fn info(&self) -> &DiscTitle {
|
|
&self.disc_title
|
|
}
|
|
fn finish(&mut self) -> io::Result<()> {
|
|
if let Some(ref mut w) = self.writer {
|
|
w.flush()?;
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Read for StdioStream {
|
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
match self.reader {
|
|
Some(ref mut r) => r.read(buf),
|
|
None => Err(io::Error::new(
|
|
io::ErrorKind::Unsupported,
|
|
"stdio:// opened for output — cannot read",
|
|
)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Write for StdioStream {
|
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
match self.writer {
|
|
Some(ref mut w) => w.write(buf),
|
|
None => Err(io::Error::new(
|
|
io::ErrorKind::Unsupported,
|
|
"stdio:// opened for input — cannot write",
|
|
)),
|
|
}
|
|
}
|
|
fn flush(&mut self) -> io::Result<()> {
|
|
match self.writer {
|
|
Some(ref mut w) => w.flush(),
|
|
None => Ok(()),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::io::{Read, Write};
|
|
|
|
#[test]
|
|
fn stdio_output_write_errors_on_read() {
|
|
let mut stream = StdioStream::output();
|
|
let mut buf = [0u8; 10];
|
|
let err = stream.read(&mut buf).unwrap_err();
|
|
assert_eq!(err.kind(), io::ErrorKind::Unsupported);
|
|
assert!(err.to_string().contains("cannot read"), "got: {}", err);
|
|
}
|
|
|
|
#[test]
|
|
fn stdio_input_read_errors_on_write() {
|
|
let mut stream = StdioStream::input();
|
|
let err = stream.write(&[0u8; 10]).unwrap_err();
|
|
assert_eq!(err.kind(), io::ErrorKind::Unsupported);
|
|
assert!(err.to_string().contains("cannot write"), "got: {}", err);
|
|
}
|
|
|
|
#[test]
|
|
fn stdio_total_bytes_returns_none() {
|
|
let input = StdioStream::input();
|
|
assert_eq!(input.total_bytes(), None);
|
|
let output = StdioStream::output();
|
|
assert_eq!(output.total_bytes(), None);
|
|
}
|
|
}
|