diff --git a/benches/sgio_read.rs b/benches/sgio_read.rs index fdaff49..74b2ed9 100644 --- a/benches/sgio_read.rs +++ b/benches/sgio_read.rs @@ -34,8 +34,10 @@ fn main() { let file = std::fs::File::create("/dev/null").unwrap(); let mut writer = std::io::BufWriter::with_capacity(4 * 1024 * 1024, file); - eprintln!("Reading 1000 batches ({:.1} MB) with write + progress...", - 1000.0 * batch as f64 * 2048.0 / 1_048_576.0); + eprintln!( + "Reading 1000 batches ({:.1} MB) with write + progress...", + 1000.0 * batch as f64 * 2048.0 / 1_048_576.0 + ); let start = Instant::now(); let mut ok = 0u32; @@ -51,7 +53,9 @@ fn main() { } Err(e) => { fail += 1; - if fail <= 5 { eprintln!(" FAIL LBA {}: {}", lba, e); } + if fail <= 5 { + eprintln!(" FAIL LBA {}: {}", lba, e); + } buf.fill(0); writer.write_all(&buf).unwrap(); } @@ -67,5 +71,12 @@ fn main() { let elapsed = start.elapsed().as_secs_f64(); 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 + ); } diff --git a/examples/iso_dump.rs b/examples/iso_dump.rs index ce43625..a90c5c4 100644 --- a/examples/iso_dump.rs +++ b/examples/iso_dump.rs @@ -50,7 +50,9 @@ fn main() { 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(); lba += count as u32; 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 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; - 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 = Instant::now(); } } 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() + ); } diff --git a/src/disc/dvd.rs b/src/disc/dvd.rs index c219973..45a9a97 100644 --- a/src/disc/dvd.rs +++ b/src/disc/dvd.rs @@ -111,7 +111,7 @@ impl Disc { chapters: Vec::new(), extents, content_format: ContentFormat::MpegPs, - codec_privates: Vec::new(), + codec_privates: Vec::new(), }); } } diff --git a/src/disc/mod.rs b/src/disc/mod.rs index 85db586..f0da508 100644 --- a/src/disc/mod.rs +++ b/src/disc/mod.rs @@ -1119,7 +1119,11 @@ impl Disc { use std::io::{Seek, SeekFrom, Write}; 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 let (start_lba, file) = if resume { @@ -1138,14 +1142,13 @@ impl Disc { (safe_sectors, f) } _ => { - let f = std::fs::File::create(path) - .map_err(|e| Error::IoError { source: e })?; + let f = + std::fs::File::create(path).map_err(|e| Error::IoError { source: e })?; (0u32, f) } } } else { - let f = std::fs::File::create(path) - .map_err(|e| Error::IoError { source: e })?; + let f = std::fs::File::create(path).map_err(|e| Error::IoError { source: e })?; (0u32, f) }; @@ -1160,7 +1163,6 @@ impl Disc { let count = remaining.min(batch as u32) as u16; let bytes = count as usize * 2048; - reader .read_sectors(lba, count, &mut buf[..bytes]) .map_err(|e| Error::IoError { diff --git a/src/drive/mod.rs b/src/drive/mod.rs index d587583..2569bbc 100644 --- a/src/drive/mod.rs +++ b/src/drive/mod.rs @@ -150,7 +150,18 @@ impl Drive { /// Uses GET EVENT STATUS NOTIFICATION which works regardless of firmware state. pub fn drive_status(&mut self) -> DriveStatus { // 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]; match self.scsi.as_mut().execute( &cdb, @@ -343,7 +354,18 @@ impl Drive { /// Read REPORT KEY RPC state (region playback control). pub fn report_key_rpc_state(&mut self) -> Option> { 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 r = self @@ -365,7 +387,18 @@ impl Drive { /// Read MODE SENSE page data. pub fn mode_sense_page(&mut self, page: u8) -> Option> { - 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 r = self .scsi @@ -421,7 +454,11 @@ impl Drive { /// Returns Err only after all attempts exhausted — user should clean /// the disc and resume. pub fn read(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result { - 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 = [ crate::scsi::SCSI_READ_10, 0x00, @@ -437,7 +474,10 @@ impl Drive { // Normal read 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 { let bytes_read = count as u64 * 2048; @@ -457,7 +497,10 @@ impl Drive { std::thread::sleep(std::time::Duration::from_secs(30)); 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; return Ok(result.bytes_transferred); @@ -479,7 +522,10 @@ impl Drive { std::thread::sleep(std::time::Duration::from_secs(30)); 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; return Ok(result.bytes_transferred); @@ -524,7 +570,14 @@ impl Drive { /// Lock the tray so the disc cannot be ejected during a rip. 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 _ = self.scsi @@ -534,7 +587,14 @@ impl Drive { /// Unlock the tray so the user can manually eject the disc. 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 _ = self.scsi diff --git a/src/error.rs b/src/error.rs index b8e9333..e69d229 100644 --- a/src/error.rs +++ b/src/error.rs @@ -89,10 +89,18 @@ pub const E_NO_METADATA: u16 = 9008; #[derive(Debug)] pub enum Error { // Device (1xxx) - DeviceNotFound { path: String }, - DevicePermission { path: String }, - DeviceNotReady { path: String }, - DeviceResetFailed { path: String }, + DeviceNotFound { + path: String, + }, + DevicePermission { + path: String, + }, + DeviceNotReady { + path: String, + }, + DeviceResetFailed { + path: String, + }, // Profile (2xxx) UnsupportedDrive { @@ -104,7 +112,10 @@ pub enum Error { // Unlock (3xxx) UnlockFailed, - SignatureMismatch { expected: [u8; 4], got: [u8; 4] }, + SignatureMismatch { + expected: [u8; 4], + got: [u8; 4], + }, // SCSI (4xxx) ScsiError { @@ -114,14 +125,23 @@ pub enum Error { }, // I/O (5xxx) - IoError { source: std::io::Error }, + IoError { + source: std::io::Error, + }, // Disc format (6xxx) - DiscRead { sector: u64 }, + DiscRead { + sector: u64, + }, MplsParse, ClpiParse, - UdfNotFound { path: String }, - DiscTitleRange { index: usize, count: usize }, + UdfNotFound { + path: String, + }, + DiscTitleRange { + index: usize, + count: usize, + }, IfoParse, MkvInvalid, NoStreams, @@ -142,22 +162,40 @@ pub enum Error { DecryptFailed, // Keydb (8xxx) - KeydbConnect { host: String }, - KeydbHttp { status: u16 }, + KeydbConnect { + host: String, + }, + KeydbHttp { + status: u16, + }, KeydbInvalid, - KeydbWrite { path: String }, + KeydbWrite { + path: String, + }, KeydbParse, - KeydbLoad { path: String }, + KeydbLoad { + path: String, + }, // Stream/mux (9xxx) StreamReadOnly, StreamWriteOnly, - StreamUrlInvalid { url: String }, - StreamUrlMissingPath { scheme: String }, - StreamUrlMissingPort { addr: String }, - PesFrameTooLarge { size: usize }, + StreamUrlInvalid { + url: String, + }, + StreamUrlMissingPath { + scheme: String, + }, + StreamUrlMissingPort { + addr: String, + }, + PesFrameTooLarge { + size: usize, + }, PesInvalidMagic, - IsoTooLarge { path: String }, + IsoTooLarge { + path: String, + }, NoMetadata, } @@ -227,22 +265,46 @@ impl std::fmt::Display for Error { product_id, product_revision, } => write!( - f, "E{}: {} {} {}", - self.code(), vendor_id.trim(), product_id.trim(), product_revision.trim() + f, + "E{}: {} {} {}", + self.code(), + vendor_id.trim(), + product_id.trim(), + product_revision.trim() ), 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(), - expected[0], expected[1], expected[2], expected[3], - got[0], got[1], got[2], got[3] + expected[0], + expected[1], + expected[2], + expected[3], + got[0], + got[1], + got[2], + got[3] ), - Error::ScsiError { opcode, status, sense_key } => { - write!(f, "E{}: 0x{:02x}/0x{:02x}/0x{:02x}", self.code(), opcode, status, sense_key) + Error::ScsiError { + 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::DiscRead { sector } => write!(f, "E{}: {}", self.code(), sector), 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::KeydbHttp { status } => write!(f, "E{}: {}", self.code(), status), Error::KeydbWrite { path } => write!(f, "E{}: {}", self.code(), path), diff --git a/src/event.rs b/src/event.rs index 16b8cb5..822472e 100644 --- a/src/event.rs +++ b/src/event.rs @@ -26,7 +26,6 @@ pub struct Event { #[derive(Debug)] pub enum EventKind { // ── Init sequence events ──────────────────────────────────────── - /// Drive opened successfully. DriveOpened { device: String }, @@ -43,7 +42,6 @@ pub enum EventKind { ScanComplete { titles: usize }, // ── Read events ───────────────────────────────────────────────── - /// Bytes successfully read and written to output. BytesRead { /// Bytes written so far. diff --git a/src/ifo.rs b/src/ifo.rs index 87399d0..50c5117 100644 --- a/src/ifo.rs +++ b/src/ifo.rs @@ -697,12 +697,12 @@ mod tests { let mut pgc = vec![0u8; 0xEA]; pgc[0x02] = 1; // 1 program 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[0x05] = 0x59; // minutes BCD pgc[0x06] = 0x30; // seconds BCD 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 pgc[0xE8] = (cell_offset >> 8) as u8; pgc[0xE9] = cell_offset as u8; @@ -710,17 +710,32 @@ mod tests { pgc.resize(pgc.len() + 48, 0); // Cell 0: sectors 100-200 let co = 0xEA; - pgc[co + 8] = 0; pgc[co + 9] = 0; 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 + pgc[co + 8] = 0; + pgc[co + 9] = 0; + 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; - pgc[co + 8] = 0; 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 + pgc[co + 8] = 0; + 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 expected = 1.0 * 3600.0 + 59.0 * 60.0 + 30.0; - assert!((title.duration_secs - expected).abs() < 0.1, - "expected ~{expected}s, got {}s", title.duration_secs); + assert!( + (title.duration_secs - expected).abs() < 0.1, + "expected ~{expected}s, got {}s", + title.duration_secs + ); assert_eq!(title.chapters, 5); assert_eq!(title.cells.len(), 2); assert_eq!(title.cells[0].first_sector, 100); diff --git a/src/lib.rs b/src/lib.rs index 32c35cf..5ec8445 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,8 +104,8 @@ pub use profile::DriveProfile; // Platform trait is pub(crate) -- callers use Drive, not Platform directly pub use decrypt::{decrypt_sectors, DecryptKeys}; pub use disc::{ - AacsState, AudioChannels, AudioStream, Clip, Codec, ColorSpace, ContentFormat, - Disc, DiscFormat, DiscTitle, Extent, FrameRate, HdrFormat, KeySource, Resolution, SampleRate, + AacsState, AudioChannels, AudioStream, Clip, Codec, ColorSpace, ContentFormat, Disc, + DiscFormat, DiscTitle, Extent, FrameRate, HdrFormat, KeySource, Resolution, SampleRate, ScanOptions, Stream, SubtitleStream, VideoStream, }; pub use mux::DiscStream; diff --git a/src/mux/codec/hevc.rs b/src/mux/codec/hevc.rs index 2fff94b..b8994fa 100644 --- a/src/mux/codec/hevc.rs +++ b/src/mux/codec/hevc.rs @@ -69,9 +69,15 @@ impl CodecParser for HevcParser { let nal_type = (data[nal_start] >> 1) & 0x3F; match nal_type { - NAL_VPS => { self.vps = 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_VPS => { + self.vps = 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 t if (NAL_BLA_W_LP..=NAL_RSV_IRAP_VCL23).contains(&t) => { keyframe = true; diff --git a/src/mux/disc.rs b/src/mux/disc.rs index 36534da..4fa39b6 100644 --- a/src/mux/disc.rs +++ b/src/mux/disc.rs @@ -5,9 +5,7 @@ //! //! Read-only. For disc→ISO (raw sector copy), use `Disc::copy()`. -use crate::disc::{ - detect_max_batch_sectors, Disc, DiscTitle, Extent, ScanOptions, -}; +use crate::disc::{detect_max_batch_sectors, Disc, DiscTitle, Extent, ScanOptions}; use crate::sector::SectorReader; use std::io; @@ -99,8 +97,8 @@ impl DiscStream { 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() })?; + 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()); @@ -110,7 +108,8 @@ impl DiscStream { return Err(crate::error::Error::DiscTitleRange { index: idx, count: disc.titles.len(), - }.into()); + } + .into()); } let title = disc.titles[idx].clone(); @@ -210,7 +209,10 @@ impl DiscStream { let bytes = sectors as usize * 2048; 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(_) => { self.buf_valid = bytes; 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 if let Some(ref mut demuxer) = self.ts_demuxer { for pes in &demuxer.flush() { - 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((_, track)) = + 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) { 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 { 0xE0..=0xEF => 0, 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, }; 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 { track, pts: pts_ns, @@ -276,22 +288,24 @@ impl crate::pes::Stream for DiscStream { } let bytes = self.buf_valid; - if let Err(e) = crate::decrypt::decrypt_sectors( - &mut self.read_buf[..bytes], - &self.decrypt_keys, - 0, - ) { + if let Err(e) = + crate::decrypt::decrypt_sectors(&mut self.read_buf[..bytes], &self.decrypt_keys, 0) + { return Err(e.into()); } if let Some(ref mut demuxer) = self.ts_demuxer { let packets = demuxer.feed(&self.read_buf[..bytes]); for pes in &packets { - 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((_, track)) = + 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) { 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 { 0xE0..=0xEF => 0, 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, }; 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 { track, pts: pts_ns, @@ -330,15 +350,22 @@ impl crate::pes::Stream for DiscStream { 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> { - let pid = self.pid_to_track.iter() + let pid = self + .pid_to_track + .iter() .find(|(_, idx)| *idx == track) .map(|(pid, _)| *pid)?; - self.parsers.iter() + self.parsers + .iter() .find(|(p, _)| *p == pid) .and_then(|(_, parser)| parser.codec_private()) } diff --git a/src/mux/iso.rs b/src/mux/iso.rs index a9c529d..ebf5747 100644 --- a/src/mux/iso.rs +++ b/src/mux/iso.rs @@ -26,7 +26,8 @@ impl IsoSectorReader { if sectors > u32::MAX as u64 { return Err(crate::error::Error::IsoTooLarge { path: path.to_string(), - }.into()); + } + .into()); } let capacity = sectors as u32; Ok(Self { file, capacity }) diff --git a/src/mux/m2ts.rs b/src/mux/m2ts.rs index 5371ae9..7e795a8 100644 --- a/src/mux/m2ts.rs +++ b/src/mux/m2ts.rs @@ -7,7 +7,11 @@ use super::{meta, ts}; use crate::disc::{DiscTitle, Stream as DiscStream}; use std::io::{self, Read, Write}; -type PesSetup = (Vec, Vec<(u16, Box)>, Vec<(u16, usize)>); +type PesSetup = ( + Vec, + Vec<(u16, Box)>, + Vec<(u16, usize)>, +); /// Size of initial scan buffer for PMT/stream detection. const SCAN_SIZE: usize = 1024 * 1024; @@ -59,11 +63,15 @@ impl M2tsStream { let m = meta::M2tsMeta::from_title(title); meta::write_header(&mut writer, &m)?; } - let pids: Vec = title.streams.iter().map(|s| match s { - DiscStream::Video(v) => v.pid, - DiscStream::Audio(a) => a.pid, - DiscStream::Subtitle(s) => s.pid, - }).collect(); + let pids: Vec = title + .streams + .iter() + .map(|s| match s { + DiscStream::Video(v) => v.pid, + DiscStream::Audio(a) => a.pid, + DiscStream::Subtitle(s) => s.pid, + }) + .collect(); let boxed: Box = Box::new(writer); let mut muxer = super::tsmux::TsMuxer::new(boxed, &pids); 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); // Chain: remaining head bytes + rest of reader let remaining_head = &head[header_end..]; - let chain: Box = Box::new(io::Cursor::new(remaining_head.to_vec()).chain(reader)); + let chain: Box = + Box::new(io::Cursor::new(remaining_head.to_vec()).chain(reader)); return Ok(Self { disc_title: title.clone(), 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, pending_frames: std::collections::VecDeque::new(), pid_to_track, @@ -146,7 +159,11 @@ impl M2tsStream { ..DiscTitle::empty() }, 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, pending_frames: std::collections::VecDeque::new(), pid_to_track, @@ -161,7 +178,9 @@ impl crate::pes::Stream for M2tsStream { if let Some(frame) = self.pending_frames.pop_front() { return Ok(Some(frame)); } - if self.pes_eof { return Ok(None); } + if self.pes_eof { + return Ok(None); + } loop { 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 if let Some(ref mut demuxer) = self.demuxer { for pes in &demuxer.flush() { - 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((_, track)) = + 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) { 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 { let packets = demuxer.feed(&buf[..n]); for pes in &packets { - 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((_, track)) = + 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) { 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> { // First check stored codec_privates from FMKV header @@ -232,10 +261,13 @@ impl crate::pes::Stream for M2tsStream { return Some(cp.clone()); } // 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) .map(|(pid, _)| *pid)?; - self.parsers.iter() + self.parsers + .iter() .find(|(p, _)| *p == pid) .and_then(|(_, parser)| parser.codec_private()) } @@ -251,4 +283,3 @@ impl crate::pes::Stream for M2tsStream { true } } - diff --git a/src/mux/meta.rs b/src/mux/meta.rs index ca0c594..7859b62 100644 --- a/src/mux/meta.rs +++ b/src/mux/meta.rs @@ -93,7 +93,9 @@ impl M2tsMeta { hdr: v.hdr.id().into(), label: v.label.clone(), secondary: v.secondary, - codec_private: title.codec_privates.get(i) + codec_private: title + .codec_privates + .get(i) .and_then(|cp| cp.as_ref()) .map(|cp| base64::engine::general_purpose::STANDARD.encode(cp)), }, @@ -141,8 +143,12 @@ impl M2tsMeta { } => Stream::Video(VideoStream { pid: *pid, codec: codec.parse().unwrap_or(crate::disc::Codec::Unknown(0)), - resolution: resolution.parse().unwrap_or(crate::disc::Resolution::Unknown), - frame_rate: frame_rate.parse().unwrap_or(crate::disc::FrameRate::Unknown), + resolution: resolution + .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), color_space: ColorSpace::Bt709, secondary: *secondary, @@ -159,9 +165,13 @@ impl M2tsMeta { } => Stream::Audio(AudioStream { pid: *pid, 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(), - sample_rate: sample_rate.parse().unwrap_or(crate::disc::SampleRate::Unknown), + sample_rate: sample_rate + .parse() + .unwrap_or(crate::disc::SampleRate::Unknown), secondary: *secondary, label: label.clone(), }), @@ -197,16 +207,23 @@ impl M2tsMeta { /// Extract codec_private data per stream (from FMKV header). /// Returns a Vec matching stream order — None for streams without codec_private. pub fn codec_privates(&self) -> Vec>> { - self.streams.iter().map(|s| { - if let MetaStream::Video { codec_private: Some(ref b64), .. } = s { -{ - use base64::Engine; - base64::engine::general_purpose::STANDARD.decode(b64).ok() + self.streams + .iter() + .map(|s| { + if let MetaStream::Video { + codec_private: Some(ref b64), + .. + } = s + { + { + use base64::Engine; + base64::engine::general_purpose::STANDARD.decode(b64).ok() + } + } else { + None } - } else { - None - } - }).collect() + }) + .collect() } } diff --git a/src/mux/mkvstream.rs b/src/mux/mkvstream.rs index 647eac6..aa01844 100644 --- a/src/mux/mkvstream.rs +++ b/src/mux/mkvstream.rs @@ -40,10 +40,7 @@ pub struct MkvStream { impl MkvStream { /// Create for writing PES frames → MKV container. /// Codec privates come from title.codec_privates (populated by input stream). - pub fn create( - writer: Box, - title: &DiscTitle, - ) -> io::Result { + pub fn create(writer: Box, title: &DiscTitle) -> io::Result { let mut tracks = Vec::new(); for (idx, s) in title.streams.iter().enumerate() { let mut track = match s { @@ -106,10 +103,14 @@ impl crate::pes::Stream for MkvStream { } ebml::SIMPLE_BLOCK => { 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); - 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 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<()> { 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::Read(_) => Err(crate::error::Error::StreamReadOnly.into()), } @@ -154,7 +157,9 @@ impl crate::pes::Stream for MkvStream { 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> { 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) ──────────────────────────── /// Returns (DiscTitle, codec_privates: Vec<(track_number, codec_private_bytes)>) -fn parse_mkv_header( - r: &mut impl Read, -) -> MkvHeaderResult { +fn parse_mkv_header(r: &mut impl Read) -> MkvHeaderResult { let mut title = String::new(); let mut duration_ms = 0.0f64; 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::DURATION => duration_ms = ebml::read_float_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; @@ -303,11 +308,15 @@ fn parse_track( match aid { 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, - _ => { 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 - 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 { 1 => { @@ -382,10 +395,19 @@ fn block_vint(d: &[u8]) -> (u64, usize) { return ((((d[0] & 0x3F) as u64) << 8) | d[1] as u64, 2); } 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 { - 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 } diff --git a/src/mux/mod.rs b/src/mux/mod.rs index 6c5565c..b10da65 100644 --- a/src/mux/mod.rs +++ b/src/mux/mod.rs @@ -19,11 +19,9 @@ pub mod codec; pub mod disc; pub mod ebml; pub mod iso; -pub mod mkv; -pub mod tsmux; -pub mod tsreader; mod m2ts; pub mod meta; +pub mod mkv; mod mkvstream; pub mod network; pub mod null; @@ -31,6 +29,8 @@ pub mod ps; pub mod resolve; pub mod stdio; pub mod ts; +pub mod tsmux; +pub mod tsreader; pub use disc::DiscStream; pub use iso::IsoSectorReader; diff --git a/src/mux/network.rs b/src/mux/network.rs index c16d89c..a43f9eb 100644 --- a/src/mux/network.rs +++ b/src/mux/network.rs @@ -79,7 +79,11 @@ impl crate::pes::Stream for NetworkStream { } fn write(&mut self, frame: &crate::pes::PesFrame) -> io::Result<()> { match &mut self.mode { - Mode::Write { writer, ref mut header_written, .. } => { + Mode::Write { + writer, + ref mut header_written, + .. + } => { if !*header_written { if !self.disc_title.streams.is_empty() { let m = meta::M2tsMeta::from_title(&self.disc_title); @@ -99,7 +103,9 @@ impl crate::pes::Stream for NetworkStream { } 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. @@ -174,7 +180,12 @@ mod tests { let dt = sample_title(); 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::finish(&mut writer).unwrap(); diff --git a/src/mux/null.rs b/src/mux/null.rs index 68a4b8e..c961e38 100644 --- a/src/mux/null.rs +++ b/src/mux/null.rs @@ -17,8 +17,16 @@ impl NullStream { } impl crate::pes::Stream for NullStream { - fn read(&mut self) -> io::Result> { Ok(None) } - fn write(&mut self, _: &crate::pes::PesFrame) -> io::Result<()> { Ok(()) } - fn finish(&mut self) -> io::Result<()> { Ok(()) } - fn info(&self) -> &DiscTitle { &self.disc_title } + fn read(&mut self) -> io::Result> { + Ok(None) + } + fn write(&mut self, _: &crate::pes::PesFrame) -> io::Result<()> { + Ok(()) + } + fn finish(&mut self) -> io::Result<()> { + Ok(()) + } + fn info(&self) -> &DiscTitle { + &self.disc_title + } } diff --git a/src/mux/ps.rs b/src/mux/ps.rs index 761b7b6..0ea5c97 100644 --- a/src/mux/ps.rs +++ b/src/mux/ps.rs @@ -163,7 +163,6 @@ impl PsDemuxer { packets } - } /// Check whether a start code byte is a valid PES stream ID that carries payload. diff --git a/src/mux/resolve.rs b/src/mux/resolve.rs index 81249f9..82f06a5 100644 --- a/src/mux/resolve.rs +++ b/src/mux/resolve.rs @@ -128,12 +128,14 @@ fn validate_file_path(path: &Path, scheme: &str) -> io::Result<()> { if path.as_os_str().is_empty() { return Err(crate::error::Error::StreamUrlMissingPath { scheme: scheme.to_string(), - }.into()); + } + .into()); } if path.file_name().is_none() { return Err(crate::error::Error::StreamUrlInvalid { url: format!("{scheme}://{}", path.display()), - }.into()); + } + .into()); } Ok(()) } @@ -143,12 +145,14 @@ fn validate_network_addr(addr: &str) -> io::Result<()> { if addr.is_empty() { return Err(crate::error::Error::StreamUrlMissingPath { scheme: "network".to_string(), - }.into()); + } + .into()); } if !addr.contains(':') { return Err(crate::error::Error::StreamUrlMissingPort { addr: addr.to_string(), - }.into()); + } + .into()); } Ok(()) } @@ -169,10 +173,15 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result { // Open drive, init, scan — caller manages the drive let mut drive = match device { - Some(ref d) => 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() })?, + Some(ref d) => { + 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() + })?, }; let _ = drive.wait_ready(); let _ = drive.init(); @@ -181,7 +190,8 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result io::Error { e.into() })?; + ) + .map_err(|e| -> io::Error { e.into() })?; if opts.raw { stream.set_raw(); } @@ -193,7 +203,8 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result crate::disc::ScanOptions::with_keydb(p), 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 { stream.set_raw(); } @@ -201,15 +212,17 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result { validate_file_path(path, "m2ts")?; - let file = std::fs::File::open(path) - .map_err(|e| io::Error::new(e.kind(), format!("m2ts://{}: {}", path.display(), e)))?; + let file = std::fs::File::open(path).map_err(|e| { + io::Error::new(e.kind(), format!("m2ts://{}: {}", path.display(), e)) + })?; let reader = std::io::BufReader::with_capacity(IO_BUF_SIZE, file); Ok(Box::new(M2tsStream::open(reader)?)) } StreamUrl::Mkv { ref path } => { validate_file_path(path, "mkv")?; - let file = std::fs::File::open(path) - .map_err(|e| io::Error::new(e.kind(), format!("mkv://{}: {}", path.display(), e)))?; + let file = std::fs::File::open(path).map_err(|e| { + io::Error::new(e.kind(), format!("mkv://{}: {}", path.display(), e)) + })?; let reader = std::io::BufReader::with_capacity(IO_BUF_SIZE, file); Ok(Box::new(MkvStream::open(reader)?)) } @@ -217,16 +230,10 @@ pub fn input(url: &str, opts: &InputOptions) -> io::Result { - Ok(Box::new(StdioStream::input())) - } - StreamUrl::Null => { - Err(crate::error::Error::StreamWriteOnly.into()) - } + StreamUrl::Stdio => Ok(Box::new(StdioStream::input())), + StreamUrl::Null => Err(crate::error::Error::StreamWriteOnly.into()), StreamUrl::Unknown { ref raw } => { - Err(crate::error::Error::StreamUrlInvalid { - url: raw.clone(), - }.into()) + Err(crate::error::Error::StreamUrlInvalid { url: raw.clone() }.into()) } } } @@ -240,16 +247,18 @@ pub fn output( match parsed { StreamUrl::Mkv { ref path } => { validate_file_path(path, "mkv")?; - let file = std::fs::File::create(path) - .map_err(|e| io::Error::new(e.kind(), format!("mkv://{}: {}", path.display(), e)))?; + let file = std::fs::File::create(path).map_err(|e| { + io::Error::new(e.kind(), format!("mkv://{}: {}", path.display(), e)) + })?; let writer: Box = Box::new(std::io::BufWriter::with_capacity(IO_BUF_SIZE, file)); Ok(Box::new(MkvStream::create(writer, title)?)) } StreamUrl::M2ts { ref path } => { validate_file_path(path, "m2ts")?; - let file = std::fs::File::create(path) - .map_err(|e| io::Error::new(e.kind(), format!("m2ts://{}: {}", path.display(), e)))?; + let file = std::fs::File::create(path).map_err(|e| { + io::Error::new(e.kind(), format!("m2ts://{}: {}", path.display(), e)) + })?; let writer = std::io::BufWriter::with_capacity(IO_BUF_SIZE, file); Ok(Box::new(M2tsStream::create(writer, title)?)) } @@ -257,22 +266,12 @@ pub fn output( validate_network_addr(addr)?; Ok(Box::new(NetworkStream::connect(addr)?.meta(title))) } - StreamUrl::Stdio => { - Ok(Box::new(StdioStream::output(title))) - } - StreamUrl::Null => { - Ok(Box::new(NullStream::new(title))) - } - StreamUrl::Disc { .. } => { - Err(crate::error::Error::StreamReadOnly.into()) - } - StreamUrl::Iso { .. } => { - Err(crate::error::Error::StreamReadOnly.into()) - } + StreamUrl::Stdio => Ok(Box::new(StdioStream::output(title))), + StreamUrl::Null => 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 } => { - Err(crate::error::Error::StreamUrlInvalid { - url: raw.clone(), - }.into()) + Err(crate::error::Error::StreamUrlInvalid { url: raw.clone() }.into()) } } } diff --git a/src/mux/stdio.rs b/src/mux/stdio.rs index d7d7f0f..4f5ad96 100644 --- a/src/mux/stdio.rs +++ b/src/mux/stdio.rs @@ -84,13 +84,19 @@ impl crate::pes::Stream for StdioStream { } } 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(()) } - fn info(&self) -> &DiscTitle { &self.disc_title } + fn info(&self) -> &DiscTitle { + &self.disc_title + } fn codec_private(&self, track: usize) -> Option> { - 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 { diff --git a/src/mux/ts.rs b/src/mux/ts.rs index 382427b..58b2d85 100644 --- a/src/mux/ts.rs +++ b/src/mux/ts.rs @@ -329,7 +329,10 @@ pub fn scan_streams(data: &[u8]) -> Option> { let section_len = (((data[pat_start + 1] & 0x0F) as usize) << 8) | data[pat_start + 2] as usize; 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 mut e = entries_start; while e + 4 <= data.len() && e < entries_end { diff --git a/src/mux/tsmux.rs b/src/mux/tsmux.rs index ba56911..2811699 100644 --- a/src/mux/tsmux.rs +++ b/src/mux/tsmux.rs @@ -12,9 +12,9 @@ const TS_PAYLOAD: usize = 184; pub struct TsMuxer { writer: W, pids: Vec, - continuity: Vec, // per-PID continuity counter (0-15) + continuity: Vec, // per-PID continuity counter (0-15) codec_privates: Vec>>, // per-track codec_private (for video parameter sets) - params_written: Vec, // per-track: have we written parameter sets? + params_written: Vec, // per-track: have we written parameter sets? } impl TsMuxer { @@ -40,12 +40,7 @@ impl TsMuxer { /// Write a PES frame as BD-TS packets. /// Video frame data is expected as length-prefixed NALUs (MKV/PES format) /// and is converted to Annex B for transport stream. - pub fn write_frame( - &mut self, - track: usize, - pts_ns: i64, - data: &[u8], - ) -> io::Result<()> { + pub fn write_frame(&mut self, track: usize, pts_ns: i64, data: &[u8]) -> io::Result<()> { if track >= self.pids.len() { return Ok(()); // unknown track, skip } @@ -125,11 +120,13 @@ impl TsMuxer { 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 { self.writer.write_all(&tp_extra)?; 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; @@ -202,24 +199,34 @@ fn hvcc_to_annex_b(hvcc: &[u8]) -> Option> { let mut offset = 23; 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) let _nal_type = hvcc[offset] & 0x3F; let num_nalus = u16::from_be_bytes([hvcc[offset + 1], hvcc[offset + 2]]) as usize; offset += 3; 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; 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(&hvcc[offset..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 diff --git a/src/mux/tsreader.rs b/src/mux/tsreader.rs index e5019db..236378b 100644 --- a/src/mux/tsreader.rs +++ b/src/mux/tsreader.rs @@ -69,10 +69,14 @@ impl TsDemuxReader { let packets = self.demuxer.feed(&self.buf[..n]); for pes in &packets { - 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((_, track)) = 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) { - 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 TsDemuxReader { /// Codec private data for a track. pub fn codec_private(&self, track: usize) -> Option> { - let pid = self.pid_to_track.iter() + let pid = self + .pid_to_track + .iter() .find(|(_, idx)| *idx == track) .map(|(pid, _)| *pid)?; - self.parsers.iter() + self.parsers + .iter() .find(|(p, _)| *p == pid) .and_then(|(_, parser)| parser.codec_private()) } diff --git a/src/pes.rs b/src/pes.rs index 013ba07..f67e4fc 100644 --- a/src/pes.rs +++ b/src/pes.rs @@ -28,7 +28,8 @@ impl PesFrame { if self.data.len() > u32::MAX as usize { return Err(crate::error::Error::PesFrameTooLarge { size: self.data.len(), - }.into()); + } + .into()); } w.write_all(&[self.track as u8])?; w.write_all(&self.pts.to_le_bytes())?; @@ -49,8 +50,7 @@ impl PesFrame { } let track = header[0] as usize; let pts = i64::from_le_bytes([ - header[1], header[2], header[3], header[4], - header[5], header[6], header[7], header[8], + header[1], header[2], header[3], header[4], header[5], header[6], header[7], header[8], ]); let keyframe = header[9] != 0; 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]; 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. @@ -88,10 +93,14 @@ pub trait Stream { fn info(&self) -> &crate::disc::DiscTitle; /// Codec initialization data for a track (SPS/PPS, etc). - fn codec_private(&self, _track: usize) -> Option> { None } + fn codec_private(&self, _track: usize) -> Option> { + None + } /// 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. diff --git a/src/scsi/linux.rs b/src/scsi/linux.rs index 8d2371e..5805e19 100644 --- a/src/scsi/linux.rs +++ b/src/scsi/linux.rs @@ -166,7 +166,10 @@ impl SgIoTransport { let err = std::io::Error::last_os_error(); Err(if err.kind() == std::io::ErrorKind::PermissionDenied { Error::DevicePermission { - path: format!("{}: permission denied (try running as root)", device.display()), + path: format!( + "{}: permission denied (try running as root)", + device.display() + ), } } else { Error::DeviceNotFound { @@ -225,10 +228,7 @@ impl SgIoTransport { if let Ok(mut entries) = std::fs::read_dir(&sg_dir) { if let Some(Ok(entry)) = entries.next() { let sg_name = entry.file_name(); - return std::path::PathBuf::from(format!( - "/dev/{}", - sg_name.to_string_lossy() - )); + return std::path::PathBuf::from(format!("/dev/{}", sg_name.to_string_lossy())); } } } diff --git a/src/sector.rs b/src/sector.rs index cf3274e..4754677 100644 --- a/src/sector.rs +++ b/src/sector.rs @@ -13,7 +13,9 @@ pub trait SectorReader: Send { fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result; /// Total capacity in sectors, if known. - fn capacity(&self) -> u32 { 0 } + fn capacity(&self) -> u32 { + 0 + } } /// SectorReader backed by a file (ISO image). @@ -47,9 +49,11 @@ impl SectorReader for FileSectorReader { use std::io::{Read, Seek, SeekFrom}; let offset = lba as u64 * 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 })?; - self.file.read_exact(&mut buf[..bytes]) + self.file + .read_exact(&mut buf[..bytes]) .map_err(|e| crate::error::Error::IoError { source: e })?; Ok(bytes) } diff --git a/tests/disc_tests.rs b/tests/disc_tests.rs index d488ac2..b6ac4d6 100644 --- a/tests/disc_tests.rs +++ b/tests/disc_tests.rs @@ -416,7 +416,11 @@ fn batch_count_u16_overflow_regression() { // 47513600 = 725 * 65536, lower 16 bits = 0 let remaining: u32 = 47533152 - 19552; // = 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 assert_eq!(buggy_batch_count(remaining, 60), 0); diff --git a/tests/streams.rs b/tests/streams.rs index 18f1747..26474b9 100644 --- a/tests/streams.rs +++ b/tests/streams.rs @@ -121,7 +121,11 @@ fn open_input_bare_path_errors() { Err(e) => e.to_string(), 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] @@ -133,7 +137,11 @@ fn open_output_bare_path_errors() { Err(e) => e.to_string(), 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] @@ -144,7 +152,11 @@ fn open_input_m2ts_empty_path_errors() { Err(e) => e.to_string(), 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] @@ -155,7 +167,11 @@ fn open_output_null_input_errors() { Err(e) => e.to_string(), 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] @@ -167,7 +183,11 @@ fn open_output_disc_errors() { Err(e) => e.to_string(), 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] @@ -178,7 +198,11 @@ fn open_input_network_no_port_errors() { Err(e) => e.to_string(), 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]