Skip to content

Commit 191fd55

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

1 file changed

Lines changed: 94 additions & 22 deletions

File tree

mp4parse/src/lib.rs

Lines changed: 94 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,6 +1594,10 @@ pub struct AvifContext {
15941594
pub sequence: Option<MediaContext>,
15951595
/// A collection of unsupported features encountered during the parse
15961596
pub unsupported_features: UnsupportedFeatures,
1597+
/// AVIF box containing the Exif metadata, if any
1598+
exif_metadata: Option<AvifItem>,
1599+
/// AVIF box containing the XMP metadata, if any
1600+
xmp_metadata: Option<AvifItem>,
15971601
}
15981602

15991603
impl AvifContext {
@@ -1656,6 +1660,18 @@ impl AvifContext {
16561660
}
16571661

16581662

1663+
pub fn exif_metadata(&self) -> Option<&[u8]> {
1664+
self.exif_metadata
1665+
.as_ref()
1666+
.map(|item| self.item_as_slice(item))
1667+
}
1668+
1669+
pub fn xmp_metadata(&self) -> Option<&[u8]> {
1670+
self.xmp_metadata
1671+
.as_ref()
1672+
.map(|item| self.item_as_slice(item))
1673+
}
1674+
16591675
pub fn spatial_extents(&self) -> Result<&ImageSpatialExtentsProperty> {
16601676
if let Some(primary_item) = &self.primary_item {
16611677
match self
@@ -1970,7 +1986,7 @@ impl DataBox {
19701986
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
19711987
struct PropertyIndex(u16);
19721988
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
1973-
struct ItemId(u32);
1989+
pub struct ItemId(u32);
19741990

19751991
impl ItemId {
19761992
fn read(src: &mut impl ReadBytesExt, version: u8) -> Result<ItemId> {
@@ -1986,9 +2002,12 @@ impl ItemId {
19862002
/// See ISOBMFF (ISO 14496-12:2020) § 8.11.6
19872003
/// Only versions {2, 3} are supported
19882004
#[derive(Debug)]
1989-
struct ItemInfoEntry {
1990-
item_id: ItemId,
1991-
item_type: u32,
2005+
pub struct ItemInfoEntry {
2006+
pub item_id: ItemId,
2007+
pub item_type: u32,
2008+
pub item_name: TryVec<u8>,
2009+
pub content_type: Option<TryVec<u8>>,
2010+
pub content_encoding: Option<TryVec<u8>>
19922011
}
19932012

19942013
/// See ISOBMFF (ISO 14496-12:2020) § 8.11.12
@@ -2276,6 +2295,26 @@ impl<T> Drop for BMFFBox<'_, T> {
22762295
}
22772296
}
22782297

2298+
impl<T: ReadBytesExt> BMFFBox<'_, T> {
2299+
fn read_string(&mut self, strictness: ParseStrictness) -> Result<TryVec<u8>> {
2300+
let mut s = TryVec::new();
2301+
loop {
2302+
let v = self.read_u8()?;
2303+
s.push(v)?;
2304+
if v == 0 {
2305+
break;
2306+
}
2307+
}
2308+
if let Err(_) = std::str::from_utf8(&s) {
2309+
fail_with_status_if(
2310+
strictness != ParseStrictness::Permissive,
2311+
Status::InvalidUtf8,
2312+
)?;
2313+
}
2314+
Ok(s)
2315+
}
2316+
}
2317+
22792318
/// Read and parse a box header.
22802319
///
22812320
/// Call this first to determine the type of a particular mp4 box
@@ -2577,24 +2616,17 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif
25772616
debug!("alpha_item_id: {alpha_item_id:?}");
25782617
let mut primary_item = None;
25792618
let mut alpha_item = None;
2619+
let mut items: TryHashMap<ItemId, AvifItem> = Default::default();
25802620

25812621
// store data or record location of relevant items
25822622
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());
2623+
let mut item: Option<AvifItem> = None;
25922624

25932625
// If our item is spread over multiple extents, we'll need to copy it
25942626
// into a contiguous buffer. Otherwise, we can just store the extent
25952627
// and return a pointer into the mdat/idat later to avoid the copy.
25962628
if loc.extents.len() > 1 {
2597-
*item = Some(AvifItem::with_inline_data(item_id))
2629+
item = Some(AvifItem::with_inline_data(item_id))
25982630
}
25992631

26002632
trace!(
@@ -2607,10 +2639,10 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif
26072639
// true if the extent is successfully added to the AvifItem
26082640
let mut find_and_add_to_item = |extent: &Extent, dat: &DataBox| -> Result<bool> {
26092641
if let Some(extent_slice) = dat.get(extent) {
2610-
match item {
2642+
match &mut item {
26112643
None => {
26122644
trace!("Using IsobmffItem::Location");
2613-
*item = Some(AvifItem {
2645+
item = Some(AvifItem {
26142646
id: item_id,
26152647
image_data: dat.location(extent),
26162648
});
@@ -2670,6 +2702,14 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif
26702702
}
26712703

26722704
assert!(item.is_some());
2705+
2706+
if Some(item_id) == primary_item_id {
2707+
primary_item = item;
2708+
} else if Some(item_id) == alpha_item_id {
2709+
alpha_item = item;
2710+
} else {
2711+
items.insert(item_id, item.unwrap())?;
2712+
};
26732713
}
26742714

26752715
if (primary_item_id.is_some() && primary_item.is_none())
@@ -2773,6 +2813,21 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif
27732813
check_image_item(&mut primary_item)?;
27742814
check_image_item(&mut alpha_item)?;
27752815

2816+
let mut exif_metadata = None;
2817+
let mut xmp_metadata = None;
2818+
2819+
item_infos.into_iter().for_each(|item| {
2820+
if &item.item_type.to_be_bytes() == b"Exif" {
2821+
exif_metadata = items.remove(&item.item_id);
2822+
} else if &item.item_type.to_be_bytes() == b"mime"
2823+
&& item
2824+
.content_type
2825+
.is_some_and(|v| v.as_slice() == b"application/rdf+xml")
2826+
{
2827+
xmp_metadata = items.remove(&item.item_id);
2828+
}
2829+
});
2830+
27762831
Ok(AvifContext {
27772832
strictness,
27782833
media_storage,
@@ -2783,6 +2838,8 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif
27832838
item_properties,
27842839
major_brand,
27852840
sequence: image_sequence,
2841+
exif_metadata,
2842+
xmp_metadata,
27862843
unsupported_features,
27872844
})
27882845
}
@@ -2988,15 +3045,30 @@ fn read_infe<T: Read>(
29883045
let item_type = be_u32(src)?;
29893046
debug!("infe {:?} item_type: {}", item_id, U32BE(item_type));
29903047

2991-
// There are some additional fields here, but they're not of interest to us
2992-
skip_box_remain(src)?;
2993-
29943048
if item_protection_index != 0 {
29953049
unsupported_features.insert(Feature::Ipro);
2996-
Ok(None)
2997-
} else {
2998-
Ok(Some(ItemInfoEntry { item_id, item_type }))
3050+
skip_box_remain(src)?;
3051+
return Ok(None);
29993052
}
3053+
3054+
let item_name = src.read_string(strictness)?;
3055+
3056+
let (content_type, content_encoding) = if item_name.as_slice() == b"mime" {
3057+
(
3058+
Some(src.read_string(strictness)?),
3059+
Some(src.read_string(strictness)?),
3060+
)
3061+
} else {
3062+
(None, None)
3063+
};
3064+
3065+
Ok(Some(ItemInfoEntry {
3066+
item_id,
3067+
item_type,
3068+
item_name,
3069+
content_type,
3070+
content_encoding
3071+
}))
30003072
}
30013073

30023074
/// Parse an Item Reference Box

0 commit comments

Comments
 (0)