Fix cargo fmt formatting

This commit is contained in:
MattJackson
2026-04-15 22:32:53 +00:00
parent d06a37d3dc
commit e0f40583c4
29 changed files with 586 additions and 240 deletions
+15 -4
View File
@@ -34,8 +34,10 @@ fn main() {
let file = std::fs::File::create("/dev/null").unwrap(); let file = std::fs::File::create("/dev/null").unwrap();
let mut writer = std::io::BufWriter::with_capacity(4 * 1024 * 1024, file); let mut writer = std::io::BufWriter::with_capacity(4 * 1024 * 1024, file);
eprintln!("Reading 1000 batches ({:.1} MB) with write + progress...", eprintln!(
1000.0 * batch as f64 * 2048.0 / 1_048_576.0); "Reading 1000 batches ({:.1} MB) with write + progress...",
1000.0 * batch as f64 * 2048.0 / 1_048_576.0
);
let start = Instant::now(); let start = Instant::now();
let mut ok = 0u32; let mut ok = 0u32;
@@ -51,7 +53,9 @@ fn main() {
} }
Err(e) => { Err(e) => {
fail += 1; fail += 1;
if fail <= 5 { eprintln!(" FAIL LBA {}: {}", lba, e); } if fail <= 5 {
eprintln!(" FAIL LBA {}: {}", lba, e);
}
buf.fill(0); buf.fill(0);
writer.write_all(&buf).unwrap(); writer.write_all(&buf).unwrap();
} }
@@ -67,5 +71,12 @@ fn main() {
let elapsed = start.elapsed().as_secs_f64(); let elapsed = start.elapsed().as_secs_f64();
let mb = ok as f64 * batch as f64 * 2048.0 / 1_048_576.0; let mb = ok as f64 * batch as f64 * 2048.0 / 1_048_576.0;
eprintln!("\n{} ok, {} fail, {:.1} MB in {:.1}s = {:.1} MB/s", ok, fail, mb, elapsed, mb / elapsed); eprintln!(
"\n{} ok, {} fail, {:.1} MB in {:.1}s = {:.1} MB/s",
ok,
fail,
mb,
elapsed,
mb / elapsed
);
} }
+16 -3
View File
@@ -50,7 +50,9 @@ fn main() {
eprintln!("\n SLOW READ: LBA {} took {}ms (ok={})", lba, read_ms, ok); eprintln!("\n SLOW READ: LBA {} took {}ms (ok={})", lba, read_ms, ok);
} }
if !ok { buf[..n].fill(0); } if !ok {
buf[..n].fill(0);
}
w.write_all(&buf[..n]).unwrap(); w.write_all(&buf[..n]).unwrap();
lba += count as u32; lba += count as u32;
bytes += n as u64; bytes += n as u64;
@@ -60,11 +62,22 @@ fn main() {
let speed = delta as f64 / last.elapsed().as_secs_f64() / 1_048_576.0; let speed = delta as f64 / last.elapsed().as_secs_f64() / 1_048_576.0;
let avg = bytes as f64 / start.elapsed().as_secs_f64() / 1_048_576.0; let avg = bytes as f64 / start.elapsed().as_secs_f64() / 1_048_576.0;
let pct = bytes as f64 / (cap as f64 * 2048.0) * 100.0; let pct = bytes as f64 / (cap as f64 * 2048.0) * 100.0;
eprint!("\r {:.1}% LBA {} | {:.0} MB/s (avg {:.0}) | {:.1} GB ", pct, lba, speed, avg, bytes as f64 / 1e9); eprint!(
"\r {:.1}% LBA {} | {:.0} MB/s (avg {:.0}) | {:.1} GB ",
pct,
lba,
speed,
avg,
bytes as f64 / 1e9
);
last_bytes = bytes; last_bytes = bytes;
last = Instant::now(); last = Instant::now();
} }
} }
w.flush().unwrap(); w.flush().unwrap();
eprintln!("\nDone: {:.1} GB in {:.0}s", bytes as f64 / 1e9, start.elapsed().as_secs_f64()); eprintln!(
"\nDone: {:.1} GB in {:.0}s",
bytes as f64 / 1e9,
start.elapsed().as_secs_f64()
);
} }
+1 -1
View File
@@ -111,7 +111,7 @@ impl Disc {
chapters: Vec::new(), chapters: Vec::new(),
extents, extents,
content_format: ContentFormat::MpegPs, content_format: ContentFormat::MpegPs,
codec_privates: Vec::new(), codec_privates: Vec::new(),
}); });
} }
} }
+8 -6
View File
@@ -1119,7 +1119,11 @@ impl Disc {
use std::io::{Seek, SeekFrom, Write}; use std::io::{Seek, SeekFrom, Write};
let total_bytes = self.capacity_sectors as u64 * 2048; let total_bytes = self.capacity_sectors as u64 * 2048;
let keys = if decrypt { self.decrypt_keys() } else { crate::decrypt::DecryptKeys::None }; let keys = if decrypt {
self.decrypt_keys()
} else {
crate::decrypt::DecryptKeys::None
};
// Resume: check existing file // Resume: check existing file
let (start_lba, file) = if resume { let (start_lba, file) = if resume {
@@ -1138,14 +1142,13 @@ impl Disc {
(safe_sectors, f) (safe_sectors, f)
} }
_ => { _ => {
let f = std::fs::File::create(path) let f =
.map_err(|e| Error::IoError { source: e })?; std::fs::File::create(path).map_err(|e| Error::IoError { source: e })?;
(0u32, f) (0u32, f)
} }
} }
} else { } else {
let f = std::fs::File::create(path) let f = std::fs::File::create(path).map_err(|e| Error::IoError { source: e })?;
.map_err(|e| Error::IoError { source: e })?;
(0u32, f) (0u32, f)
}; };
@@ -1160,7 +1163,6 @@ impl Disc {
let count = remaining.min(batch as u32) as u16; let count = remaining.min(batch as u32) as u16;
let bytes = count as usize * 2048; let bytes = count as usize * 2048;
reader reader
.read_sectors(lba, count, &mut buf[..bytes]) .read_sectors(lba, count, &mut buf[..bytes])
.map_err(|e| Error::IoError { .map_err(|e| Error::IoError {
+69 -9
View File
@@ -150,7 +150,18 @@ impl Drive {
/// Uses GET EVENT STATUS NOTIFICATION which works regardless of firmware state. /// Uses GET EVENT STATUS NOTIFICATION which works regardless of firmware state.
pub fn drive_status(&mut self) -> DriveStatus { pub fn drive_status(&mut self) -> DriveStatus {
// GET EVENT STATUS NOTIFICATION: polled, media event class (0x10) // GET EVENT STATUS NOTIFICATION: polled, media event class (0x10)
let cdb = [SCSI_GET_EVENT_STATUS, 0x01, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x08, 0x00]; let cdb = [
SCSI_GET_EVENT_STATUS,
0x01,
0x00,
0x00,
0x10,
0x00,
0x00,
0x00,
0x08,
0x00,
];
let mut buf = [0u8; 8]; let mut buf = [0u8; 8];
match self.scsi.as_mut().execute( match self.scsi.as_mut().execute(
&cdb, &cdb,
@@ -343,7 +354,18 @@ impl Drive {
/// Read REPORT KEY RPC state (region playback control). /// Read REPORT KEY RPC state (region playback control).
pub fn report_key_rpc_state(&mut self) -> Option<Vec<u8>> { pub fn report_key_rpc_state(&mut self) -> Option<Vec<u8>> {
let cdb = [ let cdb = [
SCSI_REPORT_KEY, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x08, 0x00, SCSI_REPORT_KEY,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x00,
0x08,
0x08,
0x00,
]; ];
let mut buf = vec![0u8; 8]; let mut buf = vec![0u8; 8];
let r = self let r = self
@@ -365,7 +387,18 @@ impl Drive {
/// Read MODE SENSE page data. /// Read MODE SENSE page data.
pub fn mode_sense_page(&mut self, page: u8) -> Option<Vec<u8>> { pub fn mode_sense_page(&mut self, page: u8) -> Option<Vec<u8>> {
let cdb = [SCSI_MODE_SENSE, 0x00, page, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x00]; let cdb = [
SCSI_MODE_SENSE,
0x00,
page,
0x00,
0x00,
0x00,
0x00,
0x00,
0xFC,
0x00,
];
let mut buf = vec![0u8; 252]; let mut buf = vec![0u8; 252];
let r = self let r = self
.scsi .scsi
@@ -421,7 +454,11 @@ impl Drive {
/// Returns Err only after all attempts exhausted — user should clean /// Returns Err only after all attempts exhausted — user should clean
/// the disc and resume. /// the disc and resume.
pub fn read(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> { pub fn read(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize> {
let timeout_ms = if self.recovery_bytes_remaining > 0 { 30_000 } else { 10_000 }; let timeout_ms = if self.recovery_bytes_remaining > 0 {
30_000
} else {
10_000
};
let cdb = [ let cdb = [
crate::scsi::SCSI_READ_10, crate::scsi::SCSI_READ_10,
0x00, 0x00,
@@ -437,7 +474,10 @@ impl Drive {
// Normal read // Normal read
if let Ok(result) = self.scsi.as_mut().execute( if let Ok(result) = self.scsi.as_mut().execute(
&cdb, crate::scsi::DataDirection::FromDevice, buf, timeout_ms, &cdb,
crate::scsi::DataDirection::FromDevice,
buf,
timeout_ms,
) { ) {
if self.recovery_bytes_remaining > 0 { if self.recovery_bytes_remaining > 0 {
let bytes_read = count as u64 * 2048; let bytes_read = count as u64 * 2048;
@@ -457,7 +497,10 @@ impl Drive {
std::thread::sleep(std::time::Duration::from_secs(30)); std::thread::sleep(std::time::Duration::from_secs(30));
if let Ok(result) = self.scsi.as_mut().execute( if let Ok(result) = self.scsi.as_mut().execute(
&cdb, crate::scsi::DataDirection::FromDevice, buf, 30_000, &cdb,
crate::scsi::DataDirection::FromDevice,
buf,
30_000,
) { ) {
self.recovery_bytes_remaining = RECOVERY_WINDOW; self.recovery_bytes_remaining = RECOVERY_WINDOW;
return Ok(result.bytes_transferred); return Ok(result.bytes_transferred);
@@ -479,7 +522,10 @@ impl Drive {
std::thread::sleep(std::time::Duration::from_secs(30)); std::thread::sleep(std::time::Duration::from_secs(30));
if let Ok(result) = self.scsi.as_mut().execute( if let Ok(result) = self.scsi.as_mut().execute(
&cdb, crate::scsi::DataDirection::FromDevice, buf, 30_000, &cdb,
crate::scsi::DataDirection::FromDevice,
buf,
30_000,
) { ) {
self.recovery_bytes_remaining = RECOVERY_WINDOW; self.recovery_bytes_remaining = RECOVERY_WINDOW;
return Ok(result.bytes_transferred); return Ok(result.bytes_transferred);
@@ -524,7 +570,14 @@ impl Drive {
/// Lock the tray so the disc cannot be ejected during a rip. /// Lock the tray so the disc cannot be ejected during a rip.
pub fn lock_tray(&mut self) { pub fn lock_tray(&mut self) {
let prevent = [SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0x00, 0x00, 0x00, 0x01, 0x00]; let prevent = [
SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL,
0x00,
0x00,
0x00,
0x01,
0x00,
];
let mut buf = [0u8; 0]; let mut buf = [0u8; 0];
let _ = let _ =
self.scsi self.scsi
@@ -534,7 +587,14 @@ impl Drive {
/// Unlock the tray so the user can manually eject the disc. /// Unlock the tray so the user can manually eject the disc.
pub fn unlock_tray(&mut self) { pub fn unlock_tray(&mut self) {
let allow = [SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0x00, 0x00, 0x00, 0x00, 0x00]; let allow = [
SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL,
0x00,
0x00,
0x00,
0x00,
0x00,
];
let mut buf = [0u8; 0]; let mut buf = [0u8; 0];
let _ = let _ =
self.scsi self.scsi
+88 -26
View File
@@ -89,10 +89,18 @@ pub const E_NO_METADATA: u16 = 9008;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
// Device (1xxx) // Device (1xxx)
DeviceNotFound { path: String }, DeviceNotFound {
DevicePermission { path: String }, path: String,
DeviceNotReady { path: String }, },
DeviceResetFailed { path: String }, DevicePermission {
path: String,
},
DeviceNotReady {
path: String,
},
DeviceResetFailed {
path: String,
},
// Profile (2xxx) // Profile (2xxx)
UnsupportedDrive { UnsupportedDrive {
@@ -104,7 +112,10 @@ pub enum Error {
// Unlock (3xxx) // Unlock (3xxx)
UnlockFailed, UnlockFailed,
SignatureMismatch { expected: [u8; 4], got: [u8; 4] }, SignatureMismatch {
expected: [u8; 4],
got: [u8; 4],
},
// SCSI (4xxx) // SCSI (4xxx)
ScsiError { ScsiError {
@@ -114,14 +125,23 @@ pub enum Error {
}, },
// I/O (5xxx) // I/O (5xxx)
IoError { source: std::io::Error }, IoError {
source: std::io::Error,
},
// Disc format (6xxx) // Disc format (6xxx)
DiscRead { sector: u64 }, DiscRead {
sector: u64,
},
MplsParse, MplsParse,
ClpiParse, ClpiParse,
UdfNotFound { path: String }, UdfNotFound {
DiscTitleRange { index: usize, count: usize }, path: String,
},
DiscTitleRange {
index: usize,
count: usize,
},
IfoParse, IfoParse,
MkvInvalid, MkvInvalid,
NoStreams, NoStreams,
@@ -142,22 +162,40 @@ pub enum Error {
DecryptFailed, DecryptFailed,
// Keydb (8xxx) // Keydb (8xxx)
KeydbConnect { host: String }, KeydbConnect {
KeydbHttp { status: u16 }, host: String,
},
KeydbHttp {
status: u16,
},
KeydbInvalid, KeydbInvalid,
KeydbWrite { path: String }, KeydbWrite {
path: String,
},
KeydbParse, KeydbParse,
KeydbLoad { path: String }, KeydbLoad {
path: String,
},
// Stream/mux (9xxx) // Stream/mux (9xxx)
StreamReadOnly, StreamReadOnly,
StreamWriteOnly, StreamWriteOnly,
StreamUrlInvalid { url: String }, StreamUrlInvalid {
StreamUrlMissingPath { scheme: String }, url: String,
StreamUrlMissingPort { addr: String }, },
PesFrameTooLarge { size: usize }, StreamUrlMissingPath {
scheme: String,
},
StreamUrlMissingPort {
addr: String,
},
PesFrameTooLarge {
size: usize,
},
PesInvalidMagic, PesInvalidMagic,
IsoTooLarge { path: String }, IsoTooLarge {
path: String,
},
NoMetadata, NoMetadata,
} }
@@ -227,22 +265,46 @@ impl std::fmt::Display for Error {
product_id, product_id,
product_revision, product_revision,
} => write!( } => write!(
f, "E{}: {} {} {}", f,
self.code(), vendor_id.trim(), product_id.trim(), product_revision.trim() "E{}: {} {} {}",
self.code(),
vendor_id.trim(),
product_id.trim(),
product_revision.trim()
), ),
Error::SignatureMismatch { expected, got } => write!( Error::SignatureMismatch { expected, got } => write!(
f, "E{}: {:02x}{:02x}{:02x}{:02x}!={:02x}{:02x}{:02x}{:02x}", f,
"E{}: {:02x}{:02x}{:02x}{:02x}!={:02x}{:02x}{:02x}{:02x}",
self.code(), self.code(),
expected[0], expected[1], expected[2], expected[3], expected[0],
got[0], got[1], got[2], got[3] expected[1],
expected[2],
expected[3],
got[0],
got[1],
got[2],
got[3]
), ),
Error::ScsiError { opcode, status, sense_key } => { Error::ScsiError {
write!(f, "E{}: 0x{:02x}/0x{:02x}/0x{:02x}", self.code(), opcode, status, sense_key) opcode,
status,
sense_key,
} => {
write!(
f,
"E{}: 0x{:02x}/0x{:02x}/0x{:02x}",
self.code(),
opcode,
status,
sense_key
)
} }
Error::IoError { source } => write!(f, "E{}: {}", self.code(), source), Error::IoError { source } => write!(f, "E{}: {}", self.code(), source),
Error::DiscRead { sector } => write!(f, "E{}: {}", self.code(), sector), Error::DiscRead { sector } => write!(f, "E{}: {}", self.code(), sector),
Error::UdfNotFound { path } => write!(f, "E{}: {}", self.code(), path), Error::UdfNotFound { path } => write!(f, "E{}: {}", self.code(), path),
Error::DiscTitleRange { index, count } => write!(f, "E{}: {}/{}", self.code(), index, count), Error::DiscTitleRange { index, count } => {
write!(f, "E{}: {}/{}", self.code(), index, count)
}
Error::KeydbConnect { host } => write!(f, "E{}: {}", self.code(), host), Error::KeydbConnect { host } => write!(f, "E{}: {}", self.code(), host),
Error::KeydbHttp { status } => write!(f, "E{}: {}", self.code(), status), Error::KeydbHttp { status } => write!(f, "E{}: {}", self.code(), status),
Error::KeydbWrite { path } => write!(f, "E{}: {}", self.code(), path), Error::KeydbWrite { path } => write!(f, "E{}: {}", self.code(), path),
-2
View File
@@ -26,7 +26,6 @@ pub struct Event {
#[derive(Debug)] #[derive(Debug)]
pub enum EventKind { pub enum EventKind {
// ── Init sequence events ──────────────────────────────────────── // ── Init sequence events ────────────────────────────────────────
/// Drive opened successfully. /// Drive opened successfully.
DriveOpened { device: String }, DriveOpened { device: String },
@@ -43,7 +42,6 @@ pub enum EventKind {
ScanComplete { titles: usize }, ScanComplete { titles: usize },
// ── Read events ───────────────────────────────────────────────── // ── Read events ─────────────────────────────────────────────────
/// Bytes successfully read and written to output. /// Bytes successfully read and written to output.
BytesRead { BytesRead {
/// Bytes written so far. /// Bytes written so far.
+24 -9
View File
@@ -697,12 +697,12 @@ mod tests {
let mut pgc = vec![0u8; 0xEA]; let mut pgc = vec![0u8; 0xEA];
pgc[0x02] = 1; // 1 program pgc[0x02] = 1; // 1 program
pgc[0x03] = 2; // 2 cells pgc[0x03] = 2; // 2 cells
// 1h 59m 30s at 29.97fps, 0 frames // 1h 59m 30s at 29.97fps, 0 frames
pgc[0x04] = 0x01; // hours BCD pgc[0x04] = 0x01; // hours BCD
pgc[0x05] = 0x59; // minutes BCD pgc[0x05] = 0x59; // minutes BCD
pgc[0x06] = 0x30; // seconds BCD pgc[0x06] = 0x30; // seconds BCD
pgc[0x07] = 0b11_000000; // 29.97fps, 0 frames pgc[0x07] = 0b11_000000; // 29.97fps, 0 frames
// Cell playback info offset at PGC+0xE8 // Cell playback info offset at PGC+0xE8
let cell_offset: u16 = 0xEA; // right after minimum header let cell_offset: u16 = 0xEA; // right after minimum header
pgc[0xE8] = (cell_offset >> 8) as u8; pgc[0xE8] = (cell_offset >> 8) as u8;
pgc[0xE9] = cell_offset as u8; pgc[0xE9] = cell_offset as u8;
@@ -710,17 +710,32 @@ mod tests {
pgc.resize(pgc.len() + 48, 0); pgc.resize(pgc.len() + 48, 0);
// Cell 0: sectors 100-200 // Cell 0: sectors 100-200
let co = 0xEA; let co = 0xEA;
pgc[co + 8] = 0; pgc[co + 9] = 0; pgc[co + 10] = 0; pgc[co + 11] = 100; // first sector pgc[co + 8] = 0;
pgc[co + 20] = 0; pgc[co + 21] = 0; pgc[co + 22] = 0; pgc[co + 23] = 200; // last sector pgc[co + 9] = 0;
// Cell 1: sectors 300-400 pgc[co + 10] = 0;
pgc[co + 11] = 100; // first sector
pgc[co + 20] = 0;
pgc[co + 21] = 0;
pgc[co + 22] = 0;
pgc[co + 23] = 200; // last sector
// Cell 1: sectors 300-400
let co = 0xEA + 24; let co = 0xEA + 24;
pgc[co + 8] = 0; pgc[co + 9] = 0; pgc[co + 10] = 1; pgc[co + 11] = 44; // first sector = 300 pgc[co + 8] = 0;
pgc[co + 20] = 0; pgc[co + 21] = 0; pgc[co + 22] = 1; pgc[co + 23] = 144; // last sector = 400 pgc[co + 9] = 0;
pgc[co + 10] = 1;
pgc[co + 11] = 44; // first sector = 300
pgc[co + 20] = 0;
pgc[co + 21] = 0;
pgc[co + 22] = 1;
pgc[co + 23] = 144; // last sector = 400
let title = parse_pgc(&pgc, 0, 5).unwrap(); let title = parse_pgc(&pgc, 0, 5).unwrap();
let expected = 1.0 * 3600.0 + 59.0 * 60.0 + 30.0; let expected = 1.0 * 3600.0 + 59.0 * 60.0 + 30.0;
assert!((title.duration_secs - expected).abs() < 0.1, assert!(
"expected ~{expected}s, got {}s", title.duration_secs); (title.duration_secs - expected).abs() < 0.1,
"expected ~{expected}s, got {}s",
title.duration_secs
);
assert_eq!(title.chapters, 5); assert_eq!(title.chapters, 5);
assert_eq!(title.cells.len(), 2); assert_eq!(title.cells.len(), 2);
assert_eq!(title.cells[0].first_sector, 100); assert_eq!(title.cells[0].first_sector, 100);
+2 -2
View File
@@ -104,8 +104,8 @@ pub use profile::DriveProfile;
// Platform trait is pub(crate) -- callers use Drive, not Platform directly // Platform trait is pub(crate) -- callers use Drive, not Platform directly
pub use decrypt::{decrypt_sectors, DecryptKeys}; pub use decrypt::{decrypt_sectors, DecryptKeys};
pub use disc::{ pub use disc::{
AacsState, AudioChannels, AudioStream, Clip, Codec, ColorSpace, ContentFormat, AacsState, AudioChannels, AudioStream, Clip, Codec, ColorSpace, ContentFormat, Disc,
Disc, DiscFormat, DiscTitle, Extent, FrameRate, HdrFormat, KeySource, Resolution, SampleRate, DiscFormat, DiscTitle, Extent, FrameRate, HdrFormat, KeySource, Resolution, SampleRate,
ScanOptions, Stream, SubtitleStream, VideoStream, ScanOptions, Stream, SubtitleStream, VideoStream,
}; };
pub use mux::DiscStream; pub use mux::DiscStream;
+9 -3
View File
@@ -69,9 +69,15 @@ impl CodecParser for HevcParser {
let nal_type = (data[nal_start] >> 1) & 0x3F; let nal_type = (data[nal_start] >> 1) & 0x3F;
match nal_type { match nal_type {
NAL_VPS => { self.vps = Some(data[nal_start..end].to_vec()); } NAL_VPS => {
NAL_SPS => { self.sps = Some(data[nal_start..end].to_vec()); } self.vps = Some(data[nal_start..end].to_vec());
NAL_PPS => { self.pps = Some(data[nal_start..end].to_vec()); } }
NAL_SPS => {
self.sps = Some(data[nal_start..end].to_vec());
}
NAL_PPS => {
self.pps = Some(data[nal_start..end].to_vec());
}
NAL_AUD => {} // Skip access unit delimiters NAL_AUD => {} // Skip access unit delimiters
t if (NAL_BLA_W_LP..=NAL_RSV_IRAP_VCL23).contains(&t) => { t if (NAL_BLA_W_LP..=NAL_RSV_IRAP_VCL23).contains(&t) => {
keyframe = true; keyframe = true;
+53 -26
View File
@@ -5,9 +5,7 @@
//! //!
//! Read-only. For disc→ISO (raw sector copy), use `Disc::copy()`. //! Read-only. For disc→ISO (raw sector copy), use `Disc::copy()`.
use crate::disc::{ use crate::disc::{detect_max_batch_sectors, Disc, DiscTitle, Extent, ScanOptions};
detect_max_batch_sectors, Disc, DiscTitle, Extent, ScanOptions,
};
use crate::sector::SectorReader; use crate::sector::SectorReader;
use std::io; use std::io;
@@ -99,8 +97,8 @@ impl DiscStream {
let mut reader = super::iso::IsoSectorReader::open(path)?; let mut reader = super::iso::IsoSectorReader::open(path)?;
let capacity = reader.capacity(); let capacity = reader.capacity();
let disc = Disc::scan_image(&mut reader, capacity, opts) let disc =
.map_err(|e| -> io::Error { e.into() })?; Disc::scan_image(&mut reader, capacity, opts).map_err(|e| -> io::Error { e.into() })?;
if disc.titles.is_empty() { if disc.titles.is_empty() {
return Err(crate::error::Error::NoStreams.into()); return Err(crate::error::Error::NoStreams.into());
@@ -110,7 +108,8 @@ impl DiscStream {
return Err(crate::error::Error::DiscTitleRange { return Err(crate::error::Error::DiscTitleRange {
index: idx, index: idx,
count: disc.titles.len(), count: disc.titles.len(),
}.into()); }
.into());
} }
let title = disc.titles[idx].clone(); let title = disc.titles[idx].clone();
@@ -210,7 +209,10 @@ impl DiscStream {
let bytes = sectors as usize * 2048; let bytes = sectors as usize * 2048;
self.read_buf.resize(bytes, 0); self.read_buf.resize(bytes, 0);
match self.reader.read_sectors(lba, sectors, &mut self.read_buf[..bytes]) { match self
.reader
.read_sectors(lba, sectors, &mut self.read_buf[..bytes])
{
Ok(_) => { Ok(_) => {
self.buf_valid = bytes; self.buf_valid = bytes;
self.current_offset += sectors as u32; self.current_offset += sectors as u32;
@@ -241,11 +243,15 @@ impl crate::pes::Stream for DiscStream {
// Flush demuxer — last PES packet may still be in the assembler // Flush demuxer — last PES packet may still be in the assembler
if let Some(ref mut demuxer) = self.ts_demuxer { if let Some(ref mut demuxer) = self.ts_demuxer {
for pes in &demuxer.flush() { for pes in &demuxer.flush() {
if let Some((_, track)) = self.pid_to_track.iter().find(|(pid, _)| *pid == pes.pid) { if let Some((_, track)) =
if let Some((_, parser)) = self.parsers.iter_mut().find(|(pid, _)| *pid == pes.pid) { self.pid_to_track.iter().find(|(pid, _)| *pid == pes.pid)
{
if let Some((_, parser)) =
self.parsers.iter_mut().find(|(pid, _)| *pid == pes.pid)
{
for frame in parser.parse(pes) { for frame in parser.parse(pes) {
self.pending_frames.push_back( self.pending_frames.push_back(
crate::pes::PesFrame::from_codec_frame(*track, frame) crate::pes::PesFrame::from_codec_frame(*track, frame),
); );
} }
} }
@@ -258,11 +264,17 @@ impl crate::pes::Stream for DiscStream {
let track = match ps.stream_id { let track = match ps.stream_id {
0xE0..=0xEF => 0, 0xE0..=0xEF => 0,
0xC0..=0xDF => 1, 0xC0..=0xDF => 1,
0xBD => ps.sub_stream_id.map(|s| (s & 0x1F) as usize + 1).unwrap_or(1), 0xBD => ps
.sub_stream_id
.map(|s| (s & 0x1F) as usize + 1)
.unwrap_or(1),
_ => continue, _ => continue,
}; };
if track < self.title.streams.len() { if track < self.title.streams.len() {
let pts_ns = ps.pts.map(|p| (p as i64) * 1_000_000_000 / 90_000).unwrap_or(0); let pts_ns = ps
.pts
.map(|p| (p as i64) * 1_000_000_000 / 90_000)
.unwrap_or(0);
self.pending_frames.push_back(crate::pes::PesFrame { self.pending_frames.push_back(crate::pes::PesFrame {
track, track,
pts: pts_ns, pts: pts_ns,
@@ -276,22 +288,24 @@ impl crate::pes::Stream for DiscStream {
} }
let bytes = self.buf_valid; let bytes = self.buf_valid;
if let Err(e) = crate::decrypt::decrypt_sectors( if let Err(e) =
&mut self.read_buf[..bytes], crate::decrypt::decrypt_sectors(&mut self.read_buf[..bytes], &self.decrypt_keys, 0)
&self.decrypt_keys, {
0,
) {
return Err(e.into()); return Err(e.into());
} }
if let Some(ref mut demuxer) = self.ts_demuxer { if let Some(ref mut demuxer) = self.ts_demuxer {
let packets = demuxer.feed(&self.read_buf[..bytes]); let packets = demuxer.feed(&self.read_buf[..bytes]);
for pes in &packets { for pes in &packets {
if let Some((_, track)) = self.pid_to_track.iter().find(|(pid, _)| *pid == pes.pid) { if let Some((_, track)) =
if let Some((_, parser)) = self.parsers.iter_mut().find(|(pid, _)| *pid == pes.pid) { self.pid_to_track.iter().find(|(pid, _)| *pid == pes.pid)
{
if let Some((_, parser)) =
self.parsers.iter_mut().find(|(pid, _)| *pid == pes.pid)
{
for frame in parser.parse(pes) { for frame in parser.parse(pes) {
self.pending_frames.push_back( self.pending_frames.push_back(
crate::pes::PesFrame::from_codec_frame(*track, frame) crate::pes::PesFrame::from_codec_frame(*track, frame),
); );
} }
} }
@@ -303,11 +317,17 @@ impl crate::pes::Stream for DiscStream {
let track = match ps.stream_id { let track = match ps.stream_id {
0xE0..=0xEF => 0, 0xE0..=0xEF => 0,
0xC0..=0xDF => 1, 0xC0..=0xDF => 1,
0xBD => ps.sub_stream_id.map(|s| (s & 0x1F) as usize + 1).unwrap_or(1), 0xBD => ps
.sub_stream_id
.map(|s| (s & 0x1F) as usize + 1)
.unwrap_or(1),
_ => continue, _ => continue,
}; };
if track < self.title.streams.len() { if track < self.title.streams.len() {
let pts_ns = ps.pts.map(|p| (p as i64) * 1_000_000_000 / 90_000).unwrap_or(0); let pts_ns = ps
.pts
.map(|p| (p as i64) * 1_000_000_000 / 90_000)
.unwrap_or(0);
self.pending_frames.push_back(crate::pes::PesFrame { self.pending_frames.push_back(crate::pes::PesFrame {
track, track,
pts: pts_ns, pts: pts_ns,
@@ -330,15 +350,22 @@ impl crate::pes::Stream for DiscStream {
Err(crate::error::Error::StreamReadOnly.into()) Err(crate::error::Error::StreamReadOnly.into())
} }
fn finish(&mut self) -> io::Result<()> { Ok(()) } fn finish(&mut self) -> io::Result<()> {
Ok(())
}
fn info(&self) -> &DiscTitle { &self.title } fn info(&self) -> &DiscTitle {
&self.title
}
fn codec_private(&self, track: usize) -> Option<Vec<u8>> { fn codec_private(&self, track: usize) -> Option<Vec<u8>> {
let pid = self.pid_to_track.iter() let pid = self
.pid_to_track
.iter()
.find(|(_, idx)| *idx == track) .find(|(_, idx)| *idx == track)
.map(|(pid, _)| *pid)?; .map(|(pid, _)| *pid)?;
self.parsers.iter() self.parsers
.iter()
.find(|(p, _)| *p == pid) .find(|(p, _)| *p == pid)
.and_then(|(_, parser)| parser.codec_private()) .and_then(|(_, parser)| parser.codec_private())
} }
+2 -1
View File
@@ -26,7 +26,8 @@ impl IsoSectorReader {
if sectors > u32::MAX as u64 { if sectors > u32::MAX as u64 {
return Err(crate::error::Error::IsoTooLarge { return Err(crate::error::Error::IsoTooLarge {
path: path.to_string(), path: path.to_string(),
}.into()); }
.into());
} }
let capacity = sectors as u32; let capacity = sectors as u32;
Ok(Self { file, capacity }) Ok(Self { file, capacity })
+51 -20
View File
@@ -7,7 +7,11 @@ use super::{meta, ts};
use crate::disc::{DiscTitle, Stream as DiscStream}; use crate::disc::{DiscTitle, Stream as DiscStream};
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
type PesSetup = (Vec<u16>, Vec<(u16, Box<dyn super::codec::CodecParser>)>, Vec<(u16, usize)>); type PesSetup = (
Vec<u16>,
Vec<(u16, Box<dyn super::codec::CodecParser>)>,
Vec<(u16, usize)>,
);
/// Size of initial scan buffer for PMT/stream detection. /// Size of initial scan buffer for PMT/stream detection.
const SCAN_SIZE: usize = 1024 * 1024; const SCAN_SIZE: usize = 1024 * 1024;
@@ -59,11 +63,15 @@ impl M2tsStream {
let m = meta::M2tsMeta::from_title(title); let m = meta::M2tsMeta::from_title(title);
meta::write_header(&mut writer, &m)?; meta::write_header(&mut writer, &m)?;
} }
let pids: Vec<u16> = title.streams.iter().map(|s| match s { let pids: Vec<u16> = title
DiscStream::Video(v) => v.pid, .streams
DiscStream::Audio(a) => a.pid, .iter()
DiscStream::Subtitle(s) => s.pid, .map(|s| match s {
}).collect(); DiscStream::Video(v) => v.pid,
DiscStream::Audio(a) => a.pid,
DiscStream::Subtitle(s) => s.pid,
})
.collect();
let boxed: Box<dyn Write> = Box::new(writer); let boxed: Box<dyn Write> = Box::new(writer);
let mut muxer = super::tsmux::TsMuxer::new(boxed, &pids); let mut muxer = super::tsmux::TsMuxer::new(boxed, &pids);
for (i, cp) in title.codec_privates.iter().enumerate() { for (i, cp) in title.codec_privates.iter().enumerate() {
@@ -117,11 +125,16 @@ impl M2tsStream {
let (pids, parsers, pid_to_track) = Self::setup_pes(&title.streams); let (pids, parsers, pid_to_track) = Self::setup_pes(&title.streams);
// Chain: remaining head bytes + rest of reader // Chain: remaining head bytes + rest of reader
let remaining_head = &head[header_end..]; let remaining_head = &head[header_end..];
let chain: Box<dyn Read> = Box::new(io::Cursor::new(remaining_head.to_vec()).chain(reader)); let chain: Box<dyn Read> =
Box::new(io::Cursor::new(remaining_head.to_vec()).chain(reader));
return Ok(Self { return Ok(Self {
disc_title: title.clone(), disc_title: title.clone(),
mode: Mode::Read { reader: chain }, mode: Mode::Read { reader: chain },
demuxer: if pids.is_empty() { None } else { Some(ts::TsDemuxer::new(&pids)) }, demuxer: if pids.is_empty() {
None
} else {
Some(ts::TsDemuxer::new(&pids))
},
parsers, parsers,
pending_frames: std::collections::VecDeque::new(), pending_frames: std::collections::VecDeque::new(),
pid_to_track, pid_to_track,
@@ -146,7 +159,11 @@ impl M2tsStream {
..DiscTitle::empty() ..DiscTitle::empty()
}, },
mode: Mode::Read { reader: chain }, mode: Mode::Read { reader: chain },
demuxer: if pids.is_empty() { None } else { Some(ts::TsDemuxer::new(&pids)) }, demuxer: if pids.is_empty() {
None
} else {
Some(ts::TsDemuxer::new(&pids))
},
parsers, parsers,
pending_frames: std::collections::VecDeque::new(), pending_frames: std::collections::VecDeque::new(),
pid_to_track, pid_to_track,
@@ -161,7 +178,9 @@ impl crate::pes::Stream for M2tsStream {
if let Some(frame) = self.pending_frames.pop_front() { if let Some(frame) = self.pending_frames.pop_front() {
return Ok(Some(frame)); return Ok(Some(frame));
} }
if self.pes_eof { return Ok(None); } if self.pes_eof {
return Ok(None);
}
loop { loop {
let reader = match &mut self.mode { let reader = match &mut self.mode {
@@ -175,11 +194,15 @@ impl crate::pes::Stream for M2tsStream {
// Flush demuxer — last PES packet may still be in the assembler // Flush demuxer — last PES packet may still be in the assembler
if let Some(ref mut demuxer) = self.demuxer { if let Some(ref mut demuxer) = self.demuxer {
for pes in &demuxer.flush() { for pes in &demuxer.flush() {
if let Some((_, track)) = self.pid_to_track.iter().find(|(pid, _)| *pid == pes.pid) { if let Some((_, track)) =
if let Some((_, parser)) = self.parsers.iter_mut().find(|(pid, _)| *pid == pes.pid) { self.pid_to_track.iter().find(|(pid, _)| *pid == pes.pid)
{
if let Some((_, parser)) =
self.parsers.iter_mut().find(|(pid, _)| *pid == pes.pid)
{
for frame in parser.parse(pes) { for frame in parser.parse(pes) {
self.pending_frames.push_back( self.pending_frames.push_back(
crate::pes::PesFrame::from_codec_frame(*track, frame) crate::pes::PesFrame::from_codec_frame(*track, frame),
); );
} }
} }
@@ -192,11 +215,15 @@ impl crate::pes::Stream for M2tsStream {
if let Some(ref mut demuxer) = self.demuxer { if let Some(ref mut demuxer) = self.demuxer {
let packets = demuxer.feed(&buf[..n]); let packets = demuxer.feed(&buf[..n]);
for pes in &packets { for pes in &packets {
if let Some((_, track)) = self.pid_to_track.iter().find(|(pid, _)| *pid == pes.pid) { if let Some((_, track)) =
if let Some((_, parser)) = self.parsers.iter_mut().find(|(pid, _)| *pid == pes.pid) { self.pid_to_track.iter().find(|(pid, _)| *pid == pes.pid)
{
if let Some((_, parser)) =
self.parsers.iter_mut().find(|(pid, _)| *pid == pes.pid)
{
for frame in parser.parse(pes) { for frame in parser.parse(pes) {
self.pending_frames.push_back( self.pending_frames.push_back(
crate::pes::PesFrame::from_codec_frame(*track, frame) crate::pes::PesFrame::from_codec_frame(*track, frame),
); );
} }
} }
@@ -224,7 +251,9 @@ impl crate::pes::Stream for M2tsStream {
} }
} }
fn info(&self) -> &crate::disc::DiscTitle { &self.disc_title } fn info(&self) -> &crate::disc::DiscTitle {
&self.disc_title
}
fn codec_private(&self, track: usize) -> Option<Vec<u8>> { fn codec_private(&self, track: usize) -> Option<Vec<u8>> {
// First check stored codec_privates from FMKV header // First check stored codec_privates from FMKV header
@@ -232,10 +261,13 @@ impl crate::pes::Stream for M2tsStream {
return Some(cp.clone()); return Some(cp.clone());
} }
// Fall back to parser-extracted codec_private // Fall back to parser-extracted codec_private
let pid = self.pid_to_track.iter() let pid = self
.pid_to_track
.iter()
.find(|(_, idx)| *idx == track) .find(|(_, idx)| *idx == track)
.map(|(pid, _)| *pid)?; .map(|(pid, _)| *pid)?;
self.parsers.iter() self.parsers
.iter()
.find(|(p, _)| *p == pid) .find(|(p, _)| *p == pid)
.and_then(|(_, parser)| parser.codec_private()) .and_then(|(_, parser)| parser.codec_private())
} }
@@ -251,4 +283,3 @@ impl crate::pes::Stream for M2tsStream {
true true
} }
} }
+31 -14
View File
@@ -93,7 +93,9 @@ impl M2tsMeta {
hdr: v.hdr.id().into(), hdr: v.hdr.id().into(),
label: v.label.clone(), label: v.label.clone(),
secondary: v.secondary, secondary: v.secondary,
codec_private: title.codec_privates.get(i) codec_private: title
.codec_privates
.get(i)
.and_then(|cp| cp.as_ref()) .and_then(|cp| cp.as_ref())
.map(|cp| base64::engine::general_purpose::STANDARD.encode(cp)), .map(|cp| base64::engine::general_purpose::STANDARD.encode(cp)),
}, },
@@ -141,8 +143,12 @@ impl M2tsMeta {
} => Stream::Video(VideoStream { } => Stream::Video(VideoStream {
pid: *pid, pid: *pid,
codec: codec.parse().unwrap_or(crate::disc::Codec::Unknown(0)), codec: codec.parse().unwrap_or(crate::disc::Codec::Unknown(0)),
resolution: resolution.parse().unwrap_or(crate::disc::Resolution::Unknown), resolution: resolution
frame_rate: frame_rate.parse().unwrap_or(crate::disc::FrameRate::Unknown), .parse()
.unwrap_or(crate::disc::Resolution::Unknown),
frame_rate: frame_rate
.parse()
.unwrap_or(crate::disc::FrameRate::Unknown),
hdr: hdr.parse().unwrap_or(crate::disc::HdrFormat::Sdr), hdr: hdr.parse().unwrap_or(crate::disc::HdrFormat::Sdr),
color_space: ColorSpace::Bt709, color_space: ColorSpace::Bt709,
secondary: *secondary, secondary: *secondary,
@@ -159,9 +165,13 @@ impl M2tsMeta {
} => Stream::Audio(AudioStream { } => Stream::Audio(AudioStream {
pid: *pid, pid: *pid,
codec: codec.parse().unwrap_or(crate::disc::Codec::Unknown(0)), codec: codec.parse().unwrap_or(crate::disc::Codec::Unknown(0)),
channels: channels.parse().unwrap_or(crate::disc::AudioChannels::Unknown), channels: channels
.parse()
.unwrap_or(crate::disc::AudioChannels::Unknown),
language: language.clone(), language: language.clone(),
sample_rate: sample_rate.parse().unwrap_or(crate::disc::SampleRate::Unknown), sample_rate: sample_rate
.parse()
.unwrap_or(crate::disc::SampleRate::Unknown),
secondary: *secondary, secondary: *secondary,
label: label.clone(), label: label.clone(),
}), }),
@@ -197,16 +207,23 @@ impl M2tsMeta {
/// Extract codec_private data per stream (from FMKV header). /// Extract codec_private data per stream (from FMKV header).
/// Returns a Vec matching stream order — None for streams without codec_private. /// Returns a Vec matching stream order — None for streams without codec_private.
pub fn codec_privates(&self) -> Vec<Option<Vec<u8>>> { pub fn codec_privates(&self) -> Vec<Option<Vec<u8>>> {
self.streams.iter().map(|s| { self.streams
if let MetaStream::Video { codec_private: Some(ref b64), .. } = s { .iter()
{ .map(|s| {
use base64::Engine; if let MetaStream::Video {
base64::engine::general_purpose::STANDARD.decode(b64).ok() codec_private: Some(ref b64),
..
} = s
{
{
use base64::Engine;
base64::engine::general_purpose::STANDARD.decode(b64).ok()
}
} else {
None
} }
} else { })
None .collect()
}
}).collect()
} }
} }
+39 -17
View File
@@ -40,10 +40,7 @@ pub struct MkvStream {
impl MkvStream { impl MkvStream {
/// Create for writing PES frames → MKV container. /// Create for writing PES frames → MKV container.
/// Codec privates come from title.codec_privates (populated by input stream). /// Codec privates come from title.codec_privates (populated by input stream).
pub fn create( pub fn create(writer: Box<dyn WriteSeek>, title: &DiscTitle) -> io::Result<Self> {
writer: Box<dyn WriteSeek>,
title: &DiscTitle,
) -> io::Result<Self> {
let mut tracks = Vec::new(); let mut tracks = Vec::new();
for (idx, s) in title.streams.iter().enumerate() { for (idx, s) in title.streams.iter().enumerate() {
let mut track = match s { let mut track = match s {
@@ -106,10 +103,14 @@ impl crate::pes::Stream for MkvStream {
} }
ebml::SIMPLE_BLOCK => { ebml::SIMPLE_BLOCK => {
let block = ebml::read_binary_val(&mut rs.reader, size as usize)?; let block = ebml::read_binary_val(&mut rs.reader, size as usize)?;
if block.len() < 4 { continue; } if block.len() < 4 {
continue;
}
let (track, vl) = block_vint(&block); let (track, vl) = block_vint(&block);
if vl + 3 > block.len() { continue; } if vl + 3 > block.len() {
continue;
}
let rel_ts = i16::from_be_bytes([block[vl], block[vl + 1]]); let rel_ts = i16::from_be_bytes([block[vl], block[vl + 1]]);
let keyframe = block[vl + 2] & 0x80 != 0; let keyframe = block[vl + 2] & 0x80 != 0;
@@ -139,7 +140,9 @@ impl crate::pes::Stream for MkvStream {
fn write(&mut self, frame: &crate::pes::PesFrame) -> io::Result<()> { fn write(&mut self, frame: &crate::pes::PesFrame) -> io::Result<()> {
match &mut self.mode { match &mut self.mode {
Mode::Write { muxer: Some(ref mut m) } => m.write_frame(frame.track, frame.pts, frame.keyframe, &frame.data), Mode::Write {
muxer: Some(ref mut m),
} => m.write_frame(frame.track, frame.pts, frame.keyframe, &frame.data),
Mode::Write { muxer: None } => Ok(()), Mode::Write { muxer: None } => Ok(()),
Mode::Read(_) => Err(crate::error::Error::StreamReadOnly.into()), Mode::Read(_) => Err(crate::error::Error::StreamReadOnly.into()),
} }
@@ -154,7 +157,9 @@ impl crate::pes::Stream for MkvStream {
Ok(()) Ok(())
} }
fn info(&self) -> &crate::disc::DiscTitle { &self.disc_title } fn info(&self) -> &crate::disc::DiscTitle {
&self.disc_title
}
fn codec_private(&self, track: usize) -> Option<Vec<u8>> { fn codec_private(&self, track: usize) -> Option<Vec<u8>> {
let track_num = (track + 1) as u16; // MKV tracks are 1-based let track_num = (track + 1) as u16; // MKV tracks are 1-based
@@ -176,9 +181,7 @@ impl crate::pes::Stream for MkvStream {
// ── MKV header parsing (read side) ──────────────────────────── // ── MKV header parsing (read side) ────────────────────────────
/// Returns (DiscTitle, codec_privates: Vec<(track_number, codec_private_bytes)>) /// Returns (DiscTitle, codec_privates: Vec<(track_number, codec_private_bytes)>)
fn parse_mkv_header( fn parse_mkv_header(r: &mut impl Read) -> MkvHeaderResult {
r: &mut impl Read,
) -> MkvHeaderResult {
let mut title = String::new(); let mut title = String::new();
let mut duration_ms = 0.0f64; let mut duration_ms = 0.0f64;
let mut ts_scale: u64 = 1_000_000; let mut ts_scale: u64 = 1_000_000;
@@ -220,7 +223,9 @@ fn parse_mkv_header(
ebml::TIMESTAMP_SCALE => ts_scale = ebml::read_uint_val(r, cs as usize)?, ebml::TIMESTAMP_SCALE => ts_scale = ebml::read_uint_val(r, cs as usize)?,
ebml::DURATION => duration_ms = ebml::read_float_val(r, cs as usize)?, ebml::DURATION => duration_ms = ebml::read_float_val(r, cs as usize)?,
ebml::TITLE => title = ebml::read_string_val(r, cs as usize)?, ebml::TITLE => title = ebml::read_string_val(r, cs as usize)?,
_ => { skip_bytes(r, cs)?; } _ => {
skip_bytes(r, cs)?;
}
} }
} }
got_info = true; got_info = true;
@@ -303,11 +308,15 @@ fn parse_track(
match aid { match aid {
ebml::SAMPLING_FREQUENCY => sr = ebml::read_float_val(r, as_ as usize)?, ebml::SAMPLING_FREQUENCY => sr = ebml::read_float_val(r, as_ as usize)?,
ebml::CHANNELS => ch = ebml::read_uint_val(r, as_ as usize)? as u8, ebml::CHANNELS => ch = ebml::read_uint_val(r, as_ as usize)? as u8,
_ => { skip_bytes(r, as_)?; } _ => {
skip_bytes(r, as_)?;
}
} }
} }
} }
_ => { skip_bytes(r, cs)?; } _ => {
skip_bytes(r, cs)?;
}
} }
} }
@@ -334,7 +343,11 @@ fn parse_track(
}; };
// Map MKV track numbers to BD-TS PIDs // Map MKV track numbers to BD-TS PIDs
let ts_pid = if tnum == 1 { 0x1011 } else { 0x1100 + (tnum - 2) }; let ts_pid = if tnum == 1 {
0x1011
} else {
0x1100 + (tnum - 2)
};
let stream = match ttype { let stream = match ttype {
1 => { 1 => {
@@ -382,10 +395,19 @@ fn block_vint(d: &[u8]) -> (u64, usize) {
return ((((d[0] & 0x3F) as u64) << 8) | d[1] as u64, 2); return ((((d[0] & 0x3F) as u64) << 8) | d[1] as u64, 2);
} }
if d[0] & 0x20 != 0 && d.len() >= 3 { if d[0] & 0x20 != 0 && d.len() >= 3 {
return ((((d[0] & 0x1F) as u64) << 16) | ((d[1] as u64) << 8) | d[2] as u64, 3); return (
(((d[0] & 0x1F) as u64) << 16) | ((d[1] as u64) << 8) | d[2] as u64,
3,
);
} }
if d[0] & 0x10 != 0 && d.len() >= 4 { if d[0] & 0x10 != 0 && d.len() >= 4 {
return ((((d[0] & 0x0F) as u64) << 24) | ((d[1] as u64) << 16) | ((d[2] as u64) << 8) | d[3] as u64, 4); return (
(((d[0] & 0x0F) as u64) << 24)
| ((d[1] as u64) << 16)
| ((d[2] as u64) << 8)
| d[3] as u64,
4,
);
} }
(0, 1) // Unsupported 5+ byte VINT — treat as track 0 (0, 1) // Unsupported 5+ byte VINT — treat as track 0
} }
+3 -3
View File
@@ -19,11 +19,9 @@ pub mod codec;
pub mod disc; pub mod disc;
pub mod ebml; pub mod ebml;
pub mod iso; pub mod iso;
pub mod mkv;
pub mod tsmux;
pub mod tsreader;
mod m2ts; mod m2ts;
pub mod meta; pub mod meta;
pub mod mkv;
mod mkvstream; mod mkvstream;
pub mod network; pub mod network;
pub mod null; pub mod null;
@@ -31,6 +29,8 @@ pub mod ps;
pub mod resolve; pub mod resolve;
pub mod stdio; pub mod stdio;
pub mod ts; pub mod ts;
pub mod tsmux;
pub mod tsreader;
pub use disc::DiscStream; pub use disc::DiscStream;
pub use iso::IsoSectorReader; pub use iso::IsoSectorReader;
+14 -3
View File
@@ -79,7 +79,11 @@ impl crate::pes::Stream for NetworkStream {
} }
fn write(&mut self, frame: &crate::pes::PesFrame) -> io::Result<()> { fn write(&mut self, frame: &crate::pes::PesFrame) -> io::Result<()> {
match &mut self.mode { match &mut self.mode {
Mode::Write { writer, ref mut header_written, .. } => { Mode::Write {
writer,
ref mut header_written,
..
} => {
if !*header_written { if !*header_written {
if !self.disc_title.streams.is_empty() { if !self.disc_title.streams.is_empty() {
let m = meta::M2tsMeta::from_title(&self.disc_title); let m = meta::M2tsMeta::from_title(&self.disc_title);
@@ -99,7 +103,9 @@ impl crate::pes::Stream for NetworkStream {
} }
Ok(()) Ok(())
} }
fn info(&self) -> &DiscTitle { &self.disc_title } fn info(&self) -> &DiscTitle {
&self.disc_title
}
} }
// NetworkStream is PES-only — no IOStream/Read/Write byte interface. // NetworkStream is PES-only — no IOStream/Read/Write byte interface.
@@ -174,7 +180,12 @@ mod tests {
let dt = sample_title(); let dt = sample_title();
let mut writer = NetworkStream::connect(&addr).unwrap().meta(&dt); let mut writer = NetworkStream::connect(&addr).unwrap().meta(&dt);
let frame = pes::PesFrame { track: 0, pts: 90000, keyframe: true, data: vec![0x47; 192] }; let frame = pes::PesFrame {
track: 0,
pts: 90000,
keyframe: true,
data: vec![0x47; 192],
};
pes::Stream::write(&mut writer, &frame).unwrap(); pes::Stream::write(&mut writer, &frame).unwrap();
pes::Stream::finish(&mut writer).unwrap(); pes::Stream::finish(&mut writer).unwrap();
+12 -4
View File
@@ -17,8 +17,16 @@ impl NullStream {
} }
impl crate::pes::Stream for NullStream { impl crate::pes::Stream for NullStream {
fn read(&mut self) -> io::Result<Option<crate::pes::PesFrame>> { Ok(None) } fn read(&mut self) -> io::Result<Option<crate::pes::PesFrame>> {
fn write(&mut self, _: &crate::pes::PesFrame) -> io::Result<()> { Ok(()) } Ok(None)
fn finish(&mut self) -> io::Result<()> { Ok(()) } }
fn info(&self) -> &DiscTitle { &self.disc_title } fn write(&mut self, _: &crate::pes::PesFrame) -> io::Result<()> {
Ok(())
}
fn finish(&mut self) -> io::Result<()> {
Ok(())
}
fn info(&self) -> &DiscTitle {
&self.disc_title
}
} }
-1
View File
@@ -163,7 +163,6 @@ impl PsDemuxer {
packets packets
} }
} }
/// Check whether a start code byte is a valid PES stream ID that carries payload. /// Check whether a start code byte is a valid PES stream ID that carries payload.
+41 -42
View File
@@ -128,12 +128,14 @@ fn validate_file_path(path: &Path, scheme: &str) -> io::Result<()> {
if path.as_os_str().is_empty() { if path.as_os_str().is_empty() {
return Err(crate::error::Error::StreamUrlMissingPath { return Err(crate::error::Error::StreamUrlMissingPath {
scheme: scheme.to_string(), scheme: scheme.to_string(),
}.into()); }
.into());
} }
if path.file_name().is_none() { if path.file_name().is_none() {
return Err(crate::error::Error::StreamUrlInvalid { return Err(crate::error::Error::StreamUrlInvalid {
url: format!("{scheme}://{}", path.display()), url: format!("{scheme}://{}", path.display()),
}.into()); }
.into());
} }
Ok(()) Ok(())
} }
@@ -143,12 +145,14 @@ fn validate_network_addr(addr: &str) -> io::Result<()> {
if addr.is_empty() { if addr.is_empty() {
return Err(crate::error::Error::StreamUrlMissingPath { return Err(crate::error::Error::StreamUrlMissingPath {
scheme: "network".to_string(), scheme: "network".to_string(),
}.into()); }
.into());
} }
if !addr.contains(':') { if !addr.contains(':') {
return Err(crate::error::Error::StreamUrlMissingPort { return Err(crate::error::Error::StreamUrlMissingPort {
addr: addr.to_string(), addr: addr.to_string(),
}.into()); }
.into());
} }
Ok(()) Ok(())
} }
@@ -169,10 +173,15 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::S
StreamUrl::Disc { device } => { StreamUrl::Disc { device } => {
// Open drive, init, scan — caller manages the drive // Open drive, init, scan — caller manages the drive
let mut drive = match device { let mut drive = match device {
Some(ref d) => crate::drive::Drive::open(d) Some(ref d) => {
.map_err(|e| -> io::Error { e.into() })?, crate::drive::Drive::open(d).map_err(|e| -> io::Error { e.into() })?
None => crate::drive::find_drive() }
.ok_or_else(|| -> io::Error { crate::error::Error::DeviceNotFound { path: String::new() }.into() })?, None => crate::drive::find_drive().ok_or_else(|| -> io::Error {
crate::error::Error::DeviceNotFound {
path: String::new(),
}
.into()
})?,
}; };
let _ = drive.wait_ready(); let _ = drive.wait_ready();
let _ = drive.init(); let _ = drive.init();
@@ -181,7 +190,8 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::S
drive, drive,
opts.keydb_path.as_deref(), opts.keydb_path.as_deref(),
opts.title_index.unwrap_or(0), opts.title_index.unwrap_or(0),
).map_err(|e| -> io::Error { e.into() })?; )
.map_err(|e| -> io::Error { e.into() })?;
if opts.raw { if opts.raw {
stream.set_raw(); stream.set_raw();
} }
@@ -193,7 +203,8 @@ 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 = DiscStream::open_iso(&path.to_string_lossy(), opts.title_index, &scan_opts)?; let mut stream =
DiscStream::open_iso(&path.to_string_lossy(), opts.title_index, &scan_opts)?;
if opts.raw { if opts.raw {
stream.set_raw(); stream.set_raw();
} }
@@ -201,15 +212,17 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::S
} }
StreamUrl::M2ts { ref path } => { StreamUrl::M2ts { ref path } => {
validate_file_path(path, "m2ts")?; validate_file_path(path, "m2ts")?;
let file = std::fs::File::open(path) let file = std::fs::File::open(path).map_err(|e| {
.map_err(|e| io::Error::new(e.kind(), format!("m2ts://{}: {}", path.display(), e)))?; io::Error::new(e.kind(), format!("m2ts://{}: {}", path.display(), e))
})?;
let reader = std::io::BufReader::with_capacity(IO_BUF_SIZE, file); let reader = std::io::BufReader::with_capacity(IO_BUF_SIZE, file);
Ok(Box::new(M2tsStream::open(reader)?)) Ok(Box::new(M2tsStream::open(reader)?))
} }
StreamUrl::Mkv { ref path } => { StreamUrl::Mkv { ref path } => {
validate_file_path(path, "mkv")?; validate_file_path(path, "mkv")?;
let file = std::fs::File::open(path) let file = std::fs::File::open(path).map_err(|e| {
.map_err(|e| io::Error::new(e.kind(), format!("mkv://{}: {}", path.display(), e)))?; io::Error::new(e.kind(), format!("mkv://{}: {}", path.display(), e))
})?;
let reader = std::io::BufReader::with_capacity(IO_BUF_SIZE, file); let reader = std::io::BufReader::with_capacity(IO_BUF_SIZE, file);
Ok(Box::new(MkvStream::open(reader)?)) Ok(Box::new(MkvStream::open(reader)?))
} }
@@ -217,16 +230,10 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result<Box<dyn crate::pes::S
validate_network_addr(addr)?; validate_network_addr(addr)?;
Ok(Box::new(NetworkStream::listen(addr)?)) Ok(Box::new(NetworkStream::listen(addr)?))
} }
StreamUrl::Stdio => { StreamUrl::Stdio => Ok(Box::new(StdioStream::input())),
Ok(Box::new(StdioStream::input())) StreamUrl::Null => Err(crate::error::Error::StreamWriteOnly.into()),
}
StreamUrl::Null => {
Err(crate::error::Error::StreamWriteOnly.into())
}
StreamUrl::Unknown { ref raw } => { StreamUrl::Unknown { ref raw } => {
Err(crate::error::Error::StreamUrlInvalid { Err(crate::error::Error::StreamUrlInvalid { url: raw.clone() }.into())
url: raw.clone(),
}.into())
} }
} }
} }
@@ -240,16 +247,18 @@ pub fn output(
match parsed { match parsed {
StreamUrl::Mkv { ref path } => { StreamUrl::Mkv { ref path } => {
validate_file_path(path, "mkv")?; validate_file_path(path, "mkv")?;
let file = std::fs::File::create(path) let file = std::fs::File::create(path).map_err(|e| {
.map_err(|e| io::Error::new(e.kind(), format!("mkv://{}: {}", path.display(), e)))?; io::Error::new(e.kind(), format!("mkv://{}: {}", path.display(), e))
})?;
let writer: Box<dyn super::WriteSeek> = let writer: Box<dyn super::WriteSeek> =
Box::new(std::io::BufWriter::with_capacity(IO_BUF_SIZE, file)); Box::new(std::io::BufWriter::with_capacity(IO_BUF_SIZE, file));
Ok(Box::new(MkvStream::create(writer, title)?)) Ok(Box::new(MkvStream::create(writer, title)?))
} }
StreamUrl::M2ts { ref path } => { StreamUrl::M2ts { ref path } => {
validate_file_path(path, "m2ts")?; validate_file_path(path, "m2ts")?;
let file = std::fs::File::create(path) let file = std::fs::File::create(path).map_err(|e| {
.map_err(|e| io::Error::new(e.kind(), format!("m2ts://{}: {}", path.display(), e)))?; io::Error::new(e.kind(), format!("m2ts://{}: {}", path.display(), e))
})?;
let writer = std::io::BufWriter::with_capacity(IO_BUF_SIZE, file); let writer = std::io::BufWriter::with_capacity(IO_BUF_SIZE, file);
Ok(Box::new(M2tsStream::create(writer, title)?)) Ok(Box::new(M2tsStream::create(writer, title)?))
} }
@@ -257,22 +266,12 @@ pub fn output(
validate_network_addr(addr)?; validate_network_addr(addr)?;
Ok(Box::new(NetworkStream::connect(addr)?.meta(title))) Ok(Box::new(NetworkStream::connect(addr)?.meta(title)))
} }
StreamUrl::Stdio => { StreamUrl::Stdio => Ok(Box::new(StdioStream::output(title))),
Ok(Box::new(StdioStream::output(title))) StreamUrl::Null => Ok(Box::new(NullStream::new(title))),
} StreamUrl::Disc { .. } => Err(crate::error::Error::StreamReadOnly.into()),
StreamUrl::Null => { StreamUrl::Iso { .. } => Err(crate::error::Error::StreamReadOnly.into()),
Ok(Box::new(NullStream::new(title)))
}
StreamUrl::Disc { .. } => {
Err(crate::error::Error::StreamReadOnly.into())
}
StreamUrl::Iso { .. } => {
Err(crate::error::Error::StreamReadOnly.into())
}
StreamUrl::Unknown { ref raw } => { StreamUrl::Unknown { ref raw } => {
Err(crate::error::Error::StreamUrlInvalid { Err(crate::error::Error::StreamUrlInvalid { url: raw.clone() }.into())
url: raw.clone(),
}.into())
} }
} }
} }
+9 -3
View File
@@ -84,13 +84,19 @@ impl crate::pes::Stream for StdioStream {
} }
} }
fn finish(&mut self) -> io::Result<()> { fn finish(&mut self) -> io::Result<()> {
if let Some(w) = &mut self.writer { w.flush()?; } if let Some(w) = &mut self.writer {
w.flush()?;
}
Ok(()) Ok(())
} }
fn info(&self) -> &DiscTitle { &self.disc_title } fn info(&self) -> &DiscTitle {
&self.disc_title
}
fn codec_private(&self, track: usize) -> Option<Vec<u8>> { fn codec_private(&self, track: usize) -> Option<Vec<u8>> {
self.stored_codec_privates.get(track).and_then(|c| c.clone()) self.stored_codec_privates
.get(track)
.and_then(|c| c.clone())
} }
fn headers_ready(&self) -> bool { fn headers_ready(&self) -> bool {
+4 -1
View File
@@ -329,7 +329,10 @@ pub fn scan_streams(data: &[u8]) -> Option<Vec<crate::disc::Stream>> {
let section_len = (((data[pat_start + 1] & 0x0F) as usize) << 8) let section_len = (((data[pat_start + 1] & 0x0F) as usize) << 8)
| data[pat_start + 2] as usize; | data[pat_start + 2] as usize;
let entries_start = pat_start + 8; let entries_start = pat_start + 8;
if section_len < 4 { offset += BD_TS_PACKET_SIZE; continue; } if section_len < 4 {
offset += BD_TS_PACKET_SIZE;
continue;
}
let entries_end = pat_start + 3 + section_len - 4; let entries_end = pat_start + 3 + section_len - 4;
let mut e = entries_start; let mut e = entries_start;
while e + 4 <= data.len() && e < entries_end { while e + 4 <= data.len() && e < entries_end {
+21 -14
View File
@@ -12,9 +12,9 @@ const TS_PAYLOAD: usize = 184;
pub struct TsMuxer<W: Write> { pub struct TsMuxer<W: Write> {
writer: W, writer: W,
pids: Vec<u16>, pids: Vec<u16>,
continuity: Vec<u8>, // per-PID continuity counter (0-15) continuity: Vec<u8>, // per-PID continuity counter (0-15)
codec_privates: Vec<Option<Vec<u8>>>, // per-track codec_private (for video parameter sets) codec_privates: Vec<Option<Vec<u8>>>, // per-track codec_private (for video parameter sets)
params_written: Vec<bool>, // per-track: have we written parameter sets? params_written: Vec<bool>, // per-track: have we written parameter sets?
} }
impl<W: Write> TsMuxer<W> { impl<W: Write> TsMuxer<W> {
@@ -40,12 +40,7 @@ impl<W: Write> TsMuxer<W> {
/// Write a PES frame as BD-TS packets. /// Write a PES frame as BD-TS packets.
/// Video frame data is expected as length-prefixed NALUs (MKV/PES format) /// Video frame data is expected as length-prefixed NALUs (MKV/PES format)
/// and is converted to Annex B for transport stream. /// and is converted to Annex B for transport stream.
pub fn write_frame( pub fn write_frame(&mut self, track: usize, pts_ns: i64, data: &[u8]) -> io::Result<()> {
&mut self,
track: usize,
pts_ns: i64,
data: &[u8],
) -> io::Result<()> {
if track >= self.pids.len() { if track >= self.pids.len() {
return Ok(()); // unknown track, skip return Ok(()); // unknown track, skip
} }
@@ -125,11 +120,13 @@ impl<W: Write> TsMuxer<W> {
self.writer.write_all(&STUFF_FF[..stuff_len - 2])?; self.writer.write_all(&STUFF_FF[..stuff_len - 2])?;
} }
} }
self.writer.write_all(&pes_packet[offset..offset + payload_len])?; self.writer
.write_all(&pes_packet[offset..offset + payload_len])?;
} else { } else {
self.writer.write_all(&tp_extra)?; self.writer.write_all(&tp_extra)?;
self.writer.write_all(&ts_header)?; self.writer.write_all(&ts_header)?;
self.writer.write_all(&pes_packet[offset..offset + payload_len])?; self.writer
.write_all(&pes_packet[offset..offset + payload_len])?;
} }
offset += payload_len; offset += payload_len;
@@ -202,24 +199,34 @@ fn hvcc_to_annex_b(hvcc: &[u8]) -> Option<Vec<u8>> {
let mut offset = 23; let mut offset = 23;
for _ in 0..num_arrays { for _ in 0..num_arrays {
if offset + 3 > hvcc.len() { break; } if offset + 3 > hvcc.len() {
break;
}
// array: 1 byte (completeness + NAL type), 2 bytes (numNalus) // array: 1 byte (completeness + NAL type), 2 bytes (numNalus)
let _nal_type = hvcc[offset] & 0x3F; let _nal_type = hvcc[offset] & 0x3F;
let num_nalus = u16::from_be_bytes([hvcc[offset + 1], hvcc[offset + 2]]) as usize; let num_nalus = u16::from_be_bytes([hvcc[offset + 1], hvcc[offset + 2]]) as usize;
offset += 3; offset += 3;
for _ in 0..num_nalus { for _ in 0..num_nalus {
if offset + 2 > hvcc.len() { break; } if offset + 2 > hvcc.len() {
break;
}
let nal_len = u16::from_be_bytes([hvcc[offset], hvcc[offset + 1]]) as usize; let nal_len = u16::from_be_bytes([hvcc[offset], hvcc[offset + 1]]) as usize;
offset += 2; offset += 2;
if offset + nal_len > hvcc.len() { break; } if offset + nal_len > hvcc.len() {
break;
}
out.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]); out.extend_from_slice(&[0x00, 0x00, 0x00, 0x01]);
out.extend_from_slice(&hvcc[offset..offset + nal_len]); out.extend_from_slice(&hvcc[offset..offset + nal_len]);
offset += nal_len; offset += nal_len;
} }
} }
if out.is_empty() { None } else { Some(out) } if out.is_empty() {
None
} else {
Some(out)
}
} }
/// Convert length-prefixed NALUs (4-byte BE length + NAL) to Annex B /// Convert length-prefixed NALUs (4-byte BE length + NAL) to Annex B
+12 -5
View File
@@ -69,10 +69,14 @@ impl<R: Read> TsDemuxReader<R> {
let packets = self.demuxer.feed(&self.buf[..n]); let packets = self.demuxer.feed(&self.buf[..n]);
for pes in &packets { for pes in &packets {
if let Some((_, track)) = self.pid_to_track.iter().find(|(pid, _)| *pid == pes.pid) { if let Some((_, track)) = self.pid_to_track.iter().find(|(pid, _)| *pid == pes.pid)
if let Some((_, parser)) = self.parsers.iter_mut().find(|(pid, _)| *pid == pes.pid) { {
if let Some((_, parser)) =
self.parsers.iter_mut().find(|(pid, _)| *pid == pes.pid)
{
for frame in parser.parse(pes) { for frame in parser.parse(pes) {
self.pending.push_back(PesFrame::from_codec_frame(*track, frame)); self.pending
.push_back(PesFrame::from_codec_frame(*track, frame));
} }
} }
} }
@@ -86,10 +90,13 @@ impl<R: Read> TsDemuxReader<R> {
/// Codec private data for a track. /// Codec private data for a track.
pub fn codec_private(&self, track: usize) -> Option<Vec<u8>> { pub fn codec_private(&self, track: usize) -> Option<Vec<u8>> {
let pid = self.pid_to_track.iter() let pid = self
.pid_to_track
.iter()
.find(|(_, idx)| *idx == track) .find(|(_, idx)| *idx == track)
.map(|(pid, _)| *pid)?; .map(|(pid, _)| *pid)?;
self.parsers.iter() self.parsers
.iter()
.find(|(p, _)| *p == pid) .find(|(p, _)| *p == pid)
.and_then(|(_, parser)| parser.codec_private()) .and_then(|(_, parser)| parser.codec_private())
} }
+15 -6
View File
@@ -28,7 +28,8 @@ impl PesFrame {
if self.data.len() > u32::MAX as usize { if self.data.len() > u32::MAX as usize {
return Err(crate::error::Error::PesFrameTooLarge { return Err(crate::error::Error::PesFrameTooLarge {
size: self.data.len(), size: self.data.len(),
}.into()); }
.into());
} }
w.write_all(&[self.track as u8])?; w.write_all(&[self.track as u8])?;
w.write_all(&self.pts.to_le_bytes())?; w.write_all(&self.pts.to_le_bytes())?;
@@ -49,8 +50,7 @@ impl PesFrame {
} }
let track = header[0] as usize; let track = header[0] as usize;
let pts = i64::from_le_bytes([ let pts = i64::from_le_bytes([
header[1], header[2], header[3], header[4], header[1], header[2], header[3], header[4], header[5], header[6], header[7], header[8],
header[5], header[6], header[7], header[8],
]); ]);
let keyframe = header[9] != 0; let keyframe = header[9] != 0;
let len = u32::from_le_bytes([header[10], header[11], header[12], header[13]]) as usize; let len = u32::from_le_bytes([header[10], header[11], header[12], header[13]]) as usize;
@@ -59,7 +59,12 @@ impl PesFrame {
} }
let mut data = vec![0u8; len]; let mut data = vec![0u8; len];
r.read_exact(&mut data)?; r.read_exact(&mut data)?;
Ok(Some(Self { track, pts, keyframe, data })) Ok(Some(Self {
track,
pts,
keyframe,
data,
}))
} }
/// Create from a codec::Frame with a track index. /// Create from a codec::Frame with a track index.
@@ -88,10 +93,14 @@ pub trait Stream {
fn info(&self) -> &crate::disc::DiscTitle; fn info(&self) -> &crate::disc::DiscTitle;
/// Codec initialization data for a track (SPS/PPS, etc). /// Codec initialization data for a track (SPS/PPS, etc).
fn codec_private(&self, _track: usize) -> Option<Vec<u8>> { None } fn codec_private(&self, _track: usize) -> Option<Vec<u8>> {
None
}
/// True when codec_private is available for all video tracks. /// True when codec_private is available for all video tracks.
fn headers_ready(&self) -> bool { true } fn headers_ready(&self) -> bool {
true
}
} }
/// Wraps any output stream and counts bytes written. /// Wraps any output stream and counts bytes written.
+5 -5
View File
@@ -166,7 +166,10 @@ impl SgIoTransport {
let err = std::io::Error::last_os_error(); let err = std::io::Error::last_os_error();
Err(if err.kind() == std::io::ErrorKind::PermissionDenied { Err(if err.kind() == std::io::ErrorKind::PermissionDenied {
Error::DevicePermission { Error::DevicePermission {
path: format!("{}: permission denied (try running as root)", device.display()), path: format!(
"{}: permission denied (try running as root)",
device.display()
),
} }
} else { } else {
Error::DeviceNotFound { Error::DeviceNotFound {
@@ -225,10 +228,7 @@ impl SgIoTransport {
if let Ok(mut entries) = std::fs::read_dir(&sg_dir) { if let Ok(mut entries) = std::fs::read_dir(&sg_dir) {
if let Some(Ok(entry)) = entries.next() { if let Some(Ok(entry)) = entries.next() {
let sg_name = entry.file_name(); let sg_name = entry.file_name();
return std::path::PathBuf::from(format!( return std::path::PathBuf::from(format!("/dev/{}", sg_name.to_string_lossy()));
"/dev/{}",
sg_name.to_string_lossy()
));
} }
} }
} }
+7 -3
View File
@@ -13,7 +13,9 @@ pub trait SectorReader: Send {
fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize>; fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result<usize>;
/// Total capacity in sectors, if known. /// Total capacity in sectors, if known.
fn capacity(&self) -> u32 { 0 } fn capacity(&self) -> u32 {
0
}
} }
/// SectorReader backed by a file (ISO image). /// SectorReader backed by a file (ISO image).
@@ -47,9 +49,11 @@ impl SectorReader for FileSectorReader {
use std::io::{Read, Seek, SeekFrom}; use std::io::{Read, Seek, SeekFrom};
let offset = lba as u64 * 2048; let offset = lba as u64 * 2048;
let bytes = count as usize * 2048; let bytes = count as usize * 2048;
self.file.seek(SeekFrom::Start(offset)) self.file
.seek(SeekFrom::Start(offset))
.map_err(|e| crate::error::Error::IoError { source: e })?; .map_err(|e| crate::error::Error::IoError { source: e })?;
self.file.read_exact(&mut buf[..bytes]) self.file
.read_exact(&mut buf[..bytes])
.map_err(|e| crate::error::Error::IoError { source: e })?; .map_err(|e| crate::error::Error::IoError { source: e })?;
Ok(bytes) Ok(bytes)
} }
+5 -1
View File
@@ -416,7 +416,11 @@ fn batch_count_u16_overflow_regression() {
// 47513600 = 725 * 65536, lower 16 bits = 0 // 47513600 = 725 * 65536, lower 16 bits = 0
let remaining: u32 = 47533152 - 19552; // = 47513600 let remaining: u32 = 47533152 - 19552; // = 47513600
assert_eq!(remaining, 47513600); assert_eq!(remaining, 47513600);
assert_eq!(remaining % 65536, 0, "remaining should be multiple of 65536"); assert_eq!(
remaining % 65536,
0,
"remaining should be multiple of 65536"
);
// Buggy version produces 0 → infinite loop // Buggy version produces 0 → infinite loop
assert_eq!(buggy_batch_count(remaining, 60), 0); assert_eq!(buggy_batch_count(remaining, 60), 0);
+30 -6
View File
@@ -121,7 +121,11 @@ fn open_input_bare_path_errors() {
Err(e) => e.to_string(), Err(e) => e.to_string(),
Ok(_) => panic!("expected error"), Ok(_) => panic!("expected error"),
}; };
assert!(msg.contains("not a valid stream URL") || msg.contains("E9002"), "got: {}", msg); assert!(
msg.contains("not a valid stream URL") || msg.contains("E9002"),
"got: {}",
msg
);
} }
#[test] #[test]
@@ -133,7 +137,11 @@ fn open_output_bare_path_errors() {
Err(e) => e.to_string(), Err(e) => e.to_string(),
Ok(_) => panic!("expected error"), Ok(_) => panic!("expected error"),
}; };
assert!(msg.contains("not a valid stream URL") || msg.contains("E9002"), "got: {}", msg); assert!(
msg.contains("not a valid stream URL") || msg.contains("E9002"),
"got: {}",
msg
);
} }
#[test] #[test]
@@ -144,7 +152,11 @@ fn open_input_m2ts_empty_path_errors() {
Err(e) => e.to_string(), Err(e) => e.to_string(),
Ok(_) => panic!("expected error"), Ok(_) => panic!("expected error"),
}; };
assert!(msg.contains("requires a file path") || msg.contains("E9003"), "got: {}", msg); assert!(
msg.contains("requires a file path") || msg.contains("E9003"),
"got: {}",
msg
);
} }
#[test] #[test]
@@ -155,7 +167,11 @@ fn open_output_null_input_errors() {
Err(e) => e.to_string(), Err(e) => e.to_string(),
Ok(_) => panic!("expected error"), Ok(_) => panic!("expected error"),
}; };
assert!(msg.contains("write-only") || msg.contains("E9001"), "got: {}", msg); assert!(
msg.contains("write-only") || msg.contains("E9001"),
"got: {}",
msg
);
} }
#[test] #[test]
@@ -167,7 +183,11 @@ fn open_output_disc_errors() {
Err(e) => e.to_string(), Err(e) => e.to_string(),
Ok(_) => panic!("expected error"), Ok(_) => panic!("expected error"),
}; };
assert!(msg.contains("read-only") || msg.contains("E9000"), "got: {}", msg); assert!(
msg.contains("read-only") || msg.contains("E9000"),
"got: {}",
msg
);
} }
#[test] #[test]
@@ -178,7 +198,11 @@ fn open_input_network_no_port_errors() {
Err(e) => e.to_string(), Err(e) => e.to_string(),
Ok(_) => panic!("expected error"), Ok(_) => panic!("expected error"),
}; };
assert!(msg.contains("PES pipeline") || msg.contains("missing port") || msg.contains("E9004"), "got: {}", msg); assert!(
msg.contains("PES pipeline") || msg.contains("missing port") || msg.contains("E9004"),
"got: {}",
msg
);
} }
#[test] #[test]