mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2025-03-04 23:07:20 +00:00
Add property reading for opus
This commit is contained in:
parent
660a447c50
commit
23f9566f26
6 changed files with 130 additions and 11 deletions
|
@ -5,7 +5,9 @@ use ogg_pager::Page;
|
||||||
use crate::{LoftyError, Result};
|
use crate::{LoftyError, Result};
|
||||||
|
|
||||||
pub(crate) mod constants;
|
pub(crate) mod constants;
|
||||||
|
mod opus;
|
||||||
pub(crate) mod read;
|
pub(crate) mod read;
|
||||||
|
mod vorbis;
|
||||||
pub(crate) mod write;
|
pub(crate) mod write;
|
||||||
|
|
||||||
pub fn page_from_packet(packet: &mut [u8]) -> Result<Vec<Page>> {
|
pub fn page_from_packet(packet: &mut [u8]) -> Result<Vec<Page>> {
|
||||||
|
@ -55,7 +57,7 @@ pub(self) fn reach_metadata<T>(mut data: T, sig: &[u8]) -> Result<()>
|
||||||
where
|
where
|
||||||
T: Read + Seek,
|
T: Read + Seek,
|
||||||
{
|
{
|
||||||
let first_page = Page::read(&mut data)?;
|
let first_page = Page::read(&mut data, false)?;
|
||||||
|
|
||||||
let head = first_page.content;
|
let head = first_page.content;
|
||||||
let (ident, head) = head.split_at(sig.len());
|
let (ident, head) = head.split_at(sig.len());
|
||||||
|
|
75
src/components/logic/ogg/opus.rs
Normal file
75
src/components/logic/ogg/opus.rs
Normal file
|
@ -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<R>(
|
||||||
|
data: &mut R,
|
||||||
|
first_page: Page,
|
||||||
|
stream_len: u64,
|
||||||
|
) -> Result<FileProperties>
|
||||||
|
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::<LittleEndian>()?;
|
||||||
|
let sample_rate = cursor.read_u32::<LittleEndian>()?;
|
||||||
|
|
||||||
|
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<Page> = 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)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
use super::{is_metadata, reach_metadata};
|
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::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 byteorder::{LittleEndian, ReadBytesExt};
|
||||||
use ogg_pager::Page;
|
use ogg_pager::Page;
|
||||||
use unicase::UniCase;
|
use unicase::UniCase;
|
||||||
|
@ -15,6 +17,29 @@ pub type OGGTags = (
|
||||||
OggFormat,
|
OggFormat,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
fn read_properties<R>(data: &mut R, header_sig: &[u8]) -> Result<FileProperties>
|
||||||
|
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<T>(
|
pub(crate) fn read_from<T>(
|
||||||
mut data: T,
|
mut data: T,
|
||||||
header_sig: &[u8],
|
header_sig: &[u8],
|
||||||
|
@ -26,14 +51,14 @@ where
|
||||||
{
|
{
|
||||||
reach_metadata(&mut data, header_sig)?;
|
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)?;
|
is_metadata(&md_page, comment_sig)?;
|
||||||
|
|
||||||
let mut md_pages: Vec<u8> = Vec::new();
|
let mut md_pages: Vec<u8> = Vec::new();
|
||||||
|
|
||||||
md_pages.extend(md_page.content[comment_sig.len()..].iter());
|
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 {
|
if md_pages.len() > 125_829_120 {
|
||||||
return Err(LoftyError::TooMuchData);
|
return Err(LoftyError::TooMuchData);
|
||||||
}
|
}
|
||||||
|
|
17
src/components/logic/ogg/vorbis.rs
Normal file
17
src/components/logic/ogg/vorbis.rs
Normal file
|
@ -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<R>(
|
||||||
|
data: &mut R,
|
||||||
|
first_page: Page,
|
||||||
|
stream_len: u64,
|
||||||
|
) -> Result<FileProperties>
|
||||||
|
where
|
||||||
|
R: Read + Seek,
|
||||||
|
{
|
||||||
|
Ok(FileProperties::default())
|
||||||
|
}
|
|
@ -88,7 +88,7 @@ fn vorbis_write(
|
||||||
c.seek(SeekFrom::End(0))?;
|
c.seek(SeekFrom::End(0))?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let p = Page::read(&mut data)?;
|
let p = Page::read(&mut data, false)?;
|
||||||
|
|
||||||
if p.header_type != 1 {
|
if p.header_type != 1 {
|
||||||
data.seek(SeekFrom::Start(p.start as u64))?;
|
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<()> {
|
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 ser = first_page.serial;
|
||||||
|
|
||||||
let mut writer = Vec::new();
|
let mut writer = Vec::new();
|
||||||
writer.write_all(&*first_page.as_bytes())?;
|
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)?;
|
is_metadata(&first_md_page, sig)?;
|
||||||
|
|
||||||
#[cfg(feature = "format-vorbis")]
|
#[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();
|
let mut remaining = Vec::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let p = Page::read(&mut data)?;
|
let p = Page::read(&mut data, false)?;
|
||||||
|
|
||||||
if p.header_type != 1 {
|
if p.header_type != 1 {
|
||||||
data.seek(SeekFrom::Start(p.start as u64))?;
|
data.seek(SeekFrom::Start(p.start as u64))?;
|
||||||
|
|
|
@ -161,7 +161,7 @@ impl TryFrom<metaflac::Tag> for OggTag {
|
||||||
},
|
},
|
||||||
properties: FileProperties::default(), // TODO
|
properties: FileProperties::default(), // TODO
|
||||||
_format: TagType::Ogg(OggFormat::Flac),
|
_format: TagType::Ogg(OggFormat::Flac),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(LoftyError::InvalidData(
|
Err(LoftyError::InvalidData(
|
||||||
|
@ -414,7 +414,7 @@ impl AudioTagWrite for OggTag {
|
||||||
|
|
||||||
#[cfg(any(feature = "format-opus", feature = "format-vorbis"))]
|
#[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))?;
|
file.seek(SeekFrom::Start(0))?;
|
||||||
|
|
||||||
#[cfg(feature = "format-opus")]
|
#[cfg(feature = "format-opus")]
|
||||||
|
|
Loading…
Add table
Reference in a new issue