use super::block::{BLOCK_ID_PADDING, BLOCK_ID_PICTURE, BLOCK_ID_VORBIS_COMMENTS, Block};
use super::read::verify_flac;
use crate::config::WriteOptions;
use crate::error::{FileEncodingError, FileParseError, SizeMismatchError, TagParseError};
use crate::id3::{FindId3v2Config, find_id3v2};
use crate::io::VerifiedFile;
use crate::macros::try_vec;
use crate::ogg::tag::VorbisCommentsRef;
use crate::picture::{Picture, PictureInformation};
use crate::tag::{Tag, TagType};
use crate::util::io::FileLike;
use std::borrow::Cow;
use std::io::{Read, Seek};
use std::iter::Peekable;
use byteorder::{LittleEndian, ReadBytesExt};
pub(crate) fn write_to<F>(
file: VerifiedFile<'_, F>,
tag: &Tag,
write_options: WriteOptions,
) -> Result<(), FileEncodingError>
where
F: FileLike,
{
match tag.tag_type() {
TagType::VorbisComments => {
let (vendor, items, pictures) = crate::ogg::tag::create_vorbis_comments_ref(tag);
let mut comments_ref = VorbisCommentsRef {
vendor: Cow::from(vendor),
items,
pictures,
};
write_to_inner(file, &mut comments_ref, write_options)
},
TagType::Id3v2 => {
crate::id3::v2::tag::conversion::Id3v2TagRef::empty().write_to(file, write_options)
},
_ => unreachable!("tag type verified beforehand"),
}
}
pub(crate) fn write_to_inner<'a, F, II, IP>(
file: VerifiedFile<'_, F>,
tag: &mut VorbisCommentsRef<'a, II, IP>,
write_options: WriteOptions,
) -> Result<(), FileEncodingError>
where
F: FileLike,
II: Iterator<Item = (&'a str, &'a str)>,
IP: Iterator<Item = (&'a Picture, PictureInformation)>,
{
let mut file = file.into_inner();
file.rewind()?;
find_id3v2(&mut file, FindId3v2Config::NO_READ_TAG).map_err(TagParseError::from)?;
let mut stream_info = verify_flac(&mut file).map_err(FileParseError::from)?;
let mut is_last_block = stream_info.last;
let mut has_blocks_to_remove = false;
let mut has_padding = false;
stream_info.last = false;
let mut metadata_range = stream_info.start..stream_info.end;
let mut blocks = vec![stream_info];
while !is_last_block {
let mut skip = false;
let mut block = Block::read(&mut file, |ty| match ty {
BLOCK_ID_PICTURE => {
has_blocks_to_remove = true;
skip = true;
false
},
BLOCK_ID_PADDING => {
has_padding = true;
true
},
_ => true,
})
.map_err(FileParseError::from)?;
if block.ty == BLOCK_ID_VORBIS_COMMENTS {
let reader = &mut &block.content[..];
let vendor_len = reader.read_u32::<LittleEndian>()?;
if vendor_len as usize > reader.len() {
return Err(SizeMismatchError.into());
}
let mut vendor_raw = try_vec![0; vendor_len as usize]?;
reader.read_exact(&mut vendor_raw)?;
match String::from_utf8(vendor_raw) {
Ok(vendor_str) => tag.vendor = Cow::Owned(vendor_str),
Err(_) => {
log::warn!("FLAC vendor string is not valid UTF-8, not re-using");
tag.vendor = Cow::Borrowed("");
},
}
has_blocks_to_remove = true;
skip = true;
}
is_last_block = block.last;
metadata_range.end = block.end;
if !skip {
block.last = false;
blocks.push(block);
}
}
let mut comments_peek = (&mut tag.items).peekable();
let mut pictures_peek = (&mut tag.pictures).peekable();
let has_comments = comments_peek.peek().is_some();
let has_pictures = pictures_peek.peek().is_some();
if !has_blocks_to_remove && !has_comments && !has_pictures {
log::debug!("Nothing to do");
return Ok(());
}
let will_write_padding = !has_padding && write_options.preferred_padding.is_some();
let metadata_blocks = encode_tag(&tag.vendor, comments_peek, pictures_peek)?;
blocks.extend(metadata_blocks);
if will_write_padding && let Some(preferred_padding) = write_options.preferred_padding {
log::warn!("File is missing a PADDING block. Adding one");
let metadata_len = blocks
.iter()
.map(|block| u64::from(block.len()))
.sum::<u64>();
let old_metadata_len = metadata_range.end - metadata_range.start;
let available_padding = old_metadata_len
.saturating_sub(metadata_len)
.saturating_sub(Block::BLOCK_HEADER_SIZE as u64);
let padding_len = available_padding
.max(u64::from(preferred_padding.get()))
.min(u64::from(Block::MAX_CONTENT_SIZE));
let padding_len = usize::try_from(padding_len).map_err(|_| SizeMismatchError)?;
blocks.push(Block::new_padding(padding_len)?);
}
if let Some(block) = blocks.last_mut() {
block.last = true;
}
let mut encoded_metadata = Vec::new();
for block in blocks {
block.write_to(&mut encoded_metadata)?;
log::trace!(
"Wrote a block (ty: {}, size: {})",
block.ty,
block.content.len()
);
}
file.splice(metadata_range, &encoded_metadata)?;
Ok(())
}
fn encode_tag<'a, II, IP>(
vendor: &str,
mut comments_peek: Peekable<&mut II>,
pictures_peek: Peekable<&mut IP>,
) -> Result<Vec<Block>, FileEncodingError>
where
II: Iterator<Item = (&'a str, &'a str)>,
IP: Iterator<Item = (&'a Picture, PictureInformation)>,
{
let mut metadata_blocks = Vec::new();
if comments_peek.peek().is_some() {
metadata_blocks.push(Block::new_comments(vendor, &mut comments_peek)?);
}
for (picture, info) in pictures_peek {
metadata_blocks.push(Block::new_picture(picture, info)?);
}
Ok(metadata_blocks)
}