avif_parse/
lib.rs

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