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 = "c_api")]
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 {
53 fn to_u64(self) -> u64 {
54 const _: () = assert!(std::mem::size_of::<usize>() <= std::mem::size_of::<u64>());
55 self as u64
56 }
57}
58
59pub(crate) trait ToUsize {
62 fn to_usize(self) -> usize;
63}
64
65macro_rules! impl_to_usize_from {
67 ( $from_type:ty ) => {
68 impl ToUsize for $from_type {
69 fn to_usize(self) -> usize {
70 const _: () = assert!(std::mem::size_of::<$from_type>() <= std::mem::size_of::<usize>());
71 self as usize
72 }
73 }
74 };
75}
76
77impl_to_usize_from!(u8);
78impl_to_usize_from!(u16);
79impl_to_usize_from!(u32);
80
81trait Offset {
83 fn offset(&self) -> u64;
84}
85
86struct OffsetReader<'a, T> {
88 reader: &'a mut T,
89 offset: u64,
90}
91
92impl<'a, T> OffsetReader<'a, T> {
93 fn new(reader: &'a mut T) -> Self {
94 Self { reader, offset: 0 }
95 }
96}
97
98impl<T> Offset for OffsetReader<'_, T> {
99 fn offset(&self) -> u64 {
100 self.offset
101 }
102}
103
104impl<T: Read> Read for OffsetReader<'_, T> {
105 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
106 let bytes_read = self.reader.read(buf)?;
107 self.offset = self
108 .offset
109 .checked_add(bytes_read.to_u64())
110 .ok_or(Error::Unsupported("total bytes read too large for offset type"))?;
111 Ok(bytes_read)
112 }
113}
114
115pub(crate) type TryVec<T> = fallible_collections::TryVec<T>;
116pub(crate) type TryString = fallible_collections::TryVec<u8>;
117
118#[allow(dead_code)]
120struct Vec;
121#[allow(dead_code)]
122struct Box;
123#[allow(dead_code)]
124struct HashMap;
125#[allow(dead_code)]
126struct String;
127
128#[derive(Debug)]
133pub enum Error {
134 InvalidData(&'static str),
136 Unsupported(&'static str),
138 UnexpectedEOF,
140 Io(std::io::Error),
142 NoMoov,
144 OutOfMemory,
146 ResourceLimitExceeded(&'static str),
148 Stopped(enough::StopReason),
150}
151
152impl std::fmt::Display for Error {
153 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154 let msg = match self {
155 Self::InvalidData(s) | Self::Unsupported(s) | Self::ResourceLimitExceeded(s) => s,
156 Self::UnexpectedEOF => "EOF",
157 Self::Io(err) => return err.fmt(f),
158 Self::NoMoov => "Missing Moov box",
159 Self::OutOfMemory => "OOM",
160 Self::Stopped(reason) => return write!(f, "Stopped: {}", reason),
161 };
162 f.write_str(msg)
163 }
164}
165
166impl std::error::Error for Error {}
167
168impl From<bitreader::BitReaderError> for Error {
169 #[cold]
170 #[cfg_attr(debug_assertions, track_caller)]
171 fn from(err: bitreader::BitReaderError) -> Self {
172 log::warn!("bitreader: {err}");
173 Self::InvalidData("truncated bits")
174 }
175}
176
177impl From<std::io::Error> for Error {
178 fn from(err: std::io::Error) -> Self {
179 match err.kind() {
180 std::io::ErrorKind::UnexpectedEof => Self::UnexpectedEOF,
181 _ => Self::Io(err),
182 }
183 }
184}
185
186impl From<std::string::FromUtf8Error> for Error {
187 fn from(_: std::string::FromUtf8Error) -> Self {
188 Self::InvalidData("invalid utf8")
189 }
190}
191
192impl From<std::num::TryFromIntError> for Error {
193 fn from(_: std::num::TryFromIntError) -> Self {
194 Self::Unsupported("integer conversion failed")
195 }
196}
197
198impl From<Error> for std::io::Error {
199 fn from(err: Error) -> Self {
200 let kind = match err {
201 Error::InvalidData(_) => std::io::ErrorKind::InvalidData,
202 Error::UnexpectedEOF => std::io::ErrorKind::UnexpectedEof,
203 Error::Io(io_err) => return io_err,
204 _ => std::io::ErrorKind::Other,
205 };
206 Self::new(kind, err)
207 }
208}
209
210impl From<TryReserveError> for Error {
211 fn from(_: TryReserveError) -> Self {
212 Self::OutOfMemory
213 }
214}
215
216impl From<enough::StopReason> for Error {
217 fn from(reason: enough::StopReason) -> Self {
218 Self::Stopped(reason)
219 }
220}
221
222pub type Result<T, E = Error> = std::result::Result<T, E>;
224
225#[derive(Debug, Clone, Copy)]
234struct BoxHeader {
235 name: BoxType,
237 size: u64,
239 offset: u64,
241 #[allow(unused)]
243 uuid: Option<[u8; 16]>,
244}
245
246impl BoxHeader {
247 const MIN_SIZE: u64 = 8;
249 const MIN_LARGE_SIZE: u64 = 16;
251}
252
253#[derive(Debug)]
255#[allow(unused)]
256struct FileTypeBox {
257 major_brand: FourCC,
258 minor_version: u32,
259 compatible_brands: TryVec<FourCC>,
260}
261
262#[derive(Debug)]
264#[allow(unused)]
265struct HandlerBox {
266 handler_type: FourCC,
267}
268
269#[derive(Debug, Clone, PartialEq, Eq)]
274pub struct AV1Config {
275 pub profile: u8,
277 pub level: u8,
279 pub tier: u8,
281 pub bit_depth: u8,
283 pub monochrome: bool,
285 pub chroma_subsampling_x: u8,
287 pub chroma_subsampling_y: u8,
289 pub chroma_sample_position: u8,
291}
292
293#[derive(Debug, Clone, PartialEq, Eq)]
298pub enum ColorInformation {
299 Nclx {
301 color_primaries: u16,
303 transfer_characteristics: u16,
305 matrix_coefficients: u16,
307 full_range: bool,
309 },
310 IccProfile(std::vec::Vec<u8>),
312}
313
314#[derive(Debug, Clone, Copy, PartialEq, Eq)]
319pub struct ImageRotation {
320 pub angle: u16,
322}
323
324#[derive(Debug, Clone, Copy, PartialEq, Eq)]
329pub struct ImageMirror {
330 pub axis: u8,
333}
334
335#[derive(Debug, Clone, Copy, PartialEq, Eq)]
341pub struct CleanAperture {
342 pub width_n: u32,
344 pub width_d: u32,
346 pub height_n: u32,
348 pub height_d: u32,
350 pub horiz_off_n: i32,
352 pub horiz_off_d: u32,
354 pub vert_off_n: i32,
356 pub vert_off_d: u32,
358}
359
360#[derive(Debug, Clone, Copy, PartialEq, Eq)]
365pub struct PixelAspectRatio {
366 pub h_spacing: u32,
368 pub v_spacing: u32,
370}
371
372#[derive(Debug, Clone, Copy, PartialEq, Eq)]
377pub struct ContentLightLevel {
378 pub max_content_light_level: u16,
380 pub max_pic_average_light_level: u16,
382}
383
384#[derive(Debug, Clone, Copy, PartialEq, Eq)]
389pub struct MasteringDisplayColourVolume {
390 pub primaries: [(u16, u16); 3],
393 pub white_point: (u16, u16),
395 pub max_luminance: u32,
397 pub min_luminance: u32,
399}
400
401#[derive(Debug, Clone, Copy, PartialEq, Eq)]
407pub struct ContentColourVolume {
408 pub primaries: Option<[(i32, i32); 3]>,
411 pub min_luminance: Option<u32>,
413 pub max_luminance: Option<u32>,
415 pub avg_luminance: Option<u32>,
417}
418
419#[derive(Debug, Clone, Copy, PartialEq, Eq)]
424pub struct AmbientViewingEnvironment {
425 pub ambient_illuminance: u32,
427 pub ambient_light_x: u16,
429 pub ambient_light_y: u16,
431}
432
433#[derive(Debug, Clone, Copy, PartialEq, Eq)]
438pub struct GainMapChannel {
439 pub gain_map_min_n: i32,
441 pub gain_map_min_d: u32,
443 pub gain_map_max_n: i32,
445 pub gain_map_max_d: u32,
447 pub gamma_n: u32,
449 pub gamma_d: u32,
451 pub base_offset_n: i32,
453 pub base_offset_d: u32,
455 pub alternate_offset_n: i32,
457 pub alternate_offset_d: u32,
459}
460
461#[derive(Debug, Clone, PartialEq, Eq)]
469pub struct GainMapMetadata {
470 pub is_multichannel: bool,
473 pub use_base_colour_space: bool,
476 pub backward_direction: bool,
480 pub base_hdr_headroom_n: u32,
482 pub base_hdr_headroom_d: u32,
484 pub alternate_hdr_headroom_n: u32,
486 pub alternate_hdr_headroom_d: u32,
488 pub channels: [GainMapChannel; 3],
491}
492
493impl GainMapMetadata {
494 pub fn parse_tmap_bytes(data: &[u8]) -> Result<Self> {
499 parse_tone_map_image(data)
500 }
501
502 pub fn to_bytes(&self) -> std::vec::Vec<u8> {
521 let channel_count = if self.is_multichannel { 3usize } else { 1usize };
522 let mut buf = std::vec::Vec::with_capacity(6 + 16 + channel_count * 40);
523 buf.push(0u8); buf.extend_from_slice(&0u16.to_be_bytes()); buf.extend_from_slice(&0u16.to_be_bytes()); let flags = (u8::from(self.is_multichannel) << 7)
527 | (u8::from(self.use_base_colour_space) << 6)
528 | (u8::from(self.backward_direction) << 2);
529 buf.push(flags);
530 buf.extend_from_slice(&self.base_hdr_headroom_n.to_be_bytes());
531 buf.extend_from_slice(&self.base_hdr_headroom_d.to_be_bytes());
532 buf.extend_from_slice(&self.alternate_hdr_headroom_n.to_be_bytes());
533 buf.extend_from_slice(&self.alternate_hdr_headroom_d.to_be_bytes());
534 for ch in self.channels.iter().take(channel_count) {
535 buf.extend_from_slice(&ch.gain_map_min_n.to_be_bytes());
536 buf.extend_from_slice(&ch.gain_map_min_d.to_be_bytes());
537 buf.extend_from_slice(&ch.gain_map_max_n.to_be_bytes());
538 buf.extend_from_slice(&ch.gain_map_max_d.to_be_bytes());
539 buf.extend_from_slice(&ch.gamma_n.to_be_bytes());
540 buf.extend_from_slice(&ch.gamma_d.to_be_bytes());
541 buf.extend_from_slice(&ch.base_offset_n.to_be_bytes());
542 buf.extend_from_slice(&ch.base_offset_d.to_be_bytes());
543 buf.extend_from_slice(&ch.alternate_offset_n.to_be_bytes());
544 buf.extend_from_slice(&ch.alternate_offset_d.to_be_bytes());
545 }
546 buf
547 }
548}
549
550impl From<&GainMapChannel> for zencodec::GainMapChannel {
553 fn from(ch: &GainMapChannel) -> Self {
554 Self {
555 min: ch.gain_map_min_n as f64 / ch.gain_map_min_d.max(1) as f64,
556 max: ch.gain_map_max_n as f64 / ch.gain_map_max_d.max(1) as f64,
557 gamma: ch.gamma_n as f64 / ch.gamma_d.max(1) as f64,
558 base_offset: ch.base_offset_n as f64 / ch.base_offset_d.max(1) as f64,
559 alternate_offset: ch.alternate_offset_n as f64 / ch.alternate_offset_d.max(1) as f64,
560 }
561 }
562}
563
564impl From<&GainMapMetadata> for zencodec::GainMapParams {
565 fn from(md: &GainMapMetadata) -> Self {
566 let mut p = Self::default();
567 p.channels = [
568 zencodec::GainMapChannel::from(&md.channels[0]),
569 zencodec::GainMapChannel::from(&md.channels[1]),
570 zencodec::GainMapChannel::from(&md.channels[2]),
571 ];
572 p.base_hdr_headroom =
573 md.base_hdr_headroom_n as f64 / md.base_hdr_headroom_d.max(1) as f64;
574 p.alternate_hdr_headroom =
575 md.alternate_hdr_headroom_n as f64 / md.alternate_hdr_headroom_d.max(1) as f64;
576 p.use_base_color_space = md.use_base_colour_space;
577 p.backward_direction = md.backward_direction;
578 p
579 }
580}
581
582impl From<&zencodec::GainMapChannel> for GainMapChannel {
583 fn from(ch: &zencodec::GainMapChannel) -> Self {
584 use zencodec::gainmap::{Fraction, UFraction};
585 let min = Fraction::from_f64_cf(ch.min);
586 let max = Fraction::from_f64_cf(ch.max);
587 let gamma = UFraction::from_f64_cf(ch.gamma);
588 let base_off = Fraction::from_f64_cf(ch.base_offset);
589 let alt_off = Fraction::from_f64_cf(ch.alternate_offset);
590 Self {
591 gain_map_min_n: min.numerator,
592 gain_map_min_d: min.denominator,
593 gain_map_max_n: max.numerator,
594 gain_map_max_d: max.denominator,
595 gamma_n: gamma.numerator,
596 gamma_d: gamma.denominator,
597 base_offset_n: base_off.numerator,
598 base_offset_d: base_off.denominator,
599 alternate_offset_n: alt_off.numerator,
600 alternate_offset_d: alt_off.denominator,
601 }
602 }
603}
604
605impl From<&zencodec::GainMapParams> for GainMapMetadata {
606 fn from(p: &zencodec::GainMapParams) -> Self {
607 use zencodec::gainmap::UFraction;
608 let headroom_base = UFraction::from_f64_cf(p.base_hdr_headroom);
609 let headroom_alt = UFraction::from_f64_cf(p.alternate_hdr_headroom);
610 Self {
611 is_multichannel: !p.is_single_channel(),
612 use_base_colour_space: p.use_base_color_space,
613 backward_direction: p.backward_direction,
614 base_hdr_headroom_n: headroom_base.numerator,
615 base_hdr_headroom_d: headroom_base.denominator,
616 alternate_hdr_headroom_n: headroom_alt.numerator,
617 alternate_hdr_headroom_d: headroom_alt.denominator,
618 channels: [
619 GainMapChannel::from(&p.channels[0]),
620 GainMapChannel::from(&p.channels[1]),
621 GainMapChannel::from(&p.channels[2]),
622 ],
623 }
624 }
625}
626
627#[derive(Debug, Clone)]
646pub struct AvifGainMap {
647 pub metadata: GainMapMetadata,
649 pub gain_map_data: std::vec::Vec<u8>,
652 pub alt_color_info: Option<ColorInformation>,
655}
656
657#[derive(Debug, Clone)]
679pub struct AvifDepthMap {
680 pub data: std::vec::Vec<u8>,
683 pub width: u32,
685 pub height: u32,
687 pub av1_config: Option<AV1Config>,
689 pub color_info: Option<ColorInformation>,
691}
692
693#[derive(Debug, Clone, Copy, PartialEq, Eq)]
698pub struct OperatingPointSelector {
699 pub op_index: u8,
701}
702
703#[derive(Debug, Clone, Copy, PartialEq, Eq)]
708pub struct LayerSelector {
709 pub layer_id: u16,
711}
712
713#[derive(Debug, Clone, Copy, PartialEq, Eq)]
719pub struct AV1LayeredImageIndexing {
720 pub layer_sizes: [u32; 3],
723}
724
725#[derive(Debug, Clone, Copy)]
729#[derive(Default)]
730pub struct ParseOptions {
731 pub lenient: bool,
739}
740
741#[derive(Debug, Clone)]
767pub struct DecodeConfig {
768 pub peak_memory_limit: Option<u64>,
771
772 pub total_megapixels_limit: Option<u32>,
775
776 pub max_animation_frames: Option<u32>,
779
780 pub max_grid_tiles: Option<u32>,
783
784 pub lenient: bool,
787}
788
789impl Default for DecodeConfig {
790 fn default() -> Self {
791 Self {
792 peak_memory_limit: Some(1_000_000_000),
793 total_megapixels_limit: Some(512),
794 max_animation_frames: Some(10_000),
795 max_grid_tiles: Some(1_000),
796 lenient: false,
797 }
798 }
799}
800
801impl DecodeConfig {
802 pub fn unlimited() -> Self {
806 Self {
807 peak_memory_limit: None,
808 total_megapixels_limit: None,
809 max_animation_frames: None,
810 max_grid_tiles: None,
811 lenient: false,
812 }
813 }
814
815 pub fn with_peak_memory_limit(mut self, bytes: u64) -> Self {
817 self.peak_memory_limit = Some(bytes);
818 self
819 }
820
821 pub fn with_total_megapixels_limit(mut self, megapixels: u32) -> Self {
823 self.total_megapixels_limit = Some(megapixels);
824 self
825 }
826
827 pub fn with_max_animation_frames(mut self, frames: u32) -> Self {
829 self.max_animation_frames = Some(frames);
830 self
831 }
832
833 pub fn with_max_grid_tiles(mut self, tiles: u32) -> Self {
835 self.max_grid_tiles = Some(tiles);
836 self
837 }
838
839 pub fn lenient(mut self, lenient: bool) -> Self {
841 self.lenient = lenient;
842 self
843 }
844}
845
846#[derive(Debug, Clone, PartialEq)]
848pub struct GridConfig {
866 pub rows: u8,
868 pub columns: u8,
870 pub output_width: u32,
872 pub output_height: u32,
874}
875
876#[cfg(feature = "eager")]
878#[deprecated(since = "1.5.0", note = "Use `AvifParser::frame()` which returns `FrameRef` instead")]
879#[derive(Debug)]
880pub struct AnimationFrame {
881 pub data: TryVec<u8>,
883 pub duration_ms: u32,
885}
886
887#[cfg(feature = "eager")]
889#[deprecated(since = "1.5.0", note = "Use `AvifParser::animation_info()` and `AvifParser::frames()` instead")]
890#[derive(Debug)]
891#[allow(deprecated)]
892pub struct AnimationConfig {
893 pub loop_count: u32,
895 pub frames: TryVec<AnimationFrame>,
897}
898
899#[derive(Debug)]
902struct MovieHeader {
903 _timescale: u32,
904 _duration: u64,
905}
906
907#[derive(Debug)]
908struct MediaHeader {
909 timescale: u32,
910 _duration: u64,
911}
912
913#[derive(Debug)]
914struct TimeToSampleEntry {
915 sample_count: u32,
916 sample_delta: u32,
917}
918
919#[derive(Debug)]
920struct SampleToChunkEntry {
921 first_chunk: u32,
922 samples_per_chunk: u32,
923 _sample_description_index: u32,
924}
925
926#[derive(Debug)]
927struct SampleTable {
928 time_to_sample: TryVec<TimeToSampleEntry>,
929 sample_sizes: TryVec<u32>,
930 sample_offsets: TryVec<u64>,
933}
934
935#[derive(Debug)]
937struct TrackReference {
938 reference_type: FourCC,
939 track_ids: TryVec<u32>,
940}
941
942#[derive(Debug, Clone, Default)]
944struct TrackCodecConfig {
945 av1_config: Option<AV1Config>,
946 color_info: Option<ColorInformation>,
947}
948
949#[derive(Debug)]
951struct ParsedTrack {
952 track_id: u32,
953 handler_type: FourCC,
954 media_timescale: u32,
955 sample_table: SampleTable,
956 references: TryVec<TrackReference>,
957 loop_count: u32,
958 codec_config: TrackCodecConfig,
959}
960
961struct ParsedAnimationData {
963 color_timescale: u32,
964 color_sample_table: SampleTable,
965 alpha_timescale: Option<u32>,
966 alpha_sample_table: Option<SampleTable>,
967 loop_count: u32,
968 color_codec_config: TrackCodecConfig,
969}
970
971#[cfg(feature = "eager")]
972#[deprecated(since = "1.5.0", note = "Use `AvifParser` for zero-copy parsing instead")]
973#[derive(Debug, Default)]
974#[allow(deprecated)]
975pub struct AvifData {
976 pub primary_item: TryVec<u8>,
980 pub alpha_item: Option<TryVec<u8>>,
984 pub premultiplied_alpha: bool,
988
989 pub grid_config: Option<GridConfig>,
1013
1014 pub grid_tiles: TryVec<TryVec<u8>>,
1022
1023 pub animation: Option<AnimationConfig>,
1027
1028 pub av1_config: Option<AV1Config>,
1030
1031 pub color_info: Option<ColorInformation>,
1033
1034 pub rotation: Option<ImageRotation>,
1036
1037 pub mirror: Option<ImageMirror>,
1039
1040 pub clean_aperture: Option<CleanAperture>,
1042
1043 pub pixel_aspect_ratio: Option<PixelAspectRatio>,
1045
1046 pub content_light_level: Option<ContentLightLevel>,
1048
1049 pub mastering_display: Option<MasteringDisplayColourVolume>,
1051
1052 pub content_colour_volume: Option<ContentColourVolume>,
1054
1055 pub ambient_viewing: Option<AmbientViewingEnvironment>,
1057
1058 pub operating_point: Option<OperatingPointSelector>,
1060
1061 pub layer_selector: Option<LayerSelector>,
1063
1064 pub layered_image_indexing: Option<AV1LayeredImageIndexing>,
1066
1067 pub exif: Option<TryVec<u8>>,
1071
1072 pub xmp: Option<TryVec<u8>>,
1076
1077 pub gain_map_metadata: Option<GainMapMetadata>,
1079
1080 pub gain_map_item: Option<TryVec<u8>>,
1082
1083 pub gain_map_color_info: Option<ColorInformation>,
1085
1086 pub depth_item: Option<TryVec<u8>>,
1088
1089 pub depth_width: u32,
1091
1092 pub depth_height: u32,
1094
1095 pub depth_av1_config: Option<AV1Config>,
1097
1098 pub depth_color_info: Option<ColorInformation>,
1100
1101 pub major_brand: [u8; 4],
1103
1104 pub compatible_brands: std::vec::Vec<[u8; 4]>,
1106}
1107
1108#[cfg(feature = "eager")]
1109#[allow(deprecated)]
1110impl AvifData {
1111 pub fn gain_map(&self) -> Option<AvifGainMap> {
1116 let metadata = self.gain_map_metadata.as_ref()?.clone();
1117 let gain_map_data = self.gain_map_item.as_ref()?.to_vec();
1118 Some(AvifGainMap {
1119 metadata,
1120 gain_map_data,
1121 alt_color_info: self.gain_map_color_info.clone(),
1122 })
1123 }
1124
1125 pub fn depth_map(&self) -> Option<AvifDepthMap> {
1130 let data = self.depth_item.as_ref()?.to_vec();
1131 Some(AvifDepthMap {
1132 data,
1133 width: self.depth_width,
1134 height: self.depth_height,
1135 av1_config: self.depth_av1_config.clone(),
1136 color_info: self.depth_color_info.clone(),
1137 })
1138 }
1139}
1140
1141#[cfg(feature = "eager")]
1162#[allow(deprecated)]
1163impl AvifData {
1164 #[deprecated(since = "1.5.0", note = "Use `AvifParser::from_reader()` instead")]
1165 pub fn from_reader<R: Read>(reader: &mut R) -> Result<Self> {
1166 read_avif(reader)
1167 }
1168
1169 pub fn primary_item_metadata(&self) -> Result<AV1Metadata> {
1171 AV1Metadata::parse_av1_bitstream(&self.primary_item)
1172 }
1173
1174 pub fn alpha_item_metadata(&self) -> Result<Option<AV1Metadata>> {
1176 self.alpha_item.as_deref().map(AV1Metadata::parse_av1_bitstream).transpose()
1177 }
1178}
1179
1180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1186pub struct ChromaSubsampling {
1187 pub horizontal: bool,
1189 pub vertical: bool,
1191}
1192
1193impl ChromaSubsampling {
1194 pub const NONE: Self = Self { horizontal: false, vertical: false };
1196 pub const YUV420: Self = Self { horizontal: true, vertical: true };
1198 pub const YUV422: Self = Self { horizontal: true, vertical: false };
1200}
1201
1202impl From<(bool, bool)> for ChromaSubsampling {
1203 fn from((h, v): (bool, bool)) -> Self {
1204 Self { horizontal: h, vertical: v }
1205 }
1206}
1207
1208impl From<ChromaSubsampling> for (bool, bool) {
1209 fn from(cs: ChromaSubsampling) -> Self {
1210 (cs.horizontal, cs.vertical)
1211 }
1212}
1213
1214#[non_exhaustive]
1218#[derive(Debug, Clone)]
1219pub struct AV1Metadata {
1220 pub still_picture: bool,
1222 pub max_frame_width: NonZeroU32,
1223 pub max_frame_height: NonZeroU32,
1224 pub bit_depth: u8,
1226 pub seq_profile: u8,
1228 pub chroma_subsampling: ChromaSubsampling,
1231 pub monochrome: bool,
1232 pub base_q_idx: Option<u8>,
1236 pub lossless: Option<bool>,
1240}
1241
1242impl AV1Metadata {
1243 #[inline(never)]
1251 pub fn parse_av1_bitstream(obu_bitstream: &[u8]) -> Result<Self> {
1252 let (h, frame_quant) = obu::parse_obu_with_frame_info(obu_bitstream)?;
1253 let no_chroma_subsampling = !h.color.chroma_subsampling.horizontal
1254 && !h.color.chroma_subsampling.vertical;
1255 Ok(Self {
1256 still_picture: h.still_picture,
1257 max_frame_width: h.max_frame_width,
1258 max_frame_height: h.max_frame_height,
1259 bit_depth: h.color.bit_depth,
1260 seq_profile: h.seq_profile,
1261 chroma_subsampling: h.color.chroma_subsampling,
1262 monochrome: h.color.monochrome,
1263 base_q_idx: frame_quant.map(|fq| fq.base_q_idx),
1264 lossless: frame_quant.map(|fq| fq.coded_lossless && no_chroma_subsampling),
1265 })
1266 }
1267}
1268
1269pub struct FrameRef<'a> {
1274 pub data: Cow<'a, [u8]>,
1275 pub alpha_data: Option<Cow<'a, [u8]>>,
1277 pub duration_ms: u32,
1278}
1279
1280struct MdatBounds {
1282 offset: u64,
1283 length: u64,
1284}
1285
1286struct ItemExtents {
1288 construction_method: ConstructionMethod,
1289 extents: TryVec<ExtentRange>,
1290}
1291
1292pub struct AvifParser<'data> {
1318 raw: Cow<'data, [u8]>,
1319 mdat_bounds: TryVec<MdatBounds>,
1320 idat: Option<TryVec<u8>>,
1321 primary: ItemExtents,
1322 alpha: Option<ItemExtents>,
1323 grid_config: Option<GridConfig>,
1324 tiles: TryVec<ItemExtents>,
1325 animation_data: Option<AnimationParserData>,
1326 premultiplied_alpha: bool,
1327 av1_config: Option<AV1Config>,
1328 color_info: Option<ColorInformation>,
1329 rotation: Option<ImageRotation>,
1330 mirror: Option<ImageMirror>,
1331 clean_aperture: Option<CleanAperture>,
1332 pixel_aspect_ratio: Option<PixelAspectRatio>,
1333 content_light_level: Option<ContentLightLevel>,
1334 mastering_display: Option<MasteringDisplayColourVolume>,
1335 content_colour_volume: Option<ContentColourVolume>,
1336 ambient_viewing: Option<AmbientViewingEnvironment>,
1337 operating_point: Option<OperatingPointSelector>,
1338 layer_selector: Option<LayerSelector>,
1339 layered_image_indexing: Option<AV1LayeredImageIndexing>,
1340 exif_item: Option<ItemExtents>,
1341 xmp_item: Option<ItemExtents>,
1342 gain_map_metadata: Option<GainMapMetadata>,
1343 gain_map: Option<ItemExtents>,
1344 gain_map_color_info: Option<ColorInformation>,
1345 depth_item: Option<ItemExtents>,
1346 depth_width: u32,
1347 depth_height: u32,
1348 depth_av1_config: Option<AV1Config>,
1349 depth_color_info: Option<ColorInformation>,
1350 major_brand: [u8; 4],
1351 compatible_brands: std::vec::Vec<[u8; 4]>,
1352}
1353
1354struct AnimationParserData {
1355 media_timescale: u32,
1356 sample_table: SampleTable,
1357 alpha_media_timescale: Option<u32>,
1358 alpha_sample_table: Option<SampleTable>,
1359 loop_count: u32,
1360 codec_config: TrackCodecConfig,
1361}
1362
1363#[derive(Debug, Clone, Copy)]
1365pub struct AnimationInfo {
1366 pub frame_count: usize,
1367 pub loop_count: u32,
1368 pub has_alpha: bool,
1370 pub timescale: u32,
1372}
1373
1374struct ParsedStructure {
1376 meta: Option<AvifInternalMeta>,
1378 mdat_bounds: TryVec<MdatBounds>,
1379 animation_data: Option<ParsedAnimationData>,
1380 major_brand: [u8; 4],
1381 compatible_brands: std::vec::Vec<[u8; 4]>,
1382}
1383
1384impl<'data> AvifParser<'data> {
1385 pub fn from_bytes(data: &'data [u8]) -> Result<Self> {
1394 Self::from_bytes_with_config(data, &DecodeConfig::default(), &Unstoppable)
1395 }
1396
1397 pub fn from_bytes_with_config(
1399 data: &'data [u8],
1400 config: &DecodeConfig,
1401 stop: &dyn Stop,
1402 ) -> Result<Self> {
1403 let parsed = Self::parse_raw(data, config, stop)?;
1404 Self::build(Cow::Borrowed(data), parsed, config)
1405 }
1406
1407 pub fn from_owned(data: std::vec::Vec<u8>) -> Result<AvifParser<'static>> {
1412 AvifParser::from_owned_with_config(data, &DecodeConfig::default(), &Unstoppable)
1413 }
1414
1415 pub fn from_owned_with_config(
1417 data: std::vec::Vec<u8>,
1418 config: &DecodeConfig,
1419 stop: &dyn Stop,
1420 ) -> Result<AvifParser<'static>> {
1421 let parsed = AvifParser::parse_raw(&data, config, stop)?;
1422 AvifParser::build(Cow::Owned(data), parsed, config)
1423 }
1424
1425 pub fn from_reader<R: Read>(reader: &mut R) -> Result<AvifParser<'static>> {
1427 AvifParser::from_reader_with_config(reader, &DecodeConfig::default(), &Unstoppable)
1428 }
1429
1430 pub fn from_reader_with_config<R: Read>(
1435 reader: &mut R,
1436 config: &DecodeConfig,
1437 stop: &dyn Stop,
1438 ) -> Result<AvifParser<'static>> {
1439 let buf = if let Some(limit) = config.peak_memory_limit {
1440 let mut limited = reader.take(limit.saturating_add(1));
1441 let mut buf = std::vec::Vec::new();
1442 limited.read_to_end(&mut buf)?;
1443 if buf.len() as u64 > limit {
1444 return Err(Error::ResourceLimitExceeded(
1445 "input exceeds peak_memory_limit",
1446 ));
1447 }
1448 buf
1449 } else {
1450 let mut buf = std::vec::Vec::new();
1451 reader.read_to_end(&mut buf)?;
1452 buf
1453 };
1454 AvifParser::from_owned_with_config(buf, config, stop)
1455 }
1456
1457 fn parse_raw(data: &[u8], config: &DecodeConfig, stop: &dyn Stop) -> Result<ParsedStructure> {
1464 let parse_opts = ParseOptions { lenient: config.lenient };
1465 let mut cursor = std::io::Cursor::new(data);
1466 let mut f = OffsetReader::new(&mut cursor);
1467 let mut iter = BoxIter::with_max_remaining(&mut f, data.len() as u64);
1468
1469 let (major_brand, compatible_brands) = if let Some(mut b) = iter.next_box()? {
1471 if b.head.name == BoxType::FileTypeBox {
1472 let ftyp = read_ftyp(&mut b)?;
1473 if ftyp.major_brand != b"avif" && ftyp.major_brand != b"avis" {
1474 return Err(Error::InvalidData("ftyp must be 'avif' or 'avis'"));
1475 }
1476 let major = ftyp.major_brand.value;
1477 let compat = ftyp.compatible_brands.iter().map(|b| b.value).collect();
1478 (major, compat)
1479 } else {
1480 return Err(Error::InvalidData("'ftyp' box must occur first"));
1481 }
1482 } else {
1483 return Err(Error::InvalidData("'ftyp' box must occur first"));
1484 };
1485
1486 let mut meta = None;
1487 let mut mdat_bounds = TryVec::new();
1488 let mut animation_data: Option<ParsedAnimationData> = None;
1489
1490 while let Some(mut b) = iter.next_box()? {
1491 stop.check()?;
1492
1493 match b.head.name {
1494 BoxType::MetadataBox => {
1495 if meta.is_some() {
1496 return Err(Error::InvalidData(
1497 "There should be zero or one meta boxes per ISO 14496-12:2015 § 8.11.1.1",
1498 ));
1499 }
1500 meta = Some(read_avif_meta(&mut b, &parse_opts)?);
1501 }
1502 BoxType::MovieBox => {
1503 let tracks = read_moov(&mut b)?;
1504 if !tracks.is_empty() {
1505 animation_data = Some(associate_tracks(tracks)?);
1506 }
1507 }
1508 BoxType::MediaDataBox => {
1509 if b.bytes_left() > 0 {
1510 let offset = b.offset();
1511 let length = b.bytes_left();
1512 mdat_bounds.push(MdatBounds { offset, length })?;
1513 }
1514 skip_box_content(&mut b)?;
1516 }
1517 _ => skip_box_content(&mut b)?,
1518 }
1519
1520 check_parser_state(&b.head, &b.content)?;
1521 }
1522
1523 if meta.is_none() && animation_data.is_none() {
1526 return Err(Error::InvalidData("missing meta"));
1527 }
1528
1529 Ok(ParsedStructure { meta, mdat_bounds, animation_data, major_brand, compatible_brands })
1530 }
1531
1532 fn build(raw: Cow<'data, [u8]>, parsed: ParsedStructure, config: &DecodeConfig) -> Result<Self> {
1534 let tracker = ResourceTracker::new(config);
1535
1536 let animation_data = if let Some(anim) = parsed.animation_data {
1538 tracker.validate_animation_frames(anim.color_sample_table.sample_sizes.len() as u32)?;
1539 Some(AnimationParserData {
1540 media_timescale: anim.color_timescale,
1541 sample_table: anim.color_sample_table,
1542 alpha_media_timescale: anim.alpha_timescale,
1543 alpha_sample_table: anim.alpha_sample_table,
1544 loop_count: anim.loop_count,
1545 codec_config: anim.color_codec_config,
1546 })
1547 } else {
1548 None
1549 };
1550
1551 let Some(meta) = parsed.meta else {
1554 let track_config = animation_data.as_ref()
1555 .map(|a| a.codec_config.clone())
1556 .unwrap_or_default();
1557 return Ok(Self {
1558 raw,
1559 mdat_bounds: parsed.mdat_bounds,
1560 idat: None,
1561 primary: ItemExtents { construction_method: ConstructionMethod::File, extents: TryVec::new() },
1562 alpha: None,
1563 grid_config: None,
1564 tiles: TryVec::new(),
1565 animation_data,
1566 premultiplied_alpha: false,
1567 av1_config: track_config.av1_config,
1568 color_info: track_config.color_info,
1569 rotation: None,
1570 mirror: None,
1571 clean_aperture: None,
1572 pixel_aspect_ratio: None,
1573 content_light_level: None,
1574 mastering_display: None,
1575 content_colour_volume: None,
1576 ambient_viewing: None,
1577 operating_point: None,
1578 layer_selector: None,
1579 layered_image_indexing: None,
1580 exif_item: None,
1581 xmp_item: None,
1582 gain_map_metadata: None,
1583 gain_map: None,
1584 gain_map_color_info: None,
1585 depth_item: None,
1586 depth_width: 0,
1587 depth_height: 0,
1588 depth_av1_config: None,
1589 depth_color_info: None,
1590 major_brand: parsed.major_brand,
1591 compatible_brands: parsed.compatible_brands,
1592 });
1593 };
1594
1595 let primary = Self::get_item_extents(&meta, meta.primary_item_id)?;
1597
1598 let alpha_item_id = meta
1600 .item_references
1601 .iter()
1602 .filter(|iref| {
1603 iref.to_item_id == meta.primary_item_id
1604 && iref.from_item_id != meta.primary_item_id
1605 && iref.item_type == b"auxl"
1606 })
1607 .map(|iref| iref.from_item_id)
1608 .find(|&item_id| {
1609 meta.properties.iter().any(|prop| {
1610 prop.item_id == item_id
1611 && match &prop.property {
1612 ItemProperty::AuxiliaryType(urn) => {
1613 urn.type_subtype().0 == b"urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"
1614 }
1615 _ => false,
1616 }
1617 })
1618 });
1619
1620 let alpha = alpha_item_id
1621 .map(|id| Self::get_item_extents(&meta, id))
1622 .transpose()?;
1623
1624 let premultiplied_alpha = alpha_item_id.is_some_and(|alpha_id| {
1626 meta.item_references.iter().any(|iref| {
1627 iref.from_item_id == meta.primary_item_id
1628 && iref.to_item_id == alpha_id
1629 && iref.item_type == b"prem"
1630 })
1631 });
1632
1633 let depth_item_id = meta
1635 .item_references
1636 .iter()
1637 .filter(|iref| {
1638 iref.to_item_id == meta.primary_item_id
1639 && iref.from_item_id != meta.primary_item_id
1640 && iref.item_type == b"auxl"
1641 })
1642 .map(|iref| iref.from_item_id)
1643 .find(|&item_id| {
1644 if alpha_item_id == Some(item_id) {
1646 return false;
1647 }
1648 meta.properties.iter().any(|prop| {
1649 prop.item_id == item_id
1650 && match &prop.property {
1651 ItemProperty::AuxiliaryType(urn) => {
1652 is_depth_auxiliary_urn(urn.type_subtype().0)
1653 }
1654 _ => false,
1655 }
1656 })
1657 });
1658
1659 let (depth_item, depth_width, depth_height, depth_av1_config, depth_color_info) =
1660 if let Some(depth_id) = depth_item_id {
1661 let extents = Self::get_item_extents(&meta, depth_id)?;
1662 let dims = meta.properties.iter().find_map(|p| {
1664 if p.item_id == depth_id {
1665 match &p.property {
1666 ItemProperty::ImageSpatialExtents(e) => Some((e.width, e.height)),
1667 _ => None,
1668 }
1669 } else {
1670 None
1671 }
1672 });
1673 let (w, h) = dims.unwrap_or((0, 0));
1674 let av1c = meta.properties.iter().find_map(|p| {
1676 if p.item_id == depth_id {
1677 match &p.property {
1678 ItemProperty::AV1Config(c) => Some(c.clone()),
1679 _ => None,
1680 }
1681 } else {
1682 None
1683 }
1684 });
1685 let colr = meta.properties.iter().find_map(|p| {
1687 if p.item_id == depth_id {
1688 match &p.property {
1689 ItemProperty::ColorInformation(c) => Some(c.clone()),
1690 _ => None,
1691 }
1692 } else {
1693 None
1694 }
1695 });
1696 (Some(extents), w, h, av1c, colr)
1697 } else {
1698 (None, 0, 0, None, None)
1699 };
1700
1701 let mut exif_item = None;
1703 let mut xmp_item = None;
1704 for iref in meta.item_references.iter() {
1705 if iref.to_item_id != meta.primary_item_id || iref.item_type != b"cdsc" {
1706 continue;
1707 }
1708 let desc_item_id = iref.from_item_id;
1709 let Some(info) = meta.item_infos.iter().find(|i| i.item_id == desc_item_id) else {
1710 continue;
1711 };
1712 if info.item_type == b"Exif" && exif_item.is_none() {
1713 exif_item = Some(Self::get_item_extents(&meta, desc_item_id)?);
1714 } else if info.item_type == b"mime" && xmp_item.is_none() {
1715 xmp_item = Some(Self::get_item_extents(&meta, desc_item_id)?);
1716 }
1717 }
1718
1719 let is_grid = meta
1721 .item_infos
1722 .iter()
1723 .find(|x| x.item_id == meta.primary_item_id)
1724 .is_some_and(|info| info.item_type == b"grid");
1725
1726 let (grid_config, tiles) = if is_grid {
1728 let mut tiles_with_index: TryVec<(u32, u16)> = TryVec::new();
1729 for iref in meta.item_references.iter() {
1730 if iref.from_item_id == meta.primary_item_id && iref.item_type == b"dimg" {
1731 tiles_with_index.push((iref.to_item_id, iref.reference_index))?;
1732 }
1733 }
1734
1735 tracker.validate_grid_tiles(tiles_with_index.len() as u32)?;
1736 tiles_with_index.sort_by_key(|&(_, idx)| idx);
1737
1738 let mut tile_extents = TryVec::new();
1739 for (tile_id, _) in tiles_with_index.iter() {
1740 tile_extents.push(Self::get_item_extents(&meta, *tile_id)?)?;
1741 }
1742
1743 let mut tile_ids = TryVec::new();
1744 for (tile_id, _) in tiles_with_index.iter() {
1745 tile_ids.push(*tile_id)?;
1746 }
1747
1748 let grid_config = Self::calculate_grid_config(&meta, &tile_ids)?;
1749
1750 for (tile_id, _) in tiles_with_index.iter() {
1752 for prop in meta.properties.iter() {
1753 if prop.item_id == *tile_id {
1754 match &prop.property {
1755 ItemProperty::Rotation(_)
1756 | ItemProperty::Mirror(_)
1757 | ItemProperty::CleanAperture(_) => {
1758 warn!("grid tile {} has a transformative property (irot/imir/clap), violating AVIF spec", tile_id);
1759 }
1760 _ => {}
1761 }
1762 }
1763 }
1764 }
1765
1766 (Some(grid_config), tile_extents)
1767 } else {
1768 (None, TryVec::new())
1769 };
1770
1771 let (gain_map_metadata, gain_map, gain_map_color_info) = {
1773 let tmap_item = meta.item_infos.iter()
1774 .find(|info| info.item_type == b"tmap");
1775
1776 if let Some(tmap_info) = tmap_item {
1777 let tmap_id = tmap_info.item_id;
1778
1779 let mut inputs: TryVec<(u32, u16)> = TryVec::new();
1781 for iref in meta.item_references.iter() {
1782 if iref.from_item_id == tmap_id && iref.item_type == b"dimg" {
1783 inputs.push((iref.to_item_id, iref.reference_index))?;
1784 }
1785 }
1786 inputs.sort_by_key(|&(_, idx)| idx);
1787
1788 if inputs.len() >= 2 {
1789 let base_item_id = inputs[0].0;
1790 let gmap_item_id = inputs[1].0;
1791
1792 if base_item_id == meta.primary_item_id {
1793 let tmap_extents = Self::get_item_extents(&meta, tmap_id)?;
1795 let tmap_data = Self::resolve_extents_from_raw(
1796 raw.as_ref(), &parsed.mdat_bounds, &tmap_extents,
1797 )?;
1798 let metadata = parse_tone_map_image(&tmap_data)?;
1799
1800 let gmap_extents = Self::get_item_extents(&meta, gmap_item_id)?;
1802
1803 let alt_color = meta.properties.iter().find_map(|p| {
1805 if p.item_id == tmap_id {
1806 match &p.property {
1807 ItemProperty::ColorInformation(c) => Some(c.clone()),
1808 _ => None,
1809 }
1810 } else {
1811 None
1812 }
1813 });
1814
1815 (Some(metadata), Some(gmap_extents), alt_color)
1816 } else {
1817 (None, None, None)
1818 }
1819 } else {
1820 (None, None, None)
1821 }
1822 } else {
1823 (None, None, None)
1824 }
1825 };
1826
1827 macro_rules! find_prop {
1829 ($variant:ident) => {
1830 meta.properties.iter().find_map(|p| {
1831 if p.item_id == meta.primary_item_id {
1832 match &p.property {
1833 ItemProperty::$variant(c) => Some(c.clone()),
1834 _ => None,
1835 }
1836 } else {
1837 None
1838 }
1839 })
1840 };
1841 }
1842
1843 let track_config = animation_data.as_ref().map(|a| &a.codec_config);
1844 let av1_config = find_prop!(AV1Config)
1845 .or_else(|| track_config.and_then(|c| c.av1_config.clone()));
1846 let color_info = find_prop!(ColorInformation)
1847 .or_else(|| track_config.and_then(|c| c.color_info.clone()));
1848 let rotation = find_prop!(Rotation);
1849 let mirror = find_prop!(Mirror);
1850 let clean_aperture = find_prop!(CleanAperture);
1851 let pixel_aspect_ratio = find_prop!(PixelAspectRatio);
1852 let content_light_level = find_prop!(ContentLightLevel);
1853 let mastering_display = find_prop!(MasteringDisplayColourVolume);
1854 let content_colour_volume = find_prop!(ContentColourVolume);
1855 let ambient_viewing = find_prop!(AmbientViewingEnvironment);
1856 let operating_point = find_prop!(OperatingPointSelector);
1857 let layer_selector = find_prop!(LayerSelector);
1858 let layered_image_indexing = find_prop!(AV1LayeredImageIndexing);
1859
1860 let idat = if let Some(ref idat_data) = meta.idat {
1862 let mut cloned = TryVec::new();
1863 cloned.extend_from_slice(idat_data)?;
1864 Some(cloned)
1865 } else {
1866 None
1867 };
1868
1869 Ok(Self {
1870 raw,
1871 mdat_bounds: parsed.mdat_bounds,
1872 idat,
1873 primary,
1874 alpha,
1875 grid_config,
1876 tiles,
1877 animation_data,
1878 premultiplied_alpha,
1879 av1_config,
1880 color_info,
1881 rotation,
1882 mirror,
1883 clean_aperture,
1884 pixel_aspect_ratio,
1885 content_light_level,
1886 mastering_display,
1887 content_colour_volume,
1888 ambient_viewing,
1889 operating_point,
1890 layer_selector,
1891 layered_image_indexing,
1892 exif_item,
1893 xmp_item,
1894 gain_map_metadata,
1895 gain_map,
1896 gain_map_color_info,
1897 depth_item,
1898 depth_width,
1899 depth_height,
1900 depth_av1_config,
1901 depth_color_info,
1902 major_brand: parsed.major_brand,
1903 compatible_brands: parsed.compatible_brands,
1904 })
1905 }
1906
1907 fn get_item_extents(meta: &AvifInternalMeta, item_id: u32) -> Result<ItemExtents> {
1913 let item = meta
1914 .iloc_items
1915 .iter()
1916 .find(|item| item.item_id == item_id)
1917 .ok_or(Error::InvalidData("item not found in iloc"))?;
1918
1919 let mut extents = TryVec::new();
1920 for extent in &item.extents {
1921 extents.push(extent.extent_range.clone())?;
1922 }
1923 Ok(ItemExtents {
1924 construction_method: item.construction_method,
1925 extents,
1926 })
1927 }
1928
1929 fn resolve_extents_from_raw(
1932 raw: &[u8],
1933 mdat_bounds: &[MdatBounds],
1934 item: &ItemExtents,
1935 ) -> Result<std::vec::Vec<u8>> {
1936 if item.construction_method != ConstructionMethod::File {
1937 return Err(Error::Unsupported("tmap item must use file construction method"));
1938 }
1939 let mut data = std::vec::Vec::new();
1940 for extent in &item.extents {
1941 let file_offset = extent.start();
1942 let start = usize::try_from(file_offset)?;
1943 let end = match extent {
1944 ExtentRange::WithLength(range) => {
1945 let len = range.end.checked_sub(range.start)
1946 .ok_or(Error::InvalidData("extent range start > end"))?;
1947 start.checked_add(usize::try_from(len)?)
1948 .ok_or(Error::InvalidData("extent end overflow"))?
1949 }
1950 ExtentRange::ToEnd(_) => {
1951 let mut found_end = raw.len();
1953 for mdat in mdat_bounds {
1954 if file_offset >= mdat.offset && file_offset < mdat.offset + mdat.length {
1955 found_end = usize::try_from(mdat.offset + mdat.length)?;
1956 break;
1957 }
1958 }
1959 found_end
1960 }
1961 };
1962 let slice = raw.get(start..end)
1963 .ok_or(Error::InvalidData("tmap extent out of bounds"))?;
1964 data.extend_from_slice(slice);
1965 }
1966 Ok(data)
1967 }
1968
1969 fn resolve_item(&self, item: &ItemExtents) -> Result<Cow<'_, [u8]>> {
1972 match item.construction_method {
1973 ConstructionMethod::Idat => self.resolve_idat_extents(&item.extents),
1974 ConstructionMethod::File => self.resolve_file_extents(&item.extents),
1975 ConstructionMethod::Item => Err(Error::Unsupported("construction_method 'item' not supported")),
1976 }
1977 }
1978
1979 fn resolve_file_extents(&self, extents: &[ExtentRange]) -> Result<Cow<'_, [u8]>> {
1981 let raw = self.raw.as_ref();
1982
1983 if extents.len() == 1 {
1985 let extent = &extents[0];
1986 let (start, end) = self.extent_byte_range(extent)?;
1987 let slice = raw.get(start..end).ok_or(Error::InvalidData("extent out of bounds in raw buffer"))?;
1988 return Ok(Cow::Borrowed(slice));
1989 }
1990
1991 let mut data = TryVec::new();
1993 for extent in extents {
1994 let (start, end) = self.extent_byte_range(extent)?;
1995 let slice = raw.get(start..end).ok_or(Error::InvalidData("extent out of bounds in raw buffer"))?;
1996 data.extend_from_slice(slice)?;
1997 }
1998 Ok(Cow::Owned(data.into_iter().collect()))
1999 }
2000
2001 fn extent_byte_range(&self, extent: &ExtentRange) -> Result<(usize, usize)> {
2003 let file_offset = extent.start();
2004 let start = usize::try_from(file_offset)?;
2005
2006 match extent {
2007 ExtentRange::WithLength(range) => {
2008 let len = range.end.checked_sub(range.start)
2009 .ok_or(Error::InvalidData("extent range start > end"))?;
2010 let end = start.checked_add(usize::try_from(len)?)
2011 .ok_or(Error::InvalidData("extent end overflow"))?;
2012 Ok((start, end))
2013 }
2014 ExtentRange::ToEnd(_) => {
2015 for mdat in &self.mdat_bounds {
2017 if file_offset >= mdat.offset && file_offset < mdat.offset + mdat.length {
2018 let end = usize::try_from(mdat.offset + mdat.length)?;
2019 return Ok((start, end));
2020 }
2021 }
2022 Ok((start, self.raw.len()))
2024 }
2025 }
2026 }
2027
2028 fn resolve_idat_extents(&self, extents: &[ExtentRange]) -> Result<Cow<'_, [u8]>> {
2030 let idat_data = self.idat.as_ref()
2031 .ok_or(Error::InvalidData("idat box missing but construction_method is Idat"))?;
2032
2033 if extents.len() == 1 {
2034 let extent = &extents[0];
2035 let start = usize::try_from(extent.start())?;
2036 let slice = match extent {
2037 ExtentRange::WithLength(range) => {
2038 let len = usize::try_from(range.end - range.start)?;
2039 idat_data.get(start..start + len)
2040 .ok_or(Error::InvalidData("idat extent out of bounds"))?
2041 }
2042 ExtentRange::ToEnd(_) => {
2043 idat_data.get(start..)
2044 .ok_or(Error::InvalidData("idat extent out of bounds"))?
2045 }
2046 };
2047 return Ok(Cow::Borrowed(slice));
2048 }
2049
2050 let mut data = TryVec::new();
2052 for extent in extents {
2053 let start = usize::try_from(extent.start())?;
2054 let slice = match extent {
2055 ExtentRange::WithLength(range) => {
2056 let len = usize::try_from(range.end - range.start)?;
2057 idat_data.get(start..start + len)
2058 .ok_or(Error::InvalidData("idat extent out of bounds"))?
2059 }
2060 ExtentRange::ToEnd(_) => {
2061 idat_data.get(start..)
2062 .ok_or(Error::InvalidData("idat extent out of bounds"))?
2063 }
2064 };
2065 data.extend_from_slice(slice)?;
2066 }
2067 Ok(Cow::Owned(data.into_iter().collect()))
2068 }
2069
2070 fn resolve_frame(&self, index: usize) -> Result<FrameRef<'_>> {
2072 let anim = self.animation_data.as_ref()
2073 .ok_or(Error::InvalidData("not an animated AVIF"))?;
2074
2075 if index >= anim.sample_table.sample_sizes.len() {
2076 return Err(Error::InvalidData("frame index out of bounds"));
2077 }
2078
2079 let duration_ms = self.calculate_frame_duration(&anim.sample_table, anim.media_timescale, index)?;
2080 let (offset, size) = self.calculate_sample_location(&anim.sample_table, index)?;
2081
2082 let start = usize::try_from(offset)?;
2083 let end = start.checked_add(size as usize)
2084 .ok_or(Error::InvalidData("frame end overflow"))?;
2085
2086 let raw = self.raw.as_ref();
2087 let slice = raw.get(start..end)
2088 .ok_or(Error::InvalidData("frame not found in raw buffer"))?;
2089
2090 let alpha_data = if let Some(ref alpha_st) = anim.alpha_sample_table {
2092 let alpha_timescale = anim.alpha_media_timescale.unwrap_or(anim.media_timescale);
2093 if index < alpha_st.sample_sizes.len() {
2094 let (a_offset, a_size) = self.calculate_sample_location(alpha_st, index)?;
2095 let a_start = usize::try_from(a_offset)?;
2096 let a_end = a_start.checked_add(a_size as usize)
2097 .ok_or(Error::InvalidData("alpha frame end overflow"))?;
2098 let a_slice = raw.get(a_start..a_end)
2099 .ok_or(Error::InvalidData("alpha frame not found in raw buffer"))?;
2100 let _ = alpha_timescale; Some(Cow::Borrowed(a_slice))
2102 } else {
2103 warn!("alpha track has fewer frames than color track (index {})", index);
2104 None
2105 }
2106 } else {
2107 None
2108 };
2109
2110 Ok(FrameRef {
2111 data: Cow::Borrowed(slice),
2112 alpha_data,
2113 duration_ms,
2114 })
2115 }
2116
2117 fn calculate_grid_config(meta: &AvifInternalMeta, tile_ids: &[u32]) -> Result<GridConfig> {
2119 for prop in &meta.properties {
2121 if prop.item_id == meta.primary_item_id
2122 && let ItemProperty::ImageGrid(grid) = &prop.property {
2123 return Ok(grid.clone());
2124 }
2125 }
2126
2127 let grid_dims = meta
2129 .properties
2130 .iter()
2131 .find(|p| p.item_id == meta.primary_item_id)
2132 .and_then(|p| match &p.property {
2133 ItemProperty::ImageSpatialExtents(e) => Some(e),
2134 _ => None,
2135 });
2136
2137 let tile_dims = tile_ids.first().and_then(|&tile_id| {
2138 meta.properties
2139 .iter()
2140 .find(|p| p.item_id == tile_id)
2141 .and_then(|p| match &p.property {
2142 ItemProperty::ImageSpatialExtents(e) => Some(e),
2143 _ => None,
2144 })
2145 });
2146
2147 if let (Some(grid), Some(tile)) = (grid_dims, tile_dims)
2148 && tile.width != 0
2149 && tile.height != 0
2150 && grid.width % tile.width == 0
2151 && grid.height % tile.height == 0
2152 {
2153 let columns = grid.width / tile.width;
2154 let rows = grid.height / tile.height;
2155
2156 if columns <= 255 && rows <= 255 {
2157 return Ok(GridConfig {
2158 rows: rows as u8,
2159 columns: columns as u8,
2160 output_width: grid.width,
2161 output_height: grid.height,
2162 });
2163 }
2164 }
2165
2166 let tile_count = tile_ids.len();
2167 Ok(GridConfig {
2168 rows: tile_count.min(255) as u8,
2169 columns: 1,
2170 output_width: 0,
2171 output_height: 0,
2172 })
2173 }
2174
2175 fn calculate_frame_duration(
2177 &self,
2178 st: &SampleTable,
2179 timescale: u32,
2180 index: usize,
2181 ) -> Result<u32> {
2182 let mut current_sample = 0;
2183 for entry in &st.time_to_sample {
2184 if current_sample + entry.sample_count as usize > index {
2185 let duration_ms = if timescale > 0 {
2186 ((entry.sample_delta as u64) * 1000) / (timescale as u64)
2187 } else {
2188 0
2189 };
2190 return Ok(u32::try_from(duration_ms).unwrap_or(u32::MAX));
2191 }
2192 current_sample += entry.sample_count as usize;
2193 }
2194 Ok(0)
2195 }
2196
2197 fn calculate_sample_location(&self, st: &SampleTable, index: usize) -> Result<(u64, u32)> {
2199 let offset = *st
2200 .sample_offsets
2201 .get(index)
2202 .ok_or(Error::InvalidData("sample index out of bounds"))?;
2203 let size = *st
2204 .sample_sizes
2205 .get(index)
2206 .ok_or(Error::InvalidData("sample index out of bounds"))?;
2207 Ok((offset, size))
2208 }
2209
2210 pub fn primary_data(&self) -> Result<Cow<'_, [u8]>> {
2218 self.resolve_item(&self.primary)
2219 }
2220
2221 pub fn alpha_data(&self) -> Option<Result<Cow<'_, [u8]>>> {
2223 self.alpha.as_ref().map(|item| self.resolve_item(item))
2224 }
2225
2226 pub fn tile_data(&self, index: usize) -> Result<Cow<'_, [u8]>> {
2228 let item = self.tiles.get(index)
2229 .ok_or(Error::InvalidData("tile index out of bounds"))?;
2230 self.resolve_item(item)
2231 }
2232
2233 pub fn frame(&self, index: usize) -> Result<FrameRef<'_>> {
2235 self.resolve_frame(index)
2236 }
2237
2238 pub fn frames(&self) -> FrameIterator<'_> {
2240 let count = self
2241 .animation_info()
2242 .map(|info| info.frame_count)
2243 .unwrap_or(0);
2244 FrameIterator { parser: self, index: 0, count }
2245 }
2246
2247 pub fn animation_info(&self) -> Option<AnimationInfo> {
2253 self.animation_data.as_ref().map(|data| AnimationInfo {
2254 frame_count: data.sample_table.sample_sizes.len(),
2255 loop_count: data.loop_count,
2256 has_alpha: data.alpha_sample_table.is_some(),
2257 timescale: data.media_timescale,
2258 })
2259 }
2260
2261 pub fn grid_config(&self) -> Option<&GridConfig> {
2263 self.grid_config.as_ref()
2264 }
2265
2266 pub fn grid_tile_count(&self) -> usize {
2268 self.tiles.len()
2269 }
2270
2271 pub fn premultiplied_alpha(&self) -> bool {
2273 self.premultiplied_alpha
2274 }
2275
2276 pub fn av1_config(&self) -> Option<&AV1Config> {
2280 self.av1_config.as_ref()
2281 }
2282
2283 pub fn color_info(&self) -> Option<&ColorInformation> {
2289 self.color_info.as_ref()
2290 }
2291
2292 pub fn rotation(&self) -> Option<&ImageRotation> {
2294 self.rotation.as_ref()
2295 }
2296
2297 pub fn mirror(&self) -> Option<&ImageMirror> {
2299 self.mirror.as_ref()
2300 }
2301
2302 pub fn clean_aperture(&self) -> Option<&CleanAperture> {
2304 self.clean_aperture.as_ref()
2305 }
2306
2307 pub fn pixel_aspect_ratio(&self) -> Option<&PixelAspectRatio> {
2309 self.pixel_aspect_ratio.as_ref()
2310 }
2311
2312 pub fn content_light_level(&self) -> Option<&ContentLightLevel> {
2314 self.content_light_level.as_ref()
2315 }
2316
2317 pub fn mastering_display(&self) -> Option<&MasteringDisplayColourVolume> {
2319 self.mastering_display.as_ref()
2320 }
2321
2322 pub fn content_colour_volume(&self) -> Option<&ContentColourVolume> {
2324 self.content_colour_volume.as_ref()
2325 }
2326
2327 pub fn ambient_viewing(&self) -> Option<&AmbientViewingEnvironment> {
2329 self.ambient_viewing.as_ref()
2330 }
2331
2332 pub fn operating_point(&self) -> Option<&OperatingPointSelector> {
2334 self.operating_point.as_ref()
2335 }
2336
2337 pub fn layer_selector(&self) -> Option<&LayerSelector> {
2339 self.layer_selector.as_ref()
2340 }
2341
2342 pub fn layered_image_indexing(&self) -> Option<&AV1LayeredImageIndexing> {
2344 self.layered_image_indexing.as_ref()
2345 }
2346
2347 pub fn exif(&self) -> Option<Result<Cow<'_, [u8]>>> {
2351 self.exif_item.as_ref().map(|item| {
2352 let raw = self.resolve_item(item)?;
2353 if raw.len() <= 4 {
2355 return Err(Error::InvalidData("EXIF item too short"));
2356 }
2357 let offset = u32::from_be_bytes([raw[0], raw[1], raw[2], raw[3]]) as usize;
2358 let start = 4 + offset;
2359 if start >= raw.len() {
2360 return Err(Error::InvalidData("EXIF offset exceeds item size"));
2361 }
2362 match raw {
2363 Cow::Borrowed(slice) => Ok(Cow::Borrowed(&slice[start..])),
2364 Cow::Owned(vec) => Ok(Cow::Owned(vec[start..].to_vec())),
2365 }
2366 })
2367 }
2368
2369 pub fn xmp(&self) -> Option<Result<Cow<'_, [u8]>>> {
2373 self.xmp_item.as_ref().map(|item| self.resolve_item(item))
2374 }
2375
2376 pub fn gain_map_metadata(&self) -> Option<&GainMapMetadata> {
2381 self.gain_map_metadata.as_ref()
2382 }
2383
2384 pub fn gain_map_data(&self) -> Option<Result<Cow<'_, [u8]>>> {
2386 self.gain_map.as_ref().map(|item| self.resolve_item(item))
2387 }
2388
2389 pub fn gain_map_color_info(&self) -> Option<&ColorInformation> {
2394 self.gain_map_color_info.as_ref()
2395 }
2396
2397 pub fn gain_map(&self) -> Option<Result<AvifGainMap>> {
2403 let metadata = self.gain_map_metadata.as_ref()?.clone();
2404 let data_extents = self.gain_map.as_ref()?;
2405 let alt_color_info = self.gain_map_color_info.clone();
2406
2407 Some(self.resolve_item(data_extents).map(|data| AvifGainMap {
2408 metadata,
2409 gain_map_data: data.into_owned(),
2410 alt_color_info,
2411 }))
2412 }
2413
2414 pub fn has_depth_map(&self) -> bool {
2419 self.depth_item.is_some()
2420 }
2421
2422 pub fn depth_map_data(&self) -> Option<Result<Cow<'_, [u8]>>> {
2424 self.depth_item.as_ref().map(|item| self.resolve_item(item))
2425 }
2426
2427 pub fn depth_map(&self) -> Option<Result<AvifDepthMap>> {
2443 let data_extents = self.depth_item.as_ref()?;
2444 let av1_config = self.depth_av1_config.clone();
2445 let color_info = self.depth_color_info.clone();
2446 let width = self.depth_width;
2447 let height = self.depth_height;
2448
2449 Some(self.resolve_item(data_extents).map(|data| AvifDepthMap {
2450 data: data.into_owned(),
2451 width,
2452 height,
2453 av1_config,
2454 color_info,
2455 }))
2456 }
2457
2458 pub fn major_brand(&self) -> &[u8; 4] {
2460 &self.major_brand
2461 }
2462
2463 pub fn compatible_brands(&self) -> &[[u8; 4]] {
2465 &self.compatible_brands
2466 }
2467
2468 pub fn primary_metadata(&self) -> Result<AV1Metadata> {
2470 let data = self.primary_data()?;
2471 AV1Metadata::parse_av1_bitstream(&data)
2472 }
2473
2474 pub fn alpha_metadata(&self) -> Option<Result<AV1Metadata>> {
2476 self.alpha.as_ref().map(|item| {
2477 let data = self.resolve_item(item)?;
2478 AV1Metadata::parse_av1_bitstream(&data)
2479 })
2480 }
2481
2482 #[cfg(feature = "eager")]
2491 #[deprecated(since = "1.5.0", note = "Use AvifParser methods directly instead of converting to AvifData")]
2492 #[allow(deprecated)]
2493 pub fn to_avif_data(&self) -> Result<AvifData> {
2494 let primary_data = self.primary_data()?;
2495 let mut primary_item = TryVec::new();
2496 primary_item.extend_from_slice(&primary_data)?;
2497
2498 let alpha_item = match self.alpha_data() {
2499 Some(Ok(data)) => {
2500 let mut v = TryVec::new();
2501 v.extend_from_slice(&data)?;
2502 Some(v)
2503 }
2504 Some(Err(e)) => return Err(e),
2505 None => None,
2506 };
2507
2508 let mut grid_tiles = TryVec::new();
2509 for i in 0..self.grid_tile_count() {
2510 let data = self.tile_data(i)?;
2511 let mut v = TryVec::new();
2512 v.extend_from_slice(&data)?;
2513 grid_tiles.push(v)?;
2514 }
2515
2516 let animation = if let Some(info) = self.animation_info() {
2517 let mut frames = TryVec::new();
2518 for i in 0..info.frame_count {
2519 let frame_ref = self.frame(i)?;
2520 let mut data = TryVec::new();
2521 data.extend_from_slice(&frame_ref.data)?;
2522 frames.push(AnimationFrame { data, duration_ms: frame_ref.duration_ms })?;
2523 }
2524 Some(AnimationConfig {
2525 loop_count: info.loop_count,
2526 frames,
2527 })
2528 } else {
2529 None
2530 };
2531
2532 Ok(AvifData {
2533 primary_item,
2534 alpha_item,
2535 premultiplied_alpha: self.premultiplied_alpha,
2536 grid_config: self.grid_config.clone(),
2537 grid_tiles,
2538 animation,
2539 av1_config: self.av1_config.clone(),
2540 color_info: self.color_info.clone(),
2541 rotation: self.rotation,
2542 mirror: self.mirror,
2543 clean_aperture: self.clean_aperture,
2544 pixel_aspect_ratio: self.pixel_aspect_ratio,
2545 content_light_level: self.content_light_level,
2546 mastering_display: self.mastering_display,
2547 content_colour_volume: self.content_colour_volume,
2548 ambient_viewing: self.ambient_viewing,
2549 operating_point: self.operating_point,
2550 layer_selector: self.layer_selector,
2551 layered_image_indexing: self.layered_image_indexing,
2552 exif: self.exif().and_then(|r| r.ok()).map(|c| {
2553 let mut v = TryVec::new();
2554 let _ = v.extend_from_slice(&c);
2555 v
2556 }),
2557 xmp: self.xmp().and_then(|r| r.ok()).map(|c| {
2558 let mut v = TryVec::new();
2559 let _ = v.extend_from_slice(&c);
2560 v
2561 }),
2562 gain_map_metadata: self.gain_map_metadata.clone(),
2563 gain_map_item: self.gain_map_data().and_then(|r| r.ok()).map(|c| {
2564 let mut v = TryVec::new();
2565 let _ = v.extend_from_slice(&c);
2566 v
2567 }),
2568 gain_map_color_info: self.gain_map_color_info.clone(),
2569 depth_item: self.depth_map_data().and_then(|r| r.ok()).map(|c| {
2570 let mut v = TryVec::new();
2571 let _ = v.extend_from_slice(&c);
2572 v
2573 }),
2574 depth_width: self.depth_width,
2575 depth_height: self.depth_height,
2576 depth_av1_config: self.depth_av1_config.clone(),
2577 depth_color_info: self.depth_color_info.clone(),
2578 major_brand: self.major_brand,
2579 compatible_brands: self.compatible_brands.clone(),
2580 })
2581 }
2582}
2583
2584pub struct FrameIterator<'a> {
2588 parser: &'a AvifParser<'a>,
2589 index: usize,
2590 count: usize,
2591}
2592
2593impl<'a> Iterator for FrameIterator<'a> {
2594 type Item = Result<FrameRef<'a>>;
2595
2596 fn next(&mut self) -> Option<Self::Item> {
2597 if self.index >= self.count {
2598 return None;
2599 }
2600 let result = self.parser.frame(self.index);
2601 self.index += 1;
2602 Some(result)
2603 }
2604
2605 fn size_hint(&self) -> (usize, Option<usize>) {
2606 let remaining = self.count.saturating_sub(self.index);
2607 (remaining, Some(remaining))
2608 }
2609}
2610
2611impl ExactSizeIterator for FrameIterator<'_> {
2612 fn len(&self) -> usize {
2613 self.count.saturating_sub(self.index)
2614 }
2615}
2616
2617struct AvifInternalMeta {
2618 item_references: TryVec<SingleItemTypeReferenceBox>,
2619 properties: TryVec<AssociatedProperty>,
2620 primary_item_id: u32,
2621 iloc_items: TryVec<ItemLocationBoxItem>,
2622 item_infos: TryVec<ItemInfoEntry>,
2623 idat: Option<TryVec<u8>>,
2624 #[allow(dead_code)] entity_groups: TryVec<EntityGroup>,
2626}
2627
2628#[cfg(feature = "eager")]
2631struct MediaDataBox {
2632 offset: u64,
2634 data: TryVec<u8>,
2635}
2636
2637#[cfg(feature = "eager")]
2638impl MediaDataBox {
2639 fn contains_extent(&self, extent: &ExtentRange) -> bool {
2643 if self.offset <= extent.start() {
2644 let start_offset = extent.start() - self.offset;
2645 start_offset < self.data.len().to_u64()
2646 } else {
2647 false
2648 }
2649 }
2650
2651 fn matches_extent(&self, extent: &ExtentRange) -> bool {
2653 if self.offset == extent.start() {
2654 match extent {
2655 ExtentRange::WithLength(range) => {
2656 if let Some(end) = self.offset.checked_add(self.data.len().to_u64()) {
2657 end == range.end
2658 } else {
2659 false
2660 }
2661 },
2662 ExtentRange::ToEnd(_) => true,
2663 }
2664 } else {
2665 false
2666 }
2667 }
2668
2669 fn read_extent(&self, extent: &ExtentRange, buf: &mut TryVec<u8>) -> Result<()> {
2672 let start_offset = extent
2673 .start()
2674 .checked_sub(self.offset)
2675 .ok_or(Error::InvalidData("mdat does not contain extent"))?;
2676 let slice = match extent {
2677 ExtentRange::WithLength(range) => {
2678 let range_len = range
2679 .end
2680 .checked_sub(range.start)
2681 .ok_or(Error::InvalidData("range start > end"))?;
2682 let end = start_offset
2683 .checked_add(range_len)
2684 .ok_or(Error::InvalidData("extent end overflow"))?;
2685 self.data.get(start_offset.try_into()?..end.try_into()?)
2686 },
2687 ExtentRange::ToEnd(_) => self.data.get(start_offset.try_into()?..),
2688 };
2689 let slice = slice.ok_or(Error::InvalidData("extent crosses box boundary"))?;
2690 buf.extend_from_slice(slice)?;
2691 Ok(())
2692 }
2693
2694}
2695
2696#[derive(Debug)]
2700struct ItemInfoEntry {
2701 item_id: u32,
2702 item_type: FourCC,
2703}
2704
2705#[derive(Debug)]
2707struct SingleItemTypeReferenceBox {
2708 item_type: FourCC,
2709 from_item_id: u32,
2710 to_item_id: u32,
2711 reference_index: u16,
2714}
2715
2716#[derive(Debug)]
2719enum IlocFieldSize {
2720 Zero,
2721 Four,
2722 Eight,
2723}
2724
2725impl IlocFieldSize {
2726 const fn to_bits(&self) -> u8 {
2727 match self {
2728 Self::Zero => 0,
2729 Self::Four => 32,
2730 Self::Eight => 64,
2731 }
2732 }
2733}
2734
2735impl TryFrom<u8> for IlocFieldSize {
2736 type Error = Error;
2737
2738 fn try_from(value: u8) -> Result<Self> {
2739 match value {
2740 0 => Ok(Self::Zero),
2741 4 => Ok(Self::Four),
2742 8 => Ok(Self::Eight),
2743 _ => Err(Error::InvalidData("value must be in the set {0, 4, 8}")),
2744 }
2745 }
2746}
2747
2748#[derive(PartialEq)]
2749enum IlocVersion {
2750 Zero,
2751 One,
2752 Two,
2753}
2754
2755impl TryFrom<u8> for IlocVersion {
2756 type Error = Error;
2757
2758 fn try_from(value: u8) -> Result<Self> {
2759 match value {
2760 0 => Ok(Self::Zero),
2761 1 => Ok(Self::One),
2762 2 => Ok(Self::Two),
2763 _ => Err(Error::Unsupported("unsupported version in 'iloc' box")),
2764 }
2765 }
2766}
2767
2768#[derive(Debug)]
2773struct ItemLocationBoxItem {
2774 item_id: u32,
2775 construction_method: ConstructionMethod,
2776 extents: TryVec<ItemLocationBoxExtent>,
2778}
2779
2780#[derive(Clone, Copy, Debug, PartialEq)]
2781enum ConstructionMethod {
2782 File,
2783 Idat,
2784 #[allow(dead_code)] Item,
2786}
2787
2788#[derive(Clone, Debug)]
2791struct ItemLocationBoxExtent {
2792 extent_range: ExtentRange,
2793}
2794
2795#[derive(Clone, Debug)]
2796enum ExtentRange {
2797 WithLength(Range<u64>),
2798 ToEnd(RangeFrom<u64>),
2799}
2800
2801impl ExtentRange {
2802 const fn start(&self) -> u64 {
2803 match self {
2804 Self::WithLength(r) => r.start,
2805 Self::ToEnd(r) => r.start,
2806 }
2807 }
2808}
2809
2810struct BMFFBox<'a, T> {
2812 head: BoxHeader,
2813 content: Take<&'a mut T>,
2814}
2815
2816impl<T: Read> BMFFBox<'_, T> {
2817 fn read_into_try_vec(&mut self) -> std::io::Result<TryVec<u8>> {
2818 let limit = self.content.limit();
2819 const MAX_PREALLOC: u64 = 256 * 1024 * 1024;
2826 let mut vec = if limit >= u64::MAX - BoxHeader::MIN_LARGE_SIZE {
2827 std::vec::Vec::new()
2829 } else {
2830 let mut v = std::vec::Vec::new();
2831 v.try_reserve_exact(limit.min(MAX_PREALLOC) as usize)
2832 .map_err(|_| std::io::ErrorKind::OutOfMemory)?;
2833 v
2834 };
2835 self.content.read_to_end(&mut vec)?; Ok(vec.into())
2837 }
2838}
2839
2840#[test]
2841fn box_read_to_end() {
2842 let tmp = &mut b"1234567890".as_slice();
2843 let mut src = BMFFBox {
2844 head: BoxHeader { name: BoxType::FileTypeBox, size: 5, offset: 0, uuid: None },
2845 content: <_ as Read>::take(tmp, 5),
2846 };
2847 let buf = src.read_into_try_vec().unwrap();
2848 assert_eq!(buf.len(), 5);
2849 assert_eq!(buf, b"12345".as_ref());
2850}
2851
2852#[test]
2853fn box_read_to_end_large_claim() {
2854 let tmp = &mut b"1234567890".as_slice();
2857 let mut src = BMFFBox {
2858 head: BoxHeader { name: BoxType::FileTypeBox, size: 5, offset: 0, uuid: None },
2859 content: <_ as Read>::take(tmp, u64::MAX / 2),
2860 };
2861 let buf = src.read_into_try_vec().unwrap();
2862 assert_eq!(buf.len(), 10);
2863}
2864
2865struct BoxIter<'a, T> {
2866 src: &'a mut T,
2867 max_remaining: u64,
2873}
2874
2875impl<T: Read> BoxIter<'_, T> {
2876 #[cfg(feature = "eager")]
2878 fn new(src: &mut T) -> BoxIter<'_, T> {
2879 BoxIter { src, max_remaining: u64::MAX }
2880 }
2881
2882 fn with_max_remaining(src: &mut T, max_remaining: u64) -> BoxIter<'_, T> {
2883 BoxIter { src, max_remaining }
2884 }
2885
2886 fn next_box(&mut self) -> Result<Option<BMFFBox<'_, T>>> {
2887 let r = read_box_header(self.src);
2888 match r {
2889 Ok(h) => {
2890 let claimed = h.size - h.offset;
2891 let clamped = claimed.min(self.max_remaining);
2894 self.max_remaining = self.max_remaining.saturating_sub(clamped.saturating_add(h.offset));
2897 Ok(Some(BMFFBox {
2898 head: h,
2899 content: self.src.take(clamped),
2900 }))
2901 }
2902 Err(Error::UnexpectedEOF) => Ok(None),
2903 Err(e) => Err(e),
2904 }
2905 }
2906}
2907
2908impl<T: Read> Read for BMFFBox<'_, T> {
2909 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
2910 self.content.read(buf)
2911 }
2912}
2913
2914impl<T: Offset> Offset for BMFFBox<'_, T> {
2915 fn offset(&self) -> u64 {
2916 self.content.get_ref().offset()
2917 }
2918}
2919
2920impl<T: Read> BMFFBox<'_, T> {
2921 fn bytes_left(&self) -> u64 {
2922 self.content.limit()
2923 }
2924
2925 const fn get_header(&self) -> &BoxHeader {
2926 &self.head
2927 }
2928
2929 fn box_iter(&mut self) -> BoxIter<'_, Self> {
2930 BoxIter::with_max_remaining(self, self.bytes_left())
2931 }
2932}
2933
2934impl<T> Drop for BMFFBox<'_, T> {
2935 fn drop(&mut self) {
2936 if self.content.limit() > 0 {
2937 let name: FourCC = From::from(self.head.name);
2938 debug!("Dropping {} bytes in '{}'", self.content.limit(), name);
2939 }
2940 }
2941}
2942
2943fn read_box_header<T: ReadBytesExt>(src: &mut T) -> Result<BoxHeader> {
2952 let size32 = be_u32(src)?;
2953 let name = BoxType::from(be_u32(src)?);
2954 let size = match size32 {
2955 0 => {
2957 u64::MAX
2959 },
2960 1 => {
2961 let size64 = be_u64(src)?;
2962 if size64 < BoxHeader::MIN_LARGE_SIZE {
2963 return Err(Error::InvalidData("malformed wide size"));
2964 }
2965 size64
2966 },
2967 _ => {
2968 if u64::from(size32) < BoxHeader::MIN_SIZE {
2969 return Err(Error::InvalidData("malformed size"));
2970 }
2971 u64::from(size32)
2972 },
2973 };
2974 let mut offset = match size32 {
2975 1 => BoxHeader::MIN_LARGE_SIZE,
2976 _ => BoxHeader::MIN_SIZE,
2977 };
2978 let uuid = if name == BoxType::UuidBox {
2979 if size >= offset + 16 {
2980 let mut buffer = [0u8; 16];
2981 let count = src.read(&mut buffer)?;
2982 offset += count.to_u64();
2983 if count == 16 {
2984 Some(buffer)
2985 } else {
2986 debug!("malformed uuid (short read), skipping");
2987 None
2988 }
2989 } else {
2990 debug!("malformed uuid, skipping");
2991 None
2992 }
2993 } else {
2994 None
2995 };
2996 if offset > size {
2997 return Err(Error::InvalidData("box header offset exceeds size"));
2998 }
2999 Ok(BoxHeader { name, size, offset, uuid })
3000}
3001
3002fn read_fullbox_extra<T: ReadBytesExt>(src: &mut T) -> Result<(u8, u32)> {
3004 let version = src.read_u8()?;
3005 let flags_a = src.read_u8()?;
3006 let flags_b = src.read_u8()?;
3007 let flags_c = src.read_u8()?;
3008 Ok((
3009 version,
3010 u32::from(flags_a) << 16 | u32::from(flags_b) << 8 | u32::from(flags_c),
3011 ))
3012}
3013
3014fn read_fullbox_version_no_flags<T: ReadBytesExt>(src: &mut T, options: &ParseOptions) -> Result<u8> {
3016 let (version, flags) = read_fullbox_extra(src)?;
3017
3018 if flags != 0 && !options.lenient {
3019 return Err(Error::Unsupported("expected flags to be 0"));
3020 }
3021
3022 Ok(version)
3023}
3024
3025fn skip_box_content<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<()> {
3027 let to_skip = {
3029 let header = src.get_header();
3030 debug!("{header:?} (skipped)");
3031 header
3032 .size
3033 .checked_sub(header.offset)
3034 .ok_or(Error::InvalidData("header offset > size"))?
3035 };
3036 if to_skip != src.bytes_left() {
3037 return Err(Error::InvalidData("box content size mismatch"));
3038 }
3039 skip(src, to_skip)
3040}
3041
3042fn skip_box_remain<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<()> {
3044 let remain = {
3045 let header = src.get_header();
3046 let len = src.bytes_left();
3047 debug!("remain {len} (skipped) in {header:?}");
3048 len
3049 };
3050 skip(src, remain)
3051}
3052
3053struct ResourceTracker<'a> {
3054 config: &'a DecodeConfig,
3055 #[cfg(feature = "eager")]
3056 current_memory: u64,
3057 #[cfg(feature = "eager")]
3058 peak_memory: u64,
3059}
3060
3061impl<'a> ResourceTracker<'a> {
3062 fn new(config: &'a DecodeConfig) -> Self {
3063 Self {
3064 config,
3065 #[cfg(feature = "eager")]
3066 current_memory: 0,
3067 #[cfg(feature = "eager")]
3068 peak_memory: 0,
3069 }
3070 }
3071
3072 #[cfg(feature = "eager")]
3073 fn reserve(&mut self, bytes: u64) -> Result<()> {
3074 self.current_memory = self.current_memory.saturating_add(bytes);
3075 self.peak_memory = self.peak_memory.max(self.current_memory);
3076
3077 if let Some(limit) = self.config.peak_memory_limit
3078 && self.peak_memory > limit {
3079 return Err(Error::ResourceLimitExceeded("peak memory limit exceeded"));
3080 }
3081
3082 Ok(())
3083 }
3084
3085 #[cfg(feature = "eager")]
3086 fn release(&mut self, bytes: u64) {
3087 self.current_memory = self.current_memory.saturating_sub(bytes);
3088 }
3089
3090 #[cfg(feature = "eager")]
3091 fn validate_total_megapixels(&self, width: u32, height: u32) -> Result<()> {
3092 if let Some(limit) = self.config.total_megapixels_limit {
3093 let megapixels = (width as u64)
3094 .checked_mul(height as u64)
3095 .ok_or(Error::InvalidData("dimension overflow"))?
3096 / 1_000_000;
3097
3098 if megapixels > limit as u64 {
3099 return Err(Error::ResourceLimitExceeded("total megapixels limit exceeded"));
3100 }
3101 }
3102
3103 Ok(())
3104 }
3105
3106 fn validate_animation_frames(&self, count: u32) -> Result<()> {
3107 if let Some(limit) = self.config.max_animation_frames
3108 && count > limit {
3109 return Err(Error::ResourceLimitExceeded("animation frame count limit exceeded"));
3110 }
3111
3112 Ok(())
3113 }
3114
3115 fn validate_grid_tiles(&self, count: u32) -> Result<()> {
3116 if let Some(limit) = self.config.max_grid_tiles
3117 && count > limit {
3118 return Err(Error::ResourceLimitExceeded("grid tile count limit exceeded"));
3119 }
3120
3121 Ok(())
3122 }
3123}
3124
3125#[cfg(feature = "eager")]
3136#[deprecated(since = "1.5.0", note = "Use `AvifParser::from_reader_with_config()` instead")]
3137#[allow(deprecated)]
3138pub fn read_avif_with_config<T: Read>(
3139 f: &mut T,
3140 config: &DecodeConfig,
3141 stop: &dyn Stop,
3142) -> Result<AvifData> {
3143 let mut tracker = ResourceTracker::new(config);
3144 let mut f = OffsetReader::new(f);
3145
3146 let mut iter = BoxIter::new(&mut f);
3147
3148 let (major_brand, compatible_brands) = if let Some(mut b) = iter.next_box()? {
3150 if b.head.name == BoxType::FileTypeBox {
3151 let ftyp = read_ftyp(&mut b)?;
3152 if ftyp.major_brand != b"avif" && ftyp.major_brand != b"avis" {
3154 warn!("major_brand: {}", ftyp.major_brand);
3155 return Err(Error::InvalidData("ftyp must be 'avif' or 'avis'"));
3156 }
3157 let major = ftyp.major_brand.value;
3158 let compat = ftyp.compatible_brands.iter().map(|b| b.value).collect();
3159 (major, compat)
3160 } else {
3161 return Err(Error::InvalidData("'ftyp' box must occur first"));
3162 }
3163 } else {
3164 return Err(Error::InvalidData("'ftyp' box must occur first"));
3165 };
3166
3167 let mut meta = None;
3168 let mut mdats = TryVec::new();
3169 let mut animation_data: Option<ParsedAnimationData> = None;
3170
3171 let parse_opts = ParseOptions { lenient: config.lenient };
3172
3173 while let Some(mut b) = iter.next_box()? {
3174 stop.check()?;
3175
3176 match b.head.name {
3177 BoxType::MetadataBox => {
3178 if meta.is_some() {
3179 return Err(Error::InvalidData("There should be zero or one meta boxes per ISO 14496-12:2015 § 8.11.1.1"));
3180 }
3181 meta = Some(read_avif_meta(&mut b, &parse_opts)?);
3182 },
3183 BoxType::MovieBox => {
3184 let tracks = read_moov(&mut b)?;
3185 if !tracks.is_empty() {
3186 animation_data = Some(associate_tracks(tracks)?);
3187 }
3188 },
3189 BoxType::MediaDataBox => {
3190 if b.bytes_left() > 0 {
3191 let offset = b.offset();
3192 let size = b.bytes_left();
3193 tracker.reserve(size)?;
3194 let data = b.read_into_try_vec()?;
3195 tracker.release(size);
3196 mdats.push(MediaDataBox { offset, data })?;
3197 }
3198 },
3199 _ => skip_box_content(&mut b)?,
3200 }
3201
3202 check_parser_state(&b.head, &b.content)?;
3203 }
3204
3205 if meta.is_none() && animation_data.is_none() {
3207 return Err(Error::InvalidData("missing meta"));
3208 }
3209 let Some(meta) = meta else {
3210 return Ok(AvifData {
3212 ..Default::default()
3213 });
3214 };
3215
3216 let is_grid = meta
3218 .item_infos
3219 .iter()
3220 .find(|x| x.item_id == meta.primary_item_id)
3221 .is_some_and(|info| {
3222 let is_g = info.item_type == b"grid";
3223 if is_g {
3224 log::debug!("Grid image detected: primary_item_id={}", meta.primary_item_id);
3225 }
3226 is_g
3227 });
3228
3229 let mut grid_config = if is_grid {
3231 meta.properties
3232 .iter()
3233 .find(|prop| {
3234 prop.item_id == meta.primary_item_id
3235 && matches!(prop.property, ItemProperty::ImageGrid(_))
3236 })
3237 .and_then(|prop| match &prop.property {
3238 ItemProperty::ImageGrid(config) => {
3239 log::debug!("Grid: found explicit ImageGrid property: {:?}", config);
3240 Some(config.clone())
3241 },
3242 _ => None,
3243 })
3244 } else {
3245 None
3246 };
3247
3248 let tile_item_ids: TryVec<u32> = if is_grid {
3250 let mut tiles_with_index: TryVec<(u32, u16)> = TryVec::new();
3252 for iref in meta.item_references.iter() {
3253 if iref.from_item_id == meta.primary_item_id && iref.item_type == b"dimg" {
3255 tiles_with_index.push((iref.to_item_id, iref.reference_index))?;
3256 }
3257 }
3258
3259 tracker.validate_grid_tiles(tiles_with_index.len() as u32)?;
3261
3262 tiles_with_index.sort_by_key(|&(_, idx)| idx);
3264
3265 let mut ids = TryVec::new();
3267 for (tile_id, _) in tiles_with_index.iter() {
3268 ids.push(*tile_id)?;
3269 }
3270
3271 if grid_config.is_none() && !ids.is_empty() {
3275 let grid_dims = meta.properties.iter()
3277 .find(|p| p.item_id == meta.primary_item_id)
3278 .and_then(|p| match &p.property {
3279 ItemProperty::ImageSpatialExtents(e) => Some(e),
3280 _ => None,
3281 });
3282
3283 let tile_dims = ids.first().and_then(|&tile_id| {
3284 meta.properties.iter()
3285 .find(|p| p.item_id == tile_id)
3286 .and_then(|p| match &p.property {
3287 ItemProperty::ImageSpatialExtents(e) => Some(e),
3288 _ => None,
3289 })
3290 });
3291
3292 if let (Some(grid), Some(tile)) = (grid_dims, tile_dims) {
3293 tracker.validate_total_megapixels(grid.width, grid.height)?;
3295
3296 if tile.width == 0 || tile.height == 0 {
3298 log::warn!("Grid: tile has zero dimensions, using fallback");
3299 } else if grid.width % tile.width == 0 && grid.height % tile.height == 0 {
3300 let columns = grid.width / tile.width;
3302 let rows = grid.height / tile.height;
3303
3304 if columns > 255 || rows > 255 {
3306 log::warn!("Grid: calculated dimensions {}×{} exceed 255, using fallback", rows, columns);
3307 } else {
3308 log::debug!("Grid: calculated {}×{} layout from ispe dimensions", rows, columns);
3309 grid_config = Some(GridConfig {
3310 rows: rows as u8,
3311 columns: columns as u8,
3312 output_width: grid.width,
3313 output_height: grid.height,
3314 });
3315 }
3316 } else {
3317 log::warn!("Grid: dimension mismatch - grid {}×{} not evenly divisible by tile {}×{}, using fallback",
3318 grid.width, grid.height, tile.width, tile.height);
3319 }
3320 }
3321
3322 if grid_config.is_none() {
3324 log::debug!("Grid: using fallback {}×1 layout inference", ids.len());
3325 grid_config = Some(GridConfig {
3326 rows: ids.len() as u8, columns: 1, output_width: 0, output_height: 0, });
3331 }
3332 }
3333
3334 ids
3335 } else {
3336 TryVec::new()
3337 };
3338
3339 let alpha_item_id = meta
3340 .item_references
3341 .iter()
3342 .filter(|iref| {
3344 iref.to_item_id == meta.primary_item_id
3345 && iref.from_item_id != meta.primary_item_id
3346 && iref.item_type == b"auxl"
3347 })
3348 .map(|iref| iref.from_item_id)
3349 .find(|&item_id| {
3351 meta.properties.iter().any(|prop| {
3352 prop.item_id == item_id
3353 && match &prop.property {
3354 ItemProperty::AuxiliaryType(urn) => {
3355 urn.type_subtype().0 == b"urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"
3356 }
3357 _ => false,
3358 }
3359 })
3360 });
3361
3362 macro_rules! find_prop {
3364 ($variant:ident) => {
3365 meta.properties.iter().find_map(|p| {
3366 if p.item_id == meta.primary_item_id {
3367 match &p.property {
3368 ItemProperty::$variant(c) => Some(c.clone()),
3369 _ => None,
3370 }
3371 } else {
3372 None
3373 }
3374 })
3375 };
3376 }
3377
3378 let av1_config = find_prop!(AV1Config);
3379 let color_info = find_prop!(ColorInformation);
3380 let rotation = find_prop!(Rotation);
3381 let mirror = find_prop!(Mirror);
3382 let clean_aperture = find_prop!(CleanAperture);
3383 let pixel_aspect_ratio = find_prop!(PixelAspectRatio);
3384 let content_light_level = find_prop!(ContentLightLevel);
3385 let mastering_display = find_prop!(MasteringDisplayColourVolume);
3386 let content_colour_volume = find_prop!(ContentColourVolume);
3387 let ambient_viewing = find_prop!(AmbientViewingEnvironment);
3388 let operating_point = find_prop!(OperatingPointSelector);
3389 let layer_selector = find_prop!(LayerSelector);
3390 let layered_image_indexing = find_prop!(AV1LayeredImageIndexing);
3391
3392 let mut context = AvifData {
3393 premultiplied_alpha: alpha_item_id.is_some_and(|alpha_item_id| {
3394 meta.item_references.iter().any(|iref| {
3395 iref.from_item_id == meta.primary_item_id
3396 && iref.to_item_id == alpha_item_id
3397 && iref.item_type == b"prem"
3398 })
3399 }),
3400 av1_config,
3401 color_info,
3402 rotation,
3403 mirror,
3404 clean_aperture,
3405 pixel_aspect_ratio,
3406 content_light_level,
3407 mastering_display,
3408 content_colour_volume,
3409 ambient_viewing,
3410 operating_point,
3411 layer_selector,
3412 layered_image_indexing,
3413 major_brand,
3414 compatible_brands,
3415 ..Default::default()
3416 };
3417
3418 let mut extract_item_data = |loc: &ItemLocationBoxItem, buf: &mut TryVec<u8>| -> Result<()> {
3420 match loc.construction_method {
3421 ConstructionMethod::File => {
3422 for extent in loc.extents.iter() {
3423 let mut found = false;
3424 for mdat in mdats.iter_mut() {
3425 if mdat.matches_extent(&extent.extent_range) {
3426 buf.append(&mut mdat.data)?;
3427 found = true;
3428 break;
3429 } else if mdat.contains_extent(&extent.extent_range) {
3430 mdat.read_extent(&extent.extent_range, buf)?;
3431 found = true;
3432 break;
3433 }
3434 }
3435 if !found {
3436 return Err(Error::InvalidData("iloc contains an extent that is not in mdat"));
3437 }
3438 }
3439 Ok(())
3440 },
3441 ConstructionMethod::Idat => {
3442 let idat_data = meta.idat.as_ref().ok_or(Error::InvalidData("idat box missing but construction_method is Idat"))?;
3443 for extent in loc.extents.iter() {
3444 match &extent.extent_range {
3445 ExtentRange::WithLength(range) => {
3446 let start = usize::try_from(range.start).map_err(|_| Error::InvalidData("extent start too large"))?;
3447 let end = usize::try_from(range.end).map_err(|_| Error::InvalidData("extent end too large"))?;
3448 if end > idat_data.len() {
3449 return Err(Error::InvalidData("extent exceeds idat size"));
3450 }
3451 buf.extend_from_slice(&idat_data[start..end]).map_err(|_| Error::OutOfMemory)?;
3452 },
3453 ExtentRange::ToEnd(range) => {
3454 let start = usize::try_from(range.start).map_err(|_| Error::InvalidData("extent start too large"))?;
3455 if start >= idat_data.len() {
3456 return Err(Error::InvalidData("extent start exceeds idat size"));
3457 }
3458 buf.extend_from_slice(&idat_data[start..]).map_err(|_| Error::OutOfMemory)?;
3459 },
3460 }
3461 }
3462 Ok(())
3463 },
3464 ConstructionMethod::Item => {
3465 Err(Error::Unsupported("construction_method 'item' not supported"))
3466 },
3467 }
3468 };
3469
3470 if is_grid {
3473 for (idx, &tile_id) in tile_item_ids.iter().enumerate() {
3475 if idx % 16 == 0 {
3476 stop.check()?;
3477 }
3478
3479 let mut tile_data = TryVec::new();
3480
3481 if let Some(loc) = meta.iloc_items.iter().find(|loc| loc.item_id == tile_id) {
3482 extract_item_data(loc, &mut tile_data)?;
3483 } else {
3484 return Err(Error::InvalidData("grid tile not found in iloc"));
3485 }
3486
3487 context.grid_tiles.push(tile_data)?;
3488 }
3489
3490 context.grid_config = grid_config;
3492 } else {
3493 for loc in meta.iloc_items.iter() {
3495 let item_data = if loc.item_id == meta.primary_item_id {
3496 &mut context.primary_item
3497 } else if Some(loc.item_id) == alpha_item_id {
3498 context.alpha_item.get_or_insert_with(TryVec::new)
3499 } else {
3500 continue;
3501 };
3502
3503 extract_item_data(loc, item_data)?;
3504 }
3505 }
3506
3507 for iref in meta.item_references.iter() {
3509 if iref.to_item_id != meta.primary_item_id || iref.item_type != b"cdsc" {
3510 continue;
3511 }
3512 let desc_item_id = iref.from_item_id;
3513 let Some(info) = meta.item_infos.iter().find(|i| i.item_id == desc_item_id) else {
3514 continue;
3515 };
3516 if info.item_type == b"Exif" {
3517 if let Some(loc) = meta.iloc_items.iter().find(|l| l.item_id == desc_item_id) {
3518 let mut raw = TryVec::new();
3519 extract_item_data(loc, &mut raw)?;
3520 if raw.len() > 4 {
3522 let offset = u32::from_be_bytes([raw[0], raw[1], raw[2], raw[3]]) as usize;
3523 let start = 4 + offset;
3524 if start < raw.len() {
3525 let mut exif = TryVec::new();
3526 exif.extend_from_slice(&raw[start..])?;
3527 context.exif = Some(exif);
3528 }
3529 }
3530 }
3531 } else if info.item_type == b"mime"
3532 && let Some(loc) = meta.iloc_items.iter().find(|l| l.item_id == desc_item_id)
3533 {
3534 let mut xmp = TryVec::new();
3535 extract_item_data(loc, &mut xmp)?;
3536 context.xmp = Some(xmp);
3537 }
3538 }
3539
3540 if let Some(tmap_info) = meta.item_infos.iter().find(|info| info.item_type == b"tmap") {
3542 let tmap_id = tmap_info.item_id;
3543
3544 let mut inputs: TryVec<(u32, u16)> = TryVec::new();
3545 for iref in meta.item_references.iter() {
3546 if iref.from_item_id == tmap_id && iref.item_type == b"dimg" {
3547 inputs.push((iref.to_item_id, iref.reference_index))?;
3548 }
3549 }
3550 inputs.sort_by_key(|&(_, idx)| idx);
3551
3552 if inputs.len() >= 2 && inputs[0].0 == meta.primary_item_id {
3553 let gmap_item_id = inputs[1].0;
3554
3555 if let Some(loc) = meta.iloc_items.iter().find(|l| l.item_id == tmap_id) {
3557 let mut tmap_data = TryVec::new();
3558 extract_item_data(loc, &mut tmap_data)?;
3559 if let Ok(metadata) = parse_tone_map_image(&tmap_data) {
3560 context.gain_map_metadata = Some(metadata);
3561 }
3562 }
3563
3564 if let Some(loc) = meta.iloc_items.iter().find(|l| l.item_id == gmap_item_id) {
3566 let mut gmap_data = TryVec::new();
3567 extract_item_data(loc, &mut gmap_data)?;
3568 context.gain_map_item = Some(gmap_data);
3569 }
3570
3571 context.gain_map_color_info = meta.properties.iter().find_map(|p| {
3573 if p.item_id == tmap_id {
3574 match &p.property {
3575 ItemProperty::ColorInformation(c) => Some(c.clone()),
3576 _ => None,
3577 }
3578 } else {
3579 None
3580 }
3581 });
3582 }
3583 }
3584
3585 {
3587 let depth_item_id = meta
3588 .item_references
3589 .iter()
3590 .filter(|iref| {
3591 iref.to_item_id == meta.primary_item_id
3592 && iref.from_item_id != meta.primary_item_id
3593 && iref.item_type == b"auxl"
3594 })
3595 .map(|iref| iref.from_item_id)
3596 .find(|&item_id| {
3597 if alpha_item_id == Some(item_id) {
3598 return false;
3599 }
3600 meta.properties.iter().any(|prop| {
3601 prop.item_id == item_id
3602 && match &prop.property {
3603 ItemProperty::AuxiliaryType(urn) => {
3604 is_depth_auxiliary_urn(urn.type_subtype().0)
3605 }
3606 _ => false,
3607 }
3608 })
3609 });
3610
3611 if let Some(depth_id) = depth_item_id {
3612 if let Some(loc) = meta.iloc_items.iter().find(|l| l.item_id == depth_id) {
3613 let mut depth_data = TryVec::new();
3614 extract_item_data(loc, &mut depth_data)?;
3615 context.depth_item = Some(depth_data);
3616 }
3617 if let Some((w, h)) = meta.properties.iter().find_map(|p| {
3619 if p.item_id == depth_id {
3620 match &p.property {
3621 ItemProperty::ImageSpatialExtents(e) => Some((e.width, e.height)),
3622 _ => None,
3623 }
3624 } else {
3625 None
3626 }
3627 }) {
3628 context.depth_width = w;
3629 context.depth_height = h;
3630 }
3631 context.depth_av1_config = meta.properties.iter().find_map(|p| {
3633 if p.item_id == depth_id {
3634 match &p.property {
3635 ItemProperty::AV1Config(c) => Some(c.clone()),
3636 _ => None,
3637 }
3638 } else {
3639 None
3640 }
3641 });
3642 context.depth_color_info = meta.properties.iter().find_map(|p| {
3644 if p.item_id == depth_id {
3645 match &p.property {
3646 ItemProperty::ColorInformation(c) => Some(c.clone()),
3647 _ => None,
3648 }
3649 } else {
3650 None
3651 }
3652 });
3653 }
3654 }
3655
3656 if let Some(anim) = animation_data {
3658 let frame_count = anim.color_sample_table.sample_sizes.len() as u32;
3659 tracker.validate_animation_frames(frame_count)?;
3660
3661 log::debug!("Animation: extracting frames (media_timescale={})", anim.color_timescale);
3662 match extract_animation_frames(&anim.color_sample_table, anim.color_timescale, &mut mdats) {
3663 Ok(frames) => {
3664 if !frames.is_empty() {
3665 log::debug!("Animation: extracted {} frames", frames.len());
3666 context.animation = Some(AnimationConfig {
3667 loop_count: anim.loop_count,
3668 frames,
3669 });
3670 }
3671 }
3672 Err(e) => {
3673 log::warn!("Animation: failed to extract frames: {}", e);
3674 }
3675 }
3676 }
3677
3678 Ok(context)
3679}
3680
3681#[cfg(feature = "eager")]
3690#[deprecated(since = "1.5.0", note = "Use `AvifParser::from_reader_with_config()` with `DecodeConfig::lenient()` instead")]
3691#[allow(deprecated)]
3692pub fn read_avif_with_options<T: Read>(f: &mut T, options: &ParseOptions) -> Result<AvifData> {
3693 let config = DecodeConfig::unlimited().lenient(options.lenient);
3694 read_avif_with_config(f, &config, &Unstoppable)
3695}
3696
3697#[cfg(feature = "eager")]
3705#[deprecated(since = "1.5.0", note = "Use `AvifParser::from_reader()` instead")]
3706#[allow(deprecated)]
3707pub fn read_avif<T: Read>(f: &mut T) -> Result<AvifData> {
3708 read_avif_with_options(f, &ParseOptions::default())
3709}
3710
3711#[allow(dead_code)] struct EntityGroup {
3716 group_type: FourCC,
3717 group_id: u32,
3718 entity_ids: TryVec<u32>,
3719}
3720
3721fn read_grpl<T: Read + Offset>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<EntityGroup>> {
3726 let mut groups = TryVec::new();
3727 let mut iter = src.box_iter();
3728 while let Some(mut b) = iter.next_box()? {
3729 let group_type = FourCC::from(u32::from(b.head.name));
3730 let _version = b.read_u8()?;
3732 let mut flags_buf = [0u8; 3];
3733 b.read_exact(&mut flags_buf)?;
3734
3735 let group_id = be_u32(&mut b)?;
3736 let num_entities = be_u32(&mut b)?;
3737 if (num_entities as u64) * 4 > b.bytes_left() {
3739 return Err(Error::InvalidData(
3740 "grpl num_entities exceeds remaining box bytes",
3741 ));
3742 }
3743
3744 let mut entity_ids = TryVec::new();
3745 for _ in 0..num_entities {
3746 entity_ids.push(be_u32(&mut b)?)?;
3747 }
3748
3749 groups.push(EntityGroup {
3750 group_type,
3751 group_id,
3752 entity_ids,
3753 })?;
3754
3755 skip_box_remain(&mut b)?;
3756 check_parser_state(&b.head, &b.content)?;
3757 }
3758 Ok(groups)
3759}
3760
3761const TMAP_FLAG_MULTI_CHANNEL: u8 = 0x80;
3774const TMAP_FLAG_USE_BASE_COLOUR_SPACE: u8 = 0x40;
3777const TMAP_FLAG_COMMON_DENOMINATOR: u8 = 0x08;
3781const TMAP_FLAG_BACKWARD_DIRECTION: u8 = 0x04;
3784
3785fn parse_tone_map_image(data: &[u8]) -> Result<GainMapMetadata> {
3786 let mut cursor = std::io::Cursor::new(data);
3787
3788 let version = cursor.read_u8()?;
3790 if version != 0 {
3791 return Err(Error::Unsupported("tmap version"));
3792 }
3793
3794 let minimum_version = be_u16(&mut cursor)?;
3796 if minimum_version > 0 {
3797 return Err(Error::Unsupported("tmap minimum version"));
3798 }
3799
3800 let writer_version = be_u16(&mut cursor)?;
3802 if writer_version < minimum_version {
3803 return Err(Error::InvalidData("tmap writer_version < minimum_version"));
3804 }
3805
3806 let flags = cursor.read_u8()?;
3810 let is_multichannel = (flags & TMAP_FLAG_MULTI_CHANNEL) != 0;
3811 let use_base_colour_space = (flags & TMAP_FLAG_USE_BASE_COLOUR_SPACE) != 0;
3812 let backward_direction = (flags & TMAP_FLAG_BACKWARD_DIRECTION) != 0;
3813 let common_denominator = (flags & TMAP_FLAG_COMMON_DENOMINATOR) != 0;
3814
3815 let channel_count = if is_multichannel { 3 } else { 1 };
3816 let mut channels = [GainMapChannel {
3817 gain_map_min_n: 0, gain_map_min_d: 0,
3818 gain_map_max_n: 0, gain_map_max_d: 0,
3819 gamma_n: 0, gamma_d: 0,
3820 base_offset_n: 0, base_offset_d: 0,
3821 alternate_offset_n: 0, alternate_offset_d: 0,
3822 }; 3];
3823
3824 let base_hdr_headroom_n;
3825 let base_hdr_headroom_d;
3826 let alternate_hdr_headroom_n;
3827 let alternate_hdr_headroom_d;
3828
3829 if common_denominator {
3830 let common_d = be_u32(&mut cursor)?;
3844 if common_d == 0 {
3845 return Err(Error::InvalidData("tmap common_denominator is zero"));
3846 }
3847 base_hdr_headroom_n = be_u32(&mut cursor)?;
3848 base_hdr_headroom_d = common_d;
3849 alternate_hdr_headroom_n = be_u32(&mut cursor)?;
3850 alternate_hdr_headroom_d = common_d;
3851
3852 for ch in channels.iter_mut().take(channel_count) {
3853 ch.gain_map_min_n = be_i32(&mut cursor)?;
3854 ch.gain_map_min_d = common_d;
3855 ch.gain_map_max_n = be_i32(&mut cursor)?;
3856 ch.gain_map_max_d = common_d;
3857 ch.gamma_n = be_u32(&mut cursor)?;
3858 ch.gamma_d = common_d;
3859 ch.base_offset_n = be_i32(&mut cursor)?;
3860 ch.base_offset_d = common_d;
3861 ch.alternate_offset_n = be_i32(&mut cursor)?;
3862 ch.alternate_offset_d = common_d;
3863 }
3864 } else {
3865 base_hdr_headroom_n = be_u32(&mut cursor)?;
3867 base_hdr_headroom_d = be_u32(&mut cursor)?;
3868 alternate_hdr_headroom_n = be_u32(&mut cursor)?;
3869 alternate_hdr_headroom_d = be_u32(&mut cursor)?;
3870
3871 for ch in channels.iter_mut().take(channel_count) {
3872 ch.gain_map_min_n = be_i32(&mut cursor)?;
3873 ch.gain_map_min_d = be_u32(&mut cursor)?;
3874 ch.gain_map_max_n = be_i32(&mut cursor)?;
3875 ch.gain_map_max_d = be_u32(&mut cursor)?;
3876 ch.gamma_n = be_u32(&mut cursor)?;
3877 ch.gamma_d = be_u32(&mut cursor)?;
3878 ch.base_offset_n = be_i32(&mut cursor)?;
3879 ch.base_offset_d = be_u32(&mut cursor)?;
3880 ch.alternate_offset_n = be_i32(&mut cursor)?;
3881 ch.alternate_offset_d = be_u32(&mut cursor)?;
3882 }
3883 }
3884
3885 if !is_multichannel {
3887 channels[1] = channels[0];
3888 channels[2] = channels[0];
3889 }
3890
3891 let _ = writer_version;
3894
3895 Ok(GainMapMetadata {
3896 is_multichannel,
3897 use_base_colour_space,
3898 backward_direction,
3899 base_hdr_headroom_n,
3900 base_hdr_headroom_d,
3901 alternate_hdr_headroom_n,
3902 alternate_hdr_headroom_d,
3903 channels,
3904 })
3905}
3906
3907fn read_avif_meta<T: Read + Offset>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<AvifInternalMeta> {
3912 let version = read_fullbox_version_no_flags(src, options)?;
3913
3914 if version != 0 {
3915 return Err(Error::Unsupported("unsupported meta version"));
3916 }
3917
3918 let mut primary_item_id = None;
3919 let mut item_infos = None;
3920 let mut iloc_items = None;
3921 let mut item_references = TryVec::new();
3922 let mut properties = TryVec::new();
3923 let mut idat = None;
3924 let mut entity_groups = TryVec::new();
3925
3926 let mut iter = src.box_iter();
3927 while let Some(mut b) = iter.next_box()? {
3928 match b.head.name {
3929 BoxType::ItemInfoBox => {
3930 if item_infos.is_some() {
3931 return Err(Error::InvalidData("There should be zero or one iinf boxes per ISO 14496-12:2015 § 8.11.6.1"));
3932 }
3933 item_infos = Some(read_iinf(&mut b, options)?);
3934 },
3935 BoxType::ItemLocationBox => {
3936 if iloc_items.is_some() {
3937 return Err(Error::InvalidData("There should be zero or one iloc boxes per ISO 14496-12:2015 § 8.11.3.1"));
3938 }
3939 iloc_items = Some(read_iloc(&mut b, options)?);
3940 },
3941 BoxType::PrimaryItemBox => {
3942 if primary_item_id.is_some() {
3943 return Err(Error::InvalidData("There should be zero or one iloc boxes per ISO 14496-12:2015 § 8.11.4.1"));
3944 }
3945 primary_item_id = Some(read_pitm(&mut b, options)?);
3946 },
3947 BoxType::ImageReferenceBox => {
3948 item_references.append(&mut read_iref(&mut b, options)?)?;
3949 },
3950 BoxType::ImagePropertiesBox => {
3951 properties = read_iprp(&mut b, options)?;
3952 },
3953 BoxType::ItemDataBox => {
3954 if idat.is_some() {
3955 return Err(Error::InvalidData("There should be zero or one idat boxes"));
3956 }
3957 idat = Some(b.read_into_try_vec()?);
3958 },
3959 BoxType::GroupsListBox => {
3960 entity_groups.append(&mut read_grpl(&mut b)?)?;
3961 },
3962 BoxType::HandlerBox => {
3963 let hdlr = read_hdlr(&mut b)?;
3964 if hdlr.handler_type != b"pict" {
3965 warn!("hdlr handler_type: {}", hdlr.handler_type);
3966 return Err(Error::InvalidData("meta handler_type must be 'pict' for AVIF"));
3967 }
3968 },
3969 _ => skip_box_content(&mut b)?,
3970 }
3971
3972 check_parser_state(&b.head, &b.content)?;
3973 }
3974
3975 let primary_item_id = primary_item_id.ok_or(Error::InvalidData("Required pitm box not present in meta box"))?;
3976
3977 let item_infos = item_infos.ok_or(Error::InvalidData("iinf missing"))?;
3978
3979 if let Some(item_info) = item_infos.iter().find(|x| x.item_id == primary_item_id) {
3980 if item_info.item_type != b"av01" && item_info.item_type != b"grid" {
3982 warn!("primary_item_id type: {}", item_info.item_type);
3983 return Err(Error::InvalidData("primary_item_id type is not av01 or grid"));
3984 }
3985 } else {
3986 return Err(Error::InvalidData("primary_item_id not present in iinf box"));
3987 }
3988
3989 Ok(AvifInternalMeta {
3990 properties,
3991 item_references,
3992 primary_item_id,
3993 iloc_items: iloc_items.ok_or(Error::InvalidData("iloc missing"))?,
3994 item_infos,
3995 idat,
3996 entity_groups,
3997 })
3998}
3999
4000fn read_hdlr<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<HandlerBox> {
4003 let (_version, _flags) = read_fullbox_extra(src)?;
4004 skip(src, 4)?;
4006 let handler_type = be_u32(src)?;
4008 skip_box_remain(src)?;
4010 Ok(HandlerBox {
4011 handler_type: FourCC::from(handler_type),
4012 })
4013}
4014
4015fn read_pitm<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<u32> {
4018 let version = read_fullbox_version_no_flags(src, options)?;
4019
4020 let item_id = match version {
4021 0 => be_u16(src)?.into(),
4022 1 => be_u32(src)?,
4023 _ => return Err(Error::Unsupported("unsupported pitm version")),
4024 };
4025
4026 Ok(item_id)
4027}
4028
4029fn read_iinf<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<ItemInfoEntry>> {
4032 let version = read_fullbox_version_no_flags(src, options)?;
4033
4034 match version {
4035 0 | 1 => (),
4036 _ => return Err(Error::Unsupported("unsupported iinf version")),
4037 }
4038
4039 let entry_count = if version == 0 {
4040 be_u16(src)?.to_usize()
4041 } else {
4042 be_u32(src)?.to_usize()
4043 };
4044 let mut item_infos = TryVec::with_capacity(entry_count.min(4096))?;
4046
4047 let mut iter = src.box_iter();
4048 while let Some(mut b) = iter.next_box()? {
4049 if b.head.name != BoxType::ItemInfoEntry {
4050 return Err(Error::InvalidData("iinf box should contain only infe boxes"));
4051 }
4052
4053 item_infos.push(read_infe(&mut b)?)?;
4054
4055 check_parser_state(&b.head, &b.content)?;
4056 }
4057
4058 Ok(item_infos)
4059}
4060
4061fn read_infe<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ItemInfoEntry> {
4064 let (version, _) = read_fullbox_extra(src)?;
4067
4068 let item_id = match version {
4070 2 => be_u16(src)?.into(),
4071 3 => be_u32(src)?,
4072 _ => return Err(Error::Unsupported("unsupported version in 'infe' box")),
4073 };
4074
4075 let item_protection_index = be_u16(src)?;
4076
4077 if item_protection_index != 0 {
4078 return Err(Error::Unsupported("protected items (infe.item_protection_index != 0) are not supported"));
4079 }
4080
4081 let item_type = FourCC::from(be_u32(src)?);
4082 debug!("infe item_id {item_id} item_type: {item_type}");
4083
4084 skip_box_remain(src)?;
4086
4087 Ok(ItemInfoEntry { item_id, item_type })
4088}
4089
4090fn read_iref<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<SingleItemTypeReferenceBox>> {
4091 let mut item_references = TryVec::new();
4092 let version = read_fullbox_version_no_flags(src, options)?;
4093 if version > 1 {
4094 return Err(Error::Unsupported("iref version"));
4095 }
4096
4097 let mut iter = src.box_iter();
4098 while let Some(mut b) = iter.next_box()? {
4099 let from_item_id = if version == 0 {
4100 be_u16(&mut b)?.into()
4101 } else {
4102 be_u32(&mut b)?
4103 };
4104 let reference_count = be_u16(&mut b)?;
4105 let bytes_per_ref: u64 = if version == 0 { 2 } else { 4 };
4107 if (reference_count as u64) * bytes_per_ref > b.bytes_left() {
4108 return Err(Error::InvalidData(
4109 "iref reference_count exceeds remaining box bytes",
4110 ));
4111 }
4112 for reference_index in 0..reference_count {
4113 let to_item_id = if version == 0 {
4114 be_u16(&mut b)?.into()
4115 } else {
4116 be_u32(&mut b)?
4117 };
4118 if from_item_id == to_item_id {
4119 return Err(Error::InvalidData("from_item_id and to_item_id must be different"));
4120 }
4121 item_references.push(SingleItemTypeReferenceBox {
4122 item_type: b.head.name.into(),
4123 from_item_id,
4124 to_item_id,
4125 reference_index,
4126 })?;
4127 }
4128 check_parser_state(&b.head, &b.content)?;
4129 }
4130 Ok(item_references)
4131}
4132
4133const MUST_BE_ESSENTIAL: &[&[u8; 4]] = &[b"a1op", b"lsel", b"clap", b"irot", b"imir"];
4136
4137const MUST_NOT_BE_ESSENTIAL: &[&[u8; 4]] = &[b"a1lx"];
4140
4141fn read_iprp<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<AssociatedProperty>> {
4142 let mut iter = src.box_iter();
4143 let mut properties = TryVec::new();
4144 let mut associations = TryVec::new();
4145
4146 while let Some(mut b) = iter.next_box()? {
4147 match b.head.name {
4148 BoxType::ItemPropertyContainerBox => {
4149 properties = read_ipco(&mut b, options)?;
4150 },
4151 BoxType::ItemPropertyAssociationBox => {
4152 associations = read_ipma(&mut b)?;
4153 },
4154 _ => return Err(Error::InvalidData("unexpected ipco child")),
4155 }
4156 }
4157
4158 let mut associated = TryVec::new();
4159 for a in associations {
4160 let index = match a.property_index {
4161 0 => {
4162 if a.essential {
4164 return Err(Error::InvalidData(
4165 "ipma property_index 0 must not be marked essential",
4166 ));
4167 }
4168 continue;
4169 }
4170 x => x as usize - 1,
4171 };
4172
4173 let Some(entry) = properties.get(index) else {
4174 continue;
4175 };
4176
4177 let is_supported = entry.property != ItemProperty::Unsupported;
4178 let fourcc_bytes = &entry.fourcc.value;
4179
4180 if is_supported {
4181 if a.essential && MUST_NOT_BE_ESSENTIAL.contains(&fourcc_bytes) {
4183 warn!("item {} has {} marked essential (spec forbids it)", a.item_id, entry.fourcc);
4184 if !options.lenient {
4185 return Err(Error::InvalidData(
4186 "property must not be marked essential",
4187 ));
4188 }
4189 }
4190 if !a.essential && MUST_BE_ESSENTIAL.contains(&fourcc_bytes) {
4191 warn!("item {} has {} not marked essential (spec requires it)", a.item_id, entry.fourcc);
4192 if !options.lenient {
4193 return Err(Error::InvalidData(
4194 "property must be marked essential",
4195 ));
4196 }
4197 }
4198
4199 associated.push(AssociatedProperty {
4200 item_id: a.item_id,
4201 property: entry.property.try_clone()?,
4202 })?;
4203 } else if a.essential {
4204 warn!(
4206 "item {} has unsupported property {} marked essential; item will be unusable",
4207 a.item_id, entry.fourcc
4208 );
4209 if !options.lenient {
4210 return Err(Error::Unsupported(
4211 "unsupported property marked as essential",
4212 ));
4213 }
4214 }
4215 }
4217 Ok(associated)
4218}
4219
4220#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4222pub(crate) struct ImageSpatialExtents {
4223 pub(crate) width: u32,
4224 pub(crate) height: u32,
4225}
4226
4227#[derive(Debug, PartialEq)]
4228pub(crate) enum ItemProperty {
4229 Channels(ArrayVec<u8, 16>),
4230 AuxiliaryType(AuxiliaryTypeProperty),
4231 ImageSpatialExtents(ImageSpatialExtents),
4232 ImageGrid(GridConfig),
4233 AV1Config(AV1Config),
4234 ColorInformation(ColorInformation),
4235 Rotation(ImageRotation),
4236 Mirror(ImageMirror),
4237 CleanAperture(CleanAperture),
4238 PixelAspectRatio(PixelAspectRatio),
4239 ContentLightLevel(ContentLightLevel),
4240 MasteringDisplayColourVolume(MasteringDisplayColourVolume),
4241 ContentColourVolume(ContentColourVolume),
4242 AmbientViewingEnvironment(AmbientViewingEnvironment),
4243 OperatingPointSelector(OperatingPointSelector),
4244 LayerSelector(LayerSelector),
4245 AV1LayeredImageIndexing(AV1LayeredImageIndexing),
4246 Unsupported,
4247}
4248
4249impl TryClone for ItemProperty {
4250 fn try_clone(&self) -> Result<Self, TryReserveError> {
4251 Ok(match self {
4252 Self::Channels(val) => Self::Channels(val.clone()),
4253 Self::AuxiliaryType(val) => Self::AuxiliaryType(val.try_clone()?),
4254 Self::ImageSpatialExtents(val) => Self::ImageSpatialExtents(*val),
4255 Self::ImageGrid(val) => Self::ImageGrid(val.clone()),
4256 Self::AV1Config(val) => Self::AV1Config(val.clone()),
4257 Self::ColorInformation(val) => Self::ColorInformation(val.clone()),
4258 Self::Rotation(val) => Self::Rotation(*val),
4259 Self::Mirror(val) => Self::Mirror(*val),
4260 Self::CleanAperture(val) => Self::CleanAperture(*val),
4261 Self::PixelAspectRatio(val) => Self::PixelAspectRatio(*val),
4262 Self::ContentLightLevel(val) => Self::ContentLightLevel(*val),
4263 Self::MasteringDisplayColourVolume(val) => Self::MasteringDisplayColourVolume(*val),
4264 Self::ContentColourVolume(val) => Self::ContentColourVolume(*val),
4265 Self::AmbientViewingEnvironment(val) => Self::AmbientViewingEnvironment(*val),
4266 Self::OperatingPointSelector(val) => Self::OperatingPointSelector(*val),
4267 Self::LayerSelector(val) => Self::LayerSelector(*val),
4268 Self::AV1LayeredImageIndexing(val) => Self::AV1LayeredImageIndexing(*val),
4269 Self::Unsupported => Self::Unsupported,
4270 })
4271 }
4272}
4273
4274struct Association {
4275 item_id: u32,
4276 essential: bool,
4277 property_index: u16,
4278}
4279
4280pub(crate) struct AssociatedProperty {
4281 pub item_id: u32,
4282 pub property: ItemProperty,
4283}
4284
4285fn read_ipma<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<Association>> {
4286 let (version, flags) = read_fullbox_extra(src)?;
4287
4288 let mut associations = TryVec::new();
4289
4290 let entry_count = be_u32(src)?;
4291 let min_bytes_per_entry: u64 = if version == 0 { 3 } else { 5 };
4293 if (entry_count as u64) * min_bytes_per_entry > src.bytes_left() {
4294 return Err(Error::InvalidData(
4295 "ipma entry_count exceeds remaining box bytes",
4296 ));
4297 }
4298 for _ in 0..entry_count {
4299 let item_id = if version == 0 {
4300 be_u16(src)?.into()
4301 } else {
4302 be_u32(src)?
4303 };
4304 let association_count = src.read_u8()?;
4305 for _ in 0..association_count {
4306 let num_association_bytes = if flags & 1 == 1 { 2 } else { 1 };
4307 let association = &mut [0; 2][..num_association_bytes];
4308 src.read_exact(association)?;
4309 let mut association = BitReader::new(association);
4310 let essential = association.read_bool()?;
4311 let property_index = association.read_u16(association.remaining().try_into()?)?;
4312 associations.push(Association {
4313 item_id,
4314 essential,
4315 property_index,
4316 })?;
4317 }
4318 }
4319 Ok(associations)
4320}
4321
4322struct IndexedProperty {
4324 fourcc: FourCC,
4325 property: ItemProperty,
4326}
4327
4328fn read_ipco<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<IndexedProperty>> {
4329 let mut properties = TryVec::new();
4330
4331 let mut iter = src.box_iter();
4332 while let Some(mut b) = iter.next_box()? {
4333 let fourcc: FourCC = b.head.name.into();
4334 let prop = match b.head.name {
4336 BoxType::PixelInformationBox => ItemProperty::Channels(read_pixi(&mut b, options)?),
4337 BoxType::AuxiliaryTypeProperty => ItemProperty::AuxiliaryType(read_auxc(&mut b, options)?),
4338 BoxType::ImageSpatialExtentsBox => ItemProperty::ImageSpatialExtents(read_ispe(&mut b, options)?),
4339 BoxType::ImageGridBox => ItemProperty::ImageGrid(read_grid(&mut b, options)?),
4340 BoxType::AV1CodecConfigurationBox => ItemProperty::AV1Config(read_av1c(&mut b)?),
4341 BoxType::ColorInformationBox => {
4342 match read_colr(&mut b) {
4343 Ok(colr) => ItemProperty::ColorInformation(colr),
4344 Err(_) => ItemProperty::Unsupported,
4345 }
4346 },
4347 BoxType::ImageRotationBox => ItemProperty::Rotation(read_irot(&mut b)?),
4348 BoxType::ImageMirrorBox => ItemProperty::Mirror(read_imir(&mut b)?),
4349 BoxType::CleanApertureBox => ItemProperty::CleanAperture(read_clap(&mut b)?),
4350 BoxType::PixelAspectRatioBox => ItemProperty::PixelAspectRatio(read_pasp(&mut b)?),
4351 BoxType::ContentLightLevelBox => ItemProperty::ContentLightLevel(read_clli(&mut b)?),
4352 BoxType::MasteringDisplayColourVolumeBox => ItemProperty::MasteringDisplayColourVolume(read_mdcv(&mut b)?),
4353 BoxType::ContentColourVolumeBox => ItemProperty::ContentColourVolume(read_cclv(&mut b)?),
4354 BoxType::AmbientViewingEnvironmentBox => ItemProperty::AmbientViewingEnvironment(read_amve(&mut b)?),
4355 BoxType::OperatingPointSelectorBox => ItemProperty::OperatingPointSelector(read_a1op(&mut b)?),
4356 BoxType::LayerSelectorBox => ItemProperty::LayerSelector(read_lsel(&mut b)?),
4357 BoxType::AV1LayeredImageIndexingBox => ItemProperty::AV1LayeredImageIndexing(read_a1lx(&mut b)?),
4358 _ => {
4359 skip_box_remain(&mut b)?;
4360 ItemProperty::Unsupported
4361 },
4362 };
4363 properties.push(IndexedProperty { fourcc, property: prop })?;
4364 }
4365 Ok(properties)
4366}
4367
4368fn read_pixi<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<ArrayVec<u8, 16>> {
4369 let version = read_fullbox_version_no_flags(src, options)?;
4370 if version != 0 {
4371 return Err(Error::Unsupported("pixi version"));
4372 }
4373
4374 let num_channels = usize::from(src.read_u8()?);
4375 let mut channels = ArrayVec::new();
4376 let clamped = num_channels.min(channels.capacity());
4377 channels.extend((0..clamped).map(|_| 0));
4378 src.read_exact(&mut channels).map_err(|_| Error::InvalidData("invalid num_channels"))?;
4379
4380 if options.lenient && src.bytes_left() > 0 {
4382 skip(src, src.bytes_left())?;
4383 }
4384
4385 check_parser_state(&src.head, &src.content)?;
4386 Ok(channels)
4387}
4388
4389#[derive(Debug, PartialEq)]
4390struct AuxiliaryTypeProperty {
4391 aux_data: TryString,
4392}
4393
4394impl AuxiliaryTypeProperty {
4395 #[must_use]
4396 fn type_subtype(&self) -> (&[u8], &[u8]) {
4397 let split = self.aux_data.iter().position(|&b| b == b'\0')
4398 .map(|pos| self.aux_data.split_at(pos));
4399 if let Some((aux_type, rest)) = split {
4400 (aux_type, &rest[1..])
4401 } else {
4402 (&self.aux_data, &[])
4403 }
4404 }
4405}
4406
4407impl TryClone for AuxiliaryTypeProperty {
4408 fn try_clone(&self) -> Result<Self, TryReserveError> {
4409 Ok(Self {
4410 aux_data: self.aux_data.try_clone()?,
4411 })
4412 }
4413}
4414
4415fn read_auxc<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<AuxiliaryTypeProperty> {
4416 let version = read_fullbox_version_no_flags(src, options)?;
4417 if version != 0 {
4418 return Err(Error::Unsupported("auxC version"));
4419 }
4420
4421 let aux_data = src.read_into_try_vec()?;
4422
4423 Ok(AuxiliaryTypeProperty { aux_data })
4424}
4425
4426fn is_depth_auxiliary_urn(urn: &[u8]) -> bool {
4432 urn == b"urn:mpeg:mpegB:cicp:systems:auxiliary:depth"
4433 || urn == b"urn:mpeg:hevc:2015:auxid:2"
4434}
4435
4436fn read_av1c<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<AV1Config> {
4439 let byte0 = src.read_u8()?;
4441 let marker = byte0 >> 7;
4442 let version = byte0 & 0x7F;
4443
4444 if marker != 1 {
4445 return Err(Error::InvalidData("av1C marker must be 1"));
4446 }
4447 if version != 1 {
4448 return Err(Error::Unsupported("av1C version must be 1"));
4449 }
4450
4451 let byte1 = src.read_u8()?;
4452 let profile = byte1 >> 5;
4453 let level = byte1 & 0x1F;
4454
4455 let byte2 = src.read_u8()?;
4456 let tier = byte2 >> 7;
4457 let high_bitdepth = (byte2 >> 6) & 1;
4458 let twelve_bit = (byte2 >> 5) & 1;
4459 let monochrome = (byte2 >> 4) & 1 != 0;
4460 let chroma_subsampling_x = (byte2 >> 3) & 1;
4461 let chroma_subsampling_y = (byte2 >> 2) & 1;
4462 let chroma_sample_position = byte2 & 0x03;
4463
4464 let byte3 = src.read_u8()?;
4465 let _ = byte3;
4468
4469 let bit_depth = if high_bitdepth != 0 {
4470 if twelve_bit != 0 { 12 } else { 10 }
4471 } else {
4472 8
4473 };
4474
4475 skip_box_remain(src)?;
4477
4478 Ok(AV1Config {
4479 profile,
4480 level,
4481 tier,
4482 bit_depth,
4483 monochrome,
4484 chroma_subsampling_x,
4485 chroma_subsampling_y,
4486 chroma_sample_position,
4487 })
4488}
4489
4490fn read_colr<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ColorInformation> {
4493 let colour_type = be_u32(src)?;
4495
4496 match &colour_type.to_be_bytes() {
4497 b"nclx" => {
4498 let color_primaries = be_u16(src)?;
4499 let transfer_characteristics = be_u16(src)?;
4500 let matrix_coefficients = be_u16(src)?;
4501 let full_range_byte = src.read_u8()?;
4502 let full_range = (full_range_byte >> 7) != 0;
4503 skip_box_remain(src)?;
4505 Ok(ColorInformation::Nclx {
4506 color_primaries,
4507 transfer_characteristics,
4508 matrix_coefficients,
4509 full_range,
4510 })
4511 }
4512 b"rICC" | b"prof" => {
4513 let icc_data = src.read_into_try_vec()?;
4514 Ok(ColorInformation::IccProfile(icc_data.to_vec()))
4515 }
4516 _ => {
4517 skip_box_remain(src)?;
4518 Err(Error::Unsupported("unsupported colr colour_type"))
4519 }
4520 }
4521}
4522
4523fn read_irot<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ImageRotation> {
4526 let byte = src.read_u8()?;
4527 let angle_code = byte & 0x03;
4528 let angle = match angle_code {
4529 0 => 0,
4530 1 => 90,
4531 2 => 180,
4532 _ => 270, };
4534 skip_box_remain(src)?;
4535 Ok(ImageRotation { angle })
4536}
4537
4538fn read_imir<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ImageMirror> {
4541 let byte = src.read_u8()?;
4542 let axis = byte & 0x01;
4543 skip_box_remain(src)?;
4544 Ok(ImageMirror { axis })
4545}
4546
4547fn read_clap<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<CleanAperture> {
4550 let width_n = be_u32(src)?;
4551 let width_d = be_u32(src)?;
4552 let height_n = be_u32(src)?;
4553 let height_d = be_u32(src)?;
4554 let horiz_off_n = be_i32(src)?;
4555 let horiz_off_d = be_u32(src)?;
4556 let vert_off_n = be_i32(src)?;
4557 let vert_off_d = be_u32(src)?;
4558 if width_d == 0 || height_d == 0 || horiz_off_d == 0 || vert_off_d == 0 {
4560 return Err(Error::InvalidData("clap denominator cannot be zero"));
4561 }
4562 skip_box_remain(src)?;
4563 Ok(CleanAperture {
4564 width_n, width_d,
4565 height_n, height_d,
4566 horiz_off_n, horiz_off_d,
4567 vert_off_n, vert_off_d,
4568 })
4569}
4570
4571fn read_pasp<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<PixelAspectRatio> {
4574 let h_spacing = be_u32(src)?;
4575 let v_spacing = be_u32(src)?;
4576 skip_box_remain(src)?;
4577 Ok(PixelAspectRatio { h_spacing, v_spacing })
4578}
4579
4580fn read_clli<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ContentLightLevel> {
4583 let max_content_light_level = be_u16(src)?;
4584 let max_pic_average_light_level = be_u16(src)?;
4585 skip_box_remain(src)?;
4586 Ok(ContentLightLevel {
4587 max_content_light_level,
4588 max_pic_average_light_level,
4589 })
4590}
4591
4592fn read_mdcv<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<MasteringDisplayColourVolume> {
4595 let primaries = [
4597 (be_u16(src)?, be_u16(src)?),
4598 (be_u16(src)?, be_u16(src)?),
4599 (be_u16(src)?, be_u16(src)?),
4600 ];
4601 let white_point = (be_u16(src)?, be_u16(src)?);
4602 let max_luminance = be_u32(src)?;
4603 let min_luminance = be_u32(src)?;
4604 skip_box_remain(src)?;
4605 Ok(MasteringDisplayColourVolume {
4606 primaries,
4607 white_point,
4608 max_luminance,
4609 min_luminance,
4610 })
4611}
4612
4613fn read_cclv<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ContentColourVolume> {
4616 let flags = src.read_u8()?;
4617 let primaries_present = flags & 0x20 != 0;
4618 let min_lum_present = flags & 0x10 != 0;
4619 let max_lum_present = flags & 0x08 != 0;
4620 let avg_lum_present = flags & 0x04 != 0;
4621
4622 let primaries = if primaries_present {
4623 Some([
4624 (be_i32(src)?, be_i32(src)?),
4625 (be_i32(src)?, be_i32(src)?),
4626 (be_i32(src)?, be_i32(src)?),
4627 ])
4628 } else {
4629 None
4630 };
4631
4632 let min_luminance = if min_lum_present { Some(be_u32(src)?) } else { None };
4633 let max_luminance = if max_lum_present { Some(be_u32(src)?) } else { None };
4634 let avg_luminance = if avg_lum_present { Some(be_u32(src)?) } else { None };
4635
4636 skip_box_remain(src)?;
4637 Ok(ContentColourVolume {
4638 primaries,
4639 min_luminance,
4640 max_luminance,
4641 avg_luminance,
4642 })
4643}
4644
4645fn read_amve<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<AmbientViewingEnvironment> {
4648 let ambient_illuminance = be_u32(src)?;
4649 let ambient_light_x = be_u16(src)?;
4650 let ambient_light_y = be_u16(src)?;
4651 skip_box_remain(src)?;
4652 Ok(AmbientViewingEnvironment {
4653 ambient_illuminance,
4654 ambient_light_x,
4655 ambient_light_y,
4656 })
4657}
4658
4659fn read_a1op<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<OperatingPointSelector> {
4662 let op_index = src.read_u8()?;
4663 if op_index > 31 {
4664 return Err(Error::InvalidData("a1op op_index must be 0..31"));
4665 }
4666 skip_box_remain(src)?;
4667 Ok(OperatingPointSelector { op_index })
4668}
4669
4670fn read_lsel<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<LayerSelector> {
4673 let layer_id = be_u16(src)?;
4674 skip_box_remain(src)?;
4675 Ok(LayerSelector { layer_id })
4676}
4677
4678fn read_a1lx<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<AV1LayeredImageIndexing> {
4681 let flags = src.read_u8()?;
4682 let large_size = flags & 0x01 != 0;
4683 let layer_sizes = if large_size {
4684 [be_u32(src)?, be_u32(src)?, be_u32(src)?]
4685 } else {
4686 [u32::from(be_u16(src)?), u32::from(be_u16(src)?), u32::from(be_u16(src)?)]
4687 };
4688 skip_box_remain(src)?;
4689 Ok(AV1LayeredImageIndexing { layer_sizes })
4690}
4691
4692fn read_ispe<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<ImageSpatialExtents> {
4695 let _version = read_fullbox_version_no_flags(src, options)?;
4696 let width = be_u32(src)?;
4699 let height = be_u32(src)?;
4700
4701 if width == 0 || height == 0 {
4703 return Err(Error::InvalidData("ispe dimensions cannot be zero"));
4704 }
4705
4706 Ok(ImageSpatialExtents { width, height })
4707}
4708
4709fn read_mvhd<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<MovieHeader> {
4712 let version = src.read_u8()?;
4713 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
4714
4715 let (timescale, duration) = if version == 1 {
4716 let _creation_time = be_u64(src)?;
4717 let _modification_time = be_u64(src)?;
4718 let timescale = be_u32(src)?;
4719 let duration = be_u64(src)?;
4720 (timescale, duration)
4721 } else {
4722 let _creation_time = be_u32(src)?;
4723 let _modification_time = be_u32(src)?;
4724 let timescale = be_u32(src)?;
4725 let duration = be_u32(src)?;
4726 (timescale, duration as u64)
4727 };
4728
4729 skip_box_remain(src)?;
4731
4732 Ok(MovieHeader { _timescale: timescale, _duration: duration })
4733}
4734
4735fn read_mdhd<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<MediaHeader> {
4738 let version = src.read_u8()?;
4739 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
4740
4741 let (timescale, duration) = if version == 1 {
4742 let _creation_time = be_u64(src)?;
4743 let _modification_time = be_u64(src)?;
4744 let timescale = be_u32(src)?;
4745 let duration = be_u64(src)?;
4746 (timescale, duration)
4747 } else {
4748 let _creation_time = be_u32(src)?;
4749 let _modification_time = be_u32(src)?;
4750 let timescale = be_u32(src)?;
4751 let duration = be_u32(src)?;
4752 (timescale, duration as u64)
4753 };
4754
4755 skip_box_remain(src)?;
4757
4758 Ok(MediaHeader { timescale, _duration: duration })
4759}
4760
4761fn read_stts<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<TimeToSampleEntry>> {
4764 let _version = src.read_u8()?;
4765 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
4766 let entry_count = be_u32(src)?;
4767 if (entry_count as u64) * 8 > src.bytes_left() {
4769 return Err(Error::InvalidData(
4770 "stts entry_count exceeds remaining box bytes",
4771 ));
4772 }
4773
4774 let mut entries = TryVec::new();
4775 for _ in 0..entry_count {
4776 entries.push(TimeToSampleEntry {
4777 sample_count: be_u32(src)?,
4778 sample_delta: be_u32(src)?,
4779 })?;
4780 }
4781
4782 Ok(entries)
4783}
4784
4785fn read_stsc<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<SampleToChunkEntry>> {
4788 let _version = src.read_u8()?;
4789 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
4790 let entry_count = be_u32(src)?;
4791 if (entry_count as u64) * 12 > src.bytes_left() {
4793 return Err(Error::InvalidData(
4794 "stsc entry_count exceeds remaining box bytes",
4795 ));
4796 }
4797
4798 let mut entries = TryVec::new();
4799 for _ in 0..entry_count {
4800 entries.push(SampleToChunkEntry {
4801 first_chunk: be_u32(src)?,
4802 samples_per_chunk: be_u32(src)?,
4803 _sample_description_index: be_u32(src)?,
4804 })?;
4805 }
4806
4807 Ok(entries)
4808}
4809
4810fn read_stsz<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<u32>> {
4813 let _version = src.read_u8()?;
4814 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
4815 let sample_size = be_u32(src)?;
4816 let sample_count = be_u32(src)?;
4817
4818 const MAX_SAMPLE_COUNT: u32 = 64 * 1024 * 1024;
4821 if sample_count > MAX_SAMPLE_COUNT {
4822 return Err(Error::InvalidData("stsz sample_count exceeds maximum"));
4823 }
4824
4825 let mut sizes = TryVec::new();
4826 if sample_size == 0 {
4827 if (sample_count as u64) * 4 > src.bytes_left() {
4829 return Err(Error::InvalidData(
4830 "stsz sample_count exceeds remaining box bytes",
4831 ));
4832 }
4833 for _ in 0..sample_count {
4835 sizes.push(be_u32(src)?)?;
4836 }
4837 } else {
4838 for _ in 0..sample_count {
4840 sizes.push(sample_size)?;
4841 }
4842 }
4843
4844 Ok(sizes)
4845}
4846
4847fn read_chunk_offsets<T: Read>(src: &mut BMFFBox<'_, T>, is_64bit: bool) -> Result<TryVec<u64>> {
4850 let _version = src.read_u8()?;
4851 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
4852 let entry_count = be_u32(src)?;
4853 let bytes_per_entry: u64 = if is_64bit { 8 } else { 4 };
4854 if (entry_count as u64) * bytes_per_entry > src.bytes_left() {
4855 return Err(Error::InvalidData(
4856 "chunk offset entry_count exceeds remaining box bytes",
4857 ));
4858 }
4859
4860 let mut offsets = TryVec::new();
4861 for _ in 0..entry_count {
4862 let offset = if is_64bit {
4863 be_u64(src)?
4864 } else {
4865 be_u32(src)? as u64
4866 };
4867 offsets.push(offset)?;
4868 }
4869
4870 Ok(offsets)
4871}
4872
4873fn read_stsd<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TrackCodecConfig> {
4879 let _version = src.read_u8()?;
4880 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
4881 let entry_count = be_u32(src)?;
4882
4883 let mut config = TrackCodecConfig::default();
4884
4885 let mut iter = src.box_iter();
4887 for _ in 0..entry_count {
4888 let Some(mut entry_box) = iter.next_box()? else {
4889 break;
4890 };
4891
4892 if entry_box.head.name != BoxType::AV1SampleEntry {
4894 skip_box_remain(&mut entry_box)?;
4895 continue;
4896 }
4897
4898 const VISUAL_SAMPLE_ENTRY_SIZE: u64 = 78;
4903 if entry_box.bytes_left() < VISUAL_SAMPLE_ENTRY_SIZE {
4904 skip_box_remain(&mut entry_box)?;
4905 continue;
4906 }
4907 skip(&mut entry_box, VISUAL_SAMPLE_ENTRY_SIZE)?;
4908
4909 let mut sub_iter = entry_box.box_iter();
4911 while let Some(mut sub_box) = sub_iter.next_box()? {
4912 match sub_box.head.name {
4913 BoxType::AV1CodecConfigurationBox => {
4914 config.av1_config = Some(read_av1c(&mut sub_box)?);
4915 }
4916 BoxType::ColorInformationBox => {
4917 if let Ok(colr) = read_colr(&mut sub_box) {
4918 config.color_info = Some(colr);
4919 } else {
4920 skip_box_remain(&mut sub_box)?;
4921 }
4922 }
4923 _ => {
4924 skip_box_remain(&mut sub_box)?;
4925 }
4926 }
4927 }
4928
4929 if config.av1_config.is_some() {
4931 break;
4932 }
4933 }
4934
4935 Ok(config)
4936}
4937
4938fn read_stbl<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<(SampleTable, TrackCodecConfig)> {
4941 let mut time_to_sample = TryVec::new();
4942 let mut sample_to_chunk = TryVec::new();
4943 let mut sample_sizes = TryVec::new();
4944 let mut chunk_offsets = TryVec::new();
4945 let mut codec_config = TrackCodecConfig::default();
4946
4947 let mut iter = src.box_iter();
4948 while let Some(mut b) = iter.next_box()? {
4949 match b.head.name {
4950 BoxType::SampleDescriptionBox => {
4951 codec_config = read_stsd(&mut b)?;
4952 }
4953 BoxType::TimeToSampleBox => {
4954 time_to_sample = read_stts(&mut b)?;
4955 }
4956 BoxType::SampleToChunkBox => {
4957 sample_to_chunk = read_stsc(&mut b)?;
4958 }
4959 BoxType::SampleSizeBox => {
4960 sample_sizes = read_stsz(&mut b)?;
4961 }
4962 BoxType::ChunkOffsetBox => {
4963 chunk_offsets = read_chunk_offsets(&mut b, false)?;
4964 }
4965 BoxType::ChunkLargeOffsetBox => {
4966 chunk_offsets = read_chunk_offsets(&mut b, true)?;
4967 }
4968 _ => {
4969 skip_box_remain(&mut b)?;
4970 }
4971 }
4972 }
4973
4974 let mut sample_offsets = TryVec::new();
4977 let mut sample_idx = 0usize;
4978 for (i, entry) in sample_to_chunk.iter().enumerate() {
4979 let next_first_chunk = sample_to_chunk
4980 .get(i + 1)
4981 .map(|e| e.first_chunk)
4982 .unwrap_or(u32::MAX);
4983
4984 for chunk_no in entry.first_chunk..next_first_chunk {
4985 if chunk_no == 0 {
4986 break;
4987 }
4988 let co_idx = (chunk_no - 1) as usize;
4989 let chunk_offset = match chunk_offsets.get(co_idx) {
4990 Some(&o) => o,
4991 None => break,
4992 };
4993
4994 let mut offset = chunk_offset;
4995 for _ in 0..entry.samples_per_chunk {
4996 if sample_idx >= sample_sizes.len() {
4997 break;
4998 }
4999 sample_offsets.push(offset)?;
5000 offset += *sample_sizes.get(sample_idx)
5001 .ok_or(Error::InvalidData("sample index mismatch"))? as u64;
5002 sample_idx += 1;
5003 }
5004 }
5005 }
5006
5007 Ok((SampleTable {
5008 time_to_sample,
5009 sample_sizes,
5010 sample_offsets,
5011 }, codec_config))
5012}
5013
5014fn read_tkhd<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<u32> {
5017 let version = src.read_u8()?;
5018 let _flags = [src.read_u8()?, src.read_u8()?, src.read_u8()?];
5019
5020 let track_id = if version == 1 {
5021 let _creation_time = be_u64(src)?;
5022 let _modification_time = be_u64(src)?;
5023 let track_id = be_u32(src)?;
5024 let _reserved = be_u32(src)?;
5025 let _duration = be_u64(src)?;
5026 track_id
5027 } else {
5028 let _creation_time = be_u32(src)?;
5029 let _modification_time = be_u32(src)?;
5030 let track_id = be_u32(src)?;
5031 let _reserved = be_u32(src)?;
5032 let _duration = be_u32(src)?;
5033 track_id
5034 };
5035
5036 skip_box_remain(src)?;
5038 Ok(track_id)
5039}
5040
5041fn read_tref<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<TrackReference>> {
5046 let mut refs = TryVec::new();
5047 let mut iter = src.box_iter();
5048 while let Some(mut b) = iter.next_box()? {
5049 let reference_type = FourCC::from(u32::from(b.head.name));
5050 let bytes_left = b.bytes_left();
5051 if bytes_left < 4 || bytes_left % 4 != 0 {
5052 skip_box_remain(&mut b)?;
5053 continue;
5054 }
5055 let count = bytes_left / 4;
5056 let mut track_ids = TryVec::new();
5057 for _ in 0..count {
5058 track_ids.push(be_u32(&mut b)?)?;
5059 }
5060 refs.push(TrackReference { reference_type, track_ids })?;
5061 }
5062 Ok(refs)
5063}
5064
5065fn read_elst<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<u32> {
5070 let (version, flags) = read_fullbox_extra(src)?;
5071
5072 let entry_count = be_u32(src)?;
5073 let entry_size: u64 = if version == 1 { 20 } else { 12 };
5075 skip(src, (entry_count as u64).checked_mul(entry_size)
5076 .ok_or(Error::InvalidData("edit list entry count overflow"))?)?;
5077 skip_box_remain(src)?;
5078
5079 if flags & 1 != 0 {
5081 Ok(0) } else {
5083 Ok(1) }
5085}
5086
5087fn read_moov<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<ParsedTrack>> {
5090 let mut tracks = TryVec::new();
5091
5092 let mut iter = src.box_iter();
5093 while let Some(mut b) = iter.next_box()? {
5094 match b.head.name {
5095 BoxType::MovieHeaderBox => {
5096 let _mvhd = read_mvhd(&mut b)?;
5097 }
5098 BoxType::TrackBox => {
5099 if let Some(track) = read_trak(&mut b)? {
5100 tracks.push(track)?;
5101 }
5102 }
5103 _ => {
5104 skip_box_remain(&mut b)?;
5105 }
5106 }
5107 }
5108
5109 Ok(tracks)
5110}
5111
5112fn read_trak<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<Option<ParsedTrack>> {
5115 let mut track_id = 0u32;
5116 let mut references = TryVec::new();
5117 let mut loop_count = 1u32; let mut mdia_result: Option<(FourCC, u32, SampleTable, TrackCodecConfig)> = None;
5119
5120 let mut iter = src.box_iter();
5121 while let Some(mut b) = iter.next_box()? {
5122 match b.head.name {
5123 BoxType::TrackHeaderBox => {
5124 track_id = read_tkhd(&mut b)?;
5125 }
5126 BoxType::TrackReferenceBox => {
5127 references = read_tref(&mut b)?;
5128 }
5129 BoxType::EditBox => {
5130 let mut edts_iter = b.box_iter();
5132 while let Some(mut eb) = edts_iter.next_box()? {
5133 if eb.head.name == BoxType::EditListBox {
5134 loop_count = read_elst(&mut eb)?;
5135 } else {
5136 skip_box_remain(&mut eb)?;
5137 }
5138 }
5139 }
5140 BoxType::MediaBox => {
5141 mdia_result = read_mdia(&mut b)?;
5142 }
5143 _ => {
5144 skip_box_remain(&mut b)?;
5145 }
5146 }
5147 }
5148
5149 if let Some((handler_type, media_timescale, sample_table, codec_config)) = mdia_result {
5150 Ok(Some(ParsedTrack {
5151 track_id,
5152 handler_type,
5153 media_timescale,
5154 sample_table,
5155 references,
5156 loop_count,
5157 codec_config,
5158 }))
5159 } else {
5160 Ok(None)
5161 }
5162}
5163
5164fn read_mdia<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<Option<(FourCC, u32, SampleTable, TrackCodecConfig)>> {
5167 let mut media_timescale = 1000; let mut handler_type = FourCC::default();
5169 let mut stbl_result: Option<(SampleTable, TrackCodecConfig)> = None;
5170
5171 let mut iter = src.box_iter();
5172 while let Some(mut b) = iter.next_box()? {
5173 match b.head.name {
5174 BoxType::MediaHeaderBox => {
5175 let mdhd = read_mdhd(&mut b)?;
5176 media_timescale = mdhd.timescale;
5177 }
5178 BoxType::HandlerBox => {
5179 let hdlr = read_hdlr(&mut b)?;
5180 handler_type = hdlr.handler_type;
5181 }
5182 BoxType::MediaInformationBox => {
5183 stbl_result = read_minf(&mut b)?;
5184 }
5185 _ => {
5186 skip_box_remain(&mut b)?;
5187 }
5188 }
5189 }
5190
5191 if let Some((stbl, codec_config)) = stbl_result {
5192 Ok(Some((handler_type, media_timescale, stbl, codec_config)))
5193 } else {
5194 Ok(None)
5195 }
5196}
5197
5198fn associate_tracks(tracks: TryVec<ParsedTrack>) -> Result<ParsedAnimationData> {
5204 let color_idx = tracks
5206 .iter()
5207 .position(|t| t.handler_type == b"pict")
5208 .or_else(|| {
5209 tracks.iter().position(|t| t.handler_type != b"soun")
5211 })
5212 .ok_or(Error::InvalidData("no color track found in moov"))?;
5213
5214 let color_track = tracks.get(color_idx)
5215 .ok_or(Error::InvalidData("color track index out of bounds"))?;
5216 let color_track_id = color_track.track_id;
5217
5218 let alpha_idx = tracks.iter().position(|t| {
5220 matches!(&t.handler_type.value, b"auxv" | b"pict")
5221 && t.references.iter().any(|r| {
5222 r.reference_type == b"auxl"
5223 && r.track_ids.iter().any(|&id| id == color_track_id)
5224 })
5225 });
5226
5227 if let Some(ai) = alpha_idx {
5228 let alpha_track = tracks.get(ai)
5229 .ok_or(Error::InvalidData("alpha track index out of bounds"))?;
5230 let color_track = tracks.get(color_idx)
5231 .ok_or(Error::InvalidData("color track index out of bounds"))?;
5232 let alpha_frames = alpha_track.sample_table.sample_sizes.len();
5233 let color_frames = color_track.sample_table.sample_sizes.len();
5234 if alpha_frames != color_frames {
5235 warn!(
5236 "alpha track has {} frames but color track has {} frames",
5237 alpha_frames, color_frames
5238 );
5239 }
5240 }
5241
5242 let mut tracks_vec: std::vec::Vec<ParsedTrack> = tracks.into_iter().collect();
5245
5246 let (color_track, alpha_track) = if let Some(ai) = alpha_idx {
5248 if ai > color_idx {
5249 let alpha = tracks_vec.remove(ai);
5250 let color = tracks_vec.remove(color_idx);
5251 (color, Some(alpha))
5252 } else {
5253 let color = tracks_vec.remove(color_idx);
5254 let alpha = tracks_vec.remove(ai);
5255 (color, Some(alpha))
5256 }
5257 } else {
5258 let color = tracks_vec.remove(color_idx);
5259 (color, None)
5260 };
5261
5262 let (alpha_timescale, alpha_sample_table) = match alpha_track {
5263 Some(t) => (Some(t.media_timescale), Some(t.sample_table)),
5264 None => (None, None),
5265 };
5266
5267 Ok(ParsedAnimationData {
5268 color_timescale: color_track.media_timescale,
5269 color_codec_config: color_track.codec_config,
5270 color_sample_table: color_track.sample_table,
5271 alpha_timescale,
5272 alpha_sample_table,
5273 loop_count: color_track.loop_count,
5274 })
5275}
5276
5277fn read_minf<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<Option<(SampleTable, TrackCodecConfig)>> {
5279 let mut iter = src.box_iter();
5280 while let Some(mut b) = iter.next_box()? {
5281 if b.head.name == BoxType::SampleTableBox {
5282 return Ok(Some(read_stbl(&mut b)?));
5283 } else {
5284 skip_box_remain(&mut b)?;
5285 }
5286 }
5287 Ok(None)
5288}
5289
5290#[cfg(feature = "eager")]
5292#[allow(deprecated)]
5293fn extract_animation_frames(
5294 sample_table: &SampleTable,
5295 media_timescale: u32,
5296 mdats: &mut [MediaDataBox],
5297) -> Result<TryVec<AnimationFrame>> {
5298 let mut frames = TryVec::new();
5299
5300 let mut frame_durations = TryVec::new();
5302 for entry in &sample_table.time_to_sample {
5303 for _ in 0..entry.sample_count {
5304 let duration_ms = if media_timescale > 0 {
5305 ((entry.sample_delta as u64) * 1000) / (media_timescale as u64)
5306 } else {
5307 0
5308 };
5309 frame_durations.push(u32::try_from(duration_ms).unwrap_or(u32::MAX))?;
5310 }
5311 }
5312
5313 for i in 0..sample_table.sample_sizes.len() {
5315 let sample_offset = *sample_table.sample_offsets.get(i)
5316 .ok_or(Error::InvalidData("sample offset index out of bounds"))?;
5317 let sample_size = *sample_table.sample_sizes.get(i)
5318 .ok_or(Error::InvalidData("sample size index out of bounds"))?;
5319 let duration_ms = frame_durations.get(i).copied().unwrap_or(0);
5320
5321 let mut frame_data = TryVec::new();
5322 let mut found = false;
5323
5324 for mdat in mdats.iter_mut() {
5325 let range = ExtentRange::WithLength(Range {
5326 start: sample_offset,
5327 end: sample_offset + sample_size as u64,
5328 });
5329
5330 if mdat.contains_extent(&range) {
5331 mdat.read_extent(&range, &mut frame_data)?;
5332 found = true;
5333 break;
5334 }
5335 }
5336
5337 if !found {
5338 log::warn!("Animation frame {} not found in mdat", i);
5339 }
5340
5341 frames.push(AnimationFrame {
5342 data: frame_data,
5343 duration_ms,
5344 })?;
5345 }
5346
5347 Ok(frames)
5348}
5349
5350fn read_grid<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<GridConfig> {
5353 let version = read_fullbox_version_no_flags(src, options)?;
5354 if version > 0 {
5355 return Err(Error::Unsupported("grid version > 0"));
5356 }
5357
5358 let flags_byte = src.read_u8()?;
5359 let rows = src.read_u8()?;
5360 let columns = src.read_u8()?;
5361
5362 let (output_width, output_height) = if flags_byte & 1 == 0 {
5364 (u32::from(be_u16(src)?), u32::from(be_u16(src)?))
5366 } else {
5367 (be_u32(src)?, be_u32(src)?)
5369 };
5370
5371 Ok(GridConfig {
5372 rows,
5373 columns,
5374 output_width,
5375 output_height,
5376 })
5377}
5378
5379fn read_iloc<T: Read>(src: &mut BMFFBox<'_, T>, options: &ParseOptions) -> Result<TryVec<ItemLocationBoxItem>> {
5382 let version: IlocVersion = read_fullbox_version_no_flags(src, options)?.try_into()?;
5383
5384 let iloc = src.read_into_try_vec()?;
5385 let mut iloc = BitReader::new(&iloc);
5386
5387 let offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
5388 let length_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
5389 let base_offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?;
5390
5391 let index_size: Option<IlocFieldSize> = match version {
5392 IlocVersion::One | IlocVersion::Two => Some(iloc.read_u8(4)?.try_into()?),
5393 IlocVersion::Zero => {
5394 let _reserved = iloc.read_u8(4)?;
5395 None
5396 },
5397 };
5398
5399 let item_count = match version {
5400 IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?,
5401 IlocVersion::Two => iloc.read_u32(32)?,
5402 };
5403
5404 let mut items = TryVec::with_capacity(item_count.to_usize().min(4096))?;
5406
5407 for _ in 0..item_count {
5408 let item_id = match version {
5409 IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?,
5410 IlocVersion::Two => iloc.read_u32(32)?,
5411 };
5412
5413 let construction_method = match version {
5419 IlocVersion::Zero => ConstructionMethod::File,
5420 IlocVersion::One | IlocVersion::Two => {
5421 let _reserved = iloc.read_u16(12)?;
5422 match iloc.read_u16(4)? {
5423 0 => ConstructionMethod::File,
5424 1 => ConstructionMethod::Idat,
5425 2 => return Err(Error::Unsupported("construction_method 'item_offset' is not supported")),
5426 _ => return Err(Error::InvalidData("construction_method is taken from the set 0, 1 or 2 per ISO 14496-12:2015 § 8.11.3.3")),
5427 }
5428 },
5429 };
5430
5431 let data_reference_index = iloc.read_u16(16)?;
5432
5433 if data_reference_index != 0 {
5434 return Err(Error::Unsupported("external file references (iloc.data_reference_index != 0) are not supported"));
5435 }
5436
5437 let base_offset = iloc.read_u64(base_offset_size.to_bits())?;
5438 let extent_count = iloc.read_u16(16)?;
5439
5440 if extent_count < 1 {
5441 return Err(Error::InvalidData("extent_count must have a value 1 or greater per ISO 14496-12:2015 § 8.11.3.3"));
5442 }
5443
5444 let mut extents = TryVec::with_capacity(extent_count.to_usize())?;
5445
5446 for _ in 0..extent_count {
5447 let _extent_index = match &index_size {
5449 None | Some(IlocFieldSize::Zero) => None,
5450 Some(index_size) => Some(iloc.read_u64(index_size.to_bits())?),
5451 };
5452
5453 let extent_offset = iloc.read_u64(offset_size.to_bits())?;
5458 let extent_length = iloc.read_u64(length_size.to_bits())?;
5459
5460 let start = base_offset
5463 .checked_add(extent_offset)
5464 .ok_or(Error::InvalidData("offset calculation overflow"))?;
5465 let extent_range = if extent_length == 0 {
5466 ExtentRange::ToEnd(RangeFrom { start })
5467 } else {
5468 let end = start
5469 .checked_add(extent_length)
5470 .ok_or(Error::InvalidData("end calculation overflow"))?;
5471 ExtentRange::WithLength(Range { start, end })
5472 };
5473
5474 extents.push(ItemLocationBoxExtent { extent_range })?;
5475 }
5476
5477 items.push(ItemLocationBoxItem { item_id, construction_method, extents })?;
5478 }
5479
5480 if iloc.remaining() == 0 {
5481 Ok(items)
5482 } else {
5483 Err(Error::InvalidData("invalid iloc size"))
5484 }
5485}
5486
5487fn read_ftyp<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<FileTypeBox> {
5490 let major = be_u32(src)?;
5491 let minor = be_u32(src)?;
5492 let bytes_left = src.bytes_left();
5493 if !bytes_left.is_multiple_of(4) {
5494 return Err(Error::InvalidData("invalid ftyp size"));
5495 }
5496 let brand_count = bytes_left / 4;
5498 let mut brands = TryVec::with_capacity(brand_count.try_into()?)?;
5499 for _ in 0..brand_count {
5500 brands.push(be_u32(src)?.into())?;
5501 }
5502 Ok(FileTypeBox {
5503 major_brand: From::from(major),
5504 minor_version: minor,
5505 compatible_brands: brands,
5506 })
5507}
5508
5509#[cfg_attr(debug_assertions, track_caller)]
5510fn check_parser_state<T>(header: &BoxHeader, left: &Take<T>) -> Result<(), Error> {
5511 let limit = left.limit();
5512 if limit == 0 || header.size == u64::MAX {
5514 Ok(())
5515 } else {
5516 Err(Error::InvalidData("unread box content or bad parser sync"))
5517 }
5518}
5519
5520fn skip<T: Read>(src: &mut T, bytes: u64) -> Result<()> {
5522 std::io::copy(&mut src.take(bytes), &mut std::io::sink())?;
5523 Ok(())
5524}
5525
5526fn be_u16<T: ReadBytesExt>(src: &mut T) -> Result<u16> {
5527 src.read_u16::<byteorder::BigEndian>().map_err(From::from)
5528}
5529
5530fn be_u32<T: ReadBytesExt>(src: &mut T) -> Result<u32> {
5531 src.read_u32::<byteorder::BigEndian>().map_err(From::from)
5532}
5533
5534fn be_i32<T: ReadBytesExt>(src: &mut T) -> Result<i32> {
5535 src.read_i32::<byteorder::BigEndian>().map_err(From::from)
5536}
5537
5538fn be_u64<T: ReadBytesExt>(src: &mut T) -> Result<u64> {
5539 src.read_u64::<byteorder::BigEndian>().map_err(From::from)
5540}