Skip to main content

mp4_atom/moov/trak/mdia/minf/stbl/stsd/
flac.rs

1use crate::*;
2
3#[derive(Debug, Clone, PartialEq, Eq)]
4#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
5pub struct Flac {
6    pub audio: Audio,
7    pub dfla: Dfla,
8}
9
10impl Atom for Flac {
11    const KIND: FourCC = FourCC::new(b"fLaC");
12
13    fn decode_body<B: Buf>(buf: &mut B) -> Result<Self> {
14        let audio = Audio::decode(buf)?;
15
16        let mut dfla = None;
17        while let Some(atom) = Any::decode_maybe(buf)? {
18            match atom {
19                Any::Dfla(atom) => dfla = atom.into(),
20                unknown => Self::decode_unknown(&unknown)?,
21            }
22        }
23
24        Ok(Self {
25            audio,
26            dfla: dfla.ok_or(Error::MissingBox(Dfla::KIND))?,
27        })
28    }
29
30    fn encode_body<B: BufMut>(&self, buf: &mut B) -> Result<()> {
31        self.audio.encode(buf)?;
32        self.dfla.encode(buf)?;
33        Ok(())
34    }
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
38#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
39pub enum FlacMetadataBlock {
40    StreamInfo {
41        minimum_block_size: u16,
42        maximum_block_size: u16,
43        minimum_frame_size: u24,
44        maximum_frame_size: u24,
45        sample_rate: u32,
46        num_channels_minus_one: u8,
47        bits_per_sample_minus_one: u8,
48        number_of_interchannel_samples: u64,
49        md5_checksum: Vec<u8>,
50    },
51    Padding,
52    Application,
53    SeekTable,
54    VorbisComment {
55        vendor_string: String,
56        comments: Vec<String>,
57    },
58    CueSheet,
59    Picture,
60    Reserved,
61    Forbidden,
62}
63
64impl FlacMetadataBlock {
65    fn encode_initial_byte<B: BufMut>(buf: &mut B, block_type: u8, is_last: bool) -> Result<()> {
66        match is_last {
67            true => (0x80 | block_type).encode(buf),
68            false => block_type.encode(buf),
69        }
70    }
71    fn encode<B: BufMut>(&self, buf: &mut B, is_last: bool) -> Result<()> {
72        match self {
73            FlacMetadataBlock::StreamInfo {
74                minimum_block_size,
75                maximum_block_size,
76                minimum_frame_size,
77                maximum_frame_size,
78                sample_rate,
79                num_channels_minus_one,
80                bits_per_sample_minus_one,
81                number_of_interchannel_samples,
82                md5_checksum,
83            } => {
84                Self::encode_initial_byte(buf, 0u8, is_last)?;
85                // Add a u24 length placeholder
86                u24::from([0u8, 0u8, 0u8]).encode(buf)?;
87                let length_position = buf.len();
88                minimum_block_size.encode(buf)?;
89                maximum_block_size.encode(buf)?;
90                minimum_frame_size.encode(buf)?;
91                maximum_frame_size.encode(buf)?;
92                (((*sample_rate as u64) << 44)
93                    | ((*num_channels_minus_one as u64) << 41)
94                    | ((*bits_per_sample_minus_one as u64) << 36)
95                    | number_of_interchannel_samples)
96                    .encode(buf)?;
97                if md5_checksum.len() != 16 {
98                    return Err(Error::MissingContent(
99                        "StreamInfo.md5_checksum must be 16 bytes",
100                    ));
101                }
102                md5_checksum.encode(buf)?;
103                let length: u24 = ((buf.len() - length_position) as u32)
104                    .try_into()
105                    .map_err(|_| Error::TooLarge(Dfla::KIND))?;
106                buf.set_slice(length_position - 3, &length.to_be_bytes());
107            }
108            FlacMetadataBlock::Padding => { /* cannot write this yet */ }
109            FlacMetadataBlock::Application => { /* cannot write this yet */ }
110            FlacMetadataBlock::SeekTable => { /* cannot write this yet */ }
111            FlacMetadataBlock::VorbisComment {
112                vendor_string,
113                comments,
114            } => {
115                Self::encode_initial_byte(buf, 4u8, is_last)?;
116
117                // Add a u24 length placeholder
118                u24::from([0u8, 0u8, 0u8]).encode(buf)?;
119                let length_position = buf.len();
120                let vendor_string_bytes = vendor_string.as_bytes();
121                let vendor_string_len: u32 = vendor_string_bytes.len() as u32;
122                vendor_string_len.to_le_bytes().encode(buf)?;
123                vendor_string_bytes.encode(buf)?;
124                let number_of_comments: u32 = comments.len() as u32;
125                number_of_comments.to_le_bytes().encode(buf)?;
126                for comment in comments {
127                    let comment_bytes = comment.as_bytes();
128                    let comment_len: u32 = comment_bytes.len() as u32;
129                    comment_len.to_le_bytes().encode(buf)?;
130                    comment_bytes.encode(buf)?;
131                }
132                let length: u24 = ((buf.len() - length_position) as u32)
133                    .try_into()
134                    .map_err(|_| Error::TooLarge(Dfla::KIND))?;
135                buf.set_slice(length_position - 3, &length.to_be_bytes());
136            }
137            FlacMetadataBlock::CueSheet => { /* cannot write this yet */ }
138            FlacMetadataBlock::Picture => { /* cannot write this yet */ }
139            FlacMetadataBlock::Reserved => { /* No way to write this */ }
140            FlacMetadataBlock::Forbidden => { /* No way to write this */ }
141        }
142        Ok(())
143    }
144}
145
146// FLAC specific data
147#[derive(Debug, Clone, PartialEq, Eq)]
148#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
149pub struct Dfla {
150    pub blocks: Vec<FlacMetadataBlock>,
151}
152
153/// Parse a FLAC `StreamInfo` metadata block. See RFC 9639 Section 8.2.
154fn parse_stream_info(arr: &[u8]) -> Result<FlacMetadataBlock> {
155    let buf = &mut std::io::Cursor::new(arr);
156    let minimum_block_size = u16::decode(buf)?;
157    let maximum_block_size = u16::decode(buf)?;
158    let minimum_frame_size = u24::decode(buf)?;
159    let maximum_frame_size = u24::decode(buf)?;
160    let temp64 = u64::decode(buf)?;
161    let sample_rate: u32 = (temp64 >> 44) as u32; // 20 bits
162    let num_channels_minus_one: u8 = ((temp64 >> 41) & 0x07) as u8; // 3 bits
163    let bits_per_sample_minus_one: u8 = ((temp64 >> 36) & 0x1F) as u8; // 5 bits
164    let number_of_interchannel_samples: u64 = temp64 & 0x0000_000F_FFFF_FFFF; // 36 bits
165    let md5_checksum: Vec<u8> = Vec::decode_exact(buf, 16)?;
166    Ok(FlacMetadataBlock::StreamInfo {
167        minimum_block_size,
168        maximum_block_size,
169        minimum_frame_size,
170        maximum_frame_size,
171        sample_rate,
172        num_channels_minus_one,
173        bits_per_sample_minus_one,
174        number_of_interchannel_samples,
175        md5_checksum,
176    })
177}
178
179/// Parse a FLAC `VorbisComment` metadata block. See RFC 9639 Section 8.6
180/// (D.2.5 has an example). Vorbis comments in FLAC use little-endian
181/// integers for Vorbis compatibility.
182fn parse_vorbis_comment(arr: &[u8]) -> Result<FlacMetadataBlock> {
183    let buf = &mut std::io::Cursor::new(arr);
184    let vendor_string_length = u32::from_le_bytes(<[u8; 4]>::decode(buf)?) as usize;
185    let vendor_string_bytes: Vec<u8> = Vec::decode_exact(buf, vendor_string_length)?;
186    let vendor_string = String::from_utf8_lossy(&vendor_string_bytes)
187        .trim_end_matches('\0')
188        .to_string();
189    let number_of_fields = u32::from_le_bytes(<[u8; 4]>::decode(buf)?) as usize;
190    // Each field is at least 4 bytes (the field_length u32); reject counts
191    // that cannot possibly fit in the remaining buffer before allocating.
192    if number_of_fields > buf.remaining() / 4 {
193        return Err(Error::OutOfBounds);
194    }
195    let mut comments = Vec::with_capacity(number_of_fields.min(4096));
196    for _ in 0..number_of_fields {
197        let field_length = u32::from_le_bytes(<[u8; 4]>::decode(buf)?) as usize;
198        let field_bytes: Vec<u8> = Vec::decode_exact(buf, field_length)?;
199        let comment = String::from_utf8_lossy(&field_bytes)
200            .trim_end_matches('\0')
201            .to_string();
202        comments.push(comment);
203    }
204    Ok(FlacMetadataBlock::VorbisComment {
205        vendor_string,
206        comments,
207    })
208}
209
210impl AtomExt for Dfla {
211    type Ext = ();
212
213    const KIND_EXT: FourCC = FourCC::new(b"dfLa");
214
215    fn decode_body_ext<B: Buf>(buf: &mut B, _ext: ()) -> Result<Self> {
216        let mut is_last_block = false;
217        let mut metadata_blocks = Vec::new();
218        while buf.has_remaining() && !is_last_block {
219            let initial_bytes = u32::decode(buf)?;
220            is_last_block = (initial_bytes & 0x80_00_00_00) == 0x80_00_00_00;
221            let block_type = ((initial_bytes >> 24) & 0x7f) as u8;
222            let length = initial_bytes & 0x00_FF_FF_FF;
223            let block_data: Vec<u8> = Vec::decode_exact(buf, length as usize)?;
224            let metadata_block = match block_type {
225                0 => parse_stream_info(&block_data)?,
226                1 => FlacMetadataBlock::Padding,
227                2 => FlacMetadataBlock::Application,
228                3 => FlacMetadataBlock::SeekTable,
229                4 => parse_vorbis_comment(&block_data)?,
230                5 => FlacMetadataBlock::CueSheet,
231                6 => FlacMetadataBlock::Picture,
232                7..=126 => FlacMetadataBlock::Reserved,
233                127 => FlacMetadataBlock::Forbidden,
234                _ => unreachable!("FLAC Metadata Block type is only 7 bits"),
235            };
236            metadata_blocks.push(metadata_block);
237        }
238        Ok(Dfla {
239            blocks: metadata_blocks,
240        })
241    }
242
243    fn encode_body_ext<B: BufMut>(&self, buf: &mut B) -> Result<()> {
244        if self.blocks.is_empty() {
245            return Err(Error::MissingContent("Streaminfo"));
246        }
247        for (i, metadata_block) in self.blocks.iter().enumerate() {
248            let is_last = i + 1 == self.blocks.len();
249            metadata_block.encode(buf, is_last)?;
250        }
251        Ok(())
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258
259    // Streaminfo metadata block only
260    const ENCODED_DFLA: &[u8] = &[
261        0x00, 0x00, 0x00, 0x32, 0x64, 0x66, 0x4c, 0x61, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
262        0x22, 0x12, 0x00, 0x12, 0x00, 0x00, 0x00, 0x10, 0x00, 0x23, 0x8e, 0x0a, 0xc4, 0x43, 0x70,
263        0x00, 0x01, 0xd8, 0x00, 0x75, 0x30, 0x88, 0x11, 0x2d, 0xd5, 0x7a, 0x13, 0xe7, 0xf7, 0x22,
264        0xd0, 0xee, 0x56, 0xae, 0xa3,
265    ];
266
267    #[test]
268    fn test_dfla_decode() {
269        let buf: &mut std::io::Cursor<&[u8]> = &mut std::io::Cursor::new(ENCODED_DFLA);
270
271        let dfla = Dfla::decode(buf).expect("failed to decode dfLa");
272
273        assert_eq!(
274            dfla,
275            Dfla {
276                blocks: vec![FlacMetadataBlock::StreamInfo {
277                    minimum_block_size: 4608,
278                    maximum_block_size: 4608,
279                    minimum_frame_size: 16u32.try_into().expect("should fit in u24"),
280                    maximum_frame_size: 9102u32.try_into().expect("should fit in u24"),
281                    sample_rate: 44100,
282                    num_channels_minus_one: 1,
283                    bits_per_sample_minus_one: 23,
284                    number_of_interchannel_samples: 120832,
285                    md5_checksum: vec![
286                        117, 48, 136, 17, 45, 213, 122, 19, 231, 247, 34, 208, 238, 86, 174, 163
287                    ]
288                },]
289            }
290        );
291    }
292
293    #[test]
294    fn test_dfla_encode() {
295        let dfla = Dfla {
296            blocks: vec![FlacMetadataBlock::StreamInfo {
297                minimum_block_size: 4608,
298                maximum_block_size: 4608,
299                minimum_frame_size: 16u32.try_into().expect("should fit in u24"),
300                maximum_frame_size: 9102u32.try_into().expect("should fit in u24"),
301                sample_rate: 44100,
302                num_channels_minus_one: 1,
303                bits_per_sample_minus_one: 23,
304                number_of_interchannel_samples: 120832,
305                md5_checksum: vec![
306                    117, 48, 136, 17, 45, 213, 122, 19, 231, 247, 34, 208, 238, 86, 174, 163,
307                ],
308            }],
309        };
310
311        let mut buf = Vec::new();
312        dfla.encode(&mut buf).unwrap();
313
314        assert_eq!(buf.as_slice(), ENCODED_DFLA);
315    }
316
317    // Streaminfo metadata block plus Vorbis Comment metadata block
318    const ENCODED_DFLA_2: &[u8] = &[
319        0x00, 0x00, 0x00, 0x7c, 0x64, 0x66, 0x4c, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
320        0x22, 0x12, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xc4, 0x40, 0x70,
321        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
322        0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x00, 0x46, 0x20, 0x00, 0x00, 0x00, 0x72, 0x65,
323        0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x20, 0x6c, 0x69, 0x62, 0x46, 0x4c, 0x41, 0x43,
324        0x20, 0x31, 0x2e, 0x34, 0x2e, 0x33, 0x20, 0x32, 0x30, 0x32, 0x33, 0x30, 0x36, 0x32, 0x33,
325        0x01, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x44, 0x45, 0x53, 0x43, 0x52, 0x49, 0x50,
326        0x54, 0x49, 0x4f, 0x4e, 0x3d, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x74, 0x65, 0x73, 0x74, 0x20,
327        0x77, 0x61, 0x76, 0x65,
328    ];
329
330    #[test]
331    fn test_dfla2_decode() {
332        let buf: &mut std::io::Cursor<&[u8]> = &mut std::io::Cursor::new(ENCODED_DFLA_2);
333
334        let dfla = Dfla::decode(buf).expect("failed to decode dfLa");
335
336        assert_eq!(
337            dfla,
338            Dfla {
339                blocks: vec![
340                    FlacMetadataBlock::StreamInfo {
341                        minimum_block_size: 4608,
342                        maximum_block_size: 4608,
343                        minimum_frame_size: 0u32.try_into().expect("should fit in u24"),
344                        maximum_frame_size: 0u32.try_into().expect("should fit in u24"),
345                        sample_rate: 44100,
346                        num_channels_minus_one: 0,
347                        bits_per_sample_minus_one: 7,
348                        number_of_interchannel_samples: 0,
349                        md5_checksum: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
350                    },
351                    FlacMetadataBlock::VorbisComment {
352                        vendor_string: "reference libFLAC 1.4.3 20230623".into(),
353                        comments: vec!["DESCRIPTION=audiotest wave".into()],
354                    },
355                ]
356            }
357        );
358    }
359
360    // Regression for issue #154: a u32::MAX number_of_fields must
361    // fail cleanly without attempting a ~103 GiB upfront allocation.
362    const ENCODED_DFLA_VORBIS_HUGE_COUNT: &[u8] = &[
363        // VorbisComment block: is_last=1, block_type=4, length=8
364        0x84, 0x00, 0x00, 0x08, // vendor_string_length = 0 (LE)
365        0x00, 0x00, 0x00, 0x00, // number_of_fields = u32::MAX (LE)
366        0xFF, 0xFF, 0xFF, 0xFF,
367    ];
368
369    #[test]
370    fn test_dfla_vorbis_huge_count() {
371        let buf = &mut std::io::Cursor::new(ENCODED_DFLA_VORBIS_HUGE_COUNT);
372        assert!(matches!(
373            Dfla::decode_body_ext(buf, ()),
374            Err(Error::OutOfBounds)
375        ));
376    }
377
378    #[test]
379    fn test_dfla2_encode() {
380        let dfla = Dfla {
381            blocks: vec![
382                FlacMetadataBlock::StreamInfo {
383                    minimum_block_size: 4608,
384                    maximum_block_size: 4608,
385                    minimum_frame_size: 0u32.try_into().expect("should fit in u24"),
386                    maximum_frame_size: 0u32.try_into().expect("should fit in u24"),
387                    sample_rate: 44100,
388                    num_channels_minus_one: 0,
389                    bits_per_sample_minus_one: 7,
390                    number_of_interchannel_samples: 0,
391                    md5_checksum: vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
392                },
393                FlacMetadataBlock::VorbisComment {
394                    vendor_string: "reference libFLAC 1.4.3 20230623".into(),
395                    comments: vec!["DESCRIPTION=audiotest wave".into()],
396                },
397            ],
398        };
399
400        let mut buf = Vec::new();
401        dfla.encode(&mut buf).unwrap();
402
403        assert_eq!(buf.as_slice(), ENCODED_DFLA_2);
404    }
405}