mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-12-12 13:42:34 +00:00
Add read function for vorbis/opus
Signed-off-by: Serial <69764315+Serial-ATA@users.noreply.github.com>
This commit is contained in:
parent
a5df39de78
commit
804f8f42a9
4 changed files with 182 additions and 10 deletions
|
@ -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;
|
||||
|
|
86
src/components/logic/ogg/mod.rs
Normal file
86
src/components/logic/ogg/mod.rs
Normal 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(())
|
||||
}
|
82
src/components/logic/ogg/read.rs
Normal file
82
src/components/logic/ogg/read.rs
Normal 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))
|
||||
}
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue