anni_flac/blocks/
cue_sheet.rs

1use crate::prelude::*;
2use crate::utils::*;
3use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
4use std::fmt;
5use std::io::{Read, Write};
6
7pub struct BlockCueSheet {
8    /// <128*8> Media catalog number, in ASCII printable characters 0x20-0x7e.
9    /// In general, the media catalog number may be 0 to 128 bytes long; any unused characters should be right-padded with NUL characters.
10    /// For CD-DA, this is a thirteen digit number, followed by 115 NUL bytes.
11    pub catalog: String,
12    /// <64> The number of lead-in samples.
13    /// This field has meaning only for CD-DA cuesheets; for other uses it should be 0.
14    /// For CD-DA, the lead-in is the TRACK 00 area where the table of contents is stored;
15    /// more precisely, it is the number of samples from the first sample of the media to the first sample of the first index point of the first track.
16    /// According to the Red Book, the lead-in must be silence and CD grabbing software does not usually store it;
17    /// additionally, the lead-in must be at least two seconds but may be longer.
18    /// For these reasons the lead-in length is stored here so that the absolute position of the first track can be computed.
19    /// Note that the lead-in stored here is the number of samples up to the first index point of the first track, not necessarily to INDEX 01 of the first track;
20    /// even the first track may have INDEX 00 data.
21    pub leadin_samples: u64,
22    /// <1> 1 if the CUESHEET corresponds to a Compact Disc, else 0.
23    pub is_cd: bool,
24    /// <7+258*8> Reserved. All bits must be set to zero.
25
26    /// <8> The number of tracks.
27    /// Must be at least 1 (because of the requisite lead-out track).
28    /// For CD-DA, this number must be no more than 100 (99 regular tracks and one lead-out track).
29    pub track_number: u8,
30
31    /// One or more tracks.
32    /// A CUESHEET block is required to have a lead-out track; it is always the last track in the CUESHEET.
33    /// For CD-DA, the lead-out track number must be 170 as specified by the Red Book, otherwise is must be 255.
34    pub tracks: Vec<CueSheetTrack>,
35}
36
37impl Decode for BlockCueSheet {
38    fn from_reader<R: Read>(reader: &mut R) -> Result<Self> {
39        let catalog_number = take_string(reader, 128)?;
40        let leadin_samples = reader.read_u64::<BigEndian>()?;
41        let is_cd = reader.read_u8()? > 0;
42        skip(reader, 258)?;
43        let track_number = reader.read_u8()?;
44        let mut tracks = Vec::with_capacity(track_number as usize);
45        for _ in 0..track_number {
46            tracks.push(CueSheetTrack::from_reader(reader)?);
47        }
48        Ok(BlockCueSheet {
49            catalog: catalog_number,
50            leadin_samples,
51            is_cd,
52            track_number,
53            tracks,
54        })
55    }
56}
57
58#[cfg(feature = "async")]
59#[async_trait::async_trait]
60impl AsyncDecode for BlockCueSheet {
61    async fn from_async_reader<R>(reader: &mut R) -> Result<Self>
62    where
63        R: AsyncRead + Unpin + Send,
64    {
65        let catalog_number = take_string_async(reader, 128).await?;
66        let leadin_samples = reader.read_u64().await?;
67        let is_cd = reader.read_u8().await? > 0;
68        skip_async(reader, 258).await?;
69        let track_number = reader.read_u8().await?;
70        let mut tracks = Vec::with_capacity(track_number as usize);
71        for _ in 0..track_number {
72            tracks.push(CueSheetTrack::from_async_reader(reader).await?);
73        }
74        Ok(BlockCueSheet {
75            catalog: catalog_number,
76            leadin_samples,
77            is_cd,
78            track_number,
79            tracks,
80        })
81    }
82}
83
84impl Encode for BlockCueSheet {
85    fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
86        let padding = 128 - self.catalog.len();
87        writer.write_all(self.catalog.as_bytes())?;
88        writer.write_all(&vec![0u8; padding])?;
89        writer.write_u64::<BigEndian>(self.leadin_samples)?;
90        writer.write_u8(if self.is_cd { 0b10000000 } else { 0 })?;
91        writer.write_all(&[0; 258])?;
92
93        writer.write_u8(self.track_number)?;
94        for track in self.tracks.iter() {
95            track.write_to(writer)?;
96        }
97        Ok(())
98    }
99}
100
101impl fmt::Debug for BlockCueSheet {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        let mut prefix = "".to_owned();
104        if let Some(width) = f.width() {
105            prefix = " ".repeat(width);
106        }
107        writeln!(
108            f,
109            "{prefix}media catalog number: {}",
110            self.catalog,
111            prefix = prefix
112        )?;
113        writeln!(
114            f,
115            "{prefix}lead-in: {}",
116            self.leadin_samples,
117            prefix = prefix
118        )?;
119        writeln!(f, "{prefix}is CD: {}", self.is_cd, prefix = prefix)?;
120        writeln!(
121            f,
122            "{prefix}number of tracks: {}",
123            self.track_number,
124            prefix = prefix
125        )?;
126        for (i, t) in self.tracks.iter().enumerate() {
127            writeln!(f, "{prefix}{prefix}track[{}]", i, prefix = prefix)?;
128            writeln!(
129                f,
130                "{prefix}{prefix}{prefix}offset: {}",
131                t.track_offset,
132                prefix = prefix
133            )?;
134            // TODO: https://github.com/xiph/flac/blob/ce6dd6b5732e319ef60716d9cc9af6a836a4011a/src/metaflac/operations.c#L627-L651
135        }
136        Ok(())
137    }
138}
139
140#[derive(Debug)]
141pub struct CueSheetTrack {
142    /// <64> Track offset in samples, relative to the beginning of the FLAC audio stream.
143    /// It is the offset to the first index point of the track.
144    /// (Note how this differs from CD-DA, where the track's offset in the TOC is that of the track's INDEX 01 even if there is an INDEX 00.)
145    /// For CD-DA, the offset must be evenly divisible by 588 samples (588 samples = 44100 samples/sec * 1/75th of a sec).
146    pub track_offset: u64,
147    /// <8> Track number.
148    /// A track number of 0 is not allowed to avoid conflicting with the CD-DA spec, which reserves this for the lead-in.
149    /// For CD-DA the number must be 1-99, or 170 for the lead-out; for non-CD-DA, the track number must for 255 for the lead-out.
150    /// It is not required but encouraged to start with track 1 and increase sequentially.
151    /// Track numbers must be unique within a CUESHEET.
152    pub track_number: u8,
153    /// <12*8> Track ISRC.
154    /// This is a 12-digit alphanumeric code; see here and here.
155    /// A value of 12 ASCII NUL characters may be used to denote absence of an ISRC.
156    pub isrc: [u8; 12],
157    /// <1> The track type: 0 for audio, 1 for non-audio.
158    /// This corresponds to the CD-DA Q-channel control bit 3.
159    pub is_audio: bool,
160    /// <1> The pre-emphasis flag: 0 for no pre-emphasis, 1 for pre-emphasis.
161    /// This corresponds to the CD-DA Q-channel control bit 5; see [here](http://www.chipchapin.com/CDMedia/cdda9.php3).
162    pub is_pre_emphasis: bool,
163    /// <6+13*8> Reserved. All bits must be set to zero.
164
165    /// <8> The number of track index points.
166    /// There must be at least one index in every track in a CUESHEET except for the lead-out track, which must have zero.
167    /// For CD-DA, this number may be no more than 100.
168    pub index_point_number: u8,
169
170    /// For all tracks except the lead-out track, one or more track index points.
171    pub track_index: Vec<CueSheetTrackIndex>,
172}
173
174impl Decode for CueSheetTrack {
175    fn from_reader<R: Read>(reader: &mut R) -> Result<Self> {
176        let track_offset = reader.read_u64::<BigEndian>()?;
177        let track_number = reader.read_u8()?;
178        let mut isrc = [0u8; 12];
179        reader.read_exact(&mut isrc)?;
180
181        let b = reader.read_u8()?;
182        let is_audio = (b & 0b10000000) > 0;
183        let is_pre_emphasis = (b & 0b01000000) > 0;
184        skip(reader, 13)?;
185
186        let index_point_number = reader.read_u8()?;
187        let mut track_index = Vec::with_capacity(index_point_number as usize);
188        for _ in 0..index_point_number {
189            track_index.push(CueSheetTrackIndex::from_reader(reader)?);
190        }
191
192        Ok(CueSheetTrack {
193            track_offset,
194            track_number,
195            isrc,
196            is_audio,
197            is_pre_emphasis,
198            index_point_number,
199            track_index,
200        })
201    }
202}
203
204#[cfg(feature = "async")]
205#[async_trait::async_trait]
206impl AsyncDecode for CueSheetTrack {
207    async fn from_async_reader<R>(reader: &mut R) -> Result<Self>
208    where
209        R: AsyncRead + Unpin + Send,
210    {
211        let track_offset = reader.read_u64().await?;
212        let track_number = reader.read_u8().await?;
213        let mut isrc = [0u8; 12];
214        reader.read_exact(&mut isrc).await?;
215
216        let b = reader.read_u8().await?;
217        let is_audio = (b & 0b10000000) > 0;
218        let is_pre_emphasis = (b & 0b01000000) > 0;
219        skip_async(reader, 13).await?;
220
221        let index_point_number = reader.read_u8().await?;
222        let mut track_index = Vec::with_capacity(index_point_number as usize);
223        for _ in 0..index_point_number {
224            track_index.push(CueSheetTrackIndex::from_async_reader(reader).await?);
225        }
226
227        Ok(CueSheetTrack {
228            track_offset,
229            track_number,
230            isrc,
231            is_audio,
232            is_pre_emphasis,
233            index_point_number,
234            track_index,
235        })
236    }
237}
238
239impl Encode for CueSheetTrack {
240    fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
241        writer.write_u64::<BigEndian>(self.track_offset)?;
242        writer.write_u8(self.track_number)?;
243        writer.write_all(&self.isrc)?;
244
245        let b = if self.is_audio { 0b10000000 } else { 0 }
246            + if self.is_pre_emphasis { 0b01000000 } else { 0 };
247        writer.write_u8(b)?;
248        writer.write_all(&[0; 13])?;
249
250        writer.write_u8(self.index_point_number)?;
251        for index in self.track_index.iter() {
252            index.write_to(writer)?;
253        }
254        Ok(())
255    }
256}
257
258#[derive(Debug)]
259pub struct CueSheetTrackIndex {
260    /// <64> Offset in samples, relative to the track offset, of the index point.
261    /// For CD-DA, the offset must be evenly divisible by 588 samples (588 samples = 44100 samples/sec * 1/75th of a sec).
262    /// Note that the offset is from the beginning of the track, not the beginning of the audio data.
263    pub sample_offset: u64,
264    /// <8> The index point number.
265    /// For CD-DA, an index number of 0 corresponds to the track pre-gap.
266    /// The first index in a track must have a number of 0 or 1, and subsequently, index numbers must increase by 1.
267    /// Index numbers must be unique within a track.
268    pub index_point: u8,
269    // <3*8> Reserved. All bits must be set to zero.
270}
271
272impl Decode for CueSheetTrackIndex {
273    fn from_reader<R: Read>(reader: &mut R) -> Result<Self> {
274        let sample_offset = reader.read_u64::<BigEndian>()?;
275        let index_point = reader.read_u8()?;
276        skip(reader, 3)?;
277        Ok(CueSheetTrackIndex {
278            sample_offset,
279            index_point,
280        })
281    }
282}
283
284#[cfg(feature = "async")]
285#[async_trait::async_trait]
286impl AsyncDecode for CueSheetTrackIndex {
287    async fn from_async_reader<R>(reader: &mut R) -> Result<Self>
288    where
289        R: AsyncRead + Unpin + Send,
290    {
291        let sample_offset = reader.read_u64().await?;
292        let index_point = reader.read_u8().await?;
293        skip_async(reader, 3).await?;
294        Ok(CueSheetTrackIndex {
295            sample_offset,
296            index_point,
297        })
298    }
299}
300
301impl Encode for CueSheetTrackIndex {
302    fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
303        writer.write_u64::<BigEndian>(self.sample_offset)?;
304        writer.write_u8(self.index_point)?;
305        writer.write_all(&[0; 3])?;
306        Ok(())
307    }
308}