1#![deny(unsafe_code)]
2#![allow(clippy::missing_safety_doc)]
3use arrayvec::ArrayVec;
19use log::{debug, warn};
20
21use bitreader::BitReader;
22use byteorder::ReadBytesExt;
23use fallible_collections::{TryClone, TryReserveError};
24use std::borrow::Cow;
25use std::convert::{TryFrom, TryInto as _};
26
27use std::io::{Read, Take};
28use std::num::NonZeroU32;
29use std::ops::{Range, RangeFrom};
30
31mod obu;
32
33mod boxes;
34use crate::boxes::{BoxType, FourCC};
35
36#[cfg(feature = "eager")]
38pub mod c_api;
39
40pub use enough::{Stop, StopReason, Unstoppable};
41
42trait ToU64 {
48 fn to_u64(self) -> u64;
49}
50
51impl ToU64 for usize {
55 fn to_u64(self) -> u64 {
56 const _: () = assert!(std::mem::size_of::<usize>() <= std::mem::size_of::<u64>());
57 self.try_into().ok().unwrap()
58 }
59}
60
61pub(crate) trait ToUsize {
64 fn to_usize(self) -> usize;
65}
66
67macro_rules! impl_to_usize_from {
71 ( $from_type:ty ) => {
72 impl ToUsize for $from_type {
73 fn to_usize(self) -> usize {
74 const _: () = assert!(std::mem::size_of::<$from_type>() <= std::mem::size_of::<usize>());
75 self.try_into().ok().unwrap()
76 }
77 }
78 };
79}
80
81impl_to_usize_from!(u8);
82impl_to_usize_from!(u16);
83impl_to_usize_from!(u32);
84
85trait Offset {
87 fn offset(&self) -> u64;
88}
89
90struct OffsetReader<'a, T> {
92 reader: &'a mut T,
93 offset: u64,
94}
95
96impl<'a, T> OffsetReader<'a, T> {
97 fn new(reader: &'a mut T) -> Self {
98 Self { reader, offset: 0 }
99 }
100}
101
102impl<T> Offset for OffsetReader<'_, T> {
103 fn offset(&self) -> u64 {
104 self.offset
105 }
106}
107
108impl<T: Read> Read for OffsetReader<'_, T> {
109 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
110 let bytes_read = self.reader.read(buf)?;
111 self.offset = self
112 .offset
113 .checked_add(bytes_read.to_u64())
114 .ok_or(Error::Unsupported("total bytes read too large for offset type"))?;
115 Ok(bytes_read)
116 }
117}
118
119#[doc(hidden)]
120pub type TryVec<T> = fallible_collections::TryVec<T>;
121type TryString = fallible_collections::TryVec<u8>;
122
123#[allow(dead_code)]
125struct Vec;
126#[allow(dead_code)]
127struct Box;
128#[allow(dead_code)]
129struct HashMap;
130#[allow(dead_code)]
131struct String;
132
133#[derive(Debug)]
138pub enum Error {
139 InvalidData(&'static str),
141 Unsupported(&'static str),
143 UnexpectedEOF,
145 Io(std::io::Error),
147 NoMoov,
149 OutOfMemory,
151 ResourceLimitExceeded(&'static str),
153 Stopped(enough::StopReason),
155}
156
157impl std::fmt::Display for Error {
158 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
159 let msg = match self {
160 Self::InvalidData(s) | Self::Unsupported(s) | Self::ResourceLimitExceeded(s) => s,
161 Self::UnexpectedEOF => "EOF",
162 Self::Io(err) => return err.fmt(f),
163 Self::NoMoov => "Missing Moov box",
164 Self::OutOfMemory => "OOM",
165 Self::Stopped(reason) => return write!(f, "Stopped: {}", reason),
166 };
167 f.write_str(msg)
168 }
169}
170
171impl std::error::Error for Error {}
172
173impl From<bitreader::BitReaderError> for Error {
174 #[cold]
175 #[cfg_attr(debug_assertions, track_caller)]
176 fn from(err: bitreader::BitReaderError) -> Self {
177 log::warn!("bitreader: {err}");
178 debug_assert!(!matches!(err, bitreader::BitReaderError::TooManyBitsForType { .. })); Self::InvalidData("truncated bits")
180 }
181}
182
183impl From<std::io::Error> for Error {
184 fn from(err: std::io::Error) -> Self {
185 match err.kind() {
186 std::io::ErrorKind::UnexpectedEof => Self::UnexpectedEOF,
187 _ => Self::Io(err),
188 }
189 }
190}
191
192impl From<std::string::FromUtf8Error> for Error {
193 fn from(_: std::string::FromUtf8Error) -> Self {
194 Self::InvalidData("invalid utf8")
195 }
196}
197
198impl From<std::num::TryFromIntError> for Error {
199 fn from(_: std::num::TryFromIntError) -> Self {
200 Self::Unsupported("integer conversion failed")
201 }
202}
203
204impl From<Error> for std::io::Error {
205 fn from(err: Error) -> Self {
206 let kind = match err {
207 Error::InvalidData(_) => std::io::ErrorKind::InvalidData,
208 Error::UnexpectedEOF => std::io::ErrorKind::UnexpectedEof,
209 Error::Io(io_err) => return io_err,
210 _ => std::io::ErrorKind::Other,
211 };
212 Self::new(kind, err)
213 }
214}
215
216impl From<TryReserveError> for Error {
217 fn from(_: TryReserveError) -> Self {
218 Self::OutOfMemory
219 }
220}
221
222impl From<enough::StopReason> for Error {
223 fn from(reason: enough::StopReason) -> Self {
224 Self::Stopped(reason)
225 }
226}
227
228pub type Result<T, E = Error> = std::result::Result<T, E>;
230
231#[derive(Debug, Clone, Copy)]
240struct BoxHeader {
241 name: BoxType,
243 size: u64,
245 offset: u64,
247 #[allow(unused)]
249 uuid: Option<[u8; 16]>,
250}
251
252impl BoxHeader {
253 const MIN_SIZE: u64 = 8;
255 const MIN_LARGE_SIZE: u64 = 16;
257}
258
259#[derive(Debug)]
261#[allow(unused)]
262struct FileTypeBox {
263 major_brand: FourCC,
264 minor_version: u32,
265 compatible_brands: TryVec<FourCC>,
266}
267
268#[derive(Debug)]
270#[allow(unused)]
271struct HandlerBox {
272 handler_type: FourCC,
273}
274
275#[derive(Debug, Clone, PartialEq, Eq)]
280pub struct AV1Config {
281 pub profile: u8,
283 pub level: u8,
285 pub tier: u8,
287 pub bit_depth: u8,
289 pub monochrome: bool,
291 pub chroma_subsampling_x: u8,
293 pub chroma_subsampling_y: u8,
295 pub chroma_sample_position: u8,
297}
298
299#[derive(Debug, Clone, PartialEq, Eq)]
304pub enum ColorInformation {
305 Nclx {
307 color_primaries: u16,
309 transfer_characteristics: u16,
311 matrix_coefficients: u16,
313 full_range: bool,
315 },
316 IccProfile(std::vec::Vec<u8>),
318}
319
320#[derive(Debug, Clone, Copy, PartialEq, Eq)]
325pub struct ImageRotation {
326 pub angle: u16,
328}
329
330#[derive(Debug, Clone, Copy, PartialEq, Eq)]
335pub struct ImageMirror {
336 pub axis: u8,
339}
340
341#[derive(Debug, Clone, Copy, PartialEq, Eq)]
347pub struct CleanAperture {
348 pub width_n: u32,
350 pub width_d: u32,
352 pub height_n: u32,
354 pub height_d: u32,
356 pub horiz_off_n: i32,
358 pub horiz_off_d: u32,
360 pub vert_off_n: i32,
362 pub vert_off_d: u32,
364}
365
366#[derive(Debug, Clone, Copy, PartialEq, Eq)]
371pub struct PixelAspectRatio {
372 pub h_spacing: u32,
374 pub v_spacing: u32,
376}
377
378#[derive(Debug, Clone, Copy, PartialEq, Eq)]
383pub struct ContentLightLevel {
384 pub max_content_light_level: u16,
386 pub max_pic_average_light_level: u16,
388}
389
390#[derive(Debug, Clone, Copy, PartialEq, Eq)]
395pub struct MasteringDisplayColourVolume {
396 pub primaries: [(u16, u16); 3],
399 pub white_point: (u16, u16),
401 pub max_luminance: u32,
403 pub min_luminance: u32,
405}
406
407#[derive(Debug, Clone, Copy, PartialEq, Eq)]
413pub struct ContentColourVolume {
414 pub primaries: Option<[(i32, i32); 3]>,
417 pub min_luminance: Option<u32>,
419 pub max_luminance: Option<u32>,
421 pub avg_luminance: Option<u32>,
423}
424
425#[derive(Debug, Clone, Copy, PartialEq, Eq)]
430pub struct AmbientViewingEnvironment {
431 pub ambient_illuminance: u32,
433 pub ambient_light_x: u16,
435 pub ambient_light_y: u16,
437}
438
439#[derive(Debug, Clone, Copy, PartialEq, Eq)]
444pub struct OperatingPointSelector {
445 pub op_index: u8,
447}
448
449#[derive(Debug, Clone, Copy, PartialEq, Eq)]
454pub struct LayerSelector {
455 pub layer_id: u16,
457}
458
459#[derive(Debug, Clone, Copy, PartialEq, Eq)]
465pub struct AV1LayeredImageIndexing {
466 pub layer_sizes: [u32; 3],
469}
470
471#[derive(Debug, Clone, Copy)]
475#[derive(Default)]
476pub struct ParseOptions {
477 pub lenient: bool,
485}
486
487#[derive(Debug, Clone)]
513pub struct DecodeConfig {
514 pub peak_memory_limit: Option<u64>,
517
518 pub total_megapixels_limit: Option<u32>,
521
522 pub max_animation_frames: Option<u32>,
525
526 pub max_grid_tiles: Option<u32>,
529
530 pub lenient: bool,
533}
534
535impl Default for DecodeConfig {
536 fn default() -> Self {
537 Self {
538 peak_memory_limit: Some(1_000_000_000),
539 total_megapixels_limit: Some(512),
540 max_animation_frames: Some(10_000),
541 max_grid_tiles: Some(1_000),
542 lenient: false,
543 }
544 }
545}
546
547impl DecodeConfig {
548 pub fn unlimited() -> Self {
552 Self {
553 peak_memory_limit: None,
554 total_megapixels_limit: None,
555 max_animation_frames: None,
556 max_grid_tiles: None,
557 lenient: false,
558 }
559 }
560
561 pub fn with_peak_memory_limit(mut self, bytes: u64) -> Self {
563 self.peak_memory_limit = Some(bytes);
564 self
565 }
566
567 pub fn with_total_megapixels_limit(mut self, megapixels: u32) -> Self {
569 self.total_megapixels_limit = Some(megapixels);
570 self
571 }
572
573 pub fn with_max_animation_frames(mut self, frames: u32) -> Self {
575 self.max_animation_frames = Some(frames);
576 self
577 }
578
579 pub fn with_max_grid_tiles(mut self, tiles: u32) -> Self {
581 self.max_grid_tiles = Some(tiles);
582 self
583 }
584
585 pub fn lenient(mut self, lenient: bool) -> Self {
587 self.lenient = lenient;
588 self
589 }
590}
591
592#[derive(Debug, Clone, PartialEq)]
594pub struct GridConfig {
612 pub rows: u8,
614 pub columns: u8,
616 pub output_width: u32,
618 pub output_height: u32,
620}
621
622#[cfg(feature = "eager")]
624#[deprecated(since = "1.5.0", note = "Use `AvifParser::frame()` which returns `FrameRef` instead")]
625#[derive(Debug)]
626pub struct AnimationFrame {
627 pub data: TryVec<u8>,
629 pub duration_ms: u32,
631}
632
633#[cfg(feature = "eager")]
635#[deprecated(since = "1.5.0", note = "Use `AvifParser::animation_info()` and `AvifParser::frames()` instead")]
636#[derive(Debug)]
637#[allow(deprecated)]
638pub struct AnimationConfig {
639 pub loop_count: u32,
641 pub frames: TryVec<AnimationFrame>,
643}
644
645#[derive(Debug)]
648struct MovieHeader {
649 _timescale: u32,
650 _duration: u64,
651}
652
653#[derive(Debug)]
654struct MediaHeader {
655 timescale: u32,
656 _duration: u64,
657}
658
659#[derive(Debug)]
660struct TimeToSampleEntry {
661 sample_count: u32,
662 sample_delta: u32,
663}
664
665#[derive(Debug)]
666struct SampleToChunkEntry {
667 first_chunk: u32,
668 samples_per_chunk: u32,
669 _sample_description_index: u32,
670}
671
672#[derive(Debug)]
673struct SampleTable {
674 time_to_sample: TryVec<TimeToSampleEntry>,
675 sample_to_chunk: TryVec<SampleToChunkEntry>,
676 sample_sizes: TryVec<u32>,
677 chunk_offsets: TryVec<u64>,
678}
679
680#[derive(Debug)]
682struct TrackReference {
683 reference_type: FourCC,
684 track_ids: TryVec<u32>,
685}
686
687#[derive(Debug)]
689struct ParsedTrack {
690 track_id: u32,
691 handler_type: FourCC,
692 media_timescale: u32,
693 sample_table: SampleTable,
694 references: TryVec<TrackReference>,
695 loop_count: u32,
696}
697
698struct ParsedAnimationData {
700 color_timescale: u32,
701 color_sample_table: SampleTable,
702 alpha_timescale: Option<u32>,
703 alpha_sample_table: Option<SampleTable>,
704 loop_count: u32,
705}
706
707#[cfg(feature = "eager")]
708#[deprecated(since = "1.5.0", note = "Use `AvifParser` for zero-copy parsing instead")]
709#[derive(Debug, Default)]
710#[allow(deprecated)]
711pub struct AvifData {
712 pub primary_item: TryVec<u8>,
716 pub alpha_item: Option<TryVec<u8>>,
720 pub premultiplied_alpha: bool,
724
725 pub grid_config: Option<GridConfig>,
749
750 pub grid_tiles: TryVec<TryVec<u8>>,
758
759 pub animation: Option<AnimationConfig>,
763
764 pub av1_config: Option<AV1Config>,
766
767 pub color_info: Option<ColorInformation>,
769
770 pub rotation: Option<ImageRotation>,
772
773 pub mirror: Option<ImageMirror>,
775
776 pub clean_aperture: Option<CleanAperture>,
778
779 pub pixel_aspect_ratio: Option<PixelAspectRatio>,
781
782 pub content_light_level: Option<ContentLightLevel>,
784
785 pub mastering_display: Option<MasteringDisplayColourVolume>,
787
788 pub content_colour_volume: Option<ContentColourVolume>,
790
791 pub ambient_viewing: Option<AmbientViewingEnvironment>,
793
794 pub operating_point: Option<OperatingPointSelector>,
796
797 pub layer_selector: Option<LayerSelector>,
799
800 pub layered_image_indexing: Option<AV1LayeredImageIndexing>,
802
803 pub exif: Option<TryVec<u8>>,
807
808 pub xmp: Option<TryVec<u8>>,
812
813 pub major_brand: [u8; 4],
815
816 pub compatible_brands: std::vec::Vec<[u8; 4]>,
818}
819
820#[cfg(feature = "eager")]
841#[allow(deprecated)]
842impl AvifData {
843 #[deprecated(since = "1.5.0", note = "Use `AvifParser::from_reader()` instead")]
844 pub fn from_reader<R: Read>(reader: &mut R) -> Result<Self> {
845 read_avif(reader)
846 }
847
848 pub fn primary_item_metadata(&self) -> Result<AV1Metadata> {
850 AV1Metadata::parse_av1_bitstream(&self.primary_item)
851 }
852
853 pub fn alpha_item_metadata(&self) -> Result<Option<AV1Metadata>> {
855 self.alpha_item.as_deref().map(AV1Metadata::parse_av1_bitstream).transpose()
856 }
857}
858
859#[non_exhaustive]
863#[derive(Debug, Clone)]
864pub struct AV1Metadata {
865 pub still_picture: bool,
867 pub max_frame_width: NonZeroU32,
868 pub max_frame_height: NonZeroU32,
869 pub bit_depth: u8,
871 pub seq_profile: u8,
873 pub chroma_subsampling: (bool, bool),
875 pub monochrome: bool,
876}
877
878impl AV1Metadata {
879 #[inline(never)]
884 pub fn parse_av1_bitstream(obu_bitstream: &[u8]) -> Result<Self> {
885 let h = obu::parse_obu(obu_bitstream)?;
886 Ok(Self {
887 still_picture: h.still_picture,
888 max_frame_width: h.max_frame_width,
889 max_frame_height: h.max_frame_height,
890 bit_depth: h.color.bit_depth,
891 seq_profile: h.seq_profile,
892 chroma_subsampling: h.color.chroma_subsampling,
893 monochrome: h.color.monochrome,
894 })
895 }
896}
897
898pub struct FrameRef<'a> {
903 pub data: Cow<'a, [u8]>,
904 pub alpha_data: Option<Cow<'a, [u8]>>,
906 pub duration_ms: u32,
907}
908
909struct MdatBounds {
911 offset: u64,
912 length: u64,
913}
914
915struct ItemExtents {
917 construction_method: ConstructionMethod,
918 extents: TryVec<ExtentRange>,
919}
920
921pub struct AvifParser<'data> {
947 raw: Cow<'data, [u8]>,
948 mdat_bounds: TryVec<MdatBounds>,
949 idat: Option<TryVec<u8>>,
950 primary: ItemExtents,
951 alpha: Option<ItemExtents>,
952 grid_config: Option<GridConfig>,
953 tiles: TryVec<ItemExtents>,
954 animation_data: Option<AnimationParserData>,
955 premultiplied_alpha: bool,
956 av1_config: Option<AV1Config>,
957 color_info: Option<ColorInformation>,
958 rotation: Option<ImageRotation>,
959 mirror: Option<ImageMirror>,
960 clean_aperture: Option<CleanAperture>,
961 pixel_aspect_ratio: Option<PixelAspectRatio>,
962 content_light_level: Option<ContentLightLevel>,
963 mastering_display: Option<MasteringDisplayColourVolume>,
964 content_colour_volume: Option<ContentColourVolume>,
965 ambient_viewing: Option<AmbientViewingEnvironment>,
966 operating_point: Option<OperatingPointSelector>,
967 layer_selector: Option<LayerSelector>,
968 layered_image_indexing: Option<AV1LayeredImageIndexing>,
969 exif_item: Option<ItemExtents>,
970 xmp_item: Option<ItemExtents>,
971 major_brand: [u8; 4],
972 compatible_brands: std::vec::Vec<[u8; 4]>,
973}
974
975struct AnimationParserData {
976 media_timescale: u32,
977 sample_table: SampleTable,
978 alpha_media_timescale: Option<u32>,
979 alpha_sample_table: Option<SampleTable>,
980 loop_count: u32,
981}
982
983#[derive(Debug, Clone, Copy)]
985pub struct AnimationInfo {
986 pub frame_count: usize,
987 pub loop_count: u32,
988 pub has_alpha: bool,
990 pub timescale: u32,
992}
993
994struct ParsedStructure {
996 meta: Option<AvifInternalMeta>,
998 mdat_bounds: TryVec<MdatBounds>,
999 animation_data: Option<ParsedAnimationData>,
1000 major_brand: [u8; 4],
1001 compatible_brands: std::vec::Vec<[u8; 4]>,
1002}
1003
1004impl<'data> AvifParser<'data> {
1005 pub fn from_bytes(data: &'data [u8]) -> Result<Self> {
1014 Self::from_bytes_with_config(data, &DecodeConfig::unlimited(), &Unstoppable)
1015 }
1016
1017 pub fn from_bytes_with_config(
1019 data: &'data [u8],
1020 config: &DecodeConfig,
1021 stop: &dyn Stop,
1022 ) -> Result<Self> {
1023 let parsed = Self::parse_raw(data, config, stop)?;
1024 Self::build(Cow::Borrowed(data), parsed, config)
1025 }
1026
1027 pub fn from_owned(data: std::vec::Vec<u8>) -> Result<AvifParser<'static>> {
1032 AvifParser::from_owned_with_config(data, &DecodeConfig::unlimited(), &Unstoppable)
1033 }
1034
1035 pub fn from_owned_with_config(
1037 data: std::vec::Vec<u8>,
1038 config: &DecodeConfig,
1039 stop: &dyn Stop,
1040 ) -> Result<AvifParser<'static>> {
1041 let parsed = AvifParser::parse_raw(&data, config, stop)?;
1042 AvifParser::build(Cow::Owned(data), parsed, config)
1043 }
1044
1045 pub fn from_reader<R: Read>(reader: &mut R) -> Result<AvifParser<'static>> {
1047 AvifParser::from_reader_with_config(reader, &DecodeConfig::unlimited(), &Unstoppable)
1048 }
1049
1050 pub fn from_reader_with_config<R: Read>(
1052 reader: &mut R,
1053 config: &DecodeConfig,
1054 stop: &dyn Stop,
1055 ) -> Result<AvifParser<'static>> {
1056 let mut buf = std::vec::Vec::new();
1057 reader.read_to_end(&mut buf)?;
1058 AvifParser::from_owned_with_config(buf, config, stop)
1059 }
1060
1061 fn parse_raw(data: &[u8], config: &DecodeConfig, stop: &dyn Stop) -> Result<ParsedStructure> {
1068 let parse_opts = ParseOptions { lenient: config.lenient };
1069 let mut cursor = std::io::Cursor::new(data);
1070 let mut f = OffsetReader::new(&mut cursor);
1071 let mut iter = BoxIter::new(&mut f);
1072
1073 let (major_brand, compatible_brands) = if let Some(mut b) = iter.next_box()? {
1075 if b.head.name == BoxType::FileTypeBox {
1076 let ftyp = read_ftyp(&mut b)?;
1077 if ftyp.major_brand != b"avif" && ftyp.major_brand != b"avis" {
1078 return Err(Error::InvalidData("ftyp must be 'avif' or 'avis'"));
1079 }
1080 let major = ftyp.major_brand.value;
1081 let compat = ftyp.compatible_brands.iter().map(|b| b.value).collect();
1082 (major, compat)
1083 } else {
1084 return Err(Error::InvalidData("'ftyp' box must occur first"));
1085 }
1086 } else {
1087 return Err(Error::InvalidData("'ftyp' box must occur first"));
1088 };
1089
1090 let mut meta = None;
1091 let mut mdat_bounds = TryVec::new();
1092 let mut animation_data: Option<ParsedAnimationData> = None;
1093
1094 while let Some(mut b) = iter.next_box()? {
1095 stop.check()?;
1096
1097 match b.head.name {
1098 BoxType::MetadataBox => {
1099 if meta.is_some() {
1100 return Err(Error::InvalidData(
1101 "There should be zero or one meta boxes per ISO 14496-12:2015 § 8.11.1.1",
1102 ));
1103 }
1104 meta = Some(read_avif_meta(&mut b, &parse_opts)?);
1105 }
1106 BoxType::MovieBox => {
1107 let tracks = read_moov(&mut b)?;
1108 if !tracks.is_empty() {
1109 animation_data = Some(associate_tracks(tracks)?);
1110 }
1111 }
1112 BoxType::MediaDataBox => {
1113 if b.bytes_left() > 0 {
1114 let offset = b.offset();
1115 let length = b.bytes_left();
1116 mdat_bounds.push(MdatBounds { offset, length })?;
1117 }
1118 skip_box_content(&mut b)?;
1120 }
1121 _ => skip_box_content(&mut b)?,
1122 }
1123
1124 check_parser_state(&b.head, &b.content)?;
1125 }
1126
1127 if meta.is_none() && animation_data.is_none() {
1130 return Err(Error::InvalidData("missing meta"));
1131 }
1132
1133 Ok(ParsedStructure { meta, mdat_bounds, animation_data, major_brand, compatible_brands })
1134 }
1135
1136 fn build(raw: Cow<'data, [u8]>, parsed: ParsedStructure, config: &DecodeConfig) -> Result<Self> {
1138 let tracker = ResourceTracker::new(config);
1139
1140 let animation_data = if let Some(anim) = parsed.animation_data {
1142 tracker.validate_animation_frames(anim.color_sample_table.sample_sizes.len() as u32)?;
1143 Some(AnimationParserData {
1144 media_timescale: anim.color_timescale,
1145 sample_table: anim.color_sample_table,
1146 alpha_media_timescale: anim.alpha_timescale,
1147 alpha_sample_table: anim.alpha_sample_table,
1148 loop_count: anim.loop_count,
1149 })
1150 } else {
1151 None
1152 };
1153
1154 let Some(meta) = parsed.meta else {
1156 return Ok(Self {
1157 raw,
1158 mdat_bounds: parsed.mdat_bounds,
1159 idat: None,
1160 primary: ItemExtents { construction_method: ConstructionMethod::File, extents: TryVec::new() },
1161 alpha: None,
1162 grid_config: None,
1163 tiles: TryVec::new(),
1164 animation_data,
1165 premultiplied_alpha: false,
1166 av1_config: None,
1167 color_info: None,
1168 rotation: None,
1169 mirror: None,
1170 clean_aperture: None,
1171 pixel_aspect_ratio: None,
1172 content_light_level: None,
1173 mastering_display: None,
1174 content_colour_volume: None,
1175 ambient_viewing: None,
1176 operating_point: None,
1177 layer_selector: None,
1178 layered_image_indexing: None,
1179 exif_item: None,
1180 xmp_item: None,
1181 major_brand: parsed.major_brand,
1182 compatible_brands: parsed.compatible_brands,
1183 });
1184 };
1185
1186 let primary = Self::get_item_extents(&meta, meta.primary_item_id)?;
1188
1189 let alpha_item_id = meta
1191 .item_references
1192 .iter()
1193 .filter(|iref| {
1194 iref.to_item_id == meta.primary_item_id
1195 && iref.from_item_id != meta.primary_item_id
1196 && iref.item_type == b"auxl"
1197 })
1198 .map(|iref| iref.from_item_id)
1199 .find(|&item_id| {
1200 meta.properties.iter().any(|prop| {
1201 prop.item_id == item_id
1202 && match &prop.property {
1203 ItemProperty::AuxiliaryType(urn) => {
1204 urn.type_subtype().0 == b"urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"
1205 }
1206 _ => false,
1207 }
1208 })
1209 });
1210
1211 let alpha = alpha_item_id
1212 .map(|id| Self::get_item_extents(&meta, id))
1213 .transpose()?;
1214
1215 let premultiplied_alpha = alpha_item_id.is_some_and(|alpha_id| {
1217 meta.item_references.iter().any(|iref| {
1218 iref.from_item_id == meta.primary_item_id
1219 && iref.to_item_id == alpha_id
1220 && iref.item_type == b"prem"
1221 })
1222 });
1223
1224 let mut exif_item = None;
1226 let mut xmp_item = None;
1227 for iref in meta.item_references.iter() {
1228 if iref.to_item_id != meta.primary_item_id || iref.item_type != b"cdsc" {
1229 continue;
1230 }
1231 let desc_item_id = iref.from_item_id;
1232 let Some(info) = meta.item_infos.iter().find(|i| i.item_id == desc_item_id) else {
1233 continue;
1234 };
1235 if info.item_type == b"Exif" && exif_item.is_none() {
1236 exif_item = Some(Self::get_item_extents(&meta, desc_item_id)?);
1237 } else if info.item_type == b"mime" && xmp_item.is_none() {
1238 xmp_item = Some(Self::get_item_extents(&meta, desc_item_id)?);
1239 }
1240 }
1241
1242 let is_grid = meta
1244 .item_infos
1245 .iter()
1246 .find(|x| x.item_id == meta.primary_item_id)
1247 .is_some_and(|info| info.item_type == b"grid");
1248
1249 let (grid_config, tiles) = if is_grid {
1251 let mut tiles_with_index: TryVec<(u32, u16)> = TryVec::new();
1252 for iref in meta.item_references.iter() {
1253 if iref.from_item_id == meta.primary_item_id && iref.item_type == b"dimg" {
1254 tiles_with_index.push((iref.to_item_id, iref.reference_index))?;
1255 }
1256 }
1257
1258 tracker.validate_grid_tiles(tiles_with_index.len() as u32)?;
1259 tiles_with_index.sort_by_key(|&(_, idx)| idx);
1260
1261 let mut tile_extents = TryVec::new();
1262 for (tile_id, _) in tiles_with_index.iter() {
1263 tile_extents.push(Self::get_item_extents(&meta, *tile_id)?)?;
1264 }
1265
1266 let mut tile_ids = TryVec::new();
1267 for (tile_id, _) in tiles_with_index.iter() {
1268 tile_ids.push(*tile_id)?;
1269 }
1270
1271 let grid_config = Self::calculate_grid_config(&meta, &tile_ids)?;
1272
1273 for (tile_id, _) in tiles_with_index.iter() {
1275 for prop in meta.properties.iter() {
1276 if prop.item_id == *tile_id {
1277 match &prop.property {
1278 ItemProperty::Rotation(_)
1279 | ItemProperty::Mirror(_)
1280 | ItemProperty::CleanAperture(_) => {
1281 warn!("grid tile {} has a transformative property (irot/imir/clap), violating AVIF spec", tile_id);
1282 }
1283 _ => {}
1284 }
1285 }
1286 }
1287 }
1288
1289 (Some(grid_config), tile_extents)
1290 } else {
1291 (None, TryVec::new())
1292 };
1293
1294 macro_rules! find_prop {
1296 ($variant:ident) => {
1297 meta.properties.iter().find_map(|p| {
1298 if p.item_id == meta.primary_item_id {
1299 match &p.property {
1300 ItemProperty::$variant(c) => Some(c.clone()),
1301 _ => None,
1302 }
1303 } else {
1304 None
1305 }
1306 })
1307 };
1308 }
1309
1310 let av1_config = find_prop!(AV1Config);
1311 let color_info = find_prop!(ColorInformation);
1312 let rotation = find_prop!(Rotation);
1313 let mirror = find_prop!(Mirror);
1314 let clean_aperture = find_prop!(CleanAperture);
1315 let pixel_aspect_ratio = find_prop!(PixelAspectRatio);
1316 let content_light_level = find_prop!(ContentLightLevel);
1317 let mastering_display = find_prop!(MasteringDisplayColourVolume);
1318 let content_colour_volume = find_prop!(ContentColourVolume);
1319 let ambient_viewing = find_prop!(AmbientViewingEnvironment);
1320 let operating_point = find_prop!(OperatingPointSelector);
1321 let layer_selector = find_prop!(LayerSelector);
1322 let layered_image_indexing = find_prop!(AV1LayeredImageIndexing);
1323
1324 let idat = if let Some(ref idat_data) = meta.idat {
1326 let mut cloned = TryVec::new();
1327 cloned.extend_from_slice(idat_data)?;
1328 Some(cloned)
1329 } else {
1330 None
1331 };
1332
1333 Ok(Self {
1334 raw,
1335 mdat_bounds: parsed.mdat_bounds,
1336 idat,
1337 primary,
1338 alpha,
1339 grid_config,
1340 tiles,
1341 animation_data,
1342 premultiplied_alpha,
1343 av1_config,
1344 color_info,
1345 rotation,
1346 mirror,
1347 clean_aperture,
1348 pixel_aspect_ratio,
1349 content_light_level,
1350 mastering_display,
1351 content_colour_volume,
1352 ambient_viewing,
1353 operating_point,
1354 layer_selector,
1355 layered_image_indexing,
1356 exif_item,
1357 xmp_item,
1358 major_brand: parsed.major_brand,
1359 compatible_brands: parsed.compatible_brands,
1360 })
1361 }
1362
1363 fn get_item_extents(meta: &AvifInternalMeta, item_id: u32) -> Result<ItemExtents> {
1369 let item = meta
1370 .iloc_items
1371 .iter()
1372 .find(|item| item.item_id == item_id)
1373 .ok_or(Error::InvalidData("item not found in iloc"))?;
1374
1375 let mut extents = TryVec::new();
1376 for extent in &item.extents {
1377 extents.push(extent.extent_range.clone())?;
1378 }
1379 Ok(ItemExtents {
1380 construction_method: item.construction_method,
1381 extents,
1382 })
1383 }
1384
1385 fn resolve_item(&self, item: &ItemExtents) -> Result<Cow<'_, [u8]>> {
1388 match item.construction_method {
1389 ConstructionMethod::Idat => self.resolve_idat_extents(&item.extents),
1390 ConstructionMethod::File => self.resolve_file_extents(&item.extents),
1391 ConstructionMethod::Item => Err(Error::Unsupported("construction_method 'item' not supported")),
1392 }
1393 }
1394
1395 fn resolve_file_extents(&self, extents: &[ExtentRange]) -> Result<Cow<'_, [u8]>> {
1397 let raw = self.raw.as_ref();
1398
1399 if extents.len() == 1 {
1401 let extent = &extents[0];
1402 let (start, end) = self.extent_byte_range(extent)?;
1403 let slice = raw.get(start..end).ok_or(Error::InvalidData("extent out of bounds in raw buffer"))?;
1404 return Ok(Cow::Borrowed(slice));
1405 }
1406
1407 let mut data = TryVec::new();
1409 for extent in extents {
1410 let (start, end) = self.extent_byte_range(extent)?;
1411 let slice = raw.get(start..end).ok_or(Error::InvalidData("extent out of bounds in raw buffer"))?;
1412 data.extend_from_slice(slice)?;
1413 }
1414 Ok(Cow::Owned(data.to_vec()))
1415 }
1416
1417 fn extent_byte_range(&self, extent: &ExtentRange) -> Result<(usize, usize)> {
1419 let file_offset = extent.start();
1420 let start = usize::try_from(file_offset)?;
1421
1422 match extent {
1423 ExtentRange::WithLength(range) => {
1424 let len = range.end.checked_sub(range.start)
1425 .ok_or(Error::InvalidData("extent range start > end"))?;
1426 let end = start.checked_add(usize::try_from(len)?)
1427 .ok_or(Error::InvalidData("extent end overflow"))?;
1428 Ok((start, end))
1429 }
1430 ExtentRange::ToEnd(_) => {
1431 for mdat in &self.mdat_bounds {
1433 if file_offset >= mdat.offset && file_offset < mdat.offset + mdat.length {
1434 let end = usize::try_from(mdat.offset + mdat.length)?;
1435 return Ok((start, end));
1436 }
1437 }
1438 Ok((start, self.raw.len()))
1440 }
1441 }
1442 }
1443
1444 fn resolve_idat_extents(&self, extents: &[ExtentRange]) -> Result<Cow<'_, [u8]>> {
1446 let idat_data = self.idat.as_ref()
1447 .ok_or(Error::InvalidData("idat box missing but construction_method is Idat"))?;
1448
1449 if extents.len() == 1 {
1450 let extent = &extents[0];
1451 let start = usize::try_from(extent.start())?;
1452 let slice = match extent {
1453 ExtentRange::WithLength(range) => {
1454 let len = usize::try_from(range.end - range.start)?;
1455 idat_data.get(start..start + len)
1456 .ok_or(Error::InvalidData("idat extent out of bounds"))?
1457 }
1458 ExtentRange::ToEnd(_) => {
1459 idat_data.get(start..)
1460 .ok_or(Error::InvalidData("idat extent out of bounds"))?
1461 }
1462 };
1463 return Ok(Cow::Borrowed(slice));
1464 }
1465
1466 let mut data = TryVec::new();
1468 for extent in extents {
1469 let start = usize::try_from(extent.start())?;
1470 let slice = match extent {
1471 ExtentRange::WithLength(range) => {
1472 let len = usize::try_from(range.end - range.start)?;
1473 idat_data.get(start..start + len)
1474 .ok_or(Error::InvalidData("idat extent out of bounds"))?
1475 }
1476 ExtentRange::ToEnd(_) => {
1477 idat_data.get(start..)
1478 .ok_or(Error::InvalidData("idat extent out of bounds"))?
1479 }
1480 };
1481 data.extend_from_slice(slice)?;
1482 }
1483 Ok(Cow::Owned(data.to_vec()))
1484 }
1485
1486 fn resolve_frame(&self, index: usize) -> Result<FrameRef<'_>> {
1488 let anim = self.animation_data.as_ref()
1489 .ok_or(Error::InvalidData("not an animated AVIF"))?;
1490
1491 if index >= anim.sample_table.sample_sizes.len() {
1492 return Err(Error::InvalidData("frame index out of bounds"));
1493 }
1494
1495 let duration_ms = self.calculate_frame_duration(&anim.sample_table, anim.media_timescale, index)?;
1496 let (offset, size) = self.calculate_sample_location(&anim.sample_table, index)?;
1497
1498 let start = usize::try_from(offset)?;
1499 let end = start.checked_add(size as usize)
1500 .ok_or(Error::InvalidData("frame end overflow"))?;
1501
1502 let raw = self.raw.as_ref();
1503 let slice = raw.get(start..end)
1504 .ok_or(Error::InvalidData("frame not found in raw buffer"))?;
1505
1506 let alpha_data = if let Some(ref alpha_st) = anim.alpha_sample_table {
1508 let alpha_timescale = anim.alpha_media_timescale.unwrap_or(anim.media_timescale);
1509 if index < alpha_st.sample_sizes.len() {
1510 let (a_offset, a_size) = self.calculate_sample_location(alpha_st, index)?;
1511 let a_start = usize::try_from(a_offset)?;
1512 let a_end = a_start.checked_add(a_size as usize)
1513 .ok_or(Error::InvalidData("alpha frame end overflow"))?;
1514 let a_slice = raw.get(a_start..a_end)
1515 .ok_or(Error::InvalidData("alpha frame not found in raw buffer"))?;
1516 let _ = alpha_timescale; Some(Cow::Borrowed(a_slice))
1518 } else {
1519 warn!("alpha track has fewer frames than color track (index {})", index);
1520 None
1521 }
1522 } else {
1523 None
1524 };
1525
1526 Ok(FrameRef {
1527 data: Cow::Borrowed(slice),
1528 alpha_data,
1529 duration_ms,
1530 })
1531 }
1532
1533 fn calculate_grid_config(meta: &AvifInternalMeta, tile_ids: &[u32]) -> Result<GridConfig> {
1535 for prop in &meta.properties {
1537 if prop.item_id == meta.primary_item_id
1538 && let ItemProperty::ImageGrid(grid) = &prop.property {
1539 return Ok(grid.clone());
1540 }
1541 }
1542
1543 let grid_dims = meta
1545 .properties
1546 .iter()
1547 .find(|p| p.item_id == meta.primary_item_id)
1548 .and_then(|p| match &p.property {
1549 ItemProperty::ImageSpatialExtents(e) => Some(e),
1550 _ => None,
1551 });
1552
1553 let tile_dims = tile_ids.first().and_then(|&tile_id| {
1554 meta.properties
1555 .iter()
1556 .find(|p| p.item_id == tile_id)
1557 .and_then(|p| match &p.property {
1558 ItemProperty::ImageSpatialExtents(e) => Some(e),
1559 _ => None,
1560 })
1561 });
1562
1563 if let (Some(grid), Some(tile)) = (grid_dims, tile_dims)
1564 && tile.width != 0
1565 && tile.height != 0
1566 && grid.width % tile.width == 0
1567 && grid.height % tile.height == 0
1568 {
1569 let columns = grid.width / tile.width;
1570 let rows = grid.height / tile.height;
1571
1572 if columns <= 255 && rows <= 255 {
1573 return Ok(GridConfig {
1574 rows: rows as u8,
1575 columns: columns as u8,
1576 output_width: grid.width,
1577 output_height: grid.height,
1578 });
1579 }
1580 }
1581
1582 let tile_count = tile_ids.len();
1583 Ok(GridConfig {
1584 rows: tile_count.min(255) as u8,
1585 columns: 1,
1586 output_width: 0,
1587 output_height: 0,
1588 })
1589 }
1590
1591 fn calculate_frame_duration(
1593 &self,
1594 st: &SampleTable,
1595 timescale: u32,
1596 index: usize,
1597 ) -> Result<u32> {
1598 let mut current_sample = 0;
1599 for entry in &st.time_to_sample {
1600 if current_sample + entry.sample_count as usize > index {
1601 let duration_ms = if timescale > 0 {
1602 ((entry.sample_delta as u64) * 1000) / (timescale as u64)
1603 } else {
1604 0
1605 };
1606 return Ok(duration_ms as u32);
1607 }
1608 current_sample += entry.sample_count as usize;
1609 }
1610 Ok(0)
1611 }
1612
1613 fn calculate_sample_location(&self, st: &SampleTable, index: usize) -> Result<(u64, u32)> {
1615 let sample_size = *st
1616 .sample_sizes
1617 .get(index)
1618 .ok_or(Error::InvalidData("sample index out of bounds"))?;
1619
1620 let mut current_sample = 0;
1621 for (chunk_map_idx, entry) in st.sample_to_chunk.iter().enumerate() {
1622 let next_first_chunk = st
1623 .sample_to_chunk
1624 .get(chunk_map_idx + 1)
1625 .map(|e| e.first_chunk)
1626 .unwrap_or(u32::MAX);
1627
1628 for chunk_idx in entry.first_chunk..next_first_chunk {
1629 if chunk_idx == 0 || (chunk_idx as usize) > st.chunk_offsets.len() {
1630 break;
1631 }
1632
1633 let chunk_offset = st.chunk_offsets[(chunk_idx - 1) as usize];
1634
1635 for sample_in_chunk in 0..entry.samples_per_chunk {
1636 if current_sample == index {
1637 let mut offset_in_chunk = 0u64;
1638 for s in 0..sample_in_chunk {
1639 let prev_idx = current_sample.saturating_sub((sample_in_chunk - s) as usize);
1640 if let Some(&prev_size) = st.sample_sizes.get(prev_idx) {
1641 offset_in_chunk += prev_size as u64;
1642 }
1643 }
1644
1645 return Ok((chunk_offset + offset_in_chunk, sample_size));
1646 }
1647 current_sample += 1;
1648 }
1649 }
1650 }
1651
1652 Err(Error::InvalidData("sample not found in chunk table"))
1653 }
1654
1655 pub fn primary_data(&self) -> Result<Cow<'_, [u8]>> {
1663 self.resolve_item(&self.primary)
1664 }
1665
1666 pub fn alpha_data(&self) -> Option<Result<Cow<'_, [u8]>>> {
1668 self.alpha.as_ref().map(|item| self.resolve_item(item))
1669 }
1670
1671 pub fn tile_data(&self, index: usize) -> Result<Cow<'_, [u8]>> {
1673 let item = self.tiles.get(index)
1674 .ok_or(Error::InvalidData("tile index out of bounds"))?;
1675 self.resolve_item(item)
1676 }
1677
1678 pub fn frame(&self, index: usize) -> Result<FrameRef<'_>> {
1680 self.resolve_frame(index)
1681 }
1682
1683 pub fn frames(&self) -> FrameIterator<'_> {
1685 let count = self
1686 .animation_info()
1687 .map(|info| info.frame_count)
1688 .unwrap_or(0);
1689 FrameIterator { parser: self, index: 0, count }
1690 }
1691
1692 pub fn animation_info(&self) -> Option<AnimationInfo> {
1698 self.animation_data.as_ref().map(|data| AnimationInfo {
1699 frame_count: data.sample_table.sample_sizes.len(),
1700 loop_count: data.loop_count,
1701 has_alpha: data.alpha_sample_table.is_some(),
1702 timescale: data.media_timescale,
1703 })
1704 }
1705
1706 pub fn grid_config(&self) -> Option<&GridConfig> {
1708 self.grid_config.as_ref()
1709 }
1710
1711 pub fn grid_tile_count(&self) -> usize {
1713 self.tiles.len()
1714 }
1715
1716 pub fn premultiplied_alpha(&self) -> bool {
1718 self.premultiplied_alpha
1719 }
1720
1721 pub fn av1_config(&self) -> Option<&AV1Config> {
1725 self.av1_config.as_ref()
1726 }
1727
1728 pub fn color_info(&self) -> Option<&ColorInformation> {
1734 self.color_info.as_ref()
1735 }
1736
1737 pub fn rotation(&self) -> Option<&ImageRotation> {
1739 self.rotation.as_ref()
1740 }
1741
1742 pub fn mirror(&self) -> Option<&ImageMirror> {
1744 self.mirror.as_ref()
1745 }
1746
1747 pub fn clean_aperture(&self) -> Option<&CleanAperture> {
1749 self.clean_aperture.as_ref()
1750 }
1751
1752 pub fn pixel_aspect_ratio(&self) -> Option<&PixelAspectRatio> {
1754 self.pixel_aspect_ratio.as_ref()
1755 }
1756
1757 pub fn content_light_level(&self) -> Option<&ContentLightLevel> {
1759 self.content_light_level.as_ref()
1760 }
1761
1762 pub fn mastering_display(&self) -> Option<&MasteringDisplayColourVolume> {
1764 self.mastering_display.as_ref()
1765 }
1766
1767 pub fn content_colour_volume(&self) -> Option<&ContentColourVolume> {
1769 self.content_colour_volume.as_ref()
1770 }
1771
1772 pub fn ambient_viewing(&self) -> Option<&AmbientViewingEnvironment> {
1774 self.ambient_viewing.as_ref()
1775 }
1776
1777 pub fn operating_point(&self) -> Option<&OperatingPointSelector> {
1779 self.operating_point.as_ref()
1780 }
1781
1782 pub fn layer_selector(&self) -> Option<&LayerSelector> {
1784 self.layer_selector.as_ref()
1785 }
1786
1787 pub fn layered_image_indexing(&self) -> Option<&AV1LayeredImageIndexing> {
1789 self.layered_image_indexing.as_ref()
1790 }
1791
1792 pub fn exif(&self) -> Option<Result<Cow<'_, [u8]>>> {
1796 self.exif_item.as_ref().map(|item| {
1797 let raw = self.resolve_item(item)?;
1798 if raw.len() <= 4 {
1800 return Err(Error::InvalidData("EXIF item too short"));
1801 }
1802 let offset = u32::from_be_bytes([raw[0], raw[1], raw[2], raw[3]]) as usize;
1803 let start = 4 + offset;
1804 if start >= raw.len() {
1805 return Err(Error::InvalidData("EXIF offset exceeds item size"));
1806 }
1807 Ok(Cow::Owned(raw[start..].to_vec()))
1808 })
1809 }
1810
1811 pub fn xmp(&self) -> Option<Result<Cow<'_, [u8]>>> {
1815 self.xmp_item.as_ref().map(|item| self.resolve_item(item))
1816 }
1817
1818 pub fn major_brand(&self) -> &[u8; 4] {
1820 &self.major_brand
1821 }
1822
1823 pub fn compatible_brands(&self) -> &[[u8; 4]] {
1825 &self.compatible_brands
1826 }
1827
1828 pub fn primary_metadata(&self) -> Result<AV1Metadata> {
1830 let data = self.primary_data()?;
1831 AV1Metadata::parse_av1_bitstream(&data)
1832 }
1833
1834 pub fn alpha_metadata(&self) -> Option<Result<AV1Metadata>> {
1836 self.alpha.as_ref().map(|item| {
1837 let data = self.resolve_item(item)?;
1838 AV1Metadata::parse_av1_bitstream(&data)
1839 })
1840 }
1841
1842 #[cfg(feature = "eager")]
1851 #[deprecated(since = "1.5.0", note = "Use AvifParser methods directly instead of converting to AvifData")]
1852 #[allow(deprecated)]
1853 pub fn to_avif_data(&self) -> Result<AvifData> {
1854 let primary_data = self.primary_data()?;
1855 let mut primary_item = TryVec::new();
1856 primary_item.extend_from_slice(&primary_data)?;
1857
1858 let alpha_item = match self.alpha_data() {
1859 Some(Ok(data)) => {
1860 let mut v = TryVec::new();
1861 v.extend_from_slice(&data)?;
1862 Some(v)
1863 }
1864 Some(Err(e)) => return Err(e),
1865 None => None,
1866 };
1867
1868 let mut grid_tiles = TryVec::new();
1869 for i in 0..self.grid_tile_count() {
1870 let data = self.tile_data(i)?;
1871 let mut v = TryVec::new();
1872 v.extend_from_slice(&data)?;
1873 grid_tiles.push(v)?;
1874 }
1875
1876 let animation = if let Some(info) = self.animation_info() {
1877 let mut frames = TryVec::new();
1878 for i in 0..info.frame_count {
1879 let frame_ref = self.frame(i)?;
1880 let mut data = TryVec::new();
1881 data.extend_from_slice(&frame_ref.data)?;
1882 frames.push(AnimationFrame { data, duration_ms: frame_ref.duration_ms })?;
1883 }
1884 Some(AnimationConfig {
1885 loop_count: info.loop_count,
1886 frames,
1887 })
1888 } else {
1889 None
1890 };
1891
1892 Ok(AvifData {
1893 primary_item,
1894 alpha_item,
1895 premultiplied_alpha: self.premultiplied_alpha,
1896 grid_config: self.grid_config.clone(),
1897 grid_tiles,
1898 animation,
1899 av1_config: self.av1_config.clone(),
1900 color_info: self.color_info.clone(),
1901 rotation: self.rotation,
1902 mirror: self.mirror,
1903 clean_aperture: self.clean_aperture,
1904 pixel_aspect_ratio: self.pixel_aspect_ratio,
1905 content_light_level: self.content_light_level,
1906 mastering_display: self.mastering_display,
1907 content_colour_volume: self.content_colour_volume,
1908 ambient_viewing: self.ambient_viewing,
1909 operating_point: self.operating_point,
1910 layer_selector: self.layer_selector,
1911 layered_image_indexing: self.layered_image_indexing,
1912 exif: self.exif().and_then(|r| r.ok()).map(|c| {
1913 let mut v = TryVec::new();
1914 let _ = v.extend_from_slice(&c);
1915 v
1916 }),
1917 xmp: self.xmp().and_then(|r| r.ok()).map(|c| {
1918 let mut v = TryVec::new();
1919 let _ = v.extend_from_slice(&c);
1920 v
1921 }),
1922 major_brand: self.major_brand,
1923 compatible_brands: self.compatible_brands.clone(),
1924 })
1925 }
1926}
1927
1928pub struct FrameIterator<'a> {
1932 parser: &'a AvifParser<'a>,
1933 index: usize,
1934 count: usize,
1935}
1936
1937impl<'a> Iterator for FrameIterator<'a> {
1938 type Item = Result<FrameRef<'a>>;
1939
1940 fn next(&mut self) -> Option<Self::Item> {
1941 if self.index >= self.count {
1942 return None;
1943 }
1944 let result = self.parser.frame(self.index);
1945 self.index += 1;
1946 Some(result)
1947 }
1948
1949 fn size_hint(&self) -> (usize, Option<usize>) {
1950 let remaining = self.count.saturating_sub(self.index);
1951 (remaining, Some(remaining))
1952 }
1953}
1954
1955impl ExactSizeIterator for FrameIterator<'_> {
1956 fn len(&self) -> usize {
1957 self.count.saturating_sub(self.index)
1958 }
1959}
1960
1961struct AvifInternalMeta {
1962 item_references: TryVec<SingleItemTypeReferenceBox>,
1963 properties: TryVec<AssociatedProperty>,
1964 primary_item_id: u32,
1965 iloc_items: TryVec<ItemLocationBoxItem>,
1966 item_infos: TryVec<ItemInfoEntry>,
1967 idat: Option<TryVec<u8>>,
1968}
1969
1970#[cfg(feature = "eager")]
1973struct MediaDataBox {
1974 offset: u64,
1976 data: TryVec<u8>,
1977}
1978
1979#[cfg(feature = "eager")]
1980impl MediaDataBox {
1981 fn contains_extent(&self, extent: &ExtentRange) -> bool {
1985 if self.offset <= extent.start() {
1986 let start_offset = extent.start() - self.offset;
1987 start_offset < self.data.len().to_u64()
1988 } else {
1989 false
1990 }
1991 }
1992
1993 fn matches_extent(&self, extent: &ExtentRange) -> bool {
1995 if self.offset == extent.start() {
1996 match extent {
1997 ExtentRange::WithLength(range) => {
1998 if let Some(end) = self.offset.checked_add(self.data.len().to_u64()) {
1999 end == range.end
2000 } else {
2001 false
2002 }
2003 },
2004 ExtentRange::ToEnd(_) => true,
2005 }
2006 } else {
2007 false
2008 }
2009 }
2010
2011 fn read_extent(&self, extent: &ExtentRange, buf: &mut TryVec<u8>) -> Result<()> {
2014 let start_offset = extent
2015 .start()
2016 .checked_sub(self.offset)
2017 .ok_or(Error::InvalidData("mdat does not contain extent"))?;
2018 let slice = match extent {
2019 ExtentRange::WithLength(range) => {
2020 let range_len = range
2021 .end
2022 .checked_sub(range.start)
2023 .ok_or(Error::InvalidData("range start > end"))?;
2024 let end = start_offset
2025 .checked_add(range_len)
2026 .ok_or(Error::InvalidData("extent end overflow"))?;
2027 self.data.get(start_offset.try_into()?..end.try_into()?)
2028 },
2029 ExtentRange::ToEnd(_) => self.data.get(start_offset.try_into()?..),
2030 };
2031 let slice = slice.ok_or(Error::InvalidData("extent crosses box boundary"))?;
2032 buf.extend_from_slice(slice)?;
2033 Ok(())
2034 }
2035
2036}
2037
2038#[derive(Debug)]
2042struct ItemInfoEntry {
2043 item_id: u32,
2044 item_type: FourCC,
2045}
2046
2047#[derive(Debug)]
2049struct SingleItemTypeReferenceBox {
2050 item_type: FourCC,
2051 from_item_id: u32,
2052 to_item_id: u32,
2053 reference_index: u16,
2056}
2057
2058#[derive(Debug)]
2061enum IlocFieldSize {
2062 Zero,
2063 Four,
2064 Eight,
2065}
2066
2067impl IlocFieldSize {
2068 const fn to_bits(&self) -> u8 {
2069 match self {
2070 Self::Zero => 0,
2071 Self::Four => 32,
2072 Self::Eight => 64,
2073 }
2074 }
2075}
2076
2077impl TryFrom<u8> for IlocFieldSize {
2078 type Error = Error;
2079
2080 fn try_from(value: u8) -> Result<Self> {
2081 match value {
2082 0 => Ok(Self::Zero),
2083 4 => Ok(Self::Four),
2084 8 => Ok(Self::Eight),
2085 _ => Err(Error::InvalidData("value must be in the set {0, 4, 8}")),
2086 }
2087 }
2088}
2089
2090#[derive(PartialEq)]
2091enum IlocVersion {
2092 Zero,
2093 One,
2094 Two,
2095}
2096
2097impl TryFrom<u8> for IlocVersion {
2098 type Error = Error;
2099
2100 fn try_from(value: u8) -> Result<Self> {
2101 match value {
2102 0 => Ok(Self::Zero),
2103 1 => Ok(Self::One),
2104 2 => Ok(Self::Two),
2105 _ => Err(Error::Unsupported("unsupported version in 'iloc' box")),
2106 }
2107 }
2108}
2109
2110#[derive(Debug)]
2115struct ItemLocationBoxItem {
2116 item_id: u32,
2117 construction_method: ConstructionMethod,
2118 extents: TryVec<ItemLocationBoxExtent>,
2120}
2121
2122#[derive(Clone, Copy, Debug, PartialEq)]
2123enum ConstructionMethod {
2124 File,
2125 Idat,
2126 #[allow(dead_code)] Item,
2128}
2129
2130#[derive(Clone, Debug)]
2133struct ItemLocationBoxExtent {
2134 extent_range: ExtentRange,
2135}
2136
2137#[derive(Clone, Debug)]
2138enum ExtentRange {
2139 WithLength(Range<u64>),
2140 ToEnd(RangeFrom<u64>),
2141}
2142
2143impl ExtentRange {
2144 const fn start(&self) -> u64 {
2145 match self {
2146 Self::WithLength(r) => r.start,
2147 Self::ToEnd(r) => r.start,
2148 }
2149 }
2150}
2151
2152struct BMFFBox<'a, T> {
2154 head: BoxHeader,
2155 content: Take<&'a mut T>,
2156}
2157
2158impl<T: Read> BMFFBox<'_, T> {
2159 fn read_into_try_vec(&mut self) -> std::io::Result<TryVec<u8>> {
2160 let limit = self.content.limit();
2161 let mut vec = if limit >= u64::MAX - BoxHeader::MIN_LARGE_SIZE {
2165 std::vec::Vec::new()
2167 } else {
2168 let mut v = std::vec::Vec::new();
2169 v.try_reserve_exact(limit as usize)
2170 .map_err(|_| std::io::ErrorKind::OutOfMemory)?;
2171 v
2172 };
2173 self.content.read_to_end(&mut vec)?; Ok(vec.into())
2175 }
2176}
2177
2178#[test]
2179fn box_read_to_end() {
2180 let tmp = &mut b"1234567890".as_slice();
2181 let mut src = BMFFBox {
2182 head: BoxHeader { name: BoxType::FileTypeBox, size: 5, offset: 0, uuid: None },
2183 content: <_ as Read>::take(tmp, 5),
2184 };
2185 let buf = src.read_into_try_vec().unwrap();
2186 assert_eq!(buf.len(), 5);
2187 assert_eq!(buf, b"12345".as_ref());
2188}
2189
2190#[test]
2191fn box_read_to_end_oom() {
2192 let tmp = &mut b"1234567890".as_slice();
2193 let mut src = BMFFBox {
2194 head: BoxHeader { name: BoxType::FileTypeBox, size: 5, offset: 0, uuid: None },
2195 content: <_ as Read>::take(tmp, u64::MAX / 2),
2197 };
2198 assert!(src.read_into_try_vec().is_err());
2199}
2200
2201struct BoxIter<'a, T> {
2202 src: &'a mut T,
2203}
2204
2205impl<T: Read> BoxIter<'_, T> {
2206 fn new(src: &mut T) -> BoxIter<'_, T> {
2207 BoxIter { src }
2208 }
2209
2210 fn next_box(&mut self) -> Result<Option<BMFFBox<'_, T>>> {
2211 let r = read_box_header(self.src);
2212 match r {
2213 Ok(h) => Ok(Some(BMFFBox {
2214 head: h,
2215 content: self.src.take(h.size - h.offset),
2216 })),
2217 Err(Error::UnexpectedEOF) => Ok(None),
2218 Err(e) => Err(e),
2219 }
2220 }
2221}
2222
2223impl<T: Read> Read for BMFFBox<'_, T> {
2224 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2225 self.content.read(buf)
2226 }
2227}
2228
2229impl<T: Offset> Offset for BMFFBox<'_, T> {
2230 fn offset(&self) -> u64 {
2231 self.content.get_ref().offset()
2232 }
2233}
2234
2235impl<T: Read> BMFFBox<'_, T> {
2236 fn bytes_left(&self) -> u64 {
2237 self.content.limit()
2238 }
2239
2240 const fn get_header(&self) -> &BoxHeader {
2241 &self.head
2242 }
2243
2244 fn box_iter(&mut self) -> BoxIter<'_, Self> {
2245 BoxIter::new(self)
2246 }
2247}
2248
2249impl<T> Drop for BMFFBox<'_, T> {
2250 fn drop(&mut self) {
2251 if self.content.limit() > 0 {
2252 let name: FourCC = From::from(self.head.name);
2253 debug!("Dropping {} bytes in '{}'", self.content.limit(), name);
2254 }
2255 }
2256}
2257
2258fn read_box_header<T: ReadBytesExt>(src: &mut T) -> Result<BoxHeader> {
2267 let size32 = be_u32(src)?;
2268 let name = BoxType::from(be_u32(src)?);
2269 let size = match size32 {
2270 0 => {
2272 u64::MAX
2274 },
2275 1 => {
2276 let size64 = be_u64(src)?;
2277 if size64 < BoxHeader::MIN_LARGE_SIZE {
2278 return Err(Error::InvalidData("malformed wide size"));
2279 }
2280 size64
2281 },
2282 _ => {
2283 if u64::from(size32) < BoxHeader::MIN_SIZE {
2284 return Err(Error::InvalidData("malformed size"));
2285 }
2286 u64::from(size32)
2287 },
2288 };
2289 let mut offset = match size32 {
2290 1 => BoxHeader::MIN_LARGE_SIZE,
2291 _ => BoxHeader::MIN_SIZE,
2292 };
2293 let uuid = if name == BoxType::UuidBox {
2294 if size >= offset + 16 {
2295 let mut buffer = [0u8; 16];
2296 let count = src.read(&mut buffer)?;
2297 offset += count.to_u64();
2298 if count == 16 {
2299 Some(buffer)
2300 } else {
2301 debug!("malformed uuid (short read), skipping");
2302 None
2303 }
2304 } else {
2305 debug!("malformed uuid, skipping");
2306 None
2307 }
2308 } else {
2309 None
2310 };
2311 assert!(offset <= size);
2312 Ok(BoxHeader { name, size, offset, uuid })
2313}
2314
2315fn read_fullbox_extra<T: ReadBytesExt>(src: &mut T) -> Result<(u8, u32)> {
2317 let version = src.read_u8()?;
2318 let flags_a = src.read_u8()?;
2319 let flags_b = src.read_u8()?;
2320 let flags_c = src.read_u8()?;
2321 Ok((
2322 version,
2323 u32::from(flags_a) << 16 | u32::from(flags_b) << 8 | u32::from(flags_c),
2324 ))
2325}
2326
2327fn read_fullbox_version_no_flags<T: ReadBytesExt>(src: &mut T, options: &ParseOptions) -> Result<u8> {
2329 let (version, flags) = read_fullbox_extra(src)?;
2330
2331 if flags != 0 && !options.lenient {
2332 return Err(Error::Unsupported("expected flags to be 0"));
2333 }
2334
2335 Ok(version)
2336}
2337
2338fn skip_box_content<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<()> {
2340 let to_skip = {
2342 let header = src.get_header();
2343 debug!("{header:?} (skipped)");
2344 header
2345 .size
2346 .checked_sub(header.offset)
2347 .ok_or(Error::InvalidData("header offset > size"))?
2348 };
2349 assert_eq!(to_skip, src.bytes_left());
2350 skip(src, to_skip)
2351}
2352
2353fn skip_box_remain<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<()> {
2355 let remain = {
2356 let header = src.get_header();
2357 let len = src.bytes_left();
2358 debug!("remain {len} (skipped) in {header:?}");
2359 len
2360 };
2361 skip(src, remain)
2362}
2363
2364struct ResourceTracker<'a> {
2365 config: &'a DecodeConfig,
2366 #[cfg(feature = "eager")]
2367 current_memory: u64,
2368 #[cfg(feature = "eager")]
2369 peak_memory: u64,
2370}
2371
2372impl<'a> ResourceTracker<'a> {
2373 fn new(config: &'a DecodeConfig) -> Self {
2374 Self {
2375 config,
2376 #[cfg(feature = "eager")]
2377 current_memory: 0,
2378 #[cfg(feature = "eager")]
2379 peak_memory: 0,
2380 }
2381 }
2382
2383 #[cfg(feature = "eager")]
2384 fn reserve(&mut self, bytes: u64) -> Result<()> {
2385 self.current_memory = self.current_memory.saturating_add(bytes);
2386 self.peak_memory = self.peak_memory.max(self.current_memory);
2387
2388 if let Some(limit) = self.config.peak_memory_limit
2389 && self.peak_memory > limit {
2390 return Err(Error::ResourceLimitExceeded("peak memory limit exceeded"));
2391 }
2392
2393 Ok(())
2394 }
2395
2396 #[cfg(feature = "eager")]
2397 fn release(&mut self, bytes: u64) {
2398 self.current_memory = self.current_memory.saturating_sub(bytes);
2399 }
2400
2401 #[cfg(feature = "eager")]
2402 fn validate_total_megapixels(&self, width: u32, height: u32) -> Result<()> {
2403 if let Some(limit) = self.config.total_megapixels_limit {
2404 let megapixels = (width as u64)
2405 .checked_mul(height as u64)
2406 .ok_or(Error::InvalidData("dimension overflow"))?
2407 / 1_000_000;
2408
2409 if megapixels > limit as u64 {
2410 return Err(Error::ResourceLimitExceeded("total megapixels limit exceeded"));
2411 }
2412 }
2413
2414 Ok(())
2415 }
2416
2417 fn validate_animation_frames(&self, count: u32) -> Result<()> {
2418 if let Some(limit) = self.config.max_animation_frames
2419 && count > limit {
2420 return Err(Error::ResourceLimitExceeded("animation frame count limit exceeded"));
2421 }
2422
2423 Ok(())
2424 }
2425
2426 fn validate_grid_tiles(&self, count: u32) -> Result<()> {
2427 if let Some(limit) = self.config.max_grid_tiles
2428 && count > limit {
2429 return Err(Error::ResourceLimitExceeded("grid tile count limit exceeded"));
2430 }
2431
2432 Ok(())
2433 }
2434}
2435
2436#[cfg(feature = "eager")]
2447#[deprecated(since = "1.5.0", note = "Use `AvifParser::from_reader_with_config()` instead")]
2448#[allow(deprecated)]
2449pub fn read_avif_with_config<T: Read>(
2450 f: &mut T,
2451 config: &DecodeConfig,
2452 stop: &dyn Stop,
2453) -> Result<AvifData> {
2454 let mut tracker = ResourceTracker::new(config);
2455 let mut f = OffsetReader::new(f);
2456
2457 let mut iter = BoxIter::new(&mut f);
2458
2459 let (major_brand, compatible_brands) = if let Some(mut b) = iter.next_box()? {
2461 if b.head.name == BoxType::FileTypeBox {
2462 let ftyp = read_ftyp(&mut b)?;
2463 if ftyp.major_brand != b"avif" && ftyp.major_brand != b"avis" {
2465 warn!("major_brand: {}", ftyp.major_brand);
2466 return Err(Error::InvalidData("ftyp must be 'avif' or 'avis'"));
2467 }
2468 let major = ftyp.major_brand.value;
2469 let compat = ftyp.compatible_brands.iter().map(|b| b.value).collect();
2470 (major, compat)
2471 } else {
2472 return Err(Error::InvalidData("'ftyp' box must occur first"));
2473 }
2474 } else {
2475 return Err(Error::InvalidData("'ftyp' box must occur first"));
2476 };
2477
2478 let mut meta = None;
2479 let mut mdats = TryVec::new();
2480 let mut animation_data: Option<ParsedAnimationData> = None;
2481
2482 let parse_opts = ParseOptions { lenient: config.lenient };
2483
2484 while let Some(mut b) = iter.next_box()? {
2485 stop.check()?;
2486
2487 match b.head.name {
2488 BoxType::MetadataBox => {
2489 if meta.is_some() {
2490 return Err(Error::InvalidData("There should be zero or one meta boxes per ISO 14496-12:2015 § 8.11.1.1"));
2491 }
2492 meta = Some(read_avif_meta(&mut b, &parse_opts)?);
2493 },
2494 BoxType::MovieBox => {
2495 let tracks = read_moov(&mut b)?;
2496 if !tracks.is_empty() {
2497 animation_data = Some(associate_tracks(tracks)?);
2498 }
2499 },
2500 BoxType::MediaDataBox => {
2501 if b.bytes_left() > 0 {
2502 let offset = b.offset();
2503 let size = b.bytes_left();
2504 tracker.reserve(size)?;
2505 let data = b.read_into_try_vec()?;
2506 tracker.release(size);
2507 mdats.push(MediaDataBox { offset, data })?;
2508 }
2509 },
2510 _ => skip_box_content(&mut b)?,
2511 }
2512
2513 check_parser_state(&b.head, &b.content)?;
2514 }
2515
2516 if meta.is_none() && animation_data.is_none() {
2518 return Err(Error::InvalidData("missing meta"));
2519 }
2520 let Some(meta) = meta else {
2521 return Ok(AvifData {
2523 ..Default::default()
2524 });
2525 };
2526
2527 let is_grid = meta
2529 .item_infos
2530 .iter()
2531 .find(|x| x.item_id == meta.primary_item_id)
2532 .is_some_and(|info| {
2533 let is_g = info.item_type == b"grid";
2534 if is_g {
2535 log::debug!("Grid image detected: primary_item_id={}", meta.primary_item_id);
2536 }
2537 is_g
2538 });
2539
2540 let mut grid_config = if is_grid {
2542 meta.properties
2543 .iter()
2544 .find(|prop| {
2545 prop.item_id == meta.primary_item_id
2546 && matches!(prop.property, ItemProperty::ImageGrid(_))
2547 })
2548 .and_then(|prop| match &prop.property {
2549 ItemProperty::ImageGrid(config) => {
2550 log::debug!("Grid: found explicit ImageGrid property: {:?}", config);
2551 Some(config.clone())
2552 },
2553 _ => None,
2554 })
2555 } else {
2556 None
2557 };
2558
2559 let tile_item_ids: TryVec<u32> = if is_grid {
2561 let mut tiles_with_index: TryVec<(u32, u16)> = TryVec::new();
2563 for iref in meta.item_references.iter() {
2564 if iref.from_item_id == meta.primary_item_id && iref.item_type == b"dimg" {
2566 tiles_with_index.push((iref.to_item_id, iref.reference_index))?;
2567 }
2568 }
2569
2570 tracker.validate_grid_tiles(tiles_with_index.len() as u32)?;
2572
2573 tiles_with_index.sort_by_key(|&(_, idx)| idx);
2575
2576 let mut ids = TryVec::new();
2578 for (tile_id, _) in tiles_with_index.iter() {
2579 ids.push(*tile_id)?;
2580 }
2581
2582 if grid_config.is_none() && !ids.is_empty() {
2586 let grid_dims = meta.properties.iter()
2588 .find(|p| p.item_id == meta.primary_item_id)
2589 .and_then(|p| match &p.property {
2590 ItemProperty::ImageSpatialExtents(e) => Some(e),
2591 _ => None,
2592 });
2593
2594 let tile_dims = ids.first().and_then(|&tile_id| {
2595 meta.properties.iter()
2596 .find(|p| p.item_id == tile_id)
2597 .and_then(|p| match &p.property {
2598 ItemProperty::ImageSpatialExtents(e) => Some(e),
2599 _ => None,
2600 })
2601 });
2602
2603 if let (Some(grid), Some(tile)) = (grid_dims, tile_dims) {
2604 tracker.validate_total_megapixels(grid.width, grid.height)?;
2606
2607 if tile.width == 0 || tile.height == 0 {
2609 log::warn!("Grid: tile has zero dimensions, using fallback");
2610 } else if grid.width % tile.width == 0 && grid.height % tile.height == 0 {
2611 let columns = grid.width / tile.width;
2613 let rows = grid.height / tile.height;
2614
2615 if columns > 255 || rows > 255 {
2617 log::warn!("Grid: calculated dimensions {}×{} exceed 255, using fallback", rows, columns);
2618 } else {
2619 log::debug!("Grid: calculated {}×{} layout from ispe dimensions", rows, columns);
2620 grid_config = Some(GridConfig {
2621 rows: rows as u8,
2622 columns: columns as u8,
2623 output_width: grid.width,
2624 output_height: grid.height,
2625 });
2626 }
2627 } else {
2628 log::warn!("Grid: dimension mismatch - grid {}×{} not evenly divisible by tile {}×{}, using fallback",
2629 grid.width, grid.height, tile.width, tile.height);
2630 }
2631 }
2632
2633 if grid_config.is_none() {
2635 log::debug!("Grid: using fallback {}×1 layout inference", ids.len());
2636 grid_config = Some(GridConfig {
2637 rows: ids.len() as u8, columns: 1, output_width: 0, output_height: 0, });
2642 }
2643 }
2644
2645 ids
2646 } else {
2647 TryVec::new()
2648 };
2649
2650 let alpha_item_id = meta
2651 .item_references
2652 .iter()
2653 .filter(|iref| {
2655 iref.to_item_id == meta.primary_item_id
2656 && iref.from_item_id != meta.primary_item_id
2657 && iref.item_type == b"auxl"
2658 })
2659 .map(|iref| iref.from_item_id)
2660 .find(|&item_id| {
2662 meta.properties.iter().any(|prop| {
2663 prop.item_id == item_id
2664 && match &prop.property {
2665 ItemProperty::AuxiliaryType(urn) => {
2666 urn.type_subtype().0 == b"urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"
2667 }
2668 _ => false,
2669 }
2670 })
2671 });
2672
2673 macro_rules! find_prop {
2675 ($variant:ident) => {
2676 meta.properties.iter().find_map(|p| {
2677 if p.item_id == meta.primary_item_id {
2678 match &p.property {
2679 ItemProperty::$variant(c) => Some(c.clone()),
2680 _ => None,
2681 }
2682 } else {
2683 None
2684 }
2685 })
2686 };
2687 }
2688
2689 let av1_config = find_prop!(AV1Config);
2690 let color_info = find_prop!(ColorInformation);
2691 let rotation = find_prop!(Rotation);
2692 let mirror = find_prop!(Mirror);
2693 let clean_aperture = find_prop!(CleanAperture);
2694 let pixel_aspect_ratio = find_prop!(PixelAspectRatio);
2695 let content_light_level = find_prop!(ContentLightLevel);
2696 let mastering_display = find_prop!(MasteringDisplayColourVolume);
2697 let content_colour_volume = find_prop!(ContentColourVolume);
2698 let ambient_viewing = find_prop!(AmbientViewingEnvironment);
2699 let operating_point = find_prop!(OperatingPointSelector);
2700 let layer_selector = find_prop!(LayerSelector);
2701 let layered_image_indexing = find_prop!(AV1LayeredImageIndexing);
2702
2703 let mut context = AvifData {
2704 premultiplied_alpha: alpha_item_id.is_some_and(|alpha_item_id| {
2705 meta.item_references.iter().any(|iref| {
2706 iref.from_item_id == meta.primary_item_id
2707 && iref.to_item_id == alpha_item_id
2708 && iref.item_type == b"prem"
2709 })
2710 }),
2711 av1_config,
2712 color_info,
2713 rotation,
2714 mirror,
2715 clean_aperture,
2716 pixel_aspect_ratio,
2717 content_light_level,
2718 mastering_display,
2719 content_colour_volume,
2720 ambient_viewing,
2721 operating_point,
2722 layer_selector,
2723 layered_image_indexing,
2724 major_brand,
2725 compatible_brands,
2726 ..Default::default()
2727 };
2728
2729 let mut extract_item_data = |loc: &ItemLocationBoxItem, buf: &mut TryVec<u8>| -> Result<()> {
2731 match loc.construction_method {
2732 ConstructionMethod::File => {
2733 for extent in loc.extents.iter() {
2734 let mut found = false;
2735 for mdat in mdats.iter_mut() {
2736 if mdat.matches_extent(&extent.extent_range) {
2737 buf.append(&mut mdat.data)?;
2738 found = true;
2739 break;
2740 } else if mdat.contains_extent(&extent.extent_range) {
2741 mdat.read_extent(&extent.extent_range, buf)?;
2742 found = true;
2743 break;
2744 }
2745 }
2746 if !found {
2747 return Err(Error::InvalidData("iloc contains an extent that is not in mdat"));
2748 }
2749 }
2750 Ok(())
2751 },
2752 ConstructionMethod::Idat => {
2753 let idat_data = meta.idat.as_ref().ok_or(Error::InvalidData("idat box missing but construction_method is Idat"))?;
2754 for extent in loc.extents.iter() {
2755 match &extent.extent_range {
2756 ExtentRange::WithLength(range) => {
2757 let start = usize::try_from(range.start).map_err(|_| Error::InvalidData("extent start too large"))?;
2758 let end = usize::try_from(range.end).map_err(|_| Error::InvalidData("extent end too large"))?;
2759 if end > idat_data.len() {
2760 return Err(Error::InvalidData("extent exceeds idat size"));
2761 }
2762 buf.extend_from_slice(&idat_data[start..end]).map_err(|_| Error::OutOfMemory)?;
2763 },
2764 ExtentRange::ToEnd(range) => {
2765 let start = usize::try_from(range.start).map_err(|_| Error::InvalidData("extent start too large"))?;
2766 if start >= idat_data.len() {
2767 return Err(Error::InvalidData("extent start exceeds idat size"));
2768 }
2769 buf.extend_from_slice(&idat_data[start..]).map_err(|_| Error::OutOfMemory)?;
2770 },
2771 }
2772 }
2773 Ok(())
2774 },
2775 ConstructionMethod::Item => {
2776 Err(Error::Unsupported("construction_method 'item' not supported"))
2777 },
2778 }
2779 };
2780
2781 if is_grid {
2784 for (idx, &tile_id) in tile_item_ids.iter().enumerate() {
2786 if idx % 16 == 0 {
2787 stop.check()?;
2788 }
2789
2790 let mut tile_data = TryVec::new();
2791
2792 if let Some(loc) = meta.iloc_items.iter().find(|loc| loc.item_id == tile_id) {
2793 extract_item_data(loc, &mut tile_data)?;
2794 } else {
2795 return Err(Error::InvalidData("grid tile not found in iloc"));
2796 }
2797
2798 context.grid_tiles.push(tile_data)?;
2799 }
2800
2801 context.grid_config = grid_config;
2803 } else {
2804 for loc in meta.iloc_items.iter() {
2806 let item_data = if loc.item_id == meta.primary_item_id {
2807 &mut context.primary_item
2808 } else if Some(loc.item_id) == alpha_item_id {
2809 context.alpha_item.get_or_insert_with(TryVec::new)
2810 } else {
2811 continue;
2812 };
2813
2814 extract_item_data(loc, item_data)?;
2815 }
2816 }
2817
2818 for iref in meta.item_references.iter() {
2820 if iref.to_item_id != meta.primary_item_id || iref.item_type != b"cdsc" {
2821 continue;
2822 }
2823 let desc_item_id = iref.from_item_id;
2824 let Some(info) = meta.item_infos.iter().find(|i| i.item_id == desc_item_id) else {
2825 continue;
2826 };
2827 if info.item_type == b"Exif" {
2828 if let Some(loc) = meta.iloc_items.iter().find(|l| l.item_id == desc_item_id) {
2829 let mut raw = TryVec::new();
2830 extract_item_data(loc, &mut raw)?;
2831 if raw.len() > 4 {
2833 let offset = u32::from_be_bytes([raw[0], raw[1], raw[2], raw[3]]) as usize;
2834 let start = 4 + offset;
2835 if start < raw.len() {
2836 let mut exif = TryVec::new();
2837 exif.extend_from_slice(&raw[start..])?;
2838 context.exif = Some(exif);
2839 }
2840 }
2841 }
2842 } else if info.item_type == b"mime"
2843 && let Some(loc) = meta.iloc_items.iter().find(|l| l.item_id == desc_item_id)
2844 {
2845 let mut xmp = TryVec::new();
2846 extract_item_data(loc, &mut xmp)?;
2847 context.xmp = Some(xmp);
2848 }
2849 }
2850
2851 if let Some(anim) = animation_data {
2853 let frame_count = anim.color_sample_table.sample_sizes.len() as u32;
2854 tracker.validate_animation_frames(frame_count)?;
2855
2856 log::debug!("Animation: extracting frames (media_timescale={})", anim.color_timescale);
2857 match extract_animation_frames(&anim.color_sample_table, anim.color_timescale, &mut mdats) {
2858 Ok(frames) => {
2859 if !frames.is_empty() {
2860 log::debug!("Animation: extracted {} frames", frames.len());
2861 context.animation = Some(AnimationConfig {
2862 loop_count: anim.loop_count,
2863 frames,
2864 });
2865 }
2866 }
2867 Err(e) => {
2868 log::warn!("Animation: failed to extract frames: {}", e);
2869 }
2870 }
2871 }
2872
2873 Ok(context)
2874}
2875
2876#[cfg(feature = "eager")]
2885#[deprecated(since = "1.5.0", note = "Use `AvifParser::from_reader_with_config()` with `DecodeConfig::lenient()` instead")]
2886#[allow(deprecated)]
2887pub fn read_avif_with_options<T: Read>(f: &mut T, options: &ParseOptions) -> Result<AvifData> {
2888 let config = DecodeConfig::unlimited().lenient(options.lenient);
2889 read_avif_with_config(f, &config, &Unstoppable)
2890}
2891
2892#[cfg(feature = "eager")]
2900#[deprecated(since = "1.5.0", note = "Use `AvifParser::from_reader()` instead")]
2901#[allow(deprecated)]
2902pub fn read_avif<T: Read>(f: &mut T) -> Result<AvifData> {
2903 read_avif_with_options(f, &ParseOptions::default())
2904}
2905
2906fn read_avif_meta<T: Read + Offset>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<AvifInternalMeta> {
2911 let version = read_fullbox_version_no_flags(src, options)?;
2912
2913 if version != 0 {
2914 return Err(Error::Unsupported("unsupported meta version"));
2915 }
2916
2917 let mut primary_item_id = None;
2918 let mut item_infos = None;
2919 let mut iloc_items = None;
2920 let mut item_references = TryVec::new();
2921 let mut properties = TryVec::new();
2922 let mut idat = None;
2923
2924 let mut iter = src.box_iter();
2925 while let Some(mut b) = iter.next_box()? {
2926 match b.head.name {
2927 BoxType::ItemInfoBox => {
2928 if item_infos.is_some() {
2929 return Err(Error::InvalidData("There should be zero or one iinf boxes per ISO 14496-12:2015 § 8.11.6.1"));
2930 }
2931 item_infos = Some(read_iinf(&mut b, options)?);
2932 },
2933 BoxType::ItemLocationBox => {
2934 if iloc_items.is_some() {
2935 return Err(Error::InvalidData("There should be zero or one iloc boxes per ISO 14496-12:2015 § 8.11.3.1"));
2936 }
2937 iloc_items = Some(read_iloc(&mut b, options)?);
2938 },
2939 BoxType::PrimaryItemBox => {
2940 if primary_item_id.is_some() {
2941 return Err(Error::InvalidData("There should be zero or one iloc boxes per ISO 14496-12:2015 § 8.11.4.1"));
2942 }
2943 primary_item_id = Some(read_pitm(&mut b, options)?);
2944 },
2945 BoxType::ImageReferenceBox => {
2946 item_references.append(&mut read_iref(&mut b, options)?)?;
2947 },
2948 BoxType::ImagePropertiesBox => {
2949 properties = read_iprp(&mut b, options)?;
2950 },
2951 BoxType::ItemDataBox => {
2952 if idat.is_some() {
2953 return Err(Error::InvalidData("There should be zero or one idat boxes"));
2954 }
2955 idat = Some(b.read_into_try_vec()?);
2956 },
2957 BoxType::HandlerBox => {
2958 let hdlr = read_hdlr(&mut b)?;
2959 if hdlr.handler_type != b"pict" {
2960 warn!("hdlr handler_type: {}", hdlr.handler_type);
2961 return Err(Error::InvalidData("meta handler_type must be 'pict' for AVIF"));
2962 }
2963 },
2964 _ => skip_box_content(&mut b)?,
2965 }
2966
2967 check_parser_state(&b.head, &b.content)?;
2968 }
2969
2970 let primary_item_id = primary_item_id.ok_or(Error::InvalidData("Required pitm box not present in meta box"))?;
2971
2972 let item_infos = item_infos.ok_or(Error::InvalidData("iinf missing"))?;
2973
2974 if let Some(item_info) = item_infos.iter().find(|x| x.item_id == primary_item_id) {
2975 if item_info.item_type != b"av01" && item_info.item_type != b"grid" {
2977 warn!("primary_item_id type: {}", item_info.item_type);
2978 return Err(Error::InvalidData("primary_item_id type is not av01 or grid"));
2979 }
2980 } else {
2981 return Err(Error::InvalidData("primary_item_id not present in iinf box"));
2982 }
2983
2984 Ok(AvifInternalMeta {
2985 properties,
2986 item_references,
2987 primary_item_id,
2988 iloc_items: iloc_items.ok_or(Error::InvalidData("iloc missing"))?,
2989 item_infos,
2990 idat,
2991 })
2992}
2993
2994fn read_hdlr<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<HandlerBox> {
2997 let (_version, _flags) = read_fullbox_extra(src)?;
2998 skip(src, 4)?;
3000 let handler_type = be_u32(src)?;
3002 skip_box_remain(src)?;
3004 Ok(HandlerBox {
3005 handler_type: FourCC::from(handler_type),
3006 })
3007}
3008
3009fn read_pitm<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<u32> {
3012 let version = read_fullbox_version_no_flags(src, options)?;
3013
3014 let item_id = match version {
3015 0 => be_u16(src)?.into(),
3016 1 => be_u32(src)?,
3017 _ => return Err(Error::Unsupported("unsupported pitm version")),
3018 };
3019
3020 Ok(item_id)
3021}
3022
3023fn read_iinf<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<ItemInfoEntry>> {
3026 let version = read_fullbox_version_no_flags(src, options)?;
3027
3028 match version {
3029 0 | 1 => (),
3030 _ => return Err(Error::Unsupported("unsupported iinf version")),
3031 }
3032
3033 let entry_count = if version == 0 {
3034 be_u16(src)?.to_usize()
3035 } else {
3036 be_u32(src)?.to_usize()
3037 };
3038 let mut item_infos = TryVec::with_capacity(entry_count)?;
3039
3040 let mut iter = src.box_iter();
3041 while let Some(mut b) = iter.next_box()? {
3042 if b.head.name != BoxType::ItemInfoEntry {
3043 return Err(Error::InvalidData("iinf box should contain only infe boxes"));
3044 }
3045
3046 item_infos.push(read_infe(&mut b)?)?;
3047
3048 check_parser_state(&b.head, &b.content)?;
3049 }
3050
3051 Ok(item_infos)
3052}
3053
3054fn read_infe<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ItemInfoEntry> {
3057 let (version, _) = read_fullbox_extra(src)?;
3060
3061 let item_id = match version {
3063 2 => be_u16(src)?.into(),
3064 3 => be_u32(src)?,
3065 _ => return Err(Error::Unsupported("unsupported version in 'infe' box")),
3066 };
3067
3068 let item_protection_index = be_u16(src)?;
3069
3070 if item_protection_index != 0 {
3071 return Err(Error::Unsupported("protected items (infe.item_protection_index != 0) are not supported"));
3072 }
3073
3074 let item_type = FourCC::from(be_u32(src)?);
3075 debug!("infe item_id {item_id} item_type: {item_type}");
3076
3077 skip_box_remain(src)?;
3079
3080 Ok(ItemInfoEntry { item_id, item_type })
3081}
3082
3083fn read_iref<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<SingleItemTypeReferenceBox>> {
3084 let mut item_references = TryVec::new();
3085 let version = read_fullbox_version_no_flags(src, options)?;
3086 if version > 1 {
3087 return Err(Error::Unsupported("iref version"));
3088 }
3089
3090 let mut iter = src.box_iter();
3091 while let Some(mut b) = iter.next_box()? {
3092 let from_item_id = if version == 0 {
3093 be_u16(&mut b)?.into()
3094 } else {
3095 be_u32(&mut b)?
3096 };
3097 let reference_count = be_u16(&mut b)?;
3098 for reference_index in 0..reference_count {
3099 let to_item_id = if version == 0 {
3100 be_u16(&mut b)?.into()
3101 } else {
3102 be_u32(&mut b)?
3103 };
3104 if from_item_id == to_item_id {
3105 return Err(Error::InvalidData("from_item_id and to_item_id must be different"));
3106 }
3107 item_references.push(SingleItemTypeReferenceBox {
3108 item_type: b.head.name.into(),
3109 from_item_id,
3110 to_item_id,
3111 reference_index,
3112 })?;
3113 }
3114 check_parser_state(&b.head, &b.content)?;
3115 }
3116 Ok(item_references)
3117}
3118
3119fn read_iprp<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<AssociatedProperty>> {
3120 let mut iter = src.box_iter();
3121 let mut properties = TryVec::new();
3122 let mut associations = TryVec::new();
3123
3124 while let Some(mut b) = iter.next_box()? {
3125 match b.head.name {
3126 BoxType::ItemPropertyContainerBox => {
3127 properties = read_ipco(&mut b, options)?;
3128 },
3129 BoxType::ItemPropertyAssociationBox => {
3130 associations = read_ipma(&mut b)?;
3131 },
3132 _ => return Err(Error::InvalidData("unexpected ipco child")),
3133 }
3134 }
3135
3136 let mut associated = TryVec::new();
3137 for a in associations {
3138 let index = match a.property_index {
3139 0 => continue,
3140 x => x as usize - 1,
3141 };
3142 if let Some(prop) = properties.get(index)
3143 && *prop != ItemProperty::Unsupported {
3144 associated.push(AssociatedProperty {
3145 item_id: a.item_id,
3146 property: prop.try_clone()?,
3147 })?;
3148 }
3149 }
3150 Ok(associated)
3151}
3152
3153#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3155pub(crate) struct ImageSpatialExtents {
3156 pub(crate) width: u32,
3157 pub(crate) height: u32,
3158}
3159
3160#[derive(Debug, PartialEq)]
3161pub(crate) enum ItemProperty {
3162 Channels(ArrayVec<u8, 16>),
3163 AuxiliaryType(AuxiliaryTypeProperty),
3164 ImageSpatialExtents(ImageSpatialExtents),
3165 ImageGrid(GridConfig),
3166 AV1Config(AV1Config),
3167 ColorInformation(ColorInformation),
3168 Rotation(ImageRotation),
3169 Mirror(ImageMirror),
3170 CleanAperture(CleanAperture),
3171 PixelAspectRatio(PixelAspectRatio),
3172 ContentLightLevel(ContentLightLevel),
3173 MasteringDisplayColourVolume(MasteringDisplayColourVolume),
3174 ContentColourVolume(ContentColourVolume),
3175 AmbientViewingEnvironment(AmbientViewingEnvironment),
3176 OperatingPointSelector(OperatingPointSelector),
3177 LayerSelector(LayerSelector),
3178 AV1LayeredImageIndexing(AV1LayeredImageIndexing),
3179 Unsupported,
3180}
3181
3182impl TryClone for ItemProperty {
3183 fn try_clone(&self) -> Result<Self, TryReserveError> {
3184 Ok(match self {
3185 Self::Channels(val) => Self::Channels(val.clone()),
3186 Self::AuxiliaryType(val) => Self::AuxiliaryType(val.try_clone()?),
3187 Self::ImageSpatialExtents(val) => Self::ImageSpatialExtents(*val),
3188 Self::ImageGrid(val) => Self::ImageGrid(val.clone()),
3189 Self::AV1Config(val) => Self::AV1Config(val.clone()),
3190 Self::ColorInformation(val) => Self::ColorInformation(val.clone()),
3191 Self::Rotation(val) => Self::Rotation(*val),
3192 Self::Mirror(val) => Self::Mirror(*val),
3193 Self::CleanAperture(val) => Self::CleanAperture(*val),
3194 Self::PixelAspectRatio(val) => Self::PixelAspectRatio(*val),
3195 Self::ContentLightLevel(val) => Self::ContentLightLevel(*val),
3196 Self::MasteringDisplayColourVolume(val) => Self::MasteringDisplayColourVolume(*val),
3197 Self::ContentColourVolume(val) => Self::ContentColourVolume(*val),
3198 Self::AmbientViewingEnvironment(val) => Self::AmbientViewingEnvironment(*val),
3199 Self::OperatingPointSelector(val) => Self::OperatingPointSelector(*val),
3200 Self::LayerSelector(val) => Self::LayerSelector(*val),
3201 Self::AV1LayeredImageIndexing(val) => Self::AV1LayeredImageIndexing(*val),
3202 Self::Unsupported => Self::Unsupported,
3203 })
3204 }
3205}
3206
3207struct Association {
3208 item_id: u32,
3209 #[allow(unused)]
3210 essential: bool,
3211 property_index: u16,
3212}
3213
3214pub(crate) struct AssociatedProperty {
3215 pub item_id: u32,
3216 pub property: ItemProperty,
3217}
3218
3219fn read_ipma<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<Association>> {
3220 let (version, flags) = read_fullbox_extra(src)?;
3221
3222 let mut associations = TryVec::new();
3223
3224 let entry_count = be_u32(src)?;
3225 for _ in 0..entry_count {
3226 let item_id = if version == 0 {
3227 be_u16(src)?.into()
3228 } else {
3229 be_u32(src)?
3230 };
3231 let association_count = src.read_u8()?;
3232 for _ in 0..association_count {
3233 let num_association_bytes = if flags & 1 == 1 { 2 } else { 1 };
3234 let association = &mut [0; 2][..num_association_bytes];
3235 src.read_exact(association)?;
3236 let mut association = BitReader::new(association);
3237 let essential = association.read_bool()?;
3238 let property_index = association.read_u16(association.remaining().try_into()?)?;
3239 associations.push(Association {
3240 item_id,
3241 essential,
3242 property_index,
3243 })?;
3244 }
3245 }
3246 Ok(associations)
3247}
3248
3249fn read_ipco<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<ItemProperty>> {
3250 let mut properties = TryVec::new();
3251
3252 let mut iter = src.box_iter();
3253 while let Some(mut b) = iter.next_box()? {
3254 let prop = match b.head.name {
3256 BoxType::PixelInformationBox => ItemProperty::Channels(read_pixi(&mut b, options)?),
3257 BoxType::AuxiliaryTypeProperty => ItemProperty::AuxiliaryType(read_auxc(&mut b, options)?),
3258 BoxType::ImageSpatialExtentsBox => ItemProperty::ImageSpatialExtents(read_ispe(&mut b, options)?),
3259 BoxType::ImageGridBox => ItemProperty::ImageGrid(read_grid(&mut b, options)?),
3260 BoxType::AV1CodecConfigurationBox => ItemProperty::AV1Config(read_av1c(&mut b)?),
3261 BoxType::ColorInformationBox => {
3262 match read_colr(&mut b) {
3263 Ok(colr) => ItemProperty::ColorInformation(colr),
3264 Err(_) => ItemProperty::Unsupported,
3265 }
3266 },
3267 BoxType::ImageRotationBox => ItemProperty::Rotation(read_irot(&mut b)?),
3268 BoxType::ImageMirrorBox => ItemProperty::Mirror(read_imir(&mut b)?),
3269 BoxType::CleanApertureBox => ItemProperty::CleanAperture(read_clap(&mut b)?),
3270 BoxType::PixelAspectRatioBox => ItemProperty::PixelAspectRatio(read_pasp(&mut b)?),
3271 BoxType::ContentLightLevelBox => ItemProperty::ContentLightLevel(read_clli(&mut b)?),
3272 BoxType::MasteringDisplayColourVolumeBox => ItemProperty::MasteringDisplayColourVolume(read_mdcv(&mut b)?),
3273 BoxType::ContentColourVolumeBox => ItemProperty::ContentColourVolume(read_cclv(&mut b)?),
3274 BoxType::AmbientViewingEnvironmentBox => ItemProperty::AmbientViewingEnvironment(read_amve(&mut b)?),
3275 BoxType::OperatingPointSelectorBox => ItemProperty::OperatingPointSelector(read_a1op(&mut b)?),
3276 BoxType::LayerSelectorBox => ItemProperty::LayerSelector(read_lsel(&mut b)?),
3277 BoxType::AV1LayeredImageIndexingBox => ItemProperty::AV1LayeredImageIndexing(read_a1lx(&mut b)?),
3278 _ => {
3279 skip_box_remain(&mut b)?;
3280 ItemProperty::Unsupported
3281 },
3282 };
3283 properties.push(prop)?;
3284 }
3285 Ok(properties)
3286}
3287
3288fn read_pixi<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<ArrayVec<u8, 16>> {
3289 let version = read_fullbox_version_no_flags(src, options)?;
3290 if version != 0 {
3291 return Err(Error::Unsupported("pixi version"));
3292 }
3293
3294 let num_channels = usize::from(src.read_u8()?);
3295 let mut channels = ArrayVec::new();
3296 channels.extend((0..num_channels.min(channels.capacity())).map(|_| 0));
3297 debug_assert_eq!(num_channels, channels.len());
3298 src.read_exact(&mut channels).map_err(|_| Error::InvalidData("invalid num_channels"))?;
3299
3300 if options.lenient && src.bytes_left() > 0 {
3302 skip(src, src.bytes_left())?;
3303 }
3304
3305 check_parser_state(&src.head, &src.content)?;
3306 Ok(channels)
3307}
3308
3309#[derive(Debug, PartialEq)]
3310struct AuxiliaryTypeProperty {
3311 aux_data: TryString,
3312}
3313
3314impl AuxiliaryTypeProperty {
3315 #[must_use]
3316 fn type_subtype(&self) -> (&[u8], &[u8]) {
3317 let split = self.aux_data.iter().position(|&b| b == b'\0')
3318 .map(|pos| self.aux_data.split_at(pos));
3319 if let Some((aux_type, rest)) = split {
3320 (aux_type, &rest[1..])
3321 } else {
3322 (&self.aux_data, &[])
3323 }
3324 }
3325}
3326
3327impl TryClone for AuxiliaryTypeProperty {
3328 fn try_clone(&self) -> Result<Self, TryReserveError> {
3329 Ok(Self {
3330 aux_data: self.aux_data.try_clone()?,
3331 })
3332 }
3333}
3334
3335fn read_auxc<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<AuxiliaryTypeProperty> {
3336 let version = read_fullbox_version_no_flags(src, options)?;
3337 if version != 0 {
3338 return Err(Error::Unsupported("auxC version"));
3339 }
3340
3341 let aux_data = src.read_into_try_vec()?;
3342
3343 Ok(AuxiliaryTypeProperty { aux_data })
3344}
3345
3346fn read_av1c<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<AV1Config> {
3349 let byte0 = src.read_u8()?;
3351 let marker = byte0 >> 7;
3352 let version = byte0 & 0x7F;
3353
3354 if marker != 1 {
3355 return Err(Error::InvalidData("av1C marker must be 1"));
3356 }
3357 if version != 1 {
3358 return Err(Error::Unsupported("av1C version must be 1"));
3359 }
3360
3361 let byte1 = src.read_u8()?;
3362 let profile = byte1 >> 5;
3363 let level = byte1 & 0x1F;
3364
3365 let byte2 = src.read_u8()?;
3366 let tier = byte2 >> 7;
3367 let high_bitdepth = (byte2 >> 6) & 1;
3368 let twelve_bit = (byte2 >> 5) & 1;
3369 let monochrome = (byte2 >> 4) & 1 != 0;
3370 let chroma_subsampling_x = (byte2 >> 3) & 1;
3371 let chroma_subsampling_y = (byte2 >> 2) & 1;
3372 let chroma_sample_position = byte2 & 0x03;
3373
3374 let byte3 = src.read_u8()?;
3375 let _ = byte3;
3378
3379 let bit_depth = if high_bitdepth != 0 {
3380 if twelve_bit != 0 { 12 } else { 10 }
3381 } else {
3382 8
3383 };
3384
3385 skip_box_remain(src)?;
3387
3388 Ok(AV1Config {
3389 profile,
3390 level,
3391 tier,
3392 bit_depth,
3393 monochrome,
3394 chroma_subsampling_x,
3395 chroma_subsampling_y,
3396 chroma_sample_position,
3397 })
3398}
3399
3400fn read_colr<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ColorInformation> {
3403 let colour_type = be_u32(src)?;
3405
3406 match &colour_type.to_be_bytes() {
3407 b"nclx" => {
3408 let color_primaries = be_u16(src)?;
3409 let transfer_characteristics = be_u16(src)?;
3410 let matrix_coefficients = be_u16(src)?;
3411 let full_range_byte = src.read_u8()?;
3412 let full_range = (full_range_byte >> 7) != 0;
3413 skip_box_remain(src)?;
3415 Ok(ColorInformation::Nclx {
3416 color_primaries,
3417 transfer_characteristics,
3418 matrix_coefficients,
3419 full_range,
3420 })
3421 }
3422 b"rICC" | b"prof" => {
3423 let icc_data = src.read_into_try_vec()?;
3424 Ok(ColorInformation::IccProfile(icc_data.to_vec()))
3425 }
3426 _ => {
3427 skip_box_remain(src)?;
3428 Err(Error::Unsupported("unsupported colr colour_type"))
3429 }
3430 }
3431}
3432
3433fn read_irot<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ImageRotation> {
3436 let byte = src.read_u8()?;
3437 let angle_code = byte & 0x03;
3438 let angle = match angle_code {
3439 0 => 0,
3440 1 => 90,
3441 2 => 180,
3442 3 => 270,
3443 _ => unreachable!(),
3444 };
3445 skip_box_remain(src)?;
3446 Ok(ImageRotation { angle })
3447}
3448
3449fn read_imir<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ImageMirror> {
3452 let byte = src.read_u8()?;
3453 let axis = byte & 0x01;
3454 skip_box_remain(src)?;
3455 Ok(ImageMirror { axis })
3456}
3457
3458fn read_clap<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<CleanAperture> {
3461 let width_n = be_u32(src)?;
3462 let width_d = be_u32(src)?;
3463 let height_n = be_u32(src)?;
3464 let height_d = be_u32(src)?;
3465 let horiz_off_n = be_i32(src)?;
3466 let horiz_off_d = be_u32(src)?;
3467 let vert_off_n = be_i32(src)?;
3468 let vert_off_d = be_u32(src)?;
3469 if width_d == 0 || height_d == 0 || horiz_off_d == 0 || vert_off_d == 0 {
3471 return Err(Error::InvalidData("clap denominator cannot be zero"));
3472 }
3473 skip_box_remain(src)?;
3474 Ok(CleanAperture {
3475 width_n, width_d,
3476 height_n, height_d,
3477 horiz_off_n, horiz_off_d,
3478 vert_off_n, vert_off_d,
3479 })
3480}
3481
3482fn read_pasp<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<PixelAspectRatio> {
3485 let h_spacing = be_u32(src)?;
3486 let v_spacing = be_u32(src)?;
3487 skip_box_remain(src)?;
3488 Ok(PixelAspectRatio { h_spacing, v_spacing })
3489}
3490
3491fn read_clli<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ContentLightLevel> {
3494 let max_content_light_level = be_u16(src)?;
3495 let max_pic_average_light_level = be_u16(src)?;
3496 skip_box_remain(src)?;
3497 Ok(ContentLightLevel {
3498 max_content_light_level,
3499 max_pic_average_light_level,
3500 })
3501}
3502
3503fn read_mdcv<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<MasteringDisplayColourVolume> {
3506 let primaries = [
3508 (be_u16(src)?, be_u16(src)?),
3509 (be_u16(src)?, be_u16(src)?),
3510 (be_u16(src)?, be_u16(src)?),
3511 ];
3512 let white_point = (be_u16(src)?, be_u16(src)?);
3513 let max_luminance = be_u32(src)?;
3514 let min_luminance = be_u32(src)?;
3515 skip_box_remain(src)?;
3516 Ok(MasteringDisplayColourVolume {
3517 primaries,
3518 white_point,
3519 max_luminance,
3520 min_luminance,
3521 })
3522}
3523
3524fn read_cclv<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ContentColourVolume> {
3527 let flags = src.read_u8()?;
3528 let primaries_present = flags & 0x20 != 0;
3529 let min_lum_present = flags & 0x10 != 0;
3530 let max_lum_present = flags & 0x08 != 0;
3531 let avg_lum_present = flags & 0x04 != 0;
3532
3533 let primaries = if primaries_present {
3534 Some([
3535 (be_i32(src)?, be_i32(src)?),
3536 (be_i32(src)?, be_i32(src)?),
3537 (be_i32(src)?, be_i32(src)?),
3538 ])
3539 } else {
3540 None
3541 };
3542
3543 let min_luminance = if min_lum_present { Some(be_u32(src)?) } else { None };
3544 let max_luminance = if max_lum_present { Some(be_u32(src)?) } else { None };
3545 let avg_luminance = if avg_lum_present { Some(be_u32(src)?) } else { None };
3546
3547 skip_box_remain(src)?;
3548 Ok(ContentColourVolume {
3549 primaries,
3550 min_luminance,
3551 max_luminance,
3552 avg_luminance,
3553 })
3554}
3555
3556fn read_amve<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<AmbientViewingEnvironment> {
3559 let ambient_illuminance = be_u32(src)?;
3560 let ambient_light_x = be_u16(src)?;
3561 let ambient_light_y = be_u16(src)?;
3562 skip_box_remain(src)?;
3563 Ok(AmbientViewingEnvironment {
3564 ambient_illuminance,
3565 ambient_light_x,
3566 ambient_light_y,
3567 })
3568}
3569
3570fn read_a1op<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<OperatingPointSelector> {
3573 let op_index = src.read_u8()?;
3574 if op_index > 31 {
3575 return Err(Error::InvalidData("a1op op_index must be 0..31"));
3576 }
3577 skip_box_remain(src)?;
3578 Ok(OperatingPointSelector { op_index })
3579}
3580
3581fn read_lsel<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<LayerSelector> {
3584 let layer_id = be_u16(src)?;
3585 skip_box_remain(src)?;
3586 Ok(LayerSelector { layer_id })
3587}
3588
3589fn read_a1lx<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<AV1LayeredImageIndexing> {
3592 let flags = src.read_u8()?;
3593 let large_size = flags & 0x01 != 0;
3594 let layer_sizes = if large_size {
3595 [be_u32(src)?, be_u32(src)?, be_u32(src)?]
3596 } else {
3597 [u32::from(be_u16(src)?), u32::from(be_u16(src)?), u32::from(be_u16(src)?)]
3598 };
3599 skip_box_remain(src)?;
3600 Ok(AV1LayeredImageIndexing { layer_sizes })
3601}
3602
3603fn read_ispe<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<ImageSpatialExtents> {
3606 let _version = read_fullbox_version_no_flags(src, options)?;
3607 let width = be_u32(src)?;
3610 let height = be_u32(src)?;
3611
3612 if width == 0 || height == 0 {
3614 return Err(Error::InvalidData("ispe dimensions cannot be zero"));
3615 }
3616
3617 Ok(ImageSpatialExtents { width, height })
3618}
3619
3620fn read_mvhd<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<MovieHeader> {
3623 let version = src.read_u8()?;
3624 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
3625
3626 let (timescale, duration) = if version == 1 {
3627 let _creation_time = be_u64(src)?;
3628 let _modification_time = be_u64(src)?;
3629 let timescale = be_u32(src)?;
3630 let duration = be_u64(src)?;
3631 (timescale, duration)
3632 } else {
3633 let _creation_time = be_u32(src)?;
3634 let _modification_time = be_u32(src)?;
3635 let timescale = be_u32(src)?;
3636 let duration = be_u32(src)?;
3637 (timescale, duration as u64)
3638 };
3639
3640 skip_box_remain(src)?;
3642
3643 Ok(MovieHeader { _timescale: timescale, _duration: duration })
3644}
3645
3646fn read_mdhd<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<MediaHeader> {
3649 let version = src.read_u8()?;
3650 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
3651
3652 let (timescale, duration) = if version == 1 {
3653 let _creation_time = be_u64(src)?;
3654 let _modification_time = be_u64(src)?;
3655 let timescale = be_u32(src)?;
3656 let duration = be_u64(src)?;
3657 (timescale, duration)
3658 } else {
3659 let _creation_time = be_u32(src)?;
3660 let _modification_time = be_u32(src)?;
3661 let timescale = be_u32(src)?;
3662 let duration = be_u32(src)?;
3663 (timescale, duration as u64)
3664 };
3665
3666 skip_box_remain(src)?;
3668
3669 Ok(MediaHeader { timescale, _duration: duration })
3670}
3671
3672fn read_stts<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<TimeToSampleEntry>> {
3675 let _version = src.read_u8()?;
3676 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
3677 let entry_count = be_u32(src)?;
3678
3679 let mut entries = TryVec::new();
3680 for _ in 0..entry_count {
3681 entries.push(TimeToSampleEntry {
3682 sample_count: be_u32(src)?,
3683 sample_delta: be_u32(src)?,
3684 })?;
3685 }
3686
3687 Ok(entries)
3688}
3689
3690fn read_stsc<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<SampleToChunkEntry>> {
3693 let _version = src.read_u8()?;
3694 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
3695 let entry_count = be_u32(src)?;
3696
3697 let mut entries = TryVec::new();
3698 for _ in 0..entry_count {
3699 entries.push(SampleToChunkEntry {
3700 first_chunk: be_u32(src)?,
3701 samples_per_chunk: be_u32(src)?,
3702 _sample_description_index: be_u32(src)?,
3703 })?;
3704 }
3705
3706 Ok(entries)
3707}
3708
3709fn read_stsz<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<u32>> {
3712 let _version = src.read_u8()?;
3713 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
3714 let sample_size = be_u32(src)?;
3715 let sample_count = be_u32(src)?;
3716
3717 let mut sizes = TryVec::new();
3718 if sample_size == 0 {
3719 for _ in 0..sample_count {
3721 sizes.push(be_u32(src)?)?;
3722 }
3723 } else {
3724 for _ in 0..sample_count {
3726 sizes.push(sample_size)?;
3727 }
3728 }
3729
3730 Ok(sizes)
3731}
3732
3733fn read_chunk_offsets<T: Read>(src: &mut BMFFBox<'_, T>, is_64bit: bool) -> Result<TryVec<u64>> {
3736 let _version = src.read_u8()?;
3737 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
3738 let entry_count = be_u32(src)?;
3739
3740 let mut offsets = TryVec::new();
3741 for _ in 0..entry_count {
3742 let offset = if is_64bit {
3743 be_u64(src)?
3744 } else {
3745 be_u32(src)? as u64
3746 };
3747 offsets.push(offset)?;
3748 }
3749
3750 Ok(offsets)
3751}
3752
3753fn read_stbl<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<SampleTable> {
3756 let mut time_to_sample = TryVec::new();
3757 let mut sample_to_chunk = TryVec::new();
3758 let mut sample_sizes = TryVec::new();
3759 let mut chunk_offsets = TryVec::new();
3760
3761 let mut iter = src.box_iter();
3762 while let Some(mut b) = iter.next_box()? {
3763 match b.head.name {
3764 BoxType::TimeToSampleBox => {
3765 time_to_sample = read_stts(&mut b)?;
3766 }
3767 BoxType::SampleToChunkBox => {
3768 sample_to_chunk = read_stsc(&mut b)?;
3769 }
3770 BoxType::SampleSizeBox => {
3771 sample_sizes = read_stsz(&mut b)?;
3772 }
3773 BoxType::ChunkOffsetBox => {
3774 chunk_offsets = read_chunk_offsets(&mut b, false)?;
3775 }
3776 BoxType::ChunkLargeOffsetBox => {
3777 chunk_offsets = read_chunk_offsets(&mut b, true)?;
3778 }
3779 _ => {
3780 skip_box_remain(&mut b)?;
3781 }
3782 }
3783 }
3784
3785 Ok(SampleTable {
3786 time_to_sample,
3787 sample_to_chunk,
3788 sample_sizes,
3789 chunk_offsets,
3790 })
3791}
3792
3793fn read_tkhd<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<u32> {
3796 let version = src.read_u8()?;
3797 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
3798
3799 let track_id = if version == 1 {
3800 let _creation_time = be_u64(src)?;
3801 let _modification_time = be_u64(src)?;
3802 let track_id = be_u32(src)?;
3803 let _reserved = be_u32(src)?;
3804 let _duration = be_u64(src)?;
3805 track_id
3806 } else {
3807 let _creation_time = be_u32(src)?;
3808 let _modification_time = be_u32(src)?;
3809 let track_id = be_u32(src)?;
3810 let _reserved = be_u32(src)?;
3811 let _duration = be_u32(src)?;
3812 track_id
3813 };
3814
3815 skip_box_remain(src)?;
3817 Ok(track_id)
3818}
3819
3820fn read_tref<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<TrackReference>> {
3825 let mut refs = TryVec::new();
3826 let mut iter = src.box_iter();
3827 while let Some(mut b) = iter.next_box()? {
3828 let reference_type = FourCC::from(u32::from(b.head.name));
3829 let bytes_left = b.bytes_left();
3830 if bytes_left < 4 || bytes_left % 4 != 0 {
3831 skip_box_remain(&mut b)?;
3832 continue;
3833 }
3834 let count = bytes_left / 4;
3835 let mut track_ids = TryVec::new();
3836 for _ in 0..count {
3837 track_ids.push(be_u32(&mut b)?)?;
3838 }
3839 refs.push(TrackReference { reference_type, track_ids })?;
3840 }
3841 Ok(refs)
3842}
3843
3844fn read_elst<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<u32> {
3849 let (version, flags) = read_fullbox_extra(src)?;
3850
3851 let entry_count = be_u32(src)?;
3852 let entry_size: u64 = if version == 1 { 20 } else { 12 };
3854 skip(src, entry_count as u64 * entry_size)?;
3855 skip_box_remain(src)?;
3856
3857 if flags & 1 != 0 {
3859 Ok(0) } else {
3861 Ok(1) }
3863}
3864
3865fn read_moov<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<ParsedTrack>> {
3868 let mut tracks = TryVec::new();
3869
3870 let mut iter = src.box_iter();
3871 while let Some(mut b) = iter.next_box()? {
3872 match b.head.name {
3873 BoxType::MovieHeaderBox => {
3874 let _mvhd = read_mvhd(&mut b)?;
3875 }
3876 BoxType::TrackBox => {
3877 if let Some(track) = read_trak(&mut b)? {
3878 tracks.push(track)?;
3879 }
3880 }
3881 _ => {
3882 skip_box_remain(&mut b)?;
3883 }
3884 }
3885 }
3886
3887 Ok(tracks)
3888}
3889
3890fn read_trak<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<Option<ParsedTrack>> {
3893 let mut track_id = 0u32;
3894 let mut references = TryVec::new();
3895 let mut loop_count = 1u32; let mut mdia_result: Option<(FourCC, u32, SampleTable)> = None;
3897
3898 let mut iter = src.box_iter();
3899 while let Some(mut b) = iter.next_box()? {
3900 match b.head.name {
3901 BoxType::TrackHeaderBox => {
3902 track_id = read_tkhd(&mut b)?;
3903 }
3904 BoxType::TrackReferenceBox => {
3905 references = read_tref(&mut b)?;
3906 }
3907 BoxType::EditBox => {
3908 let mut edts_iter = b.box_iter();
3910 while let Some(mut eb) = edts_iter.next_box()? {
3911 if eb.head.name == BoxType::EditListBox {
3912 loop_count = read_elst(&mut eb)?;
3913 } else {
3914 skip_box_remain(&mut eb)?;
3915 }
3916 }
3917 }
3918 BoxType::MediaBox => {
3919 mdia_result = read_mdia(&mut b)?;
3920 }
3921 _ => {
3922 skip_box_remain(&mut b)?;
3923 }
3924 }
3925 }
3926
3927 if let Some((handler_type, media_timescale, sample_table)) = mdia_result {
3928 Ok(Some(ParsedTrack {
3929 track_id,
3930 handler_type,
3931 media_timescale,
3932 sample_table,
3933 references,
3934 loop_count,
3935 }))
3936 } else {
3937 Ok(None)
3938 }
3939}
3940
3941fn read_mdia<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<Option<(FourCC, u32, SampleTable)>> {
3944 let mut media_timescale = 1000; let mut handler_type = FourCC::default();
3946 let mut sample_table: Option<SampleTable> = None;
3947
3948 let mut iter = src.box_iter();
3949 while let Some(mut b) = iter.next_box()? {
3950 match b.head.name {
3951 BoxType::MediaHeaderBox => {
3952 let mdhd = read_mdhd(&mut b)?;
3953 media_timescale = mdhd.timescale;
3954 }
3955 BoxType::HandlerBox => {
3956 let hdlr = read_hdlr(&mut b)?;
3957 handler_type = hdlr.handler_type;
3958 }
3959 BoxType::MediaInformationBox => {
3960 sample_table = read_minf(&mut b)?;
3961 }
3962 _ => {
3963 skip_box_remain(&mut b)?;
3964 }
3965 }
3966 }
3967
3968 if let Some(stbl) = sample_table {
3969 Ok(Some((handler_type, media_timescale, stbl)))
3970 } else {
3971 Ok(None)
3972 }
3973}
3974
3975fn associate_tracks(tracks: TryVec<ParsedTrack>) -> Result<ParsedAnimationData> {
3981 let color_idx = tracks
3983 .iter()
3984 .position(|t| t.handler_type == b"pict")
3985 .or_else(|| {
3986 tracks.iter().position(|t| t.handler_type != b"soun")
3988 })
3989 .ok_or(Error::InvalidData("no color track found in moov"))?;
3990
3991 let color_track_id = tracks[color_idx].track_id;
3992
3993 let alpha_idx = tracks.iter().position(|t| {
3995 t.handler_type == b"auxv"
3996 && t.references.iter().any(|r| {
3997 r.reference_type == b"auxl"
3998 && r.track_ids.iter().any(|&id| id == color_track_id)
3999 })
4000 });
4001
4002 if let Some(ai) = alpha_idx {
4003 let alpha_frames = tracks[ai].sample_table.sample_sizes.len();
4004 let color_frames = tracks[color_idx].sample_table.sample_sizes.len();
4005 if alpha_frames != color_frames {
4006 warn!(
4007 "alpha track has {} frames but color track has {} frames",
4008 alpha_frames, color_frames
4009 );
4010 }
4011 }
4012
4013 let mut tracks_vec: std::vec::Vec<ParsedTrack> = tracks.into_iter().collect();
4016
4017 let (color_track, alpha_track) = if let Some(ai) = alpha_idx {
4019 if ai > color_idx {
4020 let alpha = tracks_vec.remove(ai);
4021 let color = tracks_vec.remove(color_idx);
4022 (color, Some(alpha))
4023 } else {
4024 let color = tracks_vec.remove(color_idx);
4025 let alpha = tracks_vec.remove(ai);
4026 (color, Some(alpha))
4027 }
4028 } else {
4029 let color = tracks_vec.remove(color_idx);
4030 (color, None)
4031 };
4032
4033 let (alpha_timescale, alpha_sample_table) = match alpha_track {
4034 Some(t) => (Some(t.media_timescale), Some(t.sample_table)),
4035 None => (None, None),
4036 };
4037
4038 Ok(ParsedAnimationData {
4039 color_timescale: color_track.media_timescale,
4040 color_sample_table: color_track.sample_table,
4041 alpha_timescale,
4042 alpha_sample_table,
4043 loop_count: color_track.loop_count,
4044 })
4045}
4046
4047fn read_minf<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<Option<SampleTable>> {
4049 let mut iter = src.box_iter();
4050 while let Some(mut b) = iter.next_box()? {
4051 if b.head.name == BoxType::SampleTableBox {
4052 return Ok(Some(read_stbl(&mut b)?));
4053 } else {
4054 skip_box_remain(&mut b)?;
4055 }
4056 }
4057 Ok(None)
4058}
4059
4060#[cfg(feature = "eager")]
4062#[allow(deprecated)]
4063fn extract_animation_frames(
4064 sample_table: &SampleTable,
4065 media_timescale: u32,
4066 mdats: &mut [MediaDataBox],
4067) -> Result<TryVec<AnimationFrame>> {
4068 let mut frames = TryVec::new();
4069
4070 let mut sample_to_chunk_map = TryVec::new();
4072 for (i, entry) in sample_table.sample_to_chunk.iter().enumerate() {
4073 let next_first_chunk = sample_table
4074 .sample_to_chunk
4075 .get(i + 1)
4076 .map(|e| e.first_chunk)
4077 .unwrap_or(u32::MAX);
4078
4079 for chunk_idx in entry.first_chunk..next_first_chunk {
4080 if chunk_idx > sample_table.chunk_offsets.len() as u32 {
4081 break;
4082 }
4083 sample_to_chunk_map.push((chunk_idx, entry.samples_per_chunk))?;
4084 }
4085 }
4086
4087 let mut frame_durations = TryVec::new();
4089 for entry in &sample_table.time_to_sample {
4090 for _ in 0..entry.sample_count {
4091 let duration_ms = if media_timescale > 0 {
4093 ((entry.sample_delta as u64) * 1000) / (media_timescale as u64)
4094 } else {
4095 0
4096 };
4097 frame_durations.push(duration_ms as u32)?;
4098 }
4099 }
4100
4101 let sample_count = sample_table.sample_sizes.len();
4103 let mut current_sample = 0;
4104
4105 for (chunk_idx_1based, samples_in_chunk) in &sample_to_chunk_map {
4106 let chunk_idx = (*chunk_idx_1based as usize).saturating_sub(1);
4107 if chunk_idx >= sample_table.chunk_offsets.len() {
4108 continue;
4109 }
4110
4111 let chunk_offset = sample_table.chunk_offsets[chunk_idx];
4112
4113 for sample_in_chunk in 0..*samples_in_chunk {
4114 if current_sample >= sample_count {
4115 break;
4116 }
4117
4118 let sample_size = sample_table.sample_sizes[current_sample];
4119 let duration_ms = frame_durations.get(current_sample).copied().unwrap_or(0);
4120
4121 let mut offset_in_chunk = 0u64;
4123 for s in 0..sample_in_chunk {
4124 let prev_sample = current_sample.saturating_sub((sample_in_chunk - s) as usize);
4125 if prev_sample < sample_count {
4126 offset_in_chunk += sample_table.sample_sizes[prev_sample] as u64;
4127 }
4128 }
4129
4130 let sample_offset = chunk_offset + offset_in_chunk;
4131
4132 let mut frame_data = TryVec::new();
4134 let mut found = false;
4135
4136 for mdat in mdats.iter_mut() {
4137 let range = ExtentRange::WithLength(Range {
4138 start: sample_offset,
4139 end: sample_offset + sample_size as u64,
4140 });
4141
4142 if mdat.contains_extent(&range) {
4143 mdat.read_extent(&range, &mut frame_data)?;
4144 found = true;
4145 break;
4146 }
4147 }
4148
4149 if !found {
4150 log::warn!("Animation frame {} not found in mdat", current_sample);
4151 }
4152
4153 frames.push(AnimationFrame {
4154 data: frame_data,
4155 duration_ms,
4156 })?;
4157
4158 current_sample += 1;
4159 }
4160 }
4161
4162 Ok(frames)
4163}
4164
4165fn read_grid<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<GridConfig> {
4168 let version = read_fullbox_version_no_flags(src, options)?;
4169 if version > 0 {
4170 return Err(Error::Unsupported("grid version > 0"));
4171 }
4172
4173 let flags_byte = src.read_u8()?;
4174 let rows = src.read_u8()?;
4175 let columns = src.read_u8()?;
4176
4177 let (output_width, output_height) = if flags_byte & 1 == 0 {
4179 (u32::from(be_u16(src)?), u32::from(be_u16(src)?))
4181 } else {
4182 (be_u32(src)?, be_u32(src)?)
4184 };
4185
4186 Ok(GridConfig {
4187 rows,
4188 columns,
4189 output_width,
4190 output_height,
4191 })
4192}
4193
4194fn read_iloc<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<ItemLocationBoxItem>> {
4197 let version: IlocVersion = read_fullbox_version_no_flags(src, options)?.try_into()?;
4198
4199 let iloc = src.read_into_try_vec()?;
4200 let mut iloc = BitReader::new(&iloc);
4201
4202 let offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
4203 let length_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
4204 let base_offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
4205
4206 let index_size: Option<IlocFieldSize> = match version {
4207 IlocVersion::One | IlocVersion::Two => Some(iloc.read_u8(4)?.try_into()?),
4208 IlocVersion::Zero => {
4209 let _reserved = iloc.read_u8(4)?;
4210 None
4211 },
4212 };
4213
4214 let item_count = match version {
4215 IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?,
4216 IlocVersion::Two => iloc.read_u32(32)?,
4217 };
4218
4219 let mut items = TryVec::with_capacity(item_count.to_usize())?;
4220
4221 for _ in 0..item_count {
4222 let item_id = match version {
4223 IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?,
4224 IlocVersion::Two => iloc.read_u32(32)?,
4225 };
4226
4227 let construction_method = match version {
4233 IlocVersion::Zero => ConstructionMethod::File,
4234 IlocVersion::One | IlocVersion::Two => {
4235 let _reserved = iloc.read_u16(12)?;
4236 match iloc.read_u16(4)? {
4237 0 => ConstructionMethod::File,
4238 1 => ConstructionMethod::Idat,
4239 2 => return Err(Error::Unsupported("construction_method 'item_offset' is not supported")),
4240 _ => return Err(Error::InvalidData("construction_method is taken from the set 0, 1 or 2 per ISO 14496-12:2015 § 8.11.3.3")),
4241 }
4242 },
4243 };
4244
4245 let data_reference_index = iloc.read_u16(16)?;
4246
4247 if data_reference_index != 0 {
4248 return Err(Error::Unsupported("external file references (iloc.data_reference_index != 0) are not supported"));
4249 }
4250
4251 let base_offset = iloc.read_u64(base_offset_size.to_bits())?;
4252 let extent_count = iloc.read_u16(16)?;
4253
4254 if extent_count < 1 {
4255 return Err(Error::InvalidData("extent_count must have a value 1 or greater per ISO 14496-12:2015 § 8.11.3.3"));
4256 }
4257
4258 let mut extents = TryVec::with_capacity(extent_count.to_usize())?;
4259
4260 for _ in 0..extent_count {
4261 let _extent_index = match &index_size {
4263 None | Some(IlocFieldSize::Zero) => None,
4264 Some(index_size) => {
4265 debug_assert!(version == IlocVersion::One || version == IlocVersion::Two);
4266 Some(iloc.read_u64(index_size.to_bits())?)
4267 },
4268 };
4269
4270 let extent_offset = iloc.read_u64(offset_size.to_bits())?;
4275 let extent_length = iloc.read_u64(length_size.to_bits())?;
4276
4277 let start = base_offset
4280 .checked_add(extent_offset)
4281 .ok_or(Error::InvalidData("offset calculation overflow"))?;
4282 let extent_range = if extent_length == 0 {
4283 ExtentRange::ToEnd(RangeFrom { start })
4284 } else {
4285 let end = start
4286 .checked_add(extent_length)
4287 .ok_or(Error::InvalidData("end calculation overflow"))?;
4288 ExtentRange::WithLength(Range { start, end })
4289 };
4290
4291 extents.push(ItemLocationBoxExtent { extent_range })?;
4292 }
4293
4294 items.push(ItemLocationBoxItem { item_id, construction_method, extents })?;
4295 }
4296
4297 if iloc.remaining() == 0 {
4298 Ok(items)
4299 } else {
4300 Err(Error::InvalidData("invalid iloc size"))
4301 }
4302}
4303
4304fn read_ftyp<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<FileTypeBox> {
4307 let major = be_u32(src)?;
4308 let minor = be_u32(src)?;
4309 let bytes_left = src.bytes_left();
4310 if !bytes_left.is_multiple_of(4) {
4311 return Err(Error::InvalidData("invalid ftyp size"));
4312 }
4313 let brand_count = bytes_left / 4;
4315 let mut brands = TryVec::with_capacity(brand_count.try_into()?)?;
4316 for _ in 0..brand_count {
4317 brands.push(be_u32(src)?.into())?;
4318 }
4319 Ok(FileTypeBox {
4320 major_brand: From::from(major),
4321 minor_version: minor,
4322 compatible_brands: brands,
4323 })
4324}
4325
4326#[cfg_attr(debug_assertions, track_caller)]
4327fn check_parser_state<T>(header: &BoxHeader, left: &Take<T>) -> Result<(), Error> {
4328 let limit = left.limit();
4329 if limit == 0 || header.size == u64::MAX {
4331 Ok(())
4332 } else {
4333 debug_assert_eq!(0, limit, "bad parser state bytes left");
4334 Err(Error::InvalidData("unread box content or bad parser sync"))
4335 }
4336}
4337
4338fn skip<T: Read>(src: &mut T, bytes: u64) -> Result<()> {
4340 std::io::copy(&mut src.take(bytes), &mut std::io::sink())?;
4341 Ok(())
4342}
4343
4344fn be_u16<T: ReadBytesExt>(src: &mut T) -> Result<u16> {
4345 src.read_u16::<byteorder::BigEndian>().map_err(From::from)
4346}
4347
4348fn be_u32<T: ReadBytesExt>(src: &mut T) -> Result<u32> {
4349 src.read_u32::<byteorder::BigEndian>().map_err(From::from)
4350}
4351
4352fn be_i32<T: ReadBytesExt>(src: &mut T) -> Result<i32> {
4353 src.read_i32::<byteorder::BigEndian>().map_err(From::from)
4354}
4355
4356fn be_u64<T: ReadBytesExt>(src: &mut T) -> Result<u64> {
4357 src.read_u64::<byteorder::BigEndian>().map_err(From::from)
4358}