pile-audio refactor
This commit is contained in:
194
crates/pile-flac/src/vorbiscomment.rs
Normal file
194
crates/pile-flac/src/vorbiscomment.rs
Normal file
@@ -0,0 +1,194 @@
|
||||
//! Decode and write Vorbis comment blocks
|
||||
|
||||
use base64::Engine;
|
||||
use smartstring::{LazyCompact, SmartString};
|
||||
use std::{
|
||||
io::{Cursor, Read, Write},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use super::tagtype::TagType;
|
||||
use crate::{
|
||||
FlacDecodeError, FlacEncodeError,
|
||||
blocks::{FlacMetablockDecode, FlacMetablockEncode, FlacPictureBlock},
|
||||
};
|
||||
|
||||
/// A decoded vorbis comment block
|
||||
#[derive(Debug)]
|
||||
pub struct VorbisComment {
|
||||
/// This comment's vendor string
|
||||
pub vendor: SmartString<LazyCompact>,
|
||||
|
||||
/// List of (tag, value)
|
||||
/// Repeated tags are allowed!
|
||||
pub comments: Vec<(TagType, SmartString<LazyCompact>)>,
|
||||
|
||||
/// A list of pictures found in this comment
|
||||
pub pictures: Vec<FlacPictureBlock>,
|
||||
}
|
||||
|
||||
impl VorbisComment {
|
||||
/// Try to decode the given data as a vorbis comment block
|
||||
pub fn decode(data: &[u8]) -> Result<Self, FlacDecodeError> {
|
||||
let mut d = Cursor::new(data);
|
||||
|
||||
// This is re-used whenever we need to read four bytes
|
||||
let mut block = [0u8; 4];
|
||||
|
||||
let vendor = {
|
||||
d.read_exact(&mut block)?;
|
||||
let length = u32::from_le_bytes(block);
|
||||
|
||||
#[expect(clippy::expect_used)]
|
||||
let mut text = vec![
|
||||
0u8;
|
||||
length
|
||||
.try_into()
|
||||
.expect("vendor length does not fit into usize")
|
||||
];
|
||||
|
||||
d.read_exact(&mut text)?;
|
||||
String::from_utf8(text)?
|
||||
};
|
||||
|
||||
d.read_exact(&mut block)?;
|
||||
|
||||
#[expect(clippy::expect_used)]
|
||||
let n_comments: usize = u32::from_le_bytes(block)
|
||||
.try_into()
|
||||
.expect("comment count does not fit into usize");
|
||||
|
||||
let mut comments = Vec::new();
|
||||
let mut pictures = Vec::new();
|
||||
for _ in 0..n_comments {
|
||||
let comment = {
|
||||
d.read_exact(&mut block)?;
|
||||
|
||||
let length = u32::from_le_bytes(block);
|
||||
|
||||
#[expect(clippy::expect_used)]
|
||||
let mut text = vec![
|
||||
0u8;
|
||||
length
|
||||
.try_into()
|
||||
.expect("comment length does not fit into usize")
|
||||
];
|
||||
|
||||
d.read_exact(&mut text)?;
|
||||
|
||||
String::from_utf8(text)?
|
||||
};
|
||||
let (var, val) = comment
|
||||
.split_once('=')
|
||||
.ok_or(FlacDecodeError::MalformedCommentString(comment.clone()))?;
|
||||
|
||||
if !val.is_empty() {
|
||||
if var.to_uppercase() == "METADATA_BLOCK_PICTURE" {
|
||||
pictures.push(FlacPictureBlock::decode(
|
||||
&base64::prelude::BASE64_STANDARD
|
||||
.decode(val)
|
||||
.map_err(FlacDecodeError::MalformedPicture)?,
|
||||
)?);
|
||||
} else {
|
||||
// Make sure empty strings are saved as "None"
|
||||
comments.push((
|
||||
TagType::from_str(var).unwrap_or(TagType::Other(var.into())),
|
||||
val.into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
vendor: vendor.into(),
|
||||
comments,
|
||||
pictures,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl VorbisComment {
|
||||
/// Get the number of bytes that `encode()` will write.
|
||||
#[expect(clippy::expect_used)]
|
||||
pub fn get_len(&self) -> u32 {
|
||||
let mut sum: u32 = 0;
|
||||
sum += u32::try_from(self.vendor.len()).expect("vendor length does not fit into u32") + 4;
|
||||
sum += 4;
|
||||
|
||||
for (tagtype, value) in &self.comments {
|
||||
let tagtype_str = tagtype.to_vorbis_string();
|
||||
let str = format!("{tagtype_str}={value}");
|
||||
sum +=
|
||||
4 + u32::try_from(str.len()).expect("comment string length does not fit into u32");
|
||||
}
|
||||
|
||||
for p in &self.pictures {
|
||||
// Compute b64 len
|
||||
let mut x = p.get_len();
|
||||
if x % 3 != 0 {
|
||||
x -= x % 3;
|
||||
x += 3;
|
||||
}
|
||||
|
||||
#[expect(clippy::integer_division)]
|
||||
{
|
||||
sum += 4 * (x / 3);
|
||||
}
|
||||
|
||||
// Add "METADATA_BLOCK_PICTURE="
|
||||
sum += 23;
|
||||
|
||||
// Add length bytes
|
||||
sum += 4;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
/// Try to encode this vorbis comment
|
||||
#[expect(clippy::expect_used)]
|
||||
pub fn encode(&self, target: &mut impl Write) -> Result<(), FlacEncodeError> {
|
||||
target.write_all(
|
||||
&u32::try_from(self.vendor.len())
|
||||
.expect("vendor length does not fit into u32")
|
||||
.to_le_bytes(),
|
||||
)?;
|
||||
target.write_all(self.vendor.as_bytes())?;
|
||||
|
||||
target.write_all(
|
||||
&u32::try_from(self.comments.len() + self.pictures.len())
|
||||
.expect("total comment count does not fit into u32")
|
||||
.to_le_bytes(),
|
||||
)?;
|
||||
|
||||
for (tagtype, value) in &self.comments {
|
||||
let tagtype_str = tagtype.to_vorbis_string();
|
||||
let str = format!("{tagtype_str}={value}");
|
||||
target.write_all(
|
||||
&u32::try_from(str.len())
|
||||
.expect("comment string length does not fit into u32")
|
||||
.to_le_bytes(),
|
||||
)?;
|
||||
target.write_all(str.as_bytes())?;
|
||||
}
|
||||
|
||||
for p in &self.pictures {
|
||||
let mut pic_data = Vec::new();
|
||||
p.encode(false, false, &mut pic_data)?;
|
||||
|
||||
let pic_string = format!(
|
||||
"METADATA_BLOCK_PICTURE={}",
|
||||
&base64::prelude::BASE64_STANDARD.encode(&pic_data)
|
||||
);
|
||||
|
||||
target.write_all(
|
||||
&u32::try_from(pic_string.len())
|
||||
.expect("picture string length does not fit into u32")
|
||||
.to_le_bytes(),
|
||||
)?;
|
||||
target.write_all(pic_string.as_bytes())?;
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user