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
160 .is_multiple_of(Self::SAMPLES_PER_SECTOR)
161 .then_some(Self { offset })
162 .ok_or(offset)
163 }
164}
165
166impl std::ops::Add for CDDAOffset {
167 type Output = Self;
168
169 fn add(self, rhs: CDDAOffset) -> Self {
170 Self {
174 offset: self.offset + rhs.offset,
175 }
176 }
177}
178
179impl FromBitStream for CDDAOffset {
180 type Error = Error;
181
182 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
183 Ok(Self {
184 offset: r.read_to().map_err(Error::Io).and_then(|o| {
185 ((o % Self::SAMPLES_PER_SECTOR) == 0)
186 .then_some(o)
187 .ok_or(CuesheetError::InvalidCDDAOffset.into())
188 })?,
189 })
190 }
191}
192
193impl ToBitStream for CDDAOffset {
194 type Error = std::io::Error;
195
196 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
197 w.write_from(self.offset)
200 }
201}
202
203impl Adjacent for CDDAOffset {
204 fn valid_first(&self) -> bool {
205 self.offset == 0
206 }
207
208 fn is_next(&self, previous: &Self) -> bool {
209 self.offset > previous.offset
210 }
211}
212
213#[derive(Debug, Copy, Clone, Eq, PartialEq)]
215pub struct LeadOut;
216
217impl LeadOut {
218 pub const CDDA: NonZero<u8> = NonZero::new(170).unwrap();
220
221 pub const NON_CDDA: NonZero<u8> = NonZero::new(255).unwrap();
223}
224
225#[derive(Debug, Clone, Eq, PartialEq)]
245pub struct ISRCString(String);
246
247impl std::fmt::Display for ISRCString {
248 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
249 self.0.fmt(f)
250 }
251}
252
253impl AsRef<str> for ISRCString {
254 fn as_ref(&self) -> &str {
255 self.0.as_str()
256 }
257}
258
259impl FromStr for ISRCString {
260 type Err = CuesheetError;
261
262 fn from_str(s: &str) -> Result<Self, Self::Err> {
263 use std::borrow::Cow;
264
265 fn filter_split(s: &str, amt: usize, f: impl Fn(char) -> bool) -> Option<&str> {
266 s.split_at_checked(amt)
267 .and_then(|(prefix, rest)| prefix.chars().all(f).then_some(rest))
268 }
269
270 let isrc: Cow<'_, str> = if s.contains('-') {
272 s.chars().filter(|c| *c != '-').collect::<String>().into()
273 } else {
274 s.into()
275 };
276
277 filter_split(&isrc, 2, |c| c.is_ascii_alphabetic())
278 .and_then(|s| filter_split(s, 3, |c| c.is_ascii_alphanumeric()))
279 .and_then(|s| filter_split(s, 2, |c| c.is_ascii_digit()))
280 .and_then(|s| s.chars().all(|c| c.is_ascii_digit()).then_some(()))
281 .map(|()| ISRCString(isrc.into_owned()))
282 .ok_or(CuesheetError::InvalidISRC)
283 }
284}
285
286#[derive(Default, Debug, Clone, Eq, PartialEq)]
288pub enum ISRC {
289 #[default]
291 None,
292 String(ISRCString),
294}
295
296impl std::fmt::Display for ISRC {
297 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
298 match self {
299 Self::String(s) => s.fmt(f),
300 Self::None => "".fmt(f),
301 }
302 }
303}
304
305impl FromBitStream for ISRC {
306 type Error = Error;
307
308 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Error> {
309 let isrc = r.read_to::<[u8; 12]>()?;
310 if isrc.iter().all(|b| *b == 0) {
311 Ok(ISRC::None)
312 } else {
313 let s = str::from_utf8(&isrc).map_err(|_| CuesheetError::InvalidISRC)?;
314
315 Ok(ISRC::String(s.parse()?))
316 }
317 }
318}
319
320impl ToBitStream for ISRC {
321 type Error = std::io::Error;
322
323 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), std::io::Error> {
324 w.write_from(match self {
325 Self::String(isrc) => {
326 let mut o = [0; 12];
327 o.iter_mut()
328 .zip(isrc.as_ref().as_bytes())
329 .for_each(|(o, i)| *o = *i);
330 o
331 }
332 Self::None => [0; 12],
333 })
334 }
335}
336
337impl AsRef<str> for ISRC {
338 fn as_ref(&self) -> &str {
339 match self {
340 Self::String(s) => s.as_ref(),
341 Self::None => "",
342 }
343 }
344}
345
346impl FromStr for ISRC {
347 type Err = CuesheetError;
348
349 fn from_str(s: &str) -> Result<Self, Self::Err> {
350 ISRCString::from_str(s).map(ISRC::String)
351 }
352}
353
354#[derive(Debug, Clone, Eq, PartialEq)]
368pub struct Track<O, N, P> {
369 pub offset: O,
381
382 pub number: N,
389
390 pub isrc: ISRC,
392
393 pub non_audio: bool,
395
396 pub pre_emphasis: bool,
398
399 pub index_points: P,
408}
409
410impl<const MAX: usize, O: Adjacent, N: Adjacent> Adjacent for Track<O, N, IndexVec<MAX, O>> {
411 fn valid_first(&self) -> bool {
412 self.offset.valid_first() && self.number.valid_first()
413 }
414
415 fn is_next(&self, previous: &Self) -> bool {
416 self.number.is_next(&previous.number) && self.offset.is_next(previous.index_points.last())
417 }
418}
419
420pub type TrackGeneric = Track<u64, Option<u8>, Vec<Index<u64>>>;
424
425pub type TrackCDDA = Track<CDDAOffset, NonZero<u8>, IndexVec<100, CDDAOffset>>;
427
428impl FromBitStream for TrackCDDA {
429 type Error = Error;
430
431 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
432 let offset = r.parse()?;
433 let number = r
434 .read_to()
435 .map_err(Error::Io)
436 .and_then(|s| NonZero::new(s).ok_or(Error::from(CuesheetError::InvalidIndexPoint)))?;
437 let isrc = r.parse()?;
438 let non_audio = r.read_bit()?;
439 let pre_emphasis = r.read_bit()?;
440 r.skip(6 + 13 * 8)?;
441 let index_point_count = r.read_to::<u8>()?;
442
443 Ok(Self {
444 offset,
445 number,
446 isrc,
447 non_audio,
448 pre_emphasis,
449 index_points: IndexVec::try_from(
453 Contiguous::try_collect((0..index_point_count).map(|_| r.parse()))
454 .map_err(|_| Error::from(CuesheetError::IndexPointsOutOfSequence))??,
455 )?,
456 })
457 }
458}
459
460impl ToBitStream for TrackCDDA {
461 type Error = Error;
462
463 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
464 w.build(&self.offset)?;
465 w.write_from(self.number.get())?;
466 w.build(&self.isrc)?;
467 w.write_bit(self.non_audio)?;
468 w.write_bit(self.pre_emphasis)?;
469 w.pad(6 + 13 * 8)?;
470 w.write_from::<u8>(self.index_points.len().try_into().unwrap())?;
471 for point in self.index_points.iter() {
472 w.build(point)?;
473 }
474 Ok(())
475 }
476}
477
478pub type TrackNonCDDA = Track<u64, NonZero<u8>, IndexVec<256, u64>>;
480
481impl FromBitStream for TrackNonCDDA {
482 type Error = Error;
483
484 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
485 let offset = r.read_to()?;
486 let number = r
487 .read_to()
488 .map_err(Error::Io)
489 .and_then(|s| NonZero::new(s).ok_or(Error::from(CuesheetError::InvalidIndexPoint)))?;
490 let isrc = r.parse()?;
491 let non_audio = r.read_bit()?;
492 let pre_emphasis = r.read_bit()?;
493 r.skip(6 + 13 * 8)?;
494 let index_point_count = r.read_to::<u8>()?;
495
496 Ok(Self {
497 offset,
498 number,
499 isrc,
500 non_audio,
501 pre_emphasis,
502 index_points: IndexVec::try_from(
506 Contiguous::try_collect((0..index_point_count).map(|_| r.parse()))
507 .map_err(|_| Error::from(CuesheetError::IndexPointsOutOfSequence))??,
508 )?,
509 })
510 }
511}
512
513impl ToBitStream for TrackNonCDDA {
514 type Error = Error;
515
516 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
517 w.write_from(self.offset)?;
518 w.write_from(self.number.get())?;
519 w.build(&self.isrc)?;
520 w.write_bit(self.non_audio)?;
521 w.write_bit(self.pre_emphasis)?;
522 w.pad(6 + 13 * 8)?;
523 w.write_from::<u8>(self.index_points.len().try_into().unwrap())?;
524 for point in self.index_points.iter() {
525 w.build(point)?;
526 }
527 Ok(())
528 }
529}
530
531pub type LeadOutCDDA = Track<CDDAOffset, LeadOut, ()>;
533
534impl FromBitStream for LeadOutCDDA {
535 type Error = Error;
536
537 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
538 let offset = r.parse()?;
539 let number = r.read_to::<u8>().map_err(Error::Io).and_then(|n| {
540 NonZero::new(n)
541 .filter(|n| *n == LeadOut::CDDA)
542 .map(|_| LeadOut)
543 .ok_or(CuesheetError::TracksOutOfSequence.into())
544 })?;
545 let isrc = r.parse()?;
546 let non_audio = r.read_bit()?;
547 let pre_emphasis = r.read_bit()?;
548 r.skip(6 + 13 * 8)?;
549 match r.read_to::<u8>()? {
550 0 => Ok(Self {
551 offset,
552 number,
553 isrc,
554 non_audio,
555 pre_emphasis,
556 index_points: (),
557 }),
558 _ => Err(CuesheetError::IndexPointsInLeadout.into()),
562 }
563 }
564}
565
566impl ToBitStream for LeadOutCDDA {
567 type Error = Error;
568
569 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
570 w.build(&self.offset)?;
571 w.write_from(LeadOut::CDDA.get())?;
572 w.build(&self.isrc)?;
573 w.write_bit(self.non_audio)?;
574 w.write_bit(self.pre_emphasis)?;
575 w.pad(6 + 13 * 8)?;
576 w.write_from::<u8>(0)?;
577 Ok(())
578 }
579}
580
581impl LeadOutCDDA {
582 pub fn new(last: Option<&TrackCDDA>, offset: CDDAOffset) -> Result<Self, CuesheetError> {
586 match last {
587 Some(track) if *track.index_points.last() >= offset => Err(CuesheetError::ShortLeadOut),
588 _ => Ok(LeadOutCDDA {
589 offset,
590 number: LeadOut,
591 isrc: ISRC::None,
592 non_audio: false,
593 pre_emphasis: false,
594 index_points: (),
595 }),
596 }
597 }
598}
599
600pub type LeadOutNonCDDA = Track<u64, LeadOut, ()>;
602
603impl FromBitStream for LeadOutNonCDDA {
604 type Error = Error;
605
606 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
607 let offset = r.read_to()?;
608 let number = r.read_to::<u8>().map_err(Error::Io).and_then(|n| {
609 NonZero::new(n)
610 .filter(|n| *n == LeadOut::NON_CDDA)
611 .map(|_| LeadOut)
612 .ok_or(CuesheetError::TracksOutOfSequence.into())
613 })?;
614 let isrc = r.parse()?;
615 let non_audio = r.read_bit()?;
616 let pre_emphasis = r.read_bit()?;
617 r.skip(6 + 13 * 8)?;
618 match r.read_to::<u8>()? {
619 0 => Ok(Self {
620 offset,
621 number,
622 isrc,
623 non_audio,
624 pre_emphasis,
625 index_points: (),
626 }),
627 _ => Err(CuesheetError::IndexPointsInLeadout.into()),
631 }
632 }
633}
634
635impl ToBitStream for LeadOutNonCDDA {
636 type Error = Error;
637
638 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
639 w.write_from(self.offset)?;
640 w.write_from::<u8>(LeadOut::NON_CDDA.get())?;
641 w.build(&self.isrc)?;
642 w.write_bit(self.non_audio)?;
643 w.write_bit(self.pre_emphasis)?;
644 w.pad(6 + 13 * 8)?;
645 w.write_from::<u8>(0)?;
646 Ok(())
647 }
648}
649
650impl LeadOutNonCDDA {
651 pub fn new(last: Option<&TrackNonCDDA>, offset: u64) -> Result<Self, CuesheetError> {
653 match last {
654 Some(track) if *track.index_points.last() >= offset => Err(CuesheetError::ShortLeadOut),
655 _ => Ok(LeadOutNonCDDA {
656 offset,
657 number: LeadOut,
658 isrc: ISRC::None,
659 non_audio: false,
660 pre_emphasis: false,
661 index_points: (),
662 }),
663 }
664 }
665}
666
667#[derive(Copy, Clone, Eq, PartialEq, Debug)]
676pub struct Index<O> {
677 pub offset: O,
679
680 pub number: u8,
682}
683
684impl<O: Adjacent> Adjacent for Index<O> {
685 fn valid_first(&self) -> bool {
686 self.offset.valid_first() && matches!(self.number, 0 | 1)
687 }
688
689 fn is_next(&self, previous: &Self) -> bool {
690 self.offset.is_next(&previous.offset) && self.number == previous.number + 1
691 }
692}
693
694impl FromBitStream for Index<CDDAOffset> {
695 type Error = Error;
696
697 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
698 let offset = r.parse()?;
699 let number = r.read_to()?;
700 r.skip(3 * 8)?;
701 Ok(Self { offset, number })
702 }
703}
704
705impl FromBitStream for Index<u64> {
706 type Error = Error;
707
708 fn from_reader<R: BitRead + ?Sized>(r: &mut R) -> Result<Self, Self::Error> {
709 let offset = r.read_to()?;
710 let number = r.read_to()?;
711 r.skip(3 * 8)?;
712 Ok(Self { offset, number })
713 }
714}
715
716impl ToBitStream for Index<CDDAOffset> {
717 type Error = std::io::Error;
718
719 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
720 w.build(&self.offset)?;
721 w.write_from(self.number)?;
722 w.pad(3 * 8)
723 }
724}
725
726impl ToBitStream for Index<u64> {
727 type Error = std::io::Error;
728
729 fn to_writer<W: BitWrite + ?Sized>(&self, w: &mut W) -> Result<(), Self::Error> {
730 w.write_from(self.offset)?;
731 w.write_from(self.number)?;
732 w.pad(3 * 8)
733 }
734}
735
736#[derive(Clone, Debug, Eq, PartialEq)]
748pub struct IndexVec<const MAX: usize, O: Adjacent> {
749 index_00: Option<Index<O>>,
751 index_01: Index<O>,
753 remainder: Box<[Index<O>]>,
755}
756
757impl<const MAX: usize, O: Adjacent> IndexVec<MAX, O> {
758 #[allow(clippy::len_without_is_empty)]
763 pub fn len(&self) -> usize {
764 usize::from(self.index_00.is_some()) + 1 + self.remainder.len()
769 }
770
771 pub fn iter(&self) -> impl Iterator<Item = &Index<O>> {
773 self.index_00
774 .iter()
775 .chain(std::iter::once(&self.index_01))
776 .chain(&self.remainder)
777 }
778
779 pub fn pre_gap(&self) -> Option<&O> {
783 match &self.index_00 {
784 Some(Index { offset, .. }) => Some(offset),
785 None => None,
786 }
787 }
788
789 pub fn start(&self) -> &O {
793 &self.index_01.offset
794 }
795
796 pub fn last(&self) -> &O {
801 match self.remainder.last() {
802 Some(Index { offset, .. }) => offset,
803 None => self.start(),
804 }
805 }
806}
807
808impl<const MAX: usize, O: Adjacent> TryFrom<Contiguous<MAX, Index<O>>> for IndexVec<MAX, O> {
809 type Error = CuesheetError;
810
811 fn try_from(items: Contiguous<MAX, Index<O>>) -> Result<Self, CuesheetError> {
812 use std::collections::VecDeque;
813
814 let mut items: VecDeque<Index<O>> = items.into();
815
816 match items.pop_front().ok_or(CuesheetError::NoIndexPoints)? {
817 index_00 @ Index { number: 0, .. } => Ok(Self {
818 index_00: Some(index_00),
819 index_01: items
820 .pop_front()
821 .filter(|i| i.number == 1)
822 .ok_or(CuesheetError::IndexPointsOutOfSequence)?,
823 remainder: Vec::from(items).into_boxed_slice(),
824 }),
825 index_01 @ Index { number: 1, .. } => Ok(Self {
826 index_00: None,
827 index_01,
828 remainder: Vec::from(items).into_boxed_slice(),
829 }),
830 Index { .. } => Err(CuesheetError::IndexPointsOutOfSequence),
831 }
832 }
833}