in mp4parse/src/lib.rs [2969:3219]
fn read_iprp<T: Read>(
src: &mut BMFFBox<T>,
brand: FourCC,
strictness: ParseStrictness,
unsupported_features: &mut UnsupportedFeatures,
) -> Result<ItemPropertiesBox> {
let mut iter = src.box_iter();
let properties = match iter.next_box()? {
Some(mut b) if b.head.name == BoxType::ItemPropertyContainerBox => {
read_ipco(&mut b, strictness)
}
Some(_) => Status::IprpBadChild.into(),
None => Err(Error::UnexpectedEOF),
}?;
let mut ipma_version_and_flag_values_seen = TryVec::with_capacity(1)?;
let mut association_entries = TryVec::<ItemPropertyAssociationEntry>::new();
let mut forbidden_items = TryVec::new();
while let Some(mut b) = iter.next_box()? {
if b.head.name != BoxType::ItemPropertyAssociationBox {
return Status::IprpBadChild.into();
}
let (version, flags) = read_fullbox_extra(&mut b)?;
if ipma_version_and_flag_values_seen.contains(&(version, flags)) {
fail_with_status_if(
strictness != ParseStrictness::Permissive,
Status::IpmaBadQuantity,
)?;
}
if flags != 0 && properties.len() <= 127 {
fail_with_status_if(
strictness == ParseStrictness::Strict,
Status::IpmaFlagsNonzero,
)?;
}
ipma_version_and_flag_values_seen.push((version, flags))?;
for association_entry in read_ipma(&mut b, strictness, version, flags)? {
if forbidden_items.contains(&association_entry.item_id) {
warn!(
"Skipping {:?} since the item referenced shall not be processed",
association_entry
);
}
if let Some(previous_entry) = association_entries
.iter()
.find(|e| association_entry.item_id == e.item_id)
{
error!(
"Duplicate ipma entries for item_id\n1: {:?}\n2: {:?}",
previous_entry, association_entry
);
// It's technically possible to make sense of this situation by merging ipma
// boxes, but this is a "shall" requirement, so we'd only do it in
// ParseStrictness::Permissive mode, and this hasn't shown up in the wild
return Status::IpmaDuplicateItemId.into();
}
const TRANSFORM_ORDER: &[BoxType] = &[
BoxType::ImageSpatialExtentsProperty,
BoxType::CleanApertureBox,
BoxType::ImageRotation,
BoxType::ImageMirror,
];
let mut prev_transform_index = None;
// Realistically, there should only ever be 1 nclx and 1 icc
let mut colour_type_indexes: TryHashMap<FourCC, PropertyIndex> =
TryHashMap::with_capacity(2)?;
for a in &association_entry.associations {
if a.property_index == PropertyIndex(0) {
if a.essential {
fail_with_status_if(
strictness != ParseStrictness::Permissive,
Status::IpmaIndexZeroNoEssential,
)?;
}
continue;
}
if let Some(property) = properties.get(&a.property_index) {
assert!(brand == MIF1_BRAND);
let feature = Feature::try_from(property);
let property_supported = match feature {
Ok(feature) => {
if feature.supported() {
true
} else {
unsupported_features.insert(feature);
false
}
}
Err(_) => false,
};
if !property_supported {
if a.essential && strictness != ParseStrictness::Permissive {
error!("Unsupported essential property {:?}", property);
forbidden_items.push(association_entry.item_id)?;
} else {
debug!(
"Ignoring unknown {} property {:?}",
if a.essential {
"essential"
} else {
"non-essential"
},
property
);
}
}
// Check additional requirements on specific properties
match property {
ItemProperty::AV1Config(_)
| ItemProperty::CleanAperture
| ItemProperty::Mirroring(_)
| ItemProperty::Rotation(_) => {
if !a.essential {
warn!("{:?} is missing required 'essential' bit", property);
// This is a "shall", but it is likely to change, so only
// fail if using strict parsing.
// See https://github.com/mozilla/mp4parse-rust/issues/284
fail_with_status_if(
strictness == ParseStrictness::Strict,
Status::TxformNoEssential,
)?;
}
}
// NOTE: this is contrary to the published specification; see doc comment
// at the beginning of this function for more details
ItemProperty::Colour(colr) => {
let colour_type = colr.colour_type();
if let Some(prev_colr_index) = colour_type_indexes.get(&colour_type) {
warn!(
"Multiple '{}' type colr associations with {:?}: {:?} and {:?}",
colour_type,
association_entry.item_id,
a.property_index,
prev_colr_index
);
fail_with_status_if(
strictness != ParseStrictness::Permissive,
Status::ColrBadQuantity,
)?;
} else {
colour_type_indexes.insert(colour_type, a.property_index)?;
}
}
// The following properties are unsupported, but we still enforce that
// they've been correctly marked as essential or not.
ItemProperty::LayeredImageIndexing => {
assert!(feature.is_ok() && unsupported_features.contains(feature?));
if a.essential {
fail_with_status_if(
strictness != ParseStrictness::Permissive,
Status::A1lxEssential,
)?;
}
}
ItemProperty::LayerSelection => {
assert!(feature.is_ok() && unsupported_features.contains(feature?));
if a.essential {
assert!(
forbidden_items.contains(&association_entry.item_id)
|| strictness == ParseStrictness::Permissive
);
} else {
fail_with_status_if(
strictness != ParseStrictness::Permissive,
Status::LselNoEssential,
)?;
}
}
ItemProperty::OperatingPointSelector => {
assert!(feature.is_ok() && unsupported_features.contains(feature?));
if a.essential {
assert!(
forbidden_items.contains(&association_entry.item_id)
|| strictness == ParseStrictness::Permissive
);
} else {
fail_with_status_if(
strictness != ParseStrictness::Permissive,
Status::A1opNoEssential,
)?;
}
}
other_property => {
trace!("No additional checks for {:?}", other_property);
}
}
if let Some(transform_index) = TRANSFORM_ORDER
.iter()
.position(|t| *t == BoxType::from(property))
{
if let Some(prev) = prev_transform_index {
if prev >= transform_index {
error!(
"Invalid property order: {:?} after {:?}",
TRANSFORM_ORDER[transform_index], TRANSFORM_ORDER[prev]
);
fail_with_status_if(
strictness != ParseStrictness::Permissive,
if TRANSFORM_ORDER[transform_index]
== BoxType::ImageSpatialExtentsProperty
{
Status::TxformBeforeIspe
} else {
Status::TxformOrder
},
)?;
}
}
prev_transform_index = Some(transform_index);
}
} else {
error!(
"Missing property at {:?} for {:?}",
a.property_index, association_entry.item_id
);
fail_with_status_if(
strictness != ParseStrictness::Permissive,
Status::IpmaBadIndex,
)?;
}
}
association_entries.push(association_entry)?
}
check_parser_state!(b.content);
}
let iprp = ItemPropertiesBox {
properties,
association_entries,
forbidden_items,
};
trace!("read_iprp -> {:#?}", iprp);
Ok(iprp)
}