use crate::Error;
use crate::metadata::CuesheetError;
use crate::metadata::contiguous::{Adjacent, Contiguous};
use bitstream_io::{BitRead, BitWrite, FromBitStream, ToBitStream};
use std::num::NonZero;
use std::str::FromStr;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum Digit {
Digit0 = 48,
Digit1 = 49,
Digit2 = 50,
Digit3 = 51,
Digit4 = 52,
Digit5 = 53,
Digit6 = 54,
Digit7 = 55,
Digit8 = 56,
Digit9 = 57,
}
impl TryFrom<u8> for Digit {
type Error = u8;
fn try_from(u: u8) -> Result<Digit, u8> {
match u {
48 => Ok(Self::Digit0),
49 => Ok(Self::Digit1),
50 => Ok(Self::Digit2),
51 => Ok(Self::Digit3),
52 => Ok(Self::Digit4),
53 => Ok(Self::Digit5),
54 => Ok(Self::Digit6),
55 => Ok(Self::Digit7),
56 => Ok(Self::Digit8),
57 => Ok(Self::Digit9),
u => Err(u),
}
}
}
impl TryFrom<char> for Digit {
type Error = CuesheetError;
fn try_from(c: char) -> Result<Digit, CuesheetError> {
match c {
'0' => Ok(Self::Digit0),
'1' => Ok(Self::Digit1),
'2' => Ok(Self::Digit2),
'3' => Ok(Self::Digit3),
'4' => Ok(Self::Digit4),
'5' => Ok(Self::Digit5),
'6' => Ok(Self::Digit6),
'7' => Ok(Self::Digit7),
'8' => Ok(Self::Digit8),
'9' => Ok(Self::Digit9),
_ => Err(CuesheetError::InvalidCatalogNumber),
}
}
}
impl From<Digit> for u8 {
fn from(d: Digit) -> u8 {
d as u8
}
}
impl std::fmt::Display for Digit {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Digit0 => '0'.fmt(f),
Self::Digit1 => '1'.fmt(f),
Self::Digit2 => '2'.fmt(f),
Self::Digit3 => '3'.fmt(f),
Self::Digit4 => '4'.fmt(f),
Self::Digit5 => '5'.fmt(f),
Self::Digit6 => '6'.fmt(f),
Self::Digit7 => '7'.fmt(f),
Self::Digit8 => '8'.fmt(f),
Self::Digit9 => '9'.fmt(f),
}
}
}
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
pub struct CDDAOffset {
offset: u64,
}
impl CDDAOffset {
const SAMPLES_PER_SECTOR: u64 = 44100 / 75;
}
impl std::ops::Sub for CDDAOffset {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self {
offset: self.offset - rhs.offset,
}
}
}
impl FromStr for CDDAOffset {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
let (mm, rest) = s.split_once(':').ok_or(())?;
let (ss, ff) = rest.split_once(':').ok_or(())?;
let ff: u64 = ff.parse().ok().filter(|ff| *ff < 75).ok_or(())?;
let ss: u64 = ss.parse().ok().filter(|ss| *ss < 60).ok_or(())?;
let mm: u64 = mm.parse().map_err(|_| ())?;
Ok(Self {
offset: (ff + ss * 75 + mm * 75 * 60) * 588,
})
}
}
impl std::fmt::Display for CDDAOffset {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.offset.fmt(f)
}
}
impl From<CDDAOffset> for u64 {
fn from(o: CDDAOffset) -> Self {
o.offset
}
}
impl TryFrom<u64> for CDDAOffset {
type Error = u64;
fn try_from(offset: u64) -> Result<Self, Self::Error> {
offset
.is_multiple_of(Self::SAMPLES_PER_SECTOR)
.then_some(Self { offset })
.ok_or(offset)
}
}
impl std::ops::Add for CDDAOffset {
type Output = Self;
fn add(self, rhs: CDDAOffset) -> Self {
Self {
offset: self.offset + rhs.offset,
}
}
}
impl FromBitStream for CDDAOffset {
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
Ok(Self {
offset: r.read_to().map_err(Error::Io).and_then(|o| {
((o % Self::SAMPLES_PER_SECTOR) == 0)
.then_some(o)
.ok_or(CuesheetError::InvalidCDDAOffset.into())
})?,
})
}
}
impl ToBitStream for CDDAOffset {
type Error = std::io::Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
w.write_from(self.offset)
}
}
impl Adjacent for CDDAOffset {
fn valid_first(&self) -> bool {
self.offset == 0
}
fn is_next(&self, previous: &Self) -> bool {
self.offset > previous.offset
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct LeadOut;
impl LeadOut {
pub const CDDA: NonZero<u8> = NonZero::new(170).unwrap();
pub const NON_CDDA: NonZero<u8> = NonZero::new(255).unwrap();
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ISRCString(String);
impl std::fmt::Display for ISRCString {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl AsRef<str> for ISRCString {
fn as_ref(&self) -> &str {
self.0.as_str()
}
}
impl FromStr for ISRCString {
type Err = CuesheetError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
use std::borrow::Cow;
fn filter_split(s: &str, amt: usize, f: impl Fn(char) -> bool) -> Option<&str> {
s.split_at_checked(amt)
.and_then(|(prefix, rest)| prefix.chars().all(f).then_some(rest))
}
let isrc: Cow<'_, str> = if s.contains('-') {
s.chars().filter(|c| *c != '-').collect::<String>().into()
} else {
s.into()
};
filter_split(&isrc, 2, |c| c.is_ascii_alphabetic())
.and_then(|s| filter_split(s, 3, |c| c.is_ascii_alphanumeric()))
.and_then(|s| filter_split(s, 2, |c| c.is_ascii_digit()))
.and_then(|s| s.chars().all(|c| c.is_ascii_digit()).then_some(()))
.map(|()| ISRCString(isrc.into_owned()))
.ok_or(CuesheetError::InvalidISRC)
}
}
#[derive(Default, Debug, Clone, Eq, PartialEq)]
pub enum ISRC {
#[default]
None,
String(ISRCString),
}
impl std::fmt::Display for ISRC {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::String(s) => s.fmt(f),
Self::None => "".fmt(f),
}
}
}
impl FromBitStream for ISRC {
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Error> {
let isrc = r.read_to::<[u8; 12]>()?;
if isrc.iter().all(|b| *b == 0) {
Ok(ISRC::None)
} else {
let s = str::from_utf8(&isrc).map_err(|_| CuesheetError::InvalidISRC)?;
Ok(ISRC::String(s.parse()?))
}
}
}
impl ToBitStream for ISRC {
type Error = std::io::Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), std::io::Error> {
w.write_from(match self {
Self::String(isrc) => {
let mut o = [0; 12];
o.iter_mut()
.zip(isrc.as_ref().as_bytes())
.for_each(|(o, i)| *o = *i);
o
}
Self::None => [0; 12],
})
}
}
impl AsRef<str> for ISRC {
fn as_ref(&self) -> &str {
match self {
Self::String(s) => s.as_ref(),
Self::None => "",
}
}
}
impl FromStr for ISRC {
type Err = CuesheetError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
ISRCString::from_str(s).map(ISRC::String)
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Track<O, N, P> {
pub offset: O,
pub number: N,
pub isrc: ISRC,
pub non_audio: bool,
pub pre_emphasis: bool,
pub index_points: P,
}
impl<const MAX: usize, O: Adjacent, N: Adjacent> Adjacent for Track<O, N, IndexVec<MAX, O>> {
fn valid_first(&self) -> bool {
self.offset.valid_first() && self.number.valid_first()
}
fn is_next(&self, previous: &Self) -> bool {
self.number.is_next(&previous.number) && self.offset.is_next(previous.index_points.last())
}
}
pub type TrackGeneric = Track<u64, Option<u8>, Vec<Index<u64>>>;
pub type TrackCDDA = Track<CDDAOffset, NonZero<u8>, IndexVec<100, CDDAOffset>>;
impl FromBitStream for TrackCDDA {
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
let offset = r.parse()?;
let number = r
.read_to()
.map_err(Error::Io)
.and_then(|s| NonZero::new(s).ok_or(Error::from(CuesheetError::InvalidIndexPoint)))?;
let isrc = r.parse()?;
let non_audio = r.read_bit()?;
let pre_emphasis = r.read_bit()?;
r.skip(6 + 13 * 8)?;
let index_point_count = r.read_to::<u8>()?;
Ok(Self {
offset,
number,
isrc,
non_audio,
pre_emphasis,
index_points: IndexVec::try_from(
Contiguous::try_collect((0..index_point_count).map(|_| r.parse()))
.map_err(|_| Error::from(CuesheetError::IndexPointsOutOfSequence))??,
)?,
})
}
}
impl ToBitStream for TrackCDDA {
type Error = Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
w.build(&self.offset)?;
w.write_from(self.number.get())?;
w.build(&self.isrc)?;
w.write_bit(self.non_audio)?;
w.write_bit(self.pre_emphasis)?;
w.pad(6 + 13 * 8)?;
w.write_from::<u8>(self.index_points.len().try_into().unwrap())?;
for point in self.index_points.iter() {
w.build(point)?;
}
Ok(())
}
}
pub type TrackNonCDDA = Track<u64, NonZero<u8>, IndexVec<256, u64>>;
impl FromBitStream for TrackNonCDDA {
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
let offset = r.read_to()?;
let number = r
.read_to()
.map_err(Error::Io)
.and_then(|s| NonZero::new(s).ok_or(Error::from(CuesheetError::InvalidIndexPoint)))?;
let isrc = r.parse()?;
let non_audio = r.read_bit()?;
let pre_emphasis = r.read_bit()?;
r.skip(6 + 13 * 8)?;
let index_point_count = r.read_to::<u8>()?;
Ok(Self {
offset,
number,
isrc,
non_audio,
pre_emphasis,
index_points: IndexVec::try_from(
Contiguous::try_collect((0..index_point_count).map(|_| r.parse()))
.map_err(|_| Error::from(CuesheetError::IndexPointsOutOfSequence))??,
)?,
})
}
}
impl ToBitStream for TrackNonCDDA {
type Error = Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
w.write_from(self.offset)?;
w.write_from(self.number.get())?;
w.build(&self.isrc)?;
w.write_bit(self.non_audio)?;
w.write_bit(self.pre_emphasis)?;
w.pad(6 + 13 * 8)?;
w.write_from::<u8>(self.index_points.len().try_into().unwrap())?;
for point in self.index_points.iter() {
w.build(point)?;
}
Ok(())
}
}
pub type LeadOutCDDA = Track<CDDAOffset, LeadOut, ()>;
impl FromBitStream for LeadOutCDDA {
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
let offset = r.parse()?;
let number = r.read_to::<u8>().map_err(Error::Io).and_then(|n| {
NonZero::new(n)
.filter(|n| *n == LeadOut::CDDA)
.map(|_| LeadOut)
.ok_or(CuesheetError::TracksOutOfSequence.into())
})?;
let isrc = r.parse()?;
let non_audio = r.read_bit()?;
let pre_emphasis = r.read_bit()?;
r.skip(6 + 13 * 8)?;
match r.read_to::<u8>()? {
0 => Ok(Self {
offset,
number,
isrc,
non_audio,
pre_emphasis,
index_points: (),
}),
_ => Err(CuesheetError::IndexPointsInLeadout.into()),
}
}
}
impl ToBitStream for LeadOutCDDA {
type Error = Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
w.build(&self.offset)?;
w.write_from(LeadOut::CDDA.get())?;
w.build(&self.isrc)?;
w.write_bit(self.non_audio)?;
w.write_bit(self.pre_emphasis)?;
w.pad(6 + 13 * 8)?;
w.write_from::<u8>(0)?;
Ok(())
}
}
impl LeadOutCDDA {
pub fn new(last: Option<&TrackCDDA>, offset: CDDAOffset) -> Result<Self, CuesheetError> {
match last {
Some(track) if *track.index_points.last() >= offset => Err(CuesheetError::ShortLeadOut),
_ => Ok(LeadOutCDDA {
offset,
number: LeadOut,
isrc: ISRC::None,
non_audio: false,
pre_emphasis: false,
index_points: (),
}),
}
}
}
pub type LeadOutNonCDDA = Track<u64, LeadOut, ()>;
impl FromBitStream for LeadOutNonCDDA {
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
let offset = r.read_to()?;
let number = r.read_to::<u8>().map_err(Error::Io).and_then(|n| {
NonZero::new(n)
.filter(|n| *n == LeadOut::NON_CDDA)
.map(|_| LeadOut)
.ok_or(CuesheetError::TracksOutOfSequence.into())
})?;
let isrc = r.parse()?;
let non_audio = r.read_bit()?;
let pre_emphasis = r.read_bit()?;
r.skip(6 + 13 * 8)?;
match r.read_to::<u8>()? {
0 => Ok(Self {
offset,
number,
isrc,
non_audio,
pre_emphasis,
index_points: (),
}),
_ => Err(CuesheetError::IndexPointsInLeadout.into()),
}
}
}
impl ToBitStream for LeadOutNonCDDA {
type Error = Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
w.write_from(self.offset)?;
w.write_from::<u8>(LeadOut::NON_CDDA.get())?;
w.build(&self.isrc)?;
w.write_bit(self.non_audio)?;
w.write_bit(self.pre_emphasis)?;
w.pad(6 + 13 * 8)?;
w.write_from::<u8>(0)?;
Ok(())
}
}
impl LeadOutNonCDDA {
pub fn new(last: Option<&TrackNonCDDA>, offset: u64) -> Result<Self, CuesheetError> {
match last {
Some(track) if *track.index_points.last() >= offset => Err(CuesheetError::ShortLeadOut),
_ => Ok(LeadOutNonCDDA {
offset,
number: LeadOut,
isrc: ISRC::None,
non_audio: false,
pre_emphasis: false,
index_points: (),
}),
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct Index<O> {
pub offset: O,
pub number: u8,
}
impl<O: Adjacent> Adjacent for Index<O> {
fn valid_first(&self) -> bool {
self.offset.valid_first() && matches!(self.number, 0 | 1)
}
fn is_next(&self, previous: &Self) -> bool {
self.offset.is_next(&previous.offset) && self.number == previous.number + 1
}
}
impl FromBitStream for Index<CDDAOffset> {
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
let offset = r.parse()?;
let number = r.read_to()?;
r.skip(3 * 8)?;
Ok(Self { offset, number })
}
}
impl FromBitStream for Index<u64> {
type Error = Error;
fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
let offset = r.read_to()?;
let number = r.read_to()?;
r.skip(3 * 8)?;
Ok(Self { offset, number })
}
}
impl ToBitStream for Index<CDDAOffset> {
type Error = std::io::Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
w.build(&self.offset)?;
w.write_from(self.number)?;
w.pad(3 * 8)
}
}
impl ToBitStream for Index<u64> {
type Error = std::io::Error;
fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
w.write_from(self.offset)?;
w.write_from(self.number)?;
w.pad(3 * 8)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IndexVec<const MAX: usize, O: Adjacent> {
index_00: Option<Index<O>>,
index_01: Index<O>,
remainder: Box<[Index<O>]>,
}
impl<const MAX: usize, O: Adjacent> IndexVec<MAX, O> {
#[allow(clippy::len_without_is_empty)]
pub fn len(&self) -> usize {
usize::from(self.index_00.is_some()) + 1 + self.remainder.len()
}
pub fn iter(&self) -> impl Iterator<Item = &Index<O>> {
self.index_00
.iter()
.chain(std::iter::once(&self.index_01))
.chain(&self.remainder)
}
pub fn pre_gap(&self) -> Option<&O> {
match &self.index_00 {
Some(Index { offset, .. }) => Some(offset),
None => None,
}
}
pub fn start(&self) -> &O {
&self.index_01.offset
}
pub fn last(&self) -> &O {
match self.remainder.last() {
Some(Index { offset, .. }) => offset,
None => self.start(),
}
}
}
impl<const MAX: usize, O: Adjacent> TryFrom<Contiguous<MAX, Index<O>>> for IndexVec<MAX, O> {
type Error = CuesheetError;
fn try_from(items: Contiguous<MAX, Index<O>>) -> Result<Self, CuesheetError> {
use std::collections::VecDeque;
let mut items: VecDeque<Index<O>> = items.into();
match items.pop_front().ok_or(CuesheetError::NoIndexPoints)? {
index_00 @ Index { number: 0, .. } => Ok(Self {
index_00: Some(index_00),
index_01: items
.pop_front()
.filter(|i| i.number == 1)
.ok_or(CuesheetError::IndexPointsOutOfSequence)?,
remainder: Vec::from(items).into_boxed_slice(),
}),
index_01 @ Index { number: 1, .. } => Ok(Self {
index_00: None,
index_01,
remainder: Vec::from(items).into_boxed_slice(),
}),
Index { .. } => Err(CuesheetError::IndexPointsOutOfSequence),
}
}
}