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