ogg_pager-0.7.0/.cargo_vcs_info.json0000644000000001470000000000100127740ustar { "git": { "sha1": "da78c442469bb892a95a0132f2f62a975b18506a" }, "path_in_vcs": "ogg_pager" }ogg_pager-0.7.0/Cargo.toml0000644000000047740000000000100110040ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "ogg_pager" version = "0.7.0" authors = ["Serial <69764315+Serial-ATA@users.noreply.github.com>"] build = false include = [ "src", "Cargo.toml", "../LICENSE-*", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A simple OGG page reader" readme = "README.md" keywords = [ "ogg", "xiph", ] categories = [ "multimedia", "multimedia::audio", "parser-implementations", ] license = "MIT OR Apache-2.0" repository = "https://github.com/Serial-ATA/lofty-rs" [lib] name = "ogg_pager" path = "src/lib.rs" [dependencies.byteorder] version = "1.5.0" [lints.clippy] bool_to_int_with_if = "allow" cast_possible_truncation = "allow" cast_possible_wrap = "allow" cast_precision_loss = "allow" cast_sign_loss = "allow" dbg_macro = "forbid" doc_markdown = "allow" field_reassign_with_default = "allow" from_over_into = "allow" ignored_unit_patterns = "allow" into_iter_without_iter = "allow" len_without_is_empty = "allow" let_underscore_untyped = "allow" manual_range_patterns = "allow" match_wildcard_for_single_variants = "allow" module_name_repetitions = "allow" must_use_candidate = "allow" needless_late_init = "allow" needless_return = "allow" no_effect_underscore_binding = "allow" redundant_guards = "allow" return_self_not_must_use = "allow" semicolon_if_nothing_returned = "allow" similar_names = "allow" single_match_else = "allow" string_to_string = "forbid" struct_excessive_bools = "allow" tabs_in_doc_comments = "allow" too_many_lines = "allow" type_complexity = "allow" uninlined_format_args = "allow" upper_case_acronyms = "allow" used_underscore_binding = "allow" [lints.clippy.all] level = "deny" priority = -1 [lints.clippy.pedantic] level = "deny" priority = -1 [lints.rust] explicit_outlives_requirements = "deny" missing_docs = "deny" trivial_casts = "deny" trivial_numeric_casts = "deny" unknown_lints = "allow" unused_import_braces = "deny" [lints.rust.rust_2018_idioms] level = "deny" priority = -1 [lints.rustdoc] broken_intra_doc_links = "deny" ogg_pager-0.7.0/Cargo.toml.orig000064400000000000000000000007421046102023000144540ustar 00000000000000[package] name = "ogg_pager" version = "0.7.0" authors = ["Serial <69764315+Serial-ATA@users.noreply.github.com>"] edition = "2021" license = "MIT OR Apache-2.0" description = "A simple OGG page reader" repository = "https://github.com/Serial-ATA/lofty-rs" keywords = ["ogg", "xiph"] categories = ["multimedia", "multimedia::audio", "parser-implementations"] include = ["src", "Cargo.toml", "../LICENSE-*"] [dependencies] byteorder = { workspace = true } [lints] workspace = true ogg_pager-0.7.0/README.md000064400000000000000000000007071046102023000130450ustar 00000000000000# OGG pager [![Downloads](https://img.shields.io/crates/d/ogg_pager?style=for-the-badge&logo=rust)](https://crates.io/crates/ogg_pager) [![Version](https://img.shields.io/crates/v/ogg_pager?style=for-the-badge&logo=rust)](https://crates.io/crates/ogg_pager) [![Documentation](https://img.shields.io/badge/docs.rs-ogg_pager-informational?style=for-the-badge&logo=read-the-docs)](https://docs.rs/ogg_pager/) A simple OGG page reader, creator, and paginatorogg_pager-0.7.0/src/crc.rs000064400000000000000000000071411046102023000134710ustar 00000000000000#[rustfmt::skip] static CRC_LOOKUP_ARRAY: [u32; 256] = [ 0x0000_0000, 0x04C1_1DB7, 0x0982_3B6E, 0x0D43_26D9, 0x1304_76DC, 0x17C5_6B6B, 0x1A86_4DB2, 0x1E47_5005, 0x2608_EDB8, 0x22C9_F00F, 0x2F8A_D6D6, 0x2B4B_CB61, 0x350C_9B64, 0x31CD_86D3, 0x3C8E_A00A, 0x384F_BDBD, 0x4C11_DB70, 0x48D0_C6C7, 0x4593_E01E, 0x4152_FDA9, 0x5F15_ADAC, 0x5BD4_B01B, 0x5697_96C2, 0x5256_8B75, 0x6A19_36C8, 0x6ED8_2B7F, 0x639B_0DA6, 0x675A_1011, 0x791D_4014, 0x7DDC_5DA3, 0x709F_7B7A, 0x745E_66CD, 0x9823_B6E0, 0x9CE2_AB57, 0x91A1_8D8E, 0x9560_9039, 0x8B27_C03C, 0x8FE6_DD8B, 0x82A5_FB52, 0x8664_E6E5, 0xBE2B_5B58, 0xBAEA_46EF, 0xB7A9_6036, 0xB368_7D81, 0xAD2F_2D84, 0xA9EE_3033, 0xA4AD_16EA, 0xA06C_0B5D, 0xD432_6D90, 0xD0F3_7027, 0xDDB0_56FE, 0xD971_4B49, 0xC736_1B4C, 0xC3F7_06FB, 0xCEB4_2022, 0xCA75_3D95, 0xF23A_8028, 0xF6FB_9D9F, 0xFBB8_BB46, 0xFF79_A6F1, 0xE13E_F6F4, 0xE5FF_EB43, 0xE8BC_CD9A, 0xEC7D_D02D, 0x3486_7077, 0x3047_6DC0, 0x3D04_4B19, 0x39C5_56AE, 0x2782_06AB, 0x2343_1B1C, 0x2E00_3DC5, 0x2AC1_2072, 0x128E_9DCF, 0x164F_8078, 0x1B0C_A6A1, 0x1FCD_BB16, 0x018A_EB13, 0x054B_F6A4, 0x0808_D07D, 0x0CC9_CDCA, 0x7897_AB07, 0x7C56_B6B0, 0x7115_9069, 0x75D4_8DDE, 0x6B93_DDDB, 0x6F52_C06C, 0x6211_E6B5, 0x66D0_FB02, 0x5E9F_46BF, 0x5A5E_5B08, 0x571D_7DD1, 0x53DC_6066, 0x4D9B_3063, 0x495A_2DD4, 0x4419_0B0D, 0x40D8_16BA, 0xACA5_C697, 0xA864_DB20, 0xA527_FDF9, 0xA1E6_E04E, 0xBFA1_B04B, 0xBB60_ADFC, 0xB623_8B25, 0xB2E2_9692, 0x8AAD_2B2F, 0x8E6C_3698, 0x832F_1041, 0x87EE_0DF6, 0x99A9_5DF3, 0x9D68_4044, 0x902B_669D, 0x94EA_7B2A, 0xE0B4_1DE7, 0xE475_0050, 0xE936_2689, 0xEDF7_3B3E, 0xF3B0_6B3B, 0xF771_768C, 0xFA32_5055, 0xFEF3_4DE2, 0xC6BC_F05F, 0xC27D_EDE8, 0xCF3E_CB31, 0xCBFF_D686, 0xD5B8_8683, 0xD179_9B34, 0xDC3A_BDED, 0xD8FB_A05A, 0x690C_E0EE, 0x6DCD_FD59, 0x608E_DB80, 0x644F_C637, 0x7A08_9632, 0x7EC9_8B85, 0x738A_AD5C, 0x774B_B0EB, 0x4F04_0D56, 0x4BC5_10E1, 0x4686_3638, 0x4247_2B8F, 0x5C00_7B8A, 0x58C1_663D, 0x5582_40E4, 0x5143_5D53, 0x251D_3B9E, 0x21DC_2629, 0x2C9F_00F0, 0x285E_1D47, 0x3619_4D42, 0x32D8_50F5, 0x3F9B_762C, 0x3B5A_6B9B, 0x0315_D626, 0x07D4_CB91, 0x0A97_ED48, 0x0E56_F0FF, 0x1011_A0FA, 0x14D0_BD4D, 0x1993_9B94, 0x1D52_8623, 0xF12F_560E, 0xF5EE_4BB9, 0xF8AD_6D60, 0xFC6C_70D7, 0xE22B_20D2, 0xE6EA_3D65, 0xEBA9_1BBC, 0xEF68_060B, 0xD727_BBB6, 0xD3E6_A601, 0xDEA5_80D8, 0xDA64_9D6F, 0xC423_CD6A, 0xC0E2_D0DD, 0xCDA1_F604, 0xC960_EBB3, 0xBD3E_8D7E, 0xB9FF_90C9, 0xB4BC_B610, 0xB07D_ABA7, 0xAE3A_FBA2, 0xAAFB_E615, 0xA7B8_C0CC, 0xA379_DD7B, 0x9B36_60C6, 0x9FF7_7D71, 0x92B4_5BA8, 0x9675_461F, 0x8832_161A, 0x8CF3_0BAD, 0x81B0_2D74, 0x8571_30C3, 0x5D8A_9099, 0x594B_8D2E, 0x5408_ABF7, 0x50C9_B640, 0x4E8E_E645, 0x4A4F_FBF2, 0x470C_DD2B, 0x43CD_C09C, 0x7B82_7D21, 0x7F43_6096, 0x7200_464F, 0x76C1_5BF8, 0x6886_0BFD, 0x6C47_164A, 0x6104_3093, 0x65C5_2D24, 0x119B_4BE9, 0x155A_565E, 0x1819_7087, 0x1CD8_6D30, 0x029F_3D35, 0x065E_2082, 0x0B1D_065B, 0x0FDC_1BEC, 0x3793_A651, 0x3352_BBE6, 0x3E11_9D3F, 0x3AD0_8088, 0x2497_D08D, 0x2056_CD3A, 0x2D15_EBE3, 0x29D4_F654, 0xC5A9_2679, 0xC168_3BCE, 0xCC2B_1D17, 0xC8EA_00A0, 0xD6AD_50A5, 0xD26C_4D12, 0xDF2F_6BCB, 0xDBEE_767C, 0xE3A1_CBC1, 0xE760_D676, 0xEA23_F0AF, 0xEEE2_ED18, 0xF0A5_BD1D, 0xF464_A0AA, 0xF927_8673, 0xFDE6_9BC4, 0x89B8_FD09, 0x8D79_E0BE, 0x803A_C667, 0x84FB_DBD0, 0x9ABC_8BD5, 0x9E7D_9662, 0x933E_B0BB, 0x97FF_AD0C, 0xAFB0_10B1, 0xAB71_0D06, 0xA632_2BDF, 0xA2F3_3668, 0xBCB4_666D, 0xB875_7BDA, 0xB536_5D03, 0xB1F7_40B4, ]; /// Generates a CRC checksum based on the content #[must_use] pub fn crc32(page: &[u8]) -> u32 { let mut crc: u32 = 0; for p in page { crc = (crc << 8) ^ CRC_LOOKUP_ARRAY[(u32::from(*p) ^ (crc >> 24)) as usize]; } crc } ogg_pager-0.7.0/src/error.rs000064400000000000000000000030141046102023000140460ustar 00000000000000use std::error::Error; use std::fmt; /// Alias for `Result` pub type Result = std::result::Result; /// Errors that can occur while performing `Page` operations #[derive(Debug)] pub enum PageError { /// The reader contains a page with a nonzero version InvalidVersion, /// The reader contains a page with a segment count < 1 BadSegmentCount, /// The reader contains a page without a magic signature (OggS) MissingMagic, /// The reader contains too much data for a single page TooMuchData, /// The reader contains too little data to extract the expected information NotEnoughData, /// Any std::io::Error Io(std::io::Error), } impl fmt::Display for PageError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { PageError::InvalidVersion => { write!(f, "Invalid stream structure version (Should always be 0)") }, PageError::BadSegmentCount => write!(f, "Page has a segment count < 1"), PageError::MissingMagic => write!(f, "Page is missing a magic signature"), PageError::TooMuchData => write!(f, "Too much data was provided"), PageError::NotEnoughData => { write!(f, "Too little data is available for the expected read") }, PageError::Io(err) => write!(f, "{}", err), } } } impl Error for PageError { fn source(&self) -> Option<&(dyn Error + 'static)> { match *self { PageError::Io(ref e) => Some(e), _ => None, } } } impl From for PageError { fn from(err: std::io::Error) -> PageError { PageError::Io(err) } } ogg_pager-0.7.0/src/header.rs000064400000000000000000000046701046102023000141560ustar 00000000000000use crate::{PageError, Result}; use std::io::{Read, Seek}; use byteorder::{LittleEndian, ReadBytesExt}; /// The size of an OGG page header pub const PAGE_HEADER_SIZE: usize = 27; /// An OGG page header #[derive(Clone, PartialEq, Eq, Debug)] pub struct PageHeader { /// The position in the stream the page started at pub start: u64, pub(crate) header_type_flag: u8, /// The page's absolute granule position pub abgp: u64, /// The page's stream serial number pub stream_serial: u32, /// The page's sequence number pub sequence_number: u32, pub(crate) segments: Vec, pub(crate) checksum: u32, } impl PageHeader { /// Creates a new `PageHeader` #[must_use] pub const fn new( header_type_flag: u8, abgp: u64, stream_serial: u32, sequence_number: u32, ) -> Self { Self { start: 0, header_type_flag, abgp, stream_serial, sequence_number, segments: Vec::new(), checksum: 0, } } /// Reads a `PageHeader` from a reader /// /// # Errors /// /// * [`PageError::MissingMagic`] /// * [`PageError::InvalidVersion`] /// * [`PageError::BadSegmentCount`] /// * Reader does not have enough data pub fn read(data: &mut R) -> Result where R: Read + Seek, { let start = data.stream_position()?; let mut sig = [0; 4]; data.read_exact(&mut sig)?; if &sig != b"OggS" { return Err(PageError::MissingMagic); } // Version, always 0 let version = data.read_u8()?; if version != 0 { return Err(PageError::InvalidVersion); } let header_type_flag = data.read_u8()?; let abgp = data.read_u64::()?; let stream_serial = data.read_u32::()?; let sequence_number = data.read_u32::()?; let checksum = data.read_u32::()?; let segments = data.read_u8()?; if segments < 1 { return Err(PageError::BadSegmentCount); } let mut segment_table = vec![0; segments as usize]; data.read_exact(&mut segment_table)?; let header = Self { start, header_type_flag, abgp, stream_serial, sequence_number, segments: segment_table, checksum, }; Ok(header) } /// Returns the size of the page content, excluding the header pub fn content_size(&self) -> usize { self.segments.iter().map(|&b| usize::from(b)).sum::() } /// Returns the page's header type flag pub fn header_type_flag(&self) -> u8 { self.header_type_flag } /// Returns the page's checksum pub fn checksum(&self) -> u32 { self.checksum } } ogg_pager-0.7.0/src/lib.rs000064400000000000000000000156011046102023000134700ustar 00000000000000//! A simple OGG page reader mod crc; mod error; mod header; mod packets; mod paginate; use std::io::{Read, Seek}; pub use crc::crc32; pub use error::{PageError, Result}; pub use header::{PageHeader, PAGE_HEADER_SIZE}; pub use packets::{Packets, PacketsIter}; pub use paginate::paginate; const CONTINUED_PACKET: u8 = 0x01; pub(crate) const MAX_WRITTEN_SEGMENT_COUNT: usize = 32; pub(crate) const MAX_WRITTEN_CONTENT_SIZE: usize = MAX_WRITTEN_SEGMENT_COUNT * 255; /// The maximum page content size // NOTE: An OGG page can have up to 255 segments, or ~64KB. We cap it at 32 segments, or ~8KB when writing. pub const MAX_CONTENT_SIZE: usize = MAX_WRITTEN_CONTENT_SIZE * 4; /// The maximum number of segments a page can contain pub const MAX_SEGMENT_COUNT: usize = 255; /// The packet contains the first page of the logical bitstream pub const CONTAINS_FIRST_PAGE_OF_BITSTREAM: u8 = 0x02; /// The packet contains the last page of the logical bitstream pub const CONTAINS_LAST_PAGE_OF_BITSTREAM: u8 = 0x04; /// An OGG page #[derive(Clone, PartialEq, Eq, Debug)] pub struct Page { content: Vec, header: PageHeader, /// The position in the stream the page ended pub end: u64, } impl Page { /// Returns a reference to the `Page`'s header pub fn header(&self) -> &PageHeader { &self.header } /// Returns a mutable reference to the `Page`'s header pub fn header_mut(&mut self) -> &mut PageHeader { &mut self.header } /// Convert the Page to bytes for writing /// /// NOTE: This will write the checksum as is. It is likely [`Page::gen_crc`] will have /// to be used prior. #[must_use] pub fn as_bytes(&self) -> Vec { let segment_table = &self.header.segments; let num_segments = segment_table.len(); let mut bytes = Vec::with_capacity(PAGE_HEADER_SIZE + num_segments + self.header.content_size()); bytes.extend(b"OggS"); bytes.push(0); // Version bytes.push(self.header.header_type_flag); bytes.extend(self.header.abgp.to_le_bytes()); bytes.extend(self.header.stream_serial.to_le_bytes()); bytes.extend(self.header.sequence_number.to_le_bytes()); bytes.extend(self.header.checksum.to_le_bytes()); bytes.push(num_segments as u8); bytes.extend(segment_table); bytes.extend(self.content.iter()); bytes } /// Attempts to get a Page from a reader /// /// # Errors /// /// * [`std::io::Error`] /// * [`PageError`] pub fn read(data: &mut V) -> Result where V: Read + Seek, { let header = PageHeader::read(data)?; let mut content = vec![0; header.content_size()]; data.read_exact(&mut content)?; let end = data.stream_position()?; Ok(Page { content, header, end, }) } /// Generates the CRC checksum of the page pub fn gen_crc(&mut self) { // The value is computed over the entire header (with the CRC field in the header set to zero) and then continued over the page self.header.checksum = 0; self.header.checksum = crc::crc32(&self.as_bytes()); } /// Returns the page's content pub fn content(&self) -> &[u8] { self.content.as_slice() } /// Consumes the page and returns its content #[must_use] pub fn take_content(self) -> Vec { self.content } } #[cfg(test)] mod tests { use crate::{paginate, Page, PageHeader}; use std::io::Cursor; pub fn segment_table(length: usize) -> Vec { if length == 0 { return vec![1, 0]; } let last_len = (length % 255) as u8; let needed = (length / 255) + 1; let mut segments = Vec::with_capacity(needed); for i in 0..needed { if i + 1 < needed { segments.push(255); } else { segments.push(last_len); } } segments } #[test] fn opus_ident_header() { let expected = Page { content: vec![ 0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x02, 0x38, 0x01, 0x80, 0xBB, 0, 0, 0, 0, 0, ], header: PageHeader { start: 0, header_type_flag: 2, abgp: 0, stream_serial: 1_759_377_061, sequence_number: 0, segments: vec![19], checksum: 3_579_522_525, }, end: 47, }; let content = std::fs::read("test_assets/opus_ident_header.page").unwrap(); let page = Page::read(&mut Cursor::new(content)).unwrap(); assert_eq!(expected, page); } #[test] fn paginate_large() { let packet = std::fs::read("test_assets/large_comment_packet.page").unwrap(); let pages = paginate([packet.as_slice()], 1234, 0, 0).unwrap(); let len = pages.len(); assert_eq!(len, 130); let last_page_content = pages.last().unwrap().content(); assert_eq!( last_page_content.len() % 255, *segment_table(last_page_content.len()).last().unwrap() as usize ); for (i, page) in pages.into_iter().enumerate() { let header = page.header(); assert_eq!(header.stream_serial, 1234); if i + 1 == len { assert_eq!(header.abgp, 0); } else { // -1 assert_eq!(header.abgp, u64::MAX); } assert_eq!(header.sequence_number, i as u32); if i == 0 { assert_eq!(header.header_type_flag, 0); } else { assert_eq!(header.header_type_flag, 1); } if i + 1 == len { let segments = &header.segments[..header.segments.len()]; for s in &segments[..segments.len() - 1] { assert_eq!(*s, 255); } assert_eq!(segments.last(), Some(&171)); } else { assert_eq!(header.segments, vec![255; super::MAX_WRITTEN_SEGMENT_COUNT]); } } } #[test] fn paginate_large_perfectly_divisible() { // Create 20 max-size pages. // This means we will need to make another page with a single zero segment table. const PAGES_TO_WRITE: usize = 20; const PACKET_SIZE: usize = (super::MAX_WRITTEN_SEGMENT_COUNT * 255) * PAGES_TO_WRITE; let packet = vec![0; PACKET_SIZE]; let pages = paginate([packet.as_slice()], 1234, 0, 0).unwrap(); let len = pages.len(); assert_eq!(len, PAGES_TO_WRITE + 1); for (i, page) in pages.iter().enumerate() { if i + 1 == len { break; } assert!(page.header.segments.iter().all(|c| *c == 255)); } let last = pages.last().unwrap(); assert_eq!(last.header.segments.len(), 1); assert_eq!(*last.header.segments.first().unwrap(), 0); let mut total_size = 0; for page in pages { total_size += page .header .segments .iter() .map(|&b| usize::from(b)) .sum::(); } assert_eq!(total_size, PACKET_SIZE); } #[test] fn paginate_perfectly_divisible_terminate() { // Our segment table will have 17 max-size segments, it should be terminated with a 0 to // indicate the end of our packet. const SEGMENTS: usize = 17; const PACKET_SIZE: usize = SEGMENTS * 255; let packet = vec![0; PACKET_SIZE]; let pages = paginate([packet.as_slice()], 1234, 0, 0).unwrap(); let len = pages.len(); assert_eq!(len, 1); let page = &pages[0]; // + 1 for the terminating 0 assert_eq!(page.header.segments.len(), SEGMENTS + 1); let correct_number_of_segments = page .header .segments .iter() .take(SEGMENTS) .all(|&b| b == 255); assert!(correct_number_of_segments); assert_eq!(*page.header.segments.last().unwrap(), 0); } } ogg_pager-0.7.0/src/packets.rs000064400000000000000000000264671046102023000143700ustar 00000000000000use crate::error::{PageError, Result}; use crate::header::PageHeader; use crate::paginate::paginate; use crate::Page; use std::fmt::{Debug, Formatter}; use std::io::{Read, Seek, Write}; /// A container for packets in an OGG file pub struct Packets { content: Vec, packet_sizes: Vec, } impl Packets { /// Read as many packets as possible from a reader /// /// # Errors /// /// A page has a bad length /// /// # Examples /// /// ```rust /// use ogg_pager::Packets; /// /// # fn main() -> Result<(), ogg_pager::PageError> { /// # let path = "../lofty/tests/files/assets/minimal/full_test.ogg"; /// let mut file = std::fs::File::open(path)?; /// /// let packets = Packets::read(&mut file)?; /// # Ok(()) } /// ``` pub fn read(data: &mut R) -> Result where R: Read + Seek, { Self::read_count(data, -1) } /// Read a specific number of packets from a reader /// /// A special value of `-1` will read as many packets as possible, /// in which case [`Packets::read`] should be used. /// /// NOTE: Any value 0 or below will return an empty [`Packets`] /// /// # Errors /// /// * Unable to read the specified number of packets /// * A page has a bad length /// /// # Examples /// /// ```rust /// use ogg_pager::Packets; /// /// # fn main() -> Result<(), ogg_pager::PageError> { /// # let path = "../lofty/tests/files/assets/minimal/full_test.ogg"; /// let mut file = std::fs::File::open(path)?; /// /// // We know that the file has at least 2 packets in it /// let packets = Packets::read_count(&mut file, 2)?; /// # Ok(()) } /// ``` #[allow(clippy::read_zero_byte_vec)] pub fn read_count(data: &mut R, count: isize) -> Result where R: Read + Seek, { let mut content = Vec::new(); let mut packet_sizes = Vec::new(); if count == 0 || count < -1 { return Ok(Self { content, packet_sizes, }); } let mut read = 0; let mut packet_size = 0_u64; let mut packet_bytes_already_read = None; let mut current_packet_content; 'outer: loop { if let Ok(header) = PageHeader::read(data) { for i in header.segments { packet_size += u64::from(i); if i < 255 { if count != -1 { read += 1; } let byte_count_to_read = Self::get_byte_count_to_read( packet_size, &mut packet_bytes_already_read, ); current_packet_content = vec![0; byte_count_to_read as usize]; data.read_exact(&mut current_packet_content)?; packet_sizes.push(packet_size); packet_size = 0; packet_bytes_already_read = None; content.append(&mut current_packet_content); if read == count { break 'outer; } } } // The packet continues on the next page, write what we can so far if packet_size != 0 { let byte_count_to_read = Self::get_byte_count_to_read(packet_size, &mut packet_bytes_already_read); current_packet_content = vec![0; byte_count_to_read as usize]; data.read_exact(&mut current_packet_content)?; content.append(&mut current_packet_content); } continue; } break; } if count != -1 && packet_sizes.len() != count as usize { return Err(PageError::NotEnoughData); } Ok(Self { content, packet_sizes, }) } fn get_byte_count_to_read( packet_size: u64, packet_bytes_already_read: &mut Option, ) -> u64 { let byte_count_to_read; match packet_bytes_already_read { Some(already_read_bytes_count) => { byte_count_to_read = packet_size - *already_read_bytes_count; *packet_bytes_already_read = Some(*already_read_bytes_count + byte_count_to_read); }, None => { byte_count_to_read = packet_size; *packet_bytes_already_read = Some(packet_size); }, }; byte_count_to_read } /// Returns the number of packets /// /// # Examples /// /// ```rust /// use ogg_pager::Packets; /// /// # fn main() -> Result<(), ogg_pager::PageError> { /// # let path = "../lofty/tests/files/assets/minimal/full_test.ogg"; /// let mut file = std::fs::File::open(path)?; /// /// // I want to read 2 packets /// let packets = Packets::read_count(&mut file, 2)?; /// /// // And that's what I received! /// assert_eq!(packets.len(), 2); /// # Ok(()) } /// ``` pub fn len(&self) -> usize { self.packet_sizes.len() } /// Returns true if there are no packets /// /// # Examples /// /// ```rust /// use ogg_pager::Packets; /// /// # fn main() -> Result<(), ogg_pager::PageError> { /// # let path = "../lofty/tests/files/assets/minimal/full_test.ogg"; /// let mut file = std::fs::File::open(path)?; /// /// let packets = Packets::read(&mut file)?; /// /// // My file contains packets! /// assert!(!packets.is_empty()); /// # Ok(()) } /// ``` pub fn is_empty(&self) -> bool { self.packet_sizes.is_empty() } /// Gets the packet at a specified index, returning its contents /// /// NOTES: /// /// * This is zero-indexed /// * If the index is out of bounds, it will return [`None`] /// /// # Examples /// /// ```rust /// use ogg_pager::Packets; /// /// # fn main() -> Result<(), ogg_pager::PageError> { /// # let path = "../lofty/tests/files/assets/minimal/full_test.ogg"; /// let mut file = std::fs::File::open(path)?; /// /// let packets = Packets::read(&mut file)?; /// /// let first_packet = packets.get(0); /// assert!(first_packet.is_some()); /// /// let out_of_bounds = packets.get(1000000); /// assert!(out_of_bounds.is_none()); /// # Ok(()) } /// ``` pub fn get(&self, idx: usize) -> Option<&[u8]> { if idx >= self.content.len() { return None; } let start_pos = match idx { // Packet 0 starts at pos 0 0 => 0, // Anything else we have to get the size of the previous packet other => self.packet_sizes[other - 1] as usize, }; if let Some(packet_size) = self.packet_sizes.get(idx) { return Some(&self.content[start_pos..start_pos + *packet_size as usize]); } None } /// Sets the packet content, if it exists /// /// NOTES: /// /// * This is zero-indexed /// * If the index is out of bounds, it will return `false` /// /// # Examples /// /// ```rust /// use ogg_pager::Packets; /// /// # fn main() -> Result<(), ogg_pager::PageError> { /// # let path = "../lofty/tests/files/assets/minimal/full_test.ogg"; /// let mut file = std::fs::File::open(path)?; /// /// let mut packets = Packets::read(&mut file)?; /// /// let new_content = [0; 100]; /// /// assert_ne!(packets.get(0), Some(new_content.as_slice())); /// /// // Set our new content /// assert!(packets.set(0, new_content)); /// /// // Now our packet contains the new content /// assert_eq!(packets.get(0), Some(new_content.as_slice())); /// /// // We cannot index out of bounds /// assert!(!packets.set(1000000, new_content)); /// # Ok(()) } /// ``` pub fn set(&mut self, idx: usize, content: impl Into>) -> bool { if idx >= self.packet_sizes.len() { return false; } let start_pos = match idx { // Packet 0 starts at pos 0 0 => 0, // Anything else we have to get the size of the previous packet other => self.packet_sizes[other - 1] as usize, }; let content = content.into(); let content_size = content.len(); let end_pos = start_pos + self.packet_sizes[idx] as usize; self.content.splice(start_pos..end_pos, content); self.packet_sizes[idx] = content_size as u64; true } /// Returns an iterator over the packets /// /// # Examples /// /// ```rust /// use ogg_pager::Packets; /// /// # fn main() -> Result<(), ogg_pager::PageError> { /// # let path = "../lofty/tests/files/assets/minimal/full_test.ogg"; /// let mut file = std::fs::File::open(path)?; /// /// let packets = Packets::read(&mut file)?; /// /// for packet in packets.iter() { /// println!("Packet size: {}", packet.len()); /// } /// # Ok(()) } pub fn iter(&self) -> PacketsIter<'_> { <&Self as IntoIterator>::into_iter(self) } /// Convert the packets into a stream of pages /// /// See [paginate()] for more information. /// /// # Errors /// /// See [`paginate()`] /// /// # Examples /// /// ```rust /// use ogg_pager::{Packets, CONTAINS_FIRST_PAGE_OF_BITSTREAM, CONTAINS_LAST_PAGE_OF_BITSTREAM}; /// /// # fn main() -> Result<(), ogg_pager::PageError> { /// # let path = "../lofty/tests/files/assets/minimal/full_test.ogg"; /// let mut file = std::fs::File::open(path)?; /// /// let mut packets = Packets::read(&mut file)?; /// /// let stream_serial_number = 1234; /// let absolute_granule_position = 0; /// let flags = CONTAINS_FIRST_PAGE_OF_BITSTREAM | CONTAINS_LAST_PAGE_OF_BITSTREAM; /// /// let pages = packets.paginate(stream_serial_number, absolute_granule_position, flags)?; /// /// println!("We created {} pages!", pages.len()); /// # Ok(()) } /// ``` pub fn paginate(&self, stream_serial: u32, abgp: u64, flags: u8) -> Result> { let mut packets = Vec::new(); let mut pos = 0; for packet_size in self.packet_sizes.iter().copied() { packets.push(&self.content[pos..pos + packet_size as usize]); pos += packet_size as usize; } paginate(packets, stream_serial, abgp, flags) } /// Write packets to a writer /// /// This will paginate and write all of the packets to a writer. /// /// # Errors /// /// * Unable to write, see [`std::io::Error`] /// /// # Examples /// /// ```rust,no_run /// use ogg_pager::{Packets, CONTAINS_FIRST_PAGE_OF_BITSTREAM, CONTAINS_LAST_PAGE_OF_BITSTREAM}; /// use std::fs::OpenOptions; /// /// # fn main() -> Result<(), ogg_pager::PageError> { /// let mut file = std::fs::File::open("foo.ogg")?; /// /// let mut packets = Packets::read(&mut file)?; /// /// let stream_serial_number = 1234; /// let absolute_granule_position = 0; /// let flags = CONTAINS_FIRST_PAGE_OF_BITSTREAM | CONTAINS_LAST_PAGE_OF_BITSTREAM; /// /// let mut new_file = OpenOptions::new().write(true).open("bar.ogg")?; /// let pages_written = packets.write_to( /// &mut new_file, /// stream_serial_number, /// absolute_granule_position, /// flags, /// )?; /// /// println!("We wrote {} pages!", pages_written); /// # Ok(()) } /// ``` pub fn write_to( &self, writer: &mut W, stream_serial: u32, abgp: u64, flags: u8, ) -> Result where W: Write, { let paginated = self.paginate(stream_serial, abgp, flags)?; let num_pages = paginated.len(); for mut page in paginated { page.gen_crc(); writer.write_all(&page.as_bytes())?; } Ok(num_pages) } } /// An iterator over packets /// /// This is created by calling `into_iter` on [`Packets`] #[derive(Clone, PartialEq, Eq, Debug)] pub struct PacketsIter<'a> { content: &'a [u8], packet_sizes: &'a [u64], cap: usize, } impl<'a> Iterator for PacketsIter<'a> { type Item = &'a [u8]; fn next(&mut self) -> Option { if self.cap == 0 { return None; } let packet_size = self.packet_sizes[0]; self.cap -= 1; self.packet_sizes = &self.packet_sizes[1..]; let (ret, remaining) = self.content.split_at(packet_size as usize); self.content = remaining; Some(ret) } } impl<'a> IntoIterator for &'a Packets { type Item = &'a [u8]; type IntoIter = PacketsIter<'a>; fn into_iter(self) -> Self::IntoIter { PacketsIter { content: &self.content, packet_sizes: &self.packet_sizes, cap: self.packet_sizes.len(), } } } impl Debug for Packets { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("Packets") .field("total_bytes", &self.content.len()) .field("count", &self.packet_sizes.len()) .finish() } } ogg_pager-0.7.0/src/paginate.rs000064400000000000000000000130171046102023000145110ustar 00000000000000use crate::error::Result; use crate::{ Page, PageHeader, CONTAINS_FIRST_PAGE_OF_BITSTREAM, CONTAINS_LAST_PAGE_OF_BITSTREAM, CONTINUED_PACKET, MAX_WRITTEN_CONTENT_SIZE, MAX_WRITTEN_SEGMENT_COUNT, }; use std::io::Read; struct PaginateContext { pages: Vec, abgp: u64, stream_serial: u32, header_flags: u8, flags: PaginateContextFlags, pos: u64, idx: usize, remaining_page_size: usize, current_packet_len: usize, last_segment_size: Option, } impl PaginateContext { fn new(abgp: u64, stream_serial: u32, header_flags: u8) -> Self { Self { pages: Vec::new(), abgp, stream_serial, header_flags, flags: PaginateContextFlags { first_page: true, fresh_packet: true, packet_finished_on_page: false, need_nil_page: false, }, pos: 0, idx: 0, remaining_page_size: MAX_WRITTEN_CONTENT_SIZE, current_packet_len: 0, last_segment_size: None, } } fn fresh_packet(&mut self, packet: &[u8]) { self.flags.fresh_packet = true; self.pos = 0; self.current_packet_len = packet.len(); self.last_segment_size = Some((packet.len() % 255) as u8); } fn flush_page(&mut self, content: &mut Vec) { let mut header = PageHeader { start: self.pos, header_type_flag: { if self.flags.first_page && self.header_flags & CONTAINS_FIRST_PAGE_OF_BITSTREAM != 0 { CONTAINS_FIRST_PAGE_OF_BITSTREAM } else if !self.flags.fresh_packet { // A packet from the previous page continues onto this page CONTINUED_PACKET } else { 0 } }, abgp: 0, stream_serial: self.stream_serial, sequence_number: self.idx as u32, segments: Vec::new(), // Calculated later checksum: 0, }; let content = core::mem::take(content); let content_len = content.len(); self.pos += content_len as u64; // Determine how many *full* segments our page content takes up. // Anything < 255 will be covered by `last_segment_size` let full_segments_occupied = content_len / 255; // Moving on to a new packet debug_assert!(self.pos <= self.current_packet_len as u64); let at_packet_end = self.pos == self.current_packet_len as u64; if at_packet_end && full_segments_occupied == MAX_WRITTEN_SEGMENT_COUNT { // See comment on `PaginateContextFlags.need_nil_page` self.flags.need_nil_page = true; self.flags.packet_finished_on_page = false; } if self.flags.packet_finished_on_page { header.abgp = self.abgp; } else { // A special value of '-1' (in two's complement) indicates that no packets // finish on this page. header.abgp = 1_u64.wrapping_neg() } debug_assert!(full_segments_occupied <= MAX_WRITTEN_SEGMENT_COUNT); header.segments = vec![255; full_segments_occupied]; if full_segments_occupied != MAX_WRITTEN_SEGMENT_COUNT { // End of the packet let last_segment_size = self .last_segment_size .expect("fresh packet should be indicated at this point"); header.segments.push(last_segment_size); } self.pages.push(Page { content, header, end: self.pos, }); self.idx += 1; self.flags.packet_finished_on_page = false; self.remaining_page_size = MAX_WRITTEN_CONTENT_SIZE; } } struct PaginateContextFlags { first_page: bool, fresh_packet: bool, packet_finished_on_page: bool, // A 'nil' page just means it is zero-length. This is used when our packet is perfectly // divisible by `255 * MAX_SEGMENT_COUNT`. We need a zero-sized segment to mark the end of our // packet across page boundaries. // // Very rare circumstance, but still possible. // // From : // "Note also that a 'nil' (zero length) packet is not an error; it consists of nothing more than a lacing value of zero in the header." need_nil_page: bool, } /// Create pages from a list of packets /// /// # Errors /// /// * Unable to read packet content /// /// # Example /// /// ```rust,ignore /// use ogg_pager::paginate; /// /// // Creating the comment header /// let comment_header_packet = vec![...]; /// let stream_serial_number = 2784419176; /// /// let pages = paginate(&comment_header_packet, stream_serial_number, 0, 0); /// ``` pub fn paginate<'a, I>(packets: I, stream_serial: u32, abgp: u64, flags: u8) -> Result> where I: IntoIterator + 'a, { let mut ctx = PaginateContext::new(abgp, stream_serial, flags); for packet in packets { ctx.fresh_packet(packet); paginate_packet(&mut ctx, packet)?; } if flags & CONTAINS_LAST_PAGE_OF_BITSTREAM == 0x04 { if let Some(last) = ctx.pages.last_mut() { last.header.header_type_flag |= CONTAINS_LAST_PAGE_OF_BITSTREAM; } } Ok(ctx.pages) } fn paginate_packet(ctx: &mut PaginateContext, packet: &[u8]) -> Result<()> { let mut page_content = Vec::with_capacity(MAX_WRITTEN_CONTENT_SIZE); let mut packet = packet; loop { // See comment on `PaginateContextFlags.need_nil_page` if ctx.flags.need_nil_page { assert!(packet.is_empty()); ctx.flags.packet_finished_on_page = true; ctx.flush_page(&mut page_content); } if packet.is_empty() { break; } let bytes_read = packet .take(ctx.remaining_page_size as u64) .read_to_end(&mut page_content)?; ctx.remaining_page_size -= bytes_read; packet = &packet[bytes_read..]; if bytes_read <= MAX_WRITTEN_CONTENT_SIZE && packet.is_empty() { ctx.flags.packet_finished_on_page = true; } if ctx.remaining_page_size == 0 || packet.is_empty() { ctx.flush_page(&mut page_content); } ctx.flags.first_page = false; ctx.flags.fresh_packet = false; } // Flush any content leftover if !page_content.is_empty() { ctx.flush_page(&mut page_content); } Ok(()) }