Skip to content

Commit 4575afd

Browse files
committed
Read and expose Exif and XMP metadata in the AvifContext
1 parent ca7a660 commit 4575afd

1 file changed

Lines changed: 120 additions & 22 deletions

File tree

mp4parse/src/lib.rs

Lines changed: 120 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ pub enum Status {
227227
IlocOffsetOverflow,
228228
ImageItemType,
229229
InfeFlagsNonzero,
230+
InfeStringNoNul,
231+
InfeStringNotUtf8,
230232
InvalidUtf8,
231233
IpcoIndexOverflow,
232234
IpmaBadIndex,
@@ -611,6 +613,14 @@ impl From<Status> for &str {
611613
"'infe' flags field shall be 0 \
612614
per ISOBMFF (ISO 14496-12:2020) § 8.11.6.2"
613615
}
616+
Status::InfeStringNoNul => {
617+
"'infe' strings shall be null-terminated \
618+
per ISOBMFF (ISO 14496-12:2020) § 8.11.6.3"
619+
}
620+
Status::InfeStringNotUtf8 => {
621+
"'infe' strings field shall be valid utf8 \
622+
per ISOBMFF (ISO 14496-12:2020) § 8.11.6.3"
623+
}
614624
Status::InvalidUtf8 => {
615625
"invalid utf8"
616626
}
@@ -1594,6 +1604,10 @@ pub struct AvifContext {
15941604
pub sequence: Option<MediaContext>,
15951605
/// A collection of unsupported features encountered during the parse
15961606
pub unsupported_features: UnsupportedFeatures,
1607+
/// AVIF box containing the Exif metadata, if any
1608+
exif_metadata: Option<AvifItem>,
1609+
/// AVIF box containing the XMP metadata, if any
1610+
xmp_metadata: Option<AvifItem>,
15971611
}
15981612

15991613
impl AvifContext {
@@ -1656,6 +1670,18 @@ impl AvifContext {
16561670
}
16571671

16581672

1673+
pub fn exif_metadata(&self) -> Option<&[u8]> {
1674+
self.exif_metadata
1675+
.as_ref()
1676+
.map(|item| self.item_as_slice(item))
1677+
}
1678+
1679+
pub fn xmp_metadata(&self) -> Option<&[u8]> {
1680+
self.xmp_metadata
1681+
.as_ref()
1682+
.map(|item| self.item_as_slice(item))
1683+
}
1684+
16591685
pub fn spatial_extents(&self) -> Result<&ImageSpatialExtentsProperty> {
16601686
if let Some(primary_item) = &self.primary_item {
16611687
match self
@@ -1970,7 +1996,7 @@ impl DataBox {
19701996
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
19711997
struct PropertyIndex(u16);
19721998
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
1973-
struct ItemId(u32);
1999+
pub struct ItemId(u32);
19742000

19752001
impl ItemId {
19762002
fn read(src: &mut impl ReadBytesExt, version: u8) -> Result<ItemId> {
@@ -1986,9 +2012,13 @@ impl ItemId {
19862012
/// See ISOBMFF (ISO 14496-12:2020) § 8.11.6
19872013
/// Only versions {2, 3} are supported
19882014
#[derive(Debug)]
1989-
struct ItemInfoEntry {
1990-
item_id: ItemId,
1991-
item_type: u32,
2015+
pub struct ItemInfoEntry {
2016+
pub item_id: ItemId,
2017+
pub item_type: u32,
2018+
pub item_name: TryVec<u8>,
2019+
pub content_type: Option<TryVec<u8>>,
2020+
pub content_encoding: Option<TryVec<u8>>,
2021+
pub uri_type: Option<TryVec<u8>>,
19922022
}
19932023

19942024
/// See ISOBMFF (ISO 14496-12:2020) § 8.11.12
@@ -2577,24 +2607,17 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif
25772607
debug!("alpha_item_id: {alpha_item_id:?}");
25782608
let mut primary_item = None;
25792609
let mut alpha_item = None;
2610+
let mut items: TryHashMap<ItemId, AvifItem> = Default::default();
25802611

25812612
// store data or record location of relevant items
25822613
for (item_id, loc) in iloc_items {
2583-
let item = if Some(item_id) == primary_item_id {
2584-
&mut primary_item
2585-
} else if Some(item_id) == alpha_item_id {
2586-
&mut alpha_item
2587-
} else {
2588-
continue;
2589-
};
2590-
2591-
assert!(item.is_none());
2614+
let mut item: Option<AvifItem> = None;
25922615

25932616
// If our item is spread over multiple extents, we'll need to copy it
25942617
// into a contiguous buffer. Otherwise, we can just store the extent
25952618
// and return a pointer into the mdat/idat later to avoid the copy.
25962619
if loc.extents.len() > 1 {
2597-
*item = Some(AvifItem::with_inline_data(item_id))
2620+
item = Some(AvifItem::with_inline_data(item_id))
25982621
}
25992622

26002623
trace!(
@@ -2607,10 +2630,10 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif
26072630
// true if the extent is successfully added to the AvifItem
26082631
let mut find_and_add_to_item = |extent: &Extent, dat: &DataBox| -> Result<bool> {
26092632
if let Some(extent_slice) = dat.get(extent) {
2610-
match item {
2633+
match &mut item {
26112634
None => {
26122635
trace!("Using IsobmffItem::Location");
2613-
*item = Some(AvifItem {
2636+
item = Some(AvifItem {
26142637
id: item_id,
26152638
image_data: dat.location(extent),
26162639
});
@@ -2670,6 +2693,14 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif
26702693
}
26712694

26722695
assert!(item.is_some());
2696+
2697+
if Some(item_id) == primary_item_id {
2698+
primary_item = item;
2699+
} else if Some(item_id) == alpha_item_id {
2700+
alpha_item = item;
2701+
} else {
2702+
items.insert(item_id, item.unwrap())?;
2703+
};
26732704
}
26742705

26752706
if (primary_item_id.is_some() && primary_item.is_none())
@@ -2773,6 +2804,21 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif
27732804
check_image_item(&mut primary_item)?;
27742805
check_image_item(&mut alpha_item)?;
27752806

2807+
let mut exif_metadata = None;
2808+
let mut xmp_metadata = None;
2809+
2810+
item_infos.into_iter().for_each(|item| {
2811+
if &item.item_type.to_be_bytes() == b"Exif" {
2812+
exif_metadata = items.remove(&item.item_id);
2813+
} else if &item.item_type.to_be_bytes() == b"mime"
2814+
&& item
2815+
.content_type
2816+
.is_some_and(|v| v.as_slice() == b"application/rdf+xml")
2817+
{
2818+
xmp_metadata = items.remove(&item.item_id);
2819+
}
2820+
});
2821+
27762822
Ok(AvifContext {
27772823
strictness,
27782824
media_storage,
@@ -2783,6 +2829,8 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif
27832829
item_properties,
27842830
major_brand,
27852831
sequence: image_sequence,
2832+
exif_metadata,
2833+
xmp_metadata,
27862834
unsupported_features,
27872835
})
27882836
}
@@ -2957,6 +3005,39 @@ impl std::fmt::Display for U32BE {
29573005
}
29583006
}
29593007

3008+
fn read_infe_string<T: Read>(
3009+
src: &mut BMFFBox<T>,
3010+
strictness: ParseStrictness,
3011+
) -> Result<TryVec<u8>> {
3012+
let mut s = TryVec::new();
3013+
loop {
3014+
match src.read_u8() {
3015+
Ok(v) => {
3016+
s.push(v)?;
3017+
if v == 0 {
3018+
break;
3019+
}
3020+
},
3021+
Err(_) => break,
3022+
}
3023+
}
3024+
match std::str::from_utf8(&s) {
3025+
Ok(s) => {
3026+
if !s.bytes().any(|b| b == b'\0') {
3027+
fail_with_status_if(
3028+
strictness != ParseStrictness::Permissive,
3029+
Status::InfeStringNoNul,
3030+
)?;
3031+
}
3032+
}
3033+
Err(_) => fail_with_status_if(
3034+
strictness != ParseStrictness::Permissive,
3035+
Status::InfeStringNotUtf8,
3036+
)?,
3037+
}
3038+
Ok(s)
3039+
}
3040+
29603041
/// Parse an Item Info Entry
29613042
/// See ISOBMFF (ISO 14496-12:2020) § 8.11.6.2
29623043
fn read_infe<T: Read>(
@@ -2988,15 +3069,32 @@ fn read_infe<T: Read>(
29883069
let item_type = be_u32(src)?;
29893070
debug!("infe {:?} item_type: {}", item_id, U32BE(item_type));
29903071

2991-
// There are some additional fields here, but they're not of interest to us
2992-
skip_box_remain(src)?;
2993-
29943072
if item_protection_index != 0 {
29953073
unsupported_features.insert(Feature::Ipro);
2996-
Ok(None)
2997-
} else {
2998-
Ok(Some(ItemInfoEntry { item_id, item_type }))
3074+
skip_box_remain(src)?;
3075+
return Ok(None);
29993076
}
3077+
3078+
let item_name = read_infe_string(src, strictness)?;
3079+
3080+
let (content_type, content_encoding, uri_type) = if &item_type.to_be_bytes() == b"mime" {
3081+
(
3082+
Some(read_infe_string(src, strictness)?),
3083+
Some(read_infe_string(src, strictness)?),
3084+
None
3085+
)
3086+
} else {
3087+
(None, None, Some(read_infe_string(src, strictness)?))
3088+
};
3089+
3090+
Ok(Some(ItemInfoEntry {
3091+
item_id,
3092+
item_type,
3093+
item_name,
3094+
content_type,
3095+
content_encoding,
3096+
uri_type
3097+
}))
30003098
}
30013099

30023100
/// Parse an Item Reference Box

0 commit comments

Comments
 (0)