DiscStream::new() replaces open_drive/open_iso/from_reader
A stream is a stream. DiscStream::new() takes reader + title + keys + batch + format — same pattern as every other stream constructor. Deleted: open_drive(), open_iso(), from_reader() — these were helper functions that chained multiple operations. Library provides primitives, callers decide the sequence. Removed disc:// case from input() — callers use Drive::open() + Disc::scan() + DiscStream::new() directly for disc sources.
This commit is contained in:
@@ -1,5 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.10.7 (2026-04-17)
|
||||||
|
|
||||||
|
### DiscStream::new()
|
||||||
|
- Replaced open_drive(), open_iso(), from_reader() with single new() constructor
|
||||||
|
- Stream accepts ContentFormat and sets up demuxer internally
|
||||||
|
- Removed disc:// case from input() — callers use primitives directly
|
||||||
|
|
||||||
## 0.10.6 (2026-04-16)
|
## 0.10.6 (2026-04-16)
|
||||||
|
|
||||||
### Docker compatibility
|
### Docker compatibility
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "libfreemkv"
|
name = "libfreemkv"
|
||||||
version = "0.10.6"
|
version = "0.10.7"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.86"
|
rust-version = "1.86"
|
||||||
license = "AGPL-3.0-only"
|
license = "AGPL-3.0-only"
|
||||||
|
|||||||
+23
-95
@@ -44,101 +44,17 @@ pub struct DiscStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DiscStream {
|
impl DiscStream {
|
||||||
/// Open from a physical drive. Caller must have already called
|
/// Create a disc stream from any sector reader.
|
||||||
/// drive.wait_ready(), drive.init(), drive.probe_disc().
|
///
|
||||||
/// Drive is moved into the stream — caller manages lock/unlock before/after.
|
/// Works with physical drives and ISO files — both implement SectorReader.
|
||||||
pub fn open_drive(
|
/// The caller opens the source, scans for titles/keys, and passes them in.
|
||||||
drive: crate::drive::Drive,
|
/// The stream handles demuxing, decryption, and codec parsing internally.
|
||||||
keydb_path: Option<&str>,
|
pub fn new(
|
||||||
title_index: usize,
|
|
||||||
) -> crate::error::Result<(Self, Disc)> {
|
|
||||||
let scan_opts = match keydb_path {
|
|
||||||
Some(kp) => ScanOptions::with_keydb(kp),
|
|
||||||
None => ScanOptions::default(),
|
|
||||||
};
|
|
||||||
let mut drive = drive;
|
|
||||||
let disc = Disc::scan(&mut drive, &scan_opts)?;
|
|
||||||
|
|
||||||
if title_index >= disc.titles.len() {
|
|
||||||
return Err(crate::error::Error::DiscTitleRange {
|
|
||||||
index: title_index,
|
|
||||||
count: disc.titles.len(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let title = disc.titles[title_index].clone();
|
|
||||||
let keys = disc.decrypt_keys();
|
|
||||||
let max_batch = detect_max_batch_sectors(drive.device_path());
|
|
||||||
let content_format = disc.content_format;
|
|
||||||
|
|
||||||
let mut stream = Self::from_reader(Box::new(drive), title, keys, max_batch);
|
|
||||||
|
|
||||||
// Set demuxer based on content format
|
|
||||||
match content_format {
|
|
||||||
crate::disc::ContentFormat::MpegPs => {
|
|
||||||
stream.ps_demuxer = Some(super::ps::PsDemuxer::new());
|
|
||||||
}
|
|
||||||
crate::disc::ContentFormat::BdTs => {
|
|
||||||
let pids: Vec<u16> = stream.pid_to_track.iter().map(|(pid, _)| *pid).collect();
|
|
||||||
if !pids.is_empty() {
|
|
||||||
stream.ts_demuxer = Some(super::ts::TsDemuxer::new(&pids));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok((stream, disc))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Open from an ISO file.
|
|
||||||
pub fn open_iso(
|
|
||||||
path: &str,
|
|
||||||
title_index: Option<usize>,
|
|
||||||
opts: &ScanOptions,
|
|
||||||
) -> io::Result<Self> {
|
|
||||||
let mut reader = super::iso::IsoSectorReader::open(path)?;
|
|
||||||
let capacity = reader.capacity();
|
|
||||||
|
|
||||||
let disc =
|
|
||||||
Disc::scan_image(&mut reader, capacity, opts).map_err(|e| -> io::Error { e.into() })?;
|
|
||||||
|
|
||||||
if disc.titles.is_empty() {
|
|
||||||
return Err(crate::error::Error::NoStreams.into());
|
|
||||||
}
|
|
||||||
let idx = title_index.unwrap_or(0);
|
|
||||||
if idx >= disc.titles.len() {
|
|
||||||
return Err(crate::error::Error::DiscTitleRange {
|
|
||||||
index: idx,
|
|
||||||
count: disc.titles.len(),
|
|
||||||
}
|
|
||||||
.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
let title = disc.titles[idx].clone();
|
|
||||||
let keys = disc.decrypt_keys();
|
|
||||||
let batch: u16 = 64;
|
|
||||||
|
|
||||||
let mut stream = Self::from_reader(Box::new(reader), title, keys, batch);
|
|
||||||
// Set demuxer based on content format
|
|
||||||
match disc.content_format {
|
|
||||||
crate::disc::ContentFormat::MpegPs => {
|
|
||||||
stream.ps_demuxer = Some(super::ps::PsDemuxer::new());
|
|
||||||
}
|
|
||||||
crate::disc::ContentFormat::BdTs => {
|
|
||||||
let pids: Vec<u16> = stream.pid_to_track.iter().map(|(pid, _)| *pid).collect();
|
|
||||||
if !pids.is_empty() {
|
|
||||||
stream.ts_demuxer = Some(super::ts::TsDemuxer::new(&pids));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stream.disc = Some(disc);
|
|
||||||
Ok(stream)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create from any SectorReader + title + keys.
|
|
||||||
pub fn from_reader(
|
|
||||||
reader: Box<dyn SectorReader>,
|
reader: Box<dyn SectorReader>,
|
||||||
title: DiscTitle,
|
title: DiscTitle,
|
||||||
decrypt_keys: crate::decrypt::DecryptKeys,
|
decrypt_keys: crate::decrypt::DecryptKeys,
|
||||||
batch_sectors: u16,
|
batch_sectors: u16,
|
||||||
|
content_format: crate::disc::ContentFormat,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let extents = title.extents.clone();
|
let extents = title.extents.clone();
|
||||||
|
|
||||||
@@ -156,6 +72,20 @@ impl DiscStream {
|
|||||||
parsers.push((pid, super::codec::parser_for_codec(codec)));
|
parsers.push((pid, super::codec::parser_for_codec(codec)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut ts_demuxer = None;
|
||||||
|
let mut ps_demuxer = None;
|
||||||
|
match content_format {
|
||||||
|
crate::disc::ContentFormat::MpegPs => {
|
||||||
|
ps_demuxer = Some(super::ps::PsDemuxer::new());
|
||||||
|
}
|
||||||
|
crate::disc::ContentFormat::BdTs => {
|
||||||
|
let ts_pids: Vec<u16> = pids.clone();
|
||||||
|
if !ts_pids.is_empty() {
|
||||||
|
ts_demuxer = Some(super::ts::TsDemuxer::new(&ts_pids));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
reader,
|
reader,
|
||||||
title,
|
title,
|
||||||
@@ -169,10 +99,8 @@ impl DiscStream {
|
|||||||
batch_sectors,
|
batch_sectors,
|
||||||
errors: 0,
|
errors: 0,
|
||||||
eof: false,
|
eof: false,
|
||||||
// Demuxer set by caller — open_drive() checks content_format,
|
ts_demuxer,
|
||||||
// open_iso() always uses TS (Blu-ray ISO).
|
ps_demuxer,
|
||||||
ts_demuxer: None,
|
|
||||||
ps_demuxer: None,
|
|
||||||
parsers,
|
parsers,
|
||||||
pending_frames: std::collections::VecDeque::new(),
|
pending_frames: std::collections::VecDeque::new(),
|
||||||
pid_to_track,
|
pid_to_track,
|
||||||
|
|||||||
+26
-28
@@ -170,32 +170,13 @@ pub struct InputOptions {
|
|||||||
pub fn input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::Stream>> {
|
pub fn input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::Stream>> {
|
||||||
let parsed = parse_url(url);
|
let parsed = parse_url(url);
|
||||||
match parsed {
|
match parsed {
|
||||||
StreamUrl::Disc { device } => {
|
StreamUrl::Disc { .. } => {
|
||||||
// Open drive, init, scan — caller manages the drive
|
// Disc sources should use DiscStream::new() directly.
|
||||||
let mut drive = match device {
|
// The caller opens the drive, inits, scans, then creates the stream.
|
||||||
Some(ref d) => {
|
Err(io::Error::new(
|
||||||
crate::drive::Drive::open(d).map_err(|e| -> io::Error { e.into() })?
|
io::ErrorKind::Unsupported,
|
||||||
}
|
"Use Drive::open() + Disc::scan() + DiscStream::new() for disc sources",
|
||||||
None => crate::drive::find_drive().ok_or_else(|| -> io::Error {
|
))
|
||||||
crate::error::Error::DeviceNotFound {
|
|
||||||
path: String::new(),
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
})?,
|
|
||||||
};
|
|
||||||
let _ = drive.wait_ready();
|
|
||||||
let _ = drive.init();
|
|
||||||
let _ = drive.probe_disc();
|
|
||||||
let (mut stream, _disc) = DiscStream::open_drive(
|
|
||||||
drive,
|
|
||||||
opts.keydb_path.as_deref(),
|
|
||||||
opts.title_index.unwrap_or(0),
|
|
||||||
)
|
|
||||||
.map_err(|e| -> io::Error { e.into() })?;
|
|
||||||
if opts.raw {
|
|
||||||
stream.set_raw();
|
|
||||||
}
|
|
||||||
Ok(Box::new(stream))
|
|
||||||
}
|
}
|
||||||
StreamUrl::Iso { ref path } => {
|
StreamUrl::Iso { ref path } => {
|
||||||
validate_file_path(path, "iso")?;
|
validate_file_path(path, "iso")?;
|
||||||
@@ -203,8 +184,25 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::S
|
|||||||
Some(p) => crate::disc::ScanOptions::with_keydb(p),
|
Some(p) => crate::disc::ScanOptions::with_keydb(p),
|
||||||
None => crate::disc::ScanOptions::default(),
|
None => crate::disc::ScanOptions::default(),
|
||||||
};
|
};
|
||||||
let mut stream =
|
let mut reader = super::iso::IsoSectorReader::open(&path.to_string_lossy())?;
|
||||||
DiscStream::open_iso(&path.to_string_lossy(), opts.title_index, &scan_opts)?;
|
let capacity = reader.capacity();
|
||||||
|
let disc = crate::disc::Disc::scan_image(&mut reader, capacity, &scan_opts)
|
||||||
|
.map_err(|e| -> io::Error { e.into() })?;
|
||||||
|
if disc.titles.is_empty() {
|
||||||
|
return Err(crate::error::Error::NoStreams.into());
|
||||||
|
}
|
||||||
|
let idx = opts.title_index.unwrap_or(0);
|
||||||
|
if idx >= disc.titles.len() {
|
||||||
|
return Err(crate::error::Error::DiscTitleRange {
|
||||||
|
index: idx,
|
||||||
|
count: disc.titles.len(),
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
let title = disc.titles[idx].clone();
|
||||||
|
let keys = disc.decrypt_keys();
|
||||||
|
let format = disc.content_format;
|
||||||
|
let mut stream = DiscStream::new(Box::new(reader), title, keys, 64, format);
|
||||||
if opts.raw {
|
if opts.raw {
|
||||||
stream.set_raw();
|
stream.set_raw();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user