use crate::Error;
use bitstream_io::{
BigEndian, BitRead, BitReader, BitWrite, ByteRead, ByteReader, FromBitStream,
FromBitStreamUsing, FromBitStreamWith, LittleEndian, SignedBitCount, ToBitStream,
ToBitStreamUsing, write::Overflowed,
};
use std::fs::File;
use std::io::BufReader;
use std::num::NonZero;
use std::path::Path;
pub mod cuesheet;
const FLAC_TAG: &[u8; 4] = b"fLaC";
pub trait Metadata {
fn channel_count(&self) -> u8;
fn channel_mask(&self) -> ChannelMask {
ChannelMask::from_channels(self.channel_count())
}
fn sample_rate(&self) -> u32;
fn bits_per_sample(&self) -> u32;
fn total_samples(&self) -> Option<u64> {
None
}
fn md5(&self) -> Option<&[u8; 16]> {
None
}
fn decoded_len(&self) -> Option<u64> {
self.total_samples().map(|s| {
s * u64::from(self.channel_count()) * u64::from(self.bits_per_sample().div_ceil(8))
})
}
fn duration(&self) -> Option<std::time::Duration> {
const NANOS_PER_SEC: u64 = 1_000_000_000;
let sample_rate = u64::from(self.sample_rate());
self.total_samples().map(|s| {
std::time::Duration::new(
s / sample_rate,
u32::try_from(((s % sample_rate) * NANOS_PER_SEC) / sample_rate)
.unwrap_or_default(),
)
})
}
}
#[derive(Debug, Eq, PartialEq)]
pub struct BlockHeader {
pub last: bool,
pub block_type: BlockType,
pub size: BlockSize,
}
impl BlockHeader {
const SIZE: BlockSize = BlockSize((1 + 7 + 24) / 8);
}
pub trait MetadataBlock:
ToBitStream<Error: Into<Error>> + Into<Block> + TryFrom<Block> + Clone
{
const TYPE: BlockType;
const MULTIPLE: bool;
fn bytes(&self) -> Option<BlockSize> {
self.bits::<BlockBits>().ok().map(|b| b.into())
}
fn total_size(&self) -> Option<BlockSize> {
self.bytes().and_then(|s| s.checked_add(BlockHeader::SIZE))
}
}
#[derive(Default)]
struct BlockBits(u32);
impl BlockBits {
const MAX: u32 = BlockSize::MAX * 8;
}
impl From<u8> for BlockBits {
fn from(u: u8) -> Self {
Self(u.into())
}
}
impl TryFrom<u32> for BlockBits {
type Error = ();
fn try_from(u: u32) -> Result<Self, Self::Error> {
(u <= Self::MAX).then_some(Self(u)).ok_or(())
}
}
impl TryFrom<usize> for BlockBits {
type Error = ();
fn try_from(u: usize) -> Result<Self, Self::Error> {
u32::try_from(u)
.map_err(|_| ())
.and_then(|u| (u <= Self::MAX).then_some(Self(u)).ok_or(()))
}
}
impl bitstream_io::write::Counter for BlockBits {
fn checked_add_assign(&mut self, Self(b): Self) -> Result<(), Overflowed> {
*self = self
.0
.checked_add(b)
.filter(|b| *b <= Self::MAX)
.map(Self)
.ok_or(Overflowed)?;
Ok(())
}
fn checked_mul(self, Self(b): Self) -> Result<Self, Overflowed> {
self.0
.checked_mul(b)
.filter(|b| *b <= Self::MAX)
.map(Self)
.ok_or(Overflowed)
}
fn byte_aligned(&self) -> bool {
self.0.is_multiple_of(8)
}
}
impl From<BlockBits> for BlockSize {
fn from(BlockBits(u): BlockBits) -> Self {
assert!(u % 8 == 0);
Self(u / 8)
}
}
impl BlockHeader {
fn new<M: MetadataBlock>(last: bool, block: &M) -> Result<Self, Error> {
fn large_block<E: Into<Error>>(err: E) -> Error {
match err.into() {
Error::Io(_) => Error::ExcessiveBlockSize,
e => e,
}
}
Ok(Self {
last,
block_type: M::TYPE,
size: block.bits::<BlockBits>().map_err(large_block)?.into(),
})
}
}
impl FromBitStream for BlockHeader {
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
Ok(Self {
last: r.read::<1, _>()?,
block_type: r.parse()?,
size: r.parse()?,
})
}
}
impl ToBitStream for BlockHeader {
type Error = Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
w.write::<1, _>(self.last)?;
w.build(&self.block_type)?;
w.build(&self.size)?;
Ok(())
}
}
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub enum BlockType {
Streaminfo = 0,
Padding = 1,
Application = 2,
SeekTable = 3,
VorbisComment = 4,
Cuesheet = 5,
Picture = 6,
}
impl std::fmt::Display for BlockType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Streaminfo => "STREAMINFO".fmt(f),
Self::Padding => "PADDING".fmt(f),
Self::Application => "APPLICATION".fmt(f),
Self::SeekTable => "SEEKTABLE".fmt(f),
Self::VorbisComment => "VORBIS_COMMENT".fmt(f),
Self::Cuesheet => "CUESHEET".fmt(f),
Self::Picture => "PICTURE".fmt(f),
}
}
}
impl FromBitStream for BlockType {
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
match r.read::<7, u8>()? {
0 => Ok(Self::Streaminfo),
1 => Ok(Self::Padding),
2 => Ok(Self::Application),
3 => Ok(Self::SeekTable),
4 => Ok(Self::VorbisComment),
5 => Ok(Self::Cuesheet),
6 => Ok(Self::Picture),
7..=126 => Err(Error::ReservedMetadataBlock),
_ => Err(Error::InvalidMetadataBlock),
}
}
}
impl ToBitStream for BlockType {
type Error = Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
w.write::<7, u8>(match self {
Self::Streaminfo => 0,
Self::Padding => 1,
Self::Application => 2,
Self::SeekTable => 3,
Self::VorbisComment => 4,
Self::Cuesheet => 5,
Self::Picture => 6,
})
.map_err(Error::Io)
}
}
#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub enum OptionalBlockType {
Padding = 1,
Application = 2,
SeekTable = 3,
VorbisComment = 4,
Cuesheet = 5,
Picture = 6,
}
impl From<OptionalBlockType> for BlockType {
fn from(block: OptionalBlockType) -> Self {
match block {
OptionalBlockType::Padding => Self::Padding,
OptionalBlockType::Application => Self::Application,
OptionalBlockType::SeekTable => Self::SeekTable,
OptionalBlockType::VorbisComment => Self::VorbisComment,
OptionalBlockType::Cuesheet => Self::Cuesheet,
OptionalBlockType::Picture => Self::Picture,
}
}
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct BlockSize(u32);
impl BlockSize {
pub const ZERO: BlockSize = BlockSize(0);
const MAX: u32 = (1 << 24) - 1;
fn get(&self) -> u32 {
self.0
}
}
impl BlockSize {
pub fn checked_add(self, rhs: Self) -> Option<Self> {
self.0
.checked_add(rhs.0)
.filter(|s| *s <= Self::MAX)
.map(Self)
}
pub fn checked_sub(self, rhs: Self) -> Option<Self> {
self.0.checked_sub(rhs.0).map(Self)
}
}
impl std::fmt::Display for BlockSize {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl FromBitStream for BlockSize {
type Error = std::io::Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
r.read::<24, _>().map(Self)
}
}
impl ToBitStream for BlockSize {
type Error = std::io::Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
w.write::<24, _>(self.0)
}
}
impl From<u8> for BlockSize {
fn from(u: u8) -> Self {
Self(u.into())
}
}
impl From<u16> for BlockSize {
fn from(u: u16) -> Self {
Self(u.into())
}
}
impl TryFrom<usize> for BlockSize {
type Error = BlockSizeOverflow;
fn try_from(u: usize) -> Result<Self, Self::Error> {
u32::try_from(u)
.map_err(|_| BlockSizeOverflow)
.and_then(|s| (s <= Self::MAX).then_some(Self(s)).ok_or(BlockSizeOverflow))
}
}
impl TryFrom<u32> for BlockSize {
type Error = BlockSizeOverflow;
fn try_from(u: u32) -> Result<Self, Self::Error> {
(u <= Self::MAX).then_some(Self(u)).ok_or(BlockSizeOverflow)
}
}
impl TryFrom<u64> for BlockSize {
type Error = BlockSizeOverflow;
fn try_from(u: u64) -> Result<Self, Self::Error> {
u32::try_from(u)
.map_err(|_| BlockSizeOverflow)
.and_then(|s| (s <= Self::MAX).then_some(Self(s)).ok_or(BlockSizeOverflow))
}
}
impl From<BlockSize> for u32 {
#[inline]
fn from(size: BlockSize) -> u32 {
size.0
}
}
#[derive(Copy, Clone, Debug)]
pub struct BlockSizeOverflow;
impl std::error::Error for BlockSizeOverflow {}
impl std::fmt::Display for BlockSizeOverflow {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
"value too large for BlockSize".fmt(f)
}
}
pub struct BlockIterator<R: std::io::Read> {
reader: R,
failed: bool,
tag_read: bool,
streaminfo_read: bool,
seektable_read: bool,
vorbiscomment_read: bool,
png_read: bool,
icon_read: bool,
finished: bool,
}
impl<R: std::io::Read> BlockIterator<R> {
pub fn new(reader: R) -> Self {
Self {
reader,
failed: false,
tag_read: false,
streaminfo_read: false,
seektable_read: false,
vorbiscomment_read: false,
png_read: false,
icon_read: false,
finished: false,
}
}
fn read_block(&mut self) -> Option<Result<Block, Error>> {
struct LimitedReader<R> {
reader: R,
size: usize,
}
impl<R: std::io::Read> std::io::Read for LimitedReader<R> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let size = self.size.min(buf.len());
self.reader.read(&mut buf[0..size]).inspect(|amt_read| {
self.size -= amt_read;
})
}
}
(!self.finished).then(|| {
BitReader::endian(&mut self.reader, BigEndian)
.parse()
.and_then(|header: BlockHeader| {
let mut reader = BitReader::endian(
LimitedReader {
reader: self.reader.by_ref(),
size: header.size.get().try_into().unwrap(),
},
BigEndian,
);
let block = reader.parse_with(&header)?;
match reader.into_reader().size {
0 => {
self.finished = header.last;
Ok(block)
}
_ => Err(Error::InvalidMetadataBlockSize),
}
})
})
}
}
impl<R: std::io::Read> Iterator for BlockIterator<R> {
type Item = Result<Block, Error>;
fn next(&mut self) -> Option<Self::Item> {
if self.failed {
None
} else if !self.tag_read {
let mut tag = [0; 4];
match self.reader.read_exact(&mut tag) {
Ok(()) => match &tag {
FLAC_TAG => {
self.tag_read = true;
self.next()
}
_ => {
self.failed = true;
Some(Err(Error::MissingFlacTag))
}
},
Err(err) => {
self.failed = true;
Some(Err(Error::Io(err)))
}
}
} else if !self.streaminfo_read {
match self.read_block() {
block @ Some(Ok(Block::Streaminfo(_))) => {
self.streaminfo_read = true;
block
}
_ => {
self.failed = true;
Some(Err(Error::MissingStreaminfo))
}
}
} else {
match self.read_block() {
Some(Ok(Block::Streaminfo(_))) => Some(Err(Error::MultipleStreaminfo)),
seektable @ Some(Ok(Block::SeekTable(_))) => {
if !self.seektable_read {
self.seektable_read = true;
seektable
} else {
self.failed = true;
Some(Err(Error::MultipleSeekTable))
}
}
vorbiscomment @ Some(Ok(Block::VorbisComment(_))) => {
if !self.vorbiscomment_read {
self.vorbiscomment_read = true;
vorbiscomment
} else {
self.failed = true;
Some(Err(Error::MultipleVorbisComment))
}
}
picture @ Some(Ok(Block::Picture(Picture {
picture_type: PictureType::Png32x32,
..
}))) => {
if !self.png_read {
self.png_read = true;
picture
} else {
self.failed = true;
Some(Err(Error::MultiplePngIcon))
}
}
picture @ Some(Ok(Block::Picture(Picture {
picture_type: PictureType::GeneralFileIcon,
..
}))) => {
if !self.icon_read {
self.icon_read = true;
picture
} else {
self.failed = true;
Some(Err(Error::MultipleGeneralIcon))
}
}
block @ Some(Err(_)) => {
self.failed = true;
block
}
block => block,
}
}
}
}
pub fn read_blocks<R: std::io::Read>(r: R) -> BlockIterator<R> {
BlockIterator::new(r)
}
pub fn blocks<P: AsRef<Path>>(p: P) -> std::io::Result<BlockIterator<BufReader<File>>> {
File::open(p.as_ref()).map(|f| read_blocks(BufReader::new(f)))
}
pub fn read_block<R, B>(r: R) -> Result<Option<B>, Error>
where
R: std::io::Read,
B: MetadataBlock,
{
read_blocks(r)
.find_map(|r| r.map(|b| B::try_from(b).ok()).transpose())
.transpose()
}
pub fn block<P, B>(p: P) -> Result<Option<B>, Error>
where
P: AsRef<Path>,
B: MetadataBlock,
{
blocks(p).map_err(Error::Io).and_then(|mut blocks| {
blocks
.find_map(|r| r.map(|b| B::try_from(b).ok()).transpose())
.transpose()
})
}
pub fn info<P: AsRef<Path>>(p: P) -> Result<Streaminfo, Error> {
File::open(p)
.map_err(Error::Io)
.and_then(|f| read_info(BufReader::new(f)))
}
pub fn read_info<R: std::io::Read>(r: R) -> Result<Streaminfo, Error> {
let mut r = BitReader::endian(r, BigEndian);
if &r.read_to::<[u8; 4]>()? != FLAC_TAG {
return Err(Error::MissingFlacTag);
}
if !matches!(
r.parse()?,
BlockHeader {
block_type: BlockType::Streaminfo,
size: Streaminfo::SIZE,
last: _,
}
) {
return Err(Error::MissingStreaminfo);
}
r.parse().map_err(Error::Io)
}
pub fn blocks_of<P, B>(p: P) -> impl Iterator<Item = Result<B, Error>>
where
P: AsRef<Path>,
B: MetadataBlock + 'static,
{
match blocks(p) {
Ok(iter) => {
Box::new(iter.filter_map(|block| block.map(|b| B::try_from(b).ok()).transpose()))
as Box<dyn Iterator<Item = Result<B, Error>>>
}
Err(e) => Box::new(std::iter::once(Err(e.into()))),
}
}
pub fn write_blocks<B: AsBlockRef>(
mut w: impl std::io::Write,
blocks: impl IntoIterator<Item = B>,
) -> Result<(), Error> {
fn iter_last<T>(i: impl Iterator<Item = T>) -> impl Iterator<Item = (bool, T)> {
let mut iter = i.peekable();
std::iter::from_fn(move || {
let item = iter.next()?;
Some((iter.peek().is_none(), item))
})
}
w.write_all(FLAC_TAG).map_err(Error::Io)?;
let mut w = bitstream_io::BitWriter::endian(w, BigEndian);
let mut blocks = iter_last(blocks.into_iter());
let next = blocks.next();
match next.as_ref().map(|(last, b)| (last, b.as_block_ref())) {
Some((last, streaminfo @ BlockRef::Streaminfo(_))) => w.build_using(&streaminfo, *last)?,
_ => return Err(Error::MissingStreaminfo),
}
let mut seektable_read = false;
let mut vorbiscomment_read = false;
let mut png_read = false;
let mut icon_read = false;
blocks.try_for_each(|(last, block)| match block.as_block_ref() {
BlockRef::Streaminfo(_) => Err(Error::MultipleStreaminfo),
vorbiscomment @ BlockRef::VorbisComment(_) => match vorbiscomment_read {
false => {
vorbiscomment_read = true;
w.build_using(&vorbiscomment.as_block_ref(), last)
}
true => Err(Error::MultipleVorbisComment),
},
seektable @ BlockRef::SeekTable(_) => match seektable_read {
false => {
seektable_read = true;
w.build_using(&seektable.as_block_ref(), last)
}
true => Err(Error::MultipleSeekTable),
},
picture @ BlockRef::Picture(Picture {
picture_type: PictureType::Png32x32,
..
}) => {
if !png_read {
png_read = true;
w.build_using(&picture.as_block_ref(), last)
} else {
Err(Error::MultiplePngIcon)
}
}
picture @ BlockRef::Picture(Picture {
picture_type: PictureType::GeneralFileIcon,
..
}) => {
if !icon_read {
icon_read = true;
w.build_using(&picture.as_block_ref(), last)
} else {
Err(Error::MultipleGeneralIcon)
}
}
block => w.build_using(&block.as_block_ref(), last),
})
}
pub fn update<P, E>(path: P, f: impl FnOnce(&mut BlockList) -> Result<(), E>) -> Result<bool, E>
where
P: AsRef<Path>,
E: From<Error>,
{
use std::fs::OpenOptions;
update_file(
OpenOptions::new()
.read(true)
.write(true)
.truncate(false)
.create(false)
.open(path.as_ref())
.map_err(Error::Io)?,
|| std::fs::File::create(path.as_ref()),
f,
)
}
pub fn update_file<F, N, E>(
mut original: F,
rebuilt: impl FnOnce() -> std::io::Result<N>,
f: impl FnOnce(&mut BlockList) -> Result<(), E>,
) -> Result<bool, E>
where
F: std::io::Read + std::io::Seek + std::io::Write,
N: std::io::Write,
E: From<Error>,
{
use crate::Counter;
use std::cmp::Ordering;
use std::io::{BufReader, BufWriter, Read, sink};
fn rebuild_file<N, R>(
rebuilt: impl FnOnce() -> std::io::Result<N>,
mut r: R,
blocks: BlockList,
) -> Result<(), Error>
where
N: std::io::Write,
R: Read,
{
let mut tmp = Vec::new();
write_blocks(&mut tmp, blocks)?;
std::io::copy(&mut r, &mut tmp).map_err(Error::Io)?;
drop(r);
rebuilt()
.and_then(|mut f| f.write_all(tmp.as_slice()))
.map_err(Error::Io)
}
fn grow_padding(blocks: &mut BlockList, more_bytes: u64) -> Result<(), ()> {
let padding = blocks.get_mut::<Padding>().ok_or(())?;
padding.size = padding
.size
.checked_add(more_bytes.try_into().map_err(|_| ())?)
.ok_or(())?;
Ok(())
}
fn shrink_padding(blocks: &mut BlockList, fewer_bytes: u64) -> Result<(), ()> {
let padding = blocks.get_mut::<Padding>().ok_or(())?;
padding.size = padding
.size
.checked_sub(fewer_bytes.try_into().map_err(|_| ())?)
.ok_or(())?;
Ok(())
}
let start = std::io::SeekFrom::Start(original.stream_position().map_err(Error::Io)?);
let mut reader = Counter::new(BufReader::new(&mut original));
let mut blocks = BlockList::read(Read::by_ref(&mut reader))?;
let Counter {
stream: reader,
count: old_size,
} = reader;
f(&mut blocks)?;
let new_size = {
let mut new_size = Counter::new(sink());
write_blocks(&mut new_size, blocks.blocks())?;
new_size.count
};
match new_size.cmp(&old_size) {
Ordering::Less => {
match grow_padding(&mut blocks, old_size - new_size) {
Ok(()) => {
original.seek(start).map_err(Error::Io)?;
write_blocks(BufWriter::new(original), blocks)
.map(|()| false)
.map_err(E::from)
}
Err(()) => rebuild_file(rebuilt, reader, blocks)
.map(|()| true)
.map_err(E::from),
}
}
Ordering::Equal => {
original.seek(start).map_err(Error::Io)?;
write_blocks(BufWriter::new(original), blocks)
.map(|()| false)
.map_err(E::from)
}
Ordering::Greater => {
match shrink_padding(&mut blocks, new_size - old_size) {
Ok(()) => {
original.seek(start).map_err(Error::Io)?;
write_blocks(BufWriter::new(original), blocks)
.map(|()| false)
.map_err(E::from)
}
Err(()) => rebuild_file(rebuilt, reader, blocks)
.map(|()| true)
.map_err(E::from),
}
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Block {
Streaminfo(Streaminfo),
Padding(Padding),
Application(Application),
SeekTable(SeekTable),
VorbisComment(VorbisComment),
Cuesheet(Cuesheet),
Picture(Picture),
}
impl Block {
pub fn block_type(&self) -> BlockType {
match self {
Self::Streaminfo(_) => BlockType::Streaminfo,
Self::Padding(_) => BlockType::Padding,
Self::Application(_) => BlockType::Application,
Self::SeekTable(_) => BlockType::SeekTable,
Self::VorbisComment(_) => BlockType::VorbisComment,
Self::Cuesheet(_) => BlockType::Cuesheet,
Self::Picture(_) => BlockType::Picture,
}
}
}
impl AsBlockRef for Block {
fn as_block_ref(&self) -> BlockRef<'_> {
match self {
Self::Streaminfo(s) => BlockRef::Streaminfo(s),
Self::Padding(p) => BlockRef::Padding(p),
Self::Application(a) => BlockRef::Application(a),
Self::SeekTable(s) => BlockRef::SeekTable(s),
Self::VorbisComment(v) => BlockRef::VorbisComment(v),
Self::Cuesheet(v) => BlockRef::Cuesheet(v),
Self::Picture(p) => BlockRef::Picture(p),
}
}
}
impl FromBitStreamWith<'_> for Block {
type Context = BlockHeader;
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(
r: &mut R,
header: &BlockHeader,
) -> Result<Self, Self::Error> {
match header.block_type {
BlockType::Streaminfo => Ok(Block::Streaminfo(r.parse()?)),
BlockType::Padding => Ok(Block::Padding(r.parse_using(header.size)?)),
BlockType::Application => Ok(Block::Application(r.parse_using(header.size)?)),
BlockType::SeekTable => Ok(Block::SeekTable(r.parse_using(header.size)?)),
BlockType::VorbisComment => Ok(Block::VorbisComment(r.parse()?)),
BlockType::Cuesheet => Ok(Block::Cuesheet(r.parse()?)),
BlockType::Picture => Ok(Block::Picture(r.parse()?)),
}
}
}
impl ToBitStreamUsing for Block {
type Context = bool;
type Error = Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W, is_last: bool) -> Result<(), Error> {
match self {
Self::Streaminfo(streaminfo) => w
.build(&BlockHeader::new(is_last, streaminfo)?)
.and_then(|()| w.build(streaminfo).map_err(Error::Io)),
Self::Padding(padding) => w
.build(&BlockHeader::new(is_last, padding)?)
.and_then(|()| w.build(padding).map_err(Error::Io)),
Self::Application(application) => w
.build(&BlockHeader::new(is_last, application)?)
.and_then(|()| w.build(application).map_err(Error::Io)),
Self::SeekTable(seektable) => w
.build(&BlockHeader::new(is_last, seektable)?)
.and_then(|()| w.build(seektable)),
Self::VorbisComment(vorbis_comment) => w
.build(&BlockHeader::new(is_last, vorbis_comment)?)
.and_then(|()| w.build(vorbis_comment)),
Self::Cuesheet(cuesheet) => w
.build(&BlockHeader::new(is_last, cuesheet)?)
.and_then(|()| w.build(cuesheet)),
Self::Picture(picture) => w
.build(&BlockHeader::new(is_last, picture)?)
.and_then(|()| w.build(picture)),
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum BlockRef<'b> {
Streaminfo(&'b Streaminfo),
Padding(&'b Padding),
Application(&'b Application),
SeekTable(&'b SeekTable),
VorbisComment(&'b VorbisComment),
Cuesheet(&'b Cuesheet),
Picture(&'b Picture),
}
impl BlockRef<'_> {
pub fn block_type(&self) -> BlockType {
match self {
Self::Streaminfo(_) => BlockType::Streaminfo,
Self::Padding(_) => BlockType::Padding,
Self::Application(_) => BlockType::Application,
Self::SeekTable(_) => BlockType::SeekTable,
Self::VorbisComment(_) => BlockType::VorbisComment,
Self::Cuesheet(_) => BlockType::Cuesheet,
Self::Picture(_) => BlockType::Picture,
}
}
}
impl AsBlockRef for BlockRef<'_> {
fn as_block_ref(&self) -> BlockRef<'_> {
*self
}
}
pub trait AsBlockRef {
fn as_block_ref(&self) -> BlockRef<'_>;
}
impl<T: AsBlockRef> AsBlockRef for &T {
fn as_block_ref(&self) -> BlockRef<'_> {
<T as AsBlockRef>::as_block_ref(*self)
}
}
impl ToBitStreamUsing for BlockRef<'_> {
type Context = bool;
type Error = Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W, is_last: bool) -> Result<(), Error> {
match self {
Self::Streaminfo(streaminfo) => w
.build(&BlockHeader::new(is_last, *streaminfo)?)
.and_then(|()| w.build(*streaminfo).map_err(Error::Io)),
Self::Padding(padding) => w
.build(&BlockHeader::new(is_last, *padding)?)
.and_then(|()| w.build(*padding).map_err(Error::Io)),
Self::Application(application) => w
.build(&BlockHeader::new(is_last, *application)?)
.and_then(|()| w.build(*application).map_err(Error::Io)),
Self::SeekTable(seektable) => w
.build(&BlockHeader::new(is_last, *seektable)?)
.and_then(|()| w.build(*seektable)),
Self::VorbisComment(vorbis_comment) => w
.build(&BlockHeader::new(is_last, *vorbis_comment)?)
.and_then(|()| w.build(*vorbis_comment)),
Self::Cuesheet(cuesheet) => w
.build(&BlockHeader::new(is_last, *cuesheet)?)
.and_then(|()| w.build(*cuesheet)),
Self::Picture(picture) => w
.build(&BlockHeader::new(is_last, *picture)?)
.and_then(|()| w.build(*picture)),
}
}
}
macro_rules! block {
($t:ty, $v:ident, $m:literal) => {
impl MetadataBlock for $t {
const TYPE: BlockType = BlockType::$v;
const MULTIPLE: bool = $m;
}
impl From<$t> for Block {
fn from(b: $t) -> Self {
Self::$v(b)
}
}
impl TryFrom<Block> for $t {
type Error = ();
fn try_from(block: Block) -> Result<Self, ()> {
match block {
Block::$v(block) => Ok(block),
_ => Err(()),
}
}
}
impl AsBlockRef for $t {
fn as_block_ref(&self) -> BlockRef<'_> {
BlockRef::$v(self)
}
}
};
}
macro_rules! optional_block {
($t:ty, $v:ident) => {
impl OptionalMetadataBlock for $t {
const OPTIONAL_TYPE: OptionalBlockType = OptionalBlockType::$v;
}
impl private::OptionalMetadataBlock for $t {
fn try_from_opt_block(
block: &private::OptionalBlock,
) -> Result<&Self, &private::OptionalBlock> {
match block {
private::OptionalBlock::$v(block) => Ok(block),
block => Err(block),
}
}
fn try_from_opt_block_mut(
block: &mut private::OptionalBlock,
) -> Result<&mut Self, &mut private::OptionalBlock> {
match block {
private::OptionalBlock::$v(block) => Ok(block),
block => Err(block),
}
}
}
impl From<$t> for private::OptionalBlock {
fn from(vorbis: $t) -> Self {
private::OptionalBlock::$v(vorbis)
}
}
impl TryFrom<private::OptionalBlock> for $t {
type Error = ();
fn try_from(block: private::OptionalBlock) -> Result<Self, ()> {
match block {
private::OptionalBlock::$v(b) => Ok(b),
_ => Err(()),
}
}
}
};
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Streaminfo {
pub minimum_block_size: u16,
pub maximum_block_size: u16,
pub minimum_frame_size: Option<NonZero<u32>>,
pub maximum_frame_size: Option<NonZero<u32>>,
pub sample_rate: u32,
pub channels: NonZero<u8>,
pub bits_per_sample: SignedBitCount<32>,
pub total_samples: Option<NonZero<u64>>,
pub md5: Option<[u8; 16]>,
}
impl Streaminfo {
pub const MAX_FRAME_SIZE: u32 = (1 << 24) - 1;
pub const MAX_SAMPLE_RATE: u32 = (1 << 20) - 1;
pub const MAX_CHANNELS: NonZero<u8> = NonZero::new(8).unwrap();
pub const MAX_TOTAL_SAMPLES: NonZero<u64> = NonZero::new((1 << 36) - 1).unwrap();
const SIZE: BlockSize = BlockSize(0x22);
}
block!(Streaminfo, Streaminfo, false);
impl Metadata for Streaminfo {
fn channel_count(&self) -> u8 {
self.channels.get()
}
fn sample_rate(&self) -> u32 {
self.sample_rate
}
fn bits_per_sample(&self) -> u32 {
self.bits_per_sample.into()
}
fn total_samples(&self) -> Option<u64> {
self.total_samples.map(|s| s.get())
}
fn md5(&self) -> Option<&[u8; 16]> {
self.md5.as_ref()
}
}
impl FromBitStream for Streaminfo {
type Error = std::io::Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
Ok(Self {
minimum_block_size: r.read_to()?,
maximum_block_size: r.read_to()?,
minimum_frame_size: r.read::<24, _>()?,
maximum_frame_size: r.read::<24, _>()?,
sample_rate: r.read::<20, _>()?,
channels: r.read::<3, _>()?,
bits_per_sample: r
.read_count::<0b11111>()?
.checked_add(1)
.and_then(|c| c.signed_count())
.unwrap(),
total_samples: r.read::<36, _>()?,
md5: r
.read_to()
.map(|md5: [u8; 16]| md5.iter().any(|b| *b != 0).then_some(md5))?,
})
}
}
impl ToBitStream for Streaminfo {
type Error = std::io::Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
w.write_from(self.minimum_block_size)?;
w.write_from(self.maximum_block_size)?;
w.write::<24, _>(self.minimum_frame_size)?;
w.write::<24, _>(self.maximum_frame_size)?;
w.write::<20, _>(self.sample_rate)?;
w.write::<3, _>(self.channels)?;
w.write_count(
self.bits_per_sample
.checked_sub::<0b11111>(1)
.unwrap()
.count(),
)?;
w.write::<36, _>(self.total_samples)?;
w.write_from(self.md5.unwrap_or([0; 16]))?;
Ok(())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct Padding {
pub size: BlockSize,
}
block!(Padding, Padding, true);
optional_block!(Padding, Padding);
impl FromBitStreamUsing for Padding {
type Context = BlockSize;
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R, size: BlockSize) -> Result<Self, Self::Error> {
r.skip(size.get() * 8)?;
Ok(Self { size })
}
}
impl ToBitStream for Padding {
type Error = std::io::Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
w.pad(self.size.get() * 8)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Application {
pub id: u32,
pub data: Vec<u8>,
}
impl Application {
pub const RIFF: u32 = 0x72696666;
pub const AIFF: u32 = 0x61696666;
}
block!(Application, Application, true);
optional_block!(Application, Application);
impl FromBitStreamUsing for Application {
type Context = BlockSize;
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R, size: BlockSize) -> Result<Self, Self::Error> {
Ok(Self {
id: r.read_to()?,
data: r.read_to_vec(
size.get()
.checked_sub(4)
.ok_or(Error::InsufficientApplicationBlock)?
.try_into()
.unwrap(),
)?,
})
}
}
impl ToBitStream for Application {
type Error = std::io::Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
w.write_from(self.id)?;
w.write_bytes(&self.data)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Default)]
pub struct SeekTable {
pub points: contiguous::Contiguous<{ Self::MAX_POINTS }, SeekPoint>,
}
impl SeekTable {
pub const MAX_POINTS: usize = (1 << 24) / ((64 + 64 + 16) / 8);
}
block!(SeekTable, SeekTable, false);
optional_block!(SeekTable, SeekTable);
impl FromBitStreamUsing for SeekTable {
type Context = BlockSize;
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R, size: BlockSize) -> Result<Self, Self::Error> {
match (size.get() / 18, size.get() % 18) {
(p, 0) => Ok(Self {
points: contiguous::Contiguous::try_collect((0..p).map(|_| r.parse()))
.map_err(|_| Error::InvalidSeekTablePoint)??,
}),
_ => Err(Error::InvalidSeekTableSize),
}
}
}
impl ToBitStream for SeekTable {
type Error = Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
let mut last_offset = None;
self.points
.iter()
.try_for_each(|point| match last_offset.as_mut() {
None => {
last_offset = point.sample_offset();
w.build(point).map_err(Error::Io)
}
Some(last_offset) => match point.sample_offset() {
Some(our_offset) => match our_offset > *last_offset {
true => {
*last_offset = our_offset;
w.build(point).map_err(Error::Io)
}
false => Err(Error::InvalidSeekTablePoint),
},
_ => w.build(point).map_err(Error::Io),
},
})
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum SeekPoint {
Defined {
sample_offset: u64,
byte_offset: u64,
frame_samples: u16,
},
Placeholder,
}
impl SeekPoint {
pub fn sample_offset(&self) -> Option<u64> {
match self {
Self::Defined { sample_offset, .. } => Some(*sample_offset),
Self::Placeholder => None,
}
}
}
impl contiguous::Adjacent for SeekPoint {
fn valid_first(&self) -> bool {
true
}
fn is_next(&self, previous: &SeekPoint) -> bool {
match self {
Self::Defined {
sample_offset: our_offset,
..
} => match previous {
Self::Defined {
sample_offset: prev_offset,
..
} => our_offset > prev_offset,
Self::Placeholder => false,
},
Self::Placeholder => true,
}
}
}
impl FromBitStream for SeekPoint {
type Error = std::io::Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
match r.read_to()? {
u64::MAX => {
let _byte_offset = r.read_to::<u64>()?;
let _frame_samples = r.read_to::<u16>()?;
Ok(Self::Placeholder)
}
sample_offset => Ok(Self::Defined {
sample_offset,
byte_offset: r.read_to()?,
frame_samples: r.read_to()?,
}),
}
}
}
impl ToBitStream for SeekPoint {
type Error = std::io::Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
match self {
Self::Defined {
sample_offset,
byte_offset,
frame_samples,
} => {
w.write_from(*sample_offset)?;
w.write_from(*byte_offset)?;
w.write_from(*frame_samples)
}
Self::Placeholder => {
w.write_from(u64::MAX)?;
w.write_from::<u64>(0)?;
w.write_from::<u16>(0)
}
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct VorbisComment {
pub vendor_string: String,
pub fields: Vec<String>,
}
impl Default for VorbisComment {
fn default() -> Self {
Self {
vendor_string: concat!(env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"))
.to_owned(),
fields: vec![],
}
}
}
impl VorbisComment {
pub fn get(&self, field: &str) -> Option<&str> {
self.all(field).next()
}
pub fn set<S>(&mut self, field: &str, value: S)
where
S: std::fmt::Display,
{
self.remove(field);
self.insert(field, value);
}
pub fn all(&self, field: &str) -> impl Iterator<Item = &str> {
assert!(!field.contains('='), "field must not contain '='");
self.fields.iter().filter_map(|f| {
f.split_once('=')
.and_then(|(key, value)| key.eq_ignore_ascii_case(field).then_some(value))
})
}
pub fn insert<S>(&mut self, field: &str, value: S)
where
S: std::fmt::Display,
{
assert!(!field.contains('='), "field must not contain '='");
self.fields.push(format!("{field}={value}"));
}
pub fn remove(&mut self, field: &str) {
assert!(!field.contains('='), "field must not contain '='");
self.fields.retain(|f| match f.split_once('=') {
Some((key, _)) => !key.eq_ignore_ascii_case(field),
None => true,
});
}
pub fn replace<S: std::fmt::Display>(
&mut self,
field: &str,
replacements: impl IntoIterator<Item = S>,
) {
self.remove(field);
self.fields.extend(
replacements
.into_iter()
.map(|value| format!("{field}={value}")),
);
}
pub fn replace_with<S: std::fmt::Display>(
&mut self,
field: &str,
mut f: impl FnMut(&str) -> S,
) {
assert!(!field.contains('='), "field must not contain '='");
self.fields.iter_mut().for_each(|s| {
if let Some((key, value)) = s.split_once('=')
&& key.eq_ignore_ascii_case(field)
{
*s = format!("{key}={}", f(value));
}
})
}
}
block!(VorbisComment, VorbisComment, false);
optional_block!(VorbisComment, VorbisComment);
impl FromBitStream for VorbisComment {
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
fn read_string<R: BitRead + ?Sized>(r: &mut R) -> Result<String, Error> {
let size = r.read_as_to::<LittleEndian, u32>()?.try_into().unwrap();
Ok(String::from_utf8(r.read_to_vec(size)?)?)
}
Ok(Self {
vendor_string: read_string(r)?,
fields: (0..(r.read_as_to::<LittleEndian, u32>()?))
.map(|_| read_string(r))
.collect::<Result<Vec<_>, _>>()?,
})
}
}
impl ToBitStream for VorbisComment {
type Error = Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
fn write_string<W: BitWrite + ?Sized>(w: &mut W, s: &str) -> Result<(), Error> {
w.write_as_from::<LittleEndian, u32>(
s.len()
.try_into()
.map_err(|_| Error::ExcessiveStringLength)?,
)?;
w.write_bytes(s.as_bytes())?;
Ok(())
}
write_string(w, &self.vendor_string)?;
w.write_as_from::<LittleEndian, u32>(
self.fields
.len()
.try_into()
.map_err(|_| Error::ExcessiveVorbisEntries)?,
)?;
self.fields.iter().try_for_each(|s| write_string(w, s))
}
}
pub mod fields {
pub const TITLE: &str = "TITLE";
pub const ARTIST: &str = "ARTIST";
pub const ALBUM: &str = "ALBUM";
pub const COMPOSER: &str = "COMPOSER";
pub const CONDUCTOR: &str = "CONDUCTOR";
pub const PERFORMER: &str = "PERFORMER";
pub const PUBLISHER: &str = "PUBLISHER";
pub const CATALOG: &str = "CATALOG";
pub const DATE: &str = "DATE";
pub const COMMENT: &str = "COMMENT";
pub const TRACK_NUMBER: &str = "TRACKNUMBER";
pub const TRACK_TOTAL: &str = "TRACKTOTAL";
pub const CHANNEL_MASK: &str = "WAVEFORMATEXTENSIBLE_CHANNEL_MASK";
pub const RG_TRACK_GAIN: &str = "REPLAYGAIN_TRACK_GAIN";
pub const RG_ALBUM_GAIN: &str = "REPLAYGAIN_ALBUM_GAIN";
pub const RG_TRACK_PEAK: &str = "REPLAYGAIN_TRACK_PEAK";
pub const RG_ALBUM_PEAK: &str = "REPLAYGAIN_ALBUM_PEAK";
pub const RG_REFERENCE_LOUDNESS: &str = "REPLAYGAIN_REFERENCE_LOUDNESS";
}
pub mod contiguous {
pub trait Adjacent {
fn valid_first(&self) -> bool;
fn is_next(&self, previous: &Self) -> bool;
}
impl Adjacent for u64 {
fn valid_first(&self) -> bool {
*self == 0
}
fn is_next(&self, previous: &Self) -> bool {
*self > *previous
}
}
impl Adjacent for std::num::NonZero<u8> {
fn valid_first(&self) -> bool {
*self == Self::MIN
}
fn is_next(&self, previous: &Self) -> bool {
previous.checked_add(1).map(|n| n == *self).unwrap_or(false)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Contiguous<const MAX: usize, T: Adjacent> {
items: Vec<T>,
}
impl<const MAX: usize, T: Adjacent> Default for Contiguous<MAX, T> {
fn default() -> Self {
Self { items: Vec::new() }
}
}
impl<const MAX: usize, T: Adjacent> Contiguous<MAX, T> {
pub fn with_capacity(capacity: usize) -> Self {
Self {
items: Vec::with_capacity(capacity.min(MAX)),
}
}
pub fn clear(&mut self) {
self.items.clear()
}
pub fn try_push(&mut self, item: T) -> Result<(), NonContiguous> {
if self.items.len() < MAX {
if match self.items.last() {
None => item.valid_first(),
Some(last) => item.is_next(last),
} {
self.items.push(item);
Ok(())
} else {
Err(NonContiguous)
}
} else {
Err(NonContiguous)
}
}
pub fn try_extend(
&mut self,
iter: impl IntoIterator<Item = T>,
) -> Result<(), NonContiguous> {
iter.into_iter().try_for_each(|item| self.try_push(item))
}
pub fn try_collect<I, E>(iter: I) -> Result<Result<Self, E>, NonContiguous>
where
I: IntoIterator<Item = Result<T, E>>,
{
let iter = iter.into_iter();
let mut c = Self::with_capacity(iter.size_hint().0);
for item in iter {
match item {
Ok(item) => c.try_push(item)?,
Err(err) => return Ok(Err(err)),
}
}
Ok(Ok(c))
}
}
impl<const MAX: usize, T: Adjacent> std::ops::Deref for Contiguous<MAX, T> {
type Target = [T];
fn deref(&self) -> &[T] {
self.items.as_slice()
}
}
impl<const MAX: usize, T: Adjacent> From<Contiguous<MAX, T>> for Vec<T> {
fn from(contiguous: Contiguous<MAX, T>) -> Self {
contiguous.items
}
}
impl<const MAX: usize, T: Adjacent> From<Contiguous<MAX, T>> for std::collections::VecDeque<T> {
fn from(contiguous: Contiguous<MAX, T>) -> Self {
contiguous.items.into()
}
}
impl<const MAX: usize, T: Adjacent> TryFrom<Vec<T>> for Contiguous<MAX, T> {
type Error = NonContiguous;
fn try_from(items: Vec<T>) -> Result<Self, Self::Error> {
(items.len() <= MAX && is_contiguous(&items))
.then_some(Self { items })
.ok_or(NonContiguous)
}
}
#[derive(Copy, Clone, Debug)]
pub struct NonContiguous;
impl std::error::Error for NonContiguous {}
impl std::fmt::Display for NonContiguous {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
"item is non-contiguous".fmt(f)
}
}
fn is_contiguous<'t, T: Adjacent + 't>(items: impl IntoIterator<Item = &'t T>) -> bool {
and_previous(items).all(|(prev, item)| match prev {
Some(prev) => item.is_next(prev),
None => item.valid_first(),
})
}
fn and_previous<T: Copy>(
iter: impl IntoIterator<Item = T>,
) -> impl Iterator<Item = (Option<T>, T)> {
let mut previous = None;
iter.into_iter().map(move |i| (previous.replace(i), i))
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum Cuesheet {
CDDA {
catalog_number: Option<[cuesheet::Digit; 13]>,
lead_in_samples: u64,
tracks: contiguous::Contiguous<99, cuesheet::TrackCDDA>,
lead_out: cuesheet::LeadOutCDDA,
},
NonCDDA {
catalog_number: Vec<cuesheet::Digit>,
tracks: contiguous::Contiguous<254, cuesheet::TrackNonCDDA>,
lead_out: cuesheet::LeadOutNonCDDA,
},
}
impl Cuesheet {
const LEAD_IN: u64 = 44100 * 2;
const CATALOG_LEN: usize = 128;
pub fn catalog_number(&self) -> impl std::fmt::Display {
use cuesheet::Digit;
enum Digits<'d> {
Digits(&'d [Digit]),
Empty,
}
impl std::fmt::Display for Digits<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Digits(digits) => digits.iter().try_for_each(|d| d.fmt(f)),
Self::Empty => Ok(()),
}
}
}
match self {
Self::CDDA {
catalog_number: Some(catalog_number),
..
} => Digits::Digits(catalog_number),
Self::CDDA {
catalog_number: None,
..
} => Digits::Empty,
Self::NonCDDA { catalog_number, .. } => Digits::Digits(catalog_number),
}
}
pub fn lead_in_samples(&self) -> Option<u64> {
match self {
Self::CDDA {
lead_in_samples, ..
} => Some(*lead_in_samples),
Self::NonCDDA { .. } => None,
}
}
pub fn is_cdda(&self) -> bool {
matches!(self, Self::CDDA { .. })
}
pub fn track_count(&self) -> usize {
match self {
Self::CDDA { tracks, .. } => tracks.len() + 1,
Self::NonCDDA { tracks, .. } => tracks.len() + 1,
}
}
pub fn tracks(&self) -> Box<dyn Iterator<Item = cuesheet::TrackGeneric> + '_> {
use cuesheet::{Index, Track};
match self {
Self::CDDA {
tracks, lead_out, ..
} => Box::new(
tracks
.iter()
.map(|track| Track {
offset: track.offset.into(),
number: Some(track.number.get()),
isrc: track.isrc.clone(),
non_audio: track.non_audio,
pre_emphasis: track.pre_emphasis,
index_points: track
.index_points
.iter()
.map(|point| Index {
number: point.number,
offset: point.offset.into(),
})
.collect(),
})
.chain(std::iter::once(Track {
offset: lead_out.offset.into(),
number: None,
isrc: lead_out.isrc.clone(),
non_audio: lead_out.non_audio,
pre_emphasis: lead_out.pre_emphasis,
index_points: vec![],
})),
),
Self::NonCDDA {
tracks, lead_out, ..
} => Box::new(
tracks
.iter()
.map(|track| Track {
offset: track.offset,
number: Some(track.number.get()),
isrc: track.isrc.clone(),
non_audio: track.non_audio,
pre_emphasis: track.pre_emphasis,
index_points: track
.index_points
.iter()
.map(|point| Index {
number: point.number,
offset: point.offset,
})
.collect(),
})
.chain(std::iter::once(Track {
offset: lead_out.offset,
number: None,
isrc: lead_out.isrc.clone(),
non_audio: lead_out.non_audio,
pre_emphasis: lead_out.pre_emphasis,
index_points: vec![],
})),
),
}
}
pub fn display(&self, filename: &str) -> impl std::fmt::Display {
struct DisplayCuesheet<'c, 'f> {
cuesheet: &'c Cuesheet,
filename: &'f str,
}
impl std::fmt::Display for DisplayCuesheet<'_, '_> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
#[derive(Copy, Clone)]
pub struct Timestamp {
minutes: u64,
seconds: u8,
frames: u8,
}
impl Timestamp {
const FRAMES_PER_SECOND: u64 = 75;
const SECONDS_PER_MINUTE: u64 = 60;
const SAMPLES_PER_FRAME: u64 = 44100 / 75;
}
impl From<u64> for Timestamp {
fn from(offset: u64) -> Self {
let total_frames = offset / Self::SAMPLES_PER_FRAME;
Self {
minutes: (total_frames / Self::FRAMES_PER_SECOND)
/ Self::SECONDS_PER_MINUTE,
seconds: ((total_frames / Self::FRAMES_PER_SECOND)
% Self::SECONDS_PER_MINUTE)
.try_into()
.unwrap(),
frames: (total_frames % Self::FRAMES_PER_SECOND).try_into().unwrap(),
}
}
}
impl std::fmt::Display for Timestamp {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{:02}:{:02}:{:02}",
self.minutes, self.seconds, self.frames
)
}
}
writeln!(f, "FILE \"{}\" FLAC", self.filename)?;
match self.cuesheet {
Cuesheet::CDDA { tracks, .. } => {
for track in tracks.iter() {
writeln!(
f,
" TRACK {} {}",
track.number,
if track.non_audio {
"NON_AUDIO"
} else {
"AUDIO"
}
)?;
for index in track.index_points.iter() {
writeln!(
f,
" INDEX {:02} {}",
index.number,
Timestamp::from(u64::from(index.offset + track.offset)),
)?;
}
}
}
Cuesheet::NonCDDA { tracks, .. } => {
for track in tracks.iter() {
writeln!(
f,
" TRACK {} {}",
track.number,
if track.non_audio {
"NON_AUDIO"
} else {
"AUDIO"
}
)?;
for index in track.index_points.iter() {
writeln!(
f,
" INDEX {:02} {}",
index.number,
Timestamp::from(index.offset + track.offset),
)?;
}
}
}
}
Ok(())
}
}
DisplayCuesheet {
cuesheet: self,
filename,
}
}
pub fn parse(total_samples: u64, cuesheet: &str) -> Result<Self, CuesheetError> {
use cuesheet::Digit;
fn cdda_catalog(s: &str) -> Result<Option<[Digit; 13]>, CuesheetError> {
s.chars()
.map(Digit::try_from)
.collect::<Result<Vec<_>, _>>()
.and_then(|v| {
<[Digit; 13] as TryFrom<Vec<Digit>>>::try_from(v)
.map_err(|_| CuesheetError::InvalidCatalogNumber)
})
.map(Some)
}
fn non_cdda_catalog(s: &str) -> Result<Vec<Digit>, CuesheetError> {
s.chars()
.map(Digit::try_from)
.collect::<Result<Vec<_>, _>>()
.and_then(|v| {
(v.len() <= Cuesheet::CATALOG_LEN)
.then_some(v)
.ok_or(CuesheetError::InvalidCatalogNumber)
})
}
match total_samples.try_into() {
Ok(lead_out_offset) => ParsedCuesheet::parse(cuesheet, cdda_catalog).and_then(
|ParsedCuesheet {
catalog_number,
tracks,
}| {
Ok(Self::CDDA {
catalog_number,
lead_in_samples: Self::LEAD_IN,
lead_out: cuesheet::LeadOutCDDA::new(tracks.last(), lead_out_offset)?,
tracks,
})
},
),
Err(_) => ParsedCuesheet::parse(cuesheet, non_cdda_catalog).and_then(
|ParsedCuesheet {
catalog_number,
tracks,
}| {
Ok(Self::NonCDDA {
catalog_number,
lead_out: cuesheet::LeadOutNonCDDA::new(tracks.last(), total_samples)?,
tracks,
})
},
),
}
}
fn track_offsets(&self) -> Box<dyn Iterator<Item = u64> + '_> {
match self {
Self::CDDA {
tracks, lead_out, ..
} => Box::new(
tracks
.iter()
.map(|t| u64::from(t.offset + *t.index_points.start()))
.chain(std::iter::once(u64::from(lead_out.offset))),
),
Self::NonCDDA {
tracks, lead_out, ..
} => Box::new(
tracks
.iter()
.map(|t| t.offset + t.index_points.start())
.chain(std::iter::once(lead_out.offset)),
),
}
}
pub fn track_sample_ranges(&self) -> impl Iterator<Item = std::ops::Range<u64>> {
self.track_offsets()
.zip(self.track_offsets().skip(1))
.map(|(s, e)| s..e)
}
pub fn track_byte_ranges(
&self,
channel_count: u8,
bits_per_sample: u32,
) -> impl Iterator<Item = std::ops::Range<u64>> {
assert!(channel_count > 0, "channel_count must be > 0");
assert!(bits_per_sample > 0, "bits_per_sample > 0");
let multiplier = u64::from(channel_count) * u64::from(bits_per_sample.div_ceil(8));
self.track_sample_ranges()
.map(move |std::ops::Range { start, end }| start * multiplier..end * multiplier)
}
}
block!(Cuesheet, Cuesheet, true);
optional_block!(Cuesheet, Cuesheet);
impl FromBitStream for Cuesheet {
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
let catalog_number: [u8; Self::CATALOG_LEN] = r.read_to()?;
let lead_in_samples: u64 = r.read_to()?;
let is_cdda = r.read_bit()?;
r.skip(7 + 258 * 8)?;
let track_count: u8 = r.read_to()?;
Ok(if is_cdda {
Self::CDDA {
catalog_number: {
match trim_nulls(&catalog_number) {
[] => None,
number => Some(
number
.iter()
.copied()
.map(cuesheet::Digit::try_from)
.collect::<Result<Vec<_>, u8>>()
.map_err(|_| Error::from(CuesheetError::InvalidCatalogNumber))?
.try_into()
.map_err(|_| Error::from(CuesheetError::InvalidCatalogNumber))?,
),
}
},
lead_in_samples,
tracks: contiguous::Contiguous::try_collect(
(0..track_count
.checked_sub(1)
.filter(|c| *c <= 99)
.ok_or(Error::from(CuesheetError::NoTracks))?)
.map(|_| r.parse()),
)
.map_err(|_| Error::from(CuesheetError::TracksOutOfSequence))??,
lead_out: r.parse()?,
}
} else {
Self::NonCDDA {
catalog_number: trim_nulls(&catalog_number)
.iter()
.copied()
.map(cuesheet::Digit::try_from)
.collect::<Result<Vec<_>, _>>()
.map_err(|_| Error::from(CuesheetError::InvalidCatalogNumber))?,
tracks: contiguous::Contiguous::try_collect(
(0..track_count
.checked_sub(1)
.ok_or(Error::from(CuesheetError::NoTracks))?)
.map(|_| r.parse()),
)
.map_err(|_| Error::from(CuesheetError::TracksOutOfSequence))??,
lead_out: r.parse()?,
}
})
}
}
impl ToBitStream for Cuesheet {
type Error = Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
match self {
Self::CDDA {
catalog_number,
lead_in_samples,
tracks,
lead_out,
} => {
w.write_from(match catalog_number {
Some(number) => {
let mut catalog_number = [0; Self::CATALOG_LEN];
catalog_number
.iter_mut()
.zip(number)
.for_each(|(o, i)| *o = (*i).into());
catalog_number
}
None => [0; Self::CATALOG_LEN],
})?;
w.write_from(*lead_in_samples)?;
w.write_bit(true)?; w.pad(7 + 258 * 8)?;
w.write::<8, _>(u8::try_from(tracks.len() + 1).unwrap())?;
for track in tracks.iter() {
w.build(track)?;
}
w.build(lead_out)
}
Self::NonCDDA {
catalog_number,
tracks,
lead_out,
} => {
w.write_from({
let mut number = [0; Self::CATALOG_LEN];
number
.iter_mut()
.zip(catalog_number)
.for_each(|(o, i)| *o = (*i).into());
number
})?;
w.write_from::<u64>(0)?; w.write_bit(false)?; w.pad(7 + 258 * 8)?;
w.write::<8, _>(u8::try_from(tracks.len() + 1).unwrap())?;
for track in tracks.iter() {
w.build(track)?;
}
w.build(lead_out)
}
}
}
}
fn trim_nulls(mut s: &[u8]) -> &[u8] {
while let [rest @ .., 0] = s {
s = rest;
}
s
}
type ParsedCuesheetTrack<const INDEX_MAX: usize, O> =
cuesheet::Track<O, NonZero<u8>, cuesheet::IndexVec<INDEX_MAX, O>>;
struct ParsedCuesheet<const TRACK_MAX: usize, const INDEX_MAX: usize, C, O: contiguous::Adjacent> {
catalog_number: C,
tracks: contiguous::Contiguous<TRACK_MAX, ParsedCuesheetTrack<INDEX_MAX, O>>,
}
impl<const TRACK_MAX: usize, const INDEX_MAX: usize, C, O>
ParsedCuesheet<TRACK_MAX, INDEX_MAX, C, O>
where
C: Default,
O: contiguous::Adjacent
+ std::str::FromStr
+ Into<u64>
+ std::ops::Sub<Output = O>
+ Default
+ Copy,
{
fn parse(
cuesheet: &str,
parse_catalog: impl Fn(&str) -> Result<C, CuesheetError>,
) -> Result<Self, CuesheetError> {
type WipTrack<const INDEX_MAX: usize, O> = cuesheet::Track<
Option<O>,
NonZero<u8>,
contiguous::Contiguous<INDEX_MAX, cuesheet::Index<O>>,
>;
impl<const INDEX_MAX: usize, O: contiguous::Adjacent> WipTrack<INDEX_MAX, O> {
fn new(number: NonZero<u8>) -> Self {
Self {
offset: None,
number,
isrc: cuesheet::ISRC::None,
non_audio: false,
pre_emphasis: false,
index_points: contiguous::Contiguous::default(),
}
}
}
impl<const INDEX_MAX: usize, O: contiguous::Adjacent> TryFrom<WipTrack<INDEX_MAX, O>>
for ParsedCuesheetTrack<INDEX_MAX, O>
{
type Error = CuesheetError;
fn try_from(track: WipTrack<INDEX_MAX, O>) -> Result<Self, Self::Error> {
Ok(Self {
offset: track.offset.ok_or(CuesheetError::InvalidTrack)?,
number: track.number,
isrc: track.isrc,
non_audio: track.non_audio,
pre_emphasis: track.pre_emphasis,
index_points: track.index_points.try_into()?,
})
}
}
fn unquote(s: &str) -> &str {
if s.len() > 1 && s.starts_with('"') && s.ends_with('"') {
&s[1..s.len() - 1]
} else {
s
}
}
let mut wip_track: Option<WipTrack<INDEX_MAX, O>> = None;
let mut parsed = ParsedCuesheet {
catalog_number: None,
tracks: contiguous::Contiguous::default(),
};
for line in cuesheet.lines() {
let line = line.trim();
match line.split_once(' ').unwrap_or((line, "")) {
("CATALOG", "") => return Err(CuesheetError::CatalogMissingNumber),
("CATALOG", number) => match parsed.catalog_number {
Some(_) => return Err(CuesheetError::MultipleCatalogNumber),
ref mut num @ None => {
*num = Some(parse_catalog(unquote(number))?);
}
},
("TRACK", rest) => {
if let Some(finished) = wip_track.replace(WipTrack::new(
rest.split_once(' ')
.ok_or(CuesheetError::InvalidTrack)?
.0
.parse()
.map_err(|_| CuesheetError::InvalidTrack)?,
)) {
parsed
.tracks
.try_push(finished.try_into()?)
.map_err(|_| CuesheetError::TracksOutOfSequence)?
}
}
("INDEX", rest) => {
let (number, offset) = rest
.split_once(' ')
.ok_or(CuesheetError::InvalidIndexPoint)?;
let number: u8 = number
.parse()
.map_err(|_| CuesheetError::InvalidIndexPoint)?;
let offset: O = offset
.parse()
.map_err(|_| CuesheetError::InvalidIndexPoint)?;
let wip_track = wip_track.as_mut().ok_or(CuesheetError::PrematureIndex)?;
let index = match &mut wip_track.offset {
track_offset @ None => {
if parsed.tracks.is_empty() && offset.into() != 0 {
return Err(CuesheetError::NonZeroFirstIndex);
}
*track_offset = Some(offset);
cuesheet::Index {
number,
offset: O::default(),
}
}
Some(track_offset) => {
cuesheet::Index {
number,
offset: offset - *track_offset,
}
}
};
wip_track
.index_points
.try_push(index)
.map_err(|_| CuesheetError::IndexPointsOutOfSequence)?;
}
("ISRC", isrc) => {
use cuesheet::ISRC;
let wip_track = wip_track.as_mut().ok_or(CuesheetError::PrematureISRC)?;
if !wip_track.index_points.is_empty() {
return Err(CuesheetError::LateISRC)?;
}
match &mut wip_track.isrc {
track_isrc @ ISRC::None => {
*track_isrc = ISRC::String(
unquote(isrc)
.parse()
.map_err(|_| CuesheetError::InvalidISRC)?,
);
}
ISRC::String(_) => return Err(CuesheetError::MultipleISRC),
}
}
("FLAGS", "PRE") => {
let wip_track = wip_track.as_mut().ok_or(CuesheetError::PrematureFlags)?;
if !wip_track.index_points.is_empty() {
return Err(CuesheetError::LateFlags)?;
} else {
wip_track.pre_emphasis = true;
}
}
_ => { }
}
}
parsed
.tracks
.try_push(
wip_track
.take()
.ok_or(CuesheetError::NoTracks)?
.try_into()?,
)
.map_err(|_| CuesheetError::TracksOutOfSequence)?;
Ok(ParsedCuesheet {
catalog_number: parsed.catalog_number.unwrap_or_default(),
tracks: parsed.tracks,
})
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum CuesheetError {
CatalogMissingNumber,
MultipleCatalogNumber,
InvalidCatalogNumber,
MultipleISRC,
InvalidISRC,
PrematureISRC,
LateISRC,
PrematureFlags,
LateFlags,
ISRCMissingNumber,
InvalidTrack,
NoTracks,
NonZeroStartingIndex,
PrematureIndex,
InvalidIndexPoint,
NoIndexPoints,
ExcessiveTracks,
IndexPointsOutOfSequence,
TracksOutOfSequence,
ShortLeadOut,
IndexPointsInLeadout,
InvalidCDDAOffset,
NonZeroFirstIndex,
}
impl std::error::Error for CuesheetError {}
impl std::fmt::Display for CuesheetError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::CatalogMissingNumber => "CATALOG tag missing number".fmt(f),
Self::MultipleCatalogNumber => "multiple CATALOG numbers found".fmt(f),
Self::InvalidCatalogNumber => "invalid CATALOG number".fmt(f),
Self::MultipleISRC => "multiple ISRC numbers found for track".fmt(f),
Self::InvalidISRC => "invalid ISRC number found".fmt(f),
Self::PrematureISRC => "ISRC number found before TRACK".fmt(f),
Self::LateISRC => "ISRC number found after INDEX points".fmt(f),
Self::PrematureFlags => "FLAGS found before TRACK".fmt(f),
Self::LateFlags => "FLAGS found after INDEX points".fmt(f),
Self::ISRCMissingNumber => "ISRC tag missing number".fmt(f),
Self::InvalidTrack => "invalid TRACK entry".fmt(f),
Self::NoTracks => "no TRACK entries in cue sheet".fmt(f),
Self::NonZeroStartingIndex => {
"first INDEX of first track must have 00:00:00 offset".fmt(f)
}
Self::PrematureIndex => "INDEX found before TRACK".fmt(f),
Self::InvalidIndexPoint => "invalid INDEX entry".fmt(f),
Self::NoIndexPoints => "no INDEX points in track".fmt(f),
Self::ExcessiveTracks => "excessive tracks in CUESHEET".fmt(f),
Self::IndexPointsOutOfSequence => "INDEX points out of sequence".fmt(f),
Self::TracksOutOfSequence => "TRACKS out of sequence".fmt(f),
Self::ShortLeadOut => "lead-out track not beyond final INDEX point".fmt(f),
Self::IndexPointsInLeadout => "INDEX points in lead-out TRACK".fmt(f),
Self::InvalidCDDAOffset => "invalid offset for CD-DA CUESHEET".fmt(f),
Self::NonZeroFirstIndex => "first index of first track has non-zero offset".fmt(f),
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Picture {
pub picture_type: PictureType,
pub media_type: String,
pub description: String,
pub width: u32,
pub height: u32,
pub color_depth: u32,
pub colors_used: Option<NonZero<u32>>,
pub data: Vec<u8>,
}
block!(Picture, Picture, true);
optional_block!(Picture, Picture);
impl Picture {
pub fn new<S, V>(
picture_type: PictureType,
description: S,
data: V,
) -> Result<Self, InvalidPicture>
where
S: Into<String>,
V: Into<Vec<u8>> + AsRef<[u8]>,
{
let metrics = PictureMetrics::try_new(data.as_ref())?;
Ok(Self {
picture_type,
description: description.into(),
data: data.into(),
media_type: metrics.media_type.to_owned(),
width: metrics.width,
height: metrics.height,
color_depth: metrics.color_depth,
colors_used: metrics.colors_used,
})
}
pub fn open<S, P>(
picture_type: PictureType,
description: S,
path: P,
) -> Result<Self, InvalidPicture>
where
S: Into<String>,
P: AsRef<Path>,
{
std::fs::read(path)
.map_err(InvalidPicture::Io)
.and_then(|data| Self::new(picture_type, description, data))
}
}
impl FromBitStream for Picture {
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Error> {
fn prefixed_field<R: BitRead + ?Sized>(r: &mut R) -> std::io::Result<Vec<u8>> {
let size = r.read_to::<u32>()?;
r.read_to_vec(size.try_into().unwrap())
}
Ok(Self {
picture_type: r.parse()?,
media_type: String::from_utf8(prefixed_field(r)?)?,
description: String::from_utf8(prefixed_field(r)?)?,
width: r.read_to()?,
height: r.read_to()?,
color_depth: r.read_to()?,
colors_used: r.read::<32, _>()?,
data: prefixed_field(r)?,
})
}
}
impl ToBitStream for Picture {
type Error = Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Error> {
fn prefixed_field<W: BitWrite + ?Sized>(
w: &mut W,
field: &[u8],
error: Error,
) -> Result<(), Error> {
w.write_from::<u32>(field.len().try_into().map_err(|_| error)?)
.map_err(Error::Io)?;
w.write_bytes(field).map_err(Error::Io)
}
w.build(&self.picture_type)?;
prefixed_field(w, self.media_type.as_bytes(), Error::ExcessiveStringLength)?;
prefixed_field(w, self.description.as_bytes(), Error::ExcessiveStringLength)?;
w.write_from(self.width)?;
w.write_from(self.height)?;
w.write_from(self.color_depth)?;
w.write::<32, _>(self.colors_used)?;
prefixed_field(w, &self.data, Error::ExcessivePictureSize)
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum PictureType {
Other = 0,
Png32x32 = 1,
GeneralFileIcon = 2,
FrontCover = 3,
BackCover = 4,
LinerNotes = 5,
MediaLabel = 6,
LeadArtist = 7,
Artist = 8,
Conductor = 9,
Band = 10,
Composer = 11,
Lyricist = 12,
RecordingLocation = 13,
DuringRecording = 14,
DuringPerformance = 15,
ScreenCapture = 16,
Fish = 17,
Illustration = 18,
BandLogo = 19,
PublisherLogo = 20,
}
impl std::fmt::Display for PictureType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Other => "Other".fmt(f),
Self::Png32x32 => "32×32 PNG Icon".fmt(f),
Self::GeneralFileIcon => "General File Icon".fmt(f),
Self::FrontCover => "Cover (front)".fmt(f),
Self::BackCover => "Cover (back)".fmt(f),
Self::LinerNotes => "Liner Notes".fmt(f),
Self::MediaLabel => "Media Label".fmt(f),
Self::LeadArtist => "Lead Artist".fmt(f),
Self::Artist => "Artist".fmt(f),
Self::Conductor => "Conductor".fmt(f),
Self::Band => "Band or Orchestra".fmt(f),
Self::Composer => "Composer".fmt(f),
Self::Lyricist => "lyricist or Text Writer".fmt(f),
Self::RecordingLocation => "Recording Location".fmt(f),
Self::DuringRecording => "During Recording".fmt(f),
Self::DuringPerformance => "During Performance".fmt(f),
Self::ScreenCapture => "Movie or Video Screen Capture".fmt(f),
Self::Fish => "A Bright Colored Fish".fmt(f),
Self::Illustration => "Illustration".fmt(f),
Self::BandLogo => "Band or Artist Logotype".fmt(f),
Self::PublisherLogo => "Publisher or Studio Logotype".fmt(f),
}
}
}
impl FromBitStream for PictureType {
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Error> {
match r.read_to::<u32>()? {
0 => Ok(Self::Other),
1 => Ok(Self::Png32x32),
2 => Ok(Self::GeneralFileIcon),
3 => Ok(Self::FrontCover),
4 => Ok(Self::BackCover),
5 => Ok(Self::LinerNotes),
6 => Ok(Self::MediaLabel),
7 => Ok(Self::LeadArtist),
8 => Ok(Self::Artist),
9 => Ok(Self::Conductor),
10 => Ok(Self::Band),
11 => Ok(Self::Composer),
12 => Ok(Self::Lyricist),
13 => Ok(Self::RecordingLocation),
14 => Ok(Self::DuringRecording),
15 => Ok(Self::DuringPerformance),
16 => Ok(Self::ScreenCapture),
17 => Ok(Self::Fish),
18 => Ok(Self::Illustration),
19 => Ok(Self::BandLogo),
20 => Ok(Self::PublisherLogo),
_ => Err(Error::InvalidPictureType),
}
}
}
impl ToBitStream for PictureType {
type Error = std::io::Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
w.write_from::<u32>(match self {
Self::Other => 0,
Self::Png32x32 => 1,
Self::GeneralFileIcon => 2,
Self::FrontCover => 3,
Self::BackCover => 4,
Self::LinerNotes => 5,
Self::MediaLabel => 6,
Self::LeadArtist => 7,
Self::Artist => 8,
Self::Conductor => 9,
Self::Band => 10,
Self::Composer => 11,
Self::Lyricist => 12,
Self::RecordingLocation => 13,
Self::DuringRecording => 14,
Self::DuringPerformance => 15,
Self::ScreenCapture => 16,
Self::Fish => 17,
Self::Illustration => 18,
Self::BandLogo => 19,
Self::PublisherLogo => 20,
})
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum InvalidPicture {
Io(std::io::Error),
Unsupported,
Png(&'static str),
Jpeg(&'static str),
Gif(&'static str),
}
impl From<std::io::Error> for InvalidPicture {
#[inline]
fn from(err: std::io::Error) -> Self {
Self::Io(err)
}
}
impl std::error::Error for InvalidPicture {}
impl std::fmt::Display for InvalidPicture {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Io(err) => err.fmt(f),
Self::Unsupported => "unsupported image format".fmt(f),
Self::Png(s) => write!(f, "PNG parsing error : {s}"),
Self::Jpeg(s) => write!(f, "JPEG parsing error : {s}"),
Self::Gif(s) => write!(f, "GIF parsing error : {s}"),
}
}
}
struct PictureMetrics {
media_type: &'static str,
width: u32,
height: u32,
color_depth: u32,
colors_used: Option<NonZero<u32>>,
}
impl PictureMetrics {
fn try_new(data: &[u8]) -> Result<Self, InvalidPicture> {
if data.starts_with(b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A") {
Self::try_png(data)
} else if data.starts_with(b"\xFF\xD8\xFF") {
Self::try_jpeg(data)
} else if data.starts_with(b"GIF") {
Self::try_gif(data)
} else {
Err(InvalidPicture::Unsupported)
}
}
fn try_png(data: &[u8]) -> Result<Self, InvalidPicture> {
fn plte_colors<R: ByteRead>(mut r: R) -> Result<u32, InvalidPicture> {
loop {
let block_len = r.read::<u32>()?;
match r.read::<[u8; 4]>()?.as_slice() {
b"PLTE" => {
if block_len % 3 == 0 {
break Ok(block_len / 3);
} else {
break Err(InvalidPicture::Png("invalid PLTE length"));
}
}
_ => {
r.skip(block_len)?;
let _crc = r.read::<u32>()?;
}
}
}
}
let mut r = ByteReader::endian(data, BigEndian);
if &r.read::<[u8; 8]>()? != b"\x89\x50\x4E\x47\x0D\x0A\x1A\x0A" {
return Err(InvalidPicture::Png("not a PNG image"));
}
if r.read::<u32>()? != 0x0d {
return Err(InvalidPicture::Png("invalid IHDR length"));
}
if &r.read::<[u8; 4]>()? != b"IHDR" {
return Err(InvalidPicture::Png("IHDR chunk not first"));
}
let width = r.read()?;
let height = r.read()?;
let bit_depth = r.read::<u8>()?;
let color_type = r.read::<u8>()?;
let _compression_method = r.read::<u8>()?;
let _filter_method = r.read::<u8>()?;
let _interlace_method = r.read::<u8>()?;
let _crc = r.read::<u32>()?;
let (color_depth, colors_used) = match color_type {
0 => (bit_depth.into(), None), 2 => ((bit_depth * 3).into(), None), 3 => (0, NonZero::new(plte_colors(r)?)), 4 => ((bit_depth * 2).into(), None), 6 => ((bit_depth * 4).into(), None), _ => return Err(InvalidPicture::Png("invalid color type")),
};
Ok(Self {
media_type: "image/png",
width,
height,
color_depth,
colors_used,
})
}
fn try_jpeg(data: &[u8]) -> Result<Self, InvalidPicture> {
let mut r = ByteReader::endian(data, BigEndian);
if r.read::<u8>()? != 0xFF || r.read::<u8>()? != 0xD8 {
return Err(InvalidPicture::Jpeg("invalid JPEG marker"));
}
loop {
if r.read::<u8>()? != 0xFF {
break Err(InvalidPicture::Jpeg("invalid JPEG marker"));
}
match r.read::<u8>()? {
0xC0 | 0xC1 | 0xC2 | 0xC3 | 0xC5 | 0xC6 | 0xC7 | 0xC9 | 0xCA | 0xCB | 0xCD
| 0xCE | 0xCF => {
let _len = r.read::<u16>()?;
let data_precision = r.read::<u8>()?;
let height = r.read::<u16>()?;
let width = r.read::<u16>()?;
let components = r.read::<u8>()?;
break Ok(Self {
media_type: "image/jpeg",
width: width.into(),
height: height.into(),
color_depth: (data_precision * components).into(),
colors_used: None,
});
}
_ => {
let segment_length = r
.read::<u16>()?
.checked_sub(2)
.ok_or(InvalidPicture::Jpeg("invalid segment length"))?;
r.skip(segment_length.into())?;
}
}
}
}
fn try_gif(data: &[u8]) -> Result<Self, InvalidPicture> {
let mut r = BitReader::endian(data, LittleEndian);
if &r.read_to::<[u8; 3]>()? != b"GIF" {
return Err(InvalidPicture::Gif("invalid GIF signature"));
}
r.skip(3 * 8)?;
Ok(Self {
media_type: "image/gif",
width: r.read::<16, _>()?,
height: r.read::<16, _>()?,
colors_used: NonZero::new(1 << (r.read::<3, u32>()? + 1)),
color_depth: 0,
})
}
}
#[derive(Clone, Debug)]
pub struct BlockList {
streaminfo: Streaminfo,
blocks: Vec<private::OptionalBlock>,
}
impl BlockList {
pub fn new(streaminfo: Streaminfo) -> Self {
Self {
streaminfo,
blocks: Vec::default(),
}
}
pub fn read<R: std::io::Read>(r: R) -> Result<Self, Error> {
read_blocks(r).collect::<Result<Result<Self, _>, _>>()?
}
pub fn open<P: AsRef<Path>>(p: P) -> Result<Self, Error> {
File::open(p.as_ref())
.map(BufReader::new)
.map_err(Error::Io)
.and_then(BlockList::read)
}
pub fn streaminfo(&self) -> &Streaminfo {
&self.streaminfo
}
pub fn streaminfo_mut(&mut self) -> &mut Streaminfo {
&mut self.streaminfo
}
pub fn blocks(&self) -> impl Iterator<Item = BlockRef<'_>> {
std::iter::once(self.streaminfo.as_block_ref())
.chain(self.blocks.iter().map(|b| b.as_block_ref()))
}
pub fn insert<B: OptionalMetadataBlock>(&mut self, block: B) -> Option<B> {
if B::MULTIPLE {
self.blocks.push(block.into());
None
} else {
match self
.blocks
.iter_mut()
.find_map(|b| B::try_from_opt_block_mut(b).ok())
{
Some(b) => Some(std::mem::replace(b, block)),
None => {
self.blocks.push(block.into());
None
}
}
}
}
pub fn get<B: OptionalMetadataBlock>(&self) -> Option<&B> {
self.blocks
.iter()
.find_map(|b| B::try_from_opt_block(b).ok())
}
pub fn get_mut<B: OptionalMetadataBlock>(&mut self) -> Option<&mut B> {
self.blocks
.iter_mut()
.find_map(|b| B::try_from_opt_block_mut(b).ok())
}
pub fn get_pair_mut<B, C>(&mut self) -> (Option<&mut B>, Option<&mut C>)
where
B: OptionalMetadataBlock,
C: OptionalMetadataBlock,
{
use std::ops::ControlFlow;
match self
.blocks
.iter_mut()
.try_fold((None, None), |acc, block| match acc {
(first @ None, second @ None) => {
ControlFlow::Continue(match B::try_from_opt_block_mut(block) {
Ok(first) => (Some(first), second),
Err(block) => (first, C::try_from_opt_block_mut(block).ok()),
})
}
(first @ Some(_), None) => {
ControlFlow::Continue((first, C::try_from_opt_block_mut(block).ok()))
}
(None, second @ Some(_)) => {
ControlFlow::Continue((B::try_from_opt_block_mut(block).ok(), second))
}
pair @ (Some(_), Some(_)) => ControlFlow::Break(pair),
}) {
ControlFlow::Break(p) | ControlFlow::Continue(p) => p,
}
}
pub fn get_all<'b, B: OptionalMetadataBlock + 'b>(&'b self) -> impl Iterator<Item = &'b B> {
self.blocks
.iter()
.filter_map(|b| B::try_from_opt_block(b).ok())
}
pub fn get_all_mut<'b, B: OptionalMetadataBlock + 'b>(
&'b mut self,
) -> impl Iterator<Item = &'b mut B> {
self.blocks
.iter_mut()
.filter_map(|b| B::try_from_opt_block_mut(b).ok())
}
pub fn has<B: OptionalMetadataBlock>(&self) -> bool {
self.get::<B>().is_some()
}
pub fn remove<B: OptionalMetadataBlock>(&mut self) {
self.blocks.retain(|b| b.block_type() != B::TYPE)
}
pub fn extract<B: OptionalMetadataBlock>(&mut self) -> impl Iterator<Item = B> {
self.blocks
.extract_if(.., |block| block.block_type() == B::TYPE)
.filter_map(|b| B::try_from(b).ok())
}
pub fn update<B>(&mut self, f: impl FnOnce(&mut B))
where
B: OptionalMetadataBlock + Default,
{
match self.get_mut() {
Some(block) => f(block),
None => {
let mut b = B::default();
f(&mut b);
self.blocks.push(b.into());
}
}
}
pub fn sort_by<O: Ord>(&mut self, f: impl Fn(OptionalBlockType) -> O) {
self.blocks
.sort_by_key(|block| f(block.optional_block_type()));
}
}
impl Metadata for BlockList {
fn channel_count(&self) -> u8 {
self.streaminfo.channels.get()
}
fn channel_mask(&self) -> ChannelMask {
use fields::CHANNEL_MASK;
self.get::<VorbisComment>()
.and_then(|c| c.get(CHANNEL_MASK).and_then(|m| m.parse().ok()))
.unwrap_or(ChannelMask::from_channels(self.channel_count()))
}
fn sample_rate(&self) -> u32 {
self.streaminfo.sample_rate
}
fn bits_per_sample(&self) -> u32 {
self.streaminfo.bits_per_sample.into()
}
fn total_samples(&self) -> Option<u64> {
self.streaminfo.total_samples.map(|s| s.get())
}
fn md5(&self) -> Option<&[u8; 16]> {
self.streaminfo.md5.as_ref()
}
}
impl FromIterator<Block> for Result<BlockList, Error> {
fn from_iter<T: IntoIterator<Item = Block>>(iter: T) -> Self {
let mut iter = iter.into_iter();
let mut list = match iter.next() {
Some(Block::Streaminfo(streaminfo)) => BlockList::new(streaminfo),
Some(_) | None => return Err(Error::MissingStreaminfo),
};
for block in iter {
match block {
Block::Streaminfo(_) => return Err(Error::MultipleStreaminfo),
Block::Padding(p) => {
list.insert(p);
}
Block::Application(p) => {
list.insert(p);
}
Block::SeekTable(p) => {
list.insert(p);
}
Block::VorbisComment(p) => {
list.insert(p);
}
Block::Cuesheet(p) => {
list.insert(p);
}
Block::Picture(p) => {
list.insert(p);
}
}
}
Ok(list)
}
}
impl IntoIterator for BlockList {
type Item = Block;
type IntoIter = Box<dyn Iterator<Item = Block>>;
fn into_iter(self) -> Self::IntoIter {
Box::new(
std::iter::once(self.streaminfo.into())
.chain(self.blocks.into_iter().map(|b| b.into())),
)
}
}
impl<B: OptionalMetadataBlock> Extend<B> for BlockList {
fn extend<I>(&mut self, iter: I)
where
I: IntoIterator<Item = B>,
{
for block in iter {
self.insert(block);
}
}
}
pub trait OptionalMetadataBlock: MetadataBlock + private::OptionalMetadataBlock {
const OPTIONAL_TYPE: OptionalBlockType;
}
pub trait PortableMetadataBlock: OptionalMetadataBlock {}
impl PortableMetadataBlock for Padding {}
impl PortableMetadataBlock for Application {}
impl PortableMetadataBlock for VorbisComment {}
impl PortableMetadataBlock for Cuesheet {}
impl PortableMetadataBlock for Picture {}
mod private {
use super::{
Application, AsBlockRef, Block, BlockRef, BlockType, Cuesheet, OptionalBlockType, Padding,
Picture, SeekTable, Streaminfo, VorbisComment,
};
#[derive(Clone, Debug)]
pub enum OptionalBlock {
Padding(Padding),
Application(Application),
SeekTable(SeekTable),
VorbisComment(VorbisComment),
Cuesheet(Cuesheet),
Picture(Picture),
}
impl OptionalBlock {
pub fn block_type(&self) -> BlockType {
match self {
Self::Padding(_) => BlockType::Padding,
Self::Application(_) => BlockType::Application,
Self::SeekTable(_) => BlockType::SeekTable,
Self::VorbisComment(_) => BlockType::VorbisComment,
Self::Cuesheet(_) => BlockType::Cuesheet,
Self::Picture(_) => BlockType::Picture,
}
}
pub fn optional_block_type(&self) -> OptionalBlockType {
match self {
Self::Padding(_) => OptionalBlockType::Padding,
Self::Application(_) => OptionalBlockType::Application,
Self::SeekTable(_) => OptionalBlockType::SeekTable,
Self::VorbisComment(_) => OptionalBlockType::VorbisComment,
Self::Cuesheet(_) => OptionalBlockType::Cuesheet,
Self::Picture(_) => OptionalBlockType::Picture,
}
}
}
impl From<OptionalBlock> for Block {
fn from(block: OptionalBlock) -> Block {
match block {
OptionalBlock::Padding(p) => Block::Padding(p),
OptionalBlock::Application(a) => Block::Application(a),
OptionalBlock::SeekTable(s) => Block::SeekTable(s),
OptionalBlock::VorbisComment(v) => Block::VorbisComment(v),
OptionalBlock::Cuesheet(c) => Block::Cuesheet(c),
OptionalBlock::Picture(p) => Block::Picture(p),
}
}
}
impl TryFrom<Block> for OptionalBlock {
type Error = Streaminfo;
fn try_from(block: Block) -> Result<Self, Streaminfo> {
match block {
Block::Streaminfo(s) => Err(s),
Block::Padding(p) => Ok(OptionalBlock::Padding(p)),
Block::Application(a) => Ok(OptionalBlock::Application(a)),
Block::SeekTable(s) => Ok(OptionalBlock::SeekTable(s)),
Block::VorbisComment(v) => Ok(OptionalBlock::VorbisComment(v)),
Block::Cuesheet(c) => Ok(OptionalBlock::Cuesheet(c)),
Block::Picture(p) => Ok(OptionalBlock::Picture(p)),
}
}
}
impl AsBlockRef for OptionalBlock {
fn as_block_ref(&self) -> BlockRef<'_> {
match self {
Self::Padding(p) => BlockRef::Padding(p),
Self::Application(a) => BlockRef::Application(a),
Self::SeekTable(s) => BlockRef::SeekTable(s),
Self::VorbisComment(v) => BlockRef::VorbisComment(v),
Self::Cuesheet(v) => BlockRef::Cuesheet(v),
Self::Picture(p) => BlockRef::Picture(p),
}
}
}
pub trait OptionalMetadataBlock: Into<OptionalBlock> + TryFrom<OptionalBlock> {
fn try_from_opt_block(block: &OptionalBlock) -> Result<&Self, &OptionalBlock>;
fn try_from_opt_block_mut(
block: &mut OptionalBlock,
) -> Result<&mut Self, &mut OptionalBlock>;
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct ChannelMask {
mask: u32,
}
impl ChannelMask {
pub fn channels(&self) -> impl Iterator<Item = Channel> {
[
Channel::FrontLeft,
Channel::FrontRight,
Channel::FrontCenter,
Channel::Lfe,
Channel::BackLeft,
Channel::BackRight,
Channel::FrontLeftOfCenter,
Channel::FrontRightOfCenter,
Channel::BackCenter,
Channel::SideLeft,
Channel::SideRight,
Channel::TopCenter,
Channel::TopFrontLeft,
Channel::TopFrontCenter,
Channel::TopFrontRight,
Channel::TopRearLeft,
Channel::TopRearCenter,
Channel::TopRearRight,
]
.into_iter()
.filter(|channel| (*channel as u32 & self.mask) != 0)
}
fn from_channels(channels: u8) -> Self {
match channels {
1 => Self {
mask: Channel::FrontCenter as u32,
},
2 => Self {
mask: Channel::FrontLeft as u32 | Channel::FrontRight as u32,
},
3 => Self {
mask: Channel::FrontLeft as u32
| Channel::FrontRight as u32
| Channel::FrontCenter as u32,
},
4 => Self {
mask: Channel::FrontLeft as u32
| Channel::FrontRight as u32
| Channel::BackLeft as u32
| Channel::BackRight as u32,
},
5 => Self {
mask: Channel::FrontLeft as u32
| Channel::FrontRight as u32
| Channel::FrontCenter as u32
| Channel::SideLeft as u32
| Channel::SideRight as u32,
},
6 => Self {
mask: Channel::FrontLeft as u32
| Channel::FrontRight as u32
| Channel::FrontCenter as u32
| Channel::Lfe as u32
| Channel::SideLeft as u32
| Channel::SideRight as u32,
},
7 => Self {
mask: Channel::FrontLeft as u32
| Channel::FrontRight as u32
| Channel::FrontCenter as u32
| Channel::Lfe as u32
| Channel::BackCenter as u32
| Channel::SideLeft as u32
| Channel::SideRight as u32,
},
8 => Self {
mask: Channel::FrontLeft as u32
| Channel::FrontRight as u32
| Channel::FrontCenter as u32
| Channel::Lfe as u32
| Channel::BackLeft as u32
| Channel::BackRight as u32
| Channel::SideLeft as u32
| Channel::SideRight as u32,
},
_ => panic!("undefined channel count"),
}
}
}
impl std::str::FromStr for ChannelMask {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.split_once('x').ok_or(())? {
("0", hex) => u32::from_str_radix(hex, 16)
.map(|mask| ChannelMask { mask })
.map_err(|_| ()),
_ => Err(()),
}
}
}
impl std::fmt::Display for ChannelMask {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "0x{:04x}", self.mask)
}
}
impl From<ChannelMask> for u32 {
fn from(mask: ChannelMask) -> u32 {
mask.mask
}
}
impl From<u32> for ChannelMask {
fn from(mask: u32) -> ChannelMask {
ChannelMask { mask }
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Channel {
FrontLeft = 0b1,
FrontRight = 0b10,
FrontCenter = 0b100,
Lfe = 0b1000,
BackLeft = 0b10000,
BackRight = 0b100000,
FrontLeftOfCenter = 0b1000000,
FrontRightOfCenter = 0b10000000,
BackCenter = 0b100000000,
SideLeft = 0b1000000000,
SideRight = 0b10000000000,
TopCenter = 0b100000000000,
TopFrontLeft = 0b1000000000000,
TopFrontCenter = 0b10000000000000,
TopFrontRight = 0b100000000000000,
TopRearLeft = 0b1000000000000000,
TopRearCenter = 0b10000000000000000,
TopRearRight = 0b100000000000000000,
}
impl std::fmt::Display for Channel {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::FrontLeft => "front left".fmt(f),
Self::FrontRight => "front right".fmt(f),
Self::FrontCenter => "front center".fmt(f),
Self::Lfe => "LFE".fmt(f),
Self::BackLeft => "back left".fmt(f),
Self::BackRight => "back right".fmt(f),
Self::FrontLeftOfCenter => "front left of center".fmt(f),
Self::FrontRightOfCenter => "front right of center".fmt(f),
Self::BackCenter => "back center".fmt(f),
Self::SideLeft => "side left".fmt(f),
Self::SideRight => "side right".fmt(f),
Self::TopCenter => "top center".fmt(f),
Self::TopFrontLeft => "top front left".fmt(f),
Self::TopFrontCenter => "top front center".fmt(f),
Self::TopFrontRight => "top front right".fmt(f),
Self::TopRearLeft => "top rear left".fmt(f),
Self::TopRearCenter => "top rear center".fmt(f),
Self::TopRearRight => "top rear right".fmt(f),
}
}
}