Unified Stream trait: read() and write() on one type
Stream trait: read() returns PesFrame, write() accepts PesFrame. A stream is a stream — you read from it or write to it. No separate Input/Output traits. API: libfreemkv::input(url) and libfreemkv::output(url, title, codecs) Returns Box<dyn Stream>.
This commit is contained in:
@@ -111,12 +111,12 @@ impl CodecParser for H264Parser {
|
||||
// pictureParameterSetNALUnit = pps
|
||||
|
||||
let mut record = vec![
|
||||
1, // configurationVersion
|
||||
sps[1], // profile
|
||||
sps[2], // compatibility
|
||||
sps[3], // level
|
||||
0xFF, // 6 bits reserved (111111) + 2 bits lengthSizeMinusOne (11 = 3)
|
||||
0xE1, // 3 bits reserved (111) + 5 bits numSPS (1)
|
||||
1, // configurationVersion
|
||||
sps[1], // profile
|
||||
sps[2], // compatibility
|
||||
sps[3], // level
|
||||
0xFF, // 6 bits reserved (111111) + 2 bits lengthSizeMinusOne (11 = 3)
|
||||
0xE1, // 3 bits reserved (111) + 5 bits numSPS (1)
|
||||
(sps.len() >> 8) as u8,
|
||||
sps.len() as u8,
|
||||
];
|
||||
|
||||
@@ -172,7 +172,8 @@ fn parse_vc1_resolution(sh: &[u8]) -> Option<(u32, u32)> {
|
||||
}
|
||||
|
||||
fn find_next_sc(data: &[u8], from: usize) -> Option<usize> {
|
||||
(from..data.len().saturating_sub(2)).find(|&i| data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x01)
|
||||
(from..data.len().saturating_sub(2))
|
||||
.find(|&i| data[i] == 0x00 && data[i + 1] == 0x00 && data[i + 2] == 0x01)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+11
-2
@@ -333,8 +333,8 @@ impl IOStream for DiscStream {
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::pes::InputStream for DiscStream {
|
||||
fn next_frame(&mut self) -> io::Result<Option<crate::pes::PesFrame>> {
|
||||
impl crate::pes::Stream for DiscStream {
|
||||
fn read(&mut self) -> io::Result<Option<crate::pes::PesFrame>> {
|
||||
// Return buffered frame if available
|
||||
if let Some(frame) = self.pending_frames.pop_front() {
|
||||
return Ok(Some(frame));
|
||||
@@ -399,6 +399,15 @@ impl crate::pes::InputStream for DiscStream {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _frame: &crate::pes::PesFrame) -> io::Result<()> {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "disc is read-only"))
|
||||
}
|
||||
|
||||
fn finish(&mut self) -> io::Result<()> {
|
||||
self.drive.unlock_tray();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn info(&self) -> &DiscTitle {
|
||||
&self.title
|
||||
}
|
||||
|
||||
+8
-2
@@ -256,8 +256,8 @@ impl IsoStream {
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::pes::InputStream for IsoStream {
|
||||
fn next_frame(&mut self) -> io::Result<Option<crate::pes::PesFrame>> {
|
||||
impl crate::pes::Stream for IsoStream {
|
||||
fn read(&mut self) -> io::Result<Option<crate::pes::PesFrame>> {
|
||||
// Return buffered frame
|
||||
if let Some(frame) = self.pending_frames.pop_front() {
|
||||
return Ok(Some(frame));
|
||||
@@ -301,6 +301,12 @@ impl crate::pes::InputStream for IsoStream {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, _frame: &crate::pes::PesFrame) -> io::Result<()> {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "ISO is read-only for PES"))
|
||||
}
|
||||
|
||||
fn finish(&mut self) -> io::Result<()> { Ok(()) }
|
||||
|
||||
fn info(&self) -> &crate::disc::DiscTitle {
|
||||
&self.disc_title
|
||||
}
|
||||
|
||||
@@ -412,7 +412,7 @@ impl<W: Write + Seek> IsoWriter<W> {
|
||||
icb[ad_offset..ad_offset + 4].copy_from_slice(&(extent_len as u32).to_le_bytes());
|
||||
icb[ad_offset + 4..ad_offset + 8].copy_from_slice(§or_pos.to_le_bytes());
|
||||
ad_offset += 8; // each short_ad is 8 bytes
|
||||
let extent_sectors = ((extent_len + SECTOR_SIZE - 1) / SECTOR_SIZE) as u32;
|
||||
let extent_sectors = extent_len.div_ceil(SECTOR_SIZE) as u32;
|
||||
sector_pos += extent_sectors;
|
||||
remaining -= extent_len;
|
||||
}
|
||||
|
||||
+19
-74
@@ -3,9 +3,7 @@
|
||||
//! Format: [8B magic] [4B json_len] [JSON] [padding to 192B boundary] [BD-TS data...]
|
||||
//! Other tools skip the header during TS sync recovery (scan for 0x47).
|
||||
|
||||
use crate::disc::{
|
||||
AudioStream, Codec, ColorSpace, DiscTitle, HdrFormat, Stream, SubtitleStream, VideoStream,
|
||||
};
|
||||
use crate::disc::{AudioStream, ColorSpace, DiscTitle, Stream, SubtitleStream, VideoStream};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
|
||||
@@ -84,25 +82,25 @@ impl M2tsMeta {
|
||||
.map(|s| match s {
|
||||
Stream::Video(v) => MetaStream::Video {
|
||||
pid: v.pid,
|
||||
codec: codec_to_str(v.codec),
|
||||
resolution: v.resolution.clone(),
|
||||
frame_rate: v.frame_rate.clone(),
|
||||
hdr: hdr_to_str(v.hdr),
|
||||
codec: v.codec.id().into(),
|
||||
resolution: v.resolution.to_string(),
|
||||
frame_rate: v.frame_rate.to_string(),
|
||||
hdr: v.hdr.id().into(),
|
||||
label: v.label.clone(),
|
||||
secondary: v.secondary,
|
||||
},
|
||||
Stream::Audio(a) => MetaStream::Audio {
|
||||
pid: a.pid,
|
||||
codec: codec_to_str(a.codec),
|
||||
channels: a.channels.clone(),
|
||||
codec: a.codec.id().into(),
|
||||
channels: a.channels.to_string(),
|
||||
language: a.language.clone(),
|
||||
sample_rate: a.sample_rate.clone(),
|
||||
sample_rate: a.sample_rate.to_string(),
|
||||
label: a.label.clone(),
|
||||
secondary: a.secondary,
|
||||
},
|
||||
Stream::Subtitle(s) => MetaStream::Subtitle {
|
||||
pid: s.pid,
|
||||
codec: codec_to_str(s.codec),
|
||||
codec: s.codec.id().into(),
|
||||
language: s.language.clone(),
|
||||
forced: s.forced,
|
||||
},
|
||||
@@ -133,10 +131,10 @@ impl M2tsMeta {
|
||||
secondary,
|
||||
} => Stream::Video(VideoStream {
|
||||
pid: *pid,
|
||||
codec: str_to_codec(codec),
|
||||
resolution: resolution.clone(),
|
||||
frame_rate: frame_rate.clone(),
|
||||
hdr: str_to_hdr(hdr),
|
||||
codec: codec.parse().unwrap(),
|
||||
resolution: resolution.parse().unwrap(),
|
||||
frame_rate: frame_rate.parse().unwrap(),
|
||||
hdr: hdr.parse().unwrap(),
|
||||
color_space: ColorSpace::Bt709,
|
||||
secondary: *secondary,
|
||||
label: label.clone(),
|
||||
@@ -151,10 +149,10 @@ impl M2tsMeta {
|
||||
secondary,
|
||||
} => Stream::Audio(AudioStream {
|
||||
pid: *pid,
|
||||
codec: str_to_codec(codec),
|
||||
channels: channels.clone(),
|
||||
codec: codec.parse().unwrap(),
|
||||
channels: channels.parse().unwrap(),
|
||||
language: language.clone(),
|
||||
sample_rate: sample_rate.clone(),
|
||||
sample_rate: sample_rate.parse().unwrap(),
|
||||
secondary: *secondary,
|
||||
label: label.clone(),
|
||||
}),
|
||||
@@ -165,7 +163,7 @@ impl M2tsMeta {
|
||||
forced,
|
||||
} => Stream::Subtitle(SubtitleStream {
|
||||
pid: *pid,
|
||||
codec: str_to_codec(codec),
|
||||
codec: codec.parse().unwrap(),
|
||||
language: language.clone(),
|
||||
forced: *forced,
|
||||
codec_data: None,
|
||||
@@ -277,58 +275,5 @@ pub fn read_header_from_stream(r: &mut impl Read) -> io::Result<Option<M2tsMeta>
|
||||
Ok(Some(meta))
|
||||
}
|
||||
|
||||
// Codec string conversion (compact, no English — just codec identifiers)
|
||||
fn codec_to_str(c: Codec) -> String {
|
||||
match c {
|
||||
Codec::Hevc => "hevc",
|
||||
Codec::H264 => "h264",
|
||||
Codec::Vc1 => "vc1",
|
||||
Codec::Mpeg2 => "mpeg2",
|
||||
Codec::TrueHd => "truehd",
|
||||
Codec::DtsHdMa => "dtshd_ma",
|
||||
Codec::DtsHdHr => "dtshd_hr",
|
||||
Codec::Dts => "dts",
|
||||
Codec::Ac3 => "ac3",
|
||||
Codec::Ac3Plus => "eac3",
|
||||
Codec::Lpcm => "lpcm",
|
||||
Codec::Pgs => "pgs",
|
||||
Codec::DvdSub => "dvdsub",
|
||||
Codec::Unknown(_) => "unknown",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn str_to_codec(s: &str) -> Codec {
|
||||
match s {
|
||||
"hevc" => Codec::Hevc,
|
||||
"h264" => Codec::H264,
|
||||
"vc1" => Codec::Vc1,
|
||||
"mpeg2" => Codec::Mpeg2,
|
||||
"truehd" => Codec::TrueHd,
|
||||
"dtshd_ma" => Codec::DtsHdMa,
|
||||
"dtshd_hr" => Codec::DtsHdHr,
|
||||
"dts" => Codec::Dts,
|
||||
"ac3" => Codec::Ac3,
|
||||
"eac3" => Codec::Ac3Plus,
|
||||
"lpcm" => Codec::Lpcm,
|
||||
"pgs" => Codec::Pgs,
|
||||
_ => Codec::Unknown(0),
|
||||
}
|
||||
}
|
||||
|
||||
fn hdr_to_str(h: HdrFormat) -> String {
|
||||
match h {
|
||||
HdrFormat::Sdr => "sdr",
|
||||
HdrFormat::Hdr10 => "hdr10",
|
||||
HdrFormat::DolbyVision => "dv",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn str_to_hdr(s: &str) -> HdrFormat {
|
||||
match s {
|
||||
"hdr10" => HdrFormat::Hdr10,
|
||||
"dv" => HdrFormat::DolbyVision,
|
||||
_ => HdrFormat::Sdr,
|
||||
}
|
||||
}
|
||||
// Serialization uses Codec::id() / HdrFormat::id() and Display impls.
|
||||
// Deserialization uses FromStr impls (.parse()) on each enum.
|
||||
|
||||
+5
-42
@@ -35,7 +35,7 @@ impl MkvTrack {
|
||||
Codec::Mpeg2 => "V_MPEG2",
|
||||
_ => "V_MPEG2",
|
||||
};
|
||||
let (w, h) = parse_resolution(&v.resolution);
|
||||
let (w, h) = v.resolution.pixels();
|
||||
Self {
|
||||
track_type: ebml::TRACK_TYPE_VIDEO,
|
||||
codec_id,
|
||||
@@ -61,8 +61,8 @@ impl MkvTrack {
|
||||
Codec::Lpcm => "A_PCM/INT/BIG",
|
||||
_ => "A_AC3",
|
||||
};
|
||||
let sr = parse_sample_rate(&a.sample_rate);
|
||||
let ch = parse_channels(&a.channels);
|
||||
let sr = a.sample_rate.hz();
|
||||
let ch = a.channels.count();
|
||||
Self {
|
||||
track_type: ebml::TRACK_TYPE_AUDIO,
|
||||
codec_id,
|
||||
@@ -414,45 +414,8 @@ impl<W: Write + Seek> MkvMuxer<W> {
|
||||
// Helpers
|
||||
// ============================================================
|
||||
|
||||
fn parse_resolution(s: &str) -> (u32, u32) {
|
||||
if s.contains("2160") {
|
||||
(3840, 2160)
|
||||
} else if s.contains("1080") {
|
||||
(1920, 1080)
|
||||
} else if s.contains("720") {
|
||||
(1280, 720)
|
||||
} else if s.contains("576") {
|
||||
(720, 576)
|
||||
} else if s.contains("480") {
|
||||
(720, 480)
|
||||
} else {
|
||||
(1920, 1080)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_sample_rate(s: &str) -> f64 {
|
||||
if s.contains("192") {
|
||||
192_000.0
|
||||
} else if s.contains("96") {
|
||||
96000.0
|
||||
} else {
|
||||
48000.0
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_channels(s: &str) -> u8 {
|
||||
if s.contains("7.1") {
|
||||
8
|
||||
} else if s.contains("5.1") {
|
||||
6
|
||||
} else if s.contains("stereo") || s.contains("2.0") {
|
||||
2
|
||||
} else if s.contains("mono") {
|
||||
1
|
||||
} else {
|
||||
6
|
||||
}
|
||||
}
|
||||
// Old parse_resolution/parse_sample_rate/parse_channels removed —
|
||||
// Resolution::pixels(), SampleRate::hz(), AudioChannels::count() replace them.
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
+11
-4
@@ -6,11 +6,12 @@
|
||||
use super::mkv::{MkvMuxer, MkvTrack};
|
||||
use super::WriteSeek;
|
||||
use crate::disc::DiscTitle;
|
||||
use crate::pes::{OutputStream, PesFrame};
|
||||
use crate::pes::PesFrame;
|
||||
use std::io;
|
||||
|
||||
pub struct MkvOutputStream {
|
||||
muxer: Option<MkvMuxer<Box<dyn WriteSeek>>>,
|
||||
title: DiscTitle,
|
||||
}
|
||||
|
||||
impl MkvOutputStream {
|
||||
@@ -43,12 +44,16 @@ impl MkvOutputStream {
|
||||
&title.chapters,
|
||||
)?;
|
||||
|
||||
Ok(Self { muxer: Some(muxer) })
|
||||
Ok(Self { muxer: Some(muxer), title: title.clone() })
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputStream for MkvOutputStream {
|
||||
fn write_frame(&mut self, frame: &PesFrame) -> io::Result<()> {
|
||||
impl crate::pes::Stream for MkvOutputStream {
|
||||
fn read(&mut self) -> io::Result<Option<PesFrame>> {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "MKV output is write-only"))
|
||||
}
|
||||
|
||||
fn write(&mut self, frame: &PesFrame) -> io::Result<()> {
|
||||
if let Some(ref mut muxer) = self.muxer {
|
||||
muxer.write_frame(frame.track, frame.pts, frame.keyframe, &frame.data)
|
||||
} else {
|
||||
@@ -63,4 +68,6 @@ impl OutputStream for MkvOutputStream {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn info(&self) -> &DiscTitle { &self.title }
|
||||
}
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ pub use m2ts::M2tsStream;
|
||||
pub use mkvstream::MkvStream;
|
||||
pub use network::NetworkStream;
|
||||
pub use null::NullStream;
|
||||
pub use resolve::{open_input, open_output, open_pes_input, open_pes_output, parse_url, InputOptions, StreamUrl};
|
||||
pub use resolve::{input, output, open_input, open_output, parse_url, InputOptions, StreamUrl};
|
||||
pub use stdio::StdioStream;
|
||||
|
||||
use crate::disc::DiscTitle;
|
||||
|
||||
+6
-5
@@ -148,7 +148,8 @@ impl Read for NetworkStream {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::disc::{
|
||||
AudioStream, Codec, ColorSpace, ContentFormat, HdrFormat, Stream, VideoStream,
|
||||
AudioChannels, AudioStream, Codec, ColorSpace, ContentFormat, FrameRate, HdrFormat,
|
||||
Resolution, SampleRate, Stream, VideoStream,
|
||||
};
|
||||
use std::io::{Read, Write};
|
||||
use std::net::TcpListener;
|
||||
@@ -165,8 +166,8 @@ mod tests {
|
||||
Stream::Video(VideoStream {
|
||||
pid: 0x1011,
|
||||
codec: Codec::Hevc,
|
||||
resolution: "2160p".into(),
|
||||
frame_rate: "23.976".into(),
|
||||
resolution: Resolution::R2160p,
|
||||
frame_rate: FrameRate::F23_976,
|
||||
hdr: HdrFormat::Hdr10,
|
||||
color_space: ColorSpace::Bt2020,
|
||||
secondary: false,
|
||||
@@ -175,9 +176,9 @@ mod tests {
|
||||
Stream::Audio(AudioStream {
|
||||
pid: 0x1100,
|
||||
codec: Codec::TrueHd,
|
||||
channels: "7.1".into(),
|
||||
channels: AudioChannels::Surround71,
|
||||
language: "eng".into(),
|
||||
sample_rate: "48kHz".into(),
|
||||
sample_rate: SampleRate::S48,
|
||||
secondary: false,
|
||||
label: "English".into(),
|
||||
}),
|
||||
|
||||
+59
-48
@@ -1,19 +1,15 @@
|
||||
//! PES output adapters — every output format muxes from PES frames.
|
||||
//!
|
||||
//! Each output knows its own format:
|
||||
//! - M2TS: PES → BD-TS packets → file (via TsMuxer)
|
||||
//! - Null: discard
|
||||
//! - Stdio: raw frame data to stdout
|
||||
//! - Network: PES → BD-TS → TCP (via TsMuxer)
|
||||
//! PES output streams — each writes its own format from PES frames.
|
||||
|
||||
use super::tsmux::TsMuxer;
|
||||
use crate::disc::DiscTitle;
|
||||
use crate::pes::{OutputStream, PesFrame};
|
||||
use crate::pes::PesFrame;
|
||||
use std::io::{self, Write};
|
||||
|
||||
/// M2TS output — PES frames → BD-TS packets → file.
|
||||
// ── M2TS ────────────────────────────────────────────────────────────────────
|
||||
|
||||
pub struct M2tsOutputStream {
|
||||
muxer: TsMuxer<io::BufWriter<std::fs::File>>,
|
||||
title: DiscTitle,
|
||||
}
|
||||
|
||||
impl M2tsOutputStream {
|
||||
@@ -21,79 +17,94 @@ impl M2tsOutputStream {
|
||||
let file = std::fs::File::create(path)
|
||||
.map_err(|e| io::Error::new(e.kind(), format!("m2ts://{}: {}", path, e)))?;
|
||||
let writer = io::BufWriter::with_capacity(4 * 1024 * 1024, file);
|
||||
let pids = Self::extract_pids(title);
|
||||
Ok(Self {
|
||||
muxer: TsMuxer::new(writer, &pids),
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_pids(title: &DiscTitle) -> Vec<u16> {
|
||||
title.streams.iter().map(|s| match s {
|
||||
crate::disc::Stream::Video(v) => v.pid,
|
||||
crate::disc::Stream::Audio(a) => a.pid,
|
||||
crate::disc::Stream::Subtitle(s) => s.pid,
|
||||
}).collect()
|
||||
let pids = extract_pids(title);
|
||||
Ok(Self { muxer: TsMuxer::new(writer, &pids), title: title.clone() })
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputStream for M2tsOutputStream {
|
||||
fn write_frame(&mut self, frame: &PesFrame) -> io::Result<()> {
|
||||
impl crate::pes::Stream for M2tsOutputStream {
|
||||
fn read(&mut self) -> io::Result<Option<PesFrame>> {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "M2TS output is write-only"))
|
||||
}
|
||||
fn write(&mut self, frame: &PesFrame) -> io::Result<()> {
|
||||
self.muxer.write_frame(frame.track, frame.pts, &frame.data)
|
||||
}
|
||||
fn finish(&mut self) -> io::Result<()> {
|
||||
self.muxer.finish_ref()
|
||||
}
|
||||
fn finish(&mut self) -> io::Result<()> { self.muxer.finish_ref() }
|
||||
fn info(&self) -> &DiscTitle { &self.title }
|
||||
}
|
||||
|
||||
/// Null output — discards all frames.
|
||||
pub struct NullOutputStream;
|
||||
// ── Null ────────────────────────────────────────────────────────────────────
|
||||
|
||||
impl OutputStream for NullOutputStream {
|
||||
fn write_frame(&mut self, _frame: &PesFrame) -> io::Result<()> { Ok(()) }
|
||||
pub struct NullOutputStream { title: DiscTitle }
|
||||
|
||||
impl NullOutputStream {
|
||||
pub fn new(title: &DiscTitle) -> Self { Self { title: title.clone() } }
|
||||
}
|
||||
|
||||
impl crate::pes::Stream for NullOutputStream {
|
||||
fn read(&mut self) -> io::Result<Option<PesFrame>> { Ok(None) }
|
||||
fn write(&mut self, _: &PesFrame) -> io::Result<()> { Ok(()) }
|
||||
fn finish(&mut self) -> io::Result<()> { Ok(()) }
|
||||
fn info(&self) -> &DiscTitle { &self.title }
|
||||
}
|
||||
|
||||
/// Stdio output — writes raw frame data to stdout.
|
||||
// ── Stdio ───────────────────────────────────────────────────────────────────
|
||||
|
||||
pub struct StdioOutputStream {
|
||||
writer: io::BufWriter<io::Stdout>,
|
||||
title: DiscTitle,
|
||||
}
|
||||
|
||||
impl StdioOutputStream {
|
||||
pub fn new() -> Self {
|
||||
Self { writer: io::BufWriter::new(io::stdout()) }
|
||||
pub fn new(title: &DiscTitle) -> Self {
|
||||
Self { writer: io::BufWriter::new(io::stdout()), title: title.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputStream for StdioOutputStream {
|
||||
fn write_frame(&mut self, frame: &PesFrame) -> io::Result<()> {
|
||||
impl crate::pes::Stream for StdioOutputStream {
|
||||
fn read(&mut self) -> io::Result<Option<PesFrame>> {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "stdio output is write-only"))
|
||||
}
|
||||
fn write(&mut self, frame: &PesFrame) -> io::Result<()> {
|
||||
self.writer.write_all(&frame.data)
|
||||
}
|
||||
fn finish(&mut self) -> io::Result<()> {
|
||||
self.writer.flush()
|
||||
}
|
||||
fn finish(&mut self) -> io::Result<()> { self.writer.flush() }
|
||||
fn info(&self) -> &DiscTitle { &self.title }
|
||||
}
|
||||
|
||||
/// Network output — PES frames → BD-TS → TCP.
|
||||
// ── Network ─────────────────────────────────────────────────────────────────
|
||||
|
||||
pub struct NetworkOutputStream {
|
||||
muxer: TsMuxer<io::BufWriter<std::net::TcpStream>>,
|
||||
title: DiscTitle,
|
||||
}
|
||||
|
||||
impl NetworkOutputStream {
|
||||
pub fn connect(addr: &str, title: &DiscTitle) -> io::Result<Self> {
|
||||
let stream = std::net::TcpStream::connect(addr)?;
|
||||
let writer = io::BufWriter::with_capacity(256 * 1024, stream);
|
||||
let pids = M2tsOutputStream::extract_pids(title);
|
||||
Ok(Self {
|
||||
muxer: TsMuxer::new(writer, &pids),
|
||||
})
|
||||
let pids = extract_pids(title);
|
||||
Ok(Self { muxer: TsMuxer::new(writer, &pids), title: title.clone() })
|
||||
}
|
||||
}
|
||||
|
||||
impl OutputStream for NetworkOutputStream {
|
||||
fn write_frame(&mut self, frame: &PesFrame) -> io::Result<()> {
|
||||
impl crate::pes::Stream for NetworkOutputStream {
|
||||
fn read(&mut self) -> io::Result<Option<PesFrame>> {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "network output is write-only"))
|
||||
}
|
||||
fn write(&mut self, frame: &PesFrame) -> io::Result<()> {
|
||||
self.muxer.write_frame(frame.track, frame.pts, &frame.data)
|
||||
}
|
||||
fn finish(&mut self) -> io::Result<()> {
|
||||
self.muxer.finish_ref()
|
||||
}
|
||||
fn finish(&mut self) -> io::Result<()> { self.muxer.finish_ref() }
|
||||
fn info(&self) -> &DiscTitle { &self.title }
|
||||
}
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
fn extract_pids(title: &DiscTitle) -> Vec<u16> {
|
||||
title.streams.iter().map(|s| match s {
|
||||
crate::disc::Stream::Video(v) => v.pid,
|
||||
crate::disc::Stream::Audio(a) => a.pid,
|
||||
crate::disc::Stream::Subtitle(s) => s.pid,
|
||||
}).collect()
|
||||
}
|
||||
|
||||
@@ -82,7 +82,6 @@ impl PsDemuxer {
|
||||
let mut pos = 0;
|
||||
|
||||
while let Some(sc) = find_start_code(&self.buffer, pos) {
|
||||
|
||||
if sc + 3 >= self.buffer.len() {
|
||||
// Not enough bytes to read the start code ID.
|
||||
break;
|
||||
|
||||
+5
-5
@@ -305,7 +305,7 @@ pub struct InputOptions {
|
||||
// ── PES-based open ──────────────────────────────────────────────────────────
|
||||
|
||||
/// Open a PES input stream (produces PES frames).
|
||||
pub fn open_pes_input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::InputStream>> {
|
||||
pub fn input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::Stream>> {
|
||||
let parsed = parse_url(url);
|
||||
match parsed {
|
||||
StreamUrl::Iso { ref path } => {
|
||||
@@ -369,11 +369,11 @@ pub fn open_pes_input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crat
|
||||
}
|
||||
|
||||
/// Open a PES output stream (consumes PES frames).
|
||||
pub fn open_pes_output(
|
||||
pub fn output(
|
||||
url: &str,
|
||||
title: &crate::disc::DiscTitle,
|
||||
codec_privates: &[Option<Vec<u8>>],
|
||||
) -> io::Result<Box<dyn crate::pes::OutputStream>> {
|
||||
) -> io::Result<Box<dyn crate::pes::Stream>> {
|
||||
let parsed = parse_url(url);
|
||||
match parsed {
|
||||
StreamUrl::Mkv { ref path } => {
|
||||
@@ -393,10 +393,10 @@ pub fn open_pes_output(
|
||||
Ok(Box::new(super::pesout::NetworkOutputStream::connect(addr, title)?))
|
||||
}
|
||||
StreamUrl::Stdio => {
|
||||
Ok(Box::new(super::pesout::StdioOutputStream::new()))
|
||||
Ok(Box::new(super::pesout::StdioOutputStream::new(title)))
|
||||
}
|
||||
StreamUrl::Null => {
|
||||
Ok(Box::new(super::pesout::NullOutputStream))
|
||||
Ok(Box::new(super::pesout::NullOutputStream::new(title)))
|
||||
}
|
||||
StreamUrl::Disc { .. } => {
|
||||
Err(io::Error::new(io::ErrorKind::Unsupported, "disc:// is read-only"))
|
||||
|
||||
Reference in New Issue
Block a user