blob: 3c20319ce86c481475debd10ceeb1e4746686818 [file] [log] [blame]
// Copyright (c) 2024, Alliance for Open Media. All rights reserved
//
// This source code is subject to the terms of the BSD 2 Clause License and
// the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
// was not distributed with this source code in the LICENSE file, you can
// obtain it at www.aomedia.org/license/software. If the Alliance for Open
// Media Patent License 1.0 was not distributed with this source code in the
// PATENTS file, you can obtain it at www.aomedia.org/license/patent.
//-----------------------------------------------------------------------------
#[derive(PartialEq, Debug)]
pub enum AvifInfoError {
// The input bitstream was correctly parsed until now but bytes are missing. The request
// should be repeated with more input bytes.
NotEnoughData,
// The input bitstream was correctly parsed until now but it is too complex. The parsing was
// stopped to avoid any timeout or crash.
TooComplex,
// The input bitstream is not a valid AVIF file, truncated or not.
InvalidFile,
}
// Ok means that the file was correctly parsed and the requested information was
// extracted. It is not guaranteed that the input bitstream is a valid complete
// AVIF file.
type AvifInfoResult<T> = Result<T, AvifInfoError>;
#[derive(Debug, Default, PartialEq)]
pub struct Features {
// In number of pixels. Ignores crop and rotation.
pub width: u32,
pub height: u32,
// Likely 8, 10 or 12 bits per channel per pixel.
pub bit_depth: u8,
// Likely 1, 2, 3 or 4 channels: (1 monochrome or 3 colors) + (0 or 1 alpha)
pub num_channels: u8,
// True if a gain map was found.
pub has_gainmap: bool,
// Id of the gain map item. Assumes there is at most one. If there are several gain map items
// (e.g. because the main image is tiled and each tile has an independent gain map), then this
// is one of the ids, arbitrarily chosen.
pub gainmap_item_id: u8,
// Start location in bytes of the primary item id, relative to the beginning of the given
// payload. The primary item id is a big endian number stored on bytes primary_item_id_location
// to primary_item_id_location+primary_item_id_bytes-1 inclusive.
pub primary_item_id_location: usize,
// Number of bytes of the primary item id.
pub primary_item_id_bytes: u8,
}
//------------------------------------------------------------------------------
// Status returned when reading the content of a box (or file).
#[derive(PartialEq)]
enum InternalError {
NotFound, // Input correctly parsed but information is missing or elsewhere.
Truncated, // Input correctly parsed until missing bytes to continue.
Aborted, // Input correctly parsed until stopped to avoid timeout or crash.
Invalid, // Input incorrectly parsed.
}
// Ok means "Input correctly parsed and information retrieved".
type InternalResult<T> = Result<T, InternalError>;
impl From<InternalError> for AvifInfoError {
fn from(error: InternalError) -> Self {
match error {
InternalError::NotFound | InternalError::Truncated => AvifInfoError::NotEnoughData,
InternalError::Aborted => AvifInfoError::TooComplex,
InternalError::Invalid => AvifInfoError::InvalidFile,
}
}
}
// Be reasonable. Avoid timeouts and out-of-memory.
const AVIFINFO_MAX_NUM_BOXES: u32 = 4096;
// InternalFeatures uses u8 to store values.
const AVIFINFO_MAX_VALUE: u8 = u8::MAX;
// Maximum number of stored associations. Past that, they are skipped.
const AVIFINFO_MAX_TILES: usize = 16;
const AVIFINFO_MAX_PROPS: usize = 32;
const AVIFINFO_MAX_FEATURES: usize = 8;
const AVIFINFO_UNDEFINED: u8 = 0;
//------------------------------------------------------------------------------
// Streamed input struct and helper functions.
struct Stream<'a> {
// The available bytes.
data: Option<&'a [u8]>,
// The known size of the parent box, if any.
size: Option<usize>,
// How many bytes were read or skipped.
offset: usize,
}
impl Stream<'_> {
fn read(&mut self, num_bytes: usize) -> InternalResult<&[u8]> {
if num_bytes == 0 {
return Ok(&[]);
}
let offset = self.offset;
self.skip(num_bytes)?;
match &self.data {
Some(data) if self.offset <= data.len() => Ok(&data[offset..self.offset]),
_ => Err(InternalError::Truncated),
}
}
fn read_u8(&mut self) -> InternalResult<u8> {
Ok(self.read(1)?[0])
}
fn read_u16(&mut self) -> InternalResult<u16> {
Ok(u16::from_be_bytes(self.read(2)?.try_into().unwrap()))
}
fn read_u24(&mut self) -> InternalResult<u32> {
Ok((self.read_uint(1)? << 16) | self.read_uint(2)?)
}
fn read_u32(&mut self) -> InternalResult<u32> {
Ok(u32::from_be_bytes(self.read(4)?.try_into().unwrap()))
}
fn read_u64(&mut self) -> InternalResult<u64> {
Ok(u64::from_be_bytes(self.read(8)?.try_into().unwrap()))
}
fn read_uint(&mut self, num_bytes: u8) -> InternalResult<u32> {
match num_bytes {
1 => Ok(self.read_u8()? as u32),
2 => Ok(self.read_u16()? as u32),
4 => Ok(self.read_u32()?),
_ => Err(InternalError::Aborted),
}
}
fn read_4cc(&mut self) -> InternalResult<&[u8; 4]> {
Ok(self.read(4)?.try_into().unwrap())
}
fn skip(&mut self, num_bytes: usize) -> InternalResult<()> {
self.offset = self.offset.checked_add(num_bytes).ok_or(InternalError::Aborted)?;
if let Some(size) = self.size {
if self.offset > size {
return Err(InternalError::Invalid);
}
}
Ok(())
}
fn num_read_bytes(&self) -> usize {
self.offset // Includes the unchecked skipped bytes.
}
fn has_more_bytes(&self) -> bool {
match self.size {
Some(size) => self.offset < size,
None => true,
}
}
// Returns a portion of the stream. The size of the portion is either given
// or all remaining bytes are returned.
fn substream(&mut self, num_bytes: Option<usize>) -> InternalResult<Stream> {
let offset = self.offset;
let num_transferred_bytes = num_bytes.unwrap_or(match &self.data {
Some(data) => data.len().saturating_sub(offset),
None => 0,
});
self.skip(num_transferred_bytes)?;
self.offset = offset.checked_add(num_transferred_bytes).ok_or(InternalError::Aborted)?;
Ok(Stream {
data: if let Some(data) = &self.data {
let available_size = data.len().saturating_sub(offset);
let size = std::cmp::min(available_size, num_transferred_bytes);
if size != 0 { Some(&data[offset..offset + size]) } else { None }
} else {
None
},
size: num_bytes,
offset: 0,
})
}
}
//------------------------------------------------------------------------------
// Features are parsed into temporary property associations.
#[derive(Default)]
struct InternalTile {
tile_item_id: u8,
parent_item_id: u8,
dimg_idx: u8, // Index of this association in the dimg box (0-based).
}
#[derive(Default)]
struct InternalProp {
property_index: u8,
item_id: u8,
}
#[derive(Default)]
struct InternalDimProp {
property_index: u8,
width: u32,
height: u32,
}
#[derive(Default)]
struct InternalChanProp {
property_index: u8,
bit_depth: u8,
num_channels: u8,
}
#[derive(Default)]
struct InternalFeatures {
has_primary_item: bool, // True if "pitm" was parsed.
has_alpha: bool, // True if an alpha "auxC" was parsed.
gainmap_property_index: u8, // Index of the gain map auxC property.
primary_item_id: u8,
primary_item_features: Features, // Deduced from the data below.
data_was_skipped: bool, // True if some loops/indices were skipped.
tone_mapped_item_id: u8, // Id of the "tmap" box, > 0 if present.
iinf_parsed: bool, // True if the "iinf" (item info) box was parsed.
iref_parsed: bool, // True if the "iref" (item reference) box was parsed.
num_tiles: usize,
tiles: [InternalTile; AVIFINFO_MAX_TILES],
num_props: usize,
props: [InternalProp; AVIFINFO_MAX_PROPS],
num_dim_props: usize,
dim_props: [InternalDimProp; AVIFINFO_MAX_FEATURES],
num_chan_props: usize,
chan_props: [InternalChanProp; AVIFINFO_MAX_FEATURES],
}
impl InternalFeatures {
fn get_item_features(&mut self, target_item_id: u8, tile_depth: u32) -> InternalResult<()> {
for prop_item in 0..self.num_props {
if self.props[prop_item].item_id != target_item_id {
continue;
}
let property_index = self.props[prop_item].property_index;
// Retrieve the width and height of the primary item if not already done.
if target_item_id == self.primary_item_id
&& (self.primary_item_features.width == AVIFINFO_UNDEFINED as u32
|| self.primary_item_features.height == AVIFINFO_UNDEFINED as u32)
{
for i in 0..self.num_dim_props {
if self.dim_props[i].property_index != property_index {
continue;
}
self.primary_item_features.width = self.dim_props[i].width;
self.primary_item_features.height = self.dim_props[i].height;
if self.primary_item_features.bit_depth != AVIFINFO_UNDEFINED
&& self.primary_item_features.num_channels != AVIFINFO_UNDEFINED
{
return Ok(());
}
break;
}
}
// Retrieve the bit depth and number of channels of the target item if not
// already done.
if self.primary_item_features.bit_depth == AVIFINFO_UNDEFINED
|| self.primary_item_features.num_channels == AVIFINFO_UNDEFINED
{
for i in 0..self.num_chan_props {
if self.chan_props[i].property_index != property_index {
continue;
}
self.primary_item_features.bit_depth = self.chan_props[i].bit_depth;
self.primary_item_features.num_channels = self.chan_props[i].num_channels;
if self.primary_item_features.width != AVIFINFO_UNDEFINED as u32
&& self.primary_item_features.height != AVIFINFO_UNDEFINED as u32
{
return Ok(());
}
break;
}
}
}
// Check for the bit_depth and num_channels in a tile if not yet found.
if tile_depth < 3 {
for tile in 0..self.num_tiles {
if self.tiles[tile].parent_item_id != target_item_id {
continue;
}
match self.get_item_features(self.tiles[tile].tile_item_id, tile_depth + 1) {
Ok(()) => return Ok(()),
Err(InternalError::NotFound) => {} // Keep searching.
Err(error) => return Err(error),
}
}
}
Err(InternalError::NotFound)
}
// Generates the primary_item_features.
// Returns InternalError::NotFound if there is not enough information.
fn get_primary_item_features(&mut self) -> InternalResult<()> {
// Nothing to do without the primary item ID.
if !self.has_primary_item {
return Err(InternalError::NotFound);
}
// Early exit.
if self.num_dim_props == 0 || self.num_chan_props == 0 {
return Err(InternalError::NotFound);
}
// Look for a gain map.
// HEIF scheme: gain map is a hidden input of a derived item.
if self.tone_mapped_item_id != AVIFINFO_UNDEFINED {
for tile in 0..self.num_tiles {
if self.tiles[tile].parent_item_id == self.tone_mapped_item_id
&& self.tiles[tile].dimg_idx == 1
{
self.primary_item_features.has_gainmap = true;
self.primary_item_features.gainmap_item_id = self.tiles[tile].tile_item_id;
break;
}
}
}
// Adobe scheme: gain map is an auxiliary item.
if !self.primary_item_features.has_gainmap && self.gainmap_property_index > 0 {
for prop_item in 0..self.num_props {
if self.props[prop_item].property_index == self.gainmap_property_index {
self.primary_item_features.has_gainmap = true;
self.primary_item_features.gainmap_item_id = self.props[prop_item].item_id;
break;
}
}
}
// If the gain map has not been found but we haven't read all the relevant
// metadata, we might still find one later and cannot stop now.
if !self.primary_item_features.has_gainmap
&& (!self.iinf_parsed
|| (self.tone_mapped_item_id != AVIFINFO_UNDEFINED && !self.iref_parsed))
{
return Err(InternalError::NotFound);
}
self.get_item_features(self.primary_item_id, /* tile_depth= */ 0)?;
// "auxC" is parsed before the "ipma" properties so it is known now, if any.
if self.has_alpha {
self.primary_item_features.num_channels += 1;
}
Ok(())
}
}
//------------------------------------------------------------------------------
// Box header parsing and various size checks.
struct InternalBox {
box_type: [u8; 4], // Four characters.
version: u8, // 0 or actual version if this is a full box.
flags: u32, // 0 or actual value if this is a full box.
content_size: Option<usize>, // If known, size of the box in bytes, header exclusive.
}
// Reads the header of a box starting at the beginning of a stream.
fn parse_box(
nesting_level: u32,
stream: &mut Stream,
num_parsed_boxes: &mut u32,
) -> InternalResult<InternalBox> {
// See ISO/IEC 14496-12:2012(E) 4.2
let mut box_header_size = 8usize; // box 32-bit size + 32-bit type (at least)
let mut box_size: Option<usize> =
Some(stream.read_u32()?.try_into().or(Err(InternalError::Aborted))?);
let mut box_type = *stream.read_4cc()?;
// box_size==1 means 64-bit size should be read after the box type.
// box_size==0 means this box extends to all remaining bytes.
if box_size == Some(1) {
box_header_size += 8;
box_size = Some(stream.read_u64()?.try_into().or(Err(InternalError::Aborted))?);
} else if box_size == Some(0) {
if nesting_level != 0 {
// ISO/IEC 14496-12 4.2.2:
// if size is 0, then this box shall be in a top-level box
// (i.e. not contained in another box)
return Err(InternalError::Invalid);
}
box_size = None;
}
// 16 bytes of usertype should be read here if the box type is 'uuid'.
// 'uuid' boxes are skipped so usertype is part of the skipped body.
let has_fullbox_header = matches!(
&box_type,
b"meta" | b"pitm" | b"ipma" | b"ispe" | b"pixi" | b"iref" | b"auxC" | b"iinf" | b"infe"
);
if has_fullbox_header {
box_header_size += 4;
}
let content_size = match box_size {
Some(box_size) => {
Some(box_size.checked_sub(box_header_size).ok_or(InternalError::Invalid)?)
}
None => None,
};
if let Some(size) = stream.size {
if content_size.unwrap_or(box_header_size) > size.saturating_sub(stream.offset) {
return Err(InternalError::Invalid);
}
}
// get_features() can be called on a full stream or on a stream
// where the 'ftyp' box was already read. Do not count 'ftyp' boxes towards
// AVIFINFO_MAX_NUM_BOXES, so that this function returns the same status in
// both situations (because of the AVIFINFO_MAX_NUM_BOXES check that would
// compare a different box count otherwise). This is fine because top-level
// 'ftyp' boxes are just skipped anyway.
if nesting_level != 0 || &box_type != b"ftyp" {
// Avoid timeouts. The maximum number of parsed boxes is arbitrary.
*num_parsed_boxes += 1;
if *num_parsed_boxes >= AVIFINFO_MAX_NUM_BOXES {
return Err(InternalError::Aborted);
}
}
let mut version = 0;
let mut flags = 0;
if has_fullbox_header {
version = stream.read_u8()?;
flags = stream.read_u24()?;
// See AV1 Image File Format (AVIF) 8.1
// at https://aomediacodec.github.io/av1-avif/#avif-boxes (available when
// https://github.com/AOMediaCodec/av1-avif/pull/170 is merged).
let is_parsable = (&box_type == b"meta" && version == 0)
|| (&box_type == b"pitm" && version <= 1)
|| (&box_type == b"ipma" && version <= 1)
|| (&box_type == b"ispe" && version == 0)
|| (&box_type == b"pixi" && version == 0)
|| (&box_type == b"iref" && version <= 1)
|| (&box_type == b"auxC" && version == 0)
|| (&box_type == b"iinf" && version <= 1)
|| (&box_type == b"infe" && version <= 2);
// Instead of considering this file as invalid, skip unparsable boxes.
if !is_parsable {
box_type = *b"skip"; // FreeSpaceBox. To be ignored by readers.
}
}
Ok(InternalBox { box_type, version, flags, content_size })
}
//------------------------------------------------------------------------------
impl InternalFeatures {
// Parses a stream of an 'ipco' box.
// 'ispe' is used for width and height, 'pixi' and 'av1C' are used for bit depth
// and number of channels, and 'auxC' is used for alpha.
fn parse_ipco(
&mut self,
nesting_level: u32,
stream: &mut Stream,
num_parsed_boxes: &mut u32,
) -> InternalResult<()> {
let mut box_index = 1u8; // 1-based index. Used for iterating over properties.
while stream.has_more_bytes() {
let box_features = parse_box(nesting_level, stream, num_parsed_boxes)?;
let mut box_stream = stream.substream(box_features.content_size)?;
match &box_features.box_type {
b"ispe" => {
// See ISO/IEC 23008-12:2017(E) 6.5.3.2
let width = box_stream.read_u32()?;
let height = box_stream.read_u32()?;
if width == 0 || height == 0 {
return Err(InternalError::Invalid);
}
if self.num_dim_props < AVIFINFO_MAX_FEATURES {
self.dim_props[self.num_dim_props].property_index = box_index;
self.dim_props[self.num_dim_props].width = width;
self.dim_props[self.num_dim_props].height = height;
self.num_dim_props += 1;
} else {
self.data_was_skipped = true;
}
}
b"pixi" => {
// See ISO/IEC 23008-12:2017(E) 6.5.6.2
let num_channels = box_stream.read_u8()?;
if num_channels == 0 || num_channels > 3 {
return Err(InternalError::Invalid);
}
let bit_depth = box_stream.read_u8()?;
if bit_depth == 0 {
return Err(InternalError::Invalid);
}
for _ in 1..num_channels {
// Bit depth should be the same for all channels.
if box_stream.read_u8()? != bit_depth {
return Err(InternalError::Invalid);
}
}
if self.num_chan_props < AVIFINFO_MAX_FEATURES {
self.chan_props[self.num_chan_props].property_index = box_index;
self.chan_props[self.num_chan_props].bit_depth = bit_depth;
self.chan_props[self.num_chan_props].num_channels = num_channels;
self.num_chan_props += 1;
} else {
self.data_was_skipped = true;
}
}
b"av1C" => {
// See AV1 Codec ISO Media File Format Binding 2.3.1
// at https://aomediacodec.github.io/av1-isobmff/#av1c
// Only parse the necessary third byte. Assume that the others are valid.
box_stream.skip(2)?;
let data = box_stream.read_u8()?;
let high_bitdepth = (data & 0x40) != 0;
let twelve_bit = (data & 0x20) != 0;
let monochrome = (data & 0x10) != 0;
if twelve_bit && !high_bitdepth {
return Err(InternalError::Invalid);
}
if self.num_chan_props < AVIFINFO_MAX_FEATURES {
self.chan_props[self.num_chan_props].property_index = box_index;
self.chan_props[self.num_chan_props].bit_depth =
if high_bitdepth { if twelve_bit { 12 } else { 10 } } else { 8 };
self.chan_props[self.num_chan_props].num_channels =
if monochrome { 1 } else { 3 };
self.num_chan_props += 1;
} else {
self.data_was_skipped = true;
}
}
b"auxC" => {
// See AV1 Image File Format (AVIF) 4
// at https://aomediacodec.github.io/av1-avif/#auxiliary-images
const ALPHA_STR: &[u8] = b"urn:mpeg:mpegB:cicp:systems:auxiliary:alpha\0";
const GAINMAP_STR: &[u8] = b"urn:com:photo:aux:hdrgainmap\0";
// Check for a gain map or for an alpha plane.
let content_size = box_features.content_size.unwrap();
let data = box_stream.read(std::cmp::min(content_size, ALPHA_STR.len()))?;
if &data[..std::cmp::min(data.len(), ALPHA_STR.len())] == ALPHA_STR {
// Note: It is unlikely but possible that this alpha plane does not belong
// to the primary item or a tile. Ignore this issue.
self.has_alpha = true;
} else if &data[..std::cmp::min(data.len(), GAINMAP_STR.len())] == GAINMAP_STR {
// Note: It is unlikely but possible that this gain map does not belong to
// the primary item or a tile. Ignore this issue.
self.gainmap_property_index = box_index;
}
}
_ => {}
}
if box_index == AVIFINFO_MAX_VALUE {
self.data_was_skipped = true;
return Err(InternalError::NotFound);
}
box_index += 1;
}
Err(InternalError::NotFound)
}
// Parses a stream of an 'iprp' box.
// The 'ipco' box contains the properties which are linked to items by the
// 'ipma' box.
fn parse_iprp(
&mut self,
nesting_level: u32,
stream: &mut Stream,
num_parsed_boxes: &mut u32,
) -> InternalResult<()> {
while stream.has_more_bytes() {
let box_features = parse_box(nesting_level, stream, num_parsed_boxes)?;
let mut box_stream = stream.substream(box_features.content_size)?;
match &box_features.box_type {
b"ipco" => {
match self.parse_ipco(nesting_level + 1, &mut box_stream, num_parsed_boxes) {
Ok(()) => return Ok(()),
Err(InternalError::NotFound) => {} // Keep searching.
Err(error) => return Err(error),
}
}
b"ipma" => {
// See ISO/IEC 23008-12:2017(E) 9.3.2
let entry_count = box_stream.read_u32()?;
let id_num_bytes = if box_features.version < 1 { 2 } else { 4 };
let index_num_bytes = if box_features.flags & 1 != 0 { 2 } else { 1 };
let essential_bit_mask =
if box_features.flags & 1 != 0 { 0x8000 } else { 0x80 };
for entry in 0..entry_count as usize {
if entry >= AVIFINFO_MAX_PROPS || self.num_props >= AVIFINFO_MAX_PROPS {
self.data_was_skipped = true;
break;
}
let item_id = box_stream.read_uint(id_num_bytes)?;
let association_count = box_stream.read_u8()? as usize;
let mut property = 0usize;
while property < association_count {
if property >= AVIFINFO_MAX_PROPS
|| self.num_props >= AVIFINFO_MAX_PROPS
{
self.data_was_skipped = true;
break;
}
let value = box_stream.read_uint(index_num_bytes)?;
// let essential = (value & essential_bit_mask); // Unused.
let property_index = value & (!essential_bit_mask);
if property_index <= AVIFINFO_MAX_VALUE as u32
&& item_id <= AVIFINFO_MAX_VALUE as u32
{
self.props[self.num_props].property_index = property_index as u8;
self.props[self.num_props].item_id = item_id as u8;
self.num_props += 1;
} else {
self.data_was_skipped = true;
}
property += 1;
}
if property < association_count {
break; // Do not read garbage.
}
}
// If all features are available now, do not look further.
match self.get_primary_item_features() {
Ok(()) => return Ok(()),
Err(InternalError::NotFound) => {}
Err(error) => return Err(error),
}
}
_ => {}
}
}
Err(InternalError::NotFound)
}
// Parses a stream of an 'iref' box.
// The 'dimg' boxes contain links between tiles and their parent items, which
// can be used to infer bit depth and number of channels for the primary item
// when the latter does not have these properties.
fn parse_iref(
&mut self,
nesting_level: u32,
stream: &mut Stream,
num_parsed_boxes: &mut u32,
) -> InternalResult<()> {
self.iref_parsed = true;
while stream.has_more_bytes() {
let box_features = parse_box(nesting_level, stream, num_parsed_boxes)?;
let mut box_stream = stream.substream(box_features.content_size)?;
if let b"dimg" = &box_features.box_type {
// See ISO/IEC 14496-12:2015(E) 8.11.12.2
let num_bytes_per_id = if box_features.version == 0 { 2 } else { 4 };
let from_item_id = box_stream.read_uint(num_bytes_per_id)?;
let reference_count = box_stream.read_u16()?;
for i in 0..reference_count {
if i as usize >= AVIFINFO_MAX_TILES {
self.data_was_skipped = true;
break;
}
let to_item_id = box_stream.read_uint(num_bytes_per_id)?;
if from_item_id <= AVIFINFO_MAX_VALUE as u32
&& to_item_id <= AVIFINFO_MAX_VALUE as u32
&& self.num_tiles < AVIFINFO_MAX_TILES
{
self.tiles[self.num_tiles].tile_item_id = to_item_id as u8;
self.tiles[self.num_tiles].parent_item_id = from_item_id as u8;
self.tiles[self.num_tiles].dimg_idx = i as u8;
self.num_tiles += 1;
} else {
self.data_was_skipped = true;
}
}
// If all features are available now, do not look further.
match self.get_primary_item_features() {
Ok(()) => return Ok(()),
Err(InternalError::NotFound) => {}
Err(error) => return Err(error),
}
}
}
Err(InternalError::NotFound)
}
// Parses a stream of an 'iinf' box.
fn parse_iinf(
&mut self,
nesting_level: u32,
stream: &mut Stream,
box_version: u8,
num_parsed_boxes: &mut u32,
) -> InternalResult<()> {
self.iinf_parsed = true;
let num_bytes_per_entry_count = if box_version == 0 { 2 } else { 4 };
let entry_count = stream.read_uint(num_bytes_per_entry_count)?;
for _ in 0..entry_count {
let box_features = parse_box(nesting_level, stream, num_parsed_boxes)?;
let mut box_stream = stream.substream(box_features.content_size)?;
if let b"infe" = &box_features.box_type {
// See ISO/IEC 14496-12:2015(E) 8.11.6.2
let num_bytes_per_id = if box_features.version == 2 { 2 } else { 4 };
let item_id = box_stream.read_uint(num_bytes_per_id)?;
// Skip item_protection_index.
box_stream.skip(2)?;
if box_stream.read_4cc()? == b"tmap" {
// Tone Mapped Image: indicates the presence of a gain map.
if item_id <= AVIFINFO_MAX_VALUE as u32 {
self.tone_mapped_item_id = item_id as u8;
} else {
self.data_was_skipped = true;
}
}
}
if !stream.has_more_bytes() {
break; // Ignore entry_count bigger than box.
}
}
Err(InternalError::NotFound)
}
// Parses a stream of a 'meta' box. It looks for the primary item ID in the
// 'pitm' box and recurses into other boxes to find the features.
fn parse_meta(
&mut self,
nesting_level: u32,
stream_offset: usize,
stream: &mut Stream,
num_parsed_boxes: &mut u32,
) -> InternalResult<()> {
while stream.has_more_bytes() {
let box_features = parse_box(nesting_level, stream, num_parsed_boxes)?;
let box_header_size = stream.num_read_bytes();
let mut box_stream = stream.substream(box_features.content_size)?;
match &box_features.box_type {
b"pitm" => {
// See ISO/IEC 14496-12:2015(E) 8.11.4.2
let num_bytes_per_id = if box_features.version == 0 { 2 } else { 4 };
let primary_item_id_location =
stream_offset.checked_add(box_header_size).ok_or(InternalError::Aborted)?;
let primary_item_id = box_stream.read_uint(num_bytes_per_id)?;
if primary_item_id > AVIFINFO_MAX_VALUE as u32 {
return Err(InternalError::Aborted);
}
self.has_primary_item = true;
self.primary_item_id = primary_item_id as u8;
self.primary_item_features.primary_item_id_location = primary_item_id_location;
self.primary_item_features.primary_item_id_bytes = num_bytes_per_id;
}
b"iprp" => {
match self.parse_iprp(nesting_level + 1, &mut box_stream, num_parsed_boxes) {
Ok(()) => return Ok(()),
Err(InternalError::NotFound) => {} // Keep searching.
Err(error) => return Err(error),
}
}
b"iref" => {
match self.parse_iref(nesting_level + 1, &mut box_stream, num_parsed_boxes) {
Ok(()) => return Ok(()),
Err(InternalError::NotFound) => {} // Keep searching.
Err(error) => return Err(error),
}
}
b"iinf" => {
match self.parse_iinf(
nesting_level + 1,
&mut box_stream,
box_features.version,
num_parsed_boxes,
) {
Ok(()) => return Ok(()),
Err(InternalError::NotFound) => {} // Keep searching.
Err(error) => return Err(error),
}
}
_ => {}
}
}
// According to ISO/IEC 14496-12:2012(E) 8.11.1.1 there is at most one 'meta'.
Err(if self.data_was_skipped { InternalError::Aborted } else { InternalError::Invalid })
}
}
//------------------------------------------------------------------------------
// Parses a file stream. The file type is checked through the 'ftyp' box.
fn parse_ftyp(stream: &mut Stream) -> InternalResult<()> {
let mut num_parsed_boxes = 0u32;
let nesting_level = 0;
let box_features = parse_box(nesting_level, stream, &mut num_parsed_boxes)?;
if &box_features.box_type != b"ftyp" {
return Err(InternalError::Invalid);
}
// Consider a FileTypeBox running till the end of the file as invalid,
// because it should be first and a MetaBox should follow.
let content_size = box_features.content_size.ok_or(InternalError::Invalid)?;
// Iterate over brands. See ISO/IEC 14496-12:2012(E) 4.3.1
if content_size < 8 {
// major_brand,minor_version
return Err(InternalError::Invalid);
}
for i in 0..content_size / 4 {
let brand = stream.read_4cc()?;
if i == 1 {
continue; // Skip minor_version.
}
if brand == b"avif" || brand == b"avis" {
stream.skip(content_size - (i * 4 + 4))?;
return Ok(());
}
if i > 32 {
return Err(InternalError::Aborted); // Be reasonable.
}
}
Err(InternalError::Invalid)
}
// Parses a file stream. Features are extracted from the 'meta' box.
impl InternalFeatures {
fn parse_file(&mut self, stream: &mut Stream) -> InternalResult<()> {
let mut num_parsed_boxes = 0u32;
loop {
let box_features =
parse_box(/* nesting_level= */ 0, stream, &mut num_parsed_boxes)?;
let offset = stream.num_read_bytes();
let mut box_stream = stream.substream(box_features.content_size)?;
if &box_features.box_type == b"meta" {
return self.parse_meta(
/* nesting_level= */ 1,
offset,
&mut box_stream,
&mut num_parsed_boxes,
);
} else if box_features.content_size.is_none() {
// This non-MetaBox runs till the end of the file. 'meta' is missing.
return Err(InternalError::Invalid);
}
}
}
}
//------------------------------------------------------------------------------
// Fixed-size input public API
pub fn identify(data: &[u8]) -> AvifInfoResult<()> {
match parse_ftyp(&mut Stream { data: Some(data), size: None, offset: 0 }) {
Ok(()) => Ok(()),
Err(error) => Err(error.into()),
}
}
pub fn get_features(data: &[u8]) -> AvifInfoResult<Features> {
let mut features = InternalFeatures { ..Default::default() };
match features.parse_file(&mut Stream { data: Some(data), size: None, offset: 0 }) {
Ok(()) => Ok(features.primary_item_features),
Err(error) => Err(error.into()),
}
}
//------------------------------------------------------------------------------
// Streamed input API
// There is no streamed input API yet.