Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


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

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

[ Dauer der Verarbeitung: 0.12 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge