use bitflags::bitflags;
use byteorder::{BE, LE, ReadBytesExt, WriteBytesExt};
use std::io;
use std::io::{Read, Seek};
use std::num::{NonZero, NonZeroU32};
use std::ops::Deref;
use strum::Display;
use crate::ascii_str::{self, ArrayList, AsciiArray, AsciiStr};
use crate::bit_io::{BitRead, BitReader, BitWrite};
use crate::bits::Bitset;
use crate::num::*;
use crate::{Decode, Encode};
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("IO: {0}")]
Io(#[from] io::Error),
#[error("The metadata block type is 127, which is forbidden")]
ForbiddenBlockType,
#[error("Block size {1} is invalid for block type {0}")]
InvalidBlockSize(BlockType, U24),
#[error("A reserved block type was used")]
ReservedBlockType,
#[error("The picture's type is reserved")]
ReservedPictureType,
#[error(
"{0} is not contained within the valid range `4..=32`: The streaminfo block indicated an invalid bits_per_channel"
)]
InvalidBitsPerSample(U5),
#[error("{0} is an invalid maximum block size")]
InvalidMaxBlockSize(u16),
#[error("{0} is an invalid minimum block size")]
InvalidMinBlockSize(u16),
#[error("{0}")]
InvalidUtf8String(#[from] std::string::FromUtf8Error),
#[error("{0}")]
InvalidUtf8(#[from] std::str::Utf8Error),
#[error("{0}")]
ParseInt(#[from] std::num::ParseIntError),
#[error("Invalid vorbis comment")]
InvalidVorbisComment,
#[error("Vorbis comment key contains invalid ASCII")]
CommentKeyNotAscii,
#[error("Invalid cuesheet")]
InvalidCuesheet,
#[error("Invalid cuesheet track")]
InvalidCuesheetTrack,
#[error("Invalid cuesheet track index points")]
InvalidCuesheetIndexPoints,
#[error("Invalid cuesheet track index point")]
InvalidCuesheetIndexPoint,
#[error("Invalid channel mask")]
InvalidChannelMask,
#[error("Non-ASCII ISRC in cuesheet track")]
NonAscii(#[from] ascii_str::NonAscii),
#[error("The lengths coded within the metadata block exceed the block length")]
BlockExceededLength,
#[error("The vendor string is too long for a vorbis comment block")]
VendorTooLong,
#[error("The number of user comments is too long for a vorbis comment block")]
UserCommentsTooLong,
#[error("The user comment string is too long for a vorbis comment block")]
UserComment,
#[error("There are too many cuesheet tracks (must be less than u8::MAX)")]
CuesheetTrackCount,
#[error("Picture description is too large (was > u32::MAX)")]
OverlongDescription,
#[error("Picture data is too large (was > u32::MAX)")]
OverlongPictureData,
}
#[derive(Debug, Clone, Copy, Eq, Display, PartialEq)]
#[repr(u8)]
pub enum BlockType {
Streaminfo = 0,
Padding,
Application,
SeekTable,
VorbisComment,
Cuesheet,
Picture,
}
impl TryFrom<u8> for BlockType {
type Error = Error;
#[inline]
fn try_from(value: u8) -> Result<Self, Self::Error> {
Ok(match value {
0 => Self::Streaminfo,
1 => Self::Padding,
2 => Self::Application,
3 => Self::SeekTable,
4 => Self::VorbisComment,
5 => Self::Cuesheet,
6 => Self::Picture,
127 => return Err(Error::ForbiddenBlockType),
_ => return Err(Error::ReservedBlockType),
})
}
}
impl From<BlockType> for u8 {
#[inline]
fn from(value: BlockType) -> Self {
match value {
BlockType::Streaminfo => 0,
BlockType::Padding => 1,
BlockType::Application => 2,
BlockType::SeekTable => 3,
BlockType::VorbisComment => 4,
BlockType::Cuesheet => 5,
BlockType::Picture => 6,
}
}
}
impl Encode for BlockType {
type Error = Error;
fn encode<W: BitWrite>(&self, writer: &mut W, _opt: ()) -> Result<(), Self::Error> {
writer.write_bits(u8::from(*self).into(), 7)?;
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub struct BlockHeader {
pub is_final: bool,
pub kind: BlockType,
pub size: U24,
}
impl BlockHeader {
#[inline]
pub fn block_type_num_from_u8(byte: u8) -> Result<BlockType, Error> {
BlockType::try_from(byte.get_bit_range_msb(1, 7))
}
}
impl Decode<()> for BlockHeader {
type Error = Error;
fn decode<R: BitRead + Seek>(reader: &mut R, _opt: ()) -> Result<Self, Error> {
let kind = reader.read_u8()?;
let size = U24::new(reader.read_u24::<BE>()?).expect("this is a valid u24");
let is_final = kind.get_bit_msb(0);
let kind = Self::block_type_num_from_u8(kind)?;
Ok(Self {
is_final,
kind,
size,
})
}
}
impl Encode for BlockHeader {
type Error = Error;
fn encode<W: BitWrite>(&self, writer: &mut W, _opt: ()) -> Result<(), Self::Error> {
debug_assert!(writer.is_byte_aligned());
writer.write_bit(self.is_final)?;
self.kind.encode(writer, ())?;
writer.write_bits(self.size.inner().into(), 24)?;
debug_assert!(writer.is_byte_aligned());
Ok(())
}
}
#[derive(Clone)]
pub struct ReservedData(pub Vec<u8>);
impl std::fmt::Debug for ReservedData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ReservedData").finish_non_exhaustive()
}
}
#[derive(Debug, Clone)]
pub enum BlockData {
Streaminfo(Streaminfo),
Padding,
Application(Application),
SeekTable(SeekTable),
VorbisComment(VorbisComment),
Cuesheet(Cuesheet),
Picture(Picture),
}
#[derive(Debug)]
pub struct Block {
pub header: BlockHeader,
pub data: BlockData,
}
impl Decode<()> for Block {
type Error = Error;
fn decode<R: BitRead + Seek>(reader: &mut R, _opt: ()) -> Result<Self, Error> {
let header = BlockHeader::decode(reader, ())?;
let invalid_block_size = Err(Error::InvalidBlockSize(header.kind, header.size));
let data = match header.kind {
BlockType::Streaminfo if header.size != 34 => return invalid_block_size,
BlockType::VorbisComment if header.size < 8 => return invalid_block_size,
BlockType::Cuesheet if header.size < 432 => return invalid_block_size,
BlockType::Picture if header.size < 32 => return invalid_block_size,
BlockType::Padding if header.size == 0 => BlockData::Padding,
BlockType::Padding => {
reader.seek_relative(i64::from(header.size.inner()))?;
BlockData::Padding
}
BlockType::Streaminfo => BlockData::Streaminfo(Streaminfo::decode(reader, ())?),
BlockType::Application => {
BlockData::Application(Application::decode(reader, header.size)?)
}
BlockType::SeekTable => BlockData::SeekTable(SeekTable::decode(reader, header.size)?),
BlockType::VorbisComment => {
BlockData::VorbisComment(VorbisComment::decode(reader, ())?)
}
BlockType::Cuesheet => BlockData::Cuesheet(Cuesheet::decode(reader, ())?),
BlockType::Picture => BlockData::Picture(Picture::decode(reader, header.size.inner())?),
};
Ok(Self { header, data })
}
}
impl Encode for Block {
type Error = Error;
fn encode<W: BitWrite>(&self, writer: &mut W, _opt: ()) -> Result<(), Self::Error> {
self.header.encode(writer, ())?;
let invalid_block_size = Err(Error::InvalidBlockSize(self.header.kind, self.header.size));
match &self.data {
BlockData::Streaminfo(_) if self.header.size != 34 => return invalid_block_size,
BlockData::VorbisComment(_) if self.header.size < 8 => return invalid_block_size,
BlockData::Cuesheet(_) if self.header.size < 432 => return invalid_block_size,
BlockData::Picture(_) if self.header.size < 32 => return invalid_block_size,
BlockData::Padding => {
for _ in 0..self.header.size.inner() {
writer.write_u8(0)?;
}
}
BlockData::Streaminfo(d) => d.encode(writer, ())?,
BlockData::Application(d) => d.encode(writer, ())?,
BlockData::SeekTable(d) => d.encode(writer, ())?,
BlockData::VorbisComment(d) => d.encode(writer, ())?,
BlockData::Cuesheet(d) => d.encode(writer, ())?,
BlockData::Picture(d) => d.encode(writer, ())?,
}
Ok(())
}
}
pub struct BlockIter<'a, R: Read + Seek> {
reader: &'a mut BitReader<R>,
ended: bool,
}
impl<'a, R: Read + Seek> BlockIter<'a, R> {
pub fn new(reader: &'a mut BitReader<R>) -> Self {
Self {
reader,
ended: false,
}
}
}
impl<R: Read + Seek> Iterator for BlockIter<'_, R> {
type Item = Result<Block, Error>;
fn next(&mut self) -> Option<Self::Item> {
if self.ended {
return None;
}
match Block::decode(self.reader, ()) {
Err(e) => {
self.ended = true;
Some(Err(e))
}
Ok(block) => {
if block.header.is_final {
self.ended = true;
}
Some(Ok(block))
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Streaminfo {
pub min_block_size: u16,
pub max_block_size: u16,
pub min_frame_size: U24,
pub max_frame_size: U24,
pub sample_rate: U20,
pub num_channels: U3,
pub bits_per_sample: U5,
pub num_samples: Option<NonZeroU36>,
pub md5sum: Option<NonZero<u128>>,
}
impl Streaminfo {
pub const LARGEST_POSSIBLE_BLOCK_SIZE: u16 = u16::MAX;
#[inline]
pub const fn num_channels(&self) -> usize {
self.num_channels.inner() as usize + 1
}
#[inline]
pub const fn bits_per_sample(&self) -> u8 {
self.bits_per_sample.inner() + 1
}
}
impl Decode<()> for Streaminfo {
type Error = Error;
fn decode<R: BitRead + Seek>(reader: &mut R, _opt: ()) -> Result<Self, Self::Error> {
let min_block_size = reader.read_u16::<BE>()?;
let max_block_size = reader.read_u16::<BE>()?;
let min_frame_size = U24::new(reader.read_u24::<BE>()?).expect("Should be valid u24");
let max_frame_size = U24::new(reader.read_u24::<BE>()?).expect("Should be valid u24");
let (sample_rate, num_channels, bits_per_sample, num_samples) = {
let bytes = reader.read_u64::<BE>()?;
let sample_rate =
U20::new(bytes.get_bit_range_msb(0, 20) as u32).expect("Should be a valid u20");
let num_channels =
U3::new(bytes.get_bit_range_msb(20, 3) as u8).expect("Should be a valid u3");
let bits_per_sample =
U5::new(bytes.get_bit_range_msb(23, 5) as u8).expect("Should be a valid u5");
let num_samples = NonZeroU36::new(
U36::new(bytes.get_bit_range_msb(28, 36)).expect("Should be a valid u36"),
);
(sample_rate, num_channels, bits_per_sample, num_samples)
};
let md5sum = NonZero::new(reader.read_u128::<BE>()?);
if min_block_size < 16 {
return Err(Error::InvalidMinBlockSize(min_block_size));
}
if max_block_size < 16 {
return Err(Error::InvalidMaxBlockSize(max_block_size));
}
if !(4..=32).contains(&bits_per_sample.inner()) {
return Err(Error::InvalidBitsPerSample(bits_per_sample));
}
Ok(Self {
min_block_size,
max_block_size,
min_frame_size,
max_frame_size,
sample_rate,
num_channels,
bits_per_sample,
num_samples,
md5sum,
})
}
}
impl Encode for Streaminfo {
type Error = Error;
fn encode<W: BitWrite>(&self, writer: &mut W, _opt: ()) -> Result<(), Self::Error> {
debug_assert!(writer.is_byte_aligned());
writer.write_u16::<BE>(self.min_block_size)?;
writer.write_u16::<BE>(self.max_block_size)?;
writer.write_uint::<BE>(self.min_frame_size.inner().into(), 3)?;
writer.write_uint::<BE>(self.max_frame_size.inner().into(), 3)?;
writer.write_bits(self.sample_rate.inner().into(), 20)?;
writer.write_bits(self.num_channels.inner().into(), 3)?;
writer.write_bits(self.bits_per_sample.inner().into(), 5)?;
writer.write_bits(
self.num_samples
.map_or_else(Default::default, |x| x.get().inner()),
36,
)?;
writer.write_u128::<BE>(
self.md5sum
.map_or_else(u128::default, std::num::NonZero::get),
)?;
debug_assert!(writer.is_byte_aligned());
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct Application {
pub id: u32,
pub data: Vec<u8>,
}
impl Decode<U24> for Application {
type Error = Error;
fn decode<R: BitRead + Seek>(reader: &mut R, block_size: U24) -> Result<Self, Self::Error> {
let mut data = vec![0u8; block_size.inner().saturating_sub(4) as usize];
let id = reader.read_u32::<BE>()?;
reader.read_exact(&mut data)?;
Ok(Self { id, data })
}
}
impl Encode for Application {
type Error = Error;
fn encode<W: BitWrite>(&self, writer: &mut W, _opt: ()) -> Result<(), Self::Error> {
debug_assert!(writer.is_byte_aligned());
writer.write_u32::<BE>(self.id)?;
writer.write_all(&self.data)?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct SeekTable(pub Vec<SeekPoint>);
impl Decode<U24> for SeekTable {
type Error = Error;
fn decode<R: BitRead + Seek>(reader: &mut R, block_size: U24) -> Result<Self, Error> {
let n_seek_points = block_size.inner() / 18;
let mut seek_points = Vec::with_capacity(n_seek_points as usize);
for _ in 0..n_seek_points {
seek_points.push(SeekPoint::decode(reader, ())?);
}
Ok(Self(seek_points))
}
}
impl Encode for SeekTable {
type Error = Error;
fn encode<W: BitWrite>(&self, writer: &mut W, _opt: ()) -> Result<(), Self::Error> {
for seek_point in &self.0 {
seek_point.encode(writer, ())?;
}
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
pub struct SeekPoint {
pub sample_idx: u64,
pub offset: u64,
pub sample_count: u16,
}
impl SeekPoint {
pub const fn is_placeholder(self) -> bool {
self.sample_idx == u64::MAX
}
}
impl Decode<()> for SeekPoint {
type Error = Error;
fn decode<R: BitRead + Seek>(reader: &mut R, _opt: ()) -> Result<Self, Error> {
let sample_idx = reader.read_u64::<BE>()?;
let offset = reader.read_u64::<BE>()?;
let sample_count = reader.read_u16::<BE>()?;
Ok(Self {
sample_idx,
offset,
sample_count,
})
}
}
impl Encode for SeekPoint {
type Error = Error;
fn encode<W: BitWrite>(&self, writer: &mut W, _opt: ()) -> Result<(), Self::Error> {
writer.write_u64::<BE>(self.sample_idx)?;
writer.write_u64::<BE>(self.offset)?;
writer.write_u16::<BE>(self.sample_count)?;
Ok(())
}
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ChannelMask: u32 {
const FRONT_LEFT = 0x1;
const FRONT_RIGHT = 0x2;
const FRONT_CENTER = 0x4;
const LOW_FREQUENCY_EFFECTS = 0x8;
const BACK_LEFT = 0x10;
const BACK_RIGHT = 0x20;
const FRONT_LEFT_OF_CENTER = 0x40;
const FRONT_RIGHT_OF_CENTER = 0x80;
const BACK_CENTER = 0x100;
const SIDE_LEFT = 0x200;
const SIDE_RIGHT = 0x400;
const TOP_CENTER = 0x800;
const TOP_FRONT_LEFT = 0x1000;
const TOP_FRONT_CENTER = 0x2000;
const TOP_FRONT_RIGHT = 0x4000;
const TOP_REAR_LEFT = 0x8000;
const TOP_REAR_CENTER = 0x10_000;
const TOP_REAR_RIGHT = 0x20_000;
}
}
impl ChannelMask {
pub fn from_hex(hex: &str) -> Result<Self, std::num::ParseIntError> {
Ok(Self::from_bits_truncate(u32::from_str_radix(hex, 16)?))
}
}
#[derive(Debug, Clone)]
#[must_use]
pub struct VorbisComment {
pub vendor: String,
pub user_comment_list: Vec<VorbisUserComment>,
}
impl VorbisComment {
pub fn get_channel_mask(&self) -> Option<Result<ChannelMask, Error>> {
self.user_comment_list
.iter()
.find(|c| c.key.as_str() == "WAVEFORMATEXTENSIBLE_CHANNEL_MASK")
.map(|c| {
let value = c.value.get(2..).ok_or(Error::InvalidChannelMask);
value.and_then(|x| ChannelMask::from_hex(x).map_err(Error::from))
})
}
}
impl Decode<()> for VorbisComment {
type Error = Error;
fn decode<R: BitRead + Seek>(reader: &mut R, _opt: ()) -> Result<Self, Error> {
let vendor_length = reader.read_u32::<LE>()?;
let vendor = {
let mut buf = vec![0u8; vendor_length as usize];
reader.read_exact(&mut buf)?;
String::from_utf8(buf)?
};
let user_comment_count = reader.read_u32::<LE>()?;
let mut user_comment_list = Vec::with_capacity(user_comment_count as usize);
for _ in 0..user_comment_count {
user_comment_list.push(VorbisUserComment::decode(reader, ())?);
}
Ok(Self {
vendor,
user_comment_list,
})
}
}
impl Encode for VorbisComment {
type Error = Error;
fn encode<W: BitWrite>(&self, writer: &mut W, _opt: ()) -> Result<(), Self::Error> {
debug_assert!(writer.is_byte_aligned());
writer.write_u32::<LE>(
self.vendor
.len()
.try_into()
.map_err(|_| Error::VendorTooLong)?,
)?;
writer.write_all(self.vendor.as_bytes())?;
writer.write_u32::<LE>(
self.user_comment_list
.len()
.try_into()
.map_err(|_| Error::UserCommentsTooLong)?,
)?;
for comment in &self.user_comment_list {
comment.encode(writer, ())?;
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct VorbisUserComment {
pub key: Box<AsciiStr>,
pub value: String,
}
impl VorbisUserComment {
pub fn new(key: &AsciiStr, value: impl Into<String>) -> Option<Self> {
let key: Box<_> = key.into();
let value = value.into();
Self::key_is_valid(&key).then_some(Self { key, value })
}
#[inline]
pub fn key_is_valid(key: &AsciiStr) -> bool {
key.bytes().all(|byte| byte != b'=')
}
}
impl Decode<()> for VorbisUserComment {
type Error = Error;
fn decode<R: BitRead + Seek>(reader: &mut R, _opt: ()) -> Result<Self, Error> {
let length = reader.read_u32::<LE>()?;
let mut buf = vec![0u8; length as usize];
reader.read_exact(&mut buf)?;
let (key, value) = buf
.iter()
.position(|&x| x == b'=')
.map(|pos| buf.split_at(pos))
.map(|(key, value)| (key, &value[1..]))
.ok_or(Error::InvalidVorbisComment)?;
let key = Box::from(AsciiStr::from_bytes(key).ok_or(Error::CommentKeyNotAscii)?);
let value = str::from_utf8(value)?.to_string();
Ok(Self { key, value })
}
}
impl Encode for VorbisUserComment {
type Error = Error;
fn encode<W: BitWrite>(&self, writer: &mut W, _opt: ()) -> Result<(), Self::Error> {
let length: u32 = (self.key.len() + self.value.len() + 1)
.try_into()
.map_err(|_| Error::VendorTooLong)?;
writer.write_u32::<LE>(length)?;
writer.write_all(self.key.as_bytes())?;
writer.write_u8(b'=')?;
writer.write_all(self.value.as_bytes())?;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct Cuesheet {
pub media_catalog_number: AsciiArray<128>,
pub lead_in_samples: u64,
pub is_cdda: bool,
pub tracks: Vec<CuesheetTrack>,
}
impl Cuesheet {
#[inline]
pub const fn is_valid(&self) -> bool {
if let Some(x) = self.tracks.as_slice().last() {
x.is_lead_out(self.is_cdda)
} else {
false
}
}
}
impl Decode<()> for Cuesheet {
type Error = Error;
fn decode<R: BitRead + Seek>(reader: &mut R, _opt: ()) -> Result<Self, Error> {
let media_catalog_number = {
let mut n = [0u8; 128];
reader.read_exact(&mut n)?;
let mut n = AsciiArray::try_from(n)?;
n.rtrim(b"\0");
n
};
let lead_in_samples = reader.read_u64::<BE>()?;
let is_cdda = {
let cdda = reader.read_u8()?;
cdda.get_bit_msb(0)
};
reader.seek_relative(258)?;
let track_count = reader.read_u8()?;
let mut tracks = Vec::with_capacity(track_count.into());
for _ in 0..track_count {
tracks.push(CuesheetTrack::decode(reader, is_cdda)?);
}
let cuesheet = Self {
media_catalog_number,
lead_in_samples,
is_cdda,
tracks,
};
cuesheet
.is_valid()
.then_some(cuesheet)
.ok_or(Error::InvalidCuesheet)
}
}
impl Encode for Cuesheet {
type Error = Error;
fn encode<W: BitWrite>(&self, writer: &mut W, _opt: ()) -> Result<(), Self::Error> {
let mut cat_num = self.media_catalog_number;
cat_num.fill_rest(0u8);
writer.write_all(&cat_num)?;
writer.write_u64::<BE>(self.lead_in_samples)?;
writer.write_bit(self.is_cdda)?;
writer.flush_bits()?;
writer.write_all(&[0u8; 258])?;
writer.write_u8(
self.tracks
.len()
.try_into()
.map_err(|_| Error::CuesheetTrackCount)?,
)?;
for track in &self.tracks {
track.encode(writer, self.is_cdda)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct CuesheetTrack {
pub offset: u64,
pub number: NonZero<u8>,
pub isrc: Option<AsciiArray<12>>,
pub is_audio: bool,
pub pre_emphasis: bool,
pub index_points: CuesheetTrackIndexPoints,
}
impl CuesheetTrack {
#[inline]
pub const fn is_lead_out(&self, cdda: bool) -> bool {
!cdda && self.number.get() == 255 || self.number.get() == 170
}
#[inline]
pub fn is_valid(&self, cdda: bool) -> bool {
let is_number_valid = match (cdda, self.number.get()) {
(true, 1..=99 | 170) | (false, 1..=255) => true, _ => false,
};
let is_offset_valid = !cdda || (self.offset.is_multiple_of(588));
let is_index_point_count_valid = if cdda {
self.index_points.len <= 100 || self.is_lead_out(cdda)
} else {
true
};
let is_isrc_valid = match self.isrc {
None => true,
Some(isrc) => {
let mut valid = true;
let mut i = 0usize;
while valid && i < 12 {
valid = valid && isrc.get(i) != 0 && isrc.get(i).is_ascii_alphanumeric();
i += 1;
}
valid
}
};
is_offset_valid
&& is_number_valid
&& is_index_point_count_valid
&& is_isrc_valid
&& self.index_points.is_valid(cdda)
}
}
impl Decode<bool> for CuesheetTrack {
type Error = Error;
fn decode<R: BitRead + Seek>(reader: &mut R, is_cdda: bool) -> Result<Self, Error> {
let offset = reader.read_u64::<BE>()?;
let number = NonZero::new(reader.read_u8()?).ok_or(Error::InvalidCuesheetTrack)?;
let isrc = {
let mut isrc = [0u8; 12];
reader.read_exact(&mut isrc)?;
if isrc.iter().all(|&x| x == 0) {
None
} else {
Some(AsciiArray::try_from(isrc)?)
}
};
let (is_audio, pre_emphasis) = {
let flags = reader.read_u8()?;
let is_audio = !flags.get_bit_msb(0);
let pre_emphasis = flags.get_bit_msb(1);
(is_audio, pre_emphasis)
};
reader.seek_relative(13)?;
let index_points = CuesheetTrackIndexPoints::decode(reader, is_cdda)?;
let x = Self {
offset,
number,
isrc,
is_audio,
pre_emphasis,
index_points,
};
if x.is_valid(is_cdda) {
Ok(x)
} else {
Err(Error::InvalidCuesheetTrack)
}
}
}
impl Encode<bool> for CuesheetTrack {
type Error = Error;
fn encode<W: BitWrite>(&self, writer: &mut W, is_cdda: bool) -> Result<(), Self::Error> {
if cfg!(debug_assertions) && !self.is_valid(is_cdda) {
return Err(Error::InvalidCuesheetTrack);
}
let mut isrc = self.isrc.unwrap_or_default();
isrc.fill_rest(0);
debug_assert_eq!(isrc.len(), 12);
writer.write_u64::<BE>(self.offset)?;
writer.write_u8(self.number.get())?;
writer.write_all(&isrc)?;
writer.write_bit(!self.is_audio)?;
writer.write_bit(self.pre_emphasis)?;
writer.flush_bits()?;
writer.write_all(&[0; 13])?;
self.index_points.encode(writer, is_cdda)?;
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CuesheetTrackIndexPoints {
pub len: u8,
pub index_points: ArrayList<CuesheetTrackIndexPoint, 255>,
}
impl Deref for CuesheetTrackIndexPoints {
type Target = [CuesheetTrackIndexPoint];
#[inline]
fn deref(&self) -> &Self::Target {
self.index_points
.split_at_checked(self.len as usize)
.unwrap()
.0
}
}
impl CuesheetTrackIndexPoints {
pub fn is_valid_cdda(&self) -> bool {
let mut valid = true;
let mut i = 0usize;
while valid && i < self.len as usize {
let point = self.index_points[i];
if i == 0 {
valid = valid && matches!(point.number, 0 | 1);
} else {
let prev = self.index_points[i - 1];
valid = valid && point.number > prev.number;
}
valid = valid && point.is_valid_cdda();
i += 1;
}
valid
}
pub fn is_valid(&self, cdda: bool) -> bool {
let is_unique = {
let mut valid = true;
let mut i = 1usize;
while valid && i < self.len as usize {
let point = self.index_points[i];
let prev = self.index_points[i - 1];
valid = valid && point.number != prev.number;
i += 1;
}
valid
};
(!cdda || self.is_valid_cdda()) && is_unique
}
}
impl Decode<bool> for CuesheetTrackIndexPoints {
type Error = Error;
fn decode<R: BitRead + Seek>(reader: &mut R, is_cdda: bool) -> Result<Self, Error> {
let len = reader.read_u8()?;
let mut index_points = ArrayList::default();
for _ in 0..len {
index_points.push(CuesheetTrackIndexPoint::decode(reader, is_cdda)?);
}
let x = Self { len, index_points };
if x.is_valid(is_cdda) {
Ok(x)
} else {
Err(Error::InvalidCuesheetIndexPoints)
}
}
}
impl Encode<bool> for CuesheetTrackIndexPoints {
type Error = Error;
fn encode<W: BitWrite>(&self, writer: &mut W, is_cdda: bool) -> Result<(), Self::Error> {
debug_assert!(writer.is_byte_aligned());
if cfg!(debug_assertions) && !self.is_valid(is_cdda) {
return Err(Error::InvalidCuesheetIndexPoints);
}
writer.write_u8(self.len)?;
for point in &*self.index_points {
point.encode(writer, is_cdda)?;
}
Ok(())
}
}
#[derive(Debug, PartialEq, Clone, Copy, Eq, Default)]
pub struct CuesheetTrackIndexPoint {
pub offset: u64,
pub number: u8,
}
impl CuesheetTrackIndexPoint {
#[inline]
pub const fn is_valid(self, is_cdda: bool) -> bool {
!is_cdda || self.is_valid_cdda()
}
#[inline]
pub const fn is_valid_cdda(self) -> bool {
self.offset.is_multiple_of(588)
}
}
impl Decode<bool> for CuesheetTrackIndexPoint {
type Error = Error;
fn decode<R: BitRead + Seek>(reader: &mut R, is_cdda: bool) -> Result<Self, Error> {
let offset = reader.read_u64::<BE>()?;
let number = reader.read_u8()?;
reader.seek_relative(3)?;
let x = Self { offset, number };
let is_valid = x.is_valid(is_cdda);
if is_valid {
Ok(x)
} else {
Err(Error::InvalidCuesheetIndexPoint)
}
}
}
impl Encode<bool> for CuesheetTrackIndexPoint {
type Error = Error;
fn encode<W: BitWrite>(&self, writer: &mut W, is_cdda: bool) -> Result<(), Self::Error> {
if cfg!(debug_assertions) && !self.is_valid(is_cdda) {
return Err(Error::InvalidCuesheetIndexPoints);
}
writer.write_u64::<BE>(self.offset)?;
writer.write_u8(self.number)?;
writer.write_all(&[0; 3])?;
Ok(())
}
}
#[derive(Debug, Clone, Copy)]
#[repr(u32)]
pub enum PictureType {
Other = 0,
PngFileIcon,
FileIcon,
FrontCover,
BackCover,
LinerNotes,
MediaLabel,
LeadArtist,
Artist,
Conductor,
Band,
Composer,
Lyricist,
RecordingLocation,
Recording,
Performance,
Movie,
BrightColoredFish,
Illustration,
ArtistLogotype,
Publisher,
}
impl TryFrom<u32> for PictureType {
type Error = Error;
#[inline]
fn try_from(value: u32) -> Result<Self, Self::Error> {
use PictureType::*;
Ok(match value {
0 => Other,
1 => PngFileIcon,
2 => FileIcon,
3 => FrontCover,
4 => BackCover,
5 => LinerNotes,
6 => MediaLabel,
7 => LeadArtist,
8 => Artist,
9 => Conductor,
10 => Band,
11 => Composer,
12 => Lyricist,
13 => RecordingLocation,
14 => Recording,
15 => Performance,
16 => Movie,
17 => BrightColoredFish,
18 => Illustration,
19 => ArtistLogotype,
20 => Publisher,
21.. => return Err(Error::ReservedPictureType),
})
}
}
impl From<PictureType> for u32 {
fn from(value: PictureType) -> Self {
use PictureType::*;
match value {
Other => 0,
PngFileIcon => 1,
FileIcon => 2,
FrontCover => 3,
BackCover => 4,
LinerNotes => 5,
MediaLabel => 6,
LeadArtist => 7,
Artist => 8,
Conductor => 9,
Band => 10,
Composer => 11,
Lyricist => 12,
RecordingLocation => 13,
Recording => 14,
Performance => 15,
Movie => 16,
BrightColoredFish => 17,
Illustration => 18,
ArtistLogotype => 19,
Publisher => 20,
}
}
}
#[derive(Clone)]
pub enum PictureData {
Bytes {
media_type: Box<AsciiArray<255>>,
data: Vec<u8>,
},
Uri(Vec<u8>),
}
impl std::fmt::Debug for PictureData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Bytes { media_type, .. } => f
.debug_struct("Bytes")
.field("media_type", media_type)
.finish_non_exhaustive(),
Self::Uri(_) => f.debug_tuple("Uri").finish_non_exhaustive(),
}
}
}
#[derive(Debug, Clone)]
pub struct Picture {
pub kind: PictureType,
pub description: String,
pub width: u32,
pub height: u32,
pub color_depth: u32,
pub color_count: Option<NonZero<u32>>,
pub data: PictureData,
}
impl Decode<u32> for Picture {
type Error = Error;
fn decode<R: BitRead + Seek>(reader: &mut R, max_length: u32) -> Result<Self, Error> {
let mut total_length = 0u32;
let mut inc_total_length = |len: u32| -> Result<(), Error> {
total_length += len;
if total_length > max_length {
Err(Error::BlockExceededLength)
} else {
Ok(())
}
};
let kind = PictureType::try_from(reader.read_u32::<BE>()?)?;
let media_type = {
let length = reader.read_u32::<BE>()?;
inc_total_length(length)?;
let mut media_type = [0u8; 255];
reader.read_exact(&mut media_type[..length as usize])?;
Box::new(AsciiArray::from_array(media_type, length as usize)?)
};
let description = {
let length = reader.read_u32::<BE>()?;
inc_total_length(length)?;
let mut description = vec![0u8; length as usize];
reader.read_exact(&mut description)?;
String::from_utf8(description)?
};
let width = reader.read_u32::<BE>()?;
let height = reader.read_u32::<BE>()?;
let color_depth = reader.read_u32::<BE>()?;
let color_count = NonZero::new(reader.read_u32::<BE>()?);
let data = {
let length = reader.read_u32::<BE>()?;
inc_total_length(length)?;
let mut data = vec![0u8; length as usize];
reader.read_exact(&mut data)?;
match media_type.as_str() {
"-->" => PictureData::Uri(data),
_ => PictureData::Bytes { media_type, data },
}
};
Ok(Self {
kind,
description,
width,
height,
color_depth,
color_count,
data,
})
}
}
impl Encode for Picture {
type Error = Error;
fn encode<W: BitWrite>(&self, writer: &mut W, _opt: ()) -> Result<(), Self::Error> {
let (media_type, data) = match &self.data {
PictureData::Bytes { media_type, data } => (**media_type, data),
PictureData::Uri(data) => (AsciiArray::from_bytes(b"-->").unwrap(), data),
};
writer.write_u32::<BE>(u32::from(self.kind))?;
writer.write_u32::<BE>(media_type.len() as u32)?;
writer.write_all(&media_type)?;
writer.write_u32::<BE>(
self.description
.len()
.try_into()
.map_err(|_| Error::OverlongDescription)?,
)?;
writer.write_all(self.description.as_bytes())?;
writer.write_u32::<BE>(self.width)?;
writer.write_u32::<BE>(self.height)?;
writer.write_u32::<BE>(self.color_depth)?;
writer.write_u32::<BE>(self.color_count.map(NonZeroU32::get).unwrap_or_default())?;
writer.write_u32::<BE>(
data.len()
.try_into()
.map_err(|_| Error::OverlongPictureData)?,
)?;
writer.write_all(data)?;
Ok(())
}
}
#[allow(clippy::dbg_macro)]
#[cfg(test)]
mod tests {
use crate::MAGIC;
use super::*;
const FLAC_BIN: &[u8] = include_bytes!("../tests/audio/binaural-with-picture.flac");
#[test]
fn new_vorbis_user_comment() {
let _mask = VorbisUserComment::new(
AsciiStr::from_str("WAVEFORMATEXTENSIBLE_CHANNEL_MASK").unwrap(),
"0x8",
)
.unwrap();
}
#[test]
fn get_channel_mask() {
let mask = VorbisUserComment::new(
AsciiStr::from_str("WAVEFORMATEXTENSIBLE_CHANNEL_MASK").unwrap(),
"0x8",
)
.unwrap();
let block = VorbisComment {
vendor: String::from("Tgirl hooters"),
user_comment_list: vec![mask],
};
let channel_mask = block.get_channel_mask().unwrap().unwrap();
assert_eq!(channel_mask, ChannelMask::LOW_FREQUENCY_EFFECTS);
}
#[test]
fn block_header_is_final() {
let header = dbg!(BlockHeader::decode_bytes(&[0b1000_0000, 0x00, 0x00, 0x00], ()).unwrap());
assert!(header.is_final);
let header = dbg!(BlockHeader::decode_bytes(&[0b0000_0000, 0x00, 0x00, 0x00], ()).unwrap());
assert!(!header.is_final);
}
#[test]
fn block_header_kind() {
let bytes = [
0b0000_0100, 0x00,
0x00,
0x22, ];
let header = BlockHeader::decode_bytes(&bytes, ()).unwrap();
assert_eq!(header.kind, BlockType::VorbisComment);
}
fn parse_block<R: BitRead + Seek>(reader: &mut R, mut cb: impl FnMut(Block)) {
loop {
let block = Block::decode(reader, ()).unwrap();
let is_final = block.header.is_final;
println!("{block:#?}");
cb(block);
if is_final {
break;
}
}
}
#[test]
fn test_parse_block() {
let mut flac = BitReader::new(io::Cursor::new(FLAC_BIN));
let mut magic = [0u8; size_of_val(&MAGIC)];
flac.read_exact(&mut magic).unwrap();
assert_eq!(magic, MAGIC);
parse_block(&mut flac, |_block| {});
}
}
#[cfg(test)]
mod streaminfo_encode {
use super::*;
use crate::Encode;
use crate::bit_io::{BitReader, BitWriter};
use std::io::Cursor;
const FLAC_BIN: &[u8] = include_bytes!("../tests/audio/binaural-with-picture.flac");
fn streaminfo_from_file() -> &'static [u8] {
&FLAC_BIN[8..42] }
fn parse_streaminfo(raw: &[u8]) -> Streaminfo {
Streaminfo::decode(&mut BitReader::new(Cursor::new(raw)), ()).unwrap()
}
#[test]
fn round_trip_encode_streaminfo() {
let original_bytes = streaminfo_from_file();
let parsed = parse_streaminfo(original_bytes);
let mut w = BitWriter::new(Vec::new());
parsed.encode(&mut w, ()).unwrap();
let encoded = w.into_inner();
assert_eq!(
encoded, original_bytes,
"re-encoded STREAMINFO does not match original bytes from file"
);
}
#[test]
fn round_trip_encode_streaminfo_parsed() {
let original_bytes = streaminfo_from_file();
let original = parse_streaminfo(original_bytes);
let mut w = BitWriter::new(Vec::new());
original.encode(&mut w, ()).unwrap();
let encoded = w.into_inner();
let decoded = parse_streaminfo(&encoded);
assert_eq!(original, decoded);
}
#[test]
fn test_block_header_bytes_are_valid() {
let header_bytes = &FLAC_BIN[4..8];
let block_type = header_bytes[0] & 0x7F;
assert_eq!(block_type, 0, "first metadata block must be STREAMINFO");
let length = u32::from_be_bytes([0, header_bytes[1], header_bytes[2], header_bytes[3]]);
assert_eq!(length, 34, "STREAMINFO block length must be 34");
}
#[test]
fn round_trip_encode_block_header() {
let original_header_bytes = &FLAC_BIN[4..8];
let header =
BlockHeader::decode(&mut BitReader::new(Cursor::new(original_header_bytes)), ())
.unwrap();
let mut w = BitWriter::new(Vec::new());
header.encode(&mut w, ()).unwrap();
let encoded = w.into_inner();
assert_eq!(
encoded.as_slice(),
original_header_bytes,
"re-encoded block header does not match original"
);
}
}