use crate::vorbis_tag::VORBIS;
use crate::{Error, Picture, Result};
#[cfg(feature = "format-flac")]
use metaflac::BlockType;
#[cfg(feature = "format-vorbis")]
use ogg::PacketWriteEndInfo;
use std::borrow::{BorrowMut, Cow};
use std::collections::HashMap;
use std::fs::File;
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
#[cfg(any(feature = "format-vorbis", feature = "format-opus"))]
pub(crate) fn vorbis_generic(
file: &mut File,
sig: &[u8],
vendor: &str,
comments: &HashMap<String, String>,
) -> Result<()> {
let mut packet = Vec::new();
packet.extend(sig.iter());
let comments: Vec<(String, String)> = comments
.iter()
.map(|(a, b)| (a.to_string(), b.to_string()))
.collect();
let vendor_len = vendor.len() as u32;
packet.extend(vendor_len.to_le_bytes().iter());
packet.extend(vendor.as_bytes().iter());
let comments_len = comments.len() as u32;
packet.extend(comments_len.to_le_bytes().iter());
let mut comment_str = Vec::new();
for (a, b) in comments {
comment_str.push(format!("{}={}", a, b));
let last = comment_str.last().unwrap();
let len = last.as_bytes().len() as u32;
packet.extend(len.to_le_bytes().iter());
packet.extend(last.as_bytes().iter());
}
if sig == VORBIS {
packet.push(1);
}
let mut file_bytes = Vec::new();
file.read_to_end(&mut file_bytes)?;
let data = if sig == VORBIS {
ogg(Cursor::new(file_bytes), &*packet)?
} else {
opus(Cursor::new(file_bytes), &*packet)?
};
file.seek(SeekFrom::Start(0))?;
file.set_len(0)?;
file.write_all(&data)?;
Ok(())
}
#[cfg(feature = "format-flac")]
pub(crate) fn flac<T>(
mut data: T,
vendor: &str,
comments: &HashMap<String, String>,
pictures: &Option<Cow<'static, [Picture]>>,
) -> Result<()>
where
T: Read + Write + Seek,
{
let mut tag = metaflac::Tag::read_from(&mut data)?;
tag.remove_blocks(BlockType::VorbisComment);
tag.remove_blocks(BlockType::Picture);
let mut padding = None;
if let Some(pad) = tag.get_blocks(BlockType::Padding).last() {
padding = Some(pad.clone());
tag.remove_blocks(BlockType::Padding)
}
let mut pics_final: Vec<metaflac::Block> = Vec::new();
let mut comment_collection: HashMap<String, Vec<String>> = HashMap::new();
if let Some(pics) = pictures.clone() {
for pic in pics.iter() {
pics_final.push(metaflac::Block::Picture(
metaflac::block::Picture::from_bytes(&*pic.as_apic_bytes())?,
))
}
}
for (k, v) in comments.clone() {
comment_collection.insert(k, vec![v]);
}
let comments = metaflac::Block::VorbisComment(metaflac::block::VorbisComment {
vendor_string: vendor.to_string(),
comments: comment_collection,
});
tag.push_block(comments);
for pic in pics_final {
tag.push_block(pic)
}
if let Some(padding) = padding {
tag.push_block(padding)
}
data.seek(SeekFrom::Start(0))?;
tag.write_to(&mut data)?;
Ok(())
}
#[cfg(feature = "format-vorbis")]
pub(crate) fn ogg<T>(data: T, packet: &[u8]) -> Result<Vec<u8>>
where
T: Read + Seek,
{
let mut c = Cursor::new(Vec::new());
let mut reader = ogg::PacketReader::new(data);
let mut writer = ogg::PacketWriter::new(&mut c);
let mut replaced = false;
loop {
match reader.read_packet()? {
None => break,
Some(mut p) => {
let inf = if p.last_in_stream() {
PacketWriteEndInfo::EndStream
} else if p.last_in_page() {
PacketWriteEndInfo::EndPage
} else {
PacketWriteEndInfo::NormalPacket
};
if !replaced {
let comment_header = lewton::header::read_header_comment(&p.data);
if comment_header.is_ok() {
p.data = packet.to_vec();
replaced = true;
}
}
writer.write_packet(
p.data.clone().into_boxed_slice(),
p.stream_serial(),
inf,
p.absgp_page(),
)?;
if p.last_in_stream() && p.last_in_page() {
break;
}
},
}
}
c.seek(SeekFrom::Start(0))?;
Ok(c.into_inner())
}
#[cfg(feature = "format-opus")]
struct Page {
pub size_idx: usize,
pub content: Vec<u8>,
pub header_type: u8,
pub start: usize,
pub end: usize,
}
#[cfg(feature = "format-opus")]
pub(crate) fn opus<T>(mut data: T, packet: &[u8]) -> Result<Vec<u8>>
where
T: Read + Seek,
{
fn read_page<V>(mut data: V) -> Result<Page>
where
V: Read + Seek,
{
let mut sig = [0; 4];
data.read_exact(&mut sig)?;
if &sig != b"OggS" {
return Err(Error::UnknownFormat);
}
let mut info = [0; 2];
data.read_exact(&mut info)?;
let header_type = info[1];
let mut page = [0; 21];
data.read_exact(&mut page)?;
let size_idx = data.seek(SeekFrom::Current(0))? as usize;
let mut segment_table = vec![0; page[20] as usize];
data.read_exact(&mut segment_table)?;
let start = data.seek(SeekFrom::Current(0))? as usize;
let mut content = vec![0; segment_table.iter().map(|&b| b as usize).sum()];
data.read_exact(&mut content)?;
let end = data.seek(SeekFrom::Current(0))? as usize;
Ok(Page {
size_idx,
content,
header_type,
start,
end,
})
}
let first_page = read_page(&mut data)?;
let head = first_page.content;
let (ident, head) = head.split_at(8);
if ident != b"OpusHead" {
return Err(Error::UnknownFormat);
}
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)?;
}
let second_page = read_page(&mut data)?;
let tags = second_page.content;
let size_pos = second_page.size_idx;
let start = second_page.start;
let end = second_page.end;
if &tags[0..8] != b"OpusTags" {
return Err(Error::UnknownFormat);
}
let last_len = (packet.len() % 255) as u8;
let needed = (packet.len() / 255) + 1;
let mut segments = Vec::with_capacity(needed);
for i in 0..needed {
if i + 1 < needed {
segments.push(255)
} else {
segments.push(last_len)
}
}
data.seek(SeekFrom::Start(0))?;
let mut content = Vec::new();
data.read_to_end(&mut content)?;
content.splice(start..end, packet.to_vec());
content.insert(size_pos - 1, needed as u8);
content.remove(size_pos);
content.splice(size_pos..start, segments);
Ok(content)
}
#[cfg(feature = "format-riff")]
pub(crate) fn riff(data: &mut File, metadata: HashMap<String, String>) -> Result<()> {
let mut packet = Vec::new();
packet.extend(riff::LIST_ID.value.iter());
let fourcc = "INFO";
packet.extend(fourcc.as_bytes().iter());
for (k, v) in metadata {
if let Some(fcc) = super::read::key_to_fourcc(&*k) {
let mut val = v.as_bytes().to_vec();
if val.len() % 2 != 0 {
val.push(0)
}
let size = val.len() as u32;
packet.extend(fcc.iter());
packet.extend(size.to_le_bytes().iter());
packet.extend(val.iter());
}
}
let mut file_bytes = Vec::new();
std::io::copy(data.borrow_mut(), &mut file_bytes)?;
let len = (packet.len() - 4) as u32;
let size = len.to_le_bytes();
#[allow(clippy::needless_range_loop)]
for i in 0..4 {
packet.insert(i + 4, size[i]);
}
let mut file = Cursor::new(file_bytes);
let chunk = riff::Chunk::read(&mut file, 0)?;
let (mut list_pos, mut list_len): (Option<u32>, Option<u32>) = (None, None);
if chunk.id() != riff::RIFF_ID {
return Err(Error::Wav(
"This file does not contain a RIFF chunk".to_string(),
));
}
for child in chunk.iter(&mut file) {
if child.id() == riff::LIST_ID {
list_pos = Some(child.offset() as u32);
list_len = Some(child.len());
}
}
file.seek(SeekFrom::Start(0))?;
let mut content = Vec::new();
std::io::copy(&mut file, &mut content)?;
if let (Some(list_pos), Some(list_len)) = (list_pos, list_len) {
let list_end = (list_pos + list_len) as usize;
let _ = content.splice(list_pos as usize..list_end, packet);
let total_size = (content.len() - 8) as u32;
let _ = content.splice(4..8, total_size.to_le_bytes().to_vec());
data.seek(SeekFrom::Start(0))?;
data.set_len(0)?;
data.write_all(&*content)?;
Ok(())
} else {
Err(Error::Wav(
"This file does not contain an INFO chunk".to_string(),
))
}
}