mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2025-03-04 14:57:17 +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;
|
pub(crate) mod constants;
|
||||||
|
|
||||||
#[cfg(any(feature = "format-opus", feature = "format-vorbis"))]
|
|
||||||
pub(crate) mod ogg_generic;
|
|
||||||
|
|
||||||
#[cfg(feature = "format-flac")]
|
#[cfg(feature = "format-flac")]
|
||||||
pub(crate) mod flac;
|
pub(crate) mod flac;
|
||||||
|
|
||||||
#[cfg(feature = "format-riff")]
|
#[cfg(feature = "format-riff")]
|
||||||
pub(crate) mod 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::{
|
use crate::components::logic::constants::{
|
||||||
OPUSHEAD, OPUSTAGS, VORBIS_COMMENT_HEAD, VORBIS_IDENT_HEAD,
|
OPUSHEAD, OPUSTAGS, VORBIS_COMMENT_HEAD, VORBIS_IDENT_HEAD,
|
||||||
};
|
};
|
||||||
use crate::components::logic::{flac, ogg_generic};
|
use crate::components::logic::{flac, ogg};
|
||||||
use crate::{
|
use crate::{
|
||||||
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, LoftyError, OggFormat, Picture,
|
Album, AnyTag, AudioTag, AudioTagEdit, AudioTagWrite, LoftyError, OggFormat, Picture,
|
||||||
PictureType, Result, TagType, ToAny, ToAnyTag,
|
PictureType, Result, TagType, ToAny, ToAnyTag,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "format-opus")]
|
#[cfg(feature = "format-opus")]
|
||||||
use crate::components::logic::ogg_generic::OGGTags;
|
use crate::components::logic::ogg::read::OGGTags;
|
||||||
|
|
||||||
use lofty_attr::impl_tag;
|
use lofty_attr::impl_tag;
|
||||||
|
|
||||||
|
@ -75,13 +75,13 @@ impl VorbisInnerTag {
|
||||||
{
|
{
|
||||||
match format {
|
match format {
|
||||||
OggFormat::Vorbis => {
|
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()?;
|
let vorbis_tag: VorbisTag = tag.try_into()?;
|
||||||
|
|
||||||
Ok(vorbis_tag.inner)
|
Ok(vorbis_tag.inner)
|
||||||
},
|
},
|
||||||
OggFormat::Opus => {
|
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()?;
|
let vorbis_tag: VorbisTag = tag.try_into()?;
|
||||||
|
|
||||||
Ok(vorbis_tag.inner)
|
Ok(vorbis_tag.inner)
|
||||||
|
@ -465,7 +465,7 @@ impl AudioTagWrite for VorbisTag {
|
||||||
if let Some(format) = self.inner.format.clone() {
|
if let Some(format) = self.inner.format.clone() {
|
||||||
match format {
|
match format {
|
||||||
OggFormat::Vorbis => {
|
OggFormat::Vorbis => {
|
||||||
ogg_generic::create_pages(
|
ogg::write::create_pages(
|
||||||
file,
|
file,
|
||||||
&VORBIS_COMMENT_HEAD,
|
&VORBIS_COMMENT_HEAD,
|
||||||
&self.inner.vendor,
|
&self.inner.vendor,
|
||||||
|
@ -474,7 +474,7 @@ impl AudioTagWrite for VorbisTag {
|
||||||
)?;
|
)?;
|
||||||
},
|
},
|
||||||
OggFormat::Opus => {
|
OggFormat::Opus => {
|
||||||
ogg_generic::create_pages(
|
ogg::write::create_pages(
|
||||||
file,
|
file,
|
||||||
&OPUSTAGS,
|
&OPUSTAGS,
|
||||||
&self.inner.vendor,
|
&self.inner.vendor,
|
||||||
|
|
Loading…
Add table
Reference in a new issue