1#![deny(unsafe_code)]
2#![allow(clippy::missing_safety_doc)]
3use arrayvec::ArrayVec;
12use log::{debug, warn};
13
14use bitreader::BitReader;
15use byteorder::ReadBytesExt;
16use fallible_collections::{TryClone, TryReserveError};
17use std::borrow::Cow;
18use std::convert::{TryFrom, TryInto as _};
19
20use std::io::{Read, Take};
21use std::num::NonZeroU32;
22use std::ops::{Range, RangeFrom};
23
24mod obu;
25
26mod boxes;
27use crate::boxes::{BoxType, FourCC};
28
29#[cfg(feature = "eager")]
31pub mod c_api;
32
33pub use enough::{Stop, StopReason, Unstoppable};
34
35trait ToU64 {
41 fn to_u64(self) -> u64;
42}
43
44impl ToU64 for usize {
48 fn to_u64(self) -> u64 {
49 const _: () = assert!(std::mem::size_of::<usize>() <= std::mem::size_of::<u64>());
50 self.try_into().ok().unwrap()
51 }
52}
53
54pub(crate) trait ToUsize {
57 fn to_usize(self) -> usize;
58}
59
60macro_rules! impl_to_usize_from {
64 ( $from_type:ty ) => {
65 impl ToUsize for $from_type {
66 fn to_usize(self) -> usize {
67 const _: () = assert!(std::mem::size_of::<$from_type>() <= std::mem::size_of::<usize>());
68 self.try_into().ok().unwrap()
69 }
70 }
71 };
72}
73
74impl_to_usize_from!(u8);
75impl_to_usize_from!(u16);
76impl_to_usize_from!(u32);
77
78trait Offset {
80 fn offset(&self) -> u64;
81}
82
83struct OffsetReader<'a, T> {
85 reader: &'a mut T,
86 offset: u64,
87}
88
89impl<'a, T> OffsetReader<'a, T> {
90 fn new(reader: &'a mut T) -> Self {
91 Self { reader, offset: 0 }
92 }
93}
94
95impl<T> Offset for OffsetReader<'_, T> {
96 fn offset(&self) -> u64 {
97 self.offset
98 }
99}
100
101impl<T: Read> Read for OffsetReader<'_, T> {
102 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
103 let bytes_read = self.reader.read(buf)?;
104 self.offset = self
105 .offset
106 .checked_add(bytes_read.to_u64())
107 .ok_or(Error::Unsupported("total bytes read too large for offset type"))?;
108 Ok(bytes_read)
109 }
110}
111
112#[doc(hidden)]
113pub type TryVec<T> = fallible_collections::TryVec<T>;
114type TryString = fallible_collections::TryVec<u8>;
115
116#[allow(dead_code)]
118struct Vec;
119#[allow(dead_code)]
120struct Box;
121#[allow(dead_code)]
122struct HashMap;
123#[allow(dead_code)]
124struct String;
125
126#[derive(Debug)]
131pub enum Error {
132 InvalidData(&'static str),
134 Unsupported(&'static str),
136 UnexpectedEOF,
138 Io(std::io::Error),
140 NoMoov,
142 OutOfMemory,
144 ResourceLimitExceeded(&'static str),
146 Stopped(enough::StopReason),
148}
149
150impl std::fmt::Display for Error {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 let msg = match self {
153 Self::InvalidData(s) | Self::Unsupported(s) | Self::ResourceLimitExceeded(s) => s,
154 Self::UnexpectedEOF => "EOF",
155 Self::Io(err) => return err.fmt(f),
156 Self::NoMoov => "Missing Moov box",
157 Self::OutOfMemory => "OOM",
158 Self::Stopped(reason) => return write!(f, "Stopped: {}", reason),
159 };
160 f.write_str(msg)
161 }
162}
163
164impl std::error::Error for Error {}
165
166impl From<bitreader::BitReaderError> for Error {
167 #[cold]
168 #[cfg_attr(debug_assertions, track_caller)]
169 fn from(err: bitreader::BitReaderError) -> Self {
170 log::warn!("bitreader: {err}");
171 debug_assert!(!matches!(err, bitreader::BitReaderError::TooManyBitsForType { .. })); Self::InvalidData("truncated bits")
173 }
174}
175
176impl From<std::io::Error> for Error {
177 fn from(err: std::io::Error) -> Self {
178 match err.kind() {
179 std::io::ErrorKind::UnexpectedEof => Self::UnexpectedEOF,
180 _ => Self::Io(err),
181 }
182 }
183}
184
185impl From<std::string::FromUtf8Error> for Error {
186 fn from(_: std::string::FromUtf8Error) -> Self {
187 Self::InvalidData("invalid utf8")
188 }
189}
190
191impl From<std::num::TryFromIntError> for Error {
192 fn from(_: std::num::TryFromIntError) -> Self {
193 Self::Unsupported("integer conversion failed")
194 }
195}
196
197impl From<Error> for std::io::Error {
198 fn from(err: Error) -> Self {
199 let kind = match err {
200 Error::InvalidData(_) => std::io::ErrorKind::InvalidData,
201 Error::UnexpectedEOF => std::io::ErrorKind::UnexpectedEof,
202 Error::Io(io_err) => return io_err,
203 _ => std::io::ErrorKind::Other,
204 };
205 Self::new(kind, err)
206 }
207}
208
209impl From<TryReserveError> for Error {
210 fn from(_: TryReserveError) -> Self {
211 Self::OutOfMemory
212 }
213}
214
215impl From<enough::StopReason> for Error {
216 fn from(reason: enough::StopReason) -> Self {
217 Self::Stopped(reason)
218 }
219}
220
221pub type Result<T, E = Error> = std::result::Result<T, E>;
223
224#[derive(Debug, Clone, Copy)]
233struct BoxHeader {
234 name: BoxType,
236 size: u64,
238 offset: u64,
240 #[allow(unused)]
242 uuid: Option<[u8; 16]>,
243}
244
245impl BoxHeader {
246 const MIN_SIZE: u64 = 8;
248 const MIN_LARGE_SIZE: u64 = 16;
250}
251
252#[derive(Debug)]
254#[allow(unused)]
255struct FileTypeBox {
256 major_brand: FourCC,
257 minor_version: u32,
258 compatible_brands: TryVec<FourCC>,
259}
260
261#[derive(Debug)]
263#[allow(unused)]
264struct HandlerBox {
265 handler_type: FourCC,
266}
267
268#[derive(Debug)]
269#[allow(unused)]
270pub(crate) struct AV1ConfigBox {
271 pub(crate) profile: u8,
272 pub(crate) level: u8,
273 pub(crate) tier: u8,
274 pub(crate) bit_depth: u8,
275 pub(crate) monochrome: bool,
276 pub(crate) chroma_subsampling_x: u8,
277 pub(crate) chroma_subsampling_y: u8,
278 pub(crate) chroma_sample_position: u8,
279 pub(crate) initial_presentation_delay_present: bool,
280 pub(crate) initial_presentation_delay_minus_one: u8,
281 pub(crate) config_obus: TryVec<u8>,
282}
283
284#[derive(Debug, Clone, Copy)]
288#[derive(Default)]
289pub struct ParseOptions {
290 pub lenient: bool,
298}
299
300#[derive(Debug, Clone)]
326pub struct DecodeConfig {
327 pub peak_memory_limit: Option<u64>,
330
331 pub total_megapixels_limit: Option<u32>,
334
335 pub max_animation_frames: Option<u32>,
338
339 pub max_grid_tiles: Option<u32>,
342
343 pub lenient: bool,
346}
347
348impl Default for DecodeConfig {
349 fn default() -> Self {
350 Self {
351 peak_memory_limit: Some(1_000_000_000),
352 total_megapixels_limit: Some(512),
353 max_animation_frames: Some(10_000),
354 max_grid_tiles: Some(1_000),
355 lenient: false,
356 }
357 }
358}
359
360impl DecodeConfig {
361 pub fn unlimited() -> Self {
365 Self {
366 peak_memory_limit: None,
367 total_megapixels_limit: None,
368 max_animation_frames: None,
369 max_grid_tiles: None,
370 lenient: false,
371 }
372 }
373
374 pub fn with_peak_memory_limit(mut self, bytes: u64) -> Self {
376 self.peak_memory_limit = Some(bytes);
377 self
378 }
379
380 pub fn with_total_megapixels_limit(mut self, megapixels: u32) -> Self {
382 self.total_megapixels_limit = Some(megapixels);
383 self
384 }
385
386 pub fn with_max_animation_frames(mut self, frames: u32) -> Self {
388 self.max_animation_frames = Some(frames);
389 self
390 }
391
392 pub fn with_max_grid_tiles(mut self, tiles: u32) -> Self {
394 self.max_grid_tiles = Some(tiles);
395 self
396 }
397
398 pub fn lenient(mut self, lenient: bool) -> Self {
400 self.lenient = lenient;
401 self
402 }
403}
404
405#[derive(Debug, Clone, PartialEq)]
407pub struct GridConfig {
425 pub rows: u8,
427 pub columns: u8,
429 pub output_width: u32,
431 pub output_height: u32,
433}
434
435#[cfg(feature = "eager")]
437#[deprecated(since = "1.5.0", note = "Use `AvifParser::frame()` which returns `FrameRef` instead")]
438#[derive(Debug)]
439pub struct AnimationFrame {
440 pub data: TryVec<u8>,
442 pub duration_ms: u32,
444}
445
446#[cfg(feature = "eager")]
448#[deprecated(since = "1.5.0", note = "Use `AvifParser::animation_info()` and `AvifParser::frames()` instead")]
449#[derive(Debug)]
450#[allow(deprecated)]
451pub struct AnimationConfig {
452 pub loop_count: u32,
454 pub frames: TryVec<AnimationFrame>,
456}
457
458#[derive(Debug)]
461struct MovieHeader {
462 _timescale: u32,
463 _duration: u64,
464}
465
466#[derive(Debug)]
467struct MediaHeader {
468 timescale: u32,
469 _duration: u64,
470}
471
472#[derive(Debug)]
473struct TimeToSampleEntry {
474 sample_count: u32,
475 sample_delta: u32,
476}
477
478#[derive(Debug)]
479struct SampleToChunkEntry {
480 first_chunk: u32,
481 samples_per_chunk: u32,
482 _sample_description_index: u32,
483}
484
485#[derive(Debug)]
486struct SampleTable {
487 time_to_sample: TryVec<TimeToSampleEntry>,
488 sample_to_chunk: TryVec<SampleToChunkEntry>,
489 sample_sizes: TryVec<u32>,
490 chunk_offsets: TryVec<u64>,
491}
492
493#[cfg(feature = "eager")]
494#[deprecated(since = "1.5.0", note = "Use `AvifParser` for zero-copy parsing instead")]
495#[derive(Debug, Default)]
496#[allow(deprecated)]
497pub struct AvifData {
498 pub primary_item: TryVec<u8>,
502 pub alpha_item: Option<TryVec<u8>>,
506 pub premultiplied_alpha: bool,
510
511 pub grid_config: Option<GridConfig>,
535
536 pub grid_tiles: TryVec<TryVec<u8>>,
544
545 pub animation: Option<AnimationConfig>,
549}
550
551#[cfg(feature = "eager")]
572#[allow(deprecated)]
573impl AvifData {
574 #[deprecated(since = "1.5.0", note = "Use `AvifParser::from_reader()` instead")]
575 pub fn from_reader<R: Read>(reader: &mut R) -> Result<Self> {
576 read_avif(reader)
577 }
578
579 pub fn primary_item_metadata(&self) -> Result<AV1Metadata> {
581 AV1Metadata::parse_av1_bitstream(&self.primary_item)
582 }
583
584 pub fn alpha_item_metadata(&self) -> Result<Option<AV1Metadata>> {
586 self.alpha_item.as_deref().map(AV1Metadata::parse_av1_bitstream).transpose()
587 }
588}
589
590#[non_exhaustive]
594#[derive(Debug, Clone)]
595pub struct AV1Metadata {
596 pub still_picture: bool,
598 pub max_frame_width: NonZeroU32,
599 pub max_frame_height: NonZeroU32,
600 pub bit_depth: u8,
602 pub seq_profile: u8,
604 pub chroma_subsampling: (bool, bool),
606 pub monochrome: bool,
607}
608
609impl AV1Metadata {
610 #[inline(never)]
615 pub fn parse_av1_bitstream(obu_bitstream: &[u8]) -> Result<Self> {
616 let h = obu::parse_obu(obu_bitstream)?;
617 Ok(Self {
618 still_picture: h.still_picture,
619 max_frame_width: h.max_frame_width,
620 max_frame_height: h.max_frame_height,
621 bit_depth: h.color.bit_depth,
622 seq_profile: h.seq_profile,
623 chroma_subsampling: h.color.chroma_subsampling,
624 monochrome: h.color.monochrome,
625 })
626 }
627}
628
629pub struct FrameRef<'a> {
634 pub data: Cow<'a, [u8]>,
635 pub duration_ms: u32,
636}
637
638struct MdatBounds {
640 offset: u64,
641 length: u64,
642}
643
644struct ItemExtents {
646 construction_method: ConstructionMethod,
647 extents: TryVec<ExtentRange>,
648}
649
650pub struct AvifParser<'data> {
676 raw: Cow<'data, [u8]>,
677 mdat_bounds: TryVec<MdatBounds>,
678 idat: Option<TryVec<u8>>,
679 primary: ItemExtents,
680 alpha: Option<ItemExtents>,
681 grid_config: Option<GridConfig>,
682 tiles: TryVec<ItemExtents>,
683 animation_data: Option<AnimationParserData>,
684 premultiplied_alpha: bool,
685}
686
687struct AnimationParserData {
688 media_timescale: u32,
689 sample_table: SampleTable,
690 loop_count: u32,
691}
692
693#[derive(Debug, Clone, Copy)]
695pub struct AnimationInfo {
696 pub frame_count: usize,
697 pub loop_count: u32,
698}
699
700struct ParsedStructure {
702 meta: AvifInternalMeta,
703 mdat_bounds: TryVec<MdatBounds>,
704 animation_data: Option<(u32, SampleTable, u32)>,
705}
706
707impl<'data> AvifParser<'data> {
708 pub fn from_bytes(data: &'data [u8]) -> Result<Self> {
717 Self::from_bytes_with_config(data, &DecodeConfig::unlimited(), &Unstoppable)
718 }
719
720 pub fn from_bytes_with_config(
722 data: &'data [u8],
723 config: &DecodeConfig,
724 stop: &dyn Stop,
725 ) -> Result<Self> {
726 let parsed = Self::parse_raw(data, config, stop)?;
727 Self::build(Cow::Borrowed(data), parsed, config)
728 }
729
730 pub fn from_owned(data: std::vec::Vec<u8>) -> Result<AvifParser<'static>> {
735 AvifParser::from_owned_with_config(data, &DecodeConfig::unlimited(), &Unstoppable)
736 }
737
738 pub fn from_owned_with_config(
740 data: std::vec::Vec<u8>,
741 config: &DecodeConfig,
742 stop: &dyn Stop,
743 ) -> Result<AvifParser<'static>> {
744 let parsed = AvifParser::parse_raw(&data, config, stop)?;
745 AvifParser::build(Cow::Owned(data), parsed, config)
746 }
747
748 pub fn from_reader<R: Read>(reader: &mut R) -> Result<AvifParser<'static>> {
750 AvifParser::from_reader_with_config(reader, &DecodeConfig::unlimited(), &Unstoppable)
751 }
752
753 pub fn from_reader_with_config<R: Read>(
755 reader: &mut R,
756 config: &DecodeConfig,
757 stop: &dyn Stop,
758 ) -> Result<AvifParser<'static>> {
759 let mut buf = std::vec::Vec::new();
760 reader.read_to_end(&mut buf)?;
761 AvifParser::from_owned_with_config(buf, config, stop)
762 }
763
764 fn parse_raw(data: &[u8], config: &DecodeConfig, stop: &dyn Stop) -> Result<ParsedStructure> {
771 let parse_opts = ParseOptions { lenient: config.lenient };
772 let mut cursor = std::io::Cursor::new(data);
773 let mut f = OffsetReader::new(&mut cursor);
774 let mut iter = BoxIter::new(&mut f);
775
776 if let Some(mut b) = iter.next_box()? {
778 if b.head.name == BoxType::FileTypeBox {
779 let ftyp = read_ftyp(&mut b)?;
780 if ftyp.major_brand != b"avif" && ftyp.major_brand != b"avis" {
781 return Err(Error::InvalidData("ftyp must be 'avif' or 'avis'"));
782 }
783 } else {
784 return Err(Error::InvalidData("'ftyp' box must occur first"));
785 }
786 }
787
788 let mut meta = None;
789 let mut mdat_bounds = TryVec::new();
790 let mut animation_data: Option<(u32, SampleTable, u32)> = None;
791
792 while let Some(mut b) = iter.next_box()? {
793 stop.check()?;
794
795 match b.head.name {
796 BoxType::MetadataBox => {
797 if meta.is_some() {
798 return Err(Error::InvalidData(
799 "There should be zero or one meta boxes per ISO 14496-12:2015 § 8.11.1.1",
800 ));
801 }
802 meta = Some(read_avif_meta(&mut b, &parse_opts)?);
803 }
804 BoxType::MovieBox => {
805 if let Some((media_timescale, sample_table)) = read_moov(&mut b)? {
806 animation_data = Some((media_timescale, sample_table, 0));
807 }
808 }
809 BoxType::MediaDataBox => {
810 if b.bytes_left() > 0 {
811 let offset = b.offset();
812 let length = b.bytes_left();
813 mdat_bounds.push(MdatBounds { offset, length })?;
814 }
815 skip_box_content(&mut b)?;
817 }
818 _ => skip_box_content(&mut b)?,
819 }
820
821 check_parser_state(&b.head, &b.content)?;
822 }
823
824 let meta = meta.ok_or(Error::InvalidData("missing meta"))?;
825
826 Ok(ParsedStructure { meta, mdat_bounds, animation_data })
827 }
828
829 fn build(raw: Cow<'data, [u8]>, parsed: ParsedStructure, config: &DecodeConfig) -> Result<Self> {
831 let tracker = ResourceTracker::new(config);
832 let meta = parsed.meta;
833
834 let primary = Self::get_item_extents(&meta, meta.primary_item_id)?;
836
837 let alpha_item_id = meta
839 .item_references
840 .iter()
841 .filter(|iref| {
842 iref.to_item_id == meta.primary_item_id
843 && iref.from_item_id != meta.primary_item_id
844 && iref.item_type == b"auxl"
845 })
846 .map(|iref| iref.from_item_id)
847 .find(|&item_id| {
848 meta.properties.iter().any(|prop| {
849 prop.item_id == item_id
850 && match &prop.property {
851 ItemProperty::AuxiliaryType(urn) => {
852 urn.type_subtype().0 == b"urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"
853 }
854 _ => false,
855 }
856 })
857 });
858
859 let alpha = alpha_item_id
860 .map(|id| Self::get_item_extents(&meta, id))
861 .transpose()?;
862
863 let premultiplied_alpha = alpha_item_id.map_or(false, |alpha_id| {
865 meta.item_references.iter().any(|iref| {
866 iref.from_item_id == meta.primary_item_id
867 && iref.to_item_id == alpha_id
868 && iref.item_type == b"prem"
869 })
870 });
871
872 let is_grid = meta
874 .item_infos
875 .iter()
876 .find(|x| x.item_id == meta.primary_item_id)
877 .map_or(false, |info| info.item_type == b"grid");
878
879 let (grid_config, tiles) = if is_grid {
881 let mut tiles_with_index: TryVec<(u32, u16)> = TryVec::new();
882 for iref in meta.item_references.iter() {
883 if iref.from_item_id == meta.primary_item_id && iref.item_type == b"dimg" {
884 tiles_with_index.push((iref.to_item_id, iref.reference_index))?;
885 }
886 }
887
888 tracker.validate_grid_tiles(tiles_with_index.len() as u32)?;
889 tiles_with_index.sort_by_key(|&(_, idx)| idx);
890
891 let mut tile_extents = TryVec::new();
892 for (tile_id, _) in tiles_with_index.iter() {
893 tile_extents.push(Self::get_item_extents(&meta, *tile_id)?)?;
894 }
895
896 let mut tile_ids = TryVec::new();
897 for (tile_id, _) in tiles_with_index.iter() {
898 tile_ids.push(*tile_id)?;
899 }
900
901 let grid_config = Self::calculate_grid_config(&meta, &tile_ids)?;
902 (Some(grid_config), tile_extents)
903 } else {
904 (None, TryVec::new())
905 };
906
907 let animation_data = if let Some((media_timescale, sample_table, loop_count)) = parsed.animation_data {
909 tracker.validate_animation_frames(sample_table.sample_sizes.len() as u32)?;
910 Some(AnimationParserData { media_timescale, sample_table, loop_count })
911 } else {
912 None
913 };
914
915 let idat = if let Some(ref idat_data) = meta.idat {
917 let mut cloned = TryVec::new();
918 cloned.extend_from_slice(idat_data)?;
919 Some(cloned)
920 } else {
921 None
922 };
923
924 Ok(Self {
925 raw,
926 mdat_bounds: parsed.mdat_bounds,
927 idat,
928 primary,
929 alpha,
930 grid_config,
931 tiles,
932 animation_data,
933 premultiplied_alpha,
934 })
935 }
936
937 fn get_item_extents(meta: &AvifInternalMeta, item_id: u32) -> Result<ItemExtents> {
943 let item = meta
944 .iloc_items
945 .iter()
946 .find(|item| item.item_id == item_id)
947 .ok_or(Error::InvalidData("item not found in iloc"))?;
948
949 let mut extents = TryVec::new();
950 for extent in &item.extents {
951 extents.push(extent.extent_range.clone())?;
952 }
953 Ok(ItemExtents {
954 construction_method: item.construction_method,
955 extents,
956 })
957 }
958
959 fn resolve_item(&self, item: &ItemExtents) -> Result<Cow<'_, [u8]>> {
962 match item.construction_method {
963 ConstructionMethod::Idat => self.resolve_idat_extents(&item.extents),
964 ConstructionMethod::File => self.resolve_file_extents(&item.extents),
965 ConstructionMethod::Item => Err(Error::Unsupported("construction_method 'item' not supported")),
966 }
967 }
968
969 fn resolve_file_extents(&self, extents: &[ExtentRange]) -> Result<Cow<'_, [u8]>> {
971 let raw = self.raw.as_ref();
972
973 if extents.len() == 1 {
975 let extent = &extents[0];
976 let (start, end) = self.extent_byte_range(extent)?;
977 let slice = raw.get(start..end).ok_or(Error::InvalidData("extent out of bounds in raw buffer"))?;
978 return Ok(Cow::Borrowed(slice));
979 }
980
981 let mut data = TryVec::new();
983 for extent in extents {
984 let (start, end) = self.extent_byte_range(extent)?;
985 let slice = raw.get(start..end).ok_or(Error::InvalidData("extent out of bounds in raw buffer"))?;
986 data.extend_from_slice(slice)?;
987 }
988 Ok(Cow::Owned(data.to_vec()))
989 }
990
991 fn extent_byte_range(&self, extent: &ExtentRange) -> Result<(usize, usize)> {
993 let file_offset = extent.start();
994 let start = usize::try_from(file_offset)?;
995
996 match extent {
997 ExtentRange::WithLength(range) => {
998 let len = range.end.checked_sub(range.start)
999 .ok_or(Error::InvalidData("extent range start > end"))?;
1000 let end = start.checked_add(usize::try_from(len)?)
1001 .ok_or(Error::InvalidData("extent end overflow"))?;
1002 Ok((start, end))
1003 }
1004 ExtentRange::ToEnd(_) => {
1005 for mdat in &self.mdat_bounds {
1007 if file_offset >= mdat.offset && file_offset < mdat.offset + mdat.length {
1008 let end = usize::try_from(mdat.offset + mdat.length)?;
1009 return Ok((start, end));
1010 }
1011 }
1012 Ok((start, self.raw.len()))
1014 }
1015 }
1016 }
1017
1018 fn resolve_idat_extents(&self, extents: &[ExtentRange]) -> Result<Cow<'_, [u8]>> {
1020 let idat_data = self.idat.as_ref()
1021 .ok_or(Error::InvalidData("idat box missing but construction_method is Idat"))?;
1022
1023 if extents.len() == 1 {
1024 let extent = &extents[0];
1025 let start = usize::try_from(extent.start())?;
1026 let slice = match extent {
1027 ExtentRange::WithLength(range) => {
1028 let len = usize::try_from(range.end - range.start)?;
1029 idat_data.get(start..start + len)
1030 .ok_or(Error::InvalidData("idat extent out of bounds"))?
1031 }
1032 ExtentRange::ToEnd(_) => {
1033 idat_data.get(start..)
1034 .ok_or(Error::InvalidData("idat extent out of bounds"))?
1035 }
1036 };
1037 return Ok(Cow::Borrowed(slice));
1038 }
1039
1040 let mut data = TryVec::new();
1042 for extent in extents {
1043 let start = usize::try_from(extent.start())?;
1044 let slice = match extent {
1045 ExtentRange::WithLength(range) => {
1046 let len = usize::try_from(range.end - range.start)?;
1047 idat_data.get(start..start + len)
1048 .ok_or(Error::InvalidData("idat extent out of bounds"))?
1049 }
1050 ExtentRange::ToEnd(_) => {
1051 idat_data.get(start..)
1052 .ok_or(Error::InvalidData("idat extent out of bounds"))?
1053 }
1054 };
1055 data.extend_from_slice(slice)?;
1056 }
1057 Ok(Cow::Owned(data.to_vec()))
1058 }
1059
1060 fn resolve_frame(&self, index: usize) -> Result<FrameRef<'_>> {
1062 let anim = self.animation_data.as_ref()
1063 .ok_or(Error::InvalidData("not an animated AVIF"))?;
1064
1065 if index >= anim.sample_table.sample_sizes.len() {
1066 return Err(Error::InvalidData("frame index out of bounds"));
1067 }
1068
1069 let duration_ms = self.calculate_frame_duration(&anim.sample_table, anim.media_timescale, index)?;
1070 let (offset, size) = self.calculate_sample_location(&anim.sample_table, index)?;
1071
1072 let start = usize::try_from(offset)?;
1073 let end = start.checked_add(size as usize)
1074 .ok_or(Error::InvalidData("frame end overflow"))?;
1075
1076 let raw = self.raw.as_ref();
1077 let slice = raw.get(start..end)
1078 .ok_or(Error::InvalidData("frame not found in raw buffer"))?;
1079
1080 Ok(FrameRef {
1081 data: Cow::Borrowed(slice),
1082 duration_ms,
1083 })
1084 }
1085
1086 fn calculate_grid_config(meta: &AvifInternalMeta, tile_ids: &[u32]) -> Result<GridConfig> {
1088 for prop in &meta.properties {
1090 if prop.item_id == meta.primary_item_id {
1091 if let ItemProperty::ImageGrid(grid) = &prop.property {
1092 return Ok(grid.clone());
1093 }
1094 }
1095 }
1096
1097 let grid_dims = meta
1099 .properties
1100 .iter()
1101 .find(|p| p.item_id == meta.primary_item_id)
1102 .and_then(|p| match &p.property {
1103 ItemProperty::ImageSpatialExtents(e) => Some(e),
1104 _ => None,
1105 });
1106
1107 let tile_dims = tile_ids.first().and_then(|&tile_id| {
1108 meta.properties
1109 .iter()
1110 .find(|p| p.item_id == tile_id)
1111 .and_then(|p| match &p.property {
1112 ItemProperty::ImageSpatialExtents(e) => Some(e),
1113 _ => None,
1114 })
1115 });
1116
1117 if let (Some(grid), Some(tile)) = (grid_dims, tile_dims) {
1118 if tile.width != 0
1119 && tile.height != 0
1120 && grid.width % tile.width == 0
1121 && grid.height % tile.height == 0
1122 {
1123 let columns = grid.width / tile.width;
1124 let rows = grid.height / tile.height;
1125
1126 if columns <= 255 && rows <= 255 {
1127 return Ok(GridConfig {
1128 rows: rows as u8,
1129 columns: columns as u8,
1130 output_width: grid.width,
1131 output_height: grid.height,
1132 });
1133 }
1134 }
1135 }
1136
1137 let tile_count = tile_ids.len();
1138 Ok(GridConfig {
1139 rows: tile_count.min(255) as u8,
1140 columns: 1,
1141 output_width: 0,
1142 output_height: 0,
1143 })
1144 }
1145
1146 fn calculate_frame_duration(
1148 &self,
1149 st: &SampleTable,
1150 timescale: u32,
1151 index: usize,
1152 ) -> Result<u32> {
1153 let mut current_sample = 0;
1154 for entry in &st.time_to_sample {
1155 if current_sample + entry.sample_count as usize > index {
1156 let duration_ms = if timescale > 0 {
1157 ((entry.sample_delta as u64) * 1000) / (timescale as u64)
1158 } else {
1159 0
1160 };
1161 return Ok(duration_ms as u32);
1162 }
1163 current_sample += entry.sample_count as usize;
1164 }
1165 Ok(0)
1166 }
1167
1168 fn calculate_sample_location(&self, st: &SampleTable, index: usize) -> Result<(u64, u32)> {
1170 let sample_size = *st
1171 .sample_sizes
1172 .get(index)
1173 .ok_or(Error::InvalidData("sample index out of bounds"))?;
1174
1175 let mut current_sample = 0;
1176 for (chunk_map_idx, entry) in st.sample_to_chunk.iter().enumerate() {
1177 let next_first_chunk = st
1178 .sample_to_chunk
1179 .get(chunk_map_idx + 1)
1180 .map(|e| e.first_chunk)
1181 .unwrap_or(u32::MAX);
1182
1183 for chunk_idx in entry.first_chunk..next_first_chunk {
1184 if chunk_idx == 0 || (chunk_idx as usize) > st.chunk_offsets.len() {
1185 break;
1186 }
1187
1188 let chunk_offset = st.chunk_offsets[(chunk_idx - 1) as usize];
1189
1190 for sample_in_chunk in 0..entry.samples_per_chunk {
1191 if current_sample == index {
1192 let mut offset_in_chunk = 0u64;
1193 for s in 0..sample_in_chunk {
1194 let prev_idx = current_sample.saturating_sub((sample_in_chunk - s) as usize);
1195 if let Some(&prev_size) = st.sample_sizes.get(prev_idx) {
1196 offset_in_chunk += prev_size as u64;
1197 }
1198 }
1199
1200 return Ok((chunk_offset + offset_in_chunk, sample_size));
1201 }
1202 current_sample += 1;
1203 }
1204 }
1205 }
1206
1207 Err(Error::InvalidData("sample not found in chunk table"))
1208 }
1209
1210 pub fn primary_data(&self) -> Result<Cow<'_, [u8]>> {
1218 self.resolve_item(&self.primary)
1219 }
1220
1221 pub fn alpha_data(&self) -> Option<Result<Cow<'_, [u8]>>> {
1223 self.alpha.as_ref().map(|item| self.resolve_item(item))
1224 }
1225
1226 pub fn tile_data(&self, index: usize) -> Result<Cow<'_, [u8]>> {
1228 let item = self.tiles.get(index)
1229 .ok_or(Error::InvalidData("tile index out of bounds"))?;
1230 self.resolve_item(item)
1231 }
1232
1233 pub fn frame(&self, index: usize) -> Result<FrameRef<'_>> {
1235 self.resolve_frame(index)
1236 }
1237
1238 pub fn frames(&self) -> FrameIterator<'_> {
1240 let count = self
1241 .animation_info()
1242 .map(|info| info.frame_count)
1243 .unwrap_or(0);
1244 FrameIterator { parser: self, index: 0, count }
1245 }
1246
1247 pub fn animation_info(&self) -> Option<AnimationInfo> {
1253 self.animation_data.as_ref().map(|data| AnimationInfo {
1254 frame_count: data.sample_table.sample_sizes.len(),
1255 loop_count: data.loop_count,
1256 })
1257 }
1258
1259 pub fn grid_config(&self) -> Option<&GridConfig> {
1261 self.grid_config.as_ref()
1262 }
1263
1264 pub fn grid_tile_count(&self) -> usize {
1266 self.tiles.len()
1267 }
1268
1269 pub fn premultiplied_alpha(&self) -> bool {
1271 self.premultiplied_alpha
1272 }
1273
1274 pub fn primary_metadata(&self) -> Result<AV1Metadata> {
1276 let data = self.primary_data()?;
1277 AV1Metadata::parse_av1_bitstream(&data)
1278 }
1279
1280 pub fn alpha_metadata(&self) -> Option<Result<AV1Metadata>> {
1282 self.alpha.as_ref().map(|item| {
1283 let data = self.resolve_item(item)?;
1284 AV1Metadata::parse_av1_bitstream(&data)
1285 })
1286 }
1287
1288 #[cfg(feature = "eager")]
1297 #[deprecated(since = "1.5.0", note = "Use AvifParser methods directly instead of converting to AvifData")]
1298 #[allow(deprecated)]
1299 pub fn to_avif_data(&self) -> Result<AvifData> {
1300 let primary_data = self.primary_data()?;
1301 let mut primary_item = TryVec::new();
1302 primary_item.extend_from_slice(&primary_data)?;
1303
1304 let alpha_item = match self.alpha_data() {
1305 Some(Ok(data)) => {
1306 let mut v = TryVec::new();
1307 v.extend_from_slice(&data)?;
1308 Some(v)
1309 }
1310 Some(Err(e)) => return Err(e),
1311 None => None,
1312 };
1313
1314 let mut grid_tiles = TryVec::new();
1315 for i in 0..self.grid_tile_count() {
1316 let data = self.tile_data(i)?;
1317 let mut v = TryVec::new();
1318 v.extend_from_slice(&data)?;
1319 grid_tiles.push(v)?;
1320 }
1321
1322 let animation = if let Some(info) = self.animation_info() {
1323 let mut frames = TryVec::new();
1324 for i in 0..info.frame_count {
1325 let frame_ref = self.frame(i)?;
1326 let mut data = TryVec::new();
1327 data.extend_from_slice(&frame_ref.data)?;
1328 frames.push(AnimationFrame { data, duration_ms: frame_ref.duration_ms })?;
1329 }
1330 Some(AnimationConfig {
1331 loop_count: info.loop_count,
1332 frames,
1333 })
1334 } else {
1335 None
1336 };
1337
1338 Ok(AvifData {
1339 primary_item,
1340 alpha_item,
1341 premultiplied_alpha: self.premultiplied_alpha,
1342 grid_config: self.grid_config.clone(),
1343 grid_tiles,
1344 animation,
1345 })
1346 }
1347}
1348
1349pub struct FrameIterator<'a> {
1353 parser: &'a AvifParser<'a>,
1354 index: usize,
1355 count: usize,
1356}
1357
1358impl<'a> Iterator for FrameIterator<'a> {
1359 type Item = Result<FrameRef<'a>>;
1360
1361 fn next(&mut self) -> Option<Self::Item> {
1362 if self.index >= self.count {
1363 return None;
1364 }
1365 let result = self.parser.frame(self.index);
1366 self.index += 1;
1367 Some(result)
1368 }
1369
1370 fn size_hint(&self) -> (usize, Option<usize>) {
1371 let remaining = self.count.saturating_sub(self.index);
1372 (remaining, Some(remaining))
1373 }
1374}
1375
1376impl ExactSizeIterator for FrameIterator<'_> {
1377 fn len(&self) -> usize {
1378 self.count.saturating_sub(self.index)
1379 }
1380}
1381
1382struct AvifInternalMeta {
1383 item_references: TryVec<SingleItemTypeReferenceBox>,
1384 properties: TryVec<AssociatedProperty>,
1385 primary_item_id: u32,
1386 iloc_items: TryVec<ItemLocationBoxItem>,
1387 item_infos: TryVec<ItemInfoEntry>,
1388 idat: Option<TryVec<u8>>,
1389}
1390
1391#[cfg(feature = "eager")]
1394struct MediaDataBox {
1395 offset: u64,
1397 data: TryVec<u8>,
1398}
1399
1400#[cfg(feature = "eager")]
1401impl MediaDataBox {
1402 fn contains_extent(&self, extent: &ExtentRange) -> bool {
1406 if self.offset <= extent.start() {
1407 let start_offset = extent.start() - self.offset;
1408 start_offset < self.data.len().to_u64()
1409 } else {
1410 false
1411 }
1412 }
1413
1414 fn matches_extent(&self, extent: &ExtentRange) -> bool {
1416 if self.offset == extent.start() {
1417 match extent {
1418 ExtentRange::WithLength(range) => {
1419 if let Some(end) = self.offset.checked_add(self.data.len().to_u64()) {
1420 end == range.end
1421 } else {
1422 false
1423 }
1424 },
1425 ExtentRange::ToEnd(_) => true,
1426 }
1427 } else {
1428 false
1429 }
1430 }
1431
1432 fn read_extent(&self, extent: &ExtentRange, buf: &mut TryVec<u8>) -> Result<()> {
1435 let start_offset = extent
1436 .start()
1437 .checked_sub(self.offset)
1438 .ok_or(Error::InvalidData("mdat does not contain extent"))?;
1439 let slice = match extent {
1440 ExtentRange::WithLength(range) => {
1441 let range_len = range
1442 .end
1443 .checked_sub(range.start)
1444 .ok_or(Error::InvalidData("range start > end"))?;
1445 let end = start_offset
1446 .checked_add(range_len)
1447 .ok_or(Error::InvalidData("extent end overflow"))?;
1448 self.data.get(start_offset.try_into()?..end.try_into()?)
1449 },
1450 ExtentRange::ToEnd(_) => self.data.get(start_offset.try_into()?..),
1451 };
1452 let slice = slice.ok_or(Error::InvalidData("extent crosses box boundary"))?;
1453 buf.extend_from_slice(slice)?;
1454 Ok(())
1455 }
1456
1457}
1458
1459#[derive(Debug)]
1463struct ItemInfoEntry {
1464 item_id: u32,
1465 item_type: FourCC,
1466}
1467
1468#[derive(Debug)]
1470struct SingleItemTypeReferenceBox {
1471 item_type: FourCC,
1472 from_item_id: u32,
1473 to_item_id: u32,
1474 reference_index: u16,
1477}
1478
1479#[derive(Debug)]
1482enum IlocFieldSize {
1483 Zero,
1484 Four,
1485 Eight,
1486}
1487
1488impl IlocFieldSize {
1489 const fn to_bits(&self) -> u8 {
1490 match self {
1491 Self::Zero => 0,
1492 Self::Four => 32,
1493 Self::Eight => 64,
1494 }
1495 }
1496}
1497
1498impl TryFrom<u8> for IlocFieldSize {
1499 type Error = Error;
1500
1501 fn try_from(value: u8) -> Result<Self> {
1502 match value {
1503 0 => Ok(Self::Zero),
1504 4 => Ok(Self::Four),
1505 8 => Ok(Self::Eight),
1506 _ => Err(Error::InvalidData("value must be in the set {0, 4, 8}")),
1507 }
1508 }
1509}
1510
1511#[derive(PartialEq)]
1512enum IlocVersion {
1513 Zero,
1514 One,
1515 Two,
1516}
1517
1518impl TryFrom<u8> for IlocVersion {
1519 type Error = Error;
1520
1521 fn try_from(value: u8) -> Result<Self> {
1522 match value {
1523 0 => Ok(Self::Zero),
1524 1 => Ok(Self::One),
1525 2 => Ok(Self::Two),
1526 _ => Err(Error::Unsupported("unsupported version in 'iloc' box")),
1527 }
1528 }
1529}
1530
1531#[derive(Debug)]
1536struct ItemLocationBoxItem {
1537 item_id: u32,
1538 construction_method: ConstructionMethod,
1539 extents: TryVec<ItemLocationBoxExtent>,
1541}
1542
1543#[derive(Clone, Copy, Debug, PartialEq)]
1544enum ConstructionMethod {
1545 File,
1546 Idat,
1547 #[allow(dead_code)] Item,
1549}
1550
1551#[derive(Clone, Debug)]
1554struct ItemLocationBoxExtent {
1555 extent_range: ExtentRange,
1556}
1557
1558#[derive(Clone, Debug)]
1559enum ExtentRange {
1560 WithLength(Range<u64>),
1561 ToEnd(RangeFrom<u64>),
1562}
1563
1564impl ExtentRange {
1565 const fn start(&self) -> u64 {
1566 match self {
1567 Self::WithLength(r) => r.start,
1568 Self::ToEnd(r) => r.start,
1569 }
1570 }
1571}
1572
1573struct BMFFBox<'a, T> {
1575 head: BoxHeader,
1576 content: Take<&'a mut T>,
1577}
1578
1579impl<T: Read> BMFFBox<'_, T> {
1580 fn read_into_try_vec(&mut self) -> std::io::Result<TryVec<u8>> {
1581 let limit = self.content.limit();
1582 let mut vec = if limit >= u64::MAX - BoxHeader::MIN_LARGE_SIZE {
1586 std::vec::Vec::new()
1588 } else {
1589 let mut v = std::vec::Vec::new();
1590 v.try_reserve_exact(limit as usize)
1591 .map_err(|_| std::io::ErrorKind::OutOfMemory)?;
1592 v
1593 };
1594 self.content.read_to_end(&mut vec)?; Ok(vec.into())
1596 }
1597}
1598
1599#[test]
1600fn box_read_to_end() {
1601 let tmp = &mut b"1234567890".as_slice();
1602 let mut src = BMFFBox {
1603 head: BoxHeader { name: BoxType::FileTypeBox, size: 5, offset: 0, uuid: None },
1604 content: <_ as Read>::take(tmp, 5),
1605 };
1606 let buf = src.read_into_try_vec().unwrap();
1607 assert_eq!(buf.len(), 5);
1608 assert_eq!(buf, b"12345".as_ref());
1609}
1610
1611#[test]
1612fn box_read_to_end_oom() {
1613 let tmp = &mut b"1234567890".as_slice();
1614 let mut src = BMFFBox {
1615 head: BoxHeader { name: BoxType::FileTypeBox, size: 5, offset: 0, uuid: None },
1616 content: <_ as Read>::take(tmp, u64::MAX / 2),
1618 };
1619 assert!(src.read_into_try_vec().is_err());
1620}
1621
1622struct BoxIter<'a, T> {
1623 src: &'a mut T,
1624}
1625
1626impl<T: Read> BoxIter<'_, T> {
1627 fn new(src: &mut T) -> BoxIter<'_, T> {
1628 BoxIter { src }
1629 }
1630
1631 fn next_box(&mut self) -> Result<Option<BMFFBox<'_, T>>> {
1632 let r = read_box_header(self.src);
1633 match r {
1634 Ok(h) => Ok(Some(BMFFBox {
1635 head: h,
1636 content: self.src.take(h.size - h.offset),
1637 })),
1638 Err(Error::UnexpectedEOF) => Ok(None),
1639 Err(e) => Err(e),
1640 }
1641 }
1642}
1643
1644impl<T: Read> Read for BMFFBox<'_, T> {
1645 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
1646 self.content.read(buf)
1647 }
1648}
1649
1650impl<T: Offset> Offset for BMFFBox<'_, T> {
1651 fn offset(&self) -> u64 {
1652 self.content.get_ref().offset()
1653 }
1654}
1655
1656impl<T: Read> BMFFBox<'_, T> {
1657 fn bytes_left(&self) -> u64 {
1658 self.content.limit()
1659 }
1660
1661 const fn get_header(&self) -> &BoxHeader {
1662 &self.head
1663 }
1664
1665 fn box_iter(&mut self) -> BoxIter<'_, Self> {
1666 BoxIter::new(self)
1667 }
1668}
1669
1670impl<T> Drop for BMFFBox<'_, T> {
1671 fn drop(&mut self) {
1672 if self.content.limit() > 0 {
1673 let name: FourCC = From::from(self.head.name);
1674 debug!("Dropping {} bytes in '{}'", self.content.limit(), name);
1675 }
1676 }
1677}
1678
1679fn read_box_header<T: ReadBytesExt>(src: &mut T) -> Result<BoxHeader> {
1688 let size32 = be_u32(src)?;
1689 let name = BoxType::from(be_u32(src)?);
1690 let size = match size32 {
1691 0 => {
1693 u64::MAX
1695 },
1696 1 => {
1697 let size64 = be_u64(src)?;
1698 if size64 < BoxHeader::MIN_LARGE_SIZE {
1699 return Err(Error::InvalidData("malformed wide size"));
1700 }
1701 size64
1702 },
1703 _ => {
1704 if u64::from(size32) < BoxHeader::MIN_SIZE {
1705 return Err(Error::InvalidData("malformed size"));
1706 }
1707 u64::from(size32)
1708 },
1709 };
1710 let mut offset = match size32 {
1711 1 => BoxHeader::MIN_LARGE_SIZE,
1712 _ => BoxHeader::MIN_SIZE,
1713 };
1714 let uuid = if name == BoxType::UuidBox {
1715 if size >= offset + 16 {
1716 let mut buffer = [0u8; 16];
1717 let count = src.read(&mut buffer)?;
1718 offset += count.to_u64();
1719 if count == 16 {
1720 Some(buffer)
1721 } else {
1722 debug!("malformed uuid (short read), skipping");
1723 None
1724 }
1725 } else {
1726 debug!("malformed uuid, skipping");
1727 None
1728 }
1729 } else {
1730 None
1731 };
1732 assert!(offset <= size);
1733 Ok(BoxHeader { name, size, offset, uuid })
1734}
1735
1736fn read_fullbox_extra<T: ReadBytesExt>(src: &mut T) -> Result<(u8, u32)> {
1738 let version = src.read_u8()?;
1739 let flags_a = src.read_u8()?;
1740 let flags_b = src.read_u8()?;
1741 let flags_c = src.read_u8()?;
1742 Ok((
1743 version,
1744 u32::from(flags_a) << 16 | u32::from(flags_b) << 8 | u32::from(flags_c),
1745 ))
1746}
1747
1748fn read_fullbox_version_no_flags<T: ReadBytesExt>(src: &mut T, options: &ParseOptions) -> Result<u8> {
1750 let (version, flags) = read_fullbox_extra(src)?;
1751
1752 if flags != 0 && !options.lenient {
1753 return Err(Error::Unsupported("expected flags to be 0"));
1754 }
1755
1756 Ok(version)
1757}
1758
1759fn skip_box_content<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<()> {
1761 let to_skip = {
1763 let header = src.get_header();
1764 debug!("{header:?} (skipped)");
1765 header
1766 .size
1767 .checked_sub(header.offset)
1768 .ok_or(Error::InvalidData("header offset > size"))?
1769 };
1770 assert_eq!(to_skip, src.bytes_left());
1771 skip(src, to_skip)
1772}
1773
1774fn skip_box_remain<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<()> {
1776 let remain = {
1777 let header = src.get_header();
1778 let len = src.bytes_left();
1779 debug!("remain {len} (skipped) in {header:?}");
1780 len
1781 };
1782 skip(src, remain)
1783}
1784
1785struct ResourceTracker<'a> {
1786 config: &'a DecodeConfig,
1787 #[cfg(feature = "eager")]
1788 current_memory: u64,
1789 #[cfg(feature = "eager")]
1790 peak_memory: u64,
1791}
1792
1793impl<'a> ResourceTracker<'a> {
1794 fn new(config: &'a DecodeConfig) -> Self {
1795 Self {
1796 config,
1797 #[cfg(feature = "eager")]
1798 current_memory: 0,
1799 #[cfg(feature = "eager")]
1800 peak_memory: 0,
1801 }
1802 }
1803
1804 #[cfg(feature = "eager")]
1805 fn reserve(&mut self, bytes: u64) -> Result<()> {
1806 self.current_memory = self.current_memory.saturating_add(bytes);
1807 self.peak_memory = self.peak_memory.max(self.current_memory);
1808
1809 if let Some(limit) = self.config.peak_memory_limit {
1810 if self.peak_memory > limit {
1811 return Err(Error::ResourceLimitExceeded("peak memory limit exceeded"));
1812 }
1813 }
1814
1815 Ok(())
1816 }
1817
1818 #[cfg(feature = "eager")]
1819 fn release(&mut self, bytes: u64) {
1820 self.current_memory = self.current_memory.saturating_sub(bytes);
1821 }
1822
1823 #[cfg(feature = "eager")]
1824 fn validate_total_megapixels(&self, width: u32, height: u32) -> Result<()> {
1825 if let Some(limit) = self.config.total_megapixels_limit {
1826 let megapixels = (width as u64)
1827 .checked_mul(height as u64)
1828 .ok_or(Error::InvalidData("dimension overflow"))?
1829 / 1_000_000;
1830
1831 if megapixels > limit as u64 {
1832 return Err(Error::ResourceLimitExceeded("total megapixels limit exceeded"));
1833 }
1834 }
1835
1836 Ok(())
1837 }
1838
1839 fn validate_animation_frames(&self, count: u32) -> Result<()> {
1840 if let Some(limit) = self.config.max_animation_frames {
1841 if count > limit {
1842 return Err(Error::ResourceLimitExceeded("animation frame count limit exceeded"));
1843 }
1844 }
1845
1846 Ok(())
1847 }
1848
1849 fn validate_grid_tiles(&self, count: u32) -> Result<()> {
1850 if let Some(limit) = self.config.max_grid_tiles {
1851 if count > limit {
1852 return Err(Error::ResourceLimitExceeded("grid tile count limit exceeded"));
1853 }
1854 }
1855
1856 Ok(())
1857 }
1858}
1859
1860#[cfg(feature = "eager")]
1871#[deprecated(since = "1.5.0", note = "Use `AvifParser::from_reader_with_config()` instead")]
1872#[allow(deprecated)]
1873pub fn read_avif_with_config<T: Read>(
1874 f: &mut T,
1875 config: &DecodeConfig,
1876 stop: &dyn Stop,
1877) -> Result<AvifData> {
1878 let mut tracker = ResourceTracker::new(config);
1879 let mut f = OffsetReader::new(f);
1880
1881 let mut iter = BoxIter::new(&mut f);
1882
1883 if let Some(mut b) = iter.next_box()? {
1885 if b.head.name == BoxType::FileTypeBox {
1886 let ftyp = read_ftyp(&mut b)?;
1887 if ftyp.major_brand != b"avif" && ftyp.major_brand != b"avis" {
1889 warn!("major_brand: {}", ftyp.major_brand);
1890 return Err(Error::InvalidData("ftyp must be 'avif' or 'avis'"));
1891 }
1892 let _is_animated = ftyp.major_brand == b"avis";
1893 } else {
1894 return Err(Error::InvalidData("'ftyp' box must occur first"));
1895 }
1896 }
1897
1898 let mut meta = None;
1899 let mut mdats = TryVec::new();
1900 let mut animation_data: Option<(u32, SampleTable)> = None;
1901
1902 let parse_opts = ParseOptions { lenient: config.lenient };
1903
1904 while let Some(mut b) = iter.next_box()? {
1905 stop.check()?;
1906
1907 match b.head.name {
1908 BoxType::MetadataBox => {
1909 if meta.is_some() {
1910 return Err(Error::InvalidData("There should be zero or one meta boxes per ISO 14496-12:2015 § 8.11.1.1"));
1911 }
1912 meta = Some(read_avif_meta(&mut b, &parse_opts)?);
1913 },
1914 BoxType::MovieBox => {
1915 animation_data = read_moov(&mut b)?;
1916 },
1917 BoxType::MediaDataBox => {
1918 if b.bytes_left() > 0 {
1919 let offset = b.offset();
1920 let size = b.bytes_left();
1921 tracker.reserve(size)?;
1922 let data = b.read_into_try_vec()?;
1923 tracker.release(size);
1924 mdats.push(MediaDataBox { offset, data })?;
1925 }
1926 },
1927 _ => skip_box_content(&mut b)?,
1928 }
1929
1930 check_parser_state(&b.head, &b.content)?;
1931 }
1932
1933 let meta = meta.ok_or(Error::InvalidData("missing meta"))?;
1934
1935 let is_grid = meta
1937 .item_infos
1938 .iter()
1939 .find(|x| x.item_id == meta.primary_item_id)
1940 .map_or(false, |info| {
1941 let is_g = info.item_type == b"grid";
1942 if is_g {
1943 log::debug!("Grid image detected: primary_item_id={}", meta.primary_item_id);
1944 }
1945 is_g
1946 });
1947
1948 let mut grid_config = if is_grid {
1950 meta.properties
1951 .iter()
1952 .find(|prop| {
1953 prop.item_id == meta.primary_item_id
1954 && matches!(prop.property, ItemProperty::ImageGrid(_))
1955 })
1956 .and_then(|prop| match &prop.property {
1957 ItemProperty::ImageGrid(config) => {
1958 log::debug!("Grid: found explicit ImageGrid property: {:?}", config);
1959 Some(config.clone())
1960 },
1961 _ => None,
1962 })
1963 } else {
1964 None
1965 };
1966
1967 let tile_item_ids: TryVec<u32> = if is_grid {
1969 let mut tiles_with_index: TryVec<(u32, u16)> = TryVec::new();
1971 for iref in meta.item_references.iter() {
1972 if iref.from_item_id == meta.primary_item_id && iref.item_type == b"dimg" {
1974 tiles_with_index.push((iref.to_item_id, iref.reference_index))?;
1975 }
1976 }
1977
1978 tracker.validate_grid_tiles(tiles_with_index.len() as u32)?;
1980
1981 tiles_with_index.sort_by_key(|&(_, idx)| idx);
1983
1984 let mut ids = TryVec::new();
1986 for (tile_id, _) in tiles_with_index.iter() {
1987 ids.push(*tile_id)?;
1988 }
1989
1990 if grid_config.is_none() && !ids.is_empty() {
1994 let grid_dims = meta.properties.iter()
1996 .find(|p| p.item_id == meta.primary_item_id)
1997 .and_then(|p| match &p.property {
1998 ItemProperty::ImageSpatialExtents(e) => Some(e),
1999 _ => None,
2000 });
2001
2002 let tile_dims = ids.first().and_then(|&tile_id| {
2003 meta.properties.iter()
2004 .find(|p| p.item_id == tile_id)
2005 .and_then(|p| match &p.property {
2006 ItemProperty::ImageSpatialExtents(e) => Some(e),
2007 _ => None,
2008 })
2009 });
2010
2011 if let (Some(grid), Some(tile)) = (grid_dims, tile_dims) {
2012 tracker.validate_total_megapixels(grid.width, grid.height)?;
2014
2015 if tile.width == 0 || tile.height == 0 {
2017 log::warn!("Grid: tile has zero dimensions, using fallback");
2018 } else if grid.width % tile.width == 0 && grid.height % tile.height == 0 {
2019 let columns = grid.width / tile.width;
2021 let rows = grid.height / tile.height;
2022
2023 if columns > 255 || rows > 255 {
2025 log::warn!("Grid: calculated dimensions {}×{} exceed 255, using fallback", rows, columns);
2026 } else {
2027 log::debug!("Grid: calculated {}×{} layout from ispe dimensions", rows, columns);
2028 grid_config = Some(GridConfig {
2029 rows: rows as u8,
2030 columns: columns as u8,
2031 output_width: grid.width,
2032 output_height: grid.height,
2033 });
2034 }
2035 } else {
2036 log::warn!("Grid: dimension mismatch - grid {}×{} not evenly divisible by tile {}×{}, using fallback",
2037 grid.width, grid.height, tile.width, tile.height);
2038 }
2039 }
2040
2041 if grid_config.is_none() {
2043 log::debug!("Grid: using fallback {}×1 layout inference", ids.len());
2044 grid_config = Some(GridConfig {
2045 rows: ids.len() as u8, columns: 1, output_width: 0, output_height: 0, });
2050 }
2051 }
2052
2053 ids
2054 } else {
2055 TryVec::new()
2056 };
2057
2058 let alpha_item_id = meta
2059 .item_references
2060 .iter()
2061 .filter(|iref| {
2063 iref.to_item_id == meta.primary_item_id
2064 && iref.from_item_id != meta.primary_item_id
2065 && iref.item_type == b"auxl"
2066 })
2067 .map(|iref| iref.from_item_id)
2068 .find(|&item_id| {
2070 meta.properties.iter().any(|prop| {
2071 prop.item_id == item_id
2072 && match &prop.property {
2073 ItemProperty::AuxiliaryType(urn) => {
2074 urn.type_subtype().0 == b"urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"
2075 }
2076 _ => false,
2077 }
2078 })
2079 });
2080
2081 let mut context = AvifData {
2082 premultiplied_alpha: alpha_item_id.map_or(false, |alpha_item_id| {
2083 meta.item_references.iter().any(|iref| {
2084 iref.from_item_id == meta.primary_item_id
2085 && iref.to_item_id == alpha_item_id
2086 && iref.item_type == b"prem"
2087 })
2088 }),
2089 ..Default::default()
2090 };
2091
2092 let mut extract_item_data = |loc: &ItemLocationBoxItem, buf: &mut TryVec<u8>| -> Result<()> {
2094 match loc.construction_method {
2095 ConstructionMethod::File => {
2096 for extent in loc.extents.iter() {
2097 let mut found = false;
2098 for mdat in mdats.iter_mut() {
2099 if mdat.matches_extent(&extent.extent_range) {
2100 buf.append(&mut mdat.data)?;
2101 found = true;
2102 break;
2103 } else if mdat.contains_extent(&extent.extent_range) {
2104 mdat.read_extent(&extent.extent_range, buf)?;
2105 found = true;
2106 break;
2107 }
2108 }
2109 if !found {
2110 return Err(Error::InvalidData("iloc contains an extent that is not in mdat"));
2111 }
2112 }
2113 Ok(())
2114 },
2115 ConstructionMethod::Idat => {
2116 let idat_data = meta.idat.as_ref().ok_or(Error::InvalidData("idat box missing but construction_method is Idat"))?;
2117 for extent in loc.extents.iter() {
2118 match &extent.extent_range {
2119 ExtentRange::WithLength(range) => {
2120 let start = usize::try_from(range.start).map_err(|_| Error::InvalidData("extent start too large"))?;
2121 let end = usize::try_from(range.end).map_err(|_| Error::InvalidData("extent end too large"))?;
2122 if end > idat_data.len() {
2123 return Err(Error::InvalidData("extent exceeds idat size"));
2124 }
2125 buf.extend_from_slice(&idat_data[start..end]).map_err(|_| Error::OutOfMemory)?;
2126 },
2127 ExtentRange::ToEnd(range) => {
2128 let start = usize::try_from(range.start).map_err(|_| Error::InvalidData("extent start too large"))?;
2129 if start >= idat_data.len() {
2130 return Err(Error::InvalidData("extent start exceeds idat size"));
2131 }
2132 buf.extend_from_slice(&idat_data[start..]).map_err(|_| Error::OutOfMemory)?;
2133 },
2134 }
2135 }
2136 Ok(())
2137 },
2138 ConstructionMethod::Item => {
2139 Err(Error::Unsupported("construction_method 'item' not supported"))
2140 },
2141 }
2142 };
2143
2144 if is_grid {
2147 for (idx, &tile_id) in tile_item_ids.iter().enumerate() {
2149 if idx % 16 == 0 {
2150 stop.check()?;
2151 }
2152
2153 let mut tile_data = TryVec::new();
2154
2155 if let Some(loc) = meta.iloc_items.iter().find(|loc| loc.item_id == tile_id) {
2156 extract_item_data(loc, &mut tile_data)?;
2157 } else {
2158 return Err(Error::InvalidData("grid tile not found in iloc"));
2159 }
2160
2161 context.grid_tiles.push(tile_data)?;
2162 }
2163
2164 context.grid_config = grid_config;
2166 } else {
2167 for loc in meta.iloc_items.iter() {
2169 let item_data = if loc.item_id == meta.primary_item_id {
2170 &mut context.primary_item
2171 } else if Some(loc.item_id) == alpha_item_id {
2172 context.alpha_item.get_or_insert_with(TryVec::new)
2173 } else {
2174 continue;
2175 };
2176
2177 extract_item_data(loc, item_data)?;
2178 }
2179 }
2180
2181 if let Some((media_timescale, sample_table)) = animation_data {
2183 let frame_count = sample_table.sample_sizes.len() as u32;
2184 tracker.validate_animation_frames(frame_count)?;
2185
2186 log::debug!("Animation: extracting frames (media_timescale={})", media_timescale);
2187 match extract_animation_frames(&sample_table, media_timescale, &mut mdats) {
2188 Ok(frames) => {
2189 if !frames.is_empty() {
2190 log::debug!("Animation: extracted {} frames", frames.len());
2191 context.animation = Some(AnimationConfig {
2192 loop_count: 0, frames,
2194 });
2195 }
2196 }
2197 Err(e) => {
2198 log::warn!("Animation: failed to extract frames: {}", e);
2199 }
2200 }
2201 }
2202
2203 Ok(context)
2204}
2205
2206#[cfg(feature = "eager")]
2215#[deprecated(since = "1.5.0", note = "Use `AvifParser::from_reader_with_config()` with `DecodeConfig::lenient()` instead")]
2216#[allow(deprecated)]
2217pub fn read_avif_with_options<T: Read>(f: &mut T, options: &ParseOptions) -> Result<AvifData> {
2218 let config = DecodeConfig::unlimited().lenient(options.lenient);
2219 read_avif_with_config(f, &config, &Unstoppable)
2220}
2221
2222#[cfg(feature = "eager")]
2230#[deprecated(since = "1.5.0", note = "Use `AvifParser::from_reader()` instead")]
2231#[allow(deprecated)]
2232pub fn read_avif<T: Read>(f: &mut T) -> Result<AvifData> {
2233 read_avif_with_options(f, &ParseOptions::default())
2234}
2235
2236fn read_avif_meta<T: Read + Offset>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<AvifInternalMeta> {
2241 let version = read_fullbox_version_no_flags(src, options)?;
2242
2243 if version != 0 {
2244 return Err(Error::Unsupported("unsupported meta version"));
2245 }
2246
2247 let mut primary_item_id = None;
2248 let mut item_infos = None;
2249 let mut iloc_items = None;
2250 let mut item_references = TryVec::new();
2251 let mut properties = TryVec::new();
2252 let mut idat = None;
2253
2254 let mut iter = src.box_iter();
2255 while let Some(mut b) = iter.next_box()? {
2256 match b.head.name {
2257 BoxType::ItemInfoBox => {
2258 if item_infos.is_some() {
2259 return Err(Error::InvalidData("There should be zero or one iinf boxes per ISO 14496-12:2015 § 8.11.6.1"));
2260 }
2261 item_infos = Some(read_iinf(&mut b, options)?);
2262 },
2263 BoxType::ItemLocationBox => {
2264 if iloc_items.is_some() {
2265 return Err(Error::InvalidData("There should be zero or one iloc boxes per ISO 14496-12:2015 § 8.11.3.1"));
2266 }
2267 iloc_items = Some(read_iloc(&mut b, options)?);
2268 },
2269 BoxType::PrimaryItemBox => {
2270 if primary_item_id.is_some() {
2271 return Err(Error::InvalidData("There should be zero or one iloc boxes per ISO 14496-12:2015 § 8.11.4.1"));
2272 }
2273 primary_item_id = Some(read_pitm(&mut b, options)?);
2274 },
2275 BoxType::ImageReferenceBox => {
2276 item_references.append(&mut read_iref(&mut b, options)?)?;
2277 },
2278 BoxType::ImagePropertiesBox => {
2279 properties = read_iprp(&mut b, options)?;
2280 },
2281 BoxType::ItemDataBox => {
2282 if idat.is_some() {
2283 return Err(Error::InvalidData("There should be zero or one idat boxes"));
2284 }
2285 idat = Some(b.read_into_try_vec()?);
2286 },
2287 _ => skip_box_content(&mut b)?,
2288 }
2289
2290 check_parser_state(&b.head, &b.content)?;
2291 }
2292
2293 let primary_item_id = primary_item_id.ok_or(Error::InvalidData("Required pitm box not present in meta box"))?;
2294
2295 let item_infos = item_infos.ok_or(Error::InvalidData("iinf missing"))?;
2296
2297 if let Some(item_info) = item_infos.iter().find(|x| x.item_id == primary_item_id) {
2298 if item_info.item_type != b"av01" && item_info.item_type != b"grid" {
2300 warn!("primary_item_id type: {}", item_info.item_type);
2301 return Err(Error::InvalidData("primary_item_id type is not av01 or grid"));
2302 }
2303 } else {
2304 return Err(Error::InvalidData("primary_item_id not present in iinf box"));
2305 }
2306
2307 Ok(AvifInternalMeta {
2308 properties,
2309 item_references,
2310 primary_item_id,
2311 iloc_items: iloc_items.ok_or(Error::InvalidData("iloc missing"))?,
2312 item_infos,
2313 idat,
2314 })
2315}
2316
2317fn read_pitm<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<u32> {
2320 let version = read_fullbox_version_no_flags(src, options)?;
2321
2322 let item_id = match version {
2323 0 => be_u16(src)?.into(),
2324 1 => be_u32(src)?,
2325 _ => return Err(Error::Unsupported("unsupported pitm version")),
2326 };
2327
2328 Ok(item_id)
2329}
2330
2331fn read_iinf<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<ItemInfoEntry>> {
2334 let version = read_fullbox_version_no_flags(src, options)?;
2335
2336 match version {
2337 0 | 1 => (),
2338 _ => return Err(Error::Unsupported("unsupported iinf version")),
2339 }
2340
2341 let entry_count = if version == 0 {
2342 be_u16(src)?.to_usize()
2343 } else {
2344 be_u32(src)?.to_usize()
2345 };
2346 let mut item_infos = TryVec::with_capacity(entry_count)?;
2347
2348 let mut iter = src.box_iter();
2349 while let Some(mut b) = iter.next_box()? {
2350 if b.head.name != BoxType::ItemInfoEntry {
2351 return Err(Error::InvalidData("iinf box should contain only infe boxes"));
2352 }
2353
2354 item_infos.push(read_infe(&mut b)?)?;
2355
2356 check_parser_state(&b.head, &b.content)?;
2357 }
2358
2359 Ok(item_infos)
2360}
2361
2362fn read_infe<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ItemInfoEntry> {
2365 let (version, _) = read_fullbox_extra(src)?;
2368
2369 let item_id = match version {
2371 2 => be_u16(src)?.into(),
2372 3 => be_u32(src)?,
2373 _ => return Err(Error::Unsupported("unsupported version in 'infe' box")),
2374 };
2375
2376 let item_protection_index = be_u16(src)?;
2377
2378 if item_protection_index != 0 {
2379 return Err(Error::Unsupported("protected items (infe.item_protection_index != 0) are not supported"));
2380 }
2381
2382 let item_type = FourCC::from(be_u32(src)?);
2383 debug!("infe item_id {item_id} item_type: {item_type}");
2384
2385 skip_box_remain(src)?;
2387
2388 Ok(ItemInfoEntry { item_id, item_type })
2389}
2390
2391fn read_iref<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<SingleItemTypeReferenceBox>> {
2392 let mut item_references = TryVec::new();
2393 let version = read_fullbox_version_no_flags(src, options)?;
2394 if version > 1 {
2395 return Err(Error::Unsupported("iref version"));
2396 }
2397
2398 let mut iter = src.box_iter();
2399 while let Some(mut b) = iter.next_box()? {
2400 let from_item_id = if version == 0 {
2401 be_u16(&mut b)?.into()
2402 } else {
2403 be_u32(&mut b)?
2404 };
2405 let reference_count = be_u16(&mut b)?;
2406 for reference_index in 0..reference_count {
2407 let to_item_id = if version == 0 {
2408 be_u16(&mut b)?.into()
2409 } else {
2410 be_u32(&mut b)?
2411 };
2412 if from_item_id == to_item_id {
2413 return Err(Error::InvalidData("from_item_id and to_item_id must be different"));
2414 }
2415 item_references.push(SingleItemTypeReferenceBox {
2416 item_type: b.head.name.into(),
2417 from_item_id,
2418 to_item_id,
2419 reference_index,
2420 })?;
2421 }
2422 check_parser_state(&b.head, &b.content)?;
2423 }
2424 Ok(item_references)
2425}
2426
2427fn read_iprp<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<AssociatedProperty>> {
2428 let mut iter = src.box_iter();
2429 let mut properties = TryVec::new();
2430 let mut associations = TryVec::new();
2431
2432 while let Some(mut b) = iter.next_box()? {
2433 match b.head.name {
2434 BoxType::ItemPropertyContainerBox => {
2435 properties = read_ipco(&mut b, options)?;
2436 },
2437 BoxType::ItemPropertyAssociationBox => {
2438 associations = read_ipma(&mut b)?;
2439 },
2440 _ => return Err(Error::InvalidData("unexpected ipco child")),
2441 }
2442 }
2443
2444 let mut associated = TryVec::new();
2445 for a in associations {
2446 let index = match a.property_index {
2447 0 => continue,
2448 x => x as usize - 1,
2449 };
2450 if let Some(prop) = properties.get(index) {
2451 if *prop != ItemProperty::Unsupported {
2452 associated.push(AssociatedProperty {
2453 item_id: a.item_id,
2454 property: prop.try_clone()?,
2455 })?;
2456 }
2457 }
2458 }
2459 Ok(associated)
2460}
2461
2462#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2464pub(crate) struct ImageSpatialExtents {
2465 pub(crate) width: u32,
2466 pub(crate) height: u32,
2467}
2468
2469#[derive(Debug, PartialEq)]
2470pub(crate) enum ItemProperty {
2471 Channels(ArrayVec<u8, 16>),
2472 AuxiliaryType(AuxiliaryTypeProperty),
2473 ImageSpatialExtents(ImageSpatialExtents),
2474 ImageGrid(GridConfig),
2475 Unsupported,
2476}
2477
2478impl TryClone for ItemProperty {
2479 fn try_clone(&self) -> Result<Self, TryReserveError> {
2480 Ok(match self {
2481 Self::Channels(val) => Self::Channels(val.clone()),
2482 Self::AuxiliaryType(val) => Self::AuxiliaryType(val.try_clone()?),
2483 Self::ImageSpatialExtents(val) => Self::ImageSpatialExtents(*val),
2484 Self::ImageGrid(val) => Self::ImageGrid(val.clone()),
2485 Self::Unsupported => Self::Unsupported,
2486 })
2487 }
2488}
2489
2490struct Association {
2491 item_id: u32,
2492 #[allow(unused)]
2493 essential: bool,
2494 property_index: u16,
2495}
2496
2497pub(crate) struct AssociatedProperty {
2498 pub item_id: u32,
2499 pub property: ItemProperty,
2500}
2501
2502fn read_ipma<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<Association>> {
2503 let (version, flags) = read_fullbox_extra(src)?;
2504
2505 let mut associations = TryVec::new();
2506
2507 let entry_count = be_u32(src)?;
2508 for _ in 0..entry_count {
2509 let item_id = if version == 0 {
2510 be_u16(src)?.into()
2511 } else {
2512 be_u32(src)?
2513 };
2514 let association_count = src.read_u8()?;
2515 for _ in 0..association_count {
2516 let num_association_bytes = if flags & 1 == 1 { 2 } else { 1 };
2517 let association = &mut [0; 2][..num_association_bytes];
2518 src.read_exact(association)?;
2519 let mut association = BitReader::new(association);
2520 let essential = association.read_bool()?;
2521 let property_index = association.read_u16(association.remaining().try_into()?)?;
2522 associations.push(Association {
2523 item_id,
2524 essential,
2525 property_index,
2526 })?;
2527 }
2528 }
2529 Ok(associations)
2530}
2531
2532fn read_ipco<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<ItemProperty>> {
2533 let mut properties = TryVec::new();
2534
2535 let mut iter = src.box_iter();
2536 while let Some(mut b) = iter.next_box()? {
2537 let prop = match b.head.name {
2539 BoxType::PixelInformationBox => ItemProperty::Channels(read_pixi(&mut b, options)?),
2540 BoxType::AuxiliaryTypeProperty => ItemProperty::AuxiliaryType(read_auxc(&mut b, options)?),
2541 BoxType::ImageSpatialExtentsBox => ItemProperty::ImageSpatialExtents(read_ispe(&mut b, options)?),
2542 BoxType::ImageGridBox => ItemProperty::ImageGrid(read_grid(&mut b, options)?),
2543 _ => {
2544 skip_box_remain(&mut b)?;
2545 ItemProperty::Unsupported
2546 },
2547 };
2548 properties.push(prop)?;
2549 }
2550 Ok(properties)
2551}
2552
2553fn read_pixi<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<ArrayVec<u8, 16>> {
2554 let version = read_fullbox_version_no_flags(src, options)?;
2555 if version != 0 {
2556 return Err(Error::Unsupported("pixi version"));
2557 }
2558
2559 let num_channels = usize::from(src.read_u8()?);
2560 let mut channels = ArrayVec::new();
2561 channels.extend((0..num_channels.min(channels.capacity())).map(|_| 0));
2562 debug_assert_eq!(num_channels, channels.len());
2563 src.read_exact(&mut channels).map_err(|_| Error::InvalidData("invalid num_channels"))?;
2564
2565 if options.lenient && src.bytes_left() > 0 {
2567 skip(src, src.bytes_left())?;
2568 }
2569
2570 check_parser_state(&src.head, &src.content)?;
2571 Ok(channels)
2572}
2573
2574#[derive(Debug, PartialEq)]
2575struct AuxiliaryTypeProperty {
2576 aux_data: TryString,
2577}
2578
2579impl AuxiliaryTypeProperty {
2580 #[must_use]
2581 fn type_subtype(&self) -> (&[u8], &[u8]) {
2582 let split = self.aux_data.iter().position(|&b| b == b'\0')
2583 .map(|pos| self.aux_data.split_at(pos));
2584 if let Some((aux_type, rest)) = split {
2585 (aux_type, &rest[1..])
2586 } else {
2587 (&self.aux_data, &[])
2588 }
2589 }
2590}
2591
2592impl TryClone for AuxiliaryTypeProperty {
2593 fn try_clone(&self) -> Result<Self, TryReserveError> {
2594 Ok(Self {
2595 aux_data: self.aux_data.try_clone()?,
2596 })
2597 }
2598}
2599
2600fn read_auxc<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<AuxiliaryTypeProperty> {
2601 let version = read_fullbox_version_no_flags(src, options)?;
2602 if version != 0 {
2603 return Err(Error::Unsupported("auxC version"));
2604 }
2605
2606 let aux_data = src.read_into_try_vec()?;
2607
2608 Ok(AuxiliaryTypeProperty { aux_data })
2609}
2610
2611fn read_ispe<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<ImageSpatialExtents> {
2614 let _version = read_fullbox_version_no_flags(src, options)?;
2615 let width = be_u32(src)?;
2618 let height = be_u32(src)?;
2619
2620 if width == 0 || height == 0 {
2622 return Err(Error::InvalidData("ispe dimensions cannot be zero"));
2623 }
2624
2625 Ok(ImageSpatialExtents { width, height })
2626}
2627
2628fn read_mvhd<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<MovieHeader> {
2631 let version = src.read_u8()?;
2632 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
2633
2634 let (timescale, duration) = if version == 1 {
2635 let _creation_time = be_u64(src)?;
2636 let _modification_time = be_u64(src)?;
2637 let timescale = be_u32(src)?;
2638 let duration = be_u64(src)?;
2639 (timescale, duration)
2640 } else {
2641 let _creation_time = be_u32(src)?;
2642 let _modification_time = be_u32(src)?;
2643 let timescale = be_u32(src)?;
2644 let duration = be_u32(src)?;
2645 (timescale, duration as u64)
2646 };
2647
2648 skip_box_remain(src)?;
2650
2651 Ok(MovieHeader { _timescale: timescale, _duration: duration })
2652}
2653
2654fn read_mdhd<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<MediaHeader> {
2657 let version = src.read_u8()?;
2658 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
2659
2660 let (timescale, duration) = if version == 1 {
2661 let _creation_time = be_u64(src)?;
2662 let _modification_time = be_u64(src)?;
2663 let timescale = be_u32(src)?;
2664 let duration = be_u64(src)?;
2665 (timescale, duration)
2666 } else {
2667 let _creation_time = be_u32(src)?;
2668 let _modification_time = be_u32(src)?;
2669 let timescale = be_u32(src)?;
2670 let duration = be_u32(src)?;
2671 (timescale, duration as u64)
2672 };
2673
2674 skip_box_remain(src)?;
2676
2677 Ok(MediaHeader { timescale, _duration: duration })
2678}
2679
2680fn read_stts<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<TimeToSampleEntry>> {
2683 let _version = src.read_u8()?;
2684 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
2685 let entry_count = be_u32(src)?;
2686
2687 let mut entries = TryVec::new();
2688 for _ in 0..entry_count {
2689 entries.push(TimeToSampleEntry {
2690 sample_count: be_u32(src)?,
2691 sample_delta: be_u32(src)?,
2692 })?;
2693 }
2694
2695 Ok(entries)
2696}
2697
2698fn read_stsc<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<SampleToChunkEntry>> {
2701 let _version = src.read_u8()?;
2702 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
2703 let entry_count = be_u32(src)?;
2704
2705 let mut entries = TryVec::new();
2706 for _ in 0..entry_count {
2707 entries.push(SampleToChunkEntry {
2708 first_chunk: be_u32(src)?,
2709 samples_per_chunk: be_u32(src)?,
2710 _sample_description_index: be_u32(src)?,
2711 })?;
2712 }
2713
2714 Ok(entries)
2715}
2716
2717fn read_stsz<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<u32>> {
2720 let _version = src.read_u8()?;
2721 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
2722 let sample_size = be_u32(src)?;
2723 let sample_count = be_u32(src)?;
2724
2725 let mut sizes = TryVec::new();
2726 if sample_size == 0 {
2727 for _ in 0..sample_count {
2729 sizes.push(be_u32(src)?)?;
2730 }
2731 } else {
2732 for _ in 0..sample_count {
2734 sizes.push(sample_size)?;
2735 }
2736 }
2737
2738 Ok(sizes)
2739}
2740
2741fn read_chunk_offsets<T: Read>(src: &mut BMFFBox<'_, T>, is_64bit: bool) -> Result<TryVec<u64>> {
2744 let _version = src.read_u8()?;
2745 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
2746 let entry_count = be_u32(src)?;
2747
2748 let mut offsets = TryVec::new();
2749 for _ in 0..entry_count {
2750 let offset = if is_64bit {
2751 be_u64(src)?
2752 } else {
2753 be_u32(src)? as u64
2754 };
2755 offsets.push(offset)?;
2756 }
2757
2758 Ok(offsets)
2759}
2760
2761fn read_stbl<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<SampleTable> {
2764 let mut time_to_sample = TryVec::new();
2765 let mut sample_to_chunk = TryVec::new();
2766 let mut sample_sizes = TryVec::new();
2767 let mut chunk_offsets = TryVec::new();
2768
2769 let mut iter = src.box_iter();
2770 while let Some(mut b) = iter.next_box()? {
2771 match b.head.name {
2772 BoxType::TimeToSampleBox => {
2773 time_to_sample = read_stts(&mut b)?;
2774 }
2775 BoxType::SampleToChunkBox => {
2776 sample_to_chunk = read_stsc(&mut b)?;
2777 }
2778 BoxType::SampleSizeBox => {
2779 sample_sizes = read_stsz(&mut b)?;
2780 }
2781 BoxType::ChunkOffsetBox => {
2782 chunk_offsets = read_chunk_offsets(&mut b, false)?;
2783 }
2784 BoxType::ChunkLargeOffsetBox => {
2785 chunk_offsets = read_chunk_offsets(&mut b, true)?;
2786 }
2787 _ => {
2788 skip_box_remain(&mut b)?;
2789 }
2790 }
2791 }
2792
2793 Ok(SampleTable {
2794 time_to_sample,
2795 sample_to_chunk,
2796 sample_sizes,
2797 chunk_offsets,
2798 })
2799}
2800
2801fn read_moov<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<Option<(u32, SampleTable)>> {
2804 let mut media_timescale: Option<u32> = None;
2805 let mut sample_table: Option<SampleTable> = None;
2806
2807 let mut iter = src.box_iter();
2808 while let Some(mut b) = iter.next_box()? {
2809 match b.head.name {
2810 BoxType::MovieHeaderBox => {
2811 let _mvhd = read_mvhd(&mut b)?;
2812 }
2813 BoxType::TrackBox => {
2814 if media_timescale.is_none() {
2817 if let Some((timescale, stbl)) = read_trak(&mut b)? {
2818 media_timescale = Some(timescale);
2819 sample_table = Some(stbl);
2820 }
2821 } else {
2822 skip_box_remain(&mut b)?;
2823 }
2824 }
2825 _ => {
2826 skip_box_remain(&mut b)?;
2827 }
2828 }
2829 }
2830
2831 if let (Some(timescale), Some(stbl)) = (media_timescale, sample_table) {
2832 Ok(Some((timescale, stbl)))
2833 } else {
2834 Ok(None)
2835 }
2836}
2837
2838fn read_trak<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<Option<(u32, SampleTable)>> {
2841 let mut iter = src.box_iter();
2842 while let Some(mut b) = iter.next_box()? {
2843 if b.head.name == BoxType::MediaBox {
2844 return read_mdia(&mut b);
2845 } else {
2846 skip_box_remain(&mut b)?;
2847 }
2848 }
2849 Ok(None)
2850}
2851
2852fn read_mdia<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<Option<(u32, SampleTable)>> {
2854 let mut media_timescale = 1000; let mut sample_table: Option<SampleTable> = None;
2856
2857 let mut iter = src.box_iter();
2858 while let Some(mut b) = iter.next_box()? {
2859 match b.head.name {
2860 BoxType::MediaHeaderBox => {
2861 let mdhd = read_mdhd(&mut b)?;
2862 media_timescale = mdhd.timescale;
2863 }
2864 BoxType::MediaInformationBox => {
2865 sample_table = read_minf(&mut b)?;
2866 }
2867 _ => {
2868 skip_box_remain(&mut b)?;
2869 }
2870 }
2871 }
2872
2873 if let Some(stbl) = sample_table {
2874 Ok(Some((media_timescale, stbl)))
2875 } else {
2876 Ok(None)
2877 }
2878}
2879
2880fn read_minf<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<Option<SampleTable>> {
2882 let mut iter = src.box_iter();
2883 while let Some(mut b) = iter.next_box()? {
2884 if b.head.name == BoxType::SampleTableBox {
2885 return Ok(Some(read_stbl(&mut b)?));
2886 } else {
2887 skip_box_remain(&mut b)?;
2888 }
2889 }
2890 Ok(None)
2891}
2892
2893#[cfg(feature = "eager")]
2895#[allow(deprecated)]
2896fn extract_animation_frames(
2897 sample_table: &SampleTable,
2898 media_timescale: u32,
2899 mdats: &mut [MediaDataBox],
2900) -> Result<TryVec<AnimationFrame>> {
2901 let mut frames = TryVec::new();
2902
2903 let mut sample_to_chunk_map = TryVec::new();
2905 for (i, entry) in sample_table.sample_to_chunk.iter().enumerate() {
2906 let next_first_chunk = sample_table
2907 .sample_to_chunk
2908 .get(i + 1)
2909 .map(|e| e.first_chunk)
2910 .unwrap_or(u32::MAX);
2911
2912 for chunk_idx in entry.first_chunk..next_first_chunk {
2913 if chunk_idx > sample_table.chunk_offsets.len() as u32 {
2914 break;
2915 }
2916 sample_to_chunk_map.push((chunk_idx, entry.samples_per_chunk))?;
2917 }
2918 }
2919
2920 let mut frame_durations = TryVec::new();
2922 for entry in &sample_table.time_to_sample {
2923 for _ in 0..entry.sample_count {
2924 let duration_ms = if media_timescale > 0 {
2926 ((entry.sample_delta as u64) * 1000) / (media_timescale as u64)
2927 } else {
2928 0
2929 };
2930 frame_durations.push(duration_ms as u32)?;
2931 }
2932 }
2933
2934 let sample_count = sample_table.sample_sizes.len();
2936 let mut current_sample = 0;
2937
2938 for (chunk_idx_1based, samples_in_chunk) in &sample_to_chunk_map {
2939 let chunk_idx = (*chunk_idx_1based as usize).saturating_sub(1);
2940 if chunk_idx >= sample_table.chunk_offsets.len() {
2941 continue;
2942 }
2943
2944 let chunk_offset = sample_table.chunk_offsets[chunk_idx];
2945
2946 for sample_in_chunk in 0..*samples_in_chunk {
2947 if current_sample >= sample_count {
2948 break;
2949 }
2950
2951 let sample_size = sample_table.sample_sizes[current_sample];
2952 let duration_ms = frame_durations.get(current_sample).copied().unwrap_or(0);
2953
2954 let mut offset_in_chunk = 0u64;
2956 for s in 0..sample_in_chunk {
2957 let prev_sample = current_sample.saturating_sub((sample_in_chunk - s) as usize);
2958 if prev_sample < sample_count {
2959 offset_in_chunk += sample_table.sample_sizes[prev_sample] as u64;
2960 }
2961 }
2962
2963 let sample_offset = chunk_offset + offset_in_chunk;
2964
2965 let mut frame_data = TryVec::new();
2967 let mut found = false;
2968
2969 for mdat in mdats.iter_mut() {
2970 let range = ExtentRange::WithLength(Range {
2971 start: sample_offset,
2972 end: sample_offset + sample_size as u64,
2973 });
2974
2975 if mdat.contains_extent(&range) {
2976 mdat.read_extent(&range, &mut frame_data)?;
2977 found = true;
2978 break;
2979 }
2980 }
2981
2982 if !found {
2983 log::warn!("Animation frame {} not found in mdat", current_sample);
2984 }
2985
2986 frames.push(AnimationFrame {
2987 data: frame_data,
2988 duration_ms,
2989 })?;
2990
2991 current_sample += 1;
2992 }
2993 }
2994
2995 Ok(frames)
2996}
2997
2998fn read_grid<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<GridConfig> {
3001 let version = read_fullbox_version_no_flags(src, options)?;
3002 if version > 0 {
3003 return Err(Error::Unsupported("grid version > 0"));
3004 }
3005
3006 let flags_byte = src.read_u8()?;
3007 let rows = src.read_u8()?;
3008 let columns = src.read_u8()?;
3009
3010 let (output_width, output_height) = if flags_byte & 1 == 0 {
3012 (u32::from(be_u16(src)?), u32::from(be_u16(src)?))
3014 } else {
3015 (be_u32(src)?, be_u32(src)?)
3017 };
3018
3019 Ok(GridConfig {
3020 rows,
3021 columns,
3022 output_width,
3023 output_height,
3024 })
3025}
3026
3027fn read_iloc<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<ItemLocationBoxItem>> {
3030 let version: IlocVersion = read_fullbox_version_no_flags(src, options)?.try_into()?;
3031
3032 let iloc = src.read_into_try_vec()?;
3033 let mut iloc = BitReader::new(&iloc);
3034
3035 let offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
3036 let length_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
3037 let base_offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
3038
3039 let index_size: Option<IlocFieldSize> = match version {
3040 IlocVersion::One | IlocVersion::Two => Some(iloc.read_u8(4)?.try_into()?),
3041 IlocVersion::Zero => {
3042 let _reserved = iloc.read_u8(4)?;
3043 None
3044 },
3045 };
3046
3047 let item_count = match version {
3048 IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?,
3049 IlocVersion::Two => iloc.read_u32(32)?,
3050 };
3051
3052 let mut items = TryVec::with_capacity(item_count.to_usize())?;
3053
3054 for _ in 0..item_count {
3055 let item_id = match version {
3056 IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?,
3057 IlocVersion::Two => iloc.read_u32(32)?,
3058 };
3059
3060 let construction_method = match version {
3066 IlocVersion::Zero => ConstructionMethod::File,
3067 IlocVersion::One | IlocVersion::Two => {
3068 let _reserved = iloc.read_u16(12)?;
3069 match iloc.read_u16(4)? {
3070 0 => ConstructionMethod::File,
3071 1 => ConstructionMethod::Idat,
3072 2 => return Err(Error::Unsupported("construction_method 'item_offset' is not supported")),
3073 _ => return Err(Error::InvalidData("construction_method is taken from the set 0, 1 or 2 per ISO 14496-12:2015 § 8.11.3.3")),
3074 }
3075 },
3076 };
3077
3078 let data_reference_index = iloc.read_u16(16)?;
3079
3080 if data_reference_index != 0 {
3081 return Err(Error::Unsupported("external file references (iloc.data_reference_index != 0) are not supported"));
3082 }
3083
3084 let base_offset = iloc.read_u64(base_offset_size.to_bits())?;
3085 let extent_count = iloc.read_u16(16)?;
3086
3087 if extent_count < 1 {
3088 return Err(Error::InvalidData("extent_count must have a value 1 or greater per ISO 14496-12:2015 § 8.11.3.3"));
3089 }
3090
3091 let mut extents = TryVec::with_capacity(extent_count.to_usize())?;
3092
3093 for _ in 0..extent_count {
3094 let _extent_index = match &index_size {
3096 None | Some(IlocFieldSize::Zero) => None,
3097 Some(index_size) => {
3098 debug_assert!(version == IlocVersion::One || version == IlocVersion::Two);
3099 Some(iloc.read_u64(index_size.to_bits())?)
3100 },
3101 };
3102
3103 let extent_offset = iloc.read_u64(offset_size.to_bits())?;
3108 let extent_length = iloc.read_u64(length_size.to_bits())?;
3109
3110 let start = base_offset
3113 .checked_add(extent_offset)
3114 .ok_or(Error::InvalidData("offset calculation overflow"))?;
3115 let extent_range = if extent_length == 0 {
3116 ExtentRange::ToEnd(RangeFrom { start })
3117 } else {
3118 let end = start
3119 .checked_add(extent_length)
3120 .ok_or(Error::InvalidData("end calculation overflow"))?;
3121 ExtentRange::WithLength(Range { start, end })
3122 };
3123
3124 extents.push(ItemLocationBoxExtent { extent_range })?;
3125 }
3126
3127 items.push(ItemLocationBoxItem { item_id, construction_method, extents })?;
3128 }
3129
3130 if iloc.remaining() == 0 {
3131 Ok(items)
3132 } else {
3133 Err(Error::InvalidData("invalid iloc size"))
3134 }
3135}
3136
3137fn read_ftyp<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<FileTypeBox> {
3140 let major = be_u32(src)?;
3141 let minor = be_u32(src)?;
3142 let bytes_left = src.bytes_left();
3143 if bytes_left % 4 != 0 {
3144 return Err(Error::InvalidData("invalid ftyp size"));
3145 }
3146 let brand_count = bytes_left / 4;
3148 let mut brands = TryVec::with_capacity(brand_count.try_into()?)?;
3149 for _ in 0..brand_count {
3150 brands.push(be_u32(src)?.into())?;
3151 }
3152 Ok(FileTypeBox {
3153 major_brand: From::from(major),
3154 minor_version: minor,
3155 compatible_brands: brands,
3156 })
3157}
3158
3159#[cfg_attr(debug_assertions, track_caller)]
3160fn check_parser_state<T>(header: &BoxHeader, left: &Take<T>) -> Result<(), Error> {
3161 let limit = left.limit();
3162 if limit == 0 || header.size == u64::MAX {
3164 Ok(())
3165 } else {
3166 debug_assert_eq!(0, limit, "bad parser state bytes left");
3167 Err(Error::InvalidData("unread box content or bad parser sync"))
3168 }
3169}
3170
3171fn skip<T: Read>(src: &mut T, bytes: u64) -> Result<()> {
3173 std::io::copy(&mut src.take(bytes), &mut std::io::sink())?;
3174 Ok(())
3175}
3176
3177fn be_u16<T: ReadBytesExt>(src: &mut T) -> Result<u16> {
3178 src.read_u16::<byteorder::BigEndian>().map_err(From::from)
3179}
3180
3181fn be_u32<T: ReadBytesExt>(src: &mut T) -> Result<u32> {
3182 src.read_u32::<byteorder::BigEndian>().map_err(From::from)
3183}
3184
3185fn be_u64<T: ReadBytesExt>(src: &mut T) -> Result<u64> {
3186 src.read_u64::<byteorder::BigEndian>().map_err(From::from)
3187}