1use crate::Error;
10use crate::metadata::CuesheetError;
11use crate::metadata::contiguous::{Adjacent, Contiguous};
12use bitstream_io::{BitRead, BitWrite, FromBitStream, ToBitStream};
13use std::num::NonZero;
14use std::str::FromStr;
15
16#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
18pub enum Digit {
19 Digit0 = 48,
21 Digit1 = 49,
23 Digit2 = 50,
25 Digit3 = 51,
27 Digit4 = 52,
29 Digit5 = 53,
31 Digit6 = 54,
33 Digit7 = 55,
35 Digit8 = 56,
37 Digit9 = 57,
39}
40
41impl TryFrom<u8> for Digit {
42 type Error = u8;
43
44 fn try_from(u: u8) -> Result<Digit, u8> {
45 match u {
46 48 => Ok(Self::Digit0),
47 49 => Ok(Self::Digit1),
48 50 => Ok(Self::Digit2),
49 51 => Ok(Self::Digit3),
50 52 => Ok(Self::Digit4),
51 53 => Ok(Self::Digit5),
52 54 => Ok(Self::Digit6),
53 55 => Ok(Self::Digit7),
54 56 => Ok(Self::Digit8),
55 57 => Ok(Self::Digit9),
56 u => Err(u),
57 }
58 }
59}
60
61impl TryFrom<char> for Digit {
62 type Error = CuesheetError;
63
64 fn try_from(c: char) -> Result<Digit, CuesheetError> {
65 match c {
66 '0' => Ok(Self::Digit0),
67 '1' => Ok(Self::Digit1),
68 '2' => Ok(Self::Digit2),
69 '3' => Ok(Self::Digit3),
70 '4' => Ok(Self::Digit4),
71 '5' => Ok(Self::Digit5),
72 '6' => Ok(Self::Digit6),
73 '7' => Ok(Self::Digit7),
74 '8' => Ok(Self::Digit8),
75 '9' => Ok(Self::Digit9),
76 _ => Err(CuesheetError::InvalidCatalogNumber),
77 }
78 }
79}
80
81impl From<Digit> for u8 {
82 fn from(d: Digit) -> u8 {
83 d as u8
84 }
85}
86
87impl std::fmt::Display for Digit {
88 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
89 match self {
90 Self::Digit0 => '0'.fmt(f),
91 Self::Digit1 => '1'.fmt(f),
92 Self::Digit2 => '2'.fmt(f),
93 Self::Digit3 => '3'.fmt(f),
94 Self::Digit4 => '4'.fmt(f),
95 Self::Digit5 => '5'.fmt(f),
96 Self::Digit6 => '6'.fmt(f),
97 Self::Digit7 => '7'.fmt(f),
98 Self::Digit8 => '8'.fmt(f),
99 Self::Digit9 => '9'.fmt(f),
100 }
101 }
102}
103
104#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)]
108pub struct CDDAOffset {
109 offset: u64,
110}
111
112impl CDDAOffset {
113 const SAMPLES_PER_SECTOR: u64 = 44100 / 75;
114}
115
116impl std::ops::Sub for CDDAOffset {
117 type Output = Self;
118
119 fn sub(self, rhs: Self) -> Self {
120 Self {
121 offset: self.offset - rhs.offset,
122 }
123 }
124}
125
126impl FromStr for CDDAOffset {
127 type Err = ();
128
129 fn from_str(s: &str) -> Result<Self, ()> {
130 let (mm, rest) = s.split_once(':').ok_or(())?;
131 let (ss, ff) = rest.split_once(':').ok_or(())?;
132
133 let ff: u64 = ff.parse().ok().filter(|ff| *ff < 75).ok_or(())?;
134 let ss: u64 = ss.parse().ok().filter(|ss| *ss < 60).ok_or(())?;
135 let mm: u64 = mm.parse().map_err(|_| ())?;
136
137 Ok(Self {
138 offset: (ff + ss * 75 + mm * 75 * 60) * 588,
139 })
140 }
141}
142
143impl std::fmt::Display for CDDAOffset {
144 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
145 self.offset.fmt(f)
146 }
147}
148
149impl From<CDDAOffset> for u64 {
150 fn from(o: CDDAOffset) -> Self {
151 o.offset
152 }
153}
154
155impl TryFrom<u64> for CDDAOffset {
156 type Error = u64;
157
158 fn try_from(offset: u64) -> Result<Self, Self::Error> {
159 ((offset % Self::SAMPLES_PER_SECTOR) == 0)
160 .then_some(Self { offset })
161 .ok_or(offset)
162 }
163}
164
165impl std::ops::Add for CDDAOffset {
166 type Output = Self;
167
168 fn add(self, rhs: CDDAOffset) -> Self {
169 Self {
173 offset: self.offset + rhs.offset,
174 }
175 }
176}
177
178impl FromBitStream for CDDAOffset {
179 type Error = Error;
180
181 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
182 Ok(Self {
183 offset: r.read_to().map_err(Error::Io).and_then(|o| {
184 ((o % Self::SAMPLES_PER_SECTOR) == 0)
185 .then_some(o)
186 .ok_or(CuesheetError::InvalidCDDAOffset.into())
187 })?,
188 })
189 }
190}
191
192impl ToBitStream for CDDAOffset {
193 type Error = std::io::Error;
194
195 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
196 w.write_from(self.offset)
199 }
200}
201
202impl Adjacent for CDDAOffset {
203 fn valid_first(&self) -> bool {
204 self.offset == 0
205 }
206
207 fn is_next(&self, previous: &Self) -> bool {
208 self.offset > previous.offset
209 }
210}
211
212#[derive(Debug, Copy, Clone, Eq, PartialEq)]
214pub struct LeadOut;
215
216impl LeadOut {
217 pub const CDDA: NonZero<u8> = NonZero::new(170).unwrap();
219
220 pub const NON_CDDA: NonZero<u8> = NonZero::new(255).unwrap();
222}
223
224#[derive(Debug, Clone, Eq, PartialEq)]
244pub struct ISRCString(String);
245
246impl std::fmt::Display for ISRCString {
247 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
248 self.0.fmt(f)
249 }
250}
251
252impl AsRef<str> for ISRCString {
253 fn as_ref(&self) -> &str {
254 self.0.as_str()
255 }
256}
257
258impl FromStr for ISRCString {
259 type Err = CuesheetError;
260
261 fn from_str(s: &str) -> Result<Self, Self::Err> {
262 use std::borrow::Cow;
263
264 fn filter_split(s: &str, amt: usize, f: impl Fn(char) -> bool) -> Option<&str> {
265 s.split_at_checked(amt)
266 .and_then(|(prefix, rest)| prefix.chars().all(f).then_some(rest))
267 }
268
269 let isrc: Cow<'_, str> = if s.contains('-') {
271 s.chars().filter(|c| *c != '-').collect::<String>().into()
272 } else {
273 s.into()
274 };
275
276 filter_split(&isrc, 2, |c| c.is_ascii_alphabetic())
277 .and_then(|s| filter_split(s, 3, |c| c.is_ascii_alphanumeric()))
278 .and_then(|s| filter_split(s, 2, |c| c.is_ascii_digit()))
279 .and_then(|s| s.chars().all(|c| c.is_ascii_digit()).then_some(()))
280 .map(|()| ISRCString(isrc.into_owned()))
281 .ok_or(CuesheetError::InvalidISRC)
282 }
283}
284
285#[derive(Default, Debug, Clone, Eq, PartialEq)]
287pub enum ISRC {
288 #[default]
290 None,
291 String(ISRCString),
293}
294
295impl std::fmt::Display for ISRC {
296 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
297 match self {
298 Self::String(s) => s.fmt(f),
299 Self::None => "".fmt(f),
300 }
301 }
302}
303
304impl FromBitStream for ISRC {
305 type Error = Error;
306
307 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Error> {
308 let isrc = r.read_to::<[u8; 12]>()?;
309 if isrc.iter().all(|b| *b == 0) {
310 Ok(ISRC::None)
311 } else {
312 let s = str::from_utf8(&isrc).map_err(|_| CuesheetError::InvalidISRC)?;
313
314 Ok(ISRC::String(s.parse()?))
315 }
316 }
317}
318
319impl ToBitStream for ISRC {
320 type Error = std::io::Error;
321
322 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), std::io::Error> {
323 w.write_from(match self {
324 Self::String(isrc) => {
325 let mut o = [0; 12];
326 o.iter_mut()
327 .zip(isrc.as_ref().as_bytes())
328 .for_each(|(o, i)| *o = *i);
329 o
330 }
331 Self::None => [0; 12],
332 })
333 }
334}
335
336impl AsRef<str> for ISRC {
337 fn as_ref(&self) -> &str {
338 match self {
339 Self::String(s) => s.as_ref(),
340 Self::None => "",
341 }
342 }
343}
344
345impl FromStr for ISRC {
346 type Err = CuesheetError;
347
348 fn from_str(s: &str) -> Result<Self, Self::Err> {
349 ISRCString::from_str(s).map(ISRC::String)
350 }
351}
352
353#[derive(Debug, Clone, Eq, PartialEq)]
367pub struct Track<O, N, P> {
368 pub offset: O,
380
381 pub number: N,
388
389 pub isrc: ISRC,
391
392 pub non_audio: bool,
394
395 pub pre_emphasis: bool,
397
398 pub index_points: P,
407}
408
409impl<const MAX: usize, O: Adjacent, N: Adjacent> Adjacent for Track<O, N, IndexVec<MAX, O>> {
410 fn valid_first(&self) -> bool {
411 self.offset.valid_first() && self.number.valid_first()
412 }
413
414 fn is_next(&self, previous: &Self) -> bool {
415 self.number.is_next(&previous.number) && self.offset.is_next(previous.index_points.last())
416 }
417}
418
419pub type TrackGeneric = Track<u64, Option<u8>, Vec<Index<u64>>>;
423
424pub type TrackCDDA = Track<CDDAOffset, NonZero<u8>, IndexVec<100, CDDAOffset>>;
426
427impl FromBitStream for TrackCDDA {
428 type Error = Error;
429
430 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
431 let offset = r.parse()?;
432 let number = r
433 .read_to()
434 .map_err(Error::Io)
435 .and_then(|s| NonZero::new(s).ok_or(Error::from(CuesheetError::InvalidIndexPoint)))?;
436 let isrc = r.parse()?;
437 let non_audio = r.read_bit()?;
438 let pre_emphasis = r.read_bit()?;
439 r.skip(6 + 13 * 8)?;
440 let index_point_count = r.read_to::<u8>()?;
441
442 Ok(Self {
443 offset,
444 number,
445 isrc,
446 non_audio,
447 pre_emphasis,
448 index_points: IndexVec::try_from(
452 Contiguous::try_collect((0..index_point_count).map(|_| r.parse()))
453 .map_err(|_| Error::from(CuesheetError::IndexPointsOutOfSequence))??,
454 )?,
455 })
456 }
457}
458
459impl ToBitStream for TrackCDDA {
460 type Error = Error;
461
462 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
463 w.build(&self.offset)?;
464 w.write_from(self.number.get())?;
465 w.build(&self.isrc)?;
466 w.write_bit(self.non_audio)?;
467 w.write_bit(self.pre_emphasis)?;
468 w.pad(6 + 13 * 8)?;
469 w.write_from::<u8>(self.index_points.len().try_into().unwrap())?;
470 for point in self.index_points.iter() {
471 w.build(point)?;
472 }
473 Ok(())
474 }
475}
476
477pub type TrackNonCDDA = Track<u64, NonZero<u8>, IndexVec<256, u64>>;
479
480impl FromBitStream for TrackNonCDDA {
481 type Error = Error;
482
483 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
484 let offset = r.read_to()?;
485 let number = r
486 .read_to()
487 .map_err(Error::Io)
488 .and_then(|s| NonZero::new(s).ok_or(Error::from(CuesheetError::InvalidIndexPoint)))?;
489 let isrc = r.parse()?;
490 let non_audio = r.read_bit()?;
491 let pre_emphasis = r.read_bit()?;
492 r.skip(6 + 13 * 8)?;
493 let index_point_count = r.read_to::<u8>()?;
494
495 Ok(Self {
496 offset,
497 number,
498 isrc,
499 non_audio,
500 pre_emphasis,
501 index_points: IndexVec::try_from(
505 Contiguous::try_collect((0..index_point_count).map(|_| r.parse()))
506 .map_err(|_| Error::from(CuesheetError::IndexPointsOutOfSequence))??,
507 )?,
508 })
509 }
510}
511
512impl ToBitStream for TrackNonCDDA {
513 type Error = Error;
514
515 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
516 w.write_from(self.offset)?;
517 w.write_from(self.number.get())?;
518 w.build(&self.isrc)?;
519 w.write_bit(self.non_audio)?;
520 w.write_bit(self.pre_emphasis)?;
521 w.pad(6 + 13 * 8)?;
522 w.write_from::<u8>(self.index_points.len().try_into().unwrap())?;
523 for point in self.index_points.iter() {
524 w.build(point)?;
525 }
526 Ok(())
527 }
528}
529
530pub type LeadOutCDDA = Track<CDDAOffset, LeadOut, ()>;
532
533impl FromBitStream for LeadOutCDDA {
534 type Error = Error;
535
536 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
537 let offset = r.parse()?;
538 let number = r.read_to::<u8>().map_err(Error::Io).and_then(|n| {
539 NonZero::new(n)
540 .filter(|n| *n == LeadOut::CDDA)
541 .map(|_| LeadOut)
542 .ok_or(CuesheetError::TracksOutOfSequence.into())
543 })?;
544 let isrc = r.parse()?;
545 let non_audio = r.read_bit()?;
546 let pre_emphasis = r.read_bit()?;
547 r.skip(6 + 13 * 8)?;
548 match r.read_to::<u8>()? {
549 0 => Ok(Self {
550 offset,
551 number,
552 isrc,
553 non_audio,
554 pre_emphasis,
555 index_points: (),
556 }),
557 _ => Err(CuesheetError::IndexPointsInLeadout.into()),
561 }
562 }
563}
564
565impl ToBitStream for LeadOutCDDA {
566 type Error = Error;
567
568 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
569 w.build(&self.offset)?;
570 w.write_from(LeadOut::CDDA.get())?;
571 w.build(&self.isrc)?;
572 w.write_bit(self.non_audio)?;
573 w.write_bit(self.pre_emphasis)?;
574 w.pad(6 + 13 * 8)?;
575 w.write_from::<u8>(0)?;
576 Ok(())
577 }
578}
579
580impl LeadOutCDDA {
581 pub fn new(last: Option<&TrackCDDA>, offset: CDDAOffset) -> Result<Self, CuesheetError> {
585 match last {
586 Some(track) if *track.index_points.last() >= offset => Err(CuesheetError::ShortLeadOut),
587 _ => Ok(LeadOutCDDA {
588 offset,
589 number: LeadOut,
590 isrc: ISRC::None,
591 non_audio: false,
592 pre_emphasis: false,
593 index_points: (),
594 }),
595 }
596 }
597}
598
599pub type LeadOutNonCDDA = Track<u64, LeadOut, ()>;
601
602impl FromBitStream for LeadOutNonCDDA {
603 type Error = Error;
604
605 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
606 let offset = r.read_to()?;
607 let number = r.read_to::<u8>().map_err(Error::Io).and_then(|n| {
608 NonZero::new(n)
609 .filter(|n| *n == LeadOut::NON_CDDA)
610 .map(|_| LeadOut)
611 .ok_or(CuesheetError::TracksOutOfSequence.into())
612 })?;
613 let isrc = r.parse()?;
614 let non_audio = r.read_bit()?;
615 let pre_emphasis = r.read_bit()?;
616 r.skip(6 + 13 * 8)?;
617 match r.read_to::<u8>()? {
618 0 => Ok(Self {
619 offset,
620 number,
621 isrc,
622 non_audio,
623 pre_emphasis,
624 index_points: (),
625 }),
626 _ => Err(CuesheetError::IndexPointsInLeadout.into()),
630 }
631 }
632}
633
634impl ToBitStream for LeadOutNonCDDA {
635 type Error = Error;
636
637 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
638 w.write_from(self.offset)?;
639 w.write_from::<u8>(LeadOut::NON_CDDA.get())?;
640 w.build(&self.isrc)?;
641 w.write_bit(self.non_audio)?;
642 w.write_bit(self.pre_emphasis)?;
643 w.pad(6 + 13 * 8)?;
644 w.write_from::<u8>(0)?;
645 Ok(())
646 }
647}
648
649impl LeadOutNonCDDA {
650 pub fn new(last: Option<&TrackNonCDDA>, offset: u64) -> Result<Self, CuesheetError> {
652 match last {
653 Some(track) if *track.index_points.last() >= offset => Err(CuesheetError::ShortLeadOut),
654 _ => Ok(LeadOutNonCDDA {
655 offset,
656 number: LeadOut,
657 isrc: ISRC::None,
658 non_audio: false,
659 pre_emphasis: false,
660 index_points: (),
661 }),
662 }
663 }
664}
665
666#[derive(Copy, Clone, Eq, PartialEq, Debug)]
675pub struct Index<O> {
676 pub offset: O,
678
679 pub number: u8,
681}
682
683impl<O: Adjacent> Adjacent for Index<O> {
684 fn valid_first(&self) -> bool {
685 self.offset.valid_first() && matches!(self.number, 0 | 1)
686 }
687
688 fn is_next(&self, previous: &Self) -> bool {
689 self.offset.is_next(&previous.offset) && self.number == previous.number + 1
690 }
691}
692
693impl FromBitStream for Index<CDDAOffset> {
694 type Error = Error;
695
696 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
697 let offset = r.parse()?;
698 let number = r.read_to()?;
699 r.skip(3 * 8)?;
700 Ok(Self { offset, number })
701 }
702}
703
704impl FromBitStream for Index<u64> {
705 type Error = Error;
706
707 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
708 let offset = r.read_to()?;
709 let number = r.read_to()?;
710 r.skip(3 * 8)?;
711 Ok(Self { offset, number })
712 }
713}
714
715impl ToBitStream for Index<CDDAOffset> {
716 type Error = std::io::Error;
717
718 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
719 w.build(&self.offset)?;
720 w.write_from(self.number)?;
721 w.pad(3 * 8)
722 }
723}
724
725impl ToBitStream for Index<u64> {
726 type Error = std::io::Error;
727
728 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
729 w.write_from(self.offset)?;
730 w.write_from(self.number)?;
731 w.pad(3 * 8)
732 }
733}
734
735#[derive(Clone, Debug, Eq, PartialEq)]
747pub struct IndexVec<const MAX: usize, O: Adjacent> {
748 index_00: Option<Index<O>>,
750 index_01: Index<O>,
752 remainder: Box<[Index<O>]>,
754}
755
756impl<const MAX: usize, O: Adjacent> IndexVec<MAX, O> {
757 #[allow(clippy::len_without_is_empty)]
762 pub fn len(&self) -> usize {
763 usize::from(self.index_00.is_some()) + 1 + self.remainder.len()
768 }
769
770 pub fn iter(&self) -> impl Iterator<Item = &Index<O>> {
772 self.index_00
773 .iter()
774 .chain(std::iter::once(&self.index_01))
775 .chain(&self.remainder)
776 }
777
778 pub fn pre_gap(&self) -> Option<&O> {
782 match &self.index_00 {
783 Some(Index { offset, .. }) => Some(offset),
784 None => None,
785 }
786 }
787
788 pub fn start(&self) -> &O {
792 &self.index_01.offset
793 }
794
795 pub fn last(&self) -> &O {
800 match self.remainder.last() {
801 Some(Index { offset, .. }) => offset,
802 None => self.start(),
803 }
804 }
805}
806
807impl<const MAX: usize, O: Adjacent> TryFrom<Contiguous<MAX, Index<O>>> for IndexVec<MAX, O> {
808 type Error = CuesheetError;
809
810 fn try_from(items: Contiguous<MAX, Index<O>>) -> Result<Self, CuesheetError> {
811 use std::collections::VecDeque;
812
813 let mut items: VecDeque<Index<O>> = items.into();
814
815 match items.pop_front().ok_or(CuesheetError::NoIndexPoints)? {
816 index_00 @ Index { number: 0, .. } => Ok(Self {
817 index_00: Some(index_00),
818 index_01: items
819 .pop_front()
820 .filter(|i| i.number == 1)
821 .ok_or(CuesheetError::IndexPointsOutOfSequence)?,
822 remainder: Vec::from(items).into_boxed_slice(),
823 }),
824 index_01 @ Index { number: 1, .. } => Ok(Self {
825 index_00: None,
826 index_01,
827 remainder: Vec::from(items).into_boxed_slice(),
828 }),
829 Index { .. } => Err(CuesheetError::IndexPointsOutOfSequence),
830 }
831 }
832}