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