Skip to main content

avif_parse/
lib.rs

1#![allow(clippy::missing_safety_doc)]
2//! Module for parsing ISO Base Media Format aka video/mp4 streams.
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 arrayvec::ArrayVec;
9use log::{debug, warn};
10
11use bitreader::BitReader;
12use byteorder::ReadBytesExt;
13use fallible_collections::{TryClone, TryReserveError};
14use std::convert::{TryFrom, TryInto as _};
15
16use std::io::{Read, Take};
17use std::num::NonZeroU32;
18use std::ops::{Range, RangeFrom};
19
20mod obu;
21
22mod boxes;
23use crate::boxes::{BoxType, FourCC};
24
25/// This crate can be used from C.
26#[cfg(feature = "c_api")]
27pub mod c_api;
28
29// Arbitrary buffer size limit used for raw read_bufs on a box.
30// const BUF_SIZE_LIMIT: u64 = 10 * 1024 * 1024;
31
32/// A trait to indicate a type can be infallibly converted to `u64`.
33/// This should only be implemented for infallible conversions, so only unsigned types are valid.
34trait ToU64 {
35    fn to_u64(self) -> u64;
36}
37
38/// Statically verify that the platform `usize` can fit within a `u64`.
39/// If the size won't fit on the given platform, this will fail at compile time, but if a type
40/// which can fail `TryInto<usize>` is used, it may panic.
41impl ToU64 for usize {
42    fn to_u64(self) -> u64 {
43        const _: () = assert!(std::mem::size_of::<usize>() <= std::mem::size_of::<u64>());
44        self.try_into().ok().unwrap()
45    }
46}
47
48/// A trait to indicate a type can be infallibly converted to `usize`.
49/// This should only be implemented for infallible conversions, so only unsigned types are valid.
50pub(crate) trait ToUsize {
51    fn to_usize(self) -> usize;
52}
53
54/// Statically verify that the given type can fit within a `usize`.
55/// If the size won't fit on the given platform, this will fail at compile time, but if a type
56/// which can fail `TryInto<usize>` is used, it may panic.
57macro_rules! impl_to_usize_from {
58    ( $from_type:ty ) => {
59        impl ToUsize for $from_type {
60            fn to_usize(self) -> usize {
61                const _: () = assert!(std::mem::size_of::<$from_type>() <= std::mem::size_of::<usize>());
62                self.try_into().ok().unwrap()
63            }
64        }
65    };
66}
67
68impl_to_usize_from!(u8);
69impl_to_usize_from!(u16);
70impl_to_usize_from!(u32);
71
72/// Indicate the current offset (i.e., bytes already read) in a reader
73trait Offset {
74    fn offset(&self) -> u64;
75}
76
77/// Wraps a reader to track the current offset
78struct OffsetReader<'a, T> {
79    reader: &'a mut T,
80    offset: u64,
81}
82
83impl<'a, T> OffsetReader<'a, T> {
84    fn new(reader: &'a mut T) -> Self {
85        Self { reader, offset: 0 }
86    }
87}
88
89impl<T> Offset for OffsetReader<'_, T> {
90    fn offset(&self) -> u64 {
91        self.offset
92    }
93}
94
95impl<T: Read> Read for OffsetReader<'_, T> {
96    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
97        let bytes_read = self.reader.read(buf)?;
98        self.offset = self
99            .offset
100            .checked_add(bytes_read.to_u64())
101            .ok_or(Error::Unsupported("total bytes read too large for offset type"))?;
102        Ok(bytes_read)
103    }
104}
105
106pub(crate) type TryVec<T> = fallible_collections::TryVec<T>;
107pub(crate) type TryString = fallible_collections::TryVec<u8>;
108
109// To ensure we don't use stdlib allocating types by accident
110#[allow(dead_code)]
111struct Vec;
112#[allow(dead_code)]
113struct Box;
114#[allow(dead_code)]
115struct HashMap;
116#[allow(dead_code)]
117struct String;
118
119/// Describes parser failures.
120///
121/// This enum wraps the standard `io::Error` type, unified with
122/// our own parser error states and those of crates we use.
123#[derive(Debug)]
124pub enum Error {
125    /// Parse error caused by corrupt or malformed data.
126    InvalidData(&'static str),
127    /// Parse error caused by limited parser support rather than invalid data.
128    Unsupported(&'static str),
129    /// Reflect `std::io::ErrorKind::UnexpectedEof` for short data.
130    UnexpectedEOF,
131    /// Propagate underlying errors from `std::io`.
132    Io(std::io::Error),
133    /// `read_mp4` terminated without detecting a moov box.
134    NoMoov,
135    /// Out of memory
136    OutOfMemory,
137}
138
139impl std::fmt::Display for Error {
140    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
141        let msg = match self {
142            Self::InvalidData(s) | Self::Unsupported(s) => s,
143            Self::UnexpectedEOF => "EOF",
144            Self::Io(err) => return err.fmt(f),
145            Self::NoMoov => "Missing Moov box",
146            Self::OutOfMemory => "OOM",
147        };
148        f.write_str(msg)
149    }
150}
151
152impl std::error::Error for Error {}
153
154impl From<bitreader::BitReaderError> for Error {
155    #[cold]
156    #[cfg_attr(debug_assertions, track_caller)]
157    fn from(err: bitreader::BitReaderError) -> Self {
158        log::warn!("bitreader: {err}");
159        debug_assert!(!matches!(err, bitreader::BitReaderError::TooManyBitsForType { .. })); // bug
160        Self::InvalidData("truncated bits")
161    }
162}
163
164impl From<std::io::Error> for Error {
165    fn from(err: std::io::Error) -> Self {
166        match err.kind() {
167            std::io::ErrorKind::UnexpectedEof => Self::UnexpectedEOF,
168            _ => Self::Io(err),
169        }
170    }
171}
172
173impl From<std::string::FromUtf8Error> for Error {
174    fn from(_: std::string::FromUtf8Error) -> Self {
175        Self::InvalidData("invalid utf8")
176    }
177}
178
179impl From<std::num::TryFromIntError> for Error {
180    fn from(_: std::num::TryFromIntError) -> Self {
181        Self::Unsupported("integer conversion failed")
182    }
183}
184
185impl From<Error> for std::io::Error {
186    fn from(err: Error) -> Self {
187        let kind = match err {
188            Error::InvalidData(_) => std::io::ErrorKind::InvalidData,
189            Error::UnexpectedEOF => std::io::ErrorKind::UnexpectedEof,
190            Error::Io(io_err) => return io_err,
191            _ => std::io::ErrorKind::Other,
192        };
193        Self::new(kind, err)
194    }
195}
196
197impl From<TryReserveError> for Error {
198    fn from(_: TryReserveError) -> Self {
199        Self::OutOfMemory
200    }
201}
202
203/// Result shorthand using our Error enum.
204pub type Result<T, E = Error> = std::result::Result<T, E>;
205
206/// Basic ISO box structure.
207///
208/// mp4 files are a sequence of possibly-nested 'box' structures.  Each box
209/// begins with a header describing the length of the box's data and a
210/// four-byte box type which identifies the type of the box. Together these
211/// are enough to interpret the contents of that section of the file.
212///
213/// See ISO 14496-12:2015 § 4.2
214#[derive(Debug, Clone, Copy)]
215struct BoxHeader {
216    /// Box type.
217    name: BoxType,
218    /// Size of the box in bytes.
219    size: u64,
220    /// Offset to the start of the contained data (or header size).
221    offset: u64,
222    /// Uuid for extended type.
223    #[allow(unused)]
224    uuid: Option<[u8; 16]>,
225}
226
227impl BoxHeader {
228    /// 4-byte size + 4-byte type
229    const MIN_SIZE: u64 = 8;
230    /// 4-byte size + 4-byte type + 16-byte size
231    const MIN_LARGE_SIZE: u64 = 16;
232}
233
234/// File type box 'ftyp'.
235#[derive(Debug)]
236#[allow(unused)]
237struct FileTypeBox {
238    major_brand: FourCC,
239    minor_version: u32,
240    compatible_brands: TryVec<FourCC>,
241}
242
243// Handler reference box 'hdlr'
244#[derive(Debug)]
245#[allow(unused)]
246struct HandlerBox {
247    handler_type: FourCC,
248}
249
250#[derive(Debug)]
251#[allow(unused)]
252pub(crate) struct AV1ConfigBox {
253    pub(crate) profile: u8,
254    pub(crate) level: u8,
255    pub(crate) tier: u8,
256    pub(crate) bit_depth: u8,
257    pub(crate) monochrome: bool,
258    pub(crate) chroma_subsampling_x: u8,
259    pub(crate) chroma_subsampling_y: u8,
260    pub(crate) chroma_sample_position: u8,
261    pub(crate) initial_presentation_delay_present: bool,
262    pub(crate) initial_presentation_delay_minus_one: u8,
263    pub(crate) config_obus: TryVec<u8>,
264}
265
266/// Content Light Level Information (CEA-861.3).
267///
268/// Signals the content light level of HDR content.
269/// See ISOBMFF § 12.1.5.
270#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271pub struct ContentLightLevel {
272    /// Maximum light level of any single pixel (MaxCLL), in cd/m².
273    pub max_content_light_level: u16,
274    /// Maximum frame-average light level (MaxFALL), in cd/m².
275    pub max_pic_average_light_level: u16,
276}
277
278/// Mastering Display Colour Volume (SMPTE ST 2086).
279///
280/// Describes the color volume of the mastering display used to author the content.
281/// See ISOBMFF § 12.1.5.
282#[derive(Debug, Clone, Copy, PartialEq, Eq)]
283pub struct MasteringDisplayColourVolume {
284    /// Display primaries in CIE 1931 xy chromaticity, encoded as the value × 50000.
285    /// For example, D65 white (0.3127, 0.3290) encodes as (15635, 16450).
286    /// Order: \[green, blue, red\] per SMPTE ST 2086.
287    pub primaries: [(u16, u16); 3],
288    /// White point in CIE 1931 xy chromaticity, same encoding as `primaries`.
289    pub white_point: (u16, u16),
290    /// Maximum luminance of the mastering display in cd/m² × 10000.
291    /// For example, 1000 cd/m² = 10_000_000.
292    pub max_luminance: u32,
293    /// Minimum luminance of the mastering display in cd/m² × 10000.
294    /// For example, 0.005 cd/m² = 50.
295    pub min_luminance: u32,
296}
297
298#[derive(Debug, Default)]
299#[non_exhaustive]
300pub struct AvifData {
301    /// AV1 data for the color channels.
302    ///
303    /// The collected data indicated by the `pitm` box, See ISO 14496-12:2015 § 8.11.4
304    pub primary_item: TryVec<u8>,
305    /// AV1 data for alpha channel.
306    ///
307    /// Associated alpha channel for the primary item, if any
308    pub alpha_item: Option<TryVec<u8>>,
309    /// If true, divide RGB values by the alpha value.
310    ///
311    /// See `prem` in MIAF § 7.3.5.2
312    pub premultiplied_alpha: bool,
313    /// Content light level from the container's `clli` property, if present.
314    pub content_light_level: Option<ContentLightLevel>,
315    /// Mastering display colour volume from the container's `mdcv` property, if present.
316    pub mastering_display: Option<MasteringDisplayColourVolume>,
317}
318
319impl AvifData {
320    pub fn from_reader<R: Read>(reader: &mut R) -> Result<Self> {
321        read_avif(reader)
322    }
323
324    /// Parses AV1 data to get basic properties of the opaque channel
325    pub fn primary_item_metadata(&self) -> Result<AV1Metadata> {
326        AV1Metadata::parse_av1_bitstream(&self.primary_item)
327    }
328
329    /// Parses AV1 data to get basic properties about the alpha channel, if any
330    pub fn alpha_item_metadata(&self) -> Result<Option<AV1Metadata>> {
331        self.alpha_item.as_deref().map(AV1Metadata::parse_av1_bitstream).transpose()
332    }
333}
334
335/// See [`AvifData::primary_item_metadata()`]
336#[non_exhaustive]
337#[derive(Debug, Clone)]
338pub struct AV1Metadata {
339    /// Should be true for non-animated AVIF
340    pub still_picture: bool,
341    pub max_frame_width: NonZeroU32,
342    pub max_frame_height: NonZeroU32,
343    /// 8, 10, or 12
344    pub bit_depth: u8,
345    /// 0, 1 or 2 for the level of complexity
346    pub seq_profile: u8,
347    /// Horizontal and vertical. `false` is full-res.
348    pub chroma_subsampling: (bool, bool),
349    pub monochrome: bool,
350}
351
352impl AV1Metadata {
353    /// Parses raw AV1 bitstream (OBU sequence header) only.
354    ///
355    /// This is for the bare image payload from an encoder, not an AVIF/HEIF file.
356    /// To parse AVIF files, see [`AvifData::from_reader()`].
357    #[inline(never)]
358    pub fn parse_av1_bitstream(obu_bitstream: &[u8]) -> Result<Self> {
359        let h = obu::parse_obu(obu_bitstream)?;
360        Ok(Self {
361            still_picture: h.still_picture,
362            max_frame_width: h.max_frame_width,
363            max_frame_height: h.max_frame_height,
364            bit_depth: h.color.bit_depth,
365            seq_profile: h.seq_profile,
366            chroma_subsampling: h.color.chroma_subsampling,
367            monochrome: h.color.monochrome,
368        })
369    }
370}
371
372struct AvifInternalMeta {
373    item_references: TryVec<SingleItemTypeReferenceBox>,
374    properties: TryVec<AssociatedProperty>,
375    primary_item_id: u32,
376    iloc_items: TryVec<ItemLocationBoxItem>,
377}
378
379/// A Media Data Box
380/// See ISO 14496-12:2015 § 8.1.1
381struct MediaDataBox {
382    /// Offset of `data` from the beginning of the file. See `ConstructionMethod::File`
383    offset: u64,
384    data: TryVec<u8>,
385}
386
387impl MediaDataBox {
388    /// Check whether the beginning of `extent` is within the bounds of the `MediaDataBox`.
389    /// We assume extents to not cross box boundaries. If so, this will cause an error
390    /// in `read_extent`.
391    fn contains_extent(&self, extent: &ExtentRange) -> bool {
392        if self.offset <= extent.start() {
393            let start_offset = extent.start() - self.offset;
394            start_offset < self.data.len().to_u64()
395        } else {
396            false
397        }
398    }
399
400    /// Check whether `extent` covers the `MediaDataBox` exactly.
401    fn matches_extent(&self, extent: &ExtentRange) -> bool {
402        if self.offset == extent.start() {
403            match extent {
404                ExtentRange::WithLength(range) => {
405                    if let Some(end) = self.offset.checked_add(self.data.len().to_u64()) {
406                        end == range.end
407                    } else {
408                        false
409                    }
410                },
411                ExtentRange::ToEnd(_) => true,
412            }
413        } else {
414            false
415        }
416    }
417
418    /// Copy the range specified by `extent` to the end of `buf` or return an error if the range
419    /// is not fully contained within `MediaDataBox`.
420    fn read_extent(&self, extent: &ExtentRange, buf: &mut TryVec<u8>) -> Result<()> {
421        let start_offset = extent
422            .start()
423            .checked_sub(self.offset)
424            .ok_or(Error::InvalidData("mdat does not contain extent"))?;
425        let slice = match extent {
426            ExtentRange::WithLength(range) => {
427                let range_len = range
428                    .end
429                    .checked_sub(range.start)
430                    .ok_or(Error::InvalidData("range start > end"))?;
431                let end = start_offset
432                    .checked_add(range_len)
433                    .ok_or(Error::InvalidData("extent end overflow"))?;
434                self.data.get(start_offset.try_into()?..end.try_into()?)
435            },
436            ExtentRange::ToEnd(_) => self.data.get(start_offset.try_into()?..),
437        };
438        let slice = slice.ok_or(Error::InvalidData("extent crosses box boundary"))?;
439        buf.extend_from_slice(slice)?;
440        Ok(())
441    }
442}
443
444/// Used for 'infe' boxes within 'iinf' boxes
445/// See ISO 14496-12:2015 § 8.11.6
446/// Only versions {2, 3} are supported
447#[derive(Debug)]
448struct ItemInfoEntry {
449    item_id: u32,
450    item_type: FourCC,
451}
452
453/// See ISO 14496-12:2015 § 8.11.12
454#[derive(Debug)]
455struct SingleItemTypeReferenceBox {
456    item_type: FourCC,
457    from_item_id: u32,
458    to_item_id: u32,
459}
460
461/// Potential sizes (in bytes) of variable-sized fields of the 'iloc' box
462/// See ISO 14496-12:2015 § 8.11.3
463#[derive(Debug)]
464enum IlocFieldSize {
465    Zero,
466    Four,
467    Eight,
468}
469
470impl IlocFieldSize {
471    const fn to_bits(&self) -> u8 {
472        match self {
473            Self::Zero => 0,
474            Self::Four => 32,
475            Self::Eight => 64,
476        }
477    }
478}
479
480impl TryFrom<u8> for IlocFieldSize {
481    type Error = Error;
482
483    fn try_from(value: u8) -> Result<Self> {
484        match value {
485            0 => Ok(Self::Zero),
486            4 => Ok(Self::Four),
487            8 => Ok(Self::Eight),
488            _ => Err(Error::InvalidData("value must be in the set {0, 4, 8}")),
489        }
490    }
491}
492
493#[derive(PartialEq)]
494enum IlocVersion {
495    Zero,
496    One,
497    Two,
498}
499
500impl TryFrom<u8> for IlocVersion {
501    type Error = Error;
502
503    fn try_from(value: u8) -> Result<Self> {
504        match value {
505            0 => Ok(Self::Zero),
506            1 => Ok(Self::One),
507            2 => Ok(Self::Two),
508            _ => Err(Error::Unsupported("unsupported version in 'iloc' box")),
509        }
510    }
511}
512
513/// Used for 'iloc' boxes
514/// See ISO 14496-12:2015 § 8.11.3
515/// `base_offset` is omitted since it is integrated into the ranges in `extents`
516/// `data_reference_index` is omitted, since only 0 (i.e., this file) is supported
517#[derive(Debug)]
518struct ItemLocationBoxItem {
519    item_id: u32,
520    construction_method: ConstructionMethod,
521    /// Unused for `ConstructionMethod::Idat`
522    extents: TryVec<ItemLocationBoxExtent>,
523}
524
525#[derive(Clone, Copy, Debug, PartialEq)]
526enum ConstructionMethod {
527    File,
528    Idat,
529    #[allow(dead_code)] // TODO: see https://github.com/mozilla/mp4parse-rust/issues/196
530    Item,
531}
532
533/// `extent_index` is omitted since it's only used for `ConstructionMethod::Item` which
534/// is currently not implemented.
535#[derive(Clone, Debug)]
536struct ItemLocationBoxExtent {
537    extent_range: ExtentRange,
538}
539
540#[derive(Clone, Debug)]
541enum ExtentRange {
542    WithLength(Range<u64>),
543    ToEnd(RangeFrom<u64>),
544}
545
546impl ExtentRange {
547    const fn start(&self) -> u64 {
548        match self {
549            Self::WithLength(r) => r.start,
550            Self::ToEnd(r) => r.start,
551        }
552    }
553}
554
555/// See ISO 14496-12:2015 § 4.2
556struct BMFFBox<'a, T> {
557    head: BoxHeader,
558    content: Take<&'a mut T>,
559}
560
561impl<T: Read> BMFFBox<'_, T> {
562    fn read_into_try_vec(&mut self) -> std::io::Result<TryVec<u8>> {
563        let mut vec = std::vec::Vec::new();
564        vec.try_reserve_exact(self.content.limit() as usize)
565            .map_err(|_| std::io::ErrorKind::OutOfMemory)?;
566        self.content.read_to_end(&mut vec)?; // The default impl
567        Ok(vec.into())
568    }
569}
570
571#[test]
572fn box_read_to_end() {
573    let tmp = &mut b"1234567890".as_slice();
574    let mut src = BMFFBox {
575        head: BoxHeader { name: BoxType::FileTypeBox, size: 5, offset: 0, uuid: None },
576        content: <_ as Read>::take(tmp, 5),
577    };
578    let buf = src.read_into_try_vec().unwrap();
579    assert_eq!(buf.len(), 5);
580    assert_eq!(buf, b"12345".as_ref());
581}
582
583#[test]
584fn box_read_to_end_oom() {
585    let tmp = &mut b"1234567890".as_slice();
586    let mut src = BMFFBox {
587        head: BoxHeader { name: BoxType::FileTypeBox, size: 5, offset: 0, uuid: None },
588        content: <_ as Read>::take(tmp, usize::MAX.try_into().expect("usize < u64")),
589    };
590    assert!(src.read_into_try_vec().is_err());
591}
592
593struct BoxIter<'a, T> {
594    src: &'a mut T,
595}
596
597impl<T: Read> BoxIter<'_, T> {
598    fn new(src: &mut T) -> BoxIter<'_, T> {
599        BoxIter { src }
600    }
601
602    fn next_box(&mut self) -> Result<Option<BMFFBox<'_, T>>> {
603        let r = read_box_header(self.src);
604        match r {
605            Ok(h) => Ok(Some(BMFFBox {
606                head: h,
607                content: self.src.take(h.size - h.offset),
608            })),
609            Err(Error::UnexpectedEOF) => Ok(None),
610            Err(e) => Err(e),
611        }
612    }
613}
614
615impl<T: Read> Read for BMFFBox<'_, T> {
616    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
617        self.content.read(buf)
618    }
619}
620
621impl<T: Offset> Offset for BMFFBox<'_, T> {
622    fn offset(&self) -> u64 {
623        self.content.get_ref().offset()
624    }
625}
626
627impl<T: Read> BMFFBox<'_, T> {
628    fn bytes_left(&self) -> u64 {
629        self.content.limit()
630    }
631
632    const fn get_header(&self) -> &BoxHeader {
633        &self.head
634    }
635
636    fn box_iter(&mut self) -> BoxIter<'_, Self> {
637        BoxIter::new(self)
638    }
639}
640
641impl<T> Drop for BMFFBox<'_, T> {
642    fn drop(&mut self) {
643        if self.content.limit() > 0 {
644            let name: FourCC = From::from(self.head.name);
645            debug!("Dropping {} bytes in '{}'", self.content.limit(), name);
646        }
647    }
648}
649
650/// Read and parse a box header.
651///
652/// Call this first to determine the type of a particular mp4 box
653/// and its length. Used internally for dispatching to specific
654/// parsers for the internal content, or to get the length to
655/// skip unknown or uninteresting boxes.
656///
657/// See ISO 14496-12:2015 § 4.2
658fn read_box_header<T: ReadBytesExt>(src: &mut T) -> Result<BoxHeader> {
659    let size32 = be_u32(src)?;
660    let name = BoxType::from(be_u32(src)?);
661    let size = match size32 {
662        // valid only for top-level box and indicates it's the last box in the file.  usually mdat.
663        0 => return Err(Error::Unsupported("unknown sized box")),
664        1 => {
665            let size64 = be_u64(src)?;
666            if size64 < BoxHeader::MIN_LARGE_SIZE {
667                return Err(Error::InvalidData("malformed wide size"));
668            }
669            size64
670        },
671        _ => {
672            if u64::from(size32) < BoxHeader::MIN_SIZE {
673                return Err(Error::InvalidData("malformed size"));
674            }
675            u64::from(size32)
676        },
677    };
678    let mut offset = match size32 {
679        1 => BoxHeader::MIN_LARGE_SIZE,
680        _ => BoxHeader::MIN_SIZE,
681    };
682    let uuid = if name == BoxType::UuidBox {
683        if size >= offset + 16 {
684            let mut buffer = [0u8; 16];
685            let count = src.read(&mut buffer)?;
686            offset += count.to_u64();
687            if count == 16 {
688                Some(buffer)
689            } else {
690                debug!("malformed uuid (short read), skipping");
691                None
692            }
693        } else {
694            debug!("malformed uuid, skipping");
695            None
696        }
697    } else {
698        None
699    };
700    assert!(offset <= size);
701    Ok(BoxHeader { name, size, offset, uuid })
702}
703
704/// Parse the extra header fields for a full box.
705fn read_fullbox_extra<T: ReadBytesExt>(src: &mut T) -> Result<(u8, u32)> {
706    let version = src.read_u8()?;
707    let flags_a = src.read_u8()?;
708    let flags_b = src.read_u8()?;
709    let flags_c = src.read_u8()?;
710    Ok((
711        version,
712        u32::from(flags_a) << 16 | u32::from(flags_b) << 8 | u32::from(flags_c),
713    ))
714}
715
716// Parse the extra fields for a full box whose flag fields must be zero.
717fn read_fullbox_version_no_flags<T: ReadBytesExt>(src: &mut T) -> Result<u8> {
718    let (version, flags) = read_fullbox_extra(src)?;
719
720    if flags != 0 {
721        return Err(Error::Unsupported("expected flags to be 0"));
722    }
723
724    Ok(version)
725}
726
727/// Skip over the entire contents of a box.
728fn skip_box_content<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<()> {
729    // Skip the contents of unknown chunks.
730    let to_skip = {
731        let header = src.get_header();
732        debug!("{header:?} (skipped)");
733        header
734            .size
735            .checked_sub(header.offset)
736            .ok_or(Error::InvalidData("header offset > size"))?
737    };
738    assert_eq!(to_skip, src.bytes_left());
739    skip(src, to_skip)
740}
741
742/// Skip over the remain data of a box.
743fn skip_box_remain<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<()> {
744    let remain = {
745        let header = src.get_header();
746        let len = src.bytes_left();
747        debug!("remain {len} (skipped) in {header:?}");
748        len
749    };
750    skip(src, remain)
751}
752
753/// Read the contents of an AVIF file
754///
755/// Metadata is accumulated and returned in [`AvifData`] struct,
756pub fn read_avif<T: Read>(f: &mut T) -> Result<AvifData> {
757    let mut f = OffsetReader::new(f);
758
759    let mut iter = BoxIter::new(&mut f);
760
761    // 'ftyp' box must occur first; see ISO 14496-12:2015 § 4.3.1
762    if let Some(mut b) = iter.next_box()? {
763        if b.head.name == BoxType::FileTypeBox {
764            let ftyp = read_ftyp(&mut b)?;
765            if ftyp.major_brand != b"avif" {
766                if ftyp.major_brand == b"avis" {
767                    return Err(Error::Unsupported("Animated AVIF is not supported. Please use real AV1 videos instead."));
768                }
769                warn!("major_brand: {}", ftyp.major_brand);
770                return Err(Error::InvalidData("ftyp must be 'avif'"));
771            }
772        } else {
773            return Err(Error::InvalidData("'ftyp' box must occur first"));
774        }
775    }
776
777    let mut meta = None;
778    let mut mdats = TryVec::new();
779
780    while let Some(mut b) = iter.next_box()? {
781        match b.head.name {
782            BoxType::MetadataBox => {
783                if meta.is_some() {
784                    return Err(Error::InvalidData("There should be zero or one meta boxes per ISO 14496-12:2015 § 8.11.1.1"));
785                }
786                meta = Some(read_avif_meta(&mut b)?);
787            },
788            BoxType::MediaDataBox => {
789                if b.bytes_left() > 0 {
790                    let offset = b.offset();
791                    let data = b.read_into_try_vec()?;
792                    mdats.push(MediaDataBox { offset, data })?;
793                }
794            },
795            _ => skip_box_content(&mut b)?,
796        }
797
798        check_parser_state(&b.content)?;
799    }
800
801    let meta = meta.ok_or(Error::InvalidData("missing meta"))?;
802
803    let alpha_item_id = meta
804        .item_references
805        .iter()
806        // Auxiliary image for the primary image
807        .filter(|iref| {
808            iref.to_item_id == meta.primary_item_id
809                && iref.from_item_id != meta.primary_item_id
810                && iref.item_type == b"auxl"
811        })
812        .map(|iref| iref.from_item_id)
813        // which has the alpha property
814        .find(|&item_id| {
815            meta.properties.iter().any(|prop| {
816                prop.item_id == item_id
817                    && match &prop.property {
818                        ItemProperty::AuxiliaryType(urn) => {
819                            urn.type_subtype().0 == b"urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"
820                        }
821                        _ => false,
822                    }
823            })
824        });
825
826    // Extract HDR metadata properties for the primary item
827    let mut content_light_level = None;
828    let mut mastering_display = None;
829    for prop in meta.properties.iter() {
830        if prop.item_id == meta.primary_item_id {
831            match &prop.property {
832                ItemProperty::ContentLightLevel(cll) => content_light_level = Some(*cll),
833                ItemProperty::MasteringDisplayColourVolume(mdcv) => mastering_display = Some(*mdcv),
834                _ => {},
835            }
836        }
837    }
838
839    let mut context = AvifData {
840        premultiplied_alpha: alpha_item_id.is_some_and(|alpha_item_id| {
841            meta.item_references.iter().any(|iref| {
842                iref.from_item_id == meta.primary_item_id
843                    && iref.to_item_id == alpha_item_id
844                    && iref.item_type == b"prem"
845            })
846        }),
847        content_light_level,
848        mastering_display,
849        ..Default::default()
850    };
851
852    // load data of relevant items
853    for loc in meta.iloc_items.iter() {
854        let item_data = if loc.item_id == meta.primary_item_id {
855            &mut context.primary_item
856        } else if Some(loc.item_id) == alpha_item_id {
857            context.alpha_item.get_or_insert_with(TryVec::new)
858        } else {
859            continue;
860        };
861
862        if loc.construction_method != ConstructionMethod::File {
863            return Err(Error::Unsupported("unsupported construction_method"));
864        }
865        for extent in loc.extents.iter() {
866            let mut found = false;
867            // try to find an overlapping mdat
868            for mdat in mdats.iter_mut() {
869                if mdat.matches_extent(&extent.extent_range) {
870                    item_data.append(&mut mdat.data)?;
871                    found = true;
872                    break;
873                } else if mdat.contains_extent(&extent.extent_range) {
874                    mdat.read_extent(&extent.extent_range, item_data)?;
875                    found = true;
876                    break;
877                }
878            }
879            if !found {
880                return Err(Error::InvalidData("iloc contains an extent that is not in mdat"));
881            }
882        }
883    }
884
885    Ok(context)
886}
887
888/// Parse a metadata box in the context of an AVIF
889/// Currently requires the primary item to be an av01 item type and generates
890/// an error otherwise.
891/// See ISO 14496-12:2015 § 8.11.1
892fn read_avif_meta<T: Read + Offset>(src: &mut BMFFBox<'_, T>) -> Result<AvifInternalMeta> {
893    let version = read_fullbox_version_no_flags(src)?;
894
895    if version != 0 {
896        return Err(Error::Unsupported("unsupported meta version"));
897    }
898
899    let mut primary_item_id = None;
900    let mut item_infos = None;
901    let mut iloc_items = None;
902    let mut item_references = TryVec::new();
903    let mut properties = TryVec::new();
904
905    let mut iter = src.box_iter();
906    while let Some(mut b) = iter.next_box()? {
907        match b.head.name {
908            BoxType::ItemInfoBox => {
909                if item_infos.is_some() {
910                    return Err(Error::InvalidData("There should be zero or one iinf boxes per ISO 14496-12:2015 § 8.11.6.1"));
911                }
912                item_infos = Some(read_iinf(&mut b)?);
913            },
914            BoxType::ItemLocationBox => {
915                if iloc_items.is_some() {
916                    return Err(Error::InvalidData("There should be zero or one iloc boxes per ISO 14496-12:2015 § 8.11.3.1"));
917                }
918                iloc_items = Some(read_iloc(&mut b)?);
919            },
920            BoxType::PrimaryItemBox => {
921                if primary_item_id.is_some() {
922                    return Err(Error::InvalidData("There should be zero or one iloc boxes per ISO 14496-12:2015 § 8.11.4.1"));
923                }
924                primary_item_id = Some(read_pitm(&mut b)?);
925            },
926            BoxType::ImageReferenceBox => {
927                item_references.append(&mut read_iref(&mut b)?)?;
928            },
929            BoxType::ImagePropertiesBox => {
930                properties = read_iprp(&mut b)?;
931            },
932            _ => skip_box_content(&mut b)?,
933        }
934
935        check_parser_state(&b.content)?;
936    }
937
938    let primary_item_id = primary_item_id.ok_or(Error::InvalidData("Required pitm box not present in meta box"))?;
939
940    let item_infos = item_infos.ok_or(Error::InvalidData("iinf missing"))?;
941
942    if let Some(item_info) = item_infos.iter().find(|x| x.item_id == primary_item_id) {
943        if item_info.item_type != b"av01" {
944            if item_info.item_type == b"grid" {
945                return Err(Error::Unsupported("Grid-based AVIF collage is not supported"));
946            }
947            warn!("primary_item_id type: {}", item_info.item_type);
948            return Err(Error::InvalidData("primary_item_id type is not av01"));
949        }
950    } else {
951        return Err(Error::InvalidData("primary_item_id not present in iinf box"));
952    }
953
954    Ok(AvifInternalMeta {
955        properties,
956        item_references,
957        primary_item_id,
958        iloc_items: iloc_items.ok_or(Error::InvalidData("iloc missing"))?,
959    })
960}
961
962/// Parse a Primary Item Box
963/// See ISO 14496-12:2015 § 8.11.4
964fn read_pitm<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<u32> {
965    let version = read_fullbox_version_no_flags(src)?;
966
967    let item_id = match version {
968        0 => be_u16(src)?.into(),
969        1 => be_u32(src)?,
970        _ => return Err(Error::Unsupported("unsupported pitm version")),
971    };
972
973    Ok(item_id)
974}
975
976/// Parse an Item Information Box
977/// See ISO 14496-12:2015 § 8.11.6
978fn read_iinf<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<ItemInfoEntry>> {
979    let version = read_fullbox_version_no_flags(src)?;
980
981    match version {
982        0 | 1 => (),
983        _ => return Err(Error::Unsupported("unsupported iinf version")),
984    }
985
986    let entry_count = if version == 0 {
987        be_u16(src)?.to_usize()
988    } else {
989        be_u32(src)?.to_usize()
990    };
991    let mut item_infos = TryVec::with_capacity(entry_count)?;
992
993    let mut iter = src.box_iter();
994    while let Some(mut b) = iter.next_box()? {
995        if b.head.name != BoxType::ItemInfoEntry {
996            return Err(Error::InvalidData("iinf box should contain only infe boxes"));
997        }
998
999        item_infos.push(read_infe(&mut b)?)?;
1000
1001        check_parser_state(&b.content)?;
1002    }
1003
1004    Ok(item_infos)
1005}
1006
1007/// Parse an Item Info Entry
1008/// See ISO 14496-12:2015 § 8.11.6.2
1009fn read_infe<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ItemInfoEntry> {
1010    // According to the standard, it seems the flags field should be 0, but
1011    // at least one sample AVIF image has a nonzero value.
1012    let (version, _) = read_fullbox_extra(src)?;
1013
1014    // mif1 brand (see ISO 23008-12:2017 § 10.2.1) only requires v2 and 3
1015    let item_id = match version {
1016        2 => be_u16(src)?.into(),
1017        3 => be_u32(src)?,
1018        _ => return Err(Error::Unsupported("unsupported version in 'infe' box")),
1019    };
1020
1021    let item_protection_index = be_u16(src)?;
1022
1023    if item_protection_index != 0 {
1024        return Err(Error::Unsupported("protected items (infe.item_protection_index != 0) are not supported"));
1025    }
1026
1027    let item_type = FourCC::from(be_u32(src)?);
1028    debug!("infe item_id {item_id} item_type: {item_type}");
1029
1030    // There are some additional fields here, but they're not of interest to us
1031    skip_box_remain(src)?;
1032
1033    Ok(ItemInfoEntry { item_id, item_type })
1034}
1035
1036fn read_iref<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<SingleItemTypeReferenceBox>> {
1037    let mut item_references = TryVec::new();
1038    let version = read_fullbox_version_no_flags(src)?;
1039    if version > 1 {
1040        return Err(Error::Unsupported("iref version"));
1041    }
1042
1043    let mut iter = src.box_iter();
1044    while let Some(mut b) = iter.next_box()? {
1045        let from_item_id = if version == 0 {
1046            be_u16(&mut b)?.into()
1047        } else {
1048            be_u32(&mut b)?
1049        };
1050        let reference_count = be_u16(&mut b)?;
1051        for _ in 0..reference_count {
1052            let to_item_id = if version == 0 {
1053                be_u16(&mut b)?.into()
1054            } else {
1055                be_u32(&mut b)?
1056            };
1057            if from_item_id == to_item_id {
1058                return Err(Error::InvalidData("from_item_id and to_item_id must be different"));
1059            }
1060            item_references.push(SingleItemTypeReferenceBox {
1061                item_type: b.head.name.into(),
1062                from_item_id,
1063                to_item_id,
1064            })?;
1065        }
1066        check_parser_state(&b.content)?;
1067    }
1068    Ok(item_references)
1069}
1070
1071fn read_iprp<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<AssociatedProperty>> {
1072    let mut iter = src.box_iter();
1073    let mut properties = TryVec::new();
1074    let mut associations = TryVec::new();
1075
1076    while let Some(mut b) = iter.next_box()? {
1077        match b.head.name {
1078            BoxType::ItemPropertyContainerBox => {
1079                properties = read_ipco(&mut b)?;
1080            },
1081            BoxType::ItemPropertyAssociationBox => {
1082                associations = read_ipma(&mut b)?;
1083            },
1084            _ => return Err(Error::InvalidData("unexpected ipco child")),
1085        }
1086    }
1087
1088    let mut associated = TryVec::new();
1089    for a in associations {
1090        let index = match a.property_index {
1091            0 => continue,
1092            x => x as usize - 1,
1093        };
1094        if let Some(prop) = properties.get(index)
1095            && *prop != ItemProperty::Unsupported
1096        {
1097            associated.push(AssociatedProperty {
1098                item_id: a.item_id,
1099                property: prop.try_clone()?,
1100            })?;
1101        }
1102    }
1103    Ok(associated)
1104}
1105
1106#[derive(Debug, PartialEq)]
1107pub(crate) enum ItemProperty {
1108    Channels(ArrayVec<u8, 16>),
1109    AuxiliaryType(AuxiliaryTypeProperty),
1110    ContentLightLevel(ContentLightLevel),
1111    MasteringDisplayColourVolume(MasteringDisplayColourVolume),
1112    Unsupported,
1113}
1114
1115impl TryClone for ItemProperty {
1116    fn try_clone(&self) -> Result<Self, TryReserveError> {
1117        Ok(match self {
1118            Self::Channels(val) => Self::Channels(val.clone()),
1119            Self::AuxiliaryType(val) => Self::AuxiliaryType(val.try_clone()?),
1120            Self::ContentLightLevel(val) => Self::ContentLightLevel(*val),
1121            Self::MasteringDisplayColourVolume(val) => Self::MasteringDisplayColourVolume(*val),
1122            Self::Unsupported => Self::Unsupported,
1123        })
1124    }
1125}
1126
1127struct Association {
1128    item_id: u32,
1129    #[allow(unused)]
1130    essential: bool,
1131    property_index: u16,
1132}
1133
1134pub(crate) struct AssociatedProperty {
1135    pub item_id: u32,
1136    pub property: ItemProperty,
1137}
1138
1139fn read_ipma<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<Association>> {
1140    let (version, flags) = read_fullbox_extra(src)?;
1141
1142    let mut associations = TryVec::new();
1143
1144    let entry_count = be_u32(src)?;
1145    for _ in 0..entry_count {
1146        let item_id = if version == 0 {
1147            be_u16(src)?.into()
1148        } else {
1149            be_u32(src)?
1150        };
1151        let association_count = src.read_u8()?;
1152        for _ in 0..association_count {
1153            let num_association_bytes = if flags & 1 == 1 { 2 } else { 1 };
1154            let association = &mut [0; 2][..num_association_bytes];
1155            src.read_exact(association)?;
1156            let mut association = BitReader::new(association);
1157            let essential = association.read_bool()?;
1158            let property_index = association.read_u16(association.remaining().try_into()?)?;
1159            associations.push(Association {
1160                item_id,
1161                essential,
1162                property_index,
1163            })?;
1164        }
1165    }
1166    Ok(associations)
1167}
1168
1169fn read_ipco<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<ItemProperty>> {
1170    let mut properties = TryVec::new();
1171
1172    let mut iter = src.box_iter();
1173    while let Some(mut b) = iter.next_box()? {
1174        // Must push for every property to have correct index for them
1175        properties.push(match b.head.name {
1176            BoxType::PixelInformationBox => ItemProperty::Channels(read_pixi(&mut b)?),
1177            BoxType::AuxiliaryTypeProperty => ItemProperty::AuxiliaryType(read_auxc(&mut b)?),
1178            BoxType::ContentLightLevelBox => ItemProperty::ContentLightLevel(read_clli(&mut b)?),
1179            BoxType::MasteringDisplayColourVolumeBox => ItemProperty::MasteringDisplayColourVolume(read_mdcv(&mut b)?),
1180            _ => {
1181                skip_box_remain(&mut b)?;
1182                ItemProperty::Unsupported
1183            },
1184        })?;
1185    }
1186    Ok(properties)
1187}
1188
1189fn read_pixi<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ArrayVec<u8, 16>> {
1190    let version = read_fullbox_version_no_flags(src)?;
1191    if version != 0 {
1192        return Err(Error::Unsupported("pixi version"));
1193    }
1194
1195    let num_channels = usize::from(src.read_u8()?);
1196    let mut channels = ArrayVec::new();
1197    channels.extend((0..num_channels.min(channels.capacity())).map(|_| 0));
1198    debug_assert_eq!(num_channels, channels.len());
1199    src.read_exact(&mut channels).map_err(|_| Error::InvalidData("invalid num_channels"))?;
1200
1201    check_parser_state(&src.content)?;
1202    Ok(channels)
1203}
1204
1205#[derive(Debug, PartialEq)]
1206#[doc(hidden)]
1207// this wasn't supposed to be public
1208pub struct AuxiliaryTypeProperty {
1209    aux_data: TryString,
1210}
1211
1212impl AuxiliaryTypeProperty {
1213    #[must_use]
1214    pub fn type_subtype(&self) -> (&[u8], &[u8]) {
1215        let split = self.aux_data.iter().position(|&b| b == b'\0')
1216            .map(|pos| self.aux_data.split_at(pos));
1217        if let Some((aux_type, rest)) = split {
1218            (aux_type, &rest[1..])
1219        } else {
1220            (&self.aux_data, &[])
1221        }
1222    }
1223}
1224
1225impl TryClone for AuxiliaryTypeProperty {
1226    fn try_clone(&self) -> Result<Self, TryReserveError> {
1227        Ok(Self {
1228            aux_data: self.aux_data.try_clone()?,
1229        })
1230    }
1231}
1232
1233fn read_auxc<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<AuxiliaryTypeProperty> {
1234    let version = read_fullbox_version_no_flags(src)?;
1235    if version != 0 {
1236        return Err(Error::Unsupported("auxC version"));
1237    }
1238
1239    let aux_data = src.read_into_try_vec()?;
1240
1241    Ok(AuxiliaryTypeProperty { aux_data })
1242}
1243
1244/// Parse a Content Light Level Information property box (`clli`).
1245/// See ISOBMFF § 12.1.5 / CEA-861.3. NOT a FullBox.
1246fn read_clli<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ContentLightLevel> {
1247    let max_content_light_level = be_u16(src)?;
1248    let max_pic_average_light_level = be_u16(src)?;
1249    skip_box_remain(src)?;
1250    Ok(ContentLightLevel {
1251        max_content_light_level,
1252        max_pic_average_light_level,
1253    })
1254}
1255
1256/// Parse a Mastering Display Colour Volume property box (`mdcv`).
1257/// See ISOBMFF § 12.1.5 / SMPTE ST 2086. NOT a FullBox.
1258fn read_mdcv<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<MasteringDisplayColourVolume> {
1259    let primaries = [
1260        (be_u16(src)?, be_u16(src)?),
1261        (be_u16(src)?, be_u16(src)?),
1262        (be_u16(src)?, be_u16(src)?),
1263    ];
1264    let white_point = (be_u16(src)?, be_u16(src)?);
1265    let max_luminance = be_u32(src)?;
1266    let min_luminance = be_u32(src)?;
1267    skip_box_remain(src)?;
1268    Ok(MasteringDisplayColourVolume {
1269        primaries,
1270        white_point,
1271        max_luminance,
1272        min_luminance,
1273    })
1274}
1275
1276/// Parse an item location box inside a meta box
1277/// See ISO 14496-12:2015 § 8.11.3
1278fn read_iloc<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<ItemLocationBoxItem>> {
1279    let version: IlocVersion = read_fullbox_version_no_flags(src)?.try_into()?;
1280
1281    let iloc = src.read_into_try_vec()?;
1282    let mut iloc = BitReader::new(&iloc);
1283
1284    let offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
1285    let length_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
1286    let base_offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
1287
1288    let index_size: Option<IlocFieldSize> = match version {
1289        IlocVersion::One | IlocVersion::Two => Some(iloc.read_u8(4)?.try_into()?),
1290        IlocVersion::Zero => {
1291            let _reserved = iloc.read_u8(4)?;
1292            None
1293        },
1294    };
1295
1296    let item_count = match version {
1297        IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?,
1298        IlocVersion::Two => iloc.read_u32(32)?,
1299    };
1300
1301    let mut items = TryVec::with_capacity(item_count.to_usize())?;
1302
1303    for _ in 0..item_count {
1304        let item_id = match version {
1305            IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?,
1306            IlocVersion::Two => iloc.read_u32(32)?,
1307        };
1308
1309        // The spec isn't entirely clear how an `iloc` should be interpreted for version 0,
1310        // which has no `construction_method` field. It does say:
1311        // "For maximum compatibility, version 0 of this box should be used in preference to
1312        //  version 1 with `construction_method==0`, or version 2 when possible."
1313        // We take this to imply version 0 can be interpreted as using file offsets.
1314        let construction_method = match version {
1315            IlocVersion::Zero => ConstructionMethod::File,
1316            IlocVersion::One | IlocVersion::Two => {
1317                let _reserved = iloc.read_u16(12)?;
1318                match iloc.read_u16(4)? {
1319                    0 => ConstructionMethod::File,
1320                    1 => ConstructionMethod::Idat,
1321                    2 => return Err(Error::Unsupported("construction_method 'item_offset' is not supported")),
1322                    _ => return Err(Error::InvalidData("construction_method is taken from the set 0, 1 or 2 per ISO 14496-12:2015 § 8.11.3.3")),
1323                }
1324            },
1325        };
1326
1327        let data_reference_index = iloc.read_u16(16)?;
1328
1329        if data_reference_index != 0 {
1330            return Err(Error::Unsupported("external file references (iloc.data_reference_index != 0) are not supported"));
1331        }
1332
1333        let base_offset = iloc.read_u64(base_offset_size.to_bits())?;
1334        let extent_count = iloc.read_u16(16)?;
1335
1336        if extent_count < 1 {
1337            return Err(Error::InvalidData("extent_count must have a value 1 or greater per ISO 14496-12:2015 § 8.11.3.3"));
1338        }
1339
1340        let mut extents = TryVec::with_capacity(extent_count.to_usize())?;
1341
1342        for _ in 0..extent_count {
1343            // Parsed but currently ignored, see `ItemLocationBoxExtent`
1344            let _extent_index = match &index_size {
1345                None | Some(IlocFieldSize::Zero) => None,
1346                Some(index_size) => {
1347                    debug_assert!(version == IlocVersion::One || version == IlocVersion::Two);
1348                    Some(iloc.read_u64(index_size.to_bits())?)
1349                },
1350            };
1351
1352            // Per ISO 14496-12:2015 § 8.11.3.1:
1353            // "If the offset is not identified (the field has a length of zero), then the
1354            //  beginning of the source (offset 0) is implied"
1355            // This behavior will follow from BitReader::read_u64(0) -> 0.
1356            let extent_offset = iloc.read_u64(offset_size.to_bits())?;
1357            let extent_length = iloc.read_u64(length_size.to_bits())?;
1358
1359            // "If the length is not specified, or specified as zero, then the entire length of
1360            //  the source is implied" (ibid)
1361            let start = base_offset
1362                .checked_add(extent_offset)
1363                .ok_or(Error::InvalidData("offset calculation overflow"))?;
1364            let extent_range = if extent_length == 0 {
1365                ExtentRange::ToEnd(RangeFrom { start })
1366            } else {
1367                let end = start
1368                    .checked_add(extent_length)
1369                    .ok_or(Error::InvalidData("end calculation overflow"))?;
1370                ExtentRange::WithLength(Range { start, end })
1371            };
1372
1373            extents.push(ItemLocationBoxExtent { extent_range })?;
1374        }
1375
1376        items.push(ItemLocationBoxItem { item_id, construction_method, extents })?;
1377    }
1378
1379    if iloc.remaining() == 0 {
1380        Ok(items)
1381    } else {
1382        Err(Error::InvalidData("invalid iloc size"))
1383    }
1384}
1385
1386/// Parse an ftyp box.
1387/// See ISO 14496-12:2015 § 4.3
1388fn read_ftyp<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<FileTypeBox> {
1389    let major = be_u32(src)?;
1390    let minor = be_u32(src)?;
1391    let bytes_left = src.bytes_left();
1392    if !bytes_left.is_multiple_of(4) {
1393        return Err(Error::InvalidData("invalid ftyp size"));
1394    }
1395    // Is a brand_count of zero valid?
1396    let brand_count = bytes_left / 4;
1397    let mut brands = TryVec::with_capacity(brand_count.try_into()?)?;
1398    for _ in 0..brand_count {
1399        brands.push(be_u32(src)?.into())?;
1400    }
1401    Ok(FileTypeBox {
1402        major_brand: From::from(major),
1403        minor_version: minor,
1404        compatible_brands: brands,
1405    })
1406}
1407
1408#[cfg_attr(debug_assertions, track_caller)]
1409fn check_parser_state<T>(left: &Take<T>) -> Result<(), Error> {
1410    let limit = left.limit();
1411    if limit == 0 {
1412        Ok(())
1413    } else {
1414        debug_assert_eq!(0, limit, "bad parser state bytes left");
1415        Err(Error::InvalidData("unread box content or bad parser sync"))
1416    }
1417}
1418
1419/// Skip a number of bytes that we don't care to parse.
1420fn skip<T: Read>(src: &mut T, bytes: u64) -> Result<()> {
1421    std::io::copy(&mut src.take(bytes), &mut std::io::sink())?;
1422    Ok(())
1423}
1424
1425fn be_u16<T: ReadBytesExt>(src: &mut T) -> Result<u16> {
1426    src.read_u16::<byteorder::BigEndian>().map_err(From::from)
1427}
1428
1429fn be_u32<T: ReadBytesExt>(src: &mut T) -> Result<u32> {
1430    src.read_u32::<byteorder::BigEndian>().map_err(From::from)
1431}
1432
1433fn be_u64<T: ReadBytesExt>(src: &mut T) -> Result<u64> {
1434    src.read_u64::<byteorder::BigEndian>().map_err(From::from)
1435}