From ccb1fadedff79bd6d20f76311a4e7bc6cac3ecbb Mon Sep 17 00:00:00 2001 From: MattJackson <1085847+MattJackson@users.noreply.github.com> Date: Wed, 15 Apr 2026 02:37:54 +0000 Subject: [PATCH] =?UTF-8?q?Add=20PES=20frame=20types=20and=20FileSectorRea?= =?UTF-8?q?der=20=E2=80=94=20foundation=20for=20stream=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - pes.rs: PesFrame, InputStream, OutputStream traits - sector.rs: SectorReader now public, added FileSectorReader (ISO = file) - Foundation for unified DiscStream that handles both disc and ISO --- src/lib.rs | 16 +++++++++------- src/pes.rs | 39 +++++++++++++++++++++++++++++++++++++++ src/sector.rs | 41 ++++++++++++++++++++++++++++++++++++++++- 3 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 src/pes.rs diff --git a/src/lib.rs b/src/lib.rs index be689d2..a26aa4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,26 +79,29 @@ pub mod keydb; pub(crate) mod labels; pub(crate) mod mpls; pub mod mux; +pub mod pes; pub(crate) mod platform; pub mod profile; pub mod scsi; -pub(crate) mod sector; +pub mod sector; pub(crate) mod speed; pub(crate) mod udf; +pub use drive::capture::{ + capture_drive_data, mask_bytes, mask_string, CapturedFeature, DriveCapture, +}; pub use drive::{find_drive, find_drives, Drive, DriveStatus}; -pub use drive::capture::{DriveCapture, CapturedFeature, capture_drive_data, mask_string, mask_bytes}; pub use error::{Error, Result}; pub use event::{Event, EventKind}; pub use identity::DriveId; 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, AudioStream, Clip, Codec, ColorSpace, ContentFormat, ContentReader, Disc, - DiscFormat, DiscTitle, Extent, HdrFormat, KeySource, ScanOptions, Stream, SubtitleStream, - VideoStream, + AacsState, AudioChannels, AudioStream, Clip, Codec, ColorSpace, ContentFormat, + Disc, DiscFormat, DiscTitle, Extent, FrameRate, HdrFormat, KeySource, Resolution, SampleRate, + ScanOptions, Stream, SubtitleStream, VideoStream, }; -pub use mux::DiscOptions; pub use mux::DiscStream; pub use mux::IOStream; pub use mux::IsoStream; @@ -108,7 +111,6 @@ pub use mux::NetworkStream; pub use mux::NullStream; pub use mux::StdioStream; pub use mux::{open_input, open_output, parse_url, InputOptions, StreamUrl}; -pub use decrypt::{DecryptKeys, decrypt_sectors}; pub use scsi::ScsiTransport; pub use sector::SectorReader; pub use speed::DriveSpeed; diff --git a/src/pes.rs b/src/pes.rs new file mode 100644 index 0000000..325e263 --- /dev/null +++ b/src/pes.rs @@ -0,0 +1,39 @@ +//! PES frame — the universal intermediate format. +//! +//! Every input stream produces PES frames. Every output stream consumes them. +//! The pipeline just moves frames: input.next_frame() → output.write_frame(). +//! +//! A PES frame is one unit of elementary stream data: a video frame, +//! an audio frame, a subtitle packet. It has a track ID, timestamp, +//! and the raw codec data. + +/// One frame of elementary stream data. +#[derive(Debug, Clone)] +pub struct PesFrame { + /// Track index (0-based, matches StreamInfo track order). + pub track: usize, + /// Presentation timestamp in nanoseconds. + pub pts: i64, + /// True if this is a keyframe (IDR for video). + pub keyframe: bool, + /// Raw elementary stream data (NAL units, audio samples, etc). + pub data: Vec, +} + +/// Input stream — produces PES frames from any source. +pub trait InputStream { + /// Get the next frame. Returns None at end of stream. + fn next_frame(&mut self) -> std::io::Result>; + + /// Stream metadata (tracks, duration, etc). + fn info(&self) -> &crate::disc::DiscTitle; +} + +/// Output stream — consumes PES frames to any destination. +pub trait OutputStream { + /// Write one frame. + fn write_frame(&mut self, frame: &PesFrame) -> std::io::Result<()>; + + /// Finalize (flush, write index, close). + fn finish(&mut self) -> std::io::Result<()>; +} diff --git a/src/sector.rs b/src/sector.rs index 8c7ad42..c65ed5e 100644 --- a/src/sector.rs +++ b/src/sector.rs @@ -7,8 +7,47 @@ use crate::error::Result; /// Read 2048-byte sectors from a disc or disc image. -pub trait SectorReader { +pub trait SectorReader: Send { /// Read `count` sectors starting at `lba` into `buf`. /// `buf` must be at least `count * 2048` bytes. fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result; + + /// Total capacity in sectors, if known. + fn capacity(&self) -> u32 { 0 } +} + +/// SectorReader backed by a file (ISO image). +/// Seeks to lba * 2048, reads count * 2048 bytes. +pub struct FileSectorReader { + file: std::io::BufReader, + capacity: u32, +} + +impl FileSectorReader { + pub fn open(path: &str) -> std::io::Result { + let file = std::fs::File::open(path)?; + let len = file.metadata()?.len(); + let capacity = (len / 2048) as u32; + Ok(Self { + file: std::io::BufReader::with_capacity(4 * 1024 * 1024, file), + capacity, + }) + } +} + +impl SectorReader for FileSectorReader { + fn read_sectors(&mut self, lba: u32, count: u16, buf: &mut [u8]) -> Result { + use std::io::{Read, Seek, SeekFrom}; + let offset = lba as u64 * 2048; + let bytes = count as usize * 2048; + self.file.seek(SeekFrom::Start(offset)) + .map_err(|e| crate::error::Error::IoError { source: e })?; + self.file.read_exact(&mut buf[..bytes]) + .map_err(|e| crate::error::Error::IoError { source: e })?; + Ok(bytes) + } + + fn capacity(&self) -> u32 { + self.capacity + } }