Skip to content

Commit d4ad2a4

Browse files
committed
Add struct to collect AVIF information
1 parent e699e65 commit d4ad2a4

File tree

1 file changed

+42
-99
lines changed

1 file changed

+42
-99
lines changed

mp4parse/src/lib.rs

Lines changed: 42 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -755,6 +755,11 @@ impl AvifContext {
755755
}
756756
}
757757

758+
struct AvifMeta {
759+
primary_item_id: u32,
760+
iloc_items: TryVec<ItemLocationBoxItem>,
761+
}
762+
758763
/// A Media Data Box
759764
/// See ISO 14496-12:2015 § 8.1.1
760765
struct MediaDataBox {
@@ -901,7 +906,7 @@ struct ItemLocationBoxItem {
901906
extents: TryVec<ItemLocationBoxExtent>,
902907
}
903908

904-
#[derive(Clone, Copy, Debug)]
909+
#[derive(Clone, Copy, Debug, PartialEq)]
905910
enum ConstructionMethod {
906911
File,
907912
Idat,
@@ -1086,46 +1091,6 @@ impl<'a, T: Read> BMFFBox<'a, T> {
10861091
}
10871092
}
10881093

1089-
impl<'a, T: Read + Offset> BMFFBox<'a, T> {
1090-
/// Check whether the beginning of `extent` is within the bounds of the `BMFFBox`.
1091-
/// We assume extents to not cross box boundaries. If so, this will cause an error
1092-
/// in `read_extent`.
1093-
fn contains_extent(&self, extent: &ExtentRange) -> bool {
1094-
if self.offset() <= extent.start() {
1095-
let start_offset = extent.start() - self.offset();
1096-
start_offset < self.bytes_left()
1097-
} else {
1098-
false
1099-
}
1100-
}
1101-
1102-
/// Read the range specified by `extent` into `buf` or return an error if the range is not
1103-
/// fully contained within the `BMFFBox`.
1104-
fn read_extent(&mut self, extent: &ExtentRange, buf: &mut TryVec<u8>) -> Result<()> {
1105-
let start_offset = extent
1106-
.start()
1107-
.checked_sub(self.offset())
1108-
.expect("box does not contain extent");
1109-
skip(self, start_offset)?;
1110-
match extent {
1111-
ExtentRange::WithLength(range) => {
1112-
let len = range
1113-
.end
1114-
.checked_sub(range.start)
1115-
.expect("range start > end");
1116-
if len > self.bytes_left() {
1117-
return Err(Error::InvalidData("extent crosses box boundary"));
1118-
}
1119-
self.take(len).try_read_to_end(buf)?;
1120-
}
1121-
ExtentRange::ToEnd(_) => {
1122-
self.try_read_to_end(buf)?;
1123-
}
1124-
}
1125-
Ok(())
1126-
}
1127-
}
1128-
11291094
impl<'a, T> Drop for BMFFBox<'a, T> {
11301095
fn drop(&mut self) {
11311096
if self.content.limit() > 0 {
@@ -1264,43 +1229,20 @@ pub fn read_avif<T: Read>(f: &mut T, context: &mut AvifContext) -> Result<()> {
12641229
}
12651230
}
12661231

1267-
let mut read_meta = false;
1232+
let mut meta = None;
12681233
let mut mdats = TryVec::new();
1269-
let mut primary_item_extents = None;
1270-
let mut primary_item_extents_data: TryVec<TryVec<u8>> = TryVec::new();
12711234

12721235
while let Some(mut b) = iter.next_box()? {
12731236
match b.head.name {
12741237
BoxType::MetadataBox => {
1275-
if read_meta {
1238+
if meta.is_some() {
12761239
return Err(Error::InvalidData(
12771240
"There should be zero or one meta boxes per ISO 14496-12:2015 § 8.11.1.1",
12781241
));
12791242
}
1280-
read_meta = true;
1281-
let primary_item_loc = read_avif_meta(&mut b)?;
1282-
match primary_item_loc.construction_method {
1283-
ConstructionMethod::File => {
1284-
primary_item_extents_data
1285-
.resize_with(primary_item_loc.extents.len(), Default::default)?;
1286-
primary_item_extents = Some(primary_item_loc.extents);
1287-
}
1288-
_ => return Err(Error::Unsupported("unsupported construction_method")),
1289-
}
1243+
meta = Some(read_avif_meta(&mut b)?);
12901244
}
12911245
BoxType::MediaDataBox => {
1292-
// See ISO 14496-12:2015 § 8.1.1
1293-
// If we know our primary item location by this point, try to read it out of this
1294-
// mdat directly and avoid a copy
1295-
if let Some(extents) = &primary_item_extents {
1296-
for (extent, data) in extents.iter().zip(primary_item_extents_data.iter_mut()) {
1297-
if b.contains_extent(&extent.extent_range) {
1298-
b.read_extent(&extent.extent_range, data)?;
1299-
}
1300-
}
1301-
}
1302-
1303-
// Store any remaining data for potential later extraction
13041246
if b.bytes_left() > 0 {
13051247
let offset = b.offset();
13061248
let data = b.read_into_try_vec()?;
@@ -1313,39 +1255,35 @@ pub fn read_avif<T: Read>(f: &mut T, context: &mut AvifContext) -> Result<()> {
13131255
check_parser_state!(b.content);
13141256
}
13151257

1316-
// If the `mdat` box came before the `meta` box, we need to fill in our primary item data
1317-
let primary_item_extents =
1318-
primary_item_extents.ok_or(Error::InvalidData("primary item extents missing"))?;
1319-
for (extent, data) in primary_item_extents
1320-
.iter()
1321-
.zip(primary_item_extents_data.iter_mut())
1322-
{
1323-
if data.is_empty() {
1324-
// try to find an overlapping mdat
1325-
for mdat in mdats.iter_mut() {
1326-
if mdat.matches_extent(&extent.extent_range) {
1327-
data.append(&mut mdat.data)?;
1328-
} else if mdat.contains_extent(&extent.extent_range) {
1329-
mdat.read_extent(&extent.extent_range, data)?;
1330-
}
1258+
let meta = meta.ok_or(Error::InvalidData("missing meta"))?;
1259+
let primary_item_loc = get_primary_item_loc(&meta)?;
1260+
if primary_item_loc.construction_method != ConstructionMethod::File {
1261+
return Err(Error::Unsupported("unsupported construction_method"));
1262+
}
1263+
1264+
let mut primary_item = TryVec::new();
1265+
for extent in primary_item_loc.extents.iter() {
1266+
// try to find an overlapping mdat
1267+
for mdat in mdats.iter_mut() {
1268+
if mdat.matches_extent(&extent.extent_range) {
1269+
primary_item.append(&mut mdat.data)?;
1270+
break;
1271+
} else if mdat.contains_extent(&extent.extent_range) {
1272+
mdat.read_extent(&extent.extent_range, &mut primary_item)?;
1273+
break;
13311274
}
13321275
}
13331276
}
13341277

1335-
context.primary_item = primary_item_extents_data.concat()?;
1336-
1337-
if primary_item_extents_data.iter().any(TryVec::is_empty) {
1338-
Err(Error::InvalidData("Primary item data incomplete"))
1339-
} else {
1340-
Ok(())
1341-
}
1278+
context.primary_item = primary_item;
1279+
Ok(())
13421280
}
13431281

13441282
/// Parse a metadata box in the context of an AVIF
13451283
/// Currently requires the primary item to be an av01 item type and generates
13461284
/// an error otherwise.
13471285
/// See ISO 14496-12:2015 § 8.11.1
1348-
fn read_avif_meta<T: Read + Offset>(src: &mut BMFFBox<T>) -> Result<ItemLocationBoxItem> {
1286+
fn read_avif_meta<T: Read + Offset>(src: &mut BMFFBox<T>) -> Result<AvifMeta> {
13491287
let version = read_fullbox_version_no_flags(src)?;
13501288

13511289
if version != 0 {
@@ -1397,11 +1335,9 @@ fn read_avif_meta<T: Read + Offset>(src: &mut BMFFBox<T>) -> Result<ItemLocation
13971335
"Required pitm box not present in meta box",
13981336
))?;
13991337

1400-
if let Some(item_info) = item_infos
1401-
.iter()
1402-
.flatten()
1403-
.find(|x| x.item_id == primary_item_id)
1404-
{
1338+
let item_infos = item_infos.ok_or(Error::InvalidData("iinf missing"))?;
1339+
1340+
if let Some(item_info) = item_infos.iter().find(|x| x.item_id == primary_item_id) {
14051341
if &item_info.item_type.to_be_bytes() != b"av01" {
14061342
warn!("primary_item_id type: {}", U32BE(item_info.item_type));
14071343
return Err(Error::InvalidData("primary_item_id type is not av01"));
@@ -1412,10 +1348,17 @@ fn read_avif_meta<T: Read + Offset>(src: &mut BMFFBox<T>) -> Result<ItemLocation
14121348
));
14131349
}
14141350

1415-
if let Some(loc) = iloc_items
1416-
.into_iter()
1417-
.flatten()
1418-
.find(|loc| loc.item_id == primary_item_id)
1351+
Ok(AvifMeta {
1352+
primary_item_id,
1353+
iloc_items: iloc_items.ok_or(Error::InvalidData("iloc missing"))?,
1354+
})
1355+
}
1356+
1357+
fn get_primary_item_loc(meta: &AvifMeta) -> Result<&ItemLocationBoxItem> {
1358+
if let Some(loc) = meta
1359+
.iloc_items
1360+
.iter()
1361+
.find(|loc| loc.item_id == meta.primary_item_id)
14191362
{
14201363
Ok(loc)
14211364
} else {

0 commit comments

Comments
 (0)