mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-12-12 21:52:33 +00:00
OGG: Read entire packets instead of pages
This makes the handling of OGG files a lot more spec-compliant, and simpler overall.
This commit is contained in:
parent
a08088ddf8
commit
8051fafbc8
14 changed files with 433 additions and 214 deletions
|
@ -24,7 +24,7 @@ lofty_attr = { path = "lofty_attr" }
|
|||
# Debug logging
|
||||
log = "0.4.17"
|
||||
# OGG Vorbis/Opus
|
||||
ogg_pager = "0.3.2"
|
||||
ogg_pager = { path = "ogg_pager" }
|
||||
# Key maps
|
||||
once_cell = "1.13.0"
|
||||
paste = "1.0.7"
|
||||
|
|
|
@ -15,6 +15,8 @@ pub enum PageError {
|
|||
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),
|
||||
}
|
||||
|
@ -28,6 +30,9 @@ impl fmt::Display for PageError {
|
|||
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),
|
||||
}
|
||||
}
|
||||
|
|
94
ogg_pager/src/header.rs
Normal file
94
ogg_pager/src/header.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
use crate::{PageError, Result};
|
||||
|
||||
use std::io::{Read, Seek};
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
|
||||
// An OGG page header
|
||||
#[derive(Copy, 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) checksum: u32,
|
||||
}
|
||||
|
||||
impl PageHeader {
|
||||
pub 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,
|
||||
checksum: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read<R>(data: &mut R) -> Result<(Self, Vec<u8>)>
|
||||
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::<LittleEndian>()?;
|
||||
let stream_serial = data.read_u32::<LittleEndian>()?;
|
||||
let sequence_number = data.read_u32::<LittleEndian>()?;
|
||||
let checksum = data.read_u32::<LittleEndian>()?;
|
||||
|
||||
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,
|
||||
checksum,
|
||||
};
|
||||
|
||||
Ok((header, segment_table))
|
||||
}
|
||||
|
||||
/// Returns the page's header type flag
|
||||
pub fn header_type_flag(&self) -> u8 {
|
||||
self.header_type_flag
|
||||
}
|
||||
|
||||
/// Returns the page's checksum
|
||||
///
|
||||
/// NOTE: This will not generate a new CRC. It will return
|
||||
/// the CRC as-is. Use [`Page::gen_crc`] to generate a new one.
|
||||
pub fn checksum(&self) -> u32 {
|
||||
self.checksum
|
||||
}
|
||||
}
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
mod crc;
|
||||
mod error;
|
||||
mod header;
|
||||
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
|
||||
pub use crc::crc32;
|
||||
pub use error::{PageError, Result};
|
||||
pub use header::PageHeader;
|
||||
|
||||
const CONTINUED_PACKET: u8 = 0x01;
|
||||
|
||||
|
@ -23,16 +23,7 @@ pub const CONTAINS_LAST_PAGE_OF_BITSTREAM: u8 = 0x04;
|
|||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct Page {
|
||||
content: Vec<u8>,
|
||||
header_type: u8,
|
||||
/// The page's absolute granule position
|
||||
pub abgp: u64,
|
||||
/// The page's stream serial number
|
||||
pub serial: u32,
|
||||
/// The page's sequence number
|
||||
pub seq_num: u32,
|
||||
checksum: u32,
|
||||
/// The position in the stream the page started at
|
||||
pub start: u64,
|
||||
header: PageHeader,
|
||||
/// The position in the stream the page ended
|
||||
pub end: u64,
|
||||
}
|
||||
|
@ -66,13 +57,7 @@ impl Page {
|
|||
/// ident_header_packet,
|
||||
/// );
|
||||
/// ```
|
||||
pub fn new(
|
||||
header_type_flag: u8,
|
||||
abgp: u64,
|
||||
stream_serial: u32,
|
||||
sequence_number: u32,
|
||||
content: Vec<u8>,
|
||||
) -> Result<Self> {
|
||||
pub fn new(header: PageHeader, content: Vec<u8>) -> Result<Self> {
|
||||
let content_len = content.len();
|
||||
|
||||
if content_len > MAX_CONTENT_SIZE {
|
||||
|
@ -81,16 +66,19 @@ impl Page {
|
|||
|
||||
Ok(Self {
|
||||
content,
|
||||
header_type: header_type_flag,
|
||||
abgp,
|
||||
serial: stream_serial,
|
||||
seq_num: sequence_number,
|
||||
checksum: 0,
|
||||
start: 0,
|
||||
header,
|
||||
end: content_len as u64,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn header(&self) -> &PageHeader {
|
||||
&self.header
|
||||
}
|
||||
|
||||
pub fn header_mut(&mut self) -> &mut PageHeader {
|
||||
&mut self.header
|
||||
}
|
||||
|
||||
/// Convert the Page to Vec<u8> for writing
|
||||
///
|
||||
/// NOTE: This will write the checksum as is. It is likely [`Page::gen_crc`] will have
|
||||
|
@ -105,11 +93,11 @@ impl Page {
|
|||
|
||||
bytes.extend(b"OggS");
|
||||
bytes.push(0); // Version
|
||||
bytes.push(self.header_type);
|
||||
bytes.extend(self.abgp.to_le_bytes());
|
||||
bytes.extend(self.serial.to_le_bytes());
|
||||
bytes.extend(self.seq_num.to_le_bytes());
|
||||
bytes.extend(self.checksum.to_le_bytes());
|
||||
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(segment_table.len() as u8);
|
||||
bytes.append(&mut segment_table);
|
||||
bytes.extend(self.content.iter());
|
||||
|
@ -129,37 +117,7 @@ impl Page {
|
|||
where
|
||||
V: 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 = data.read_u8()?;
|
||||
|
||||
let abgp = data.read_u64::<LittleEndian>()?;
|
||||
let serial = data.read_u32::<LittleEndian>()?;
|
||||
let seq_num = data.read_u32::<LittleEndian>()?;
|
||||
let checksum = data.read_u32::<LittleEndian>()?;
|
||||
|
||||
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, segment_table) = PageHeader::read(data)?;
|
||||
|
||||
let mut content: Vec<u8> = Vec::new();
|
||||
let content_len: u16 = segment_table.iter().map(|&b| u16::from(b)).sum();
|
||||
|
@ -175,12 +133,7 @@ impl Page {
|
|||
|
||||
Ok(Page {
|
||||
content,
|
||||
header_type,
|
||||
abgp,
|
||||
serial,
|
||||
seq_num,
|
||||
checksum,
|
||||
start,
|
||||
header,
|
||||
end,
|
||||
})
|
||||
}
|
||||
|
@ -191,7 +144,7 @@ impl Page {
|
|||
///
|
||||
/// See [`Page::as_bytes`]
|
||||
pub fn gen_crc(&mut self) -> Result<()> {
|
||||
self.checksum = crc::crc32(&self.as_bytes()?);
|
||||
self.header.checksum = crc::crc32(&self.as_bytes()?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -220,19 +173,21 @@ impl Page {
|
|||
let remaining = 65025 - self_len;
|
||||
|
||||
self.content.extend(content[0..remaining].iter());
|
||||
self.header_type = 0;
|
||||
self.abgp = 1_u64.wrapping_neg(); // -1 in two's complement indicates that no packets finish on this page
|
||||
self.header.header_type_flag = 0;
|
||||
self.header.abgp = 1_u64.wrapping_neg(); // -1 in two's complement indicates that no packets finish on this page
|
||||
self.end += remaining as u64;
|
||||
|
||||
let mut p = Page {
|
||||
content: content[remaining..].to_vec(),
|
||||
header_type: 1,
|
||||
abgp: 0,
|
||||
serial: self.serial,
|
||||
seq_num: self.seq_num + 1,
|
||||
checksum: 0,
|
||||
start: self.end,
|
||||
end: self.start + content.len() as u64,
|
||||
header: PageHeader {
|
||||
start: self.end,
|
||||
header_type_flag: 1,
|
||||
abgp: 0,
|
||||
stream_serial: self.header.stream_serial,
|
||||
sequence_number: self.header.sequence_number + 1,
|
||||
checksum: 0,
|
||||
},
|
||||
end: self.header().start + content.len() as u64,
|
||||
};
|
||||
|
||||
p.gen_crc()?;
|
||||
|
@ -253,19 +208,6 @@ impl Page {
|
|||
self.content
|
||||
}
|
||||
|
||||
/// Returns the page's header type flag
|
||||
pub fn header_type(&self) -> u8 {
|
||||
self.header_type
|
||||
}
|
||||
|
||||
/// Returns the page's checksum
|
||||
///
|
||||
/// NOTE: This will not generate a new CRC. It will return
|
||||
/// the CRC as-is. Use [`Page::gen_crc`] to generate a new one.
|
||||
pub fn checksum(&self) -> u32 {
|
||||
self.checksum
|
||||
}
|
||||
|
||||
/// Returns the page's segment table
|
||||
///
|
||||
/// # Errors
|
||||
|
@ -299,22 +241,24 @@ pub fn paginate(packet: &[u8], stream_serial: u32, abgp: u64, flags: u8) -> Vec<
|
|||
for (idx, page) in packet.chunks(MAX_CONTENT_SIZE).enumerate() {
|
||||
let p = Page {
|
||||
content: page.to_vec(),
|
||||
header_type: {
|
||||
if first_page {
|
||||
if flags & CONTAINS_FIRST_PAGE_OF_BITSTREAM == 0x02 {
|
||||
CONTAINS_LAST_PAGE_OF_BITSTREAM
|
||||
header: PageHeader {
|
||||
start: pos,
|
||||
header_type_flag: {
|
||||
if first_page {
|
||||
if flags & CONTAINS_FIRST_PAGE_OF_BITSTREAM == 0x02 {
|
||||
CONTAINS_LAST_PAGE_OF_BITSTREAM
|
||||
} else {
|
||||
0
|
||||
}
|
||||
} else {
|
||||
0
|
||||
CONTINUED_PACKET
|
||||
}
|
||||
} else {
|
||||
CONTINUED_PACKET
|
||||
}
|
||||
},
|
||||
abgp,
|
||||
stream_serial,
|
||||
sequence_number: (idx + 1) as u32,
|
||||
checksum: 0,
|
||||
},
|
||||
abgp,
|
||||
serial: stream_serial,
|
||||
seq_num: (idx + 1) as u32,
|
||||
checksum: 0,
|
||||
start: pos,
|
||||
end: {
|
||||
pos += page.len() as u64;
|
||||
pos
|
||||
|
@ -327,7 +271,7 @@ pub fn paginate(packet: &[u8], stream_serial: u32, abgp: u64, flags: u8) -> Vec<
|
|||
|
||||
if flags & CONTAINS_LAST_PAGE_OF_BITSTREAM == 0x04 {
|
||||
if let Some(last) = pages.last_mut() {
|
||||
last.header_type |= CONTAINS_LAST_PAGE_OF_BITSTREAM;
|
||||
last.header.header_type_flag |= CONTAINS_LAST_PAGE_OF_BITSTREAM;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -339,7 +283,7 @@ pub fn paginate(packet: &[u8], stream_serial: u32, abgp: u64, flags: u8) -> Vec<
|
|||
break;
|
||||
}
|
||||
|
||||
p.abgp = 1_u64.wrapping_neg();
|
||||
p.header.abgp = 1_u64.wrapping_neg();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -379,9 +323,135 @@ pub fn segment_table(length: usize) -> Result<Vec<u8>> {
|
|||
Ok(segments)
|
||||
}
|
||||
|
||||
pub struct Packets {
|
||||
content: Vec<u8>,
|
||||
packet_sizes: Vec<u64>,
|
||||
}
|
||||
|
||||
impl Packets {
|
||||
pub fn read<R>(data: &mut R) -> Result<Self>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
Self::read_count(data, -1)
|
||||
}
|
||||
|
||||
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;
|
||||
'outer: loop {
|
||||
if let Ok(page) = Page::read(data, false) {
|
||||
let segment_table = page.segment_table()?;
|
||||
|
||||
let mut packet_size = 0_u64;
|
||||
for i in segment_table {
|
||||
packet_size += i as u64;
|
||||
|
||||
if i < 255 {
|
||||
if count != -1 {
|
||||
read += 1;
|
||||
}
|
||||
|
||||
content.extend_from_slice(&page.content[0..packet_size as usize]);
|
||||
packet_sizes.push(packet_size);
|
||||
packet_size = 0;
|
||||
|
||||
if read == count {
|
||||
break 'outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if count != -1 && packet_sizes.len() != read as usize {
|
||||
return Err(PageError::NotEnoughData);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
content,
|
||||
packet_sizes,
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{paginate, segment_table, Page};
|
||||
use crate::{paginate, segment_table, Page, PageHeader};
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
|
@ -391,12 +461,14 @@ mod tests {
|
|||
0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x02, 0x38, 0x01, 0x80, 0xBB,
|
||||
0, 0, 0, 0, 0,
|
||||
],
|
||||
header_type: 2,
|
||||
abgp: 0,
|
||||
serial: 1759377061,
|
||||
seq_num: 0,
|
||||
checksum: 3579522525,
|
||||
start: 0,
|
||||
header: PageHeader {
|
||||
start: 0,
|
||||
header_type_flag: 2,
|
||||
abgp: 0,
|
||||
stream_serial: 1759377061,
|
||||
sequence_number: 0,
|
||||
checksum: 3579522525,
|
||||
},
|
||||
end: 47,
|
||||
};
|
||||
|
||||
|
@ -427,21 +499,23 @@ mod tests {
|
|||
);
|
||||
|
||||
for (i, page) in pages.into_iter().enumerate() {
|
||||
assert_eq!(page.serial, 1234);
|
||||
let header = page.header;
|
||||
|
||||
assert_eq!(header.stream_serial, 1234);
|
||||
|
||||
if i + 1 == len {
|
||||
assert_eq!(page.abgp, 0);
|
||||
assert_eq!(header.abgp, 0);
|
||||
} else {
|
||||
// -1
|
||||
assert_eq!(page.abgp, u64::MAX);
|
||||
assert_eq!(header.abgp, u64::MAX);
|
||||
}
|
||||
|
||||
assert_eq!(page.seq_num, (i + 1) as u32);
|
||||
assert_eq!(header.sequence_number, (i + 1) as u32);
|
||||
|
||||
if i == 0 {
|
||||
assert_eq!(page.header_type, 0);
|
||||
assert_eq!(header.header_type_flag, 0);
|
||||
} else {
|
||||
assert_eq!(page.header_type, 1);
|
||||
assert_eq!(header.header_type_flag, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,10 +34,10 @@ pub use speex::SpeexFile;
|
|||
pub use vorbis::properties::VorbisProperties;
|
||||
pub use vorbis::VorbisFile;
|
||||
|
||||
pub(self) fn verify_signature(page: &Page, sig: &[u8]) -> Result<()> {
|
||||
pub(self) fn verify_signature(content: &[u8], sig: &[u8]) -> Result<()> {
|
||||
let sig_len = sig.len();
|
||||
|
||||
if page.content().len() < sig_len || &page.content()[..sig_len] != sig {
|
||||
if content.len() < sig_len || &content[..sig_len] != sig {
|
||||
decode_err!(@BAIL Vorbis, "File missing magic signature");
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ impl AudioFile for OpusFile {
|
|||
let file_information = super::read::read_from(reader, OPUSHEAD, OPUSTAGS)?;
|
||||
|
||||
Ok(Self {
|
||||
properties: if parse_options.read_properties { properties::read_properties(reader, &file_information.1)? } else { OpusProperties::default() },
|
||||
properties: if parse_options.read_properties { properties::read_properties(reader, file_information.1, &file_information.2)? } else { OpusProperties::default() },
|
||||
#[cfg(feature = "vorbis_comments")]
|
||||
// Safe to unwrap, a metadata packet is mandatory in Opus
|
||||
vorbis_comments_tag: file_information.0.unwrap(),
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::io::{Read, Seek, SeekFrom};
|
|||
use std::time::Duration;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use ogg_pager::Page;
|
||||
use ogg_pager::{Packets, PageHeader};
|
||||
|
||||
/// An Opus file's audio properties
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
||||
|
@ -66,35 +66,35 @@ impl OpusProperties {
|
|||
}
|
||||
}
|
||||
|
||||
pub(in crate::ogg) fn read_properties<R>(data: &mut R, first_page: &Page) -> Result<OpusProperties>
|
||||
pub(in crate::ogg) fn read_properties<R>(
|
||||
data: &mut R,
|
||||
first_page_header: PageHeader,
|
||||
packets: &Packets,
|
||||
) -> Result<OpusProperties>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let (stream_len, file_length) = {
|
||||
let current = data.stream_position()?;
|
||||
let end = data.seek(SeekFrom::End(0))?;
|
||||
data.seek(SeekFrom::Start(current))?;
|
||||
|
||||
(end - first_page.start, end)
|
||||
};
|
||||
|
||||
let mut properties = OpusProperties::default();
|
||||
|
||||
let first_page_abgp = first_page.abgp;
|
||||
let first_page_abgp = first_page_header.abgp;
|
||||
|
||||
// Safe to unwrap, it is impossible to get this far without
|
||||
// an identification packet.
|
||||
let identification_packet = packets.get(0).unwrap();
|
||||
|
||||
// Skip identification header
|
||||
let first_page_content = &mut &first_page.content()[8..];
|
||||
let identification_packet_reader = &mut &identification_packet[8..];
|
||||
|
||||
properties.version = first_page_content.read_u8()?;
|
||||
properties.channels = first_page_content.read_u8()?;
|
||||
properties.version = identification_packet_reader.read_u8()?;
|
||||
properties.channels = identification_packet_reader.read_u8()?;
|
||||
|
||||
let pre_skip = first_page_content.read_u16::<LittleEndian>()?;
|
||||
let pre_skip = identification_packet_reader.read_u16::<LittleEndian>()?;
|
||||
|
||||
properties.input_sample_rate = first_page_content.read_u32::<LittleEndian>()?;
|
||||
properties.input_sample_rate = identification_packet_reader.read_u32::<LittleEndian>()?;
|
||||
|
||||
let _output_gain = first_page_content.read_u16::<LittleEndian>()?;
|
||||
let _output_gain = identification_packet_reader.read_u16::<LittleEndian>()?;
|
||||
|
||||
let channel_mapping_family = first_page_content.read_u8()?;
|
||||
let channel_mapping_family = identification_packet_reader.read_u8()?;
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/rfc7845.html#section-5.1.1
|
||||
if (channel_mapping_family == 0 && properties.channels > 2)
|
||||
|
@ -103,18 +103,27 @@ where
|
|||
decode_err!(@BAIL Opus, "Invalid channel count for mapping family");
|
||||
}
|
||||
|
||||
// Subtract the identification and metadata packet length from the total
|
||||
let audio_size = stream_len - data.stream_position()?;
|
||||
// Get the last pages' absolute granule position
|
||||
|
||||
let last_page = find_last_page(data)?;
|
||||
let last_page_abgp = last_page.abgp;
|
||||
let last_page_abgp = last_page.header().abgp;
|
||||
|
||||
// Get the stream length
|
||||
|
||||
// Also safe to unwrap, metadata is checked prior
|
||||
let metadata_packet = packets.get(1).unwrap();
|
||||
|
||||
let header_size = identification_packet.len() + metadata_packet.len();
|
||||
|
||||
let file_length = data.seek(SeekFrom::End(0))?;
|
||||
let stream_len = file_length - header_size as u64;
|
||||
|
||||
if let Some(frame_count) = last_page_abgp.checked_sub(first_page_abgp + u64::from(pre_skip)) {
|
||||
let length = (frame_count as f64) * 1000.0 / 48000.0_f64 + 0.5;
|
||||
properties.duration = Duration::from_millis(length as u64);
|
||||
|
||||
properties.overall_bitrate = ((file_length as f64) * 8.0 / length) as u32;
|
||||
properties.audio_bitrate = ((audio_size as f64) * 8.0 / length) as u32;
|
||||
properties.audio_bitrate = ((stream_len as f64) * 8.0 / length) as u32;
|
||||
}
|
||||
|
||||
Ok(properties)
|
||||
|
|
|
@ -10,13 +10,13 @@ use std::io::{Read, Seek, SeekFrom};
|
|||
|
||||
#[cfg(feature = "vorbis_comments")]
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use ogg_pager::Page;
|
||||
use ogg_pager::{Packets, PageHeader};
|
||||
|
||||
#[cfg(feature = "vorbis_comments")]
|
||||
pub type OGGTags = (Option<VorbisComments>, Page);
|
||||
pub type OGGTags = (Option<VorbisComments>, PageHeader, Packets);
|
||||
|
||||
#[cfg(not(feature = "vorbis_comments"))]
|
||||
pub type OGGTags = (Option<()>, Page);
|
||||
pub type OGGTags = (Option<()>, PageHeader, Packets);
|
||||
|
||||
#[cfg(feature = "vorbis_comments")]
|
||||
pub(crate) fn read_comments<R>(data: &mut R, mut len: u64, tag: &mut VorbisComments) -> Result<()>
|
||||
|
@ -99,39 +99,38 @@ pub(crate) fn read_from<T>(data: &mut T, header_sig: &[u8], comment_sig: &[u8])
|
|||
where
|
||||
T: Read + Seek,
|
||||
{
|
||||
let first_page = Page::read(data, false)?;
|
||||
verify_signature(&first_page, header_sig)?;
|
||||
// TODO: Would be nice if we didn't have to read just to seek and reread immediately
|
||||
let start = data.stream_position()?;
|
||||
let (first_page_header, _) = PageHeader::read(data)?;
|
||||
|
||||
let md_page = Page::read(data, false)?;
|
||||
verify_signature(&md_page, comment_sig)?;
|
||||
data.seek(SeekFrom::Start(start))?;
|
||||
|
||||
let mut md_pages: Vec<u8> = Vec::new();
|
||||
// Read the first 3 packets, which are all headers
|
||||
let packets = Packets::read_count(data, 3)?;
|
||||
|
||||
md_pages.extend_from_slice(&md_page.content()[comment_sig.len()..]);
|
||||
let identification_packet = packets
|
||||
.get(0)
|
||||
.ok_or_else(|| decode_err!("OGG: Expected identification packet"))?;
|
||||
verify_signature(identification_packet, header_sig)?;
|
||||
|
||||
while let Ok(page) = Page::read(data, false) {
|
||||
if md_pages.len() > 125_829_120 {
|
||||
err!(TooMuchData);
|
||||
}
|
||||
let mut metadata_packet = packets
|
||||
.get(1)
|
||||
.ok_or_else(|| decode_err!("OGG: Expected comment packet"))?;
|
||||
verify_signature(metadata_packet, comment_sig)?;
|
||||
|
||||
if page.header_type() & 0x01 == 1 {
|
||||
md_pages.extend_from_slice(page.content());
|
||||
} else {
|
||||
data.seek(SeekFrom::Start(page.start))?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Remove the signature from the packet
|
||||
metadata_packet = &metadata_packet[comment_sig.len()..];
|
||||
|
||||
#[cfg(feature = "vorbis_comments")]
|
||||
{
|
||||
let mut tag = VorbisComments::default();
|
||||
|
||||
let reader = &mut &md_pages[..];
|
||||
let reader = &mut metadata_packet;
|
||||
read_comments(reader, reader.len() as u64, &mut tag)?;
|
||||
|
||||
Ok((Some(tag), first_page))
|
||||
Ok((Some(tag), first_page_header, packets))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "vorbis_comments"))]
|
||||
Ok((None, first_page))
|
||||
Ok((None, first_page_header, packets))
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ impl AudioFile for SpeexFile {
|
|||
let file_information = super::read::read_from(reader, SPEEXHEADER, &[])?;
|
||||
|
||||
Ok(Self {
|
||||
properties: if parse_options.read_properties { properties::read_properties(reader, &file_information.1)? } else { SpeexProperties::default() },
|
||||
properties: if parse_options.read_properties { properties::read_properties(reader, file_information.1, &file_information.2)? } else { SpeexProperties::default() },
|
||||
#[cfg(feature = "vorbis_comments")]
|
||||
// Safe to unwrap, a metadata packet is mandatory in Speex
|
||||
vorbis_comments_tag: file_information.0.unwrap(),
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::io::{Read, Seek, SeekFrom};
|
|||
use std::time::Duration;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use ogg_pager::Page;
|
||||
use ogg_pager::{Packets, PageHeader};
|
||||
|
||||
/// A Speex file's audio properties
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
|
||||
|
@ -84,13 +84,19 @@ impl SpeexProperties {
|
|||
}
|
||||
}
|
||||
|
||||
pub(in crate::ogg) fn read_properties<R>(data: &mut R, first_page: &Page) -> Result<SpeexProperties>
|
||||
pub(in crate::ogg) fn read_properties<R>(
|
||||
data: &mut R,
|
||||
first_page_header: PageHeader,
|
||||
packets: &Packets,
|
||||
) -> Result<SpeexProperties>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let first_page_abgp = first_page.abgp;
|
||||
// Safe to unwrap, it is impossible to get to this point without an
|
||||
// identification header.
|
||||
let identification_packet = packets.get(0).unwrap();
|
||||
|
||||
if first_page.content().len() < 80 {
|
||||
if identification_packet.len() < 80 {
|
||||
decode_err!(@BAIL Speex, "Header packet too small");
|
||||
}
|
||||
|
||||
|
@ -101,49 +107,64 @@ where
|
|||
// Skipping:
|
||||
// Speex string ("Speex ", 8)
|
||||
// Speex version (20)
|
||||
let first_page_content = &mut &first_page.content()[28..];
|
||||
let identification_packet_reader = &mut &identification_packet[28..];
|
||||
|
||||
properties.version = first_page_content.read_u32::<LittleEndian>()?;
|
||||
properties.version = identification_packet_reader.read_u32::<LittleEndian>()?;
|
||||
|
||||
// Total size of the speex header
|
||||
let _header_size = first_page_content.read_u32::<LittleEndian>()?;
|
||||
let _header_size = identification_packet_reader.read_u32::<LittleEndian>()?;
|
||||
|
||||
properties.sample_rate = first_page_content.read_u32::<LittleEndian>()?;
|
||||
properties.mode = first_page_content.read_u32::<LittleEndian>()?;
|
||||
properties.sample_rate = identification_packet_reader.read_u32::<LittleEndian>()?;
|
||||
properties.mode = identification_packet_reader.read_u32::<LittleEndian>()?;
|
||||
|
||||
// Version ID of the bitstream
|
||||
let _mode_bitstream_version = first_page_content.read_u32::<LittleEndian>()?;
|
||||
let _mode_bitstream_version = identification_packet_reader.read_u32::<LittleEndian>()?;
|
||||
|
||||
let channels = first_page_content.read_u32::<LittleEndian>()?;
|
||||
let channels = identification_packet_reader.read_u32::<LittleEndian>()?;
|
||||
|
||||
if channels != 1 && channels != 2 {
|
||||
decode_err!(@BAIL Speex, "Found invalid channel count, must be mono or stereo");
|
||||
}
|
||||
|
||||
properties.channels = channels as u8;
|
||||
properties.nominal_bitrate = first_page_content.read_i32::<LittleEndian>()?;
|
||||
properties.nominal_bitrate = identification_packet_reader.read_i32::<LittleEndian>()?;
|
||||
|
||||
// The size of the frames in samples
|
||||
let _frame_size = first_page_content.read_u32::<LittleEndian>()?;
|
||||
let _frame_size = identification_packet_reader.read_u32::<LittleEndian>()?;
|
||||
|
||||
properties.vbr = first_page_content.read_u32::<LittleEndian>()? == 1;
|
||||
|
||||
let last_page = find_last_page(data)?;
|
||||
let last_page_abgp = last_page.abgp;
|
||||
properties.vbr = identification_packet_reader.read_u32::<LittleEndian>()? == 1;
|
||||
|
||||
let last_page = find_last_page(data);
|
||||
let file_length = data.seek(SeekFrom::End(0))?;
|
||||
|
||||
if let Some(frame_count) = last_page_abgp.checked_sub(first_page_abgp) {
|
||||
// This is used for bitrate calculation, it should be the length in
|
||||
// milliseconds, but if we can't determine it then we'll just use 1000.
|
||||
let mut length = 1000;
|
||||
if let Ok(last_page) = last_page {
|
||||
let first_page_abgp = first_page_header.abgp;
|
||||
let last_page_abgp = last_page.header().abgp;
|
||||
|
||||
if properties.sample_rate > 0 {
|
||||
let length = ((frame_count as f64) * 1000.0) / f64::from(properties.sample_rate) + 0.5;
|
||||
properties.duration = Duration::from_millis(length as u64);
|
||||
let total_samples = last_page_abgp.saturating_sub(first_page_abgp);
|
||||
|
||||
properties.overall_bitrate = ((file_length as f64) * 8.0 / length) as u32;
|
||||
|
||||
// TODO: Calculate with the stream length, and make this the fallback
|
||||
properties.audio_bitrate = (properties.nominal_bitrate as u64 / 1000) as u32;
|
||||
// Best case scenario
|
||||
if total_samples > 0 {
|
||||
length = total_samples * 1000 / u64::from(properties.sample_rate);
|
||||
properties.duration = Duration::from_millis(length);
|
||||
} else {
|
||||
log::debug!(
|
||||
"Speex: The file contains invalid PCM values, unable to calculate length"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log::debug!("Speex: Sample rate = 0, unable to calculate length");
|
||||
}
|
||||
}
|
||||
|
||||
if properties.nominal_bitrate > 0 {
|
||||
properties.overall_bitrate = (file_length.saturating_mul(8) / length) as u32;
|
||||
properties.audio_bitrate = (properties.nominal_bitrate as u64 / 1000) as u32;
|
||||
}
|
||||
|
||||
Ok(properties)
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ impl AudioFile for VorbisFile {
|
|||
super::read::read_from(reader, VORBIS_IDENT_HEAD, VORBIS_COMMENT_HEAD)?;
|
||||
|
||||
Ok(Self {
|
||||
properties: if parse_options.read_properties { properties::read_properties(reader, &file_information.1)? } else { VorbisProperties::default() },
|
||||
properties: if parse_options.read_properties { properties::read_properties(reader, file_information.1, &file_information.2)? } else { VorbisProperties::default() },
|
||||
#[cfg(feature = "vorbis_comments")]
|
||||
// Safe to unwrap, a metadata packet is mandatory in OGG Vorbis
|
||||
vorbis_comments_tag: file_information.0.unwrap(),
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::io::{Read, Seek, SeekFrom};
|
|||
use std::time::Duration;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use ogg_pager::Page;
|
||||
use ogg_pager::{Packets, PageHeader};
|
||||
|
||||
/// An OGG Vorbis file's audio properties
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
|
||||
|
@ -85,17 +85,19 @@ impl VorbisProperties {
|
|||
|
||||
pub(in crate::ogg) fn read_properties<R>(
|
||||
data: &mut R,
|
||||
first_page: &Page,
|
||||
first_page_header: PageHeader,
|
||||
packets: &Packets,
|
||||
) -> Result<VorbisProperties>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let first_page_abgp = first_page.abgp;
|
||||
|
||||
let mut properties = VorbisProperties::default();
|
||||
|
||||
// It's impossible to get this far without the identification packet, safe to unwrap
|
||||
let first_packet = packets.get(0).unwrap();
|
||||
|
||||
// Skip identification header
|
||||
let first_page_content = &mut &first_page.content()[7..];
|
||||
let first_page_content = &mut &first_packet[7..];
|
||||
|
||||
properties.version = first_page_content.read_u32::<LittleEndian>()?;
|
||||
|
||||
|
@ -106,22 +108,37 @@ where
|
|||
properties.bitrate_nominal = first_page_content.read_i32::<LittleEndian>()?;
|
||||
properties.bitrate_minimum = first_page_content.read_i32::<LittleEndian>()?;
|
||||
|
||||
let last_page = find_last_page(data)?;
|
||||
let last_page_abgp = last_page.abgp;
|
||||
|
||||
let last_page = find_last_page(data);
|
||||
let file_length = data.seek(SeekFrom::End(0))?;
|
||||
|
||||
if let Some(frame_count) = last_page_abgp.checked_sub(first_page_abgp) {
|
||||
// This is used for bitrate calculation, it should be the length in
|
||||
// milliseconds, but if we can't determine it then we'll just use 1000.
|
||||
let mut length = 1000;
|
||||
if let Ok(last_page) = last_page {
|
||||
let first_page_abgp = first_page_header.abgp;
|
||||
let last_page_abgp = last_page.header().abgp;
|
||||
|
||||
if properties.sample_rate > 0 {
|
||||
let length = frame_count * 1000 / u64::from(properties.sample_rate);
|
||||
properties.duration = Duration::from_millis(length);
|
||||
let total_samples = last_page_abgp.saturating_sub(first_page_abgp);
|
||||
|
||||
properties.overall_bitrate = ((file_length * 8) / length) as u32;
|
||||
|
||||
// TODO: Calculate with the stream length, and make this the fallback
|
||||
properties.audio_bitrate = (properties.bitrate_nominal as u64 / 1000) as u32;
|
||||
// Best case scenario
|
||||
if total_samples > 0 {
|
||||
length = total_samples * 1000 / u64::from(properties.sample_rate);
|
||||
properties.duration = Duration::from_millis(length);
|
||||
} else {
|
||||
log::debug!(
|
||||
"Vorbis: The file contains invalid PCM values, unable to calculate length"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log::debug!("Vorbis: Sample rate = 0, unable to calculate length");
|
||||
}
|
||||
}
|
||||
|
||||
if properties.bitrate_nominal > 0 {
|
||||
properties.overall_bitrate = (file_length.saturating_mul(8) / length) as u32;
|
||||
properties.audio_bitrate = (properties.bitrate_nominal as u64 / 1000) as u32;
|
||||
}
|
||||
|
||||
Ok(properties)
|
||||
}
|
||||
|
|
|
@ -36,8 +36,8 @@ pub(crate) fn write_to(
|
|||
loop {
|
||||
let p = Page::read(data, false)?;
|
||||
|
||||
if p.header_type() & 0x01 != 1 {
|
||||
data.seek(SeekFrom::Start(p.start))?;
|
||||
if p.header().header_type_flag() & 0x01 != 1 {
|
||||
data.seek(SeekFrom::Start(p.header().start))?;
|
||||
data.read_to_end(&mut remaining)?;
|
||||
|
||||
reached_md_end = true;
|
||||
|
|
|
@ -146,7 +146,7 @@ where
|
|||
{
|
||||
let first_page = Page::read(data, false)?;
|
||||
|
||||
let ser = first_page.serial;
|
||||
let ser = first_page.header().stream_serial;
|
||||
|
||||
let mut writer = Vec::new();
|
||||
writer.write_all(&first_page.as_bytes()?)?;
|
||||
|
@ -155,7 +155,7 @@ where
|
|||
|
||||
let comment_signature = format.comment_signature();
|
||||
if let Some(comment_signature) = comment_signature {
|
||||
verify_signature(&first_md_page, comment_signature)?;
|
||||
verify_signature(first_md_page.content(), comment_signature)?;
|
||||
}
|
||||
|
||||
let comment_signature = comment_signature.unwrap_or_default();
|
||||
|
@ -211,8 +211,8 @@ fn replace_packet(
|
|||
loop {
|
||||
let p = Page::read(data, true)?;
|
||||
|
||||
if p.header_type() & 0x01 != 0x01 {
|
||||
data.seek(SeekFrom::Start(p.start))?;
|
||||
if p.header().header_type_flag() & 0x01 != 0x01 {
|
||||
data.seek(SeekFrom::Start(p.header().start))?;
|
||||
reached_md_end = true;
|
||||
break;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue