Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/mp4parse/src/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 213 kB image not shown  

Quelle  lib.rs   Sprache: unbekannt

 
//! Module for parsing ISO Base Media Format aka video/mp4 streams.

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

// `clippy::upper_case_acronyms` is a nightly-only lint as of 2021-03-15, so we
// allow `clippy::unknown_clippy_lints` to ignore it on stable - but
// `clippy::unknown_clippy_lints` has been renamed in nightly, so we need to
// allow `renamed_and_removed_lints` to ignore a warning for that.
#![allow(renamed_and_removed_lints)]
#![allow(clippy::unknown_clippy_lints)]
#![allow(clippy::upper_case_acronyms)]

#[macro_use]
extern crate log;

extern crate bitreader;
extern crate byteorder;
extern crate fallible_collections;
extern crate num_traits;
use bitreader::{BitReader, ReadInto};
use byteorder::{ReadBytesExt, WriteBytesExt};

use fallible_collections::TryRead;
use fallible_collections::TryReserveError;

use num_traits::Num;
use std::convert::{TryFrom, TryInto as _};
use std::fmt;
use std::io::Cursor;
use std::io::{Read, Take};

#[macro_use]
mod macros;

mod boxes;
use crate::boxes::{BoxType, FourCC};

// Unit tests.
#[cfg(test)]
mod tests;

#[cfg(feature = "unstable-api")]
pub mod unstable;

/// The HEIF image and image collection brand
/// The 'mif1' brand indicates structural requirements on files
/// See HEIF (ISO 23008-12:2017) § 10.2.1
pub const MIF1_BRAND: FourCC = FourCC { value: *b"mif1" };

/// The HEIF image sequence brand
/// The 'msf1' brand indicates structural requirements on files
/// See HEIF (ISO 23008-12:2017) § 10.3.1
pub const MSF1_BRAND: FourCC = FourCC { value: *b"msf1" };

/// The brand to identify AV1 image items
/// The 'avif' brand indicates structural requirements on files
/// See <https://aomediacodec.github.io/av1-avif/#image-and-image-collection-brand>
pub const AVIF_BRAND: FourCC = FourCC { value: *b"avif" };

/// The brand to identify AVIF image sequences
/// The 'avis' brand indicates structural requirements on files
/// See <https://aomediacodec.github.io/av1-avif/#image-and-image-collection-brand>
pub const AVIS_BRAND: FourCC = FourCC { value: *b"avis" };

/// A trait to indicate a type can be infallibly converted to `u64`.
/// This should only be implemented for infallible conversions, so only unsigned types are valid.
trait ToU64 {
    fn to_u64(self) -> u64;
}

/// Statically verify that the platform `usize` can fit within a `u64`.
/// If the size won't fit on the given platform, this will fail at compile time, but if a type
/// which can fail `TryInto<usize>` is used, it may panic.
impl ToU64 for usize {
    fn to_u64(self) -> u64 {
        static_assertions::const_assert!(
            std::mem::size_of::<usize>() <= std::mem::size_of::<u64>()
        );
        self.try_into().expect("usize -> u64 conversion failed")
    }
}

/// A trait to indicate a type can be infallibly converted to `usize`.
/// This should only be implemented for infallible conversions, so only unsigned types are valid.
pub trait ToUsize {
    fn to_usize(self) -> usize;
}

/// Statically verify that the given type can fit within a `usize`.
/// If the size won't fit on the given platform, this will fail at compile time, but if a type
/// which can fail `TryInto<usize>` is used, it may panic.
macro_rules! impl_to_usize_from {
    ( $from_type:ty ) => {
        impl ToUsize for $from_type {
            fn to_usize(self) -> usize {
                static_assertions::const_assert!(
                    std::mem::size_of::<$from_type>() <= std::mem::size_of::<usize>()
                );
                self.try_into().expect(concat!(
                    stringify!($from_type),
                    " -> usize conversion failed"
                ))
            }
        }
    };
}

impl_to_usize_from!(u8);
impl_to_usize_from!(u16);
impl_to_usize_from!(u32);

/// Indicate the current offset (i.e., bytes already read) in a reader
trait Offset {
    fn offset(&self) -> u64;
}

/// Wraps a reader to track the current offset
struct OffsetReader<'a, T: 'a> {
    reader: &'a mut T,
    offset: u64,
}

impl<'a, T> OffsetReader<'a, T> {
    fn new(reader: &'a mut T) -> Self {
        Self { reader, offset: 0 }
    }
}

impl<'a, T> Offset for OffsetReader<'a, T> {
    fn offset(&self) -> u64 {
        self.offset
    }
}

impl<'a, T: Read> Read for OffsetReader<'a, T> {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        let bytes_read = self.reader.read(buf)?;
        trace!("Read {} bytes at offset {}", bytes_read, self.offset);
        self.offset = self
            .offset
            .checked_add(bytes_read.to_u64())
            .expect("total bytes read too large for offset type");
        Ok(bytes_read)
    }
}

pub type TryVec<T> = fallible_collections::TryVec<T>;
pub type TryString = fallible_collections::TryVec<u8>;
pub type TryHashMap<K, V> = fallible_collections::TryHashMap<K, V>;
pub type TryBox<T> = fallible_collections::TryBox<T>;

// To ensure we don't use stdlib allocating types by accident
#[allow(dead_code)]
struct Vec;
#[allow(dead_code)]
struct Box;
#[allow(dead_code)]
struct HashMap;
#[allow(dead_code)]
struct String;

/// The return value to the C API
/// Any detail that needs to be communicated to the caller must be encoded here
/// since the [`Error`] type's associated data is part of the FFI.
#[repr(C)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum Status {
    Ok = 0,
    BadArg = 1,
    Invalid = 2,
    Unsupported = 3,
    Eof = 4,
    Io = 5,
    Oom = 6,
    A1lxEssential,
    A1opNoEssential,
    AlacBadMagicCookieSize,
    AlacFlagsNonzero,
    Av1cMissing,
    BitReaderError,
    BoxBadSize,
    BoxBadWideSize,
    CheckParserStateErr,
    ColrBadQuantity,
    ColrBadSize,
    ColrBadType,
    ColrReservedNonzero,
    ConstructionMethod,
    CttsBadSize,
    CttsBadVersion,
    DflaBadMetadataBlockSize,
    DflaFlagsNonzero,
    DflaMissingMetadata,
    DflaStreamInfoBadSize,
    DflaStreamInfoNotFirst,
    DopsChannelMappingWriteErr,
    DopsOpusHeadWriteErr,
    ElstBadVersion,
    EsdsBadAudioSampleEntry,
    EsdsBadDescriptor,
    EsdsDecSpecificIntoTagQuantity,
    FtypBadSize,
    FtypNotFirst,
    HdlrNameNoNul,
    HdlrNameNotUtf8,
    HdlrNotFirst,
    HdlrPredefinedNonzero,
    HdlrReservedNonzero,
    HdlrTypeNotPict,
    HdlrUnsupportedVersion,
    HdrlBadQuantity,
    IdatBadQuantity,
    IdatMissing,
    IinfBadChild,
    IinfBadQuantity,
    IlocBadConstructionMethod,
    IlocBadExtent,
    IlocBadExtentCount,
    IlocBadFieldSize,
    IlocBadQuantity,
    IlocBadSize,
    IlocDuplicateItemId,
    IlocNotFound,
    IlocOffsetOverflow,
    ImageItemType,
    InfeFlagsNonzero,
    InvalidUtf8,
    IpcoIndexOverflow,
    IpmaBadIndex,
    IpmaBadItemOrder,
    IpmaBadQuantity,
    IpmaBadVersion,
    IpmaDuplicateItemId,
    IpmaFlagsNonzero,
    IpmaIndexZeroNoEssential,
    IpmaTooBig,
    IpmaTooSmall,
    IprpBadChild,
    IprpBadQuantity,
    IprpConflict,
    IrefBadQuantity,
    IrefRecursion,
    IspeMissing,
    ItemTypeMissing,
    LselNoEssential,
    MdhdBadTimescale,
    MdhdBadVersion,
    MehdBadVersion,
    MetaBadQuantity,
    MissingAvifOrAvisBrand,
    MissingMif1Brand,
    MoovBadQuantity,
    MoovMissing,
    MultipleAlpha,
    MvhdBadTimescale,
    MvhdBadVersion,
    NoImage,
    PitmBadQuantity,
    PitmMissing,
    PitmNotFound,
    PixiBadChannelCount,
    PixiMissing,
    PsshSizeOverflow,
    ReadBufErr,
    SchiQuantity,
    StsdBadAudioSampleEntry,
    StsdBadVideoSampleEntry,
    TkhdBadVersion,
    TxformBeforeIspe,
    TxformNoEssential,
    TxformOrder,
}

#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Feature {
    A1lx,
    A1op,
    Auxc,
    Av1c,
    Avis,
    Clap,
    Colr,
    Grid,
    Imir,
    Ipro,
    Irot,
    Ispe,
    Lsel,
    Pasp,
    Pixi,
}

impl Feature {
    fn supported(self) -> bool {
        match self {
            Self::Auxc
            | Self::Av1c
            | Self::Avis
            | Self::Colr
            | Self::Imir
            | Self::Irot
            | Self::Ispe
            | Self::Pasp
            | Self::Pixi => true,
            Self::A1lx | Self::A1op | Self::Clap | Self::Grid | Self::Ipro | Self::Lsel => false,
        }
    }
}

impl TryFrom<&ItemProperty> for Feature {
    type Error = Error;

    fn try_from(item_property: &ItemProperty) -> Result<Self, Self::Error> {
        Ok(match item_property {
            ItemProperty::AuxiliaryType(_) => Self::Auxc,
            ItemProperty::AV1Config(_) => Self::Av1c,
            ItemProperty::Channels(_) => Self::Pixi,
            ItemProperty::CleanAperture => Self::Clap,
            ItemProperty::Colour(_) => Self::Colr,
            ItemProperty::ImageSpatialExtents(_) => Self::Ispe,
            ItemProperty::LayeredImageIndexing => Self::A1lx,
            ItemProperty::LayerSelection => Self::Lsel,
            ItemProperty::Mirroring(_) => Self::Imir,
            ItemProperty::OperatingPointSelector => Self::A1op,
            ItemProperty::PixelAspectRatio(_) => Self::Pasp,
            ItemProperty::Rotation(_) => Self::Irot,
            item_property => {
                error!("No known Feature variant for {:?}", item_property);
                return Err(Error::Unsupported("missing Feature fox ItemProperty"));
            }
        })
    }
}

/// A collection to indicate unsupported features that were encountered during
/// parsing. Since the default behavior for many such features is to ignore
/// them, this often not fatal and there may be several to report.
#[derive(Debug, Default)]
pub struct UnsupportedFeatures(u32);

impl UnsupportedFeatures {
    pub fn new() -> Self {
        Self(0x0)
    }

    pub fn into_bitfield(&self) -> u32 {
        self.0
    }

    fn feature_to_bitfield(feature: Feature) -> u32 {
        let index = feature as usize;
        assert!(
            u8::BITS.to_usize() * std::mem::size_of::<Self>() > index,
            "You're gonna need a bigger bitfield"
        );
        let bitfield = 1u32 << index;
        assert_eq!(bitfield.count_ones(), 1);
        bitfield
    }

    pub fn insert(&mut self, feature: Feature) {
        warn!("Unsupported feature: {:?}", feature);
        self.0 |= Self::feature_to_bitfield(feature);
    }

    pub fn contains(&self, feature: Feature) -> bool {
        self.0 & Self::feature_to_bitfield(feature) != 0x0
    }

    pub fn is_empty(&self) -> bool {
        self.0 == 0x0
    }
}

impl<T> From<Status> for Result<T> {
    /// A convenience method to enable shortcuts like
    /// ```
    /// # use mp4parse::{Result,Status};
    /// # let _: Result<()> =
    /// Status::MissingAvifOrAvisBrand.into();
    /// ```
    /// instead of
    /// ```
    /// # use mp4parse::{Error,Result,Status};
    /// # let _: Result<()> =
    /// Err(Error::from(Status::MissingAvifOrAvisBrand));
    /// ```
    /// Note that `Status::Ok` can't be supported this way and will panic.
    fn from(parse_status: Status) -> Self {
        match parse_status {
            Status::Ok => panic!("Can't determine Ok(_) inner value from Status"),
            err_status => Err(err_status.into()),
        }
    }
}

/// For convenience of creating an error for an unsupported feature which we
/// want to communicate the specific feature back to the C API caller
impl From<Status> for Error {
    fn from(parse_status: Status) -> Self {
        match parse_status {
            Status::Ok
            | Status::BadArg
            | Status::Invalid
            | Status::Unsupported
            | Status::Eof
            | Status::Io
            | Status::Oom => {
                panic!("Status -> Error is only for Status:InvalidData errors")
            }
            _ => Self::InvalidData(parse_status),
        }
    }
}

impl From<Status> for &str {
    fn from(status: Status) -> Self {
        match status {
            Status::Ok
            | Status::BadArg
            | Status::Invalid
            | Status::Unsupported
            | Status::Eof
            | Status::Io
            | Status::Oom => {
                panic!("Status -> Error is only for specific parsing errors")
            }
            Status::A1lxEssential => {
                "AV1LayeredImageIndexingProperty (a1lx) shall not be marked as essential \
                 per https://aomediacodec.github.io/av1-avif/#layered-image-indexing-property-description"
            }
            Status::A1opNoEssential => {
                "OperatingPointSelectorProperty (a1op) shall be marked as essential \
                 per https://aomediacodec.github.io/av1-avif/#operating-point-selector-property-description"
            }
            Status::AlacBadMagicCookieSize => {
                "ALACSpecificBox magic cookie is the wrong size"
            }
            Status::AlacFlagsNonzero => {
                "no-zero alac (ALAC) flags"
            }
            Status::Av1cMissing => {
                "One AV1 Item Configuration Property (av1C) is mandatory for an \
                 image item of type 'av01' \
                 per AVIF specification § 2.2.1"
            }
            Status::BitReaderError => {
                "Bitwise read failed"
            }
            Status::BoxBadSize => {
                "malformed size"
            }
            Status::BoxBadWideSize => {
                "malformed wide size"
            }
            Status::CheckParserStateErr => {
                "unread box content or bad parser sync"
            }
            Status::ColrBadQuantity => {
                "Each item shall have at most one property association with a
                 ColourInformationBox (colr) for a given value of colour_type \
                 per HEIF (ISO/IEC DIS 23008-12) § 6.5.5.1"
            }
            Status::ColrBadSize => {
                "Unexpected size for colr box"
            }
            Status::ColrBadType => {
                "Unsupported colour_type for ColourInformationBox"
            }
            Status::ColrReservedNonzero => {
                "The 7 reserved bits at the end of the ColourInformationBox \
                 for colour_type == 'nclx' must be 0 \
                 per ISOBMFF (ISO 14496-12:2020) § 12.1.5.2"
            }
            Status::ConstructionMethod => {
                "construction_method shall be 0 (file) or 1 (idat) per MIAF (ISO 23000-22:2019) § 7.2.1.7"
            }
            Status::CttsBadSize => {
                "insufficient data in 'ctts' box"
            }
            Status::CttsBadVersion => {
                "unsupported version in 'ctts' box"
            }
            Status::DflaBadMetadataBlockSize => {
                "FLACMetadataBlock larger than parent box"
            }
            Status::DflaFlagsNonzero => {
                "no-zero dfLa (FLAC) flags"
            }
            Status::DflaMissingMetadata => {
                "FLACSpecificBox missing metadata"
            }
            Status::DflaStreamInfoBadSize => {
                "FLACSpecificBox STREAMINFO block is the wrong size"
            }
            Status::DflaStreamInfoNotFirst => {
                "FLACSpecificBox must have STREAMINFO metadata first"
            }
            Status::DopsChannelMappingWriteErr => {
                "Couldn't write channel mapping table data."
            }
            Status::DopsOpusHeadWriteErr => {
                "Couldn't write OpusHead tag."
            }
            Status::ElstBadVersion => {
                "unhandled elst version"
            }
            Status::EsdsBadAudioSampleEntry => {
                "malformed audio sample entry"
            }
            Status::EsdsBadDescriptor => {
                "Invalid descriptor."
            }
            Status::EsdsDecSpecificIntoTagQuantity => {
                "There can be only one DecSpecificInfoTag descriptor"
            }
            Status::FtypBadSize => {
                "invalid ftyp size"
            }
            Status::FtypNotFirst => {
                "The FileTypeBox shall be placed as early as possible in the file \
                 per ISOBMFF (ISO 14496-12:2020) § 4.3.1"
            }
            Status::HdlrNameNoNul => {
                "The HandlerBox 'name' field shall be null-terminated \
                 per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2"
            }
            Status::HdlrNameNotUtf8 => {
                "The HandlerBox 'name' field shall be valid utf8 \
                 per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2"
            }
            Status::HdlrNotFirst => {
                "The HandlerBox shall be the first contained box within the MetaBox \
                 per MIAF (ISO 23000-22:2019) § 7.2.1.5"
            }
            Status::HdlrPredefinedNonzero => {
                "The HandlerBox 'pre_defined' field shall be 0 \
                 per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2"
            }
            Status::HdlrReservedNonzero => {
                "The HandlerBox 'reserved' fields shall be 0 \
                 per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2"
            }
            Status::HdlrTypeNotPict => {
                "The HandlerBox handler_type must be 'pict' \
                 per MIAF (ISO 23000-22:2019) § 7.2.1.5"
            }
            Status::HdlrUnsupportedVersion => {
                "The HandlerBox version shall be 0 (zero) \
                 per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2"
            }
            Status::HdrlBadQuantity => {
                "There shall be exactly one hdlr box \
                 per ISOBMFF (ISO 14496-12:2020) § 8.4.3.1"
            }
            Status::IdatBadQuantity => {
                "There shall be zero or one idat boxes \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.11"
            }
            Status::IdatMissing => {
                "ItemLocationBox (iloc) construction_method indicates 1 (idat), \
                 but no idat box is present."
            }
            Status::IinfBadChild => {
                "iinf box shall contain only infe boxes \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.6.2"
            }
            Status::IinfBadQuantity => {
                "There shall be zero or one iinf boxes \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.6.1"
            }
            Status::IlocBadConstructionMethod => {
                "construction_method is taken from the set 0, 1 or 2 \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.3.3"
            }
            Status::IlocBadExtent => {
                "extent_count != 1 requires explicit offset and length \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.3.3"
            }
            Status::IlocBadExtentCount => {
                "extent_count must have a value 1 or greater \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.3.3"
            }
            Status::IlocBadFieldSize => {
                "value must be in the set {0, 4, 8}"
            }
            Status::IlocBadQuantity => {
                "There shall be zero or one iloc boxes \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.3.1"
            }
            Status::IlocBadSize => {
                "invalid iloc size"
            }
            Status::IlocDuplicateItemId => {
                "duplicate item_ID in iloc"
            }
            Status::IlocNotFound => {
                "ItemLocationBox (iloc) contains an extent not present in any mdat or idat box"
            }
            Status::IlocOffsetOverflow => {
                "offset calculation overflow"
            }
            Status::ImageItemType => {
                "Image item type is neither 'av01' nor 'grid'"
            }
            Status::InfeFlagsNonzero => {
                "'infe' flags field shall be 0 \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.6.2"
            }
            Status::InvalidUtf8 => {
                "invalid utf8"
            }
            Status::IpcoIndexOverflow => {
                "ipco index overflow"
            }
            Status::IpmaBadIndex => {
                "Invalid property index in ipma"
            }
            Status::IpmaBadItemOrder => {
                "Each ItemPropertyAssociation box shall be ordered by increasing item_ID"
            }
            Status::IpmaBadQuantity => {
                "There shall be at most one ItemPropertyAssociationbox with a given pair of \
                 values of version and flags \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.14.1"
            }
            Status::IpmaBadVersion => {
                "The ipma version 0 should be used unless 32-bit item_ID values are needed \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.14.1"
            }
            Status::IpmaDuplicateItemId => {
                "There shall be at most one occurrence of a given item_ID, \
                 in the set of ItemPropertyAssociationBox boxes \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.14.1"
            }
            Status::IpmaFlagsNonzero => {
                "Unless there are more than 127 properties in the ItemPropertyContainerBox, \
                 flags should be equal to 0 \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.14.1"
            }
            Status::IpmaIndexZeroNoEssential => {
                "the essential indicator shall be 0 for property index 0 \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.14.3"
            }
            Status::IpmaTooBig => {
                "ipma box exceeds maximum size for entry_count"
            }
            Status::IpmaTooSmall => {
                "ipma box below minimum size for entry_count"
            }
            Status::IprpBadChild => {
                "unexpected iprp child"
            }
            Status::IprpBadQuantity => {
                "There shall be zero or one iprp boxes \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.14.1"
            }
            Status::IprpConflict => {
                "conflicting item property values"
            }
            Status::IrefBadQuantity => {
                "There shall be zero or one iref boxes \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.12.1"
            }
            Status::IrefRecursion => {
                "from_item_id and to_item_id must be different"
            }
            Status::IspeMissing => {
                "Missing 'ispe' property for image item, required \
                 per HEIF (ISO/IEC 23008-12:2017) § 6.5.3.1"
            }
            Status::ItemTypeMissing => {
                "No ItemInfoEntry for item_ID"
            }
            Status::LselNoEssential => {
                "LayerSelectorProperty (lsel) shall be marked as essential \
                 per HEIF (ISO/IEC 23008-12:2017) § 6.5.11.1"
            }
            Status::MdhdBadTimescale => {
                "zero timescale in mdhd"
            }
            Status::MdhdBadVersion => {
                "unhandled mdhd version"
            }
            Status::MehdBadVersion => {
                "unhandled mehd version"
            }
            Status::MetaBadQuantity => {
                "There should be zero or one meta boxes \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.1.1"
            }
            Status::MissingAvifOrAvisBrand => {
                "The file shall list 'avif' or 'avis' in the compatible_brands field
                 of the FileTypeBox \
                 per https://aomediacodec.github.io/av1-avif/#file-constraints"
            }
            Status::MissingMif1Brand => {
                "The FileTypeBox should contain 'mif1' in the compatible_brands list \
                 per MIAF (ISO 23000-22:2019/Amd. 2:2021) § 7.2.1.2"
            }
            Status::MoovBadQuantity => {
                "Multiple moov boxes found; \
                 files with avis or msf1 brands shall contain exactly one moov box \
                 per ISOBMFF (ISO 14496-12:2020) § 8.2.1.1"
            }
            Status::MoovMissing => {
                "No moov box found; \
                 files with avis or msf1 brands shall contain exactly one moov box \
                 per ISOBMFF (ISO 14496-12:2020) § 8.2.1.1"
            }
            Status::MultipleAlpha => {
                "multiple alpha planes"
            }
            Status::MvhdBadTimescale => {
                "zero timescale in mvhd"
            }
            Status::MvhdBadVersion => {
                "unhandled mvhd version"
            }
            Status::NoImage => "No primary image or image sequence found",
            Status::PitmBadQuantity => {
                "There shall be zero or one pitm boxes \
                 per ISOBMFF (ISO 14496-12:2020) § 8.11.4.1"
            }
            Status::PitmMissing => {
                "Missing required PrimaryItemBox (pitm), required \
                 per HEIF (ISO/IEC 23008-12:2017) § 10.2.1"
            }
            Status::PitmNotFound => {
                "PrimaryItemBox (pitm) referenced an item ID that was not present"
            }
            Status::PixiBadChannelCount => {
                "invalid num_channels"
            }
            Status::PixiMissing => {
                "The pixel information property shall be associated with every image \
                 that is displayable (not hidden) \
                 per MIAF (ISO/IEC 23000-22:2019) specification § 7.3.6.6"
            }
            Status::PsshSizeOverflow => {
                "overflow in read_pssh"
            }
            Status::ReadBufErr => {
                "failed buffer read"
            }
            Status::SchiQuantity => {
                "tenc box should be only one at most in sinf box"
            }
            Status::StsdBadAudioSampleEntry => {
                "malformed audio sample entry"
            }
            Status::StsdBadVideoSampleEntry => {
                "malformed video sample entry"
            }
            Status::TkhdBadVersion => {
                "unhandled tkhd version"
            }
            Status::TxformBeforeIspe => {
                "Every image item shall be associated with one property of \
                 type ImageSpatialExtentsProperty (ispe), prior to the \
                 association of all transformative properties. \
                 per HEIF (ISO/IEC 23008-12:2017) § 6.5.3.1"
            }
            Status::TxformNoEssential => {
                "All transformative properties associated with coded and \
                 derived images required or conditionally required by this \
                 document shall be marked as essential \
                 per MIAF (ISO 23000-22:2019) § 7.3.9"
            }
            Status::TxformOrder => {
                "These properties, if used, shall be indicated to be applied \
                 in the following order: clean aperture first, then rotation, \
                 then mirror. \
                 per MIAF (ISO/IEC 23000-22:2019) § 7.3.6.7"
            }
        }
    }
}

impl From<Error> for Status {
    fn from(error: Error) -> Self {
        match error {
            Error::Unsupported(_) => Self::Unsupported,
            Error::InvalidData(parse_status) => parse_status,
            Error::UnexpectedEOF => Self::Eof,
            Error::Io(_) => {
                // Getting std::io::ErrorKind::UnexpectedEof is normal
                // but our From trait implementation should have converted
                // those to our Error::UnexpectedEOF variant.
                Self::Io
            }
            Error::MoovMissing => Self::MoovMissing,
            Error::OutOfMemory => Self::Oom,
        }
    }
}

impl From<Result<(), Status>> for Status {
    fn from(result: Result<(), Status>) -> Self {
        match result {
            Ok(()) => Status::Ok,
            Err(Status::Ok) => unreachable!(),
            Err(e) => e,
        }
    }
}

impl<T> From<Result<T>> for Status {
    fn from(result: Result<T>) -> Self {
        match result {
            Ok(_) => Status::Ok,
            Err(e) => Status::from(e),
        }
    }
}

impl From<fallible_collections::TryReserveError> for Status {
    fn from(_: fallible_collections::TryReserveError) -> Self {
        Status::Oom
    }
}

impl From<std::io::Error> for Status {
    fn from(_: std::io::Error) -> Self {
        Status::Io
    }
}

/// Describes parser failures.
///
/// This enum wraps the standard `io::Error` type, unified with
/// our own parser error states and those of crates we use.
#[derive(Debug)]
pub enum Error {
    /// Parse error caused by corrupt or malformed data.
    /// See the helper [`From<Status> for Error`](enum.Error.html#impl-From<Status>)
    InvalidData(Status),
    /// Parse error caused by limited parser support rather than invalid data.
    Unsupported(&'static str),
    /// Reflect `std::io::ErrorKind::UnexpectedEof` for short data.
    UnexpectedEOF,
    /// Propagate underlying errors from `std::io`.
    Io(std::io::Error),
    /// read_mp4 terminated without detecting a moov box.
    MoovMissing,
    /// Out of memory
    OutOfMemory,
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{self:?}")
    }
}

impl std::error::Error for Error {}

impl From<bitreader::BitReaderError> for Error {
    fn from(_: bitreader::BitReaderError) -> Error {
        Status::BitReaderError.into()
    }
}

impl From<std::io::Error> for Error {
    fn from(err: std::io::Error) -> Error {
        match err.kind() {
            std::io::ErrorKind::UnexpectedEof => Error::UnexpectedEOF,
            _ => Error::Io(err),
        }
    }
}

impl From<std::string::FromUtf8Error> for Error {
    fn from(_: std::string::FromUtf8Error) -> Error {
        Status::InvalidUtf8.into()
    }
}

impl From<std::str::Utf8Error> for Error {
    fn from(_: std::str::Utf8Error) -> Error {
        Status::InvalidUtf8.into()
    }
}

impl From<std::num::TryFromIntError> for Error {
    fn from(_: std::num::TryFromIntError) -> Error {
        Error::Unsupported("integer conversion failed")
    }
}

impl From<Error> for std::io::Error {
    fn from(err: Error) -> Self {
        let kind = match err {
            Error::UnexpectedEOF => std::io::ErrorKind::UnexpectedEof,
            Error::Io(io_err) => return io_err,
            _ => std::io::ErrorKind::Other,
        };
        Self::new(kind, err)
    }
}

impl From<TryReserveError> for Error {
    fn from(_: TryReserveError) -> Error {
        Error::OutOfMemory
    }
}

/// Result shorthand using our Error enum.
pub type Result<T, E = Error> = std::result::Result<T, E>;

/// Basic ISO box structure.
///
/// mp4 files are a sequence of possibly-nested 'box' structures.  Each box
/// begins with a header describing the length of the box's data and a
/// four-byte box type which identifies the type of the box. Together these
/// are enough to interpret the contents of that section of the file.
///
/// See ISOBMFF (ISO 14496-12:2020) § 4.2
#[derive(Debug, Clone, Copy)]
struct BoxHeader {
    /// Box type.
    name: BoxType,
    /// Size of the box in bytes.
    size: u64,
    /// Offset to the start of the contained data (or header size).
    offset: u64,
    /// Uuid for extended type.
    #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340
    uuid: Option<[u8; 16]>,
}

impl BoxHeader {
    const MIN_SIZE: u64 = 8; // 4-byte size + 4-byte type
    const MIN_LARGE_SIZE: u64 = 16; // 4-byte size + 4-byte type + 16-byte size
}

/// File type box 'ftyp'.
#[derive(Debug)]
struct FileTypeBox {
    #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340
    major_brand: FourCC,
    #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340
    minor_version: u32,
    compatible_brands: TryVec<FourCC>,
}

impl FileTypeBox {
    fn contains(&self, brand: &FourCC) -> bool {
        self.compatible_brands.contains(brand) || self.major_brand == *brand
    }
}

/// Movie header box 'mvhd'.
#[derive(Debug)]
struct MovieHeaderBox {
    pub timescale: u32,
    #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340
    duration: u64,
}

#[derive(Debug, Clone, Copy)]
pub struct Matrix {
    pub a: i32, // 16.16 fix point
    pub b: i32, // 16.16 fix point
    pub u: i32, // 2.30 fix point
    pub c: i32, // 16.16 fix point
    pub d: i32, // 16.16 fix point
    pub v: i32, // 2.30 fix point
    pub x: i32, // 16.16 fix point
    pub y: i32, // 16.16 fix point
    pub w: i32, // 2.30 fix point
}

/// Track header box 'tkhd'
#[derive(Debug, Clone)]
pub struct TrackHeaderBox {
    track_id: u32,
    pub disabled: bool,
    pub duration: u64,
    pub width: u32,
    pub height: u32,
    pub matrix: Matrix,
}

/// Edit list box 'elst'
#[derive(Debug)]
struct EditListBox {
    looped: bool,
    edits: TryVec<Edit>,
}

#[derive(Debug)]
struct Edit {
    segment_duration: u64,
    media_time: i64,
    #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340
    media_rate_integer: i16,
    #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340
    media_rate_fraction: i16,
}

/// Media header box 'mdhd'
#[derive(Debug)]
struct MediaHeaderBox {
    timescale: u32,
    duration: u64,
}

// Chunk offset box 'stco' or 'co64'
#[derive(Debug)]
pub struct ChunkOffsetBox {
    pub offsets: TryVec<u64>,
}

// Sync sample box 'stss'
#[derive(Debug)]
pub struct SyncSampleBox {
    pub samples: TryVec<u32>,
}

// Sample to chunk box 'stsc'
#[derive(Debug)]
pub struct SampleToChunkBox {
    pub samples: TryVec<SampleToChunk>,
}

#[derive(Debug)]
pub struct SampleToChunk {
    pub first_chunk: u32,
    pub samples_per_chunk: u32,
    pub sample_description_index: u32,
}

// Sample size box 'stsz'
#[derive(Debug)]
pub struct SampleSizeBox {
    pub sample_size: u32,
    pub sample_sizes: TryVec<u32>,
}

// Time to sample box 'stts'
#[derive(Debug)]
pub struct TimeToSampleBox {
    pub samples: TryVec<Sample>,
}

#[repr(C)]
#[derive(Debug)]
pub struct Sample {
    pub sample_count: u32,
    pub sample_delta: u32,
}

#[derive(Debug, Clone, Copy)]
pub enum TimeOffsetVersion {
    Version0(u32),
    Version1(i32),
}

#[derive(Debug, Clone)]
pub struct TimeOffset {
    pub sample_count: u32,
    pub time_offset: TimeOffsetVersion,
}

#[derive(Debug)]
pub struct CompositionOffsetBox {
    pub samples: TryVec<TimeOffset>,
}

// Handler reference box 'hdlr'
#[derive(Debug)]
struct HandlerBox {
    handler_type: FourCC,
}

// Sample description box 'stsd'
#[derive(Debug)]
pub struct SampleDescriptionBox {
    pub descriptions: TryVec<SampleEntry>,
}

#[derive(Debug)]
pub enum SampleEntry {
    Audio(AudioSampleEntry),
    Video(VideoSampleEntry),
    Unknown,
}

#[derive(Debug)]
pub struct TrackReferenceBox {
    pub references: TryVec<TrackReferenceEntry>,
}

impl TrackReferenceBox {
    pub fn has_auxl_reference(&self, track_id: u32) -> bool {
        self.references.iter().any(|entry| match entry {
            TrackReferenceEntry::Auxiliary(aux_entry) => aux_entry.track_ids.contains(&track_id),
        })
    }
}

#[derive(Debug)]
pub enum TrackReferenceEntry {
    Auxiliary(TrackReference),
}

#[derive(Debug)]
pub struct TrackReference {
    pub track_ids: TryVec<u32>,
}

/// An Elementary Stream Descriptor
/// See MPEG-4 Systems (ISO 14496-1:2010) § 7.2.6.5
#[allow(non_camel_case_types)]
#[derive(Debug, Default)]
pub struct ES_Descriptor {
    pub audio_codec: CodecType,
    pub audio_object_type: Option<u16>,
    pub extended_audio_object_type: Option<u16>,
    pub audio_sample_rate: Option<u32>,
    pub audio_channel_count: Option<u16>,
    #[cfg(feature = "mp4v")]
    pub video_codec: CodecType,
    pub codec_esds: TryVec<u8>,
    pub decoder_specific_data: TryVec<u8>, // Data in DECODER_SPECIFIC_TAG
}

#[allow(non_camel_case_types)]
#[derive(Debug)]
pub enum AudioCodecSpecific {
    ES_Descriptor(ES_Descriptor),
    FLACSpecificBox(FLACSpecificBox),
    OpusSpecificBox(OpusSpecificBox),
    ALACSpecificBox(ALACSpecificBox),
    MP3,
    LPCM,
    #[cfg(feature = "3gpp")]
    AMRSpecificBox(TryVec<u8>),
}

#[derive(Debug)]
pub struct AudioSampleEntry {
    pub codec_type: CodecType,
    #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340
    data_reference_index: u16,
    pub channelcount: u32,
    pub samplesize: u16,
    pub samplerate: f64,
    pub codec_specific: AudioCodecSpecific,
    pub protection_info: TryVec<ProtectionSchemeInfoBox>,
}

#[derive(Debug)]
pub enum VideoCodecSpecific {
    AVCConfig(TryVec<u8>),
    VPxConfig(VPxConfigBox),
    AV1Config(AV1ConfigBox),
    ESDSConfig(TryVec<u8>),
    H263Config(TryVec<u8>),
    HEVCConfig(TryVec<u8>),
}

#[derive(Debug)]
pub struct VideoSampleEntry {
    pub codec_type: CodecType,
    #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340
    data_reference_index: u16,
    pub width: u16,
    pub height: u16,
    pub codec_specific: VideoCodecSpecific,
    pub protection_info: TryVec<ProtectionSchemeInfoBox>,
}

/// Represent a Video Partition Codec Configuration 'vpcC' box (aka vp9). The meaning of each
/// field is covered in detail in "VP Codec ISO Media File Format Binding".
#[derive(Debug)]
pub struct VPxConfigBox {
    /// An integer that specifies the VP codec profile.
    #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340
    profile: u8,
    /// An integer that specifies a VP codec level all samples conform to the following table.
    /// For a description of the various levels, please refer to the VP9 Bitstream Specification.
    #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340
    level: u8,
    /// An integer that specifies the bit depth of the luma and color components. Valid values
    /// are 8, 10, and 12.
    pub bit_depth: u8,
    /// Really an enum defined by the "Colour primaries" section of ISO 23091-2:2019 § 8.1.
    pub colour_primaries: u8,
    /// Really an enum defined by "VP Codec ISO Media File Format Binding".
    pub chroma_subsampling: u8,
    /// Really an enum defined by the "Transfer characteristics" section of ISO 23091-2:2019 § 8.2.
    #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340
    transfer_characteristics: u8,
    /// Really an enum defined by the "Matrix coefficients" section of ISO 23091-2:2019 § 8.3.
    /// Available in 'VP Codec ISO Media File Format' version 1 only.
    #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340
    matrix_coefficients: Option<u8>,
    /// Indicates the black level and range of the luma and chroma signals. 0 = legal range
    /// (e.g. 16-235 for 8 bit sample depth); 1 = full range (e.g. 0-255 for 8-bit sample depth).
    #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340
    video_full_range_flag: bool,
    /// This is not used for VP8 and VP9 . Intended for binary codec initialization data.
    pub codec_init: TryVec<u8>,
}

/// See [AV1-ISOBMFF § 2.3.3](https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax)
#[derive(Debug)]
pub struct AV1ConfigBox {
    pub profile: u8,
    pub level: u8,
    pub tier: u8,
    pub bit_depth: u8,
    pub monochrome: bool,
    pub chroma_subsampling_x: u8,
    pub chroma_subsampling_y: u8,
    pub chroma_sample_position: u8,
    pub initial_presentation_delay_present: bool,
    pub initial_presentation_delay_minus_one: u8,
    // The raw config contained in the av1c box. Because some decoders accept this data as a binary
    // blob, rather than as structured data, we store the blob here for convenience.
    pub raw_config: TryVec<u8>,
}

impl AV1ConfigBox {
    const CONFIG_OBUS_OFFSET: usize = 4;

    pub fn config_obus(&self) -> &[u8] {
        &self.raw_config[Self::CONFIG_OBUS_OFFSET..]
    }
}

#[derive(Debug)]
pub struct FLACMetadataBlock {
    pub block_type: u8,
    pub data: TryVec<u8>,
}

/// Represents a FLACSpecificBox 'dfLa'
#[derive(Debug)]
pub struct FLACSpecificBox {
    #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340
    version: u8,
    pub blocks: TryVec<FLACMetadataBlock>,
}

#[derive(Debug)]
struct ChannelMappingTable {
    stream_count: u8,
    coupled_count: u8,
    channel_mapping: TryVec<u8>,
}

/// Represent an OpusSpecificBox 'dOps'
#[derive(Debug)]
pub struct OpusSpecificBox {
    pub version: u8,
    output_channel_count: u8,
    pre_skip: u16,
    input_sample_rate: u32,
    output_gain: i16,
    channel_mapping_family: u8,
    channel_mapping_table: Option<ChannelMappingTable>,
}

/// Represent an ALACSpecificBox 'alac'
#[derive(Debug)]
pub struct ALACSpecificBox {
    #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340
    version: u8,
    pub data: TryVec<u8>,
}

#[derive(Debug)]
pub struct MovieExtendsBox {
    pub fragment_duration: Option<MediaScaledTime>,
}

pub type ByteData = TryVec<u8>;

#[derive(Debug, Default)]
pub struct ProtectionSystemSpecificHeaderBox {
    pub system_id: ByteData,
    pub kid: TryVec<ByteData>,
    pub data: ByteData,

    // The entire pssh box (include header) required by Gecko.
    pub box_content: ByteData,
}

#[derive(Debug, Default, Clone)]
pub struct SchemeTypeBox {
    pub scheme_type: FourCC,
    pub scheme_version: u32,
}

#[derive(Debug, Default)]
pub struct TrackEncryptionBox {
    pub is_encrypted: u8,
    pub iv_size: u8,
    pub kid: TryVec<u8>,
    // Members for pattern encryption schemes
    pub crypt_byte_block_count: Option<u8>,
    pub skip_byte_block_count: Option<u8>,
    pub constant_iv: Option<TryVec<u8>>,
    // End pattern encryption scheme members
}

#[derive(Debug, Default)]
pub struct ProtectionSchemeInfoBox {
    pub original_format: FourCC,
    pub scheme_type: Option<SchemeTypeBox>,
    pub tenc: Option<TrackEncryptionBox>,
}

/// Represents a userdata box 'udta'.
/// Currently, only the metadata atom 'meta'
/// is parsed.
#[derive(Debug, Default)]
pub struct UserdataBox {
    pub meta: Option<MetadataBox>,
}

/// Represents possible contents of the
/// ©gen or gnre atoms within a metadata box.
/// 'udta.meta.ilst' may only have either a
/// standard genre box 'gnre' or a custom
/// genre box '©gen', but never both at once.
#[derive(Debug, PartialEq)]
pub enum Genre {
    /// A standard ID3v1 numbered genre.
    StandardGenre(u8),
    /// Any custom genre string.
    CustomGenre(TryString),
}

/// Represents the contents of a 'stik'
/// atom that indicates content types within
/// iTunes.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum MediaType {
    /// Movie is stored as 0 in a 'stik' atom.
    Movie, // 0
    /// Normal is stored as 1 in a 'stik' atom.
    Normal, // 1
    /// AudioBook is stored as 2 in a 'stik' atom.
    AudioBook, // 2
    /// WhackedBookmark is stored as 5 in a 'stik' atom.
    WhackedBookmark, // 5
    /// MusicVideo is stored as 6 in a 'stik' atom.
    MusicVideo, // 6
    /// ShortFilm is stored as 9 in a 'stik' atom.
    ShortFilm, // 9
    /// TVShow is stored as 10 in a 'stik' atom.
    TVShow, // 10
    /// Booklet is stored as 11 in a 'stik' atom.
    Booklet, // 11
    /// An unknown 'stik' value.
    Unknown(u8),
}

/// Represents the parental advisory rating on the track,
/// stored within the 'rtng' atom.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum AdvisoryRating {
    /// Clean is always stored as 2 in an 'rtng' atom.
    Clean, // 2
    /// A value of 0 in an 'rtng' atom indicates 'Inoffensive'
    Inoffensive, // 0
    /// Any non 2 or 0 value in 'rtng' indicates the track is explicit.
    Explicit(u8),
}

/// Represents the contents of 'ilst' atoms within
/// a metadata box 'meta', parsed as iTunes metadata using
/// the conventional tags.
#[derive(Debug, Default)]
pub struct MetadataBox {
    /// The album name, '©alb'
    pub album: Option<TryString>,
    /// The artist name '©art' or '©ART'
    pub artist: Option<TryString>,
    /// The album artist 'aART'
    pub album_artist: Option<TryString>,
    /// Track comments '©cmt'
    pub comment: Option<TryString>,
    /// The date or year field '©day'
    ///
    /// This is stored as an arbitrary string,
    /// and may not necessarily be in a valid date
    /// format.
    pub year: Option<TryString>,
    /// The track title '©nam'
    pub title: Option<TryString>,
    /// The track genre '©gen' or 'gnre'.
    pub genre: Option<Genre>,
    /// The track number 'trkn'.
    pub track_number: Option<u8>,
    /// The disc number 'disk'
    pub disc_number: Option<u8>,
    /// The total number of tracks on the disc,
    /// stored in 'trkn'
    pub total_tracks: Option<u8>,
    /// The total number of discs in the album,
    /// stored in 'disk'
    pub total_discs: Option<u8>,
    /// The composer of the track '©wrt'
    pub composer: Option<TryString>,
    /// The encoder used to create this track '©too'
    pub encoder: Option<TryString>,
    /// The encoded-by settingo this track '©enc'
    pub encoded_by: Option<TryString>,
    /// The tempo or BPM of the track 'tmpo'
    pub beats_per_minute: Option<u8>,
    /// Copyright information of the track 'cprt'
    pub copyright: Option<TryString>,
    /// Whether or not this track is part of a compilation 'cpil'
    pub compilation: Option<bool>,
    /// The advisory rating of this track 'rtng'
    pub advisory: Option<AdvisoryRating>,
    /// The personal rating of this track, 'rate'.
    ///
    /// This is stored in the box as string data, but
    /// the format is an integer percentage from 0 - 100,
    /// where 100 is displayed as 5 stars out of 5.
    pub rating: Option<TryString>,
    /// The grouping this track belongs to '©grp'
    pub grouping: Option<TryString>,
    /// The media type of this track 'stik'
    pub media_type: Option<MediaType>, // stik
    /// Whether or not this track is a podcast 'pcst'
    pub podcast: Option<bool>,
    /// The category of ths track 'catg'
    pub category: Option<TryString>,
    /// The podcast keyword 'keyw'
    pub keyword: Option<TryString>,
    /// The podcast url 'purl'
    pub podcast_url: Option<TryString>,
    /// The podcast episode GUID 'egid'
    pub podcast_guid: Option<TryString>,
    /// The description of the track 'desc'
    pub description: Option<TryString>,
    /// The long description of the track 'ldes'.
    ///
    /// Unlike other string fields, the long description field
    /// can be longer than 256 characters.
    pub long_description: Option<TryString>,
    /// The lyrics of the track '©lyr'.
    ///
    /// Unlike other string fields, the lyrics field
    /// can be longer than 256 characters.
    pub lyrics: Option<TryString>,
    /// The name of the TV network this track aired on 'tvnn'.
    pub tv_network_name: Option<TryString>,
    /// The name of the TV Show for this track 'tvsh'.
    pub tv_show_name: Option<TryString>,
    /// The name of the TV Episode for this track 'tven'.
    pub tv_episode_name: Option<TryString>,
    /// The number of the TV Episode for this track 'tves'.
    pub tv_episode_number: Option<u8>,
    /// The season of the TV Episode of this track 'tvsn'.
    pub tv_season: Option<u8>,
    /// The date this track was purchased 'purd'.
    pub purchase_date: Option<TryString>,
    /// Whether or not this track supports gapless playback 'pgap'
    pub gapless_playback: Option<bool>,
    /// Any cover artwork attached to this track 'covr'
    ///
    /// 'covr' is unique in that it may contain multiple 'data' sub-entries,
    /// each an image file. Here, each subentry's raw binary data is exposed,
    /// which may contain image data in JPEG or PNG format.
    pub cover_art: Option<TryVec<TryVec<u8>>>,
    /// The owner of the track 'ownr'
    pub owner: Option<TryString>,
    /// Whether or not this track is HD Video 'hdvd'
    pub hd_video: Option<bool>,
    /// The name of the track to sort by 'sonm'
    pub sort_name: Option<TryString>,
    /// The name of the album to sort by 'soal'
    pub sort_album: Option<TryString>,
    /// The name of the artist to sort by 'soar'
    pub sort_artist: Option<TryString>,
    /// The name of the album artist to sort by 'soaa'
    pub sort_album_artist: Option<TryString>,
    /// The name of the composer to sort by 'soco'
    pub sort_composer: Option<TryString>,
    /// Metadata
    #[cfg(feature = "meta-xml")]
    pub xml: Option<XmlBox>,
}

/// See ISOBMFF (ISO 14496-12:2020) § 8.11.2.1
#[cfg(feature = "meta-xml")]
#[derive(Debug)]
pub enum XmlBox {
    /// XML metadata
    StringXmlBox(TryString),
    /// Binary XML metadata
    BinaryXmlBox(TryVec<u8>),
}

/// Internal data structures.
#[derive(Debug, Default)]
pub struct MediaContext {
    pub timescale: Option<MediaTimeScale>,
    /// Tracks found in the file.
    pub tracks: TryVec<Track>,
    pub mvex: Option<MovieExtendsBox>,
    pub psshs: TryVec<ProtectionSystemSpecificHeaderBox>,
    pub userdata: Option<Result<UserdataBox>>,
    #[cfg(feature = "meta-xml")]
    pub metadata: Option<Result<MetadataBox>>,
}

/// An ISOBMFF item as described by an iloc box. For the sake of avoiding copies,
/// this can either be represented by the `Location` variant, which indicates
/// where the data exists within a `DataBox` stored separately, or the `Data`
/// variant which owns the data. Unfortunately, it's not simple to represent
/// this as a [`std::borrow::Cow`], or other reference-based type, because
/// multiple instances may references different parts of the same [`DataBox`]
/// and we want to avoid the copy that splitting the storage would entail.
enum IsobmffItem {
    MdatLocation(Extent),
    IdatLocation(Extent),
    Data(TryVec<u8>),
}

impl fmt::Debug for IsobmffItem {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match &self {
            IsobmffItem::MdatLocation(extent) | IsobmffItem::IdatLocation(extent) => f
                .debug_struct("IsobmffItem::Location")
                .field("0", &format_args!("{extent:?}"))
                .finish(),
            IsobmffItem::Data(data) => f
                .debug_struct("IsobmffItem::Data")
                .field("0", &format_args!("{} bytes", data.len()))
                .finish(),
        }
    }
}

#[derive(Debug)]
struct AvifItem {
    /// The `item_ID` from ISOBMFF (ISO 14496-12:2020) § 8.11.3
    ///
    /// See [`read_iloc`]
    id: ItemId,

    /// AV1 Image Item per <https://aomediacodec.github.io/av1-avif/#image-item>
    image_data: IsobmffItem,
}

impl AvifItem {
    fn with_inline_data(id: ItemId) -> Self {
        Self {
            id,
            image_data: IsobmffItem::Data(TryVec::new()),
        }
    }
}

#[derive(Default, Debug)]
pub struct AvifContext {
    /// Level of deviation from the specification before failing the parse
    strictness: ParseStrictness,
    /// Storage elements which can be located anywhere within the "file" identified by
    /// [`BoxType::ItemLocationBox`]es using [`ConstructionMethod::File`].
    /// Referred to by the [`IsobmffItem`]`::*Location` variants of the `AvifItem`s in this struct
    media_storage: TryVec<DataBox>,
    /// Similar to `media_storage`, but for a single optional chunk of storage within the
    /// MetaBox itentified by [`BoxType::ItemLocationBox`]es using [`ConstructionMethod::Idat`].
    item_data_box: Option<DataBox>,
    /// The item indicated by the `pitm` box, See ISOBMFF (ISO 14496-12:2020) § 8.11.4
    /// May be `None` in the pure image sequence case.
    primary_item: Option<AvifItem>,
    /// Associated alpha channel for the primary item, if any
    alpha_item: Option<AvifItem>,
    /// If true, divide RGB values by the alpha value.
    /// See `prem` in MIAF (ISO 23000-22:2019) § 7.3.5.2
    pub premultiplied_alpha: bool,
    /// All properties associated with `primary_item` or `alpha_item`
    item_properties: ItemPropertiesBox,
    /// Should probably only ever be [`AVIF_BRAND`] or [`AVIS_BRAND`], but other values
    /// are legal as long as one of the two is the `compatible_brand` list.
    pub major_brand: FourCC,
    /// Information on the sequence contained in the image, or None if not present
    pub sequence: Option<MediaContext>,
    /// A collection of unsupported features encountered during the parse
    pub unsupported_features: UnsupportedFeatures,
}

impl AvifContext {
    pub fn primary_item_is_present(&self) -> bool {
        self.primary_item.is_some()
    }

    pub fn primary_item_coded_data(&self) -> Option<&[u8]> {
        self.primary_item
            .as_ref()
            .map(|item| self.item_as_slice(item))
    }

    pub fn primary_item_bits_per_channel(&self) -> Option<Result<&[u8]>> {
        self.primary_item
            .as_ref()
            .map(|item| self.image_bits_per_channel(item.id))
    }

    pub fn alpha_item_is_present(&self) -> bool {
        self.alpha_item.is_some()
    }

    pub fn alpha_item_coded_data(&self) -> Option<&[u8]> {
        self.alpha_item
            .as_ref()
            .map(|item| self.item_as_slice(item))
    }

    pub fn alpha_item_bits_per_channel(&self) -> Option<Result<&[u8]>> {
        self.alpha_item
            .as_ref()
            .map(|item| self.image_bits_per_channel(item.id))
    }

    fn image_bits_per_channel(&self, item_id: ItemId) -> Result<&[u8]> {
        match self
            .item_properties
            .get(item_id, BoxType::PixelInformationBox)?
        {
            Some(ItemProperty::Channels(pixi)) => Ok(pixi.bits_per_channel.as_slice()),
            Some(other_property) => panic!("property key mismatch: {:?}", other_property),
            None => Ok(&[]),
        }
    }

    pub fn spatial_extents_ptr(&self) -> Result<*const ImageSpatialExtentsProperty> {
        if let Some(primary_item) = &self.primary_item {
            match self
                .item_properties
                .get(primary_item.id, BoxType::ImageSpatialExtentsProperty)?
            {
                Some(ItemProperty::ImageSpatialExtents(ispe)) => Ok(ispe),
                Some(other_property) => panic!("property key mismatch: {:?}", other_property),
                None => {
                    fail_with_status_if(
                        self.strictness != ParseStrictness::Permissive,
                        Status::IspeMissing,
                    )?;
                    Ok(std::ptr::null())
                }
            }
        } else {
            Ok(std::ptr::null())
        }
    }

    /// Returns None if there is no primary item or it has no associated NCLX colour boxes.
    pub fn nclx_colour_information_ptr(&self) -> Option<Result<*const NclxColourInformation>> {
        if let Some(primary_item) = &self.primary_item {
            match self.item_properties.get_multiple(primary_item.id, |prop| {
                matches!(prop, ItemProperty::Colour(ColourInformation::Nclx(_)))
            }) {
                Ok(nclx_colr_boxes) => match *nclx_colr_boxes.as_slice() {
                    [] => None,
                    [ItemProperty::Colour(ColourInformation::Nclx(nclx)), ..] => {
                        if nclx_colr_boxes.len() > 1 {
                            warn!("Multiple nclx colr boxes, using first");
                        }
                        Some(Ok(nclx))
                    }
                    _ => unreachable!("Expect only ColourInformation::Nclx(_) matches"),
                },
                Err(e) => Some(Err(e)),
            }
        } else {
            None
        }
    }

    /// Returns None if there is no primary item or it has no associated ICC colour boxes.
    pub fn icc_colour_information(&self) -> Option<Result<&[u8]>> {
        if let Some(primary_item) = &self.primary_item {
            match self.item_properties.get_multiple(primary_item.id, |prop| {
                matches!(prop, ItemProperty::Colour(ColourInformation::Icc(_, _)))
            }) {
                Ok(icc_colr_boxes) => match *icc_colr_boxes.as_slice() {
                    [] => None,
                    [ItemProperty::Colour(ColourInformation::Icc(icc, _)), ..] => {
                        if icc_colr_boxes.len() > 1 {
                            warn!("Multiple ICC profiles in colr boxes, using first");
                        }
                        Some(Ok(icc.bytes.as_slice()))
                    }
                    _ => unreachable!("Expect only ColourInformation::Icc(_) matches"),
                },
                Err(e) => Some(Err(e)),
            }
        } else {
            None
        }
    }

    pub fn image_rotation(&self) -> Result<ImageRotation> {
        if let Some(primary_item) = &self.primary_item {
            match self
                .item_properties
                .get(primary_item.id, BoxType::ImageRotation)?
            {
                Some(ItemProperty::Rotation(irot)) => Ok(*irot),
                Some(other_property) => panic!("property key mismatch: {:?}", other_property),
                None => Ok(ImageRotation::D0),
            }
        } else {
            Ok(ImageRotation::D0)
        }
    }

    pub fn image_mirror_ptr(&self) -> Result<*const ImageMirror> {
        if let Some(primary_item) = &self.primary_item {
            match self
                .item_properties
                .get(primary_item.id, BoxType::ImageMirror)?
            {
                Some(ItemProperty::Mirroring(imir)) => Ok(imir),
                Some(other_property) => panic!("property key mismatch: {:?}", other_property),
                None => Ok(std::ptr::null()),
            }
        } else {
            Ok(std::ptr::null())
        }
    }

    pub fn pixel_aspect_ratio_ptr(&self) -> Result<*const PixelAspectRatio> {
        if let Some(primary_item) = &self.primary_item {
            match self
                .item_properties
                .get(primary_item.id, BoxType::PixelAspectRatioBox)?
            {
                Some(ItemProperty::PixelAspectRatio(pasp)) => Ok(pasp),
                Some(other_property) => panic!("property key mismatch: {:?}", other_property),
                None => Ok(std::ptr::null()),
            }
        } else {
            Ok(std::ptr::null())
        }
    }

    /// A helper for the various `AvifItem`s to expose a reference to the
    /// underlying data while avoiding copies.
    fn item_as_slice<'a>(&'a self, item: &'a AvifItem) -> &'a [u8] {
        match &item.image_data {
            IsobmffItem::MdatLocation(extent) => {
                for mdat in &self.media_storage {
                    if let Some(slice) = mdat.get(extent) {
                        return slice;
                    }
                }
                unreachable!(
                    "IsobmffItem::MdatLocation requires the location exists in AvifContext::media_storage"
                );
            }
            IsobmffItem::IdatLocation(extent) => {
                self.item_data_box
                    .as_ref()
                    .and_then(|idat| idat.get(extent))
                    .unwrap_or_else(|| unreachable!("IsobmffItem::IdatLocation equires the location exists in AvifContext::item_data_box"))
            }
            IsobmffItem::Data(data) => data.as_slice(),
        }
    }
}

struct AvifMeta {
    item_references: TryVec<SingleItemTypeReferenceBox>,
    item_properties: ItemPropertiesBox,
    /// Required for AvifImageType::Primary, but optional otherwise
    /// See HEIF (ISO/IEC 23008-12:2017) § 7.1, 10.2.1
    primary_item_id: Option<ItemId>,
    item_infos: TryVec<ItemInfoEntry>,
    iloc_items: TryHashMap<ItemId, ItemLocationBoxItem>,
    item_data_box: Option<DataBox>,
}

#[derive(Debug)]
enum DataBoxMetadata {
    Idat,
    Mdat {
        /// Offset of `data` from the beginning of the "file". See ConstructionMethod::File.
        /// Note: the file may not be an actual file, read_avif supports any `&mut impl Read`
        /// source for input. However we try to match the terminology used in the spec.
        file_offset: u64,
    },
}

/// Represents either an Item Data Box (ISOBMFF (ISO 14496-12:2020) § 8.11.11)
/// Or a Media Data Box (ISOBMFF (ISO 14496-12:2020) § 8.1.1)
struct DataBox {
    metadata: DataBoxMetadata,
    data: TryVec<u8>,
}

impl fmt::Debug for DataBox {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.debug_struct("DataBox")
            .field("metadata", &self.metadata)
            .field("data", &format_args!("{} bytes", self.data.len()))
            .finish()
    }
}

fn u64_to_usize_logged(x: u64) -> Option<usize> {
    match x.try_into() {
        Ok(x) => Some(x),
        Err(e) => {
            error!("{:?} converting {:?}", e, x);
            None
        }
    }
}

impl DataBox {
    fn from_mdat(file_offset: u64, data: TryVec<u8>) -> Self {
        Self {
            metadata: DataBoxMetadata::Mdat { file_offset },
            data,
        }
    }

    fn from_idat(data: TryVec<u8>) -> Self {
        Self {
            metadata: DataBoxMetadata::Idat,
            data,
        }
    }

    fn data(&self) -> &[u8] {
        &self.data
    }

    /// Convert an absolute offset to an offset relative to the beginning of the
    /// slice [`DataBox::data`] returns. Returns None if the offset would be
    /// negative or if the offset would overflow a `usize`.
    fn start(&self, offset: u64) -> Option<usize> {
        match self.metadata {
            DataBoxMetadata::Idat => u64_to_usize_logged(offset),
            DataBoxMetadata::Mdat { file_offset } => {
                let start = offset.checked_sub(file_offset);
                if start.is_none() {
                    error!("Overflow subtracting {} + {}", offset, file_offset);
                }
                u64_to_usize_logged(start?)
            }
        }
    }

    /// Returns an appropriate variant of [`IsobmffItem`] to describe the extent
    /// referencing data within this type of box.
    fn location(&self, extent: &Extent) -> IsobmffItem {
        match self.metadata {
            DataBoxMetadata::Idat => IsobmffItem::IdatLocation(extent.clone()),
            DataBoxMetadata::Mdat { .. } => IsobmffItem::MdatLocation(extent.clone()),
        }
    }

    /// Return a slice from the DataBox specified by the provided `extent`.
    /// Returns `None` if the extent isn't fully contained by the DataBox or if
    /// either the offset or length (if the extent is bounded) of the slice
    /// would overflow a `usize`.
    fn get<'a>(&'a self, extent: &'a Extent) -> Option<&'a [u8]> {
        match extent {
            Extent::WithLength { offset, len } => {
                let start = self.start(*offset)?;
                let end = start.checked_add(*len);
                if end.is_none() {
                    error!("Overflow adding {} + {}", start, len);
                }
                self.data().get(start..end?)
            }
            Extent::ToEnd { offset } => {
                let start = self.start(*offset)?;
                self.data().get(start..)
            }
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
struct PropertyIndex(u16);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
struct ItemId(u32);

impl ItemId {
    fn read(src: &mut impl ReadBytesExt, version: u8) -> Result<ItemId> {
        Ok(ItemId(if version == 0 {
            be_u16(src)?.into()
        } else {
            be_u32(src)?
        }))
    }
}

/// Used for 'infe' boxes within 'iinf' boxes
/// See ISOBMFF (ISO 14496-12:2020) § 8.11.6
/// Only versions {2, 3} are supported
#[derive(Debug)]
struct ItemInfoEntry {
    item_id: ItemId,
    item_type: u32,
}

/// See ISOBMFF (ISO 14496-12:2020) § 8.11.12
#[derive(Debug)]
struct SingleItemTypeReferenceBox {
    item_type: FourCC,
    from_item_id: ItemId,
    to_item_id: ItemId,
}

/// Potential sizes (in bytes) of variable-sized fields of the 'iloc' box
/// See ISOBMFF (ISO 14496-12:2020) § 8.11.3
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum IlocFieldSize {
    Zero,
    Four,
    Eight,
}

impl IlocFieldSize {
    fn as_bits(&self) -> u8 {
        match self {
            IlocFieldSize::Zero => 0,
            IlocFieldSize::Four => 32,
            IlocFieldSize::Eight => 64,
        }
    }
}

impl TryFrom<u8> for IlocFieldSize {
    type Error = Error;

    fn try_from(value: u8) -> Result<Self> {
        match value {
            0 => Ok(Self::Zero),
            4 => Ok(Self::Four),
            8 => Ok(Self::Eight),
            _ => Status::IlocBadFieldSize.into(),
        }
    }
}

#[derive(Debug, PartialEq, Eq)]
enum IlocVersion {
    Zero,
    One,
    Two,
}

impl TryFrom<u8> for IlocVersion {
    type Error = Error;

    fn try_from(value: u8) -> Result<Self> {
        match value {
            0 => Ok(Self::Zero),
            1 => Ok(Self::One),
            2 => Ok(Self::Two),
            _ => Err(Error::Unsupported("unsupported version in 'iloc' box")),
        }
    }
}

/// Used for 'iloc' boxes
/// See ISOBMFF (ISO 14496-12:2020) § 8.11.3
/// `base_offset` is omitted since it is integrated into the ranges in `extents`
/// `data_reference_index` is omitted, since only 0 (i.e., this file) is supported
#[derive(Debug)]
struct ItemLocationBoxItem {
    construction_method: ConstructionMethod,
    /// Unused for ConstructionMethod::Idat
--> --------------------

--> maximum size reached

--> --------------------

[ zur Elbe Produktseite wechseln0.103Quellennavigators  Analyse erneut starten  ]