#![deny(unsafe_code)]
#![allow(clippy::missing_safety_doc)]
use arrayvec::ArrayVec;
use log::{debug, warn};
use bitreader::BitReader;
use byteorder::ReadBytesExt;
use fallible_collections::{TryClone, TryReserveError};
use std::borrow::Cow;
use std::convert::{TryFrom, TryInto as _};
use std::io::{Read, Take};
use std::num::NonZeroU32;
use std::ops::{Range, RangeFrom};
mod obu;
mod boxes;
use crate::boxes::{BoxType, FourCC};
#[cfg(feature = "c_api")]
pub mod c_api;
pub use enough::{Stop, StopReason, Unstoppable};
trait ToU64 {
fn to_u64(self) -> u64;
}
impl ToU64 for usize {
fn to_u64(self) -> u64 {
const _: () = assert!(std::mem::size_of::<usize>() <= std::mem::size_of::<u64>());
self as u64
}
}
pub(crate) trait ToUsize {
fn to_usize(self) -> usize;
}
macro_rules! impl_to_usize_from {
( $from_type:ty ) => {
impl ToUsize for $from_type {
fn to_usize(self) -> usize {
const _: () = assert!(std::mem::size_of::<$from_type>() <= std::mem::size_of::<usize>());
self as usize
}
}
};
}
impl_to_usize_from!(u8);
impl_to_usize_from!(u16);
impl_to_usize_from!(u32);
trait Offset {
fn offset(&self) -> u64;
}
struct OffsetReader<'a, T> {
reader: &'a mut T,
offset: u64,
}
impl<'a, T> OffsetReader<'a, T> {
fn new(reader: &'a mut T) -> Self {
Self { reader, offset: 0 }
}
}
impl<T> Offset for OffsetReader<'_, T> {
fn offset(&self) -> u64 {
self.offset
}
}
impl<T: Read> Read for OffsetReader<'_, T> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let bytes_read = self.reader.read(buf)?;
self.offset = self
.offset
.checked_add(bytes_read.to_u64())
.ok_or(Error::Unsupported("total bytes read too large for offset type"))?;
Ok(bytes_read)
}
}
pub(crate) type TryVec<T> = fallible_collections::TryVec<T>;
pub(crate) type TryString = fallible_collections::TryVec<u8>;
#[allow(dead_code)]
struct Vec;
#[allow(dead_code)]
struct Box;
#[allow(dead_code)]
struct HashMap;
#[allow(dead_code)]
struct String;
#[derive(Debug)]
pub enum Error {
InvalidData(&'static str),
Unsupported(&'static str),
UnexpectedEOF,
Io(std::io::Error),
NoMoov,
OutOfMemory,
ResourceLimitExceeded(&'static str),
Stopped(enough::StopReason),
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let msg = match self {
Self::InvalidData(s) | Self::Unsupported(s) | Self::ResourceLimitExceeded(s) => s,
Self::UnexpectedEOF => "EOF",
Self::Io(err) => return err.fmt(f),
Self::NoMoov => "Missing Moov box",
Self::OutOfMemory => "OOM",
Self::Stopped(reason) => return write!(f, "Stopped: {}", reason),
};
f.write_str(msg)
}
}
impl std::error::Error for Error {}
impl From<bitreader::BitReaderError> for Error {
#[cold]
#[cfg_attr(debug_assertions, track_caller)]
fn from(err: bitreader::BitReaderError) -> Self {
log::warn!("bitreader: {err}");
Self::InvalidData("truncated bits")
}
}
impl From<std::io::Error> for Error {
fn from(err: std::io::Error) -> Self {
match err.kind() {
std::io::ErrorKind::UnexpectedEof => Self::UnexpectedEOF,
_ => Self::Io(err),
}
}
}
impl From<std::string::FromUtf8Error> for Error {
fn from(_: std::string::FromUtf8Error) -> Self {
Self::InvalidData("invalid utf8")
}
}
impl From<std::num::TryFromIntError> for Error {
fn from(_: std::num::TryFromIntError) -> Self {
Self::Unsupported("integer conversion failed")
}
}
impl From<Error> for std::io::Error {
fn from(err: Error) -> Self {
let kind = match err {
Error::InvalidData(_) => std::io::ErrorKind::InvalidData,
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) -> Self {
Self::OutOfMemory
}
}
impl From<enough::StopReason> for Error {
fn from(reason: enough::StopReason) -> Self {
Self::Stopped(reason)
}
}
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, Clone, Copy)]
struct BoxHeader {
name: BoxType,
size: u64,
offset: u64,
#[allow(unused)]
uuid: Option<[u8; 16]>,
}
impl BoxHeader {
const MIN_SIZE: u64 = 8;
const MIN_LARGE_SIZE: u64 = 16;
}
#[derive(Debug)]
#[allow(unused)]
struct FileTypeBox {
major_brand: FourCC,
minor_version: u32,
compatible_brands: TryVec<FourCC>,
}
#[derive(Debug)]
#[allow(unused)]
struct HandlerBox {
handler_type: FourCC,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AV1Config {
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,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ColorInformation {
Nclx {
color_primaries: u16,
transfer_characteristics: u16,
matrix_coefficients: u16,
full_range: bool,
},
IccProfile(std::vec::Vec<u8>),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ImageRotation {
pub angle: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ImageMirror {
pub axis: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CleanAperture {
pub width_n: u32,
pub width_d: u32,
pub height_n: u32,
pub height_d: u32,
pub horiz_off_n: i32,
pub horiz_off_d: u32,
pub vert_off_n: i32,
pub vert_off_d: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PixelAspectRatio {
pub h_spacing: u32,
pub v_spacing: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ContentLightLevel {
pub max_content_light_level: u16,
pub max_pic_average_light_level: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MasteringDisplayColourVolume {
pub primaries: [(u16, u16); 3],
pub white_point: (u16, u16),
pub max_luminance: u32,
pub min_luminance: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ContentColourVolume {
pub primaries: Option<[(i32, i32); 3]>,
pub min_luminance: Option<u32>,
pub max_luminance: Option<u32>,
pub avg_luminance: Option<u32>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AmbientViewingEnvironment {
pub ambient_illuminance: u32,
pub ambient_light_x: u16,
pub ambient_light_y: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GainMapChannel {
pub gain_map_min_n: i32,
pub gain_map_min_d: u32,
pub gain_map_max_n: i32,
pub gain_map_max_d: u32,
pub gamma_n: u32,
pub gamma_d: u32,
pub base_offset_n: i32,
pub base_offset_d: u32,
pub alternate_offset_n: i32,
pub alternate_offset_d: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GainMapMetadata {
pub is_multichannel: bool,
pub use_base_colour_space: bool,
pub backward_direction: bool,
pub base_hdr_headroom_n: u32,
pub base_hdr_headroom_d: u32,
pub alternate_hdr_headroom_n: u32,
pub alternate_hdr_headroom_d: u32,
pub channels: [GainMapChannel; 3],
}
impl GainMapMetadata {
pub fn parse_tmap_bytes(data: &[u8]) -> Result<Self> {
parse_tone_map_image(data)
}
pub fn to_bytes(&self) -> std::vec::Vec<u8> {
let channel_count = if self.is_multichannel { 3usize } else { 1usize };
let mut buf = std::vec::Vec::with_capacity(6 + 16 + channel_count * 40);
buf.push(0u8); buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes()); let flags = (u8::from(self.is_multichannel) << 7)
| (u8::from(self.use_base_colour_space) << 6)
| (u8::from(self.backward_direction) << 2);
buf.push(flags);
buf.extend_from_slice(&self.base_hdr_headroom_n.to_be_bytes());
buf.extend_from_slice(&self.base_hdr_headroom_d.to_be_bytes());
buf.extend_from_slice(&self.alternate_hdr_headroom_n.to_be_bytes());
buf.extend_from_slice(&self.alternate_hdr_headroom_d.to_be_bytes());
for ch in self.channels.iter().take(channel_count) {
buf.extend_from_slice(&ch.gain_map_min_n.to_be_bytes());
buf.extend_from_slice(&ch.gain_map_min_d.to_be_bytes());
buf.extend_from_slice(&ch.gain_map_max_n.to_be_bytes());
buf.extend_from_slice(&ch.gain_map_max_d.to_be_bytes());
buf.extend_from_slice(&ch.gamma_n.to_be_bytes());
buf.extend_from_slice(&ch.gamma_d.to_be_bytes());
buf.extend_from_slice(&ch.base_offset_n.to_be_bytes());
buf.extend_from_slice(&ch.base_offset_d.to_be_bytes());
buf.extend_from_slice(&ch.alternate_offset_n.to_be_bytes());
buf.extend_from_slice(&ch.alternate_offset_d.to_be_bytes());
}
buf
}
}
impl From<&GainMapChannel> for zencodec::GainMapChannel {
fn from(ch: &GainMapChannel) -> Self {
Self {
min: ch.gain_map_min_n as f64 / ch.gain_map_min_d.max(1) as f64,
max: ch.gain_map_max_n as f64 / ch.gain_map_max_d.max(1) as f64,
gamma: ch.gamma_n as f64 / ch.gamma_d.max(1) as f64,
base_offset: ch.base_offset_n as f64 / ch.base_offset_d.max(1) as f64,
alternate_offset: ch.alternate_offset_n as f64 / ch.alternate_offset_d.max(1) as f64,
}
}
}
impl From<&GainMapMetadata> for zencodec::GainMapParams {
fn from(md: &GainMapMetadata) -> Self {
let mut p = Self::default();
p.channels = [
zencodec::GainMapChannel::from(&md.channels[0]),
zencodec::GainMapChannel::from(&md.channels[1]),
zencodec::GainMapChannel::from(&md.channels[2]),
];
p.base_hdr_headroom =
md.base_hdr_headroom_n as f64 / md.base_hdr_headroom_d.max(1) as f64;
p.alternate_hdr_headroom =
md.alternate_hdr_headroom_n as f64 / md.alternate_hdr_headroom_d.max(1) as f64;
p.use_base_color_space = md.use_base_colour_space;
p.backward_direction = md.backward_direction;
p
}
}
impl From<&zencodec::GainMapChannel> for GainMapChannel {
fn from(ch: &zencodec::GainMapChannel) -> Self {
use zencodec::gainmap::{Fraction, UFraction};
let min = Fraction::from_f64_cf(ch.min);
let max = Fraction::from_f64_cf(ch.max);
let gamma = UFraction::from_f64_cf(ch.gamma);
let base_off = Fraction::from_f64_cf(ch.base_offset);
let alt_off = Fraction::from_f64_cf(ch.alternate_offset);
Self {
gain_map_min_n: min.numerator,
gain_map_min_d: min.denominator,
gain_map_max_n: max.numerator,
gain_map_max_d: max.denominator,
gamma_n: gamma.numerator,
gamma_d: gamma.denominator,
base_offset_n: base_off.numerator,
base_offset_d: base_off.denominator,
alternate_offset_n: alt_off.numerator,
alternate_offset_d: alt_off.denominator,
}
}
}
impl From<&zencodec::GainMapParams> for GainMapMetadata {
fn from(p: &zencodec::GainMapParams) -> Self {
use zencodec::gainmap::UFraction;
let headroom_base = UFraction::from_f64_cf(p.base_hdr_headroom);
let headroom_alt = UFraction::from_f64_cf(p.alternate_hdr_headroom);
Self {
is_multichannel: !p.is_single_channel(),
use_base_colour_space: p.use_base_color_space,
backward_direction: p.backward_direction,
base_hdr_headroom_n: headroom_base.numerator,
base_hdr_headroom_d: headroom_base.denominator,
alternate_hdr_headroom_n: headroom_alt.numerator,
alternate_hdr_headroom_d: headroom_alt.denominator,
channels: [
GainMapChannel::from(&p.channels[0]),
GainMapChannel::from(&p.channels[1]),
GainMapChannel::from(&p.channels[2]),
],
}
}
}
#[derive(Debug, Clone)]
pub struct AvifGainMap {
pub metadata: GainMapMetadata,
pub gain_map_data: std::vec::Vec<u8>,
pub alt_color_info: Option<ColorInformation>,
}
#[derive(Debug, Clone)]
pub struct AvifDepthMap {
pub data: std::vec::Vec<u8>,
pub width: u32,
pub height: u32,
pub av1_config: Option<AV1Config>,
pub color_info: Option<ColorInformation>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OperatingPointSelector {
pub op_index: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct LayerSelector {
pub layer_id: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AV1LayeredImageIndexing {
pub layer_sizes: [u32; 3],
}
#[derive(Debug, Clone, Copy)]
#[derive(Default)]
pub struct ParseOptions {
pub lenient: bool,
}
#[derive(Debug, Clone)]
pub struct DecodeConfig {
pub peak_memory_limit: Option<u64>,
pub total_megapixels_limit: Option<u32>,
pub max_animation_frames: Option<u32>,
pub max_grid_tiles: Option<u32>,
pub lenient: bool,
}
impl Default for DecodeConfig {
fn default() -> Self {
Self {
peak_memory_limit: Some(1_000_000_000),
total_megapixels_limit: Some(512),
max_animation_frames: Some(10_000),
max_grid_tiles: Some(1_000),
lenient: false,
}
}
}
impl DecodeConfig {
pub fn unlimited() -> Self {
Self {
peak_memory_limit: None,
total_megapixels_limit: None,
max_animation_frames: None,
max_grid_tiles: None,
lenient: false,
}
}
pub fn with_peak_memory_limit(mut self, bytes: u64) -> Self {
self.peak_memory_limit = Some(bytes);
self
}
pub fn with_total_megapixels_limit(mut self, megapixels: u32) -> Self {
self.total_megapixels_limit = Some(megapixels);
self
}
pub fn with_max_animation_frames(mut self, frames: u32) -> Self {
self.max_animation_frames = Some(frames);
self
}
pub fn with_max_grid_tiles(mut self, tiles: u32) -> Self {
self.max_grid_tiles = Some(tiles);
self
}
pub fn lenient(mut self, lenient: bool) -> Self {
self.lenient = lenient;
self
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GridConfig {
pub rows: u8,
pub columns: u8,
pub output_width: u32,
pub output_height: u32,
}
#[cfg(feature = "eager")]
#[deprecated(since = "1.5.0", note = "Use `AvifParser::frame()` which returns `FrameRef` instead")]
#[derive(Debug)]
pub struct AnimationFrame {
pub data: TryVec<u8>,
pub duration_ms: u32,
}
#[cfg(feature = "eager")]
#[deprecated(since = "1.5.0", note = "Use `AvifParser::animation_info()` and `AvifParser::frames()` instead")]
#[derive(Debug)]
#[allow(deprecated)]
pub struct AnimationConfig {
pub loop_count: u32,
pub frames: TryVec<AnimationFrame>,
}
#[derive(Debug)]
struct MovieHeader {
_timescale: u32,
_duration: u64,
}
#[derive(Debug)]
struct MediaHeader {
timescale: u32,
_duration: u64,
}
#[derive(Debug)]
struct TimeToSampleEntry {
sample_count: u32,
sample_delta: u32,
}
#[derive(Debug)]
struct SampleToChunkEntry {
first_chunk: u32,
samples_per_chunk: u32,
_sample_description_index: u32,
}
#[derive(Debug)]
struct SampleTable {
time_to_sample: TryVec<TimeToSampleEntry>,
sample_sizes: TryVec<u32>,
sample_offsets: TryVec<u64>,
}
#[derive(Debug)]
struct TrackReference {
reference_type: FourCC,
track_ids: TryVec<u32>,
}
#[derive(Debug, Clone, Default)]
struct TrackCodecConfig {
av1_config: Option<AV1Config>,
color_info: Option<ColorInformation>,
}
#[derive(Debug)]
struct ParsedTrack {
track_id: u32,
handler_type: FourCC,
media_timescale: u32,
sample_table: SampleTable,
references: TryVec<TrackReference>,
loop_count: u32,
codec_config: TrackCodecConfig,
}
struct ParsedAnimationData {
color_timescale: u32,
color_sample_table: SampleTable,
alpha_timescale: Option<u32>,
alpha_sample_table: Option<SampleTable>,
loop_count: u32,
color_codec_config: TrackCodecConfig,
}
#[cfg(feature = "eager")]
#[deprecated(since = "1.5.0", note = "Use `AvifParser` for zero-copy parsing instead")]
#[derive(Debug, Default)]
#[allow(deprecated)]
pub struct AvifData {
pub primary_item: TryVec<u8>,
pub alpha_item: Option<TryVec<u8>>,
pub premultiplied_alpha: bool,
pub grid_config: Option<GridConfig>,
pub grid_tiles: TryVec<TryVec<u8>>,
pub animation: Option<AnimationConfig>,
pub av1_config: Option<AV1Config>,
pub color_info: Option<ColorInformation>,
pub rotation: Option<ImageRotation>,
pub mirror: Option<ImageMirror>,
pub clean_aperture: Option<CleanAperture>,
pub pixel_aspect_ratio: Option<PixelAspectRatio>,
pub content_light_level: Option<ContentLightLevel>,
pub mastering_display: Option<MasteringDisplayColourVolume>,
pub content_colour_volume: Option<ContentColourVolume>,
pub ambient_viewing: Option<AmbientViewingEnvironment>,
pub operating_point: Option<OperatingPointSelector>,
pub layer_selector: Option<LayerSelector>,
pub layered_image_indexing: Option<AV1LayeredImageIndexing>,
pub exif: Option<TryVec<u8>>,
pub xmp: Option<TryVec<u8>>,
pub gain_map_metadata: Option<GainMapMetadata>,
pub gain_map_item: Option<TryVec<u8>>,
pub gain_map_color_info: Option<ColorInformation>,
pub depth_item: Option<TryVec<u8>>,
pub depth_width: u32,
pub depth_height: u32,
pub depth_av1_config: Option<AV1Config>,
pub depth_color_info: Option<ColorInformation>,
pub major_brand: [u8; 4],
pub compatible_brands: std::vec::Vec<[u8; 4]>,
}
#[cfg(feature = "eager")]
#[allow(deprecated)]
impl AvifData {
pub fn gain_map(&self) -> Option<AvifGainMap> {
let metadata = self.gain_map_metadata.as_ref()?.clone();
let gain_map_data = self.gain_map_item.as_ref()?.to_vec();
Some(AvifGainMap {
metadata,
gain_map_data,
alt_color_info: self.gain_map_color_info.clone(),
})
}
pub fn depth_map(&self) -> Option<AvifDepthMap> {
let data = self.depth_item.as_ref()?.to_vec();
Some(AvifDepthMap {
data,
width: self.depth_width,
height: self.depth_height,
av1_config: self.depth_av1_config.clone(),
color_info: self.depth_color_info.clone(),
})
}
}
#[cfg(feature = "eager")]
#[allow(deprecated)]
impl AvifData {
#[deprecated(since = "1.5.0", note = "Use `AvifParser::from_reader()` instead")]
pub fn from_reader<R: Read>(reader: &mut R) -> Result<Self> {
read_avif(reader)
}
pub fn primary_item_metadata(&self) -> Result<AV1Metadata> {
AV1Metadata::parse_av1_bitstream(&self.primary_item)
}
pub fn alpha_item_metadata(&self) -> Result<Option<AV1Metadata>> {
self.alpha_item.as_deref().map(AV1Metadata::parse_av1_bitstream).transpose()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ChromaSubsampling {
pub horizontal: bool,
pub vertical: bool,
}
impl ChromaSubsampling {
pub const NONE: Self = Self { horizontal: false, vertical: false };
pub const YUV420: Self = Self { horizontal: true, vertical: true };
pub const YUV422: Self = Self { horizontal: true, vertical: false };
}
impl From<(bool, bool)> for ChromaSubsampling {
fn from((h, v): (bool, bool)) -> Self {
Self { horizontal: h, vertical: v }
}
}
impl From<ChromaSubsampling> for (bool, bool) {
fn from(cs: ChromaSubsampling) -> Self {
(cs.horizontal, cs.vertical)
}
}
#[non_exhaustive]
#[derive(Debug, Clone)]
pub struct AV1Metadata {
pub still_picture: bool,
pub max_frame_width: NonZeroU32,
pub max_frame_height: NonZeroU32,
pub bit_depth: u8,
pub seq_profile: u8,
pub chroma_subsampling: ChromaSubsampling,
pub monochrome: bool,
pub base_q_idx: Option<u8>,
pub lossless: Option<bool>,
}
impl AV1Metadata {
#[inline(never)]
pub fn parse_av1_bitstream(obu_bitstream: &[u8]) -> Result<Self> {
let (h, frame_quant) = obu::parse_obu_with_frame_info(obu_bitstream)?;
let no_chroma_subsampling = !h.color.chroma_subsampling.horizontal
&& !h.color.chroma_subsampling.vertical;
Ok(Self {
still_picture: h.still_picture,
max_frame_width: h.max_frame_width,
max_frame_height: h.max_frame_height,
bit_depth: h.color.bit_depth,
seq_profile: h.seq_profile,
chroma_subsampling: h.color.chroma_subsampling,
monochrome: h.color.monochrome,
base_q_idx: frame_quant.map(|fq| fq.base_q_idx),
lossless: frame_quant.map(|fq| fq.coded_lossless && no_chroma_subsampling),
})
}
}
pub struct FrameRef<'a> {
pub data: Cow<'a, [u8]>,
pub alpha_data: Option<Cow<'a, [u8]>>,
pub duration_ms: u32,
}
struct MdatBounds {
offset: u64,
length: u64,
}
struct ItemExtents {
construction_method: ConstructionMethod,
extents: TryVec<ExtentRange>,
}
pub struct AvifParser<'data> {
raw: Cow<'data, [u8]>,
mdat_bounds: TryVec<MdatBounds>,
idat: Option<TryVec<u8>>,
primary: ItemExtents,
alpha: Option<ItemExtents>,
grid_config: Option<GridConfig>,
tiles: TryVec<ItemExtents>,
animation_data: Option<AnimationParserData>,
premultiplied_alpha: bool,
av1_config: Option<AV1Config>,
color_info: Option<ColorInformation>,
rotation: Option<ImageRotation>,
mirror: Option<ImageMirror>,
clean_aperture: Option<CleanAperture>,
pixel_aspect_ratio: Option<PixelAspectRatio>,
content_light_level: Option<ContentLightLevel>,
mastering_display: Option<MasteringDisplayColourVolume>,
content_colour_volume: Option<ContentColourVolume>,
ambient_viewing: Option<AmbientViewingEnvironment>,
operating_point: Option<OperatingPointSelector>,
layer_selector: Option<LayerSelector>,
layered_image_indexing: Option<AV1LayeredImageIndexing>,
exif_item: Option<ItemExtents>,
xmp_item: Option<ItemExtents>,
gain_map_metadata: Option<GainMapMetadata>,
gain_map: Option<ItemExtents>,
gain_map_color_info: Option<ColorInformation>,
depth_item: Option<ItemExtents>,
depth_width: u32,
depth_height: u32,
depth_av1_config: Option<AV1Config>,
depth_color_info: Option<ColorInformation>,
major_brand: [u8; 4],
compatible_brands: std::vec::Vec<[u8; 4]>,
}
struct AnimationParserData {
media_timescale: u32,
sample_table: SampleTable,
alpha_media_timescale: Option<u32>,
alpha_sample_table: Option<SampleTable>,
loop_count: u32,
codec_config: TrackCodecConfig,
}
#[derive(Debug, Clone, Copy)]
pub struct AnimationInfo {
pub frame_count: usize,
pub loop_count: u32,
pub has_alpha: bool,
pub timescale: u32,
}
struct ParsedStructure {
meta: Option<AvifInternalMeta>,
mdat_bounds: TryVec<MdatBounds>,
animation_data: Option<ParsedAnimationData>,
major_brand: [u8; 4],
compatible_brands: std::vec::Vec<[u8; 4]>,
}
impl<'data> AvifParser<'data> {
pub fn from_bytes(data: &'data [u8]) -> Result<Self> {
Self::from_bytes_with_config(data, &DecodeConfig::default(), &Unstoppable)
}
pub fn from_bytes_with_config(
data: &'data [u8],
config: &DecodeConfig,
stop: &dyn Stop,
) -> Result<Self> {
let parsed = Self::parse_raw(data, config, stop)?;
Self::build(Cow::Borrowed(data), parsed, config)
}
pub fn from_owned(data: std::vec::Vec<u8>) -> Result<AvifParser<'static>> {
AvifParser::from_owned_with_config(data, &DecodeConfig::default(), &Unstoppable)
}
pub fn from_owned_with_config(
data: std::vec::Vec<u8>,
config: &DecodeConfig,
stop: &dyn Stop,
) -> Result<AvifParser<'static>> {
let parsed = AvifParser::parse_raw(&data, config, stop)?;
AvifParser::build(Cow::Owned(data), parsed, config)
}
pub fn from_reader<R: Read>(reader: &mut R) -> Result<AvifParser<'static>> {
AvifParser::from_reader_with_config(reader, &DecodeConfig::default(), &Unstoppable)
}
pub fn from_reader_with_config<R: Read>(
reader: &mut R,
config: &DecodeConfig,
stop: &dyn Stop,
) -> Result<AvifParser<'static>> {
let buf = if let Some(limit) = config.peak_memory_limit {
let mut limited = reader.take(limit.saturating_add(1));
let mut buf = std::vec::Vec::new();
limited.read_to_end(&mut buf)?;
if buf.len() as u64 > limit {
return Err(Error::ResourceLimitExceeded(
"input exceeds peak_memory_limit",
));
}
buf
} else {
let mut buf = std::vec::Vec::new();
reader.read_to_end(&mut buf)?;
buf
};
AvifParser::from_owned_with_config(buf, config, stop)
}
fn parse_raw(data: &[u8], config: &DecodeConfig, stop: &dyn Stop) -> Result<ParsedStructure> {
let parse_opts = ParseOptions { lenient: config.lenient };
let mut cursor = std::io::Cursor::new(data);
let mut f = OffsetReader::new(&mut cursor);
let mut iter = BoxIter::with_max_remaining(&mut f, data.len() as u64);
let (major_brand, compatible_brands) = if let Some(mut b) = iter.next_box()? {
if b.head.name == BoxType::FileTypeBox {
let ftyp = read_ftyp(&mut b)?;
if ftyp.major_brand != b"avif" && ftyp.major_brand != b"avis" {
return Err(Error::InvalidData("ftyp must be 'avif' or 'avis'"));
}
let major = ftyp.major_brand.value;
let compat = ftyp.compatible_brands.iter().map(|b| b.value).collect();
(major, compat)
} else {
return Err(Error::InvalidData("'ftyp' box must occur first"));
}
} else {
return Err(Error::InvalidData("'ftyp' box must occur first"));
};
let mut meta = None;
let mut mdat_bounds = TryVec::new();
let mut animation_data: Option<ParsedAnimationData> = None;
while let Some(mut b) = iter.next_box()? {
stop.check()?;
match b.head.name {
BoxType::MetadataBox => {
if meta.is_some() {
return Err(Error::InvalidData(
"There should be zero or one meta boxes per ISO 14496-12:2015 § 8.11.1.1",
));
}
meta = Some(read_avif_meta(&mut b, &parse_opts)?);
}
BoxType::MovieBox => {
let tracks = read_moov(&mut b)?;
if !tracks.is_empty() {
animation_data = Some(associate_tracks(tracks)?);
}
}
BoxType::MediaDataBox => {
if b.bytes_left() > 0 {
let offset = b.offset();
let length = b.bytes_left();
mdat_bounds.push(MdatBounds { offset, length })?;
}
skip_box_content(&mut b)?;
}
_ => skip_box_content(&mut b)?,
}
check_parser_state(&b.head, &b.content)?;
}
if meta.is_none() && animation_data.is_none() {
return Err(Error::InvalidData("missing meta"));
}
Ok(ParsedStructure { meta, mdat_bounds, animation_data, major_brand, compatible_brands })
}
fn build(raw: Cow<'data, [u8]>, parsed: ParsedStructure, config: &DecodeConfig) -> Result<Self> {
let tracker = ResourceTracker::new(config);
let animation_data = if let Some(anim) = parsed.animation_data {
tracker.validate_animation_frames(anim.color_sample_table.sample_sizes.len() as u32)?;
Some(AnimationParserData {
media_timescale: anim.color_timescale,
sample_table: anim.color_sample_table,
alpha_media_timescale: anim.alpha_timescale,
alpha_sample_table: anim.alpha_sample_table,
loop_count: anim.loop_count,
codec_config: anim.color_codec_config,
})
} else {
None
};
let Some(meta) = parsed.meta else {
let track_config = animation_data.as_ref()
.map(|a| a.codec_config.clone())
.unwrap_or_default();
return Ok(Self {
raw,
mdat_bounds: parsed.mdat_bounds,
idat: None,
primary: ItemExtents { construction_method: ConstructionMethod::File, extents: TryVec::new() },
alpha: None,
grid_config: None,
tiles: TryVec::new(),
animation_data,
premultiplied_alpha: false,
av1_config: track_config.av1_config,
color_info: track_config.color_info,
rotation: None,
mirror: None,
clean_aperture: None,
pixel_aspect_ratio: None,
content_light_level: None,
mastering_display: None,
content_colour_volume: None,
ambient_viewing: None,
operating_point: None,
layer_selector: None,
layered_image_indexing: None,
exif_item: None,
xmp_item: None,
gain_map_metadata: None,
gain_map: None,
gain_map_color_info: None,
depth_item: None,
depth_width: 0,
depth_height: 0,
depth_av1_config: None,
depth_color_info: None,
major_brand: parsed.major_brand,
compatible_brands: parsed.compatible_brands,
});
};
let primary = Self::get_item_extents(&meta, meta.primary_item_id)?;
let alpha_item_id = meta
.item_references
.iter()
.filter(|iref| {
iref.to_item_id == meta.primary_item_id
&& iref.from_item_id != meta.primary_item_id
&& iref.item_type == b"auxl"
})
.map(|iref| iref.from_item_id)
.find(|&item_id| {
meta.properties.iter().any(|prop| {
prop.item_id == item_id
&& match &prop.property {
ItemProperty::AuxiliaryType(urn) => {
urn.type_subtype().0 == b"urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"
}
_ => false,
}
})
});
let alpha = alpha_item_id
.map(|id| Self::get_item_extents(&meta, id))
.transpose()?;
let premultiplied_alpha = alpha_item_id.is_some_and(|alpha_id| {
meta.item_references.iter().any(|iref| {
iref.from_item_id == meta.primary_item_id
&& iref.to_item_id == alpha_id
&& iref.item_type == b"prem"
})
});
let depth_item_id = meta
.item_references
.iter()
.filter(|iref| {
iref.to_item_id == meta.primary_item_id
&& iref.from_item_id != meta.primary_item_id
&& iref.item_type == b"auxl"
})
.map(|iref| iref.from_item_id)
.find(|&item_id| {
if alpha_item_id == Some(item_id) {
return false;
}
meta.properties.iter().any(|prop| {
prop.item_id == item_id
&& match &prop.property {
ItemProperty::AuxiliaryType(urn) => {
is_depth_auxiliary_urn(urn.type_subtype().0)
}
_ => false,
}
})
});
let (depth_item, depth_width, depth_height, depth_av1_config, depth_color_info) =
if let Some(depth_id) = depth_item_id {
let extents = Self::get_item_extents(&meta, depth_id)?;
let dims = meta.properties.iter().find_map(|p| {
if p.item_id == depth_id {
match &p.property {
ItemProperty::ImageSpatialExtents(e) => Some((e.width, e.height)),
_ => None,
}
} else {
None
}
});
let (w, h) = dims.unwrap_or((0, 0));
let av1c = meta.properties.iter().find_map(|p| {
if p.item_id == depth_id {
match &p.property {
ItemProperty::AV1Config(c) => Some(c.clone()),
_ => None,
}
} else {
None
}
});
let colr = meta.properties.iter().find_map(|p| {
if p.item_id == depth_id {
match &p.property {
ItemProperty::ColorInformation(c) => Some(c.clone()),
_ => None,
}
} else {
None
}
});
(Some(extents), w, h, av1c, colr)
} else {
(None, 0, 0, None, None)
};
let mut exif_item = None;
let mut xmp_item = None;
for iref in meta.item_references.iter() {
if iref.to_item_id != meta.primary_item_id || iref.item_type != b"cdsc" {
continue;
}
let desc_item_id = iref.from_item_id;
let Some(info) = meta.item_infos.iter().find(|i| i.item_id == desc_item_id) else {
continue;
};
if info.item_type == b"Exif" && exif_item.is_none() {
exif_item = Some(Self::get_item_extents(&meta, desc_item_id)?);
} else if info.item_type == b"mime" && xmp_item.is_none() {
xmp_item = Some(Self::get_item_extents(&meta, desc_item_id)?);
}
}
let is_grid = meta
.item_infos
.iter()
.find(|x| x.item_id == meta.primary_item_id)
.is_some_and(|info| info.item_type == b"grid");
let (grid_config, tiles) = if is_grid {
let mut tiles_with_index: TryVec<(u32, u16)> = TryVec::new();
for iref in meta.item_references.iter() {
if iref.from_item_id == meta.primary_item_id && iref.item_type == b"dimg" {
tiles_with_index.push((iref.to_item_id, iref.reference_index))?;
}
}
tracker.validate_grid_tiles(tiles_with_index.len() as u32)?;
tiles_with_index.sort_by_key(|&(_, idx)| idx);
let mut tile_extents = TryVec::new();
for (tile_id, _) in tiles_with_index.iter() {
tile_extents.push(Self::get_item_extents(&meta, *tile_id)?)?;
}
let mut tile_ids = TryVec::new();
for (tile_id, _) in tiles_with_index.iter() {
tile_ids.push(*tile_id)?;
}
let grid_config = Self::calculate_grid_config(&meta, &tile_ids)?;
for (tile_id, _) in tiles_with_index.iter() {
for prop in meta.properties.iter() {
if prop.item_id == *tile_id {
match &prop.property {
ItemProperty::Rotation(_)
| ItemProperty::Mirror(_)
| ItemProperty::CleanAperture(_) => {
warn!("grid tile {} has a transformative property (irot/imir/clap), violating AVIF spec", tile_id);
}
_ => {}
}
}
}
}
(Some(grid_config), tile_extents)
} else {
(None, TryVec::new())
};
let (gain_map_metadata, gain_map, gain_map_color_info) = {
let tmap_item = meta.item_infos.iter()
.find(|info| info.item_type == b"tmap");
if let Some(tmap_info) = tmap_item {
let tmap_id = tmap_info.item_id;
let mut inputs: TryVec<(u32, u16)> = TryVec::new();
for iref in meta.item_references.iter() {
if iref.from_item_id == tmap_id && iref.item_type == b"dimg" {
inputs.push((iref.to_item_id, iref.reference_index))?;
}
}
inputs.sort_by_key(|&(_, idx)| idx);
if inputs.len() >= 2 {
let base_item_id = inputs[0].0;
let gmap_item_id = inputs[1].0;
if base_item_id == meta.primary_item_id {
let tmap_extents = Self::get_item_extents(&meta, tmap_id)?;
let tmap_data = Self::resolve_extents_from_raw(
raw.as_ref(), &parsed.mdat_bounds, &tmap_extents,
)?;
let metadata = parse_tone_map_image(&tmap_data)?;
let gmap_extents = Self::get_item_extents(&meta, gmap_item_id)?;
let alt_color = meta.properties.iter().find_map(|p| {
if p.item_id == tmap_id {
match &p.property {
ItemProperty::ColorInformation(c) => Some(c.clone()),
_ => None,
}
} else {
None
}
});
(Some(metadata), Some(gmap_extents), alt_color)
} else {
(None, None, None)
}
} else {
(None, None, None)
}
} else {
(None, None, None)
}
};
macro_rules! find_prop {
($variant:ident) => {
meta.properties.iter().find_map(|p| {
if p.item_id == meta.primary_item_id {
match &p.property {
ItemProperty::$variant(c) => Some(c.clone()),
_ => None,
}
} else {
None
}
})
};
}
let track_config = animation_data.as_ref().map(|a| &a.codec_config);
let av1_config = find_prop!(AV1Config)
.or_else(|| track_config.and_then(|c| c.av1_config.clone()));
let color_info = find_prop!(ColorInformation)
.or_else(|| track_config.and_then(|c| c.color_info.clone()));
let rotation = find_prop!(Rotation);
let mirror = find_prop!(Mirror);
let clean_aperture = find_prop!(CleanAperture);
let pixel_aspect_ratio = find_prop!(PixelAspectRatio);
let content_light_level = find_prop!(ContentLightLevel);
let mastering_display = find_prop!(MasteringDisplayColourVolume);
let content_colour_volume = find_prop!(ContentColourVolume);
let ambient_viewing = find_prop!(AmbientViewingEnvironment);
let operating_point = find_prop!(OperatingPointSelector);
let layer_selector = find_prop!(LayerSelector);
let layered_image_indexing = find_prop!(AV1LayeredImageIndexing);
let idat = if let Some(ref idat_data) = meta.idat {
let mut cloned = TryVec::new();
cloned.extend_from_slice(idat_data)?;
Some(cloned)
} else {
None
};
Ok(Self {
raw,
mdat_bounds: parsed.mdat_bounds,
idat,
primary,
alpha,
grid_config,
tiles,
animation_data,
premultiplied_alpha,
av1_config,
color_info,
rotation,
mirror,
clean_aperture,
pixel_aspect_ratio,
content_light_level,
mastering_display,
content_colour_volume,
ambient_viewing,
operating_point,
layer_selector,
layered_image_indexing,
exif_item,
xmp_item,
gain_map_metadata,
gain_map,
gain_map_color_info,
depth_item,
depth_width,
depth_height,
depth_av1_config,
depth_color_info,
major_brand: parsed.major_brand,
compatible_brands: parsed.compatible_brands,
})
}
fn get_item_extents(meta: &AvifInternalMeta, item_id: u32) -> Result<ItemExtents> {
let item = meta
.iloc_items
.iter()
.find(|item| item.item_id == item_id)
.ok_or(Error::InvalidData("item not found in iloc"))?;
let mut extents = TryVec::new();
for extent in &item.extents {
extents.push(extent.extent_range.clone())?;
}
Ok(ItemExtents {
construction_method: item.construction_method,
extents,
})
}
fn resolve_extents_from_raw(
raw: &[u8],
mdat_bounds: &[MdatBounds],
item: &ItemExtents,
) -> Result<std::vec::Vec<u8>> {
if item.construction_method != ConstructionMethod::File {
return Err(Error::Unsupported("tmap item must use file construction method"));
}
let mut data = std::vec::Vec::new();
for extent in &item.extents {
let file_offset = extent.start();
let start = usize::try_from(file_offset)?;
let end = match extent {
ExtentRange::WithLength(range) => {
let len = range.end.checked_sub(range.start)
.ok_or(Error::InvalidData("extent range start > end"))?;
start.checked_add(usize::try_from(len)?)
.ok_or(Error::InvalidData("extent end overflow"))?
}
ExtentRange::ToEnd(_) => {
let mut found_end = raw.len();
for mdat in mdat_bounds {
if file_offset >= mdat.offset && file_offset < mdat.offset + mdat.length {
found_end = usize::try_from(mdat.offset + mdat.length)?;
break;
}
}
found_end
}
};
let slice = raw.get(start..end)
.ok_or(Error::InvalidData("tmap extent out of bounds"))?;
data.extend_from_slice(slice);
}
Ok(data)
}
fn resolve_item(&self, item: &ItemExtents) -> Result<Cow<'_, [u8]>> {
match item.construction_method {
ConstructionMethod::Idat => self.resolve_idat_extents(&item.extents),
ConstructionMethod::File => self.resolve_file_extents(&item.extents),
ConstructionMethod::Item => Err(Error::Unsupported("construction_method 'item' not supported")),
}
}
fn resolve_file_extents(&self, extents: &[ExtentRange]) -> Result<Cow<'_, [u8]>> {
let raw = self.raw.as_ref();
if extents.len() == 1 {
let extent = &extents[0];
let (start, end) = self.extent_byte_range(extent)?;
let slice = raw.get(start..end).ok_or(Error::InvalidData("extent out of bounds in raw buffer"))?;
return Ok(Cow::Borrowed(slice));
}
let mut data = TryVec::new();
for extent in extents {
let (start, end) = self.extent_byte_range(extent)?;
let slice = raw.get(start..end).ok_or(Error::InvalidData("extent out of bounds in raw buffer"))?;
data.extend_from_slice(slice)?;
}
Ok(Cow::Owned(data.into_iter().collect()))
}
fn extent_byte_range(&self, extent: &ExtentRange) -> Result<(usize, usize)> {
let file_offset = extent.start();
let start = usize::try_from(file_offset)?;
match extent {
ExtentRange::WithLength(range) => {
let len = range.end.checked_sub(range.start)
.ok_or(Error::InvalidData("extent range start > end"))?;
let end = start.checked_add(usize::try_from(len)?)
.ok_or(Error::InvalidData("extent end overflow"))?;
Ok((start, end))
}
ExtentRange::ToEnd(_) => {
for mdat in &self.mdat_bounds {
if file_offset >= mdat.offset && file_offset < mdat.offset + mdat.length {
let end = usize::try_from(mdat.offset + mdat.length)?;
return Ok((start, end));
}
}
Ok((start, self.raw.len()))
}
}
}
fn resolve_idat_extents(&self, extents: &[ExtentRange]) -> Result<Cow<'_, [u8]>> {
let idat_data = self.idat.as_ref()
.ok_or(Error::InvalidData("idat box missing but construction_method is Idat"))?;
if extents.len() == 1 {
let extent = &extents[0];
let start = usize::try_from(extent.start())?;
let slice = match extent {
ExtentRange::WithLength(range) => {
let len = usize::try_from(range.end - range.start)?;
idat_data.get(start..start + len)
.ok_or(Error::InvalidData("idat extent out of bounds"))?
}
ExtentRange::ToEnd(_) => {
idat_data.get(start..)
.ok_or(Error::InvalidData("idat extent out of bounds"))?
}
};
return Ok(Cow::Borrowed(slice));
}
let mut data = TryVec::new();
for extent in extents {
let start = usize::try_from(extent.start())?;
let slice = match extent {
ExtentRange::WithLength(range) => {
let len = usize::try_from(range.end - range.start)?;
idat_data.get(start..start + len)
.ok_or(Error::InvalidData("idat extent out of bounds"))?
}
ExtentRange::ToEnd(_) => {
idat_data.get(start..)
.ok_or(Error::InvalidData("idat extent out of bounds"))?
}
};
data.extend_from_slice(slice)?;
}
Ok(Cow::Owned(data.into_iter().collect()))
}
fn resolve_frame(&self, index: usize) -> Result<FrameRef<'_>> {
let anim = self.animation_data.as_ref()
.ok_or(Error::InvalidData("not an animated AVIF"))?;
if index >= anim.sample_table.sample_sizes.len() {
return Err(Error::InvalidData("frame index out of bounds"));
}
let duration_ms = self.calculate_frame_duration(&anim.sample_table, anim.media_timescale, index)?;
let (offset, size) = self.calculate_sample_location(&anim.sample_table, index)?;
let start = usize::try_from(offset)?;
let end = start.checked_add(size as usize)
.ok_or(Error::InvalidData("frame end overflow"))?;
let raw = self.raw.as_ref();
let slice = raw.get(start..end)
.ok_or(Error::InvalidData("frame not found in raw buffer"))?;
let alpha_data = if let Some(ref alpha_st) = anim.alpha_sample_table {
let alpha_timescale = anim.alpha_media_timescale.unwrap_or(anim.media_timescale);
if index < alpha_st.sample_sizes.len() {
let (a_offset, a_size) = self.calculate_sample_location(alpha_st, index)?;
let a_start = usize::try_from(a_offset)?;
let a_end = a_start.checked_add(a_size as usize)
.ok_or(Error::InvalidData("alpha frame end overflow"))?;
let a_slice = raw.get(a_start..a_end)
.ok_or(Error::InvalidData("alpha frame not found in raw buffer"))?;
let _ = alpha_timescale; Some(Cow::Borrowed(a_slice))
} else {
warn!("alpha track has fewer frames than color track (index {})", index);
None
}
} else {
None
};
Ok(FrameRef {
data: Cow::Borrowed(slice),
alpha_data,
duration_ms,
})
}
fn calculate_grid_config(meta: &AvifInternalMeta, tile_ids: &[u32]) -> Result<GridConfig> {
for prop in &meta.properties {
if prop.item_id == meta.primary_item_id
&& let ItemProperty::ImageGrid(grid) = &prop.property {
return Ok(grid.clone());
}
}
let grid_dims = meta
.properties
.iter()
.find(|p| p.item_id == meta.primary_item_id)
.and_then(|p| match &p.property {
ItemProperty::ImageSpatialExtents(e) => Some(e),
_ => None,
});
let tile_dims = tile_ids.first().and_then(|&tile_id| {
meta.properties
.iter()
.find(|p| p.item_id == tile_id)
.and_then(|p| match &p.property {
ItemProperty::ImageSpatialExtents(e) => Some(e),
_ => None,
})
});
if let (Some(grid), Some(tile)) = (grid_dims, tile_dims)
&& tile.width != 0
&& tile.height != 0
&& grid.width % tile.width == 0
&& grid.height % tile.height == 0
{
let columns = grid.width / tile.width;
let rows = grid.height / tile.height;
if columns <= 255 && rows <= 255 {
return Ok(GridConfig {
rows: rows as u8,
columns: columns as u8,
output_width: grid.width,
output_height: grid.height,
});
}
}
let tile_count = tile_ids.len();
Ok(GridConfig {
rows: tile_count.min(255) as u8,
columns: 1,
output_width: 0,
output_height: 0,
})
}
fn calculate_frame_duration(
&self,
st: &SampleTable,
timescale: u32,
index: usize,
) -> Result<u32> {
let mut current_sample = 0;
for entry in &st.time_to_sample {
if current_sample + entry.sample_count as usize > index {
let duration_ms = if timescale > 0 {
((entry.sample_delta as u64) * 1000) / (timescale as u64)
} else {
0
};
return Ok(u32::try_from(duration_ms).unwrap_or(u32::MAX));
}
current_sample += entry.sample_count as usize;
}
Ok(0)
}
fn calculate_sample_location(&self, st: &SampleTable, index: usize) -> Result<(u64, u32)> {
let offset = *st
.sample_offsets
.get(index)
.ok_or(Error::InvalidData("sample index out of bounds"))?;
let size = *st
.sample_sizes
.get(index)
.ok_or(Error::InvalidData("sample index out of bounds"))?;
Ok((offset, size))
}
pub fn primary_data(&self) -> Result<Cow<'_, [u8]>> {
self.resolve_item(&self.primary)
}
pub fn alpha_data(&self) -> Option<Result<Cow<'_, [u8]>>> {
self.alpha.as_ref().map(|item| self.resolve_item(item))
}
pub fn tile_data(&self, index: usize) -> Result<Cow<'_, [u8]>> {
let item = self.tiles.get(index)
.ok_or(Error::InvalidData("tile index out of bounds"))?;
self.resolve_item(item)
}
pub fn frame(&self, index: usize) -> Result<FrameRef<'_>> {
self.resolve_frame(index)
}
pub fn frames(&self) -> FrameIterator<'_> {
let count = self
.animation_info()
.map(|info| info.frame_count)
.unwrap_or(0);
FrameIterator { parser: self, index: 0, count }
}
pub fn animation_info(&self) -> Option<AnimationInfo> {
self.animation_data.as_ref().map(|data| AnimationInfo {
frame_count: data.sample_table.sample_sizes.len(),
loop_count: data.loop_count,
has_alpha: data.alpha_sample_table.is_some(),
timescale: data.media_timescale,
})
}
pub fn grid_config(&self) -> Option<&GridConfig> {
self.grid_config.as_ref()
}
pub fn grid_tile_count(&self) -> usize {
self.tiles.len()
}
pub fn premultiplied_alpha(&self) -> bool {
self.premultiplied_alpha
}
pub fn av1_config(&self) -> Option<&AV1Config> {
self.av1_config.as_ref()
}
pub fn color_info(&self) -> Option<&ColorInformation> {
self.color_info.as_ref()
}
pub fn rotation(&self) -> Option<&ImageRotation> {
self.rotation.as_ref()
}
pub fn mirror(&self) -> Option<&ImageMirror> {
self.mirror.as_ref()
}
pub fn clean_aperture(&self) -> Option<&CleanAperture> {
self.clean_aperture.as_ref()
}
pub fn pixel_aspect_ratio(&self) -> Option<&PixelAspectRatio> {
self.pixel_aspect_ratio.as_ref()
}
pub fn content_light_level(&self) -> Option<&ContentLightLevel> {
self.content_light_level.as_ref()
}
pub fn mastering_display(&self) -> Option<&MasteringDisplayColourVolume> {
self.mastering_display.as_ref()
}
pub fn content_colour_volume(&self) -> Option<&ContentColourVolume> {
self.content_colour_volume.as_ref()
}
pub fn ambient_viewing(&self) -> Option<&AmbientViewingEnvironment> {
self.ambient_viewing.as_ref()
}
pub fn operating_point(&self) -> Option<&OperatingPointSelector> {
self.operating_point.as_ref()
}
pub fn layer_selector(&self) -> Option<&LayerSelector> {
self.layer_selector.as_ref()
}
pub fn layered_image_indexing(&self) -> Option<&AV1LayeredImageIndexing> {
self.layered_image_indexing.as_ref()
}
pub fn exif(&self) -> Option<Result<Cow<'_, [u8]>>> {
self.exif_item.as_ref().map(|item| {
let raw = self.resolve_item(item)?;
if raw.len() <= 4 {
return Err(Error::InvalidData("EXIF item too short"));
}
let offset = u32::from_be_bytes([raw[0], raw[1], raw[2], raw[3]]) as usize;
let start = 4 + offset;
if start >= raw.len() {
return Err(Error::InvalidData("EXIF offset exceeds item size"));
}
match raw {
Cow::Borrowed(slice) => Ok(Cow::Borrowed(&slice[start..])),
Cow::Owned(vec) => Ok(Cow::Owned(vec[start..].to_vec())),
}
})
}
pub fn xmp(&self) -> Option<Result<Cow<'_, [u8]>>> {
self.xmp_item.as_ref().map(|item| self.resolve_item(item))
}
pub fn gain_map_metadata(&self) -> Option<&GainMapMetadata> {
self.gain_map_metadata.as_ref()
}
pub fn gain_map_data(&self) -> Option<Result<Cow<'_, [u8]>>> {
self.gain_map.as_ref().map(|item| self.resolve_item(item))
}
pub fn gain_map_color_info(&self) -> Option<&ColorInformation> {
self.gain_map_color_info.as_ref()
}
pub fn gain_map(&self) -> Option<Result<AvifGainMap>> {
let metadata = self.gain_map_metadata.as_ref()?.clone();
let data_extents = self.gain_map.as_ref()?;
let alt_color_info = self.gain_map_color_info.clone();
Some(self.resolve_item(data_extents).map(|data| AvifGainMap {
metadata,
gain_map_data: data.into_owned(),
alt_color_info,
}))
}
pub fn has_depth_map(&self) -> bool {
self.depth_item.is_some()
}
pub fn depth_map_data(&self) -> Option<Result<Cow<'_, [u8]>>> {
self.depth_item.as_ref().map(|item| self.resolve_item(item))
}
pub fn depth_map(&self) -> Option<Result<AvifDepthMap>> {
let data_extents = self.depth_item.as_ref()?;
let av1_config = self.depth_av1_config.clone();
let color_info = self.depth_color_info.clone();
let width = self.depth_width;
let height = self.depth_height;
Some(self.resolve_item(data_extents).map(|data| AvifDepthMap {
data: data.into_owned(),
width,
height,
av1_config,
color_info,
}))
}
pub fn major_brand(&self) -> &[u8; 4] {
&self.major_brand
}
pub fn compatible_brands(&self) -> &[[u8; 4]] {
&self.compatible_brands
}
pub fn primary_metadata(&self) -> Result<AV1Metadata> {
let data = self.primary_data()?;
AV1Metadata::parse_av1_bitstream(&data)
}
pub fn alpha_metadata(&self) -> Option<Result<AV1Metadata>> {
self.alpha.as_ref().map(|item| {
let data = self.resolve_item(item)?;
AV1Metadata::parse_av1_bitstream(&data)
})
}
#[cfg(feature = "eager")]
#[deprecated(since = "1.5.0", note = "Use AvifParser methods directly instead of converting to AvifData")]
#[allow(deprecated)]
pub fn to_avif_data(&self) -> Result<AvifData> {
let primary_data = self.primary_data()?;
let mut primary_item = TryVec::new();
primary_item.extend_from_slice(&primary_data)?;
let alpha_item = match self.alpha_data() {
Some(Ok(data)) => {
let mut v = TryVec::new();
v.extend_from_slice(&data)?;
Some(v)
}
Some(Err(e)) => return Err(e),
None => None,
};
let mut grid_tiles = TryVec::new();
for i in 0..self.grid_tile_count() {
let data = self.tile_data(i)?;
let mut v = TryVec::new();
v.extend_from_slice(&data)?;
grid_tiles.push(v)?;
}
let animation = if let Some(info) = self.animation_info() {
let mut frames = TryVec::new();
for i in 0..info.frame_count {
let frame_ref = self.frame(i)?;
let mut data = TryVec::new();
data.extend_from_slice(&frame_ref.data)?;
frames.push(AnimationFrame { data, duration_ms: frame_ref.duration_ms })?;
}
Some(AnimationConfig {
loop_count: info.loop_count,
frames,
})
} else {
None
};
Ok(AvifData {
primary_item,
alpha_item,
premultiplied_alpha: self.premultiplied_alpha,
grid_config: self.grid_config.clone(),
grid_tiles,
animation,
av1_config: self.av1_config.clone(),
color_info: self.color_info.clone(),
rotation: self.rotation,
mirror: self.mirror,
clean_aperture: self.clean_aperture,
pixel_aspect_ratio: self.pixel_aspect_ratio,
content_light_level: self.content_light_level,
mastering_display: self.mastering_display,
content_colour_volume: self.content_colour_volume,
ambient_viewing: self.ambient_viewing,
operating_point: self.operating_point,
layer_selector: self.layer_selector,
layered_image_indexing: self.layered_image_indexing,
exif: self.exif().and_then(|r| r.ok()).map(|c| {
let mut v = TryVec::new();
let _ = v.extend_from_slice(&c);
v
}),
xmp: self.xmp().and_then(|r| r.ok()).map(|c| {
let mut v = TryVec::new();
let _ = v.extend_from_slice(&c);
v
}),
gain_map_metadata: self.gain_map_metadata.clone(),
gain_map_item: self.gain_map_data().and_then(|r| r.ok()).map(|c| {
let mut v = TryVec::new();
let _ = v.extend_from_slice(&c);
v
}),
gain_map_color_info: self.gain_map_color_info.clone(),
depth_item: self.depth_map_data().and_then(|r| r.ok()).map(|c| {
let mut v = TryVec::new();
let _ = v.extend_from_slice(&c);
v
}),
depth_width: self.depth_width,
depth_height: self.depth_height,
depth_av1_config: self.depth_av1_config.clone(),
depth_color_info: self.depth_color_info.clone(),
major_brand: self.major_brand,
compatible_brands: self.compatible_brands.clone(),
})
}
}
pub struct FrameIterator<'a> {
parser: &'a AvifParser<'a>,
index: usize,
count: usize,
}
impl<'a> Iterator for FrameIterator<'a> {
type Item = Result<FrameRef<'a>>;
fn next(&mut self) -> Option<Self::Item> {
if self.index >= self.count {
return None;
}
let result = self.parser.frame(self.index);
self.index += 1;
Some(result)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.count.saturating_sub(self.index);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for FrameIterator<'_> {
fn len(&self) -> usize {
self.count.saturating_sub(self.index)
}
}
struct AvifInternalMeta {
item_references: TryVec<SingleItemTypeReferenceBox>,
properties: TryVec<AssociatedProperty>,
primary_item_id: u32,
iloc_items: TryVec<ItemLocationBoxItem>,
item_infos: TryVec<ItemInfoEntry>,
idat: Option<TryVec<u8>>,
#[allow(dead_code)] entity_groups: TryVec<EntityGroup>,
}
#[cfg(feature = "eager")]
struct MediaDataBox {
offset: u64,
data: TryVec<u8>,
}
#[cfg(feature = "eager")]
impl MediaDataBox {
fn contains_extent(&self, extent: &ExtentRange) -> bool {
if self.offset <= extent.start() {
let start_offset = extent.start() - self.offset;
start_offset < self.data.len().to_u64()
} else {
false
}
}
fn matches_extent(&self, extent: &ExtentRange) -> bool {
if self.offset == extent.start() {
match extent {
ExtentRange::WithLength(range) => {
if let Some(end) = self.offset.checked_add(self.data.len().to_u64()) {
end == range.end
} else {
false
}
},
ExtentRange::ToEnd(_) => true,
}
} else {
false
}
}
fn read_extent(&self, extent: &ExtentRange, buf: &mut TryVec<u8>) -> Result<()> {
let start_offset = extent
.start()
.checked_sub(self.offset)
.ok_or(Error::InvalidData("mdat does not contain extent"))?;
let slice = match extent {
ExtentRange::WithLength(range) => {
let range_len = range
.end
.checked_sub(range.start)
.ok_or(Error::InvalidData("range start > end"))?;
let end = start_offset
.checked_add(range_len)
.ok_or(Error::InvalidData("extent end overflow"))?;
self.data.get(start_offset.try_into()?..end.try_into()?)
},
ExtentRange::ToEnd(_) => self.data.get(start_offset.try_into()?..),
};
let slice = slice.ok_or(Error::InvalidData("extent crosses box boundary"))?;
buf.extend_from_slice(slice)?;
Ok(())
}
}
#[derive(Debug)]
struct ItemInfoEntry {
item_id: u32,
item_type: FourCC,
}
#[derive(Debug)]
struct SingleItemTypeReferenceBox {
item_type: FourCC,
from_item_id: u32,
to_item_id: u32,
reference_index: u16,
}
#[derive(Debug)]
enum IlocFieldSize {
Zero,
Four,
Eight,
}
impl IlocFieldSize {
const fn to_bits(&self) -> u8 {
match self {
Self::Zero => 0,
Self::Four => 32,
Self::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),
_ => Err(Error::InvalidData("value must be in the set {0, 4, 8}")),
}
}
}
#[derive(PartialEq)]
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")),
}
}
}
#[derive(Debug)]
struct ItemLocationBoxItem {
item_id: u32,
construction_method: ConstructionMethod,
extents: TryVec<ItemLocationBoxExtent>,
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum ConstructionMethod {
File,
Idat,
#[allow(dead_code)] Item,
}
#[derive(Clone, Debug)]
struct ItemLocationBoxExtent {
extent_range: ExtentRange,
}
#[derive(Clone, Debug)]
enum ExtentRange {
WithLength(Range<u64>),
ToEnd(RangeFrom<u64>),
}
impl ExtentRange {
const fn start(&self) -> u64 {
match self {
Self::WithLength(r) => r.start,
Self::ToEnd(r) => r.start,
}
}
}
struct BMFFBox<'a, T> {
head: BoxHeader,
content: Take<&'a mut T>,
}
impl<T: Read> BMFFBox<'_, T> {
fn read_into_try_vec(&mut self) -> std::io::Result<TryVec<u8>> {
let limit = self.content.limit();
const MAX_PREALLOC: u64 = 256 * 1024 * 1024;
let mut vec = if limit >= u64::MAX - BoxHeader::MIN_LARGE_SIZE {
std::vec::Vec::new()
} else {
let mut v = std::vec::Vec::new();
v.try_reserve_exact(limit.min(MAX_PREALLOC) as usize)
.map_err(|_| std::io::ErrorKind::OutOfMemory)?;
v
};
self.content.read_to_end(&mut vec)?; Ok(vec.into())
}
}
#[test]
fn box_read_to_end() {
let tmp = &mut b"1234567890".as_slice();
let mut src = BMFFBox {
head: BoxHeader { name: BoxType::FileTypeBox, size: 5, offset: 0, uuid: None },
content: <_ as Read>::take(tmp, 5),
};
let buf = src.read_into_try_vec().unwrap();
assert_eq!(buf.len(), 5);
assert_eq!(buf, b"12345".as_ref());
}
#[test]
fn box_read_to_end_large_claim() {
let tmp = &mut b"1234567890".as_slice();
let mut src = BMFFBox {
head: BoxHeader { name: BoxType::FileTypeBox, size: 5, offset: 0, uuid: None },
content: <_ as Read>::take(tmp, u64::MAX / 2),
};
let buf = src.read_into_try_vec().unwrap();
assert_eq!(buf.len(), 10);
}
struct BoxIter<'a, T> {
src: &'a mut T,
max_remaining: u64,
}
impl<T: Read> BoxIter<'_, T> {
#[cfg(feature = "eager")]
fn new(src: &mut T) -> BoxIter<'_, T> {
BoxIter { src, max_remaining: u64::MAX }
}
fn with_max_remaining(src: &mut T, max_remaining: u64) -> BoxIter<'_, T> {
BoxIter { src, max_remaining }
}
fn next_box(&mut self) -> Result<Option<BMFFBox<'_, T>>> {
let r = read_box_header(self.src);
match r {
Ok(h) => {
let claimed = h.size - h.offset;
let clamped = claimed.min(self.max_remaining);
self.max_remaining = self.max_remaining.saturating_sub(clamped.saturating_add(h.offset));
Ok(Some(BMFFBox {
head: h,
content: self.src.take(clamped),
}))
}
Err(Error::UnexpectedEOF) => Ok(None),
Err(e) => Err(e),
}
}
}
impl<T: Read> Read for BMFFBox<'_, T> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.content.read(buf)
}
}
impl<T: Offset> Offset for BMFFBox<'_, T> {
fn offset(&self) -> u64 {
self.content.get_ref().offset()
}
}
impl<T: Read> BMFFBox<'_, T> {
fn bytes_left(&self) -> u64 {
self.content.limit()
}
const fn get_header(&self) -> &BoxHeader {
&self.head
}
fn box_iter(&mut self) -> BoxIter<'_, Self> {
BoxIter::with_max_remaining(self, self.bytes_left())
}
}
impl<T> Drop for BMFFBox<'_, T> {
fn drop(&mut self) {
if self.content.limit() > 0 {
let name: FourCC = From::from(self.head.name);
debug!("Dropping {} bytes in '{}'", self.content.limit(), name);
}
}
}
fn read_box_header<T: ReadBytesExt>(src: &mut T) -> Result<BoxHeader> {
let size32 = be_u32(src)?;
let name = BoxType::from(be_u32(src)?);
let size = match size32 {
0 => {
u64::MAX
},
1 => {
let size64 = be_u64(src)?;
if size64 < BoxHeader::MIN_LARGE_SIZE {
return Err(Error::InvalidData("malformed wide size"));
}
size64
},
_ => {
if u64::from(size32) < BoxHeader::MIN_SIZE {
return Err(Error::InvalidData("malformed size"));
}
u64::from(size32)
},
};
let mut offset = match size32 {
1 => BoxHeader::MIN_LARGE_SIZE,
_ => BoxHeader::MIN_SIZE,
};
let uuid = if name == BoxType::UuidBox {
if size >= offset + 16 {
let mut buffer = [0u8; 16];
let count = src.read(&mut buffer)?;
offset += count.to_u64();
if count == 16 {
Some(buffer)
} else {
debug!("malformed uuid (short read), skipping");
None
}
} else {
debug!("malformed uuid, skipping");
None
}
} else {
None
};
if offset > size {
return Err(Error::InvalidData("box header offset exceeds size"));
}
Ok(BoxHeader { name, size, offset, uuid })
}
fn read_fullbox_extra<T: ReadBytesExt>(src: &mut T) -> Result<(u8, u32)> {
let version = src.read_u8()?;
let flags_a = src.read_u8()?;
let flags_b = src.read_u8()?;
let flags_c = src.read_u8()?;
Ok((
version,
u32::from(flags_a) << 16 | u32::from(flags_b) << 8 | u32::from(flags_c),
))
}
fn read_fullbox_version_no_flags<T: ReadBytesExt>(src: &mut T, options: &ParseOptions) -> Result<u8> {
let (version, flags) = read_fullbox_extra(src)?;
if flags != 0 && !options.lenient {
return Err(Error::Unsupported("expected flags to be 0"));
}
Ok(version)
}
fn skip_box_content<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<()> {
let to_skip = {
let header = src.get_header();
debug!("{header:?} (skipped)");
header
.size
.checked_sub(header.offset)
.ok_or(Error::InvalidData("header offset > size"))?
};
if to_skip != src.bytes_left() {
return Err(Error::InvalidData("box content size mismatch"));
}
skip(src, to_skip)
}
fn skip_box_remain<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<()> {
let remain = {
let header = src.get_header();
let len = src.bytes_left();
debug!("remain {len} (skipped) in {header:?}");
len
};
skip(src, remain)
}
struct ResourceTracker<'a> {
config: &'a DecodeConfig,
#[cfg(feature = "eager")]
current_memory: u64,
#[cfg(feature = "eager")]
peak_memory: u64,
}
impl<'a> ResourceTracker<'a> {
fn new(config: &'a DecodeConfig) -> Self {
Self {
config,
#[cfg(feature = "eager")]
current_memory: 0,
#[cfg(feature = "eager")]
peak_memory: 0,
}
}
#[cfg(feature = "eager")]
fn reserve(&mut self, bytes: u64) -> Result<()> {
self.current_memory = self.current_memory.saturating_add(bytes);
self.peak_memory = self.peak_memory.max(self.current_memory);
if let Some(limit) = self.config.peak_memory_limit
&& self.peak_memory > limit {
return Err(Error::ResourceLimitExceeded("peak memory limit exceeded"));
}
Ok(())
}
#[cfg(feature = "eager")]
fn release(&mut self, bytes: u64) {
self.current_memory = self.current_memory.saturating_sub(bytes);
}
#[cfg(feature = "eager")]
fn validate_total_megapixels(&self, width: u32, height: u32) -> Result<()> {
if let Some(limit) = self.config.total_megapixels_limit {
let megapixels = (width as u64)
.checked_mul(height as u64)
.ok_or(Error::InvalidData("dimension overflow"))?
/ 1_000_000;
if megapixels > limit as u64 {
return Err(Error::ResourceLimitExceeded("total megapixels limit exceeded"));
}
}
Ok(())
}
fn validate_animation_frames(&self, count: u32) -> Result<()> {
if let Some(limit) = self.config.max_animation_frames
&& count > limit {
return Err(Error::ResourceLimitExceeded("animation frame count limit exceeded"));
}
Ok(())
}
fn validate_grid_tiles(&self, count: u32) -> Result<()> {
if let Some(limit) = self.config.max_grid_tiles
&& count > limit {
return Err(Error::ResourceLimitExceeded("grid tile count limit exceeded"));
}
Ok(())
}
}
#[cfg(feature = "eager")]
#[deprecated(since = "1.5.0", note = "Use `AvifParser::from_reader_with_config()` instead")]
#[allow(deprecated)]
pub fn read_avif_with_config<T: Read>(
f: &mut T,
config: &DecodeConfig,
stop: &dyn Stop,
) -> Result<AvifData> {
let mut tracker = ResourceTracker::new(config);
let mut f = OffsetReader::new(f);
let mut iter = BoxIter::new(&mut f);
let (major_brand, compatible_brands) = if let Some(mut b) = iter.next_box()? {
if b.head.name == BoxType::FileTypeBox {
let ftyp = read_ftyp(&mut b)?;
if ftyp.major_brand != b"avif" && ftyp.major_brand != b"avis" {
warn!("major_brand: {}", ftyp.major_brand);
return Err(Error::InvalidData("ftyp must be 'avif' or 'avis'"));
}
let major = ftyp.major_brand.value;
let compat = ftyp.compatible_brands.iter().map(|b| b.value).collect();
(major, compat)
} else {
return Err(Error::InvalidData("'ftyp' box must occur first"));
}
} else {
return Err(Error::InvalidData("'ftyp' box must occur first"));
};
let mut meta = None;
let mut mdats = TryVec::new();
let mut animation_data: Option<ParsedAnimationData> = None;
let parse_opts = ParseOptions { lenient: config.lenient };
while let Some(mut b) = iter.next_box()? {
stop.check()?;
match b.head.name {
BoxType::MetadataBox => {
if meta.is_some() {
return Err(Error::InvalidData("There should be zero or one meta boxes per ISO 14496-12:2015 § 8.11.1.1"));
}
meta = Some(read_avif_meta(&mut b, &parse_opts)?);
},
BoxType::MovieBox => {
let tracks = read_moov(&mut b)?;
if !tracks.is_empty() {
animation_data = Some(associate_tracks(tracks)?);
}
},
BoxType::MediaDataBox => {
if b.bytes_left() > 0 {
let offset = b.offset();
let size = b.bytes_left();
tracker.reserve(size)?;
let data = b.read_into_try_vec()?;
tracker.release(size);
mdats.push(MediaDataBox { offset, data })?;
}
},
_ => skip_box_content(&mut b)?,
}
check_parser_state(&b.head, &b.content)?;
}
if meta.is_none() && animation_data.is_none() {
return Err(Error::InvalidData("missing meta"));
}
let Some(meta) = meta else {
return Ok(AvifData {
..Default::default()
});
};
let is_grid = meta
.item_infos
.iter()
.find(|x| x.item_id == meta.primary_item_id)
.is_some_and(|info| {
let is_g = info.item_type == b"grid";
if is_g {
log::debug!("Grid image detected: primary_item_id={}", meta.primary_item_id);
}
is_g
});
let mut grid_config = if is_grid {
meta.properties
.iter()
.find(|prop| {
prop.item_id == meta.primary_item_id
&& matches!(prop.property, ItemProperty::ImageGrid(_))
})
.and_then(|prop| match &prop.property {
ItemProperty::ImageGrid(config) => {
log::debug!("Grid: found explicit ImageGrid property: {:?}", config);
Some(config.clone())
},
_ => None,
})
} else {
None
};
let tile_item_ids: TryVec<u32> = if is_grid {
let mut tiles_with_index: TryVec<(u32, u16)> = TryVec::new();
for iref in meta.item_references.iter() {
if iref.from_item_id == meta.primary_item_id && iref.item_type == b"dimg" {
tiles_with_index.push((iref.to_item_id, iref.reference_index))?;
}
}
tracker.validate_grid_tiles(tiles_with_index.len() as u32)?;
tiles_with_index.sort_by_key(|&(_, idx)| idx);
let mut ids = TryVec::new();
for (tile_id, _) in tiles_with_index.iter() {
ids.push(*tile_id)?;
}
if grid_config.is_none() && !ids.is_empty() {
let grid_dims = meta.properties.iter()
.find(|p| p.item_id == meta.primary_item_id)
.and_then(|p| match &p.property {
ItemProperty::ImageSpatialExtents(e) => Some(e),
_ => None,
});
let tile_dims = ids.first().and_then(|&tile_id| {
meta.properties.iter()
.find(|p| p.item_id == tile_id)
.and_then(|p| match &p.property {
ItemProperty::ImageSpatialExtents(e) => Some(e),
_ => None,
})
});
if let (Some(grid), Some(tile)) = (grid_dims, tile_dims) {
tracker.validate_total_megapixels(grid.width, grid.height)?;
if tile.width == 0 || tile.height == 0 {
log::warn!("Grid: tile has zero dimensions, using fallback");
} else if grid.width % tile.width == 0 && grid.height % tile.height == 0 {
let columns = grid.width / tile.width;
let rows = grid.height / tile.height;
if columns > 255 || rows > 255 {
log::warn!("Grid: calculated dimensions {}×{} exceed 255, using fallback", rows, columns);
} else {
log::debug!("Grid: calculated {}×{} layout from ispe dimensions", rows, columns);
grid_config = Some(GridConfig {
rows: rows as u8,
columns: columns as u8,
output_width: grid.width,
output_height: grid.height,
});
}
} else {
log::warn!("Grid: dimension mismatch - grid {}×{} not evenly divisible by tile {}×{}, using fallback",
grid.width, grid.height, tile.width, tile.height);
}
}
if grid_config.is_none() {
log::debug!("Grid: using fallback {}×1 layout inference", ids.len());
grid_config = Some(GridConfig {
rows: ids.len() as u8, columns: 1, output_width: 0, output_height: 0, });
}
}
ids
} else {
TryVec::new()
};
let alpha_item_id = meta
.item_references
.iter()
.filter(|iref| {
iref.to_item_id == meta.primary_item_id
&& iref.from_item_id != meta.primary_item_id
&& iref.item_type == b"auxl"
})
.map(|iref| iref.from_item_id)
.find(|&item_id| {
meta.properties.iter().any(|prop| {
prop.item_id == item_id
&& match &prop.property {
ItemProperty::AuxiliaryType(urn) => {
urn.type_subtype().0 == b"urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"
}
_ => false,
}
})
});
macro_rules! find_prop {
($variant:ident) => {
meta.properties.iter().find_map(|p| {
if p.item_id == meta.primary_item_id {
match &p.property {
ItemProperty::$variant(c) => Some(c.clone()),
_ => None,
}
} else {
None
}
})
};
}
let av1_config = find_prop!(AV1Config);
let color_info = find_prop!(ColorInformation);
let rotation = find_prop!(Rotation);
let mirror = find_prop!(Mirror);
let clean_aperture = find_prop!(CleanAperture);
let pixel_aspect_ratio = find_prop!(PixelAspectRatio);
let content_light_level = find_prop!(ContentLightLevel);
let mastering_display = find_prop!(MasteringDisplayColourVolume);
let content_colour_volume = find_prop!(ContentColourVolume);
let ambient_viewing = find_prop!(AmbientViewingEnvironment);
let operating_point = find_prop!(OperatingPointSelector);
let layer_selector = find_prop!(LayerSelector);
let layered_image_indexing = find_prop!(AV1LayeredImageIndexing);
let mut context = AvifData {
premultiplied_alpha: alpha_item_id.is_some_and(|alpha_item_id| {
meta.item_references.iter().any(|iref| {
iref.from_item_id == meta.primary_item_id
&& iref.to_item_id == alpha_item_id
&& iref.item_type == b"prem"
})
}),
av1_config,
color_info,
rotation,
mirror,
clean_aperture,
pixel_aspect_ratio,
content_light_level,
mastering_display,
content_colour_volume,
ambient_viewing,
operating_point,
layer_selector,
layered_image_indexing,
major_brand,
compatible_brands,
..Default::default()
};
let mut extract_item_data = |loc: &ItemLocationBoxItem, buf: &mut TryVec<u8>| -> Result<()> {
match loc.construction_method {
ConstructionMethod::File => {
for extent in loc.extents.iter() {
let mut found = false;
for mdat in mdats.iter_mut() {
if mdat.matches_extent(&extent.extent_range) {
buf.append(&mut mdat.data)?;
found = true;
break;
} else if mdat.contains_extent(&extent.extent_range) {
mdat.read_extent(&extent.extent_range, buf)?;
found = true;
break;
}
}
if !found {
return Err(Error::InvalidData("iloc contains an extent that is not in mdat"));
}
}
Ok(())
},
ConstructionMethod::Idat => {
let idat_data = meta.idat.as_ref().ok_or(Error::InvalidData("idat box missing but construction_method is Idat"))?;
for extent in loc.extents.iter() {
match &extent.extent_range {
ExtentRange::WithLength(range) => {
let start = usize::try_from(range.start).map_err(|_| Error::InvalidData("extent start too large"))?;
let end = usize::try_from(range.end).map_err(|_| Error::InvalidData("extent end too large"))?;
if end > idat_data.len() {
return Err(Error::InvalidData("extent exceeds idat size"));
}
buf.extend_from_slice(&idat_data[start..end]).map_err(|_| Error::OutOfMemory)?;
},
ExtentRange::ToEnd(range) => {
let start = usize::try_from(range.start).map_err(|_| Error::InvalidData("extent start too large"))?;
if start >= idat_data.len() {
return Err(Error::InvalidData("extent start exceeds idat size"));
}
buf.extend_from_slice(&idat_data[start..]).map_err(|_| Error::OutOfMemory)?;
},
}
}
Ok(())
},
ConstructionMethod::Item => {
Err(Error::Unsupported("construction_method 'item' not supported"))
},
}
};
if is_grid {
for (idx, &tile_id) in tile_item_ids.iter().enumerate() {
if idx % 16 == 0 {
stop.check()?;
}
let mut tile_data = TryVec::new();
if let Some(loc) = meta.iloc_items.iter().find(|loc| loc.item_id == tile_id) {
extract_item_data(loc, &mut tile_data)?;
} else {
return Err(Error::InvalidData("grid tile not found in iloc"));
}
context.grid_tiles.push(tile_data)?;
}
context.grid_config = grid_config;
} else {
for loc in meta.iloc_items.iter() {
let item_data = if loc.item_id == meta.primary_item_id {
&mut context.primary_item
} else if Some(loc.item_id) == alpha_item_id {
context.alpha_item.get_or_insert_with(TryVec::new)
} else {
continue;
};
extract_item_data(loc, item_data)?;
}
}
for iref in meta.item_references.iter() {
if iref.to_item_id != meta.primary_item_id || iref.item_type != b"cdsc" {
continue;
}
let desc_item_id = iref.from_item_id;
let Some(info) = meta.item_infos.iter().find(|i| i.item_id == desc_item_id) else {
continue;
};
if info.item_type == b"Exif" {
if let Some(loc) = meta.iloc_items.iter().find(|l| l.item_id == desc_item_id) {
let mut raw = TryVec::new();
extract_item_data(loc, &mut raw)?;
if raw.len() > 4 {
let offset = u32::from_be_bytes([raw[0], raw[1], raw[2], raw[3]]) as usize;
let start = 4 + offset;
if start < raw.len() {
let mut exif = TryVec::new();
exif.extend_from_slice(&raw[start..])?;
context.exif = Some(exif);
}
}
}
} else if info.item_type == b"mime"
&& let Some(loc) = meta.iloc_items.iter().find(|l| l.item_id == desc_item_id)
{
let mut xmp = TryVec::new();
extract_item_data(loc, &mut xmp)?;
context.xmp = Some(xmp);
}
}
if let Some(tmap_info) = meta.item_infos.iter().find(|info| info.item_type == b"tmap") {
let tmap_id = tmap_info.item_id;
let mut inputs: TryVec<(u32, u16)> = TryVec::new();
for iref in meta.item_references.iter() {
if iref.from_item_id == tmap_id && iref.item_type == b"dimg" {
inputs.push((iref.to_item_id, iref.reference_index))?;
}
}
inputs.sort_by_key(|&(_, idx)| idx);
if inputs.len() >= 2 && inputs[0].0 == meta.primary_item_id {
let gmap_item_id = inputs[1].0;
if let Some(loc) = meta.iloc_items.iter().find(|l| l.item_id == tmap_id) {
let mut tmap_data = TryVec::new();
extract_item_data(loc, &mut tmap_data)?;
if let Ok(metadata) = parse_tone_map_image(&tmap_data) {
context.gain_map_metadata = Some(metadata);
}
}
if let Some(loc) = meta.iloc_items.iter().find(|l| l.item_id == gmap_item_id) {
let mut gmap_data = TryVec::new();
extract_item_data(loc, &mut gmap_data)?;
context.gain_map_item = Some(gmap_data);
}
context.gain_map_color_info = meta.properties.iter().find_map(|p| {
if p.item_id == tmap_id {
match &p.property {
ItemProperty::ColorInformation(c) => Some(c.clone()),
_ => None,
}
} else {
None
}
});
}
}
{
let depth_item_id = meta
.item_references
.iter()
.filter(|iref| {
iref.to_item_id == meta.primary_item_id
&& iref.from_item_id != meta.primary_item_id
&& iref.item_type == b"auxl"
})
.map(|iref| iref.from_item_id)
.find(|&item_id| {
if alpha_item_id == Some(item_id) {
return false;
}
meta.properties.iter().any(|prop| {
prop.item_id == item_id
&& match &prop.property {
ItemProperty::AuxiliaryType(urn) => {
is_depth_auxiliary_urn(urn.type_subtype().0)
}
_ => false,
}
})
});
if let Some(depth_id) = depth_item_id {
if let Some(loc) = meta.iloc_items.iter().find(|l| l.item_id == depth_id) {
let mut depth_data = TryVec::new();
extract_item_data(loc, &mut depth_data)?;
context.depth_item = Some(depth_data);
}
if let Some((w, h)) = meta.properties.iter().find_map(|p| {
if p.item_id == depth_id {
match &p.property {
ItemProperty::ImageSpatialExtents(e) => Some((e.width, e.height)),
_ => None,
}
} else {
None
}
}) {
context.depth_width = w;
context.depth_height = h;
}
context.depth_av1_config = meta.properties.iter().find_map(|p| {
if p.item_id == depth_id {
match &p.property {
ItemProperty::AV1Config(c) => Some(c.clone()),
_ => None,
}
} else {
None
}
});
context.depth_color_info = meta.properties.iter().find_map(|p| {
if p.item_id == depth_id {
match &p.property {
ItemProperty::ColorInformation(c) => Some(c.clone()),
_ => None,
}
} else {
None
}
});
}
}
if let Some(anim) = animation_data {
let frame_count = anim.color_sample_table.sample_sizes.len() as u32;
tracker.validate_animation_frames(frame_count)?;
log::debug!("Animation: extracting frames (media_timescale={})", anim.color_timescale);
match extract_animation_frames(&anim.color_sample_table, anim.color_timescale, &mut mdats) {
Ok(frames) => {
if !frames.is_empty() {
log::debug!("Animation: extracted {} frames", frames.len());
context.animation = Some(AnimationConfig {
loop_count: anim.loop_count,
frames,
});
}
}
Err(e) => {
log::warn!("Animation: failed to extract frames: {}", e);
}
}
}
Ok(context)
}
#[cfg(feature = "eager")]
#[deprecated(since = "1.5.0", note = "Use `AvifParser::from_reader_with_config()` with `DecodeConfig::lenient()` instead")]
#[allow(deprecated)]
pub fn read_avif_with_options<T: Read>(f: &mut T, options: &ParseOptions) -> Result<AvifData> {
let config = DecodeConfig::unlimited().lenient(options.lenient);
read_avif_with_config(f, &config, &Unstoppable)
}
#[cfg(feature = "eager")]
#[deprecated(since = "1.5.0", note = "Use `AvifParser::from_reader()` instead")]
#[allow(deprecated)]
pub fn read_avif<T: Read>(f: &mut T) -> Result<AvifData> {
read_avif_with_options(f, &ParseOptions::default())
}
#[allow(dead_code)] struct EntityGroup {
group_type: FourCC,
group_id: u32,
entity_ids: TryVec<u32>,
}
fn read_grpl<T: Read + Offset>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<EntityGroup>> {
let mut groups = TryVec::new();
let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? {
let group_type = FourCC::from(u32::from(b.head.name));
let _version = b.read_u8()?;
let mut flags_buf = [0u8; 3];
b.read_exact(&mut flags_buf)?;
let group_id = be_u32(&mut b)?;
let num_entities = be_u32(&mut b)?;
if (num_entities as u64) * 4 > b.bytes_left() {
return Err(Error::InvalidData(
"grpl num_entities exceeds remaining box bytes",
));
}
let mut entity_ids = TryVec::new();
for _ in 0..num_entities {
entity_ids.push(be_u32(&mut b)?)?;
}
groups.push(EntityGroup {
group_type,
group_id,
entity_ids,
})?;
skip_box_remain(&mut b)?;
check_parser_state(&b.head, &b.content)?;
}
Ok(groups)
}
const TMAP_FLAG_MULTI_CHANNEL: u8 = 0x80;
const TMAP_FLAG_USE_BASE_COLOUR_SPACE: u8 = 0x40;
const TMAP_FLAG_COMMON_DENOMINATOR: u8 = 0x08;
const TMAP_FLAG_BACKWARD_DIRECTION: u8 = 0x04;
fn parse_tone_map_image(data: &[u8]) -> Result<GainMapMetadata> {
let mut cursor = std::io::Cursor::new(data);
let version = cursor.read_u8()?;
if version != 0 {
return Err(Error::Unsupported("tmap version"));
}
let minimum_version = be_u16(&mut cursor)?;
if minimum_version > 0 {
return Err(Error::Unsupported("tmap minimum version"));
}
let writer_version = be_u16(&mut cursor)?;
if writer_version < minimum_version {
return Err(Error::InvalidData("tmap writer_version < minimum_version"));
}
let flags = cursor.read_u8()?;
let is_multichannel = (flags & TMAP_FLAG_MULTI_CHANNEL) != 0;
let use_base_colour_space = (flags & TMAP_FLAG_USE_BASE_COLOUR_SPACE) != 0;
let backward_direction = (flags & TMAP_FLAG_BACKWARD_DIRECTION) != 0;
let common_denominator = (flags & TMAP_FLAG_COMMON_DENOMINATOR) != 0;
let channel_count = if is_multichannel { 3 } else { 1 };
let mut channels = [GainMapChannel {
gain_map_min_n: 0, gain_map_min_d: 0,
gain_map_max_n: 0, gain_map_max_d: 0,
gamma_n: 0, gamma_d: 0,
base_offset_n: 0, base_offset_d: 0,
alternate_offset_n: 0, alternate_offset_d: 0,
}; 3];
let base_hdr_headroom_n;
let base_hdr_headroom_d;
let alternate_hdr_headroom_n;
let alternate_hdr_headroom_d;
if common_denominator {
let common_d = be_u32(&mut cursor)?;
if common_d == 0 {
return Err(Error::InvalidData("tmap common_denominator is zero"));
}
base_hdr_headroom_n = be_u32(&mut cursor)?;
base_hdr_headroom_d = common_d;
alternate_hdr_headroom_n = be_u32(&mut cursor)?;
alternate_hdr_headroom_d = common_d;
for ch in channels.iter_mut().take(channel_count) {
ch.gain_map_min_n = be_i32(&mut cursor)?;
ch.gain_map_min_d = common_d;
ch.gain_map_max_n = be_i32(&mut cursor)?;
ch.gain_map_max_d = common_d;
ch.gamma_n = be_u32(&mut cursor)?;
ch.gamma_d = common_d;
ch.base_offset_n = be_i32(&mut cursor)?;
ch.base_offset_d = common_d;
ch.alternate_offset_n = be_i32(&mut cursor)?;
ch.alternate_offset_d = common_d;
}
} else {
base_hdr_headroom_n = be_u32(&mut cursor)?;
base_hdr_headroom_d = be_u32(&mut cursor)?;
alternate_hdr_headroom_n = be_u32(&mut cursor)?;
alternate_hdr_headroom_d = be_u32(&mut cursor)?;
for ch in channels.iter_mut().take(channel_count) {
ch.gain_map_min_n = be_i32(&mut cursor)?;
ch.gain_map_min_d = be_u32(&mut cursor)?;
ch.gain_map_max_n = be_i32(&mut cursor)?;
ch.gain_map_max_d = be_u32(&mut cursor)?;
ch.gamma_n = be_u32(&mut cursor)?;
ch.gamma_d = be_u32(&mut cursor)?;
ch.base_offset_n = be_i32(&mut cursor)?;
ch.base_offset_d = be_u32(&mut cursor)?;
ch.alternate_offset_n = be_i32(&mut cursor)?;
ch.alternate_offset_d = be_u32(&mut cursor)?;
}
}
if !is_multichannel {
channels[1] = channels[0];
channels[2] = channels[0];
}
let _ = writer_version;
Ok(GainMapMetadata {
is_multichannel,
use_base_colour_space,
backward_direction,
base_hdr_headroom_n,
base_hdr_headroom_d,
alternate_hdr_headroom_n,
alternate_hdr_headroom_d,
channels,
})
}
fn read_avif_meta<T: Read + Offset>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<AvifInternalMeta> {
let version = read_fullbox_version_no_flags(src, options)?;
if version != 0 {
return Err(Error::Unsupported("unsupported meta version"));
}
let mut primary_item_id = None;
let mut item_infos = None;
let mut iloc_items = None;
let mut item_references = TryVec::new();
let mut properties = TryVec::new();
let mut idat = None;
let mut entity_groups = TryVec::new();
let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? {
match b.head.name {
BoxType::ItemInfoBox => {
if item_infos.is_some() {
return Err(Error::InvalidData("There should be zero or one iinf boxes per ISO 14496-12:2015 § 8.11.6.1"));
}
item_infos = Some(read_iinf(&mut b, options)?);
},
BoxType::ItemLocationBox => {
if iloc_items.is_some() {
return Err(Error::InvalidData("There should be zero or one iloc boxes per ISO 14496-12:2015 § 8.11.3.1"));
}
iloc_items = Some(read_iloc(&mut b, options)?);
},
BoxType::PrimaryItemBox => {
if primary_item_id.is_some() {
return Err(Error::InvalidData("There should be zero or one iloc boxes per ISO 14496-12:2015 § 8.11.4.1"));
}
primary_item_id = Some(read_pitm(&mut b, options)?);
},
BoxType::ImageReferenceBox => {
item_references.append(&mut read_iref(&mut b, options)?)?;
},
BoxType::ImagePropertiesBox => {
properties = read_iprp(&mut b, options)?;
},
BoxType::ItemDataBox => {
if idat.is_some() {
return Err(Error::InvalidData("There should be zero or one idat boxes"));
}
idat = Some(b.read_into_try_vec()?);
},
BoxType::GroupsListBox => {
entity_groups.append(&mut read_grpl(&mut b)?)?;
},
BoxType::HandlerBox => {
let hdlr = read_hdlr(&mut b)?;
if hdlr.handler_type != b"pict" {
warn!("hdlr handler_type: {}", hdlr.handler_type);
return Err(Error::InvalidData("meta handler_type must be 'pict' for AVIF"));
}
},
_ => skip_box_content(&mut b)?,
}
check_parser_state(&b.head, &b.content)?;
}
let primary_item_id = primary_item_id.ok_or(Error::InvalidData("Required pitm box not present in meta box"))?;
let item_infos = item_infos.ok_or(Error::InvalidData("iinf missing"))?;
if let Some(item_info) = item_infos.iter().find(|x| x.item_id == primary_item_id) {
if item_info.item_type != b"av01" && item_info.item_type != b"grid" {
warn!("primary_item_id type: {}", item_info.item_type);
return Err(Error::InvalidData("primary_item_id type is not av01 or grid"));
}
} else {
return Err(Error::InvalidData("primary_item_id not present in iinf box"));
}
Ok(AvifInternalMeta {
properties,
item_references,
primary_item_id,
iloc_items: iloc_items.ok_or(Error::InvalidData("iloc missing"))?,
item_infos,
idat,
entity_groups,
})
}
fn read_hdlr<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<HandlerBox> {
let (_version, _flags) = read_fullbox_extra(src)?;
skip(src, 4)?;
let handler_type = be_u32(src)?;
skip_box_remain(src)?;
Ok(HandlerBox {
handler_type: FourCC::from(handler_type),
})
}
fn read_pitm<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<u32> {
let version = read_fullbox_version_no_flags(src, options)?;
let item_id = match version {
0 => be_u16(src)?.into(),
1 => be_u32(src)?,
_ => return Err(Error::Unsupported("unsupported pitm version")),
};
Ok(item_id)
}
fn read_iinf<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<ItemInfoEntry>> {
let version = read_fullbox_version_no_flags(src, options)?;
match version {
0 | 1 => (),
_ => return Err(Error::Unsupported("unsupported iinf version")),
}
let entry_count = if version == 0 {
be_u16(src)?.to_usize()
} else {
be_u32(src)?.to_usize()
};
let mut item_infos = TryVec::with_capacity(entry_count.min(4096))?;
let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? {
if b.head.name != BoxType::ItemInfoEntry {
return Err(Error::InvalidData("iinf box should contain only infe boxes"));
}
item_infos.push(read_infe(&mut b)?)?;
check_parser_state(&b.head, &b.content)?;
}
Ok(item_infos)
}
fn read_infe<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ItemInfoEntry> {
let (version, _) = read_fullbox_extra(src)?;
let item_id = match version {
2 => be_u16(src)?.into(),
3 => be_u32(src)?,
_ => return Err(Error::Unsupported("unsupported version in 'infe' box")),
};
let item_protection_index = be_u16(src)?;
if item_protection_index != 0 {
return Err(Error::Unsupported("protected items (infe.item_protection_index != 0) are not supported"));
}
let item_type = FourCC::from(be_u32(src)?);
debug!("infe item_id {item_id} item_type: {item_type}");
skip_box_remain(src)?;
Ok(ItemInfoEntry { item_id, item_type })
}
fn read_iref<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<SingleItemTypeReferenceBox>> {
let mut item_references = TryVec::new();
let version = read_fullbox_version_no_flags(src, options)?;
if version > 1 {
return Err(Error::Unsupported("iref version"));
}
let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? {
let from_item_id = if version == 0 {
be_u16(&mut b)?.into()
} else {
be_u32(&mut b)?
};
let reference_count = be_u16(&mut b)?;
let bytes_per_ref: u64 = if version == 0 { 2 } else { 4 };
if (reference_count as u64) * bytes_per_ref > b.bytes_left() {
return Err(Error::InvalidData(
"iref reference_count exceeds remaining box bytes",
));
}
for reference_index in 0..reference_count {
let to_item_id = if version == 0 {
be_u16(&mut b)?.into()
} else {
be_u32(&mut b)?
};
if from_item_id == to_item_id {
return Err(Error::InvalidData("from_item_id and to_item_id must be different"));
}
item_references.push(SingleItemTypeReferenceBox {
item_type: b.head.name.into(),
from_item_id,
to_item_id,
reference_index,
})?;
}
check_parser_state(&b.head, &b.content)?;
}
Ok(item_references)
}
const MUST_BE_ESSENTIAL: &[&[u8; 4]] = &[b"a1op", b"lsel", b"clap", b"irot", b"imir"];
const MUST_NOT_BE_ESSENTIAL: &[&[u8; 4]] = &[b"a1lx"];
fn read_iprp<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<AssociatedProperty>> {
let mut iter = src.box_iter();
let mut properties = TryVec::new();
let mut associations = TryVec::new();
while let Some(mut b) = iter.next_box()? {
match b.head.name {
BoxType::ItemPropertyContainerBox => {
properties = read_ipco(&mut b, options)?;
},
BoxType::ItemPropertyAssociationBox => {
associations = read_ipma(&mut b)?;
},
_ => return Err(Error::InvalidData("unexpected ipco child")),
}
}
let mut associated = TryVec::new();
for a in associations {
let index = match a.property_index {
0 => {
if a.essential {
return Err(Error::InvalidData(
"ipma property_index 0 must not be marked essential",
));
}
continue;
}
x => x as usize - 1,
};
let Some(entry) = properties.get(index) else {
continue;
};
let is_supported = entry.property != ItemProperty::Unsupported;
let fourcc_bytes = &entry.fourcc.value;
if is_supported {
if a.essential && MUST_NOT_BE_ESSENTIAL.contains(&fourcc_bytes) {
warn!("item {} has {} marked essential (spec forbids it)", a.item_id, entry.fourcc);
if !options.lenient {
return Err(Error::InvalidData(
"property must not be marked essential",
));
}
}
if !a.essential && MUST_BE_ESSENTIAL.contains(&fourcc_bytes) {
warn!("item {} has {} not marked essential (spec requires it)", a.item_id, entry.fourcc);
if !options.lenient {
return Err(Error::InvalidData(
"property must be marked essential",
));
}
}
associated.push(AssociatedProperty {
item_id: a.item_id,
property: entry.property.try_clone()?,
})?;
} else if a.essential {
warn!(
"item {} has unsupported property {} marked essential; item will be unusable",
a.item_id, entry.fourcc
);
if !options.lenient {
return Err(Error::Unsupported(
"unsupported property marked as essential",
));
}
}
}
Ok(associated)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ImageSpatialExtents {
pub(crate) width: u32,
pub(crate) height: u32,
}
#[derive(Debug, PartialEq)]
pub(crate) enum ItemProperty {
Channels(ArrayVec<u8, 16>),
AuxiliaryType(AuxiliaryTypeProperty),
ImageSpatialExtents(ImageSpatialExtents),
ImageGrid(GridConfig),
AV1Config(AV1Config),
ColorInformation(ColorInformation),
Rotation(ImageRotation),
Mirror(ImageMirror),
CleanAperture(CleanAperture),
PixelAspectRatio(PixelAspectRatio),
ContentLightLevel(ContentLightLevel),
MasteringDisplayColourVolume(MasteringDisplayColourVolume),
ContentColourVolume(ContentColourVolume),
AmbientViewingEnvironment(AmbientViewingEnvironment),
OperatingPointSelector(OperatingPointSelector),
LayerSelector(LayerSelector),
AV1LayeredImageIndexing(AV1LayeredImageIndexing),
Unsupported,
}
impl TryClone for ItemProperty {
fn try_clone(&self) -> Result<Self, TryReserveError> {
Ok(match self {
Self::Channels(val) => Self::Channels(val.clone()),
Self::AuxiliaryType(val) => Self::AuxiliaryType(val.try_clone()?),
Self::ImageSpatialExtents(val) => Self::ImageSpatialExtents(*val),
Self::ImageGrid(val) => Self::ImageGrid(val.clone()),
Self::AV1Config(val) => Self::AV1Config(val.clone()),
Self::ColorInformation(val) => Self::ColorInformation(val.clone()),
Self::Rotation(val) => Self::Rotation(*val),
Self::Mirror(val) => Self::Mirror(*val),
Self::CleanAperture(val) => Self::CleanAperture(*val),
Self::PixelAspectRatio(val) => Self::PixelAspectRatio(*val),
Self::ContentLightLevel(val) => Self::ContentLightLevel(*val),
Self::MasteringDisplayColourVolume(val) => Self::MasteringDisplayColourVolume(*val),
Self::ContentColourVolume(val) => Self::ContentColourVolume(*val),
Self::AmbientViewingEnvironment(val) => Self::AmbientViewingEnvironment(*val),
Self::OperatingPointSelector(val) => Self::OperatingPointSelector(*val),
Self::LayerSelector(val) => Self::LayerSelector(*val),
Self::AV1LayeredImageIndexing(val) => Self::AV1LayeredImageIndexing(*val),
Self::Unsupported => Self::Unsupported,
})
}
}
struct Association {
item_id: u32,
essential: bool,
property_index: u16,
}
pub(crate) struct AssociatedProperty {
pub item_id: u32,
pub property: ItemProperty,
}
fn read_ipma<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<Association>> {
let (version, flags) = read_fullbox_extra(src)?;
let mut associations = TryVec::new();
let entry_count = be_u32(src)?;
let min_bytes_per_entry: u64 = if version == 0 { 3 } else { 5 };
if (entry_count as u64) * min_bytes_per_entry > src.bytes_left() {
return Err(Error::InvalidData(
"ipma entry_count exceeds remaining box bytes",
));
}
for _ in 0..entry_count {
let item_id = if version == 0 {
be_u16(src)?.into()
} else {
be_u32(src)?
};
let association_count = src.read_u8()?;
for _ in 0..association_count {
let num_association_bytes = if flags & 1 == 1 { 2 } else { 1 };
let association = &mut [0; 2][..num_association_bytes];
src.read_exact(association)?;
let mut association = BitReader::new(association);
let essential = association.read_bool()?;
let property_index = association.read_u16(association.remaining().try_into()?)?;
associations.push(Association {
item_id,
essential,
property_index,
})?;
}
}
Ok(associations)
}
struct IndexedProperty {
fourcc: FourCC,
property: ItemProperty,
}
fn read_ipco<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<IndexedProperty>> {
let mut properties = TryVec::new();
let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? {
let fourcc: FourCC = b.head.name.into();
let prop = match b.head.name {
BoxType::PixelInformationBox => ItemProperty::Channels(read_pixi(&mut b, options)?),
BoxType::AuxiliaryTypeProperty => ItemProperty::AuxiliaryType(read_auxc(&mut b, options)?),
BoxType::ImageSpatialExtentsBox => ItemProperty::ImageSpatialExtents(read_ispe(&mut b, options)?),
BoxType::ImageGridBox => ItemProperty::ImageGrid(read_grid(&mut b, options)?),
BoxType::AV1CodecConfigurationBox => ItemProperty::AV1Config(read_av1c(&mut b)?),
BoxType::ColorInformationBox => {
match read_colr(&mut b) {
Ok(colr) => ItemProperty::ColorInformation(colr),
Err(_) => ItemProperty::Unsupported,
}
},
BoxType::ImageRotationBox => ItemProperty::Rotation(read_irot(&mut b)?),
BoxType::ImageMirrorBox => ItemProperty::Mirror(read_imir(&mut b)?),
BoxType::CleanApertureBox => ItemProperty::CleanAperture(read_clap(&mut b)?),
BoxType::PixelAspectRatioBox => ItemProperty::PixelAspectRatio(read_pasp(&mut b)?),
BoxType::ContentLightLevelBox => ItemProperty::ContentLightLevel(read_clli(&mut b)?),
BoxType::MasteringDisplayColourVolumeBox => ItemProperty::MasteringDisplayColourVolume(read_mdcv(&mut b)?),
BoxType::ContentColourVolumeBox => ItemProperty::ContentColourVolume(read_cclv(&mut b)?),
BoxType::AmbientViewingEnvironmentBox => ItemProperty::AmbientViewingEnvironment(read_amve(&mut b)?),
BoxType::OperatingPointSelectorBox => ItemProperty::OperatingPointSelector(read_a1op(&mut b)?),
BoxType::LayerSelectorBox => ItemProperty::LayerSelector(read_lsel(&mut b)?),
BoxType::AV1LayeredImageIndexingBox => ItemProperty::AV1LayeredImageIndexing(read_a1lx(&mut b)?),
_ => {
skip_box_remain(&mut b)?;
ItemProperty::Unsupported
},
};
properties.push(IndexedProperty { fourcc, property: prop })?;
}
Ok(properties)
}
fn read_pixi<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<ArrayVec<u8, 16>> {
let version = read_fullbox_version_no_flags(src, options)?;
if version != 0 {
return Err(Error::Unsupported("pixi version"));
}
let num_channels = usize::from(src.read_u8()?);
let mut channels = ArrayVec::new();
let clamped = num_channels.min(channels.capacity());
channels.extend((0..clamped).map(|_| 0));
src.read_exact(&mut channels).map_err(|_| Error::InvalidData("invalid num_channels"))?;
if options.lenient && src.bytes_left() > 0 {
skip(src, src.bytes_left())?;
}
check_parser_state(&src.head, &src.content)?;
Ok(channels)
}
#[derive(Debug, PartialEq)]
struct AuxiliaryTypeProperty {
aux_data: TryString,
}
impl AuxiliaryTypeProperty {
#[must_use]
fn type_subtype(&self) -> (&[u8], &[u8]) {
let split = self.aux_data.iter().position(|&b| b == b'\0')
.map(|pos| self.aux_data.split_at(pos));
if let Some((aux_type, rest)) = split {
(aux_type, &rest[1..])
} else {
(&self.aux_data, &[])
}
}
}
impl TryClone for AuxiliaryTypeProperty {
fn try_clone(&self) -> Result<Self, TryReserveError> {
Ok(Self {
aux_data: self.aux_data.try_clone()?,
})
}
}
fn read_auxc<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<AuxiliaryTypeProperty> {
let version = read_fullbox_version_no_flags(src, options)?;
if version != 0 {
return Err(Error::Unsupported("auxC version"));
}
let aux_data = src.read_into_try_vec()?;
Ok(AuxiliaryTypeProperty { aux_data })
}
fn is_depth_auxiliary_urn(urn: &[u8]) -> bool {
urn == b"urn:mpeg:mpegB:cicp:systems:auxiliary:depth"
|| urn == b"urn:mpeg:hevc:2015:auxid:2"
}
fn read_av1c<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<AV1Config> {
let byte0 = src.read_u8()?;
let marker = byte0 >> 7;
let version = byte0 & 0x7F;
if marker != 1 {
return Err(Error::InvalidData("av1C marker must be 1"));
}
if version != 1 {
return Err(Error::Unsupported("av1C version must be 1"));
}
let byte1 = src.read_u8()?;
let profile = byte1 >> 5;
let level = byte1 & 0x1F;
let byte2 = src.read_u8()?;
let tier = byte2 >> 7;
let high_bitdepth = (byte2 >> 6) & 1;
let twelve_bit = (byte2 >> 5) & 1;
let monochrome = (byte2 >> 4) & 1 != 0;
let chroma_subsampling_x = (byte2 >> 3) & 1;
let chroma_subsampling_y = (byte2 >> 2) & 1;
let chroma_sample_position = byte2 & 0x03;
let byte3 = src.read_u8()?;
let _ = byte3;
let bit_depth = if high_bitdepth != 0 {
if twelve_bit != 0 { 12 } else { 10 }
} else {
8
};
skip_box_remain(src)?;
Ok(AV1Config {
profile,
level,
tier,
bit_depth,
monochrome,
chroma_subsampling_x,
chroma_subsampling_y,
chroma_sample_position,
})
}
fn read_colr<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ColorInformation> {
let colour_type = be_u32(src)?;
match &colour_type.to_be_bytes() {
b"nclx" => {
let color_primaries = be_u16(src)?;
let transfer_characteristics = be_u16(src)?;
let matrix_coefficients = be_u16(src)?;
let full_range_byte = src.read_u8()?;
let full_range = (full_range_byte >> 7) != 0;
skip_box_remain(src)?;
Ok(ColorInformation::Nclx {
color_primaries,
transfer_characteristics,
matrix_coefficients,
full_range,
})
}
b"rICC" | b"prof" => {
let icc_data = src.read_into_try_vec()?;
Ok(ColorInformation::IccProfile(icc_data.to_vec()))
}
_ => {
skip_box_remain(src)?;
Err(Error::Unsupported("unsupported colr colour_type"))
}
}
}
fn read_irot<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ImageRotation> {
let byte = src.read_u8()?;
let angle_code = byte & 0x03;
let angle = match angle_code {
0 => 0,
1 => 90,
2 => 180,
_ => 270, };
skip_box_remain(src)?;
Ok(ImageRotation { angle })
}
fn read_imir<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ImageMirror> {
let byte = src.read_u8()?;
let axis = byte & 0x01;
skip_box_remain(src)?;
Ok(ImageMirror { axis })
}
fn read_clap<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<CleanAperture> {
let width_n = be_u32(src)?;
let width_d = be_u32(src)?;
let height_n = be_u32(src)?;
let height_d = be_u32(src)?;
let horiz_off_n = be_i32(src)?;
let horiz_off_d = be_u32(src)?;
let vert_off_n = be_i32(src)?;
let vert_off_d = be_u32(src)?;
if width_d == 0 || height_d == 0 || horiz_off_d == 0 || vert_off_d == 0 {
return Err(Error::InvalidData("clap denominator cannot be zero"));
}
skip_box_remain(src)?;
Ok(CleanAperture {
width_n, width_d,
height_n, height_d,
horiz_off_n, horiz_off_d,
vert_off_n, vert_off_d,
})
}
fn read_pasp<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<PixelAspectRatio> {
let h_spacing = be_u32(src)?;
let v_spacing = be_u32(src)?;
skip_box_remain(src)?;
Ok(PixelAspectRatio { h_spacing, v_spacing })
}
fn read_clli<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ContentLightLevel> {
let max_content_light_level = be_u16(src)?;
let max_pic_average_light_level = be_u16(src)?;
skip_box_remain(src)?;
Ok(ContentLightLevel {
max_content_light_level,
max_pic_average_light_level,
})
}
fn read_mdcv<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<MasteringDisplayColourVolume> {
let primaries = [
(be_u16(src)?, be_u16(src)?),
(be_u16(src)?, be_u16(src)?),
(be_u16(src)?, be_u16(src)?),
];
let white_point = (be_u16(src)?, be_u16(src)?);
let max_luminance = be_u32(src)?;
let min_luminance = be_u32(src)?;
skip_box_remain(src)?;
Ok(MasteringDisplayColourVolume {
primaries,
white_point,
max_luminance,
min_luminance,
})
}
fn read_cclv<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ContentColourVolume> {
let flags = src.read_u8()?;
let primaries_present = flags & 0x20 != 0;
let min_lum_present = flags & 0x10 != 0;
let max_lum_present = flags & 0x08 != 0;
let avg_lum_present = flags & 0x04 != 0;
let primaries = if primaries_present {
Some([
(be_i32(src)?, be_i32(src)?),
(be_i32(src)?, be_i32(src)?),
(be_i32(src)?, be_i32(src)?),
])
} else {
None
};
let min_luminance = if min_lum_present { Some(be_u32(src)?) } else { None };
let max_luminance = if max_lum_present { Some(be_u32(src)?) } else { None };
let avg_luminance = if avg_lum_present { Some(be_u32(src)?) } else { None };
skip_box_remain(src)?;
Ok(ContentColourVolume {
primaries,
min_luminance,
max_luminance,
avg_luminance,
})
}
fn read_amve<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<AmbientViewingEnvironment> {
let ambient_illuminance = be_u32(src)?;
let ambient_light_x = be_u16(src)?;
let ambient_light_y = be_u16(src)?;
skip_box_remain(src)?;
Ok(AmbientViewingEnvironment {
ambient_illuminance,
ambient_light_x,
ambient_light_y,
})
}
fn read_a1op<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<OperatingPointSelector> {
let op_index = src.read_u8()?;
if op_index > 31 {
return Err(Error::InvalidData("a1op op_index must be 0..31"));
}
skip_box_remain(src)?;
Ok(OperatingPointSelector { op_index })
}
fn read_lsel<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<LayerSelector> {
let layer_id = be_u16(src)?;
skip_box_remain(src)?;
Ok(LayerSelector { layer_id })
}
fn read_a1lx<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<AV1LayeredImageIndexing> {
let flags = src.read_u8()?;
let large_size = flags & 0x01 != 0;
let layer_sizes = if large_size {
[be_u32(src)?, be_u32(src)?, be_u32(src)?]
} else {
[u32::from(be_u16(src)?), u32::from(be_u16(src)?), u32::from(be_u16(src)?)]
};
skip_box_remain(src)?;
Ok(AV1LayeredImageIndexing { layer_sizes })
}
fn read_ispe<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<ImageSpatialExtents> {
let _version = read_fullbox_version_no_flags(src, options)?;
let width = be_u32(src)?;
let height = be_u32(src)?;
if width == 0 || height == 0 {
return Err(Error::InvalidData("ispe dimensions cannot be zero"));
}
Ok(ImageSpatialExtents { width, height })
}
fn read_mvhd<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<MovieHeader> {
let version = src.read_u8()?;
let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
let (timescale, duration) = if version == 1 {
let _creation_time = be_u64(src)?;
let _modification_time = be_u64(src)?;
let timescale = be_u32(src)?;
let duration = be_u64(src)?;
(timescale, duration)
} else {
let _creation_time = be_u32(src)?;
let _modification_time = be_u32(src)?;
let timescale = be_u32(src)?;
let duration = be_u32(src)?;
(timescale, duration as u64)
};
skip_box_remain(src)?;
Ok(MovieHeader { _timescale: timescale, _duration: duration })
}
fn read_mdhd<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<MediaHeader> {
let version = src.read_u8()?;
let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
let (timescale, duration) = if version == 1 {
let _creation_time = be_u64(src)?;
let _modification_time = be_u64(src)?;
let timescale = be_u32(src)?;
let duration = be_u64(src)?;
(timescale, duration)
} else {
let _creation_time = be_u32(src)?;
let _modification_time = be_u32(src)?;
let timescale = be_u32(src)?;
let duration = be_u32(src)?;
(timescale, duration as u64)
};
skip_box_remain(src)?;
Ok(MediaHeader { timescale, _duration: duration })
}
fn read_stts<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<TimeToSampleEntry>> {
let _version = src.read_u8()?;
let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
let entry_count = be_u32(src)?;
if (entry_count as u64) * 8 > src.bytes_left() {
return Err(Error::InvalidData(
"stts entry_count exceeds remaining box bytes",
));
}
let mut entries = TryVec::new();
for _ in 0..entry_count {
entries.push(TimeToSampleEntry {
sample_count: be_u32(src)?,
sample_delta: be_u32(src)?,
})?;
}
Ok(entries)
}
fn read_stsc<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<SampleToChunkEntry>> {
let _version = src.read_u8()?;
let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
let entry_count = be_u32(src)?;
if (entry_count as u64) * 12 > src.bytes_left() {
return Err(Error::InvalidData(
"stsc entry_count exceeds remaining box bytes",
));
}
let mut entries = TryVec::new();
for _ in 0..entry_count {
entries.push(SampleToChunkEntry {
first_chunk: be_u32(src)?,
samples_per_chunk: be_u32(src)?,
_sample_description_index: be_u32(src)?,
})?;
}
Ok(entries)
}
fn read_stsz<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<u32>> {
let _version = src.read_u8()?;
let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
let sample_size = be_u32(src)?;
let sample_count = be_u32(src)?;
const MAX_SAMPLE_COUNT: u32 = 64 * 1024 * 1024;
if sample_count > MAX_SAMPLE_COUNT {
return Err(Error::InvalidData("stsz sample_count exceeds maximum"));
}
let mut sizes = TryVec::new();
if sample_size == 0 {
if (sample_count as u64) * 4 > src.bytes_left() {
return Err(Error::InvalidData(
"stsz sample_count exceeds remaining box bytes",
));
}
for _ in 0..sample_count {
sizes.push(be_u32(src)?)?;
}
} else {
for _ in 0..sample_count {
sizes.push(sample_size)?;
}
}
Ok(sizes)
}
fn read_chunk_offsets<T: Read>(src: &mut BMFFBox<'_, T>, is_64bit: bool) -> Result<TryVec<u64>> {
let _version = src.read_u8()?;
let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
let entry_count = be_u32(src)?;
let bytes_per_entry: u64 = if is_64bit { 8 } else { 4 };
if (entry_count as u64) * bytes_per_entry > src.bytes_left() {
return Err(Error::InvalidData(
"chunk offset entry_count exceeds remaining box bytes",
));
}
let mut offsets = TryVec::new();
for _ in 0..entry_count {
let offset = if is_64bit {
be_u64(src)?
} else {
be_u32(src)? as u64
};
offsets.push(offset)?;
}
Ok(offsets)
}
fn read_stsd<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TrackCodecConfig> {
let _version = src.read_u8()?;
let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
let entry_count = be_u32(src)?;
let mut config = TrackCodecConfig::default();
let mut iter = src.box_iter();
for _ in 0..entry_count {
let Some(mut entry_box) = iter.next_box()? else {
break;
};
if entry_box.head.name != BoxType::AV1SampleEntry {
skip_box_remain(&mut entry_box)?;
continue;
}
const VISUAL_SAMPLE_ENTRY_SIZE: u64 = 78;
if entry_box.bytes_left() < VISUAL_SAMPLE_ENTRY_SIZE {
skip_box_remain(&mut entry_box)?;
continue;
}
skip(&mut entry_box, VISUAL_SAMPLE_ENTRY_SIZE)?;
let mut sub_iter = entry_box.box_iter();
while let Some(mut sub_box) = sub_iter.next_box()? {
match sub_box.head.name {
BoxType::AV1CodecConfigurationBox => {
config.av1_config = Some(read_av1c(&mut sub_box)?);
}
BoxType::ColorInformationBox => {
if let Ok(colr) = read_colr(&mut sub_box) {
config.color_info = Some(colr);
} else {
skip_box_remain(&mut sub_box)?;
}
}
_ => {
skip_box_remain(&mut sub_box)?;
}
}
}
if config.av1_config.is_some() {
break;
}
}
Ok(config)
}
fn read_stbl<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<(SampleTable, TrackCodecConfig)> {
let mut time_to_sample = TryVec::new();
let mut sample_to_chunk = TryVec::new();
let mut sample_sizes = TryVec::new();
let mut chunk_offsets = TryVec::new();
let mut codec_config = TrackCodecConfig::default();
let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? {
match b.head.name {
BoxType::SampleDescriptionBox => {
codec_config = read_stsd(&mut b)?;
}
BoxType::TimeToSampleBox => {
time_to_sample = read_stts(&mut b)?;
}
BoxType::SampleToChunkBox => {
sample_to_chunk = read_stsc(&mut b)?;
}
BoxType::SampleSizeBox => {
sample_sizes = read_stsz(&mut b)?;
}
BoxType::ChunkOffsetBox => {
chunk_offsets = read_chunk_offsets(&mut b, false)?;
}
BoxType::ChunkLargeOffsetBox => {
chunk_offsets = read_chunk_offsets(&mut b, true)?;
}
_ => {
skip_box_remain(&mut b)?;
}
}
}
let mut sample_offsets = TryVec::new();
let mut sample_idx = 0usize;
for (i, entry) in sample_to_chunk.iter().enumerate() {
let next_first_chunk = sample_to_chunk
.get(i + 1)
.map(|e| e.first_chunk)
.unwrap_or(u32::MAX);
for chunk_no in entry.first_chunk..next_first_chunk {
if chunk_no == 0 {
break;
}
let co_idx = (chunk_no - 1) as usize;
let chunk_offset = match chunk_offsets.get(co_idx) {
Some(&o) => o,
None => break,
};
let mut offset = chunk_offset;
for _ in 0..entry.samples_per_chunk {
if sample_idx >= sample_sizes.len() {
break;
}
sample_offsets.push(offset)?;
offset += *sample_sizes.get(sample_idx)
.ok_or(Error::InvalidData("sample index mismatch"))? as u64;
sample_idx += 1;
}
}
}
Ok((SampleTable {
time_to_sample,
sample_sizes,
sample_offsets,
}, codec_config))
}
fn read_tkhd<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<u32> {
let version = src.read_u8()?;
let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
let track_id = if version == 1 {
let _creation_time = be_u64(src)?;
let _modification_time = be_u64(src)?;
let track_id = be_u32(src)?;
let _reserved = be_u32(src)?;
let _duration = be_u64(src)?;
track_id
} else {
let _creation_time = be_u32(src)?;
let _modification_time = be_u32(src)?;
let track_id = be_u32(src)?;
let _reserved = be_u32(src)?;
let _duration = be_u32(src)?;
track_id
};
skip_box_remain(src)?;
Ok(track_id)
}
fn read_tref<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<TrackReference>> {
let mut refs = TryVec::new();
let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? {
let reference_type = FourCC::from(u32::from(b.head.name));
let bytes_left = b.bytes_left();
if bytes_left < 4 || bytes_left % 4 != 0 {
skip_box_remain(&mut b)?;
continue;
}
let count = bytes_left / 4;
let mut track_ids = TryVec::new();
for _ in 0..count {
track_ids.push(be_u32(&mut b)?)?;
}
refs.push(TrackReference { reference_type, track_ids })?;
}
Ok(refs)
}
fn read_elst<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<u32> {
let (version, flags) = read_fullbox_extra(src)?;
let entry_count = be_u32(src)?;
let entry_size: u64 = if version == 1 { 20 } else { 12 };
skip(src, (entry_count as u64).checked_mul(entry_size)
.ok_or(Error::InvalidData("edit list entry count overflow"))?)?;
skip_box_remain(src)?;
if flags & 1 != 0 {
Ok(0) } else {
Ok(1) }
}
fn read_moov<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<ParsedTrack>> {
let mut tracks = TryVec::new();
let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? {
match b.head.name {
BoxType::MovieHeaderBox => {
let _mvhd = read_mvhd(&mut b)?;
}
BoxType::TrackBox => {
if let Some(track) = read_trak(&mut b)? {
tracks.push(track)?;
}
}
_ => {
skip_box_remain(&mut b)?;
}
}
}
Ok(tracks)
}
fn read_trak<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<Option<ParsedTrack>> {
let mut track_id = 0u32;
let mut references = TryVec::new();
let mut loop_count = 1u32; let mut mdia_result: Option<(FourCC, u32, SampleTable, TrackCodecConfig)> = None;
let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? {
match b.head.name {
BoxType::TrackHeaderBox => {
track_id = read_tkhd(&mut b)?;
}
BoxType::TrackReferenceBox => {
references = read_tref(&mut b)?;
}
BoxType::EditBox => {
let mut edts_iter = b.box_iter();
while let Some(mut eb) = edts_iter.next_box()? {
if eb.head.name == BoxType::EditListBox {
loop_count = read_elst(&mut eb)?;
} else {
skip_box_remain(&mut eb)?;
}
}
}
BoxType::MediaBox => {
mdia_result = read_mdia(&mut b)?;
}
_ => {
skip_box_remain(&mut b)?;
}
}
}
if let Some((handler_type, media_timescale, sample_table, codec_config)) = mdia_result {
Ok(Some(ParsedTrack {
track_id,
handler_type,
media_timescale,
sample_table,
references,
loop_count,
codec_config,
}))
} else {
Ok(None)
}
}
fn read_mdia<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<Option<(FourCC, u32, SampleTable, TrackCodecConfig)>> {
let mut media_timescale = 1000; let mut handler_type = FourCC::default();
let mut stbl_result: Option<(SampleTable, TrackCodecConfig)> = None;
let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? {
match b.head.name {
BoxType::MediaHeaderBox => {
let mdhd = read_mdhd(&mut b)?;
media_timescale = mdhd.timescale;
}
BoxType::HandlerBox => {
let hdlr = read_hdlr(&mut b)?;
handler_type = hdlr.handler_type;
}
BoxType::MediaInformationBox => {
stbl_result = read_minf(&mut b)?;
}
_ => {
skip_box_remain(&mut b)?;
}
}
}
if let Some((stbl, codec_config)) = stbl_result {
Ok(Some((handler_type, media_timescale, stbl, codec_config)))
} else {
Ok(None)
}
}
fn associate_tracks(tracks: TryVec<ParsedTrack>) -> Result<ParsedAnimationData> {
let color_idx = tracks
.iter()
.position(|t| t.handler_type == b"pict")
.or_else(|| {
tracks.iter().position(|t| t.handler_type != b"soun")
})
.ok_or(Error::InvalidData("no color track found in moov"))?;
let color_track = tracks.get(color_idx)
.ok_or(Error::InvalidData("color track index out of bounds"))?;
let color_track_id = color_track.track_id;
let alpha_idx = tracks.iter().position(|t| {
matches!(&t.handler_type.value, b"auxv" | b"pict")
&& t.references.iter().any(|r| {
r.reference_type == b"auxl"
&& r.track_ids.iter().any(|&id| id == color_track_id)
})
});
if let Some(ai) = alpha_idx {
let alpha_track = tracks.get(ai)
.ok_or(Error::InvalidData("alpha track index out of bounds"))?;
let color_track = tracks.get(color_idx)
.ok_or(Error::InvalidData("color track index out of bounds"))?;
let alpha_frames = alpha_track.sample_table.sample_sizes.len();
let color_frames = color_track.sample_table.sample_sizes.len();
if alpha_frames != color_frames {
warn!(
"alpha track has {} frames but color track has {} frames",
alpha_frames, color_frames
);
}
}
let mut tracks_vec: std::vec::Vec<ParsedTrack> = tracks.into_iter().collect();
let (color_track, alpha_track) = if let Some(ai) = alpha_idx {
if ai > color_idx {
let alpha = tracks_vec.remove(ai);
let color = tracks_vec.remove(color_idx);
(color, Some(alpha))
} else {
let color = tracks_vec.remove(color_idx);
let alpha = tracks_vec.remove(ai);
(color, Some(alpha))
}
} else {
let color = tracks_vec.remove(color_idx);
(color, None)
};
let (alpha_timescale, alpha_sample_table) = match alpha_track {
Some(t) => (Some(t.media_timescale), Some(t.sample_table)),
None => (None, None),
};
Ok(ParsedAnimationData {
color_timescale: color_track.media_timescale,
color_codec_config: color_track.codec_config,
color_sample_table: color_track.sample_table,
alpha_timescale,
alpha_sample_table,
loop_count: color_track.loop_count,
})
}
fn read_minf<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<Option<(SampleTable, TrackCodecConfig)>> {
let mut iter = src.box_iter();
while let Some(mut b) = iter.next_box()? {
if b.head.name == BoxType::SampleTableBox {
return Ok(Some(read_stbl(&mut b)?));
} else {
skip_box_remain(&mut b)?;
}
}
Ok(None)
}
#[cfg(feature = "eager")]
#[allow(deprecated)]
fn extract_animation_frames(
sample_table: &SampleTable,
media_timescale: u32,
mdats: &mut [MediaDataBox],
) -> Result<TryVec<AnimationFrame>> {
let mut frames = TryVec::new();
let mut frame_durations = TryVec::new();
for entry in &sample_table.time_to_sample {
for _ in 0..entry.sample_count {
let duration_ms = if media_timescale > 0 {
((entry.sample_delta as u64) * 1000) / (media_timescale as u64)
} else {
0
};
frame_durations.push(u32::try_from(duration_ms).unwrap_or(u32::MAX))?;
}
}
for i in 0..sample_table.sample_sizes.len() {
let sample_offset = *sample_table.sample_offsets.get(i)
.ok_or(Error::InvalidData("sample offset index out of bounds"))?;
let sample_size = *sample_table.sample_sizes.get(i)
.ok_or(Error::InvalidData("sample size index out of bounds"))?;
let duration_ms = frame_durations.get(i).copied().unwrap_or(0);
let mut frame_data = TryVec::new();
let mut found = false;
for mdat in mdats.iter_mut() {
let range = ExtentRange::WithLength(Range {
start: sample_offset,
end: sample_offset + sample_size as u64,
});
if mdat.contains_extent(&range) {
mdat.read_extent(&range, &mut frame_data)?;
found = true;
break;
}
}
if !found {
log::warn!("Animation frame {} not found in mdat", i);
}
frames.push(AnimationFrame {
data: frame_data,
duration_ms,
})?;
}
Ok(frames)
}
fn read_grid<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<GridConfig> {
let version = read_fullbox_version_no_flags(src, options)?;
if version > 0 {
return Err(Error::Unsupported("grid version > 0"));
}
let flags_byte = src.read_u8()?;
let rows = src.read_u8()?;
let columns = src.read_u8()?;
let (output_width, output_height) = if flags_byte & 1 == 0 {
(u32::from(be_u16(src)?), u32::from(be_u16(src)?))
} else {
(be_u32(src)?, be_u32(src)?)
};
Ok(GridConfig {
rows,
columns,
output_width,
output_height,
})
}
fn read_iloc<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<ItemLocationBoxItem>> {
let version: IlocVersion = read_fullbox_version_no_flags(src, options)?.try_into()?;
let iloc = src.read_into_try_vec()?;
let mut iloc = BitReader::new(&iloc);
let offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
let length_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
let base_offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
let index_size: Option<IlocFieldSize> = match version {
IlocVersion::One | IlocVersion::Two => Some(iloc.read_u8(4)?.try_into()?),
IlocVersion::Zero => {
let _reserved = iloc.read_u8(4)?;
None
},
};
let item_count = match version {
IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?,
IlocVersion::Two => iloc.read_u32(32)?,
};
let mut items = TryVec::with_capacity(item_count.to_usize().min(4096))?;
for _ in 0..item_count {
let item_id = match version {
IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?,
IlocVersion::Two => iloc.read_u32(32)?,
};
let construction_method = match version {
IlocVersion::Zero => ConstructionMethod::File,
IlocVersion::One | IlocVersion::Two => {
let _reserved = iloc.read_u16(12)?;
match iloc.read_u16(4)? {
0 => ConstructionMethod::File,
1 => ConstructionMethod::Idat,
2 => return Err(Error::Unsupported("construction_method 'item_offset' is not supported")),
_ => return Err(Error::InvalidData("construction_method is taken from the set 0, 1 or 2 per ISO 14496-12:2015 § 8.11.3.3")),
}
},
};
let data_reference_index = iloc.read_u16(16)?;
if data_reference_index != 0 {
return Err(Error::Unsupported("external file references (iloc.data_reference_index != 0) are not supported"));
}
let base_offset = iloc.read_u64(base_offset_size.to_bits())?;
let extent_count = iloc.read_u16(16)?;
if extent_count < 1 {
return Err(Error::InvalidData("extent_count must have a value 1 or greater per ISO 14496-12:2015 § 8.11.3.3"));
}
let mut extents = TryVec::with_capacity(extent_count.to_usize())?;
for _ in 0..extent_count {
let _extent_index = match &index_size {
None | Some(IlocFieldSize::Zero) => None,
Some(index_size) => Some(iloc.read_u64(index_size.to_bits())?),
};
let extent_offset = iloc.read_u64(offset_size.to_bits())?;
let extent_length = iloc.read_u64(length_size.to_bits())?;
let start = base_offset
.checked_add(extent_offset)
.ok_or(Error::InvalidData("offset calculation overflow"))?;
let extent_range = if extent_length == 0 {
ExtentRange::ToEnd(RangeFrom { start })
} else {
let end = start
.checked_add(extent_length)
.ok_or(Error::InvalidData("end calculation overflow"))?;
ExtentRange::WithLength(Range { start, end })
};
extents.push(ItemLocationBoxExtent { extent_range })?;
}
items.push(ItemLocationBoxItem { item_id, construction_method, extents })?;
}
if iloc.remaining() == 0 {
Ok(items)
} else {
Err(Error::InvalidData("invalid iloc size"))
}
}
fn read_ftyp<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<FileTypeBox> {
let major = be_u32(src)?;
let minor = be_u32(src)?;
let bytes_left = src.bytes_left();
if !bytes_left.is_multiple_of(4) {
return Err(Error::InvalidData("invalid ftyp size"));
}
let brand_count = bytes_left / 4;
let mut brands = TryVec::with_capacity(brand_count.try_into()?)?;
for _ in 0..brand_count {
brands.push(be_u32(src)?.into())?;
}
Ok(FileTypeBox {
major_brand: From::from(major),
minor_version: minor,
compatible_brands: brands,
})
}
#[cfg_attr(debug_assertions, track_caller)]
fn check_parser_state<T>(header: &BoxHeader, left: &Take<T>) -> Result<(), Error> {
let limit = left.limit();
if limit == 0 || header.size == u64::MAX {
Ok(())
} else {
Err(Error::InvalidData("unread box content or bad parser sync"))
}
}
fn skip<T: Read>(src: &mut T, bytes: u64) -> Result<()> {
std::io::copy(&mut src.take(bytes), &mut std::io::sink())?;
Ok(())
}
fn be_u16<T: ReadBytesExt>(src: &mut T) -> Result<u16> {
src.read_u16::<byteorder::BigEndian>().map_err(From::from)
}
fn be_u32<T: ReadBytesExt>(src: &mut T) -> Result<u32> {
src.read_u32::<byteorder::BigEndian>().map_err(From::from)
}
fn be_i32<T: ReadBytesExt>(src: &mut T) -> Result<i32> {
src.read_i32::<byteorder::BigEndian>().map_err(From::from)
}
fn be_u64<T: ReadBytesExt>(src: &mut T) -> Result<u64> {
src.read_u64::<byteorder::BigEndian>().map_err(From::from)
}