Add read function for vorbis/opus

Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com>
This commit is contained in:
Serial 2021-06-27 12:49:08 -04:00
parent a5df39de78
commit 804f8f42a9
4 changed files with 182 additions and 10 deletions

View file

@ -1,11 +1,15 @@
#[cfg(any(feature = "format-opus", feature = "format-vorbis", feature = "format-riff"))]
#[cfg(any(
feature = "format-opus",
feature = "format-vorbis",
feature = "format-riff"
))]
pub(crate) mod constants;
#[cfg(any(feature = "format-opus", feature = "format-vorbis"))]
pub(crate) mod ogg_generic;
#[cfg(feature = "format-flac")]
pub(crate) mod flac;
#[cfg(feature = "format-riff")]
pub(crate) mod riff;
#[cfg(any(feature = "format-opus", feature = "format-vorbis"))]
pub(crate) mod ogg;

View file

@ -0,0 +1,86 @@
pub(crate) mod read;
pub(crate) mod write;
use crate::{LoftyError, Result};
use ogg_pager::Page;
use std::io::{Read, Seek};
pub fn page_from_packet(packet: &mut [u8]) -> Result<Vec<Page>> {
let mut pages: Vec<Page> = Vec::new();
let reader = &mut &packet[..];
let mut start = 0_usize;
let mut i = 0;
while !reader.is_empty() {
let header_type = if i == 0 { 0 } else { 1_u8 };
let size = std::cmp::min(65025, reader.len());
if i != 0 {
if let Some(s) = start.checked_add(size) {
start = s
} else {
return Err(LoftyError::TooMuchData);
}
}
let mut content = vec![0; size];
reader.read_exact(&mut content)?;
let end = start + size;
pages.push(Page {
content,
header_type,
abgp: 0,
serial: 0, // Retrieved later
seq_num: (i + 1) as u32,
checksum: 0, // Calculated later
start,
end,
});
i += 1;
}
Ok(pages)
}
pub(self) fn reach_metadata<T>(mut data: T, sig: &[u8]) -> Result<()>
where
T: Read + Seek,
{
let first_page = Page::read(&mut data)?;
let head = first_page.content;
let (ident, head) = head.split_at(sig.len());
if ident != sig {
return Err(LoftyError::InvalidData("OGG file missing magic signature"));
}
if head[10] != 0 {
let mut channel_mapping_info = [0; 1];
data.read_exact(&mut channel_mapping_info)?;
let mut channel_mapping = vec![0; channel_mapping_info[0] as usize];
data.read_exact(&mut channel_mapping)?;
}
Ok(())
}
// Verify the 2nd page contains the comment header
pub(self) fn is_metadata(page: &Page, sig: &[u8]) -> Result<()> {
let sig_len = sig.len();
if page.content.len() < sig_len || &page.content[0..sig_len] != sig {
return Err(LoftyError::InvalidData(
"OGG file missing the mandatory comment header",
));
}
Ok(())
}

View file

@ -0,0 +1,82 @@
use crate::components::logic::constants::OPUSHEAD;
use crate::components::logic::ogg::{is_metadata, reach_metadata};
use crate::{LoftyError, OggFormat, Result};
use std::collections::HashMap;
use std::io::{Read, Seek};
use byteorder::{LittleEndian, ReadBytesExt};
use ogg_pager::Page;
pub type OGGTags = (String, Vec<String>, HashMap<String, String>, OggFormat);
pub(crate) fn read_from<T>(mut data: T, header_sig: &[u8], comment_sig: &[u8]) -> Result<OGGTags>
where
T: Read + Seek,
{
reach_metadata(&mut data, header_sig)?;
let md_page = Page::read(&mut data)?;
is_metadata(&md_page, comment_sig)?;
let mut md_pages: Vec<u8> = Vec::new();
md_pages.extend(md_page.content[comment_sig.len()..].iter());
while let Ok(page) = Page::read(&mut data) {
if md_pages.len() > 125_829_120 {
return Err(LoftyError::TooMuchData);
}
if page.header_type == 1 {
md_pages.extend(page.content.iter());
} else {
break;
}
}
let mut md: HashMap<String, String> = HashMap::new();
let mut pictures = Vec::new();
let reader = &mut &md_pages[..];
let vendor_len = reader.read_u32::<LittleEndian>()?;
let mut vendor = vec![0; vendor_len as usize];
reader.read_exact(&mut vendor)?;
let vendor_str = match String::from_utf8(vendor) {
Ok(v) => v,
Err(_) => {
return Err(LoftyError::InvalidData(
"OGG file has an invalid vendor string",
))
},
};
let comments_total_len = reader.read_u32::<LittleEndian>()?;
for _ in 0..comments_total_len {
let comment_len = reader.read_u32::<LittleEndian>()?;
let mut comment_bytes = vec![0; comment_len as usize];
reader.read_exact(&mut comment_bytes)?;
let comment = String::from_utf8(comment_bytes)?;
let split: Vec<&str> = comment.splitn(2, '=').collect();
if split[0] == "METADATA_BLOCK_PICTURE" {
pictures.push(split[1].to_string())
} else {
md.insert(split[0].to_string(), split[1].to_string());
}
}
let vorbis_format = if header_sig == OPUSHEAD {
OggFormat::Opus
} else {
OggFormat::Vorbis
};
Ok((vendor_str, pictures, md, vorbis_format))
}

View file

@ -7,14 +7,14 @@
use crate::components::logic::constants::{
OPUSHEAD, OPUSTAGS, VORBIS_COMMENT_HEAD, VORBIS_IDENT_HEAD,
};
use crate::components::logic::{flac, ogg_generic};
use crate::components::logic::{flac, ogg};
use crate::{
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, LoftyError, OggFormat, Picture,
PictureType, Result, TagType, ToAny, ToAnyTag,
};
#[cfg(feature = "format-opus")]
use crate::components::logic::ogg_generic::OGGTags;
use crate::components::logic::ogg::read::OGGTags;
use lofty_attr::impl_tag;
@ -75,13 +75,13 @@ impl VorbisInnerTag {
{
match format {
OggFormat::Vorbis => {
let tag = ogg_generic::read_from(reader, &VORBIS_IDENT_HEAD, &VORBIS_COMMENT_HEAD)?;
let tag = ogg::read::read_from(reader, &VORBIS_IDENT_HEAD, &VORBIS_COMMENT_HEAD)?;
let vorbis_tag: VorbisTag = tag.try_into()?;
Ok(vorbis_tag.inner)
},
OggFormat::Opus => {
let tag = ogg_generic::read_from(reader, &OPUSHEAD, &OPUSTAGS)?;
let tag = ogg::read::read_from(reader, &OPUSHEAD, &OPUSTAGS)?;
let vorbis_tag: VorbisTag = tag.try_into()?;
Ok(vorbis_tag.inner)
@ -465,7 +465,7 @@ impl AudioTagWrite for VorbisTag {
if let Some(format) = self.inner.format.clone() {
match format {
OggFormat::Vorbis => {
ogg_generic::create_pages(
ogg::write::create_pages(
file,
&VORBIS_COMMENT_HEAD,
&self.inner.vendor,
@ -474,7 +474,7 @@ impl AudioTagWrite for VorbisTag {
)?;
},
OggFormat::Opus => {
ogg_generic::create_pages(
ogg::write::create_pages(
file,
&OPUSTAGS,
&self.inner.vendor,