symphonia_utils_xiph/flac/
metadata.rs1use std::ascii;
9
10use symphonia_core::audio::Channels;
11use symphonia_core::errors::{decode_error, Result};
12use symphonia_core::formats::{util::SeekIndex, Cue, CuePoint};
13use symphonia_core::io::*;
14use symphonia_core::meta::{StandardTagKey, Tag, Value, VendorData};
15
16#[derive(PartialEq, Eq)]
17pub enum MetadataBlockType {
18 StreamInfo,
19 Padding,
20 Application,
21 SeekTable,
22 VorbisComment,
23 Cuesheet,
24 Picture,
25 Unknown(u8),
26}
27
28fn flac_channels_to_channels(channels: u32) -> Channels {
29 debug_assert!(channels > 0 && channels < 9);
30
31 match channels {
32 1 => Channels::FRONT_LEFT,
33 2 => Channels::FRONT_LEFT | Channels::FRONT_RIGHT,
34 3 => Channels::FRONT_LEFT | Channels::FRONT_RIGHT | Channels::FRONT_CENTRE,
35 4 => {
36 Channels::FRONT_LEFT
37 | Channels::FRONT_RIGHT
38 | Channels::REAR_LEFT
39 | Channels::REAR_RIGHT
40 }
41 5 => {
42 Channels::FRONT_LEFT
43 | Channels::FRONT_RIGHT
44 | Channels::FRONT_CENTRE
45 | Channels::REAR_LEFT
46 | Channels::REAR_RIGHT
47 }
48 6 => {
49 Channels::FRONT_LEFT
50 | Channels::FRONT_RIGHT
51 | Channels::FRONT_CENTRE
52 | Channels::LFE1
53 | Channels::REAR_LEFT
54 | Channels::REAR_RIGHT
55 }
56 7 => {
57 Channels::FRONT_LEFT
58 | Channels::FRONT_RIGHT
59 | Channels::FRONT_CENTRE
60 | Channels::LFE1
61 | Channels::REAR_CENTRE
62 | Channels::SIDE_LEFT
63 | Channels::SIDE_RIGHT
64 }
65 8 => {
66 Channels::FRONT_LEFT
67 | Channels::FRONT_RIGHT
68 | Channels::FRONT_CENTRE
69 | Channels::LFE1
70 | Channels::REAR_LEFT
71 | Channels::REAR_RIGHT
72 | Channels::SIDE_LEFT
73 | Channels::SIDE_RIGHT
74 }
75 _ => unreachable!(),
76 }
77}
78
79#[derive(Debug, Default)]
80pub struct StreamInfo {
81 pub block_len_min: u16,
83 pub block_len_max: u16,
84 pub frame_byte_len_min: u32,
87 pub frame_byte_len_max: u32,
88 pub sample_rate: u32,
90 pub channels: Channels,
92 pub bits_per_sample: u32,
94 pub n_samples: Option<u64>,
96 pub md5: Option<[u8; 16]>,
98}
99
100impl StreamInfo {
101 pub fn read<B: ReadBytes>(reader: &mut B) -> Result<StreamInfo> {
103 let mut info = StreamInfo {
104 block_len_min: 0,
105 block_len_max: 0,
106 frame_byte_len_min: 0,
107 frame_byte_len_max: 0,
108 sample_rate: 0,
109 channels: Channels::empty(),
110 bits_per_sample: 0,
111 n_samples: None,
112 md5: None,
113 };
114
115 info.block_len_min = reader.read_be_u16()?;
117 info.block_len_max = reader.read_be_u16()?;
118
119 if info.block_len_min < 16 || info.block_len_max < 16 {
121 return decode_error("flac: minimum block length is 16 samples");
122 }
123
124 if info.block_len_max < info.block_len_min {
126 return decode_error(
127 "flac: maximum block length is less than the minimum block length",
128 );
129 }
130
131 info.frame_byte_len_min = reader.read_be_u24()?;
133 info.frame_byte_len_max = reader.read_be_u24()?;
134
135 if info.frame_byte_len_min > 0
139 && info.frame_byte_len_max > 0
140 && info.frame_byte_len_max < info.frame_byte_len_min
141 {
142 return decode_error(
143 "flac: maximum frame length is less than the minimum frame length",
144 );
145 }
146
147 let mut br = BitStreamLtr::new(reader);
148
149 info.sample_rate = br.read_bits_leq32(20)?;
151
152 if info.sample_rate < 1 || info.sample_rate > 655_350 {
153 return decode_error("flac: stream sample rate out of bounds");
154 }
155
156 let channels_enc = br.read_bits_leq32(3)? + 1;
158
159 if channels_enc < 1 || channels_enc > 8 {
160 return decode_error("flac: stream channels are out of bounds");
161 }
162
163 info.channels = flac_channels_to_channels(channels_enc);
164
165 info.bits_per_sample = br.read_bits_leq32(5)? + 1;
167
168 if info.bits_per_sample < 4 || info.bits_per_sample > 32 {
169 return decode_error("flac: stream bits per sample are out of bounds");
170 }
171
172 info.n_samples = match br.read_bits_leq64(36)? {
175 0 => None,
176 samples => Some(samples),
177 };
178
179 let mut md5 = [0; 16];
181 reader.read_buf_exact(&mut md5)?;
182
183 if md5 != [0; 16] {
184 info.md5 = Some(md5);
185 }
186
187 Ok(info)
188 }
189
190 pub fn is_valid_size(size: u64) -> bool {
192 const STREAM_INFO_BLOCK_SIZE: u64 = 34;
193
194 size == STREAM_INFO_BLOCK_SIZE
195 }
196}
197
198pub fn read_seek_table_block<B: ReadBytes>(
200 reader: &mut B,
201 block_length: u32,
202 table: &mut SeekIndex,
203) -> Result<()> {
204 let count = block_length / 18;
207
208 for _ in 0..count {
209 let sample = reader.read_be_u64()?;
210
211 if sample != 0xffff_ffff_ffff_ffff {
215 table.insert(sample, reader.read_be_u64()?, u32::from(reader.read_be_u16()?));
216 }
217 else {
218 reader.ignore_bytes(10)?;
219 }
220 }
221
222 Ok(())
223}
224
225fn printable_ascii_to_string(bytes: &[u8]) -> Option<String> {
228 let mut result = String::with_capacity(bytes.len());
229
230 for c in bytes {
231 match c {
232 0x00 => break,
233 0x20..=0x7e => result.push(char::from(*c)),
234 _ => return None,
235 }
236 }
237
238 Some(result)
239}
240
241pub fn read_cuesheet_block<B: ReadBytes>(reader: &mut B, cues: &mut Vec<Cue>) -> Result<()> {
243 let mut catalog_number_buf = vec![0u8; 128];
245 reader.read_buf_exact(&mut catalog_number_buf)?;
246
247 let _catalog_number = match printable_ascii_to_string(&catalog_number_buf) {
248 Some(s) => s,
249 None => return decode_error("flac: cuesheet catalog number contains invalid characters"),
250 };
251
252 let n_lead_in_samples = reader.read_be_u64()?;
254
255 let is_cdda = (reader.read_u8()? & 0x80) == 0x80;
257
258 if !is_cdda && n_lead_in_samples > 0 {
260 return decode_error("flac: cuesheet lead-in samples should be zero if not CD-DA");
261 }
262
263 for _ in 0..129 {
265 if reader.read_be_u16()? != 0 {
266 return decode_error("flac: cuesheet reserved bits should be zero");
267 }
268 }
269
270 let n_tracks = reader.read_u8()?;
271
272 if n_tracks == 0 {
274 return decode_error("flac: cuesheet must have at-least one track");
275 }
276
277 if is_cdda && n_tracks > 100 {
279 return decode_error("flac: cuesheets for CD-DA must not have more than 100 tracks");
280 }
281
282 for _ in 0..n_tracks {
283 read_cuesheet_track(reader, is_cdda, cues)?;
284 }
285
286 Ok(())
287}
288
289fn read_cuesheet_track<B: ReadBytes>(
290 reader: &mut B,
291 is_cdda: bool,
292 cues: &mut Vec<Cue>,
293) -> Result<()> {
294 let n_offset_samples = reader.read_be_u64()?;
295
296 if is_cdda && n_offset_samples % 588 != 0 {
300 return decode_error(
301 "flac: cuesheet track sample offset is not a multiple of 588 for CD-DA",
302 );
303 }
304
305 let number = u32::from(reader.read_u8()?);
306
307 if number == 0 {
310 return decode_error("flac: cuesheet track number of 0 not allowed");
311 }
312
313 if is_cdda && number > 99 && number != 170 {
316 return decode_error(
317 "flac: cuesheet track numbers greater than 99 are not allowed for CD-DA",
318 );
319 }
320
321 let mut isrc_buf = vec![0u8; 12];
322 reader.read_buf_exact(&mut isrc_buf)?;
323
324 let isrc = match printable_ascii_to_string(&isrc_buf) {
325 Some(s) => s,
326 None => return decode_error("flac: cuesheet track ISRC contains invalid characters"),
327 };
328
329 let flags = reader.read_be_u16()?;
332
333 let _is_audio = (flags & 0x8000) == 0x0000;
335 let _use_pre_emphasis = (flags & 0x4000) == 0x4000;
336
337 if flags & 0x3fff != 0 {
338 return decode_error("flac: cuesheet track reserved bits should be zero");
339 }
340
341 for _ in 0..3 {
343 if reader.read_be_u32()? != 0 {
344 return decode_error("flac: cuesheet track reserved bits should be zero");
345 }
346 }
347
348 let n_indicies = reader.read_u8()? as usize;
349
350 if is_cdda && n_indicies > 100 {
352 return decode_error("flac: cuesheet track indicies cannot exceed 100 for CD-DA");
353 }
354
355 let mut cue =
356 Cue { index: number, start_ts: n_offset_samples, tags: Vec::new(), points: Vec::new() };
357
358 cue.tags.push(Tag::new(Some(StandardTagKey::IdentIsrc), "ISRC", Value::from(isrc)));
360
361 for _ in 0..n_indicies {
362 cue.points.push(read_cuesheet_track_index(reader, is_cdda)?);
363 }
364
365 cues.push(cue);
366
367 Ok(())
368}
369
370fn read_cuesheet_track_index<B: ReadBytes>(reader: &mut B, is_cdda: bool) -> Result<CuePoint> {
371 let n_offset_samples = reader.read_be_u64()?;
372 let idx_point_enc = reader.read_be_u32()?;
373
374 if is_cdda && n_offset_samples % 588 != 0 {
377 return decode_error(
378 "flac: cuesheet track index point sample offset is not a multiple of 588 for CD-DA",
379 );
380 }
381
382 if idx_point_enc & 0x00ff_ffff != 0 {
383 return decode_error("flac: cuesheet track index reserved bits should be 0");
384 }
385
386 let _idx_point = ((idx_point_enc & 0xff00_0000) >> 24) as u8;
388
389 Ok(CuePoint { start_offset_ts: n_offset_samples, tags: Vec::new() })
390}
391
392pub fn read_application_block<B: ReadBytes>(
394 reader: &mut B,
395 block_length: u32,
396) -> Result<VendorData> {
397 let ident_buf = reader.read_quad_bytes()?;
401 let ident = String::from_utf8(
402 ident_buf.as_ref().iter().copied().flat_map(ascii::escape_default).collect(),
403 )
404 .unwrap();
405
406 let data = reader.read_boxed_slice_exact(block_length as usize - 4)?;
407 Ok(VendorData { ident, data })
408}
409
410pub use symphonia_metadata::flac::read_comment_block;
411pub use symphonia_metadata::flac::read_picture_block;
412
413pub struct MetadataBlockHeader {
414 pub is_last: bool,
415 pub block_type: MetadataBlockType,
416 pub block_len: u32,
417}
418
419impl MetadataBlockHeader {
420 pub fn read<B: ReadBytes>(reader: &mut B) -> Result<MetadataBlockHeader> {
422 let header_enc = reader.read_u8()?;
423
424 let is_last = (header_enc & 0x80) == 0x80;
426
427 let block_type_id = header_enc & 0x7f;
429
430 let block_type = match block_type_id {
431 0 => MetadataBlockType::StreamInfo,
432 1 => MetadataBlockType::Padding,
433 2 => MetadataBlockType::Application,
434 3 => MetadataBlockType::SeekTable,
435 4 => MetadataBlockType::VorbisComment,
436 5 => MetadataBlockType::Cuesheet,
437 6 => MetadataBlockType::Picture,
438 _ => MetadataBlockType::Unknown(block_type_id),
439 };
440
441 let block_len = reader.read_be_u24()?;
442
443 Ok(MetadataBlockHeader { is_last, block_type, block_len })
444 }
445}