use 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<u8>,
packet_sizes: Vec<u64>,
}
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 = "../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<R>(data: &mut R) -> Result<Self>
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 = "../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<R>(data: &mut R, count: isize) -> Result<Self>
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 += i as u64;
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>,
) -> 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 = "../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 = "../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_eq!(!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 = "../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 = "../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));
///
/// // 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));
///
/// // We cannot index out of bounds
/// assert!(!packets.set(1000000, new_content));
/// # Ok(()) }
/// ```
pub fn set(&mut self, idx: usize, content: impl Into<Vec<u8>>) -> 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
}
/// Convert the packets into a stream of pages
///
/// See [paginate()] for more information.
///
/// # Examples
///
/// ```rust
/// use ogg_pager::{Packets, CONTAINS_FIRST_PAGE_OF_BITSTREAM, CONTAINS_LAST_PAGE_OF_BITSTREAM};
///
/// # fn main() -> Result<(), ogg_pager::PageError> {
/// # let path = "../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<Vec<Page>> {
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.
///
/// # 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<W>(
&self,
writer: &mut W,
stream_serial: u32,
abgp: u64,
flags: u8,
) -> Result<usize>
where
W: Write,
{
let paginated = self.paginate(stream_serial, abgp, flags)?;
let num_pages = paginated.len();
for mut page in paginated.into_iter() {
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<Self::Item> {
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()
}
}