symphonia_utils_xiph/flac/
metadata.rs

1// Symphonia
2// Copyright (c) 2019-2022 The Project Symphonia Developers.
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at https://mozilla.org/MPL/2.0/.
7
8use 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    /// The minimum and maximum number of decoded samples per block of audio.
82    pub block_len_min: u16,
83    pub block_len_max: u16,
84    /// The minimum and maximum byte length of an encoded block (frame) of audio. Either value may
85    /// be 0 if unknown.
86    pub frame_byte_len_min: u32,
87    pub frame_byte_len_max: u32,
88    /// The sample rate in Hz.
89    pub sample_rate: u32,
90    /// The channel mask.
91    pub channels: Channels,
92    /// The number of bits per sample of the stream.
93    pub bits_per_sample: u32,
94    /// The total number of samples in the stream, if available.
95    pub n_samples: Option<u64>,
96    /// The MD5 hash value of the decoded audio.
97    pub md5: Option<[u8; 16]>,
98}
99
100impl StreamInfo {
101    /// Read a stream information block.
102    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        // Read the block length bounds in number of samples.
116        info.block_len_min = reader.read_be_u16()?;
117        info.block_len_max = reader.read_be_u16()?;
118
119        // Validate the block length bounds are in the range [16, 65535] samples.
120        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        // Validate the maximum block size is greater than or equal to the minimum block size.
125        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        // Read the frame byte length bounds.
132        info.frame_byte_len_min = reader.read_be_u24()?;
133        info.frame_byte_len_max = reader.read_be_u24()?;
134
135        // Validate the maximum frame byte length is greater than or equal to the minimum frame byte
136        // length if both are known. A value of 0 for either indicates the respective byte length is
137        // unknown. Valid values are in the range [0, (2^24) - 1] bytes.
138        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        // Read sample rate, valid rates are [1, 655350] Hz.
150        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        // Read number of channels minus 1. Valid number of channels are 1-8.
157        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        // Read bits per sample minus 1. Valid number of bits per sample are 4-32.
166        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        // Read the total number of samples. All values are valid. A value of 0 indiciates a stream
173        // of unknown length.
174        info.n_samples = match br.read_bits_leq64(36)? {
175            0 => None,
176            samples => Some(samples),
177        };
178
179        // Read the decoded audio data MD5. If the MD5 buffer is zeroed then no checksum is present.
180        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    /// Check if the size is valid for a stream information block.
191    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
198/// Read a seek table block.
199pub fn read_seek_table_block<B: ReadBytes>(
200    reader: &mut B,
201    block_length: u32,
202    table: &mut SeekIndex,
203) -> Result<()> {
204    // The number of seek table entries is always the block length divided by the length of a single
205    // entry, 18 bytes.
206    let count = block_length / 18;
207
208    for _ in 0..count {
209        let sample = reader.read_be_u64()?;
210
211        // A sample value of 0xFFFFFFFFFFFFFFFF is designated as a placeholder and is to be
212        // ignored by decoders. The remaining 10 bytes of the seek point are undefined and must
213        // still be consumed.
214        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
225/// Converts a string of bytes to an ASCII string if all characters are within the printable ASCII
226/// range. If a null byte is encounted, the string terminates at that point.
227fn 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
241/// Read a cuesheet block.
242pub fn read_cuesheet_block<B: ReadBytes>(reader: &mut B, cues: &mut Vec<Cue>) -> Result<()> {
243    // Read cuesheet catalog number. The catalog number only allows printable ASCII characters.
244    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    // Number of lead-in samples.
253    let n_lead_in_samples = reader.read_be_u64()?;
254
255    // Next bit is set for CD-DA cuesheets.
256    let is_cdda = (reader.read_u8()? & 0x80) == 0x80;
257
258    // Lead-in should be non-zero only for CD-DA cuesheets.
259    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    // Next 258 bytes (read as 129 u16's) must be zero.
264    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    // There should be at-least one track in the cuesheet.
273    if n_tracks == 0 {
274        return decode_error("flac: cuesheet must have at-least one track");
275    }
276
277    // CD-DA cuesheets must have no more than 100 tracks (99 audio tracks + lead-out track)
278    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    // For a CD-DA cuesheet, the track sample offset is the same as the first index (INDEX 00 or
297    // INDEX 01) on the CD. Therefore, the offset must be a multiple of 588 samples
298    // (588 samples = 44100 samples/sec * 1/75th of a sec).
299    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    // A track number of 0 is disallowed in all cases. For CD-DA cuesheets, track 0 is reserved for
308    // lead-in.
309    if number == 0 {
310        return decode_error("flac: cuesheet track number of 0 not allowed");
311    }
312
313    // For CD-DA cuesheets, only track numbers 1-99 are allowed for regular tracks and 170 for
314    // lead-out.
315    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    // Next 14 bytes are reserved. However, the first two bits are flags. Consume the reserved bytes
330    // in u16 chunks a minor performance improvement.
331    let flags = reader.read_be_u16()?;
332
333    // These values are contained in the Cuesheet but have no analogue in Symphonia.
334    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    // Consume the remaining 12 bytes read in 3 u32 chunks.
342    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    // For CD-DA cuesheets, the track index cannot exceed 100 indicies.
351    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    // Push the ISRC as a tag.
359    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    // CD-DA track index points must have a sample offset that is a multiple of 588 samples
375    // (588 samples = 44100 samples/sec * 1/75th of a sec).
376    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    // TODO: Should be 0 or 1 for the first index for CD-DA.
387    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
392/// Read a vendor-specific application block.
393pub fn read_application_block<B: ReadBytes>(
394    reader: &mut B,
395    block_length: u32,
396) -> Result<VendorData> {
397    // Read the application identifier. Usually this is just 4 ASCII characters, but it is not
398    // limited to that. Non-printable ASCII characters must be escaped to create a valid UTF8
399    // string.
400    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    /// Read a metadata block header.
421    pub fn read<B: ReadBytes>(reader: &mut B) -> Result<MetadataBlockHeader> {
422        let header_enc = reader.read_u8()?;
423
424        // First bit of the header indicates if this is the last metadata block.
425        let is_last = (header_enc & 0x80) == 0x80;
426
427        // The next 7 bits of the header indicates the block type.
428        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}