Code for representing CUE files / TOC info
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 8pub enum TrackType { 9 Data, 10 Audio, 11} 12 13#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)] 14pub enum TrackMode { 15 Mode1, 16 Mode1DataOnly, 17 Mode2, 18 Audio, 19} 20 21impl TrackMode { 22 #[must_use] 23 pub fn to_type(self) -> TrackType { 24 match self { 25 Self::Mode1 | Self::Mode1DataOnly | Self::Mode2 => TrackType::Data, 26 Self::Audio => TrackType::Audio, 27 } 28 } 29 30 #[must_use] 31 pub fn bytes_per_sector(self) -> u64 { 32 match self { 33 Self::Mode1DataOnly => 2048, 34 Self::Mode1 | Self::Mode2 | Self::Audio => 2352, 35 } 36 } 37} 38 39impl FromStr for TrackMode { 40 type Err = String; 41 42 fn from_str(s: &str) -> Result<Self, Self::Err> { 43 match s { 44 "MODE1/2352" => Ok(Self::Mode1), 45 "MODE1/2048" => Ok(Self::Mode1DataOnly), 46 "MODE2/2352" => Ok(Self::Mode2), 47 "AUDIO" => Ok(Self::Audio), 48 _ => Err(format!("unsupported CD track type: {s}")), 49 } 50 } 51} 52 53#[derive(Debug, Clone, Encode, Decode)] 54pub struct Track { 55 pub number: u8, 56 pub mode: TrackMode, 57 pub track_type: TrackType, 58 pub start_time: CdTime, 59 pub end_time: CdTime, 60 pub pregap_len: CdTime, 61 pub pause_len: CdTime, 62 pub postgap_len: CdTime, 63} 64 65impl Track { 66 #[must_use] 67 pub fn effective_start_time(&self) -> CdTime { 68 self.start_time + self.pregap_len + self.pause_len 69 } 70} 71 72#[derive(Debug, Clone, Encode, Decode)] 73pub struct CueSheet { 74 tracks: Vec<Track>, 75 track_start_times: Vec<CdTime>, 76} 77 78impl CueSheet {
Create a new CueSheet from the given track list.
Panics
This function will panic if the track list is empty.
Find the track containing the specified time. Returns None if the time is past the end of
the disc.
106 #[must_use] 107 pub fn find_track_by_time(&self, time: CdTime) -> Option<&Track> { 108 match self.track_start_times.binary_search(&time) { 109 Ok(i) => Some(&self.tracks[i]), 110 Err(i) => { 111 if i < self.tracks.len() { 112 Some(&self.tracks[i - 1]) 113 } else { 114 let last_track = self.last_track(); 115 (time <= last_track.end_time).then_some(last_track) 116 } 117 } 118 } 119 } 120}
122#[must_use] 123pub(crate) fn tracks_are_continuous(tracks: &[Track]) -> bool { 124 if tracks[0].start_time != CdTime::ZERO { 125 return false; 126 } 127 128 for window in tracks.windows(2) { 129 let [track, next] = window else { unreachable!("windows(2)") }; 130 if next.start_time != track.end_time { 131 return false; 132 } 133 } 134 135 true 136} 137 138pub(crate) fn finalize_track_list(tracks: &mut [Track]) { 139 tracks.sort_by_key(|track| track.number); 140 141 // The final track always has a 2-second postgap 142 let last_track = tracks.last_mut().unwrap(); 143 if last_track.postgap_len == CdTime::ZERO { 144 last_track.postgap_len = CdTime::new(0, 2, 0); 145 last_track.end_time += CdTime::new(0, 2, 0); 146 } 147}