mirror of
https://github.com/Serial-ATA/lofty-rs
synced 2024-12-14 14:42:33 +00:00
Properties for Opus and Vorbis
This commit is contained in:
parent
c0457f1ce7
commit
f95e7cfdff
6 changed files with 321 additions and 297 deletions
|
@ -5,23 +5,26 @@ use ogg_pager::Page;
|
|||
use crate::{LoftyError, Result};
|
||||
|
||||
pub(crate) mod constants;
|
||||
mod opus;
|
||||
pub(crate) mod read;
|
||||
mod vorbis;
|
||||
pub(crate) mod write;
|
||||
|
||||
#[cfg(feature = "format-opus")]
|
||||
mod opus;
|
||||
#[cfg(feature = "format-vorbis")]
|
||||
mod vorbis;
|
||||
|
||||
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 start = 0_u64;
|
||||
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());
|
||||
let size = std::cmp::min(65025_u64, reader.len() as u64);
|
||||
|
||||
if i != 0 {
|
||||
if let Some(s) = start.checked_add(size) {
|
||||
|
@ -31,7 +34,7 @@ pub fn page_from_packet(packet: &mut [u8]) -> Result<Vec<Page>> {
|
|||
}
|
||||
}
|
||||
|
||||
let mut content = vec![0; size];
|
||||
let mut content = vec![0; size as usize];
|
||||
reader.read_exact(&mut content)?;
|
||||
|
||||
let end = start + size;
|
||||
|
@ -53,39 +56,31 @@ pub fn page_from_packet(packet: &mut [u8]) -> Result<Vec<Page>> {
|
|||
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, false)?;
|
||||
pub(self) fn verify_signature(page: &Page, sig: &[u8]) -> Result<()> {
|
||||
let sig_len = sig.len();
|
||||
|
||||
let head = first_page.content;
|
||||
let (ident, head) = head.split_at(sig.len());
|
||||
|
||||
if ident != sig {
|
||||
if page.content.len() < sig_len || &page.content[..sig_len] != 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();
|
||||
pub(self) fn find_last_page<R>(data: &mut R) -> Result<Page>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let next_page = Page::read(data, true)?;
|
||||
|
||||
if page.content.len() < sig_len || &page.content[0..sig_len] != sig {
|
||||
return Err(LoftyError::InvalidData(
|
||||
"OGG file missing the mandatory comment header",
|
||||
));
|
||||
// Find the last page
|
||||
let mut pages: Vec<Page> = vec![next_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 Ok(pages.pop().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
use crate::{FileProperties, Result, LoftyError};
|
||||
use super::find_last_page;
|
||||
use crate::{FileProperties, LoftyError, Result};
|
||||
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use ogg_pager::Page;
|
||||
use std::fs::File;
|
||||
use std::time::Duration;
|
||||
|
||||
pub(in crate::components) fn read_properties<R>(
|
||||
|
@ -14,62 +16,80 @@ pub(in crate::components) fn read_properties<R>(
|
|||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
let first_page_abgp = first_page.abgp as i64;
|
||||
|
||||
let mut cursor = Cursor::new(&*first_page.content);
|
||||
let first_page_abgp = first_page.abgp;
|
||||
|
||||
// Skip identification header and version
|
||||
cursor.seek(SeekFrom::Start(11))?;
|
||||
let first_page_content = &mut &first_page.content[11..];
|
||||
|
||||
let channels = cursor.read_u8()?;
|
||||
let pre_skip = cursor.read_u16::<LittleEndian>()?;
|
||||
let sample_rate = cursor.read_u32::<LittleEndian>()?;
|
||||
let channels = first_page_content.read_u8()?;
|
||||
let pre_skip = first_page_content.read_u16::<LittleEndian>()?;
|
||||
let sample_rate = first_page_content.read_u32::<LittleEndian>()?;
|
||||
|
||||
let _first_comment_page = Page::read(data, true)?;
|
||||
|
||||
// Skip over the metadata packet
|
||||
loop {
|
||||
let 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
|
||||
}
|
||||
}
|
||||
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)?;
|
||||
let last_page = find_last_page(data)?;
|
||||
let last_page_abgp = last_page.abgp;
|
||||
|
||||
// Find the last page
|
||||
let mut pages: Vec<Page> = vec![next_page];
|
||||
return if let Some(frame_count) = last_page_abgp.checked_sub(first_page_abgp + pre_skip as u64)
|
||||
{
|
||||
let length = frame_count * 1000 / 48000;
|
||||
let duration = Duration::from_millis(length as u64);
|
||||
let bitrate = ((audio_size * 8) / length) as u32;
|
||||
|
||||
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)
|
||||
})
|
||||
Ok(FileProperties {
|
||||
duration,
|
||||
bitrate: Some(bitrate),
|
||||
sample_rate: Some(sample_rate),
|
||||
channels: Some(channels),
|
||||
})
|
||||
} else {
|
||||
Err(LoftyError::InvalidData(
|
||||
"OGG file contains incorrect PCM values",
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
pub fn write_to(data: &mut File, writer: &mut Vec<u8>, ser: u32, pages: &mut [Page]) -> Result<()> {
|
||||
let reached_md_end: bool;
|
||||
let mut remaining = Vec::new();
|
||||
|
||||
loop {
|
||||
let p = Page::read(data, true)?;
|
||||
|
||||
if p.header_type != 1 {
|
||||
data.seek(SeekFrom::Start(p.start as u64))?;
|
||||
reached_md_end = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !reached_md_end {
|
||||
return Err(LoftyError::InvalidData("OGG file ends with comment header"));
|
||||
}
|
||||
|
||||
data.read_to_end(&mut remaining)?;
|
||||
|
||||
for mut p in pages.iter_mut() {
|
||||
p.serial = ser;
|
||||
p.gen_crc();
|
||||
|
||||
writer.write_all(&*p.as_bytes())?;
|
||||
}
|
||||
|
||||
writer.write_all(&*remaining)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use super::{is_metadata, reach_metadata};
|
||||
use super::verify_signature;
|
||||
use crate::components::logic::ogg::constants::OPUSHEAD;
|
||||
use crate::components::logic::ogg::{opus, vorbis};
|
||||
use crate::{FileProperties, LoftyError, OggFormat, Picture, Result};
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
|
||||
use crate::components::logic::ogg::{opus, vorbis};
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use ogg_pager::Page;
|
||||
use unicase::UniCase;
|
||||
|
@ -14,10 +14,11 @@ pub type OGGTags = (
|
|||
String,
|
||||
Vec<Picture>,
|
||||
HashMap<UniCase<String>, String>,
|
||||
FileProperties,
|
||||
OggFormat,
|
||||
);
|
||||
|
||||
fn read_properties<R>(data: &mut R, header_sig: &[u8]) -> Result<FileProperties>
|
||||
fn read_properties<R>(data: &mut R, header_sig: &[u8], first_page: Page) -> Result<FileProperties>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
|
@ -26,11 +27,9 @@ where
|
|||
let end = data.seek(SeekFrom::End(0))?;
|
||||
data.seek(SeekFrom::Start(current))?;
|
||||
|
||||
end - current
|
||||
end - first_page.start
|
||||
};
|
||||
|
||||
let first_page = Page::read(data, false)?;
|
||||
|
||||
let properties = if header_sig == OPUSHEAD {
|
||||
opus::read_properties(data, first_page, stream_len)?
|
||||
} else {
|
||||
|
@ -41,7 +40,7 @@ where
|
|||
}
|
||||
|
||||
pub(crate) fn read_from<T>(
|
||||
mut data: T,
|
||||
data: &mut T,
|
||||
header_sig: &[u8],
|
||||
comment_sig: &[u8],
|
||||
format: OggFormat,
|
||||
|
@ -49,16 +48,17 @@ pub(crate) fn read_from<T>(
|
|||
where
|
||||
T: Read + Seek,
|
||||
{
|
||||
reach_metadata(&mut data, header_sig)?;
|
||||
let first_page = Page::read(data, false)?;
|
||||
verify_signature(&first_page, header_sig)?;
|
||||
|
||||
let md_page = Page::read(&mut data, false)?;
|
||||
is_metadata(&md_page, comment_sig)?;
|
||||
let md_page = Page::read(data, false)?;
|
||||
verify_signature(&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, false) {
|
||||
while let Ok(page) = Page::read(data, false) {
|
||||
if md_pages.len() > 125_829_120 {
|
||||
return Err(LoftyError::TooMuchData);
|
||||
}
|
||||
|
@ -66,6 +66,7 @@ where
|
|||
if page.header_type == 1 {
|
||||
md_pages.extend(page.content.iter());
|
||||
} else {
|
||||
data.seek(SeekFrom::Start(page.start))?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -107,5 +108,7 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
Ok((vendor_str, pictures, md, format))
|
||||
let properties = read_properties(data, header_sig, first_page)?;
|
||||
|
||||
Ok((vendor_str, pictures, md, properties, format))
|
||||
}
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
use crate::{FileProperties, Result};
|
||||
use super::find_last_page;
|
||||
use crate::components::logic::ogg::constants::VORBIS_SETUP_HEAD;
|
||||
use crate::{FileProperties, LoftyError, Result};
|
||||
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom};
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use ogg_pager::Page;
|
||||
use std::fs::File;
|
||||
use std::time::Duration;
|
||||
|
||||
pub(in crate::components) fn read_properties<R>(
|
||||
data: &mut R,
|
||||
|
@ -13,5 +17,174 @@ pub(in crate::components) fn read_properties<R>(
|
|||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
Ok(FileProperties::default())
|
||||
let first_page_abgp = first_page.abgp;
|
||||
|
||||
// Skip identification header and version
|
||||
let first_page_content = &mut &first_page.content[11..];
|
||||
|
||||
let channels = first_page_content.read_u8()?;
|
||||
let sample_rate = first_page_content.read_u32::<LittleEndian>()?;
|
||||
|
||||
// Identification and metadata packets have already been skipped
|
||||
// Have to find the end of the setup packet now
|
||||
let header_end = loop {
|
||||
let page = Page::read(data, false)?;
|
||||
let segments = page.segments();
|
||||
|
||||
if let Some(seg_end) = segments.iter().position(|s| s != &255_u8) {
|
||||
let packet_size: usize = segments[..seg_end].iter().map(|s| *s as usize).sum();
|
||||
|
||||
// Position in stream + page header (26 bytes long) + segment table + however long the packet is
|
||||
let header_end = page.start + 26 + segments.len() as u64 + packet_size as u64;
|
||||
|
||||
break header_end;
|
||||
}
|
||||
};
|
||||
|
||||
let audio_size = stream_len - header_end;
|
||||
|
||||
let last_page = find_last_page(data)?;
|
||||
let last_page_abgp = last_page.abgp;
|
||||
|
||||
return if let Some(frame_count) = last_page_abgp.checked_sub(first_page_abgp) {
|
||||
let length = frame_count * 1000 / sample_rate as u64;
|
||||
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),
|
||||
})
|
||||
} else {
|
||||
Err(LoftyError::InvalidData(
|
||||
"OGG file contains incorrect PCM values",
|
||||
))
|
||||
};
|
||||
}
|
||||
|
||||
pub fn write_to(
|
||||
data: &mut File,
|
||||
writer: &mut Vec<u8>,
|
||||
first_md_content: Vec<u8>,
|
||||
ser: u32,
|
||||
pages: &mut [Page],
|
||||
) -> Result<()> {
|
||||
let mut remaining = Vec::new();
|
||||
|
||||
let reached_md_end: bool;
|
||||
|
||||
// Find the total comment count in the first page's content
|
||||
let mut c = Cursor::new(first_md_content);
|
||||
|
||||
// Skip the header
|
||||
c.seek(SeekFrom::Start(7))?;
|
||||
|
||||
// Skip the vendor
|
||||
let vendor_len = c.read_u32::<LittleEndian>()?;
|
||||
c.seek(SeekFrom::Current(i64::from(vendor_len)))?;
|
||||
|
||||
let total_comments = c.read_u32::<LittleEndian>()?;
|
||||
let comments_pos = c.seek(SeekFrom::Current(0))?;
|
||||
|
||||
c.seek(SeekFrom::End(0))?;
|
||||
|
||||
loop {
|
||||
let p = Page::read(data, false)?;
|
||||
|
||||
if p.header_type != 1 {
|
||||
data.seek(SeekFrom::Start(p.start as u64))?;
|
||||
data.read_to_end(&mut remaining)?;
|
||||
|
||||
reached_md_end = true;
|
||||
break;
|
||||
}
|
||||
|
||||
c.write_all(&p.content)?;
|
||||
}
|
||||
|
||||
if !reached_md_end {
|
||||
return Err(LoftyError::InvalidData("OGG file ends with comment header"));
|
||||
}
|
||||
|
||||
c.seek(SeekFrom::Start(comments_pos))?;
|
||||
|
||||
for _ in 0..total_comments {
|
||||
let len = c.read_u32::<LittleEndian>()?;
|
||||
c.seek(SeekFrom::Current(i64::from(len)))?;
|
||||
}
|
||||
|
||||
if c.read_u8()? != 1 {
|
||||
return Err(LoftyError::InvalidData(
|
||||
"OGG Vorbis file is missing a framing bit",
|
||||
));
|
||||
}
|
||||
|
||||
// Comments should be followed by the setup header
|
||||
let mut header_ident = [0; 7];
|
||||
c.read_exact(&mut header_ident)?;
|
||||
|
||||
if header_ident != VORBIS_SETUP_HEAD {
|
||||
return Err(LoftyError::InvalidData(
|
||||
"OGG Vorbis file is missing setup header",
|
||||
));
|
||||
}
|
||||
|
||||
c.seek(SeekFrom::Current(-7))?;
|
||||
|
||||
let mut setup = Vec::new();
|
||||
c.read_to_end(&mut setup)?;
|
||||
|
||||
let pages_len = pages.len() - 1;
|
||||
|
||||
for (i, mut p) in pages.iter_mut().enumerate() {
|
||||
p.serial = ser;
|
||||
|
||||
if i == pages_len {
|
||||
// Add back the framing bit
|
||||
p.content.push(1);
|
||||
|
||||
// The segment tables of current page and the setup header have to be combined
|
||||
let mut seg_table = Vec::new();
|
||||
seg_table.extend(p.segments().iter());
|
||||
seg_table.extend(ogg_pager::segments(&*setup));
|
||||
|
||||
let mut seg_table_len = seg_table.len();
|
||||
|
||||
if seg_table_len > 255 {
|
||||
seg_table = seg_table.split_at(255).0.to_vec();
|
||||
seg_table_len = 255;
|
||||
}
|
||||
|
||||
seg_table.insert(0, seg_table_len as u8);
|
||||
|
||||
let page = p.extend(&*setup);
|
||||
|
||||
let mut p_bytes = p.as_bytes();
|
||||
let seg_count = p_bytes[26] as usize;
|
||||
|
||||
// Replace segment table and checksum
|
||||
p_bytes.splice(26..27 + seg_count, seg_table);
|
||||
p_bytes.splice(22..26, ogg_pager::crc32(&*p_bytes).to_le_bytes().to_vec());
|
||||
|
||||
writer.write_all(&*p_bytes)?;
|
||||
|
||||
if let Some(mut page) = page {
|
||||
page.serial = ser;
|
||||
page.gen_crc();
|
||||
|
||||
writer.write_all(&*page.as_bytes())?;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
p.gen_crc();
|
||||
writer.write_all(&*p.as_bytes())?;
|
||||
}
|
||||
|
||||
writer.write_all(&*remaining)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
use super::{is_metadata, page_from_packet};
|
||||
use crate::{LoftyError, Picture, Result};
|
||||
use super::{opus, page_from_packet, verify_signature, vorbis};
|
||||
use crate::{Picture, Result};
|
||||
|
||||
#[cfg(feature = "format-opus")]
|
||||
use crate::components::logic::ogg::constants::OPUSTAGS;
|
||||
#[cfg(feature = "format-vorbis")]
|
||||
use crate::components::logic::ogg::constants::{VORBIS_COMMENT_HEAD, VORBIS_SETUP_HEAD};
|
||||
use crate::components::logic::ogg::constants::VORBIS_COMMENT_HEAD;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
|
||||
use std::io::{Seek, SeekFrom, Write};
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use ogg_pager::Page;
|
||||
use unicase::UniCase;
|
||||
|
||||
|
@ -60,132 +59,6 @@ pub(crate) fn create_pages(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "format-vorbis")]
|
||||
fn vorbis_write(
|
||||
mut data: &mut File,
|
||||
writer: &mut Vec<u8>,
|
||||
first_md_content: Vec<u8>,
|
||||
ser: u32,
|
||||
pages: &mut [Page],
|
||||
) -> Result<()> {
|
||||
let mut remaining = Vec::new();
|
||||
|
||||
let reached_md_end: bool;
|
||||
|
||||
// Find the total comment count in the first page's content
|
||||
let mut c = Cursor::new(first_md_content);
|
||||
|
||||
// Skip the header
|
||||
c.seek(SeekFrom::Start(7))?;
|
||||
|
||||
// Skip the vendor
|
||||
let vendor_len = c.read_u32::<LittleEndian>()?;
|
||||
c.seek(SeekFrom::Current(i64::from(vendor_len)))?;
|
||||
|
||||
let total_comments = c.read_u32::<LittleEndian>()?;
|
||||
let comments_pos = c.seek(SeekFrom::Current(0))?;
|
||||
|
||||
c.seek(SeekFrom::End(0))?;
|
||||
|
||||
loop {
|
||||
let p = Page::read(&mut data, false)?;
|
||||
|
||||
if p.header_type != 1 {
|
||||
data.seek(SeekFrom::Start(p.start as u64))?;
|
||||
data.read_to_end(&mut remaining)?;
|
||||
|
||||
reached_md_end = true;
|
||||
break;
|
||||
}
|
||||
|
||||
c.write_all(&p.content)?;
|
||||
}
|
||||
|
||||
if !reached_md_end {
|
||||
return Err(LoftyError::InvalidData("OGG file ends with comment header"));
|
||||
}
|
||||
|
||||
c.seek(SeekFrom::Start(comments_pos))?;
|
||||
|
||||
for _ in 0..total_comments {
|
||||
let len = c.read_u32::<LittleEndian>()?;
|
||||
c.seek(SeekFrom::Current(i64::from(len)))?;
|
||||
}
|
||||
|
||||
if c.read_u8()? != 1 {
|
||||
return Err(LoftyError::InvalidData(
|
||||
"OGG Vorbis file is missing a framing bit",
|
||||
));
|
||||
}
|
||||
|
||||
// Comments should be followed by the setup header
|
||||
let mut header_ident = [0; 7];
|
||||
c.read_exact(&mut header_ident)?;
|
||||
|
||||
if header_ident != VORBIS_SETUP_HEAD {
|
||||
return Err(LoftyError::InvalidData(
|
||||
"OGG Vorbis file is missing setup header",
|
||||
));
|
||||
}
|
||||
|
||||
c.seek(SeekFrom::Current(-7))?;
|
||||
|
||||
let mut setup = Vec::new();
|
||||
c.read_to_end(&mut setup)?;
|
||||
|
||||
let pages_len = pages.len() - 1;
|
||||
|
||||
for (i, mut p) in pages.iter_mut().enumerate() {
|
||||
p.serial = ser;
|
||||
|
||||
if i == pages_len {
|
||||
// Add back the framing bit
|
||||
p.content.push(1);
|
||||
|
||||
// The segment tables of current page and the setup header have to be combined
|
||||
let mut seg_table = Vec::new();
|
||||
seg_table.extend(p.segments().iter());
|
||||
seg_table.extend(ogg_pager::segments(&*setup));
|
||||
|
||||
let mut seg_table_len = seg_table.len();
|
||||
|
||||
if seg_table_len > 255 {
|
||||
seg_table = seg_table.split_at(255).0.to_vec();
|
||||
seg_table_len = 255;
|
||||
}
|
||||
|
||||
seg_table.insert(0, seg_table_len as u8);
|
||||
|
||||
let page = p.extend(&*setup);
|
||||
|
||||
let mut p_bytes = p.as_bytes();
|
||||
let seg_count = p_bytes[26] as usize;
|
||||
|
||||
// Replace segment table and checksum
|
||||
p_bytes.splice(26..27 + seg_count, seg_table);
|
||||
p_bytes.splice(22..26, ogg_pager::crc32(&*p_bytes).to_le_bytes().to_vec());
|
||||
|
||||
writer.write_all(&*p_bytes)?;
|
||||
|
||||
if let Some(mut page) = page {
|
||||
page.serial = ser;
|
||||
page.gen_crc();
|
||||
|
||||
writer.write_all(&*page.as_bytes())?;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
p.gen_crc();
|
||||
writer.write_all(&*p.as_bytes())?;
|
||||
}
|
||||
|
||||
writer.write_all(&*remaining)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_to(mut data: &mut File, pages: &mut [Page], sig: &[u8]) -> Result<()> {
|
||||
let first_page = Page::read(&mut data, false)?;
|
||||
|
||||
|
@ -195,46 +68,20 @@ fn write_to(mut data: &mut File, pages: &mut [Page], sig: &[u8]) -> Result<()> {
|
|||
writer.write_all(&*first_page.as_bytes())?;
|
||||
|
||||
let first_md_page = Page::read(&mut data, false)?;
|
||||
is_metadata(&first_md_page, sig)?;
|
||||
verify_signature(&first_md_page, sig)?;
|
||||
|
||||
#[cfg(feature = "format-vorbis")]
|
||||
if sig == VORBIS_COMMENT_HEAD {
|
||||
vorbis_write(data, &mut writer, first_md_page.content, ser, pages)?;
|
||||
vorbis::write_to(data, &mut writer, first_md_page.content, ser, pages)?;
|
||||
}
|
||||
|
||||
#[cfg(feature = "format-opus")]
|
||||
if sig == OPUSTAGS {
|
||||
let reached_md_end: bool;
|
||||
let mut remaining = Vec::new();
|
||||
|
||||
loop {
|
||||
let p = Page::read(&mut data, false)?;
|
||||
|
||||
if p.header_type != 1 {
|
||||
data.seek(SeekFrom::Start(p.start as u64))?;
|
||||
reached_md_end = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if !reached_md_end {
|
||||
return Err(LoftyError::InvalidData("OGG file ends with comment header"));
|
||||
}
|
||||
|
||||
data.read_to_end(&mut remaining)?;
|
||||
|
||||
for mut p in pages.iter_mut() {
|
||||
p.serial = ser;
|
||||
p.gen_crc();
|
||||
|
||||
writer.write_all(&*p.as_bytes())?;
|
||||
}
|
||||
|
||||
writer.write_all(&*remaining)?;
|
||||
opus::write_to(data, &mut writer, ser, pages)?;
|
||||
}
|
||||
|
||||
data.seek(SeekFrom::Start(0))?;
|
||||
data.set_len(0)?;
|
||||
data.set_len(first_page.end as u64)?;
|
||||
data.write_all(&*writer)?;
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -39,42 +39,6 @@ impl Default for OggInnerTag {
|
|||
}
|
||||
}
|
||||
|
||||
impl OggInnerTag {
|
||||
fn read_from<R>(reader: &mut R, format: &OggFormat) -> Result<Self>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
match format {
|
||||
#[cfg(feature = "format-vorbis")]
|
||||
OggFormat::Vorbis => {
|
||||
let tag = ogg::read::read_from(
|
||||
reader,
|
||||
&VORBIS_IDENT_HEAD,
|
||||
&VORBIS_COMMENT_HEAD,
|
||||
OggFormat::Vorbis,
|
||||
)?;
|
||||
let vorbis_tag: OggTag = tag.try_into()?;
|
||||
|
||||
Ok(vorbis_tag.inner)
|
||||
},
|
||||
#[cfg(feature = "format-opus")]
|
||||
OggFormat::Opus => {
|
||||
let tag = ogg::read::read_from(reader, &OPUSHEAD, &OPUSTAGS, OggFormat::Opus)?;
|
||||
let vorbis_tag: OggTag = tag.try_into()?;
|
||||
|
||||
Ok(vorbis_tag.inner)
|
||||
},
|
||||
#[cfg(feature = "format-flac")]
|
||||
OggFormat::Flac => {
|
||||
let tag = metaflac::Tag::read_from(reader)?;
|
||||
let vorbis_tag: OggTag = tag.try_into()?;
|
||||
|
||||
Ok(vorbis_tag.inner)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "format-opus")] {
|
||||
#[derive(LoftyTag)]
|
||||
|
@ -121,8 +85,8 @@ impl TryFrom<OGGTags> for OggTag {
|
|||
comments,
|
||||
pictures: (!pictures.is_empty()).then(|| Cow::from(pictures)),
|
||||
},
|
||||
properties: FileProperties::default(), // TODO
|
||||
_format: TagType::Ogg(inp.3),
|
||||
properties: inp.3,
|
||||
_format: TagType::Ogg(inp.4),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -177,11 +141,33 @@ impl OggTag {
|
|||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
Ok(Self {
|
||||
inner: OggInnerTag::read_from(reader, &format)?,
|
||||
properties: FileProperties::default(), // TODO
|
||||
_format: TagType::Ogg(format),
|
||||
})
|
||||
let tag: Self = match format {
|
||||
#[cfg(feature = "format-vorbis")]
|
||||
OggFormat::Vorbis => {
|
||||
let tag = ogg::read::read_from(
|
||||
reader,
|
||||
&VORBIS_IDENT_HEAD,
|
||||
&VORBIS_COMMENT_HEAD,
|
||||
OggFormat::Vorbis,
|
||||
)?;
|
||||
|
||||
tag.try_into()?
|
||||
},
|
||||
#[cfg(feature = "format-opus")]
|
||||
OggFormat::Opus => {
|
||||
let tag = ogg::read::read_from(reader, &OPUSHEAD, &OPUSTAGS, OggFormat::Opus)?;
|
||||
|
||||
tag.try_into()?
|
||||
},
|
||||
#[cfg(feature = "format-flac")]
|
||||
OggFormat::Flac => {
|
||||
let tag = metaflac::Tag::read_from(reader)?;
|
||||
|
||||
tag.try_into()?
|
||||
},
|
||||
};
|
||||
|
||||
Ok(tag)
|
||||
}
|
||||
|
||||
fn get_value(&self, key: &str) -> Option<&str> {
|
||||
|
|
Loading…
Reference in a new issue