use crate::config::ParsingMode;
use crate::error::{Id3v2Error, Id3v2ErrorKind, Result};
use crate::macros::try_vec;
use crate::util::text::{decode_text, encode_text, TextDecodeOptions, TextEncoding};
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
use std::io::Read;
use byteorder::{BigEndian, ReadBytesExt};
#[repr(u8)]
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Hash)]
#[allow(missing_docs)]
pub enum ChannelType {
Other = 0,
MasterVolume = 1,
FrontRight = 2,
FrontLeft = 3,
BackRight = 4,
BackLeft = 5,
FrontCentre = 6,
BackCentre = 7,
Subwoofer = 8,
}
impl ChannelType {
pub fn from_u8(byte: u8) -> Option<Self> {
match byte {
0 => Some(Self::Other),
1 => Some(Self::MasterVolume),
2 => Some(Self::FrontRight),
3 => Some(Self::FrontLeft),
4 => Some(Self::BackRight),
5 => Some(Self::BackLeft),
6 => Some(Self::FrontCentre),
7 => Some(Self::BackCentre),
8 => Some(Self::Subwoofer),
_ => None,
}
}
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct ChannelInformation {
pub channel_type: ChannelType,
pub volume_adjustment: i16,
pub bits_representing_peak: u8,
pub peak_volume: Option<Vec<u8>>,
}
#[derive(Clone, Debug, Eq)]
pub struct RelativeVolumeAdjustmentFrame {
pub identification: String,
pub channels: HashMap<ChannelType, ChannelInformation>,
}
impl PartialEq for RelativeVolumeAdjustmentFrame {
fn eq(&self, other: &Self) -> bool {
self.identification == other.identification
}
}
impl Hash for RelativeVolumeAdjustmentFrame {
fn hash<H: Hasher>(&self, state: &mut H) {
self.identification.hash(state)
}
}
impl RelativeVolumeAdjustmentFrame {
pub fn parse<R>(reader: &mut R, parse_mode: ParsingMode) -> Result<Option<Self>>
where
R: Read,
{
let identification = decode_text(
reader,
TextDecodeOptions::new()
.encoding(TextEncoding::Latin1)
.terminated(true),
)?
.content;
let mut channels = HashMap::new();
while let Ok(channel_type_byte) = reader.read_u8() {
let channel_type;
match ChannelType::from_u8(channel_type_byte) {
Some(channel_ty) => channel_type = channel_ty,
None if parse_mode == ParsingMode::BestAttempt => channel_type = ChannelType::Other,
_ => return Err(Id3v2Error::new(Id3v2ErrorKind::BadRva2ChannelType).into()),
}
let volume_adjustment = reader.read_i16::<BigEndian>()?;
let bits_representing_peak = reader.read_u8()?;
let mut peak_volume = None;
if bits_representing_peak > 0 {
let bytes_representing_peak = (u16::from(bits_representing_peak) + 7) >> 3;
let mut peak_volume_bytes = try_vec![0; bytes_representing_peak as usize];
reader.read_exact(&mut peak_volume_bytes)?;
peak_volume = Some(peak_volume_bytes);
}
channels.insert(
channel_type,
ChannelInformation {
channel_type,
volume_adjustment,
bits_representing_peak,
peak_volume,
},
);
}
Ok(Some(Self {
identification,
channels,
}))
}
pub fn as_bytes(&self) -> Vec<u8> {
let mut content = Vec::new();
content.extend(encode_text(
&self.identification,
TextEncoding::Latin1,
true,
));
for (channel_type, info) in &self.channels {
let mut bits_representing_peak = info.bits_representing_peak;
let expected_peak_byte_length = (u16::from(bits_representing_peak) + 7) >> 3;
content.push(*channel_type as u8);
content.extend(info.volume_adjustment.to_be_bytes());
if info.peak_volume.is_none() {
content.push(0);
continue;
}
if let Some(peak) = &info.peak_volume {
if peak.len() > expected_peak_byte_length as usize {
bits_representing_peak = 0;
for b in peak.iter().copied().take(32) {
bits_representing_peak += b.leading_ones() as u8;
}
}
content.push(bits_representing_peak);
content.extend(peak.iter().take(32));
}
}
content
}
}
#[cfg(test)]
mod tests {
use crate::config::ParsingMode;
use crate::id3::v2::{ChannelInformation, ChannelType, RelativeVolumeAdjustmentFrame};
use std::collections::HashMap;
use std::io::Read;
fn expected() -> RelativeVolumeAdjustmentFrame {
let mut channels = HashMap::new();
channels.insert(
ChannelType::MasterVolume,
ChannelInformation {
channel_type: ChannelType::MasterVolume,
volume_adjustment: 15,
bits_representing_peak: 4,
peak_volume: Some(vec![4]),
},
);
channels.insert(
ChannelType::FrontLeft,
ChannelInformation {
channel_type: ChannelType::FrontLeft,
volume_adjustment: 21,
bits_representing_peak: 0,
peak_volume: None,
},
);
channels.insert(
ChannelType::Subwoofer,
ChannelInformation {
channel_type: ChannelType::Subwoofer,
volume_adjustment: 30,
bits_representing_peak: 11,
peak_volume: Some(vec![0xFF, 0x07]),
},
);
RelativeVolumeAdjustmentFrame {
identification: String::from("Surround sound"),
channels,
}
}
#[test]
fn rva2_decode() {
let cont = crate::tag::utils::test_utils::read_path("tests/tags/assets/id3v2/test.rva2");
let parsed_rva2 = RelativeVolumeAdjustmentFrame::parse(&mut &cont[..], ParsingMode::Strict)
.unwrap()
.unwrap();
assert_eq!(parsed_rva2, expected());
}
#[test]
#[allow(unstable_name_collisions)]
fn rva2_encode() {
let encoded = expected().as_bytes();
let expected_bytes =
crate::tag::utils::test_utils::read_path("tests/tags/assets/id3v2/test.rva2");
assert_eq!(encoded.len(), expected_bytes.len());
let mut needles = vec![
&[1, 0, 15, 4, 4][..], &[8, 0, 30, 11, 255, 7][..], &[3, 0, 21, 0][..], ];
let encoded_reader = &mut &encoded[..];
let mut ident = [0; 15];
encoded_reader.read_exact(&mut ident).unwrap();
assert_eq!(ident, b"Surround sound\0"[..]);
loop {
if needles.is_empty() {
break;
}
let mut remove_idx = None;
for (idx, needle) in needles.iter().enumerate() {
if encoded_reader.starts_with(needle) {
std::io::copy(
&mut encoded_reader.take(needle.len() as u64),
&mut std::io::sink(),
)
.unwrap();
remove_idx = Some(idx);
break;
}
}
let Some(remove_idx) = remove_idx else {
unreachable!("Unexpected data in RVA2 frame: {:?}", &encoded);
};
needles.remove(remove_idx);
}
assert!(needles.is_empty());
}
}