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