diff --git a/src/components/logic/ogg/mod.rs b/src/components/logic/ogg/mod.rs index af7351d8..bf0a0b16 100644 --- a/src/components/logic/ogg/mod.rs +++ b/src/components/logic/ogg/mod.rs @@ -5,7 +5,9 @@ use ogg_pager::Page; use crate::{LoftyError, Result}; pub(crate) mod constants; +mod opus; pub(crate) mod read; +mod vorbis; pub(crate) mod write; pub fn page_from_packet(packet: &mut [u8]) -> Result> { @@ -55,7 +57,7 @@ pub(self) fn reach_metadata(mut data: T, sig: &[u8]) -> Result<()> where T: Read + Seek, { - let first_page = Page::read(&mut data)?; + let first_page = Page::read(&mut data, false)?; let head = first_page.content; let (ident, head) = head.split_at(sig.len()); diff --git a/src/components/logic/ogg/opus.rs b/src/components/logic/ogg/opus.rs new file mode 100644 index 00000000..878cfee4 --- /dev/null +++ b/src/components/logic/ogg/opus.rs @@ -0,0 +1,75 @@ +use crate::{FileProperties, Result, LoftyError}; + +use std::io::{Cursor, Read, Seek, SeekFrom}; + +use byteorder::{LittleEndian, ReadBytesExt}; +use ogg_pager::Page; +use std::time::Duration; + +pub(in crate::components) fn read_properties( + data: &mut R, + first_page: Page, + stream_len: u64, +) -> Result +where + R: Read + Seek, +{ + let first_page_abgp = first_page.abgp as i64; + + let mut cursor = Cursor::new(&*first_page.content); + + // Skip identification header and version + cursor.seek(SeekFrom::Start(11))?; + + let channels = cursor.read_u8()?; + let pre_skip = cursor.read_u16::()?; + let sample_rate = cursor.read_u32::()?; + + let _first_comment_page = Page::read(data, true)?; + + // Skip over the metadata packet + loop { + let page = Page::read(data, true)?; + + if page.header_type != 1 { + data.seek(SeekFrom::Start(page.start as u64))?; + break + } + } + + // Subtract the identification and metadata packet length from the total + let audio_size = stream_len - data.seek(SeekFrom::Current(0))?; + + let next_page = Page::read(data, true)?; + + // Find the last page + let mut pages: Vec = vec![next_page]; + + let last_page = loop { + if let Ok(current) = Page::read(data, true) { + pages.push(current) + } else { + // Safe to unwrap since the Vec starts off with a Page + break pages.pop().unwrap() + } + }; + + let last_page_abgp = last_page.abgp as i64; + + let frame_count = last_page_abgp - first_page_abgp - pre_skip as i64; + + if frame_count < 0 { + return Err(LoftyError::InvalidData("OGG file contains incorrect PCM values")) + } + + let length = frame_count * 1000 / 48000; + let duration = Duration::from_millis(length as u64); + let bitrate = (audio_size * 8 / length) as u32; + + Ok(FileProperties { + duration, + bitrate: Some(bitrate), + sample_rate: Some(sample_rate), + channels: Some(channels) + }) +} diff --git a/src/components/logic/ogg/read.rs b/src/components/logic/ogg/read.rs index 3cec6921..e3c1859d 100644 --- a/src/components/logic/ogg/read.rs +++ b/src/components/logic/ogg/read.rs @@ -1,9 +1,11 @@ use super::{is_metadata, reach_metadata}; -use crate::{LoftyError, OggFormat, Picture, Result}; +use crate::components::logic::ogg::constants::OPUSHEAD; +use crate::{FileProperties, LoftyError, OggFormat, Picture, Result}; use std::collections::HashMap; -use std::io::{Read, Seek}; +use std::io::{Read, Seek, SeekFrom}; +use crate::components::logic::ogg::{opus, vorbis}; use byteorder::{LittleEndian, ReadBytesExt}; use ogg_pager::Page; use unicase::UniCase; @@ -15,6 +17,29 @@ pub type OGGTags = ( OggFormat, ); +fn read_properties(data: &mut R, header_sig: &[u8]) -> Result +where + R: Read + Seek, +{ + let stream_len = { + let current = data.seek(SeekFrom::Current(0))?; + let end = data.seek(SeekFrom::End(0))?; + data.seek(SeekFrom::Start(current))?; + + end - current + }; + + let first_page = Page::read(data, false)?; + + let properties = if header_sig == OPUSHEAD { + opus::read_properties(data, first_page, stream_len)? + } else { + vorbis::read_properties(data, first_page, stream_len)? + }; + + Ok(properties) +} + pub(crate) fn read_from( mut data: T, header_sig: &[u8], @@ -26,14 +51,14 @@ where { reach_metadata(&mut data, header_sig)?; - let md_page = Page::read(&mut data)?; + let md_page = Page::read(&mut data, false)?; is_metadata(&md_page, comment_sig)?; let mut md_pages: Vec = Vec::new(); md_pages.extend(md_page.content[comment_sig.len()..].iter()); - while let Ok(page) = Page::read(&mut data) { + while let Ok(page) = Page::read(&mut data, false) { if md_pages.len() > 125_829_120 { return Err(LoftyError::TooMuchData); } diff --git a/src/components/logic/ogg/vorbis.rs b/src/components/logic/ogg/vorbis.rs new file mode 100644 index 00000000..da6cbdfd --- /dev/null +++ b/src/components/logic/ogg/vorbis.rs @@ -0,0 +1,17 @@ +use crate::{FileProperties, Result}; + +use std::io::{Cursor, Read, Seek, SeekFrom}; + +use byteorder::{LittleEndian, ReadBytesExt}; +use ogg_pager::Page; + +pub(in crate::components) fn read_properties( + data: &mut R, + first_page: Page, + stream_len: u64, +) -> Result +where + R: Read + Seek, +{ + Ok(FileProperties::default()) +} diff --git a/src/components/logic/ogg/write.rs b/src/components/logic/ogg/write.rs index 9ca1d4b8..b8bb217c 100644 --- a/src/components/logic/ogg/write.rs +++ b/src/components/logic/ogg/write.rs @@ -88,7 +88,7 @@ fn vorbis_write( c.seek(SeekFrom::End(0))?; loop { - let p = Page::read(&mut data)?; + let p = Page::read(&mut data, false)?; if p.header_type != 1 { data.seek(SeekFrom::Start(p.start as u64))?; @@ -187,14 +187,14 @@ fn vorbis_write( } fn write_to(mut data: &mut File, pages: &mut [Page], sig: &[u8]) -> Result<()> { - let first_page = Page::read(&mut data)?; + let first_page = Page::read(&mut data, false)?; let ser = first_page.serial; let mut writer = Vec::new(); writer.write_all(&*first_page.as_bytes())?; - let first_md_page = Page::read(&mut data)?; + let first_md_page = Page::read(&mut data, false)?; is_metadata(&first_md_page, sig)?; #[cfg(feature = "format-vorbis")] @@ -208,7 +208,7 @@ fn write_to(mut data: &mut File, pages: &mut [Page], sig: &[u8]) -> Result<()> { let mut remaining = Vec::new(); loop { - let p = Page::read(&mut data)?; + let p = Page::read(&mut data, false)?; if p.header_type != 1 { data.seek(SeekFrom::Start(p.start as u64))?; diff --git a/src/components/tags/ogg_tag.rs b/src/components/tags/ogg_tag.rs index 696105bc..bf0a8711 100644 --- a/src/components/tags/ogg_tag.rs +++ b/src/components/tags/ogg_tag.rs @@ -161,7 +161,7 @@ impl TryFrom for OggTag { }, properties: FileProperties::default(), // TODO _format: TagType::Ogg(OggFormat::Flac), - }) + }); } Err(LoftyError::InvalidData( @@ -414,7 +414,7 @@ impl AudioTagWrite for OggTag { #[cfg(any(feature = "format-opus", feature = "format-vorbis"))] { - let p = ogg_pager::Page::read(file)?; + let p = ogg_pager::Page::read(file, false)?; file.seek(SeekFrom::Start(0))?; #[cfg(feature = "format-opus")]