1use crate::bitwriter::BitWriter;
8
9pub struct StreamInfo {
13 pub min_blocksize: u32,
14 pub max_blocksize: u32,
15 pub min_framesize: u32,
16 pub max_framesize: u32,
17 pub sample_rate: u32,
18 pub channels: u32,
19 pub bits_per_sample: u32,
20 pub total_samples: u64,
21 pub md5: [u8; 16],
22}
23
24const METADATA_TYPE_STREAMINFO: u32 = 0;
26const METADATA_TYPE_PADDING: u32 = 1;
27const METADATA_TYPE_APPLICATION: u32 = 2;
28const METADATA_TYPE_SEEKTABLE: u32 = 3;
29const METADATA_TYPE_VORBIS_COMMENT: u32 = 4;
30const METADATA_TYPE_CUESHEET: u32 = 5;
31const METADATA_TYPE_PICTURE: u32 = 6;
32const STREAMINFO_LENGTH: u32 = 34;
34const SEEKPOINT_LENGTH: u32 = 18;
37pub const SEEKPOINT_PLACEHOLDER: u64 = 0xffff_ffff_ffff_ffff;
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46pub struct SeekPoint {
47 pub sample_number: u64,
48 pub stream_offset: u64,
49 pub frame_samples: u32,
50}
51
52const CUESHEET_RESERVED_BITS: u32 = 7 + 258 * 8;
56const CUESHEET_TRACK_RESERVED_BITS: u32 = 6 + 13 * 8;
57const CUESHEET_INDEX_RESERVED_BITS: u32 = 3 * 8;
58
59#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61pub struct CueSheetIndex {
62 pub offset: u64,
64 pub number: u8,
65}
66
67pub struct CueSheetTrack<'a> {
69 pub offset: u64,
71 pub number: u8,
72 pub isrc: [u8; 12],
74 pub non_audio: bool,
76 pub pre_emphasis: bool,
77 pub indices: &'a [CueSheetIndex],
78}
79
80pub enum MetadataBlock<'a> {
84 VorbisComment(&'a str),
86 Padding(u32),
88 Application { id: [u8; 4], data: &'a [u8] },
90 Seektable(&'a [SeekPoint]),
95 CueSheet {
99 media_catalog_number: &'a [u8; 128],
100 lead_in: u64,
101 is_cd: bool,
102 tracks: &'a [CueSheetTrack<'a>],
103 },
104 Picture {
107 picture_type: u32,
108 mime_type: &'a str,
109 description: &'a str,
110 width: u32,
111 height: u32,
112 depth: u32,
113 colors: u32,
114 data: &'a [u8],
115 },
116}
117
118pub fn write_block(bw: &mut BitWriter, block: &MetadataBlock, is_last: bool) {
120 match block {
121 MetadataBlock::VorbisComment(vendor) => write_vorbis_comment(bw, vendor, is_last),
122 MetadataBlock::Padding(len) => write_padding(bw, *len, is_last),
123 MetadataBlock::Application { id, data } => write_application(bw, id, data, is_last),
124 MetadataBlock::Seektable(points) => write_seektable(bw, points, is_last),
125 MetadataBlock::CueSheet {
126 media_catalog_number,
127 lead_in,
128 is_cd,
129 tracks,
130 } => write_cuesheet(bw, media_catalog_number, *lead_in, *is_cd, tracks, is_last),
131 MetadataBlock::Picture {
132 picture_type,
133 mime_type,
134 description,
135 width,
136 height,
137 depth,
138 colors,
139 data,
140 } => {
141 let mime = mime_type.as_bytes();
142 let desc = description.as_bytes();
143 let length = 4 + 4 + mime.len() + 4 + desc.len() + 16 + 4 + data.len();
145 write_block_header(bw, is_last, METADATA_TYPE_PICTURE, length as u32);
146 bw.write_raw_u32(*picture_type, 32);
147 bw.write_raw_u32(mime.len() as u32, 32);
148 bw.write_byte_block(mime);
149 bw.write_raw_u32(desc.len() as u32, 32);
150 bw.write_byte_block(desc);
151 bw.write_raw_u32(*width, 32);
152 bw.write_raw_u32(*height, 32);
153 bw.write_raw_u32(*depth, 32);
154 bw.write_raw_u32(*colors, 32);
155 bw.write_raw_u32(data.len() as u32, 32);
156 bw.write_byte_block(data);
157 }
158 }
159}
160
161pub fn write_application(bw: &mut BitWriter, id: &[u8; 4], data: &[u8], is_last: bool) {
164 write_block_header(
165 bw,
166 is_last,
167 METADATA_TYPE_APPLICATION,
168 4 + data.len() as u32,
169 );
170 bw.write_byte_block(id);
171 bw.write_byte_block(data);
172}
173
174pub fn write_seektable(bw: &mut BitWriter, points: &[SeekPoint], is_last: bool) {
181 write_block_header(
182 bw,
183 is_last,
184 METADATA_TYPE_SEEKTABLE,
185 points.len() as u32 * SEEKPOINT_LENGTH,
186 );
187 for p in points {
188 bw.write_raw_u64(p.sample_number, 64);
189 bw.write_raw_u64(p.stream_offset, 64);
190 bw.write_raw_u32(p.frame_samples, 16);
191 }
192}
193
194pub fn spaced_seek_points(num: u32, total_samples: u64) -> Vec<SeekPoint> {
202 if num == 0 || total_samples == 0 {
203 return Vec::new();
204 }
205 (0..num as u64)
206 .map(|i| SeekPoint {
207 sample_number: total_samples * i / num as u64,
208 stream_offset: 0,
209 frame_samples: 0,
210 })
211 .collect()
212}
213
214pub fn seektable_sort(points: &mut [SeekPoint]) {
221 if points.is_empty() {
222 return;
223 }
224 points.sort_by_key(|p| p.sample_number);
227 let mut j = 0usize;
228 let mut first = true;
229 for i in 0..points.len() {
230 let sn = points[i].sample_number;
231 if !first && sn != SEEKPOINT_PLACEHOLDER && sn == points[j - 1].sample_number {
232 continue; }
234 first = false;
235 points[j] = points[i];
236 j += 1;
237 }
238 for p in &mut points[j..] {
239 *p = SeekPoint {
240 sample_number: SEEKPOINT_PLACEHOLDER,
241 stream_offset: 0,
242 frame_samples: 0,
243 };
244 }
245}
246
247fn cuesheet_length(tracks: &[CueSheetTrack]) -> u32 {
252 let mut len = 396u32;
253 for t in tracks {
254 len += 36 + t.indices.len() as u32 * 12;
255 }
256 len
257}
258
259pub fn write_cuesheet(
264 bw: &mut BitWriter,
265 media_catalog_number: &[u8; 128],
266 lead_in: u64,
267 is_cd: bool,
268 tracks: &[CueSheetTrack],
269 is_last: bool,
270) {
271 write_block_header(bw, is_last, METADATA_TYPE_CUESHEET, cuesheet_length(tracks));
272 bw.write_byte_block(media_catalog_number);
273 bw.write_raw_u64(lead_in, 64);
274 bw.write_raw_u32(is_cd as u32, 1);
275 bw.write_zeroes(CUESHEET_RESERVED_BITS);
276 bw.write_raw_u32(tracks.len() as u32, 8);
277 for t in tracks {
278 bw.write_raw_u64(t.offset, 64);
279 bw.write_raw_u32(t.number as u32, 8);
280 bw.write_byte_block(&t.isrc);
281 bw.write_raw_u32(t.non_audio as u32, 1);
282 bw.write_raw_u32(t.pre_emphasis as u32, 1);
283 bw.write_zeroes(CUESHEET_TRACK_RESERVED_BITS);
284 bw.write_raw_u32(t.indices.len() as u32, 8);
285 for idx in t.indices {
286 bw.write_raw_u64(idx.offset, 64);
287 bw.write_raw_u32(idx.number as u32, 8);
288 bw.write_zeroes(CUESHEET_INDEX_RESERVED_BITS);
289 }
290 }
291}
292
293pub const LIBFLAC_VENDOR_STRING: &str = "reference libFLAC 1.4.3 20230623";
297
298fn write_block_header(bw: &mut BitWriter, is_last: bool, block_type: u32, length: u32) {
300 bw.write_raw_u32(is_last as u32, 1);
301 bw.write_raw_u32(block_type, 7);
302 bw.write_raw_u32(length, 24);
303}
304
305pub fn write_streaminfo(bw: &mut BitWriter, si: &StreamInfo, is_last: bool) {
308 write_block_header(bw, is_last, METADATA_TYPE_STREAMINFO, STREAMINFO_LENGTH);
309
310 bw.write_raw_u32(si.min_blocksize, 16);
311 bw.write_raw_u32(si.max_blocksize, 16);
312 bw.write_raw_u32(si.min_framesize, 24);
313 bw.write_raw_u32(si.max_framesize, 24);
314 bw.write_raw_u32(si.sample_rate, 20);
315 bw.write_raw_u32(si.channels - 1, 3);
316 bw.write_raw_u32(si.bits_per_sample - 1, 5);
317 bw.write_raw_u64(si.total_samples, 36);
318 bw.write_byte_block(&si.md5);
319}
320
321pub fn write_vorbis_comment(bw: &mut BitWriter, vendor: &str, is_last: bool) {
326 let vendor = vendor.as_bytes();
327 let length = 4 + vendor.len() as u32 + 4; write_block_header(bw, is_last, METADATA_TYPE_VORBIS_COMMENT, length);
329 bw.write_raw_u32_little_endian(vendor.len() as u32);
330 bw.write_byte_block(vendor);
331 bw.write_raw_u32_little_endian(0); }
333
334pub fn write_padding(bw: &mut BitWriter, length: u32, is_last: bool) {
337 write_block_header(bw, is_last, METADATA_TYPE_PADDING, length);
338 bw.write_zeroes(length * 8);
339}
340
341#[cfg(test)]
342mod tests {
343 use super::*;
344
345 fn pt(sample_number: u64, stream_offset: u64, frame_samples: u32) -> SeekPoint {
346 SeekPoint {
347 sample_number,
348 stream_offset,
349 frame_samples,
350 }
351 }
352
353 fn placeholder() -> SeekPoint {
354 pt(SEEKPOINT_PLACEHOLDER, 0, 0)
355 }
356
357 #[test]
358 fn spaced_points_formula() {
359 assert_eq!(
361 spaced_seek_points(4, 8000),
362 vec![pt(0, 0, 0), pt(2000, 0, 0), pt(4000, 0, 0), pt(6000, 0, 0)],
363 );
364 assert_eq!(
365 spaced_seek_points(3, 10),
366 vec![pt(0, 0, 0), pt(3, 0, 0), pt(6, 0, 0)], );
368 assert!(spaced_seek_points(0, 8000).is_empty());
369 assert!(spaced_seek_points(4, 0).is_empty());
370 }
371
372 #[test]
373 fn sort_dedups_and_pads_with_placeholders() {
374 let mut points = vec![
378 pt(30, 300, 2048),
379 pt(10, 100, 2048),
380 pt(10, 100, 2048), placeholder(),
382 pt(20, 200, 2048),
383 ];
384 seektable_sort(&mut points);
385 assert_eq!(
386 points,
387 vec![
388 pt(10, 100, 2048),
389 pt(20, 200, 2048),
390 pt(30, 300, 2048),
391 placeholder(),
392 placeholder(),
393 ],
394 );
395 }
396
397 #[test]
398 fn sort_keeps_existing_placeholders_at_tail() {
399 let mut points = vec![placeholder(), pt(5, 50, 2048), placeholder()];
400 seektable_sort(&mut points);
401 assert_eq!(points, vec![pt(5, 50, 2048), placeholder(), placeholder()]);
402 }
403
404 #[test]
405 fn seektable_byte_layout() {
406 let mut bw = BitWriter::new();
407 write_seektable(
408 &mut bw,
409 &[pt(0x0102_0304_0506_0708, 0x1112_1314_1516_1718, 2048)],
410 true,
411 );
412 assert_eq!(
413 bw.as_bytes(),
414 &[
415 0x83, 0x00, 0x00, 0x12, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x08, 0x00, ],
420 );
421 }
422
423 #[test]
424 fn cuesheet_byte_layout() {
425 let mcn = [0u8; 128];
426 let indices = [CueSheetIndex {
427 offset: 0x1122_3344_5566_7788,
428 number: 1,
429 }];
430 let tracks = [CueSheetTrack {
431 offset: 0x0102_0304_0506_0708,
432 number: 7,
433 isrc: *b"ABCDEFGHIJKL",
434 non_audio: true,
435 pre_emphasis: false,
436 indices: &indices,
437 }];
438 let mut bw = BitWriter::new();
439 write_cuesheet(&mut bw, &mcn, 0x00FF_00FF_00FF_00FF, true, &tracks, true);
440 let out = bw.as_bytes();
441
442 assert_eq!(out.len(), 448);
444 assert_eq!(&out[0..4], &[0x85, 0x00, 0x01, 0xBC]);
446 assert_eq!(&out[4..132], &[0u8; 128]); assert_eq!(&out[132..140], &0x00FF_00FF_00FF_00FFu64.to_be_bytes()); assert_eq!(out[140], 0x80); assert_eq!(&out[141..399], &[0u8; 258]); assert_eq!(out[399], 1); assert_eq!(&out[400..408], &0x0102_0304_0506_0708u64.to_be_bytes()); assert_eq!(out[408], 7); assert_eq!(&out[409..421], b"ABCDEFGHIJKL"); assert_eq!(out[421], 0b1000_0000); assert_eq!(&out[422..435], &[0u8; 13]); assert_eq!(out[435], 1); assert_eq!(&out[436..444], &0x1122_3344_5566_7788u64.to_be_bytes()); assert_eq!(out[444], 1); assert_eq!(&out[445..448], &[0u8; 3]); }
461}