use crate::prelude::*;
use crate::utils::*;
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use std::collections::HashMap;
use std::fmt;
use std::fmt::Display;
use std::io::{Read, Write};
pub struct BlockVorbisComment {
pub vendor_string: String,
pub comments: Vec<UserComment>,
}
impl BlockVorbisComment {
pub fn push(&mut self, comment: UserComment) {
self.comments.push(comment);
}
#[inline]
pub fn len(&self) -> usize {
self.comments.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn clear(&mut self) {
self.comments.clear()
}
pub fn to_map(&self) -> HashMap<String, &UserComment> {
let mut map: HashMap<_, _> = Default::default();
for comment in self.comments.iter() {
if !(map.contains_key(&comment.key()) && comment.value().is_empty()) {
map.insert(comment.key(), comment);
}
}
map
}
}
impl Decode for BlockVorbisComment {
fn from_reader<R: Read>(reader: &mut R) -> Result<Self> {
let vendor_length = reader.read_u32::<LittleEndian>()?;
let vendor_string = take_string(reader, vendor_length as usize)?;
let comment_number = reader.read_u32::<LittleEndian>()?;
let mut comments = Vec::with_capacity(comment_number as usize);
for _ in 0..comment_number {
comments.push(UserComment::from_reader(reader)?);
}
Ok(BlockVorbisComment {
vendor_string,
comments,
})
}
}
#[cfg(feature = "async")]
#[async_trait::async_trait]
impl AsyncDecode for BlockVorbisComment {
async fn from_async_reader<R>(reader: &mut R) -> Result<Self>
where
R: AsyncRead + Unpin + Send,
{
let vendor_length = reader.read_u32_le().await?;
let vendor_string = take_string_async(reader, vendor_length as usize).await?;
let comment_number = reader.read_u32_le().await?;
let mut comments = Vec::with_capacity(comment_number as usize);
for _ in 0..comment_number {
comments.push(UserComment::from_async_reader(reader).await?);
}
Ok(BlockVorbisComment {
vendor_string,
comments,
})
}
}
impl Encode for BlockVorbisComment {
fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_u32::<LittleEndian>(self.vendor_string.len() as u32)?;
writer.write_all(self.vendor_string.as_bytes())?;
writer.write_u32::<LittleEndian>(self.comments.len() as u32)?;
for comment in self.comments.iter() {
comment.write_to(writer)?;
}
Ok(())
}
}
impl fmt::Debug for BlockVorbisComment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut prefix = "".to_owned();
if let Some(width) = f.width() {
prefix = " ".repeat(width);
}
writeln!(
f,
"{prefix}vendor string: {}",
self.vendor_string,
prefix = prefix
)?;
writeln!(f, "{prefix}comments: {}", self.len(), prefix = prefix)?;
for (i, c) in self.comments.iter().enumerate() {
write!(f, "{}", prefix)?;
writeln!(
f,
"{prefix}comment[{}]: {}={}",
i,
c.key_raw(),
c.value(),
prefix = prefix
)?;
}
Ok(())
}
}
impl fmt::Display for BlockVorbisComment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for c in self.comments.iter() {
writeln!(f, "{}", c.comment)?;
}
Ok(())
}
}
#[derive(Debug)]
pub struct UserComment {
comment: String,
value_offset: Option<usize>,
}
impl UserComment {
pub fn new(comment: String) -> Self {
let value_offset = comment.find('=');
Self {
comment,
value_offset,
}
}
pub fn key(&self) -> String {
self.key_raw().to_ascii_uppercase()
}
pub fn key_raw(&self) -> &str {
match self.value_offset {
Some(offset) => &self.comment[..offset],
None => &self.comment,
}
}
pub fn is_key_uppercase(&self) -> bool {
let key = match self.value_offset {
Some(offset) => &self.comment[..offset],
None => &self.comment,
};
key.chars().all(|c| !c.is_ascii_lowercase())
}
pub fn value(&self) -> &str {
match self.value_offset {
Some(offset) => &self.comment[offset + 1..],
None => &self.comment[self.comment.len()..],
}
}
pub fn len(&self) -> usize {
self.comment.len()
}
pub fn entry(&self) -> String {
self.comment.clone()
}
pub fn is_empty(&self) -> bool {
self.comment.is_empty()
}
pub fn clear(&mut self) {
self.comment.clear();
self.value_offset = None;
}
}
impl Decode for UserComment {
fn from_reader<R: Read>(reader: &mut R) -> Result<Self> {
let length = reader.read_u32::<LittleEndian>()?;
let comment = take_string(reader, length as usize)?;
Ok(UserComment::new(comment))
}
}
#[cfg(feature = "async")]
#[async_trait::async_trait]
impl AsyncDecode for UserComment {
async fn from_async_reader<R>(reader: &mut R) -> Result<Self>
where
R: AsyncRead + Unpin + Send,
{
let length = reader.read_u32_le().await?;
let comment = take_string_async(reader, length as usize).await?;
Ok(UserComment::new(comment))
}
}
impl Encode for UserComment {
fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
writer.write_u32::<LittleEndian>(self.comment.len() as u32)?;
writer.write_all(self.comment.as_bytes())?;
Ok(())
}
}
pub trait UserCommentExt {
fn title<S>(value: S) -> Self
where
S: Display;
fn artist<S>(value: S) -> Self
where
S: Display;
fn album<S>(value: S) -> Self
where
S: Display;
fn date<S>(value: S) -> Self
where
S: Display;
fn track_number<S>(value: S) -> Self
where
S: Display;
fn track_total<S>(value: S) -> Self
where
S: Display;
fn disc_number<S>(value: S) -> Self
where
S: Display;
fn disc_total<S>(value: S) -> Self
where
S: Display;
fn album_artist<S>(value: S) -> Self
where
S: Display;
}
impl UserCommentExt for UserComment {
fn title<S>(value: S) -> Self
where
S: Display,
{
Self::new(format!("TITLE={}", value))
}
fn artist<S>(value: S) -> Self
where
S: Display,
{
Self::new(format!("ARTIST={}", value))
}
fn album<S>(value: S) -> Self
where
S: Display,
{
Self::new(format!("ALBUM={}", value))
}
fn date<S>(value: S) -> Self
where
S: Display,
{
Self::new(format!("DATE={}", value))
}
fn track_number<S>(value: S) -> Self
where
S: Display,
{
Self::new(format!("TRACKNUMBER={}", value))
}
fn track_total<S>(value: S) -> Self
where
S: Display,
{
Self::new(format!("TRACKTOTAL={}", value))
}
fn disc_number<S>(value: S) -> Self
where
S: Display,
{
Self::new(format!("DISCNUMBER={}", value))
}
fn disc_total<S>(value: S) -> Self
where
S: Display,
{
Self::new(format!("DISCTOTAL={}", value))
}
fn album_artist<S>(value: S) -> Self
where
S: Display,
{
Self::new(format!("ALBUMARTIST={}", value))
}
}