Skip to content

Commit fde99ab

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

1 file changed

Lines changed: 101 additions & 22 deletions

File tree

mp4parse/src/lib.rs

Lines changed: 101 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 {
@@ -1655,6 +1659,25 @@ impl AvifContext {
16551659
}
16561660
}
16571661

1662+
pub fn exif_metadata_is_present(&self) -> bool {
1663+
self.exif_metadata.is_some()
1664+
}
1665+
1666+
pub fn exif_metadata(&self) -> Option<&[u8]> {
1667+
self.exif_metadata
1668+
.as_ref()
1669+
.map(|item| self.item_as_slice(item))
1670+
}
1671+
1672+
pub fn xmp_metadata_is_present(&self) -> bool {
1673+
self.xmp_metadata.is_some()
1674+
}
1675+
1676+
pub fn xmp_metadata(&self) -> Option<&[u8]> {
1677+
self.xmp_metadata
1678+
.as_ref()
1679+
.map(|item| self.item_as_slice(item))
1680+
}
16581681

16591682
pub fn spatial_extents(&self) -> Result<&ImageSpatialExtentsProperty> {
16601683
if let Some(primary_item) = &self.primary_item {
@@ -1970,7 +1993,7 @@ impl DataBox {
19701993
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
19711994
struct PropertyIndex(u16);
19721995
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)]
1973-
struct ItemId(u32);
1996+
pub struct ItemId(u32);
19741997

19751998
impl ItemId {
19761999
fn read(src: &mut impl ReadBytesExt, version: u8) -> Result<ItemId> {
@@ -1986,9 +2009,12 @@ impl ItemId {
19862009
/// See ISOBMFF (ISO 14496-12:2020) § 8.11.6
19872010
/// Only versions {2, 3} are supported
19882011
#[derive(Debug)]
1989-
struct ItemInfoEntry {
1990-
item_id: ItemId,
1991-
item_type: u32,
2012+
pub struct ItemInfoEntry {
2013+
pub item_id: ItemId,
2014+
pub item_type: u32,
2015+
pub item_name: TryVec<u8>,
2016+
pub content_type: Option<TryVec<u8>>,
2017+
pub content_encoding: Option<TryVec<u8>>
19922018
}
19932019

19942020
/// See ISOBMFF (ISO 14496-12:2020) § 8.11.12
@@ -2276,6 +2302,26 @@ impl<T> Drop for BMFFBox<'_, T> {
22762302
}
22772303
}
22782304

2305+
impl<T: ReadBytesExt> BMFFBox<'_, T> {
2306+
fn read_string(&mut self, strictness: ParseStrictness) -> Result<TryVec<u8>> {
2307+
let mut s = TryVec::new();
2308+
loop {
2309+
let v = self.read_u8()?;
2310+
s.push(v)?;
2311+
if v == 0 {
2312+
break;
2313+
}
2314+
}
2315+
if let Err(_) = std::str::from_utf8(&s) {
2316+
fail_with_status_if(
2317+
strictness != ParseStrictness::Permissive,
2318+
Status::InvalidUtf8,
2319+
)?;
2320+
}
2321+
Ok(s)
2322+
}
2323+
}
2324+
22792325
/// Read and parse a box header.
22802326
///
22812327
/// Call this first to determine the type of a particular mp4 box
@@ -2577,24 +2623,17 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif
25772623
debug!("alpha_item_id: {alpha_item_id:?}");
25782624
let mut primary_item = None;
25792625
let mut alpha_item = None;
2626+
let mut items: std::collections::HashMap<ItemId, AvifItem> = Default::default();
25802627

25812628
// store data or record location of relevant items
25822629
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());
2630+
let mut item: Option<AvifItem> = None;
25922631

25932632
// If our item is spread over multiple extents, we'll need to copy it
25942633
// into a contiguous buffer. Otherwise, we can just store the extent
25952634
// and return a pointer into the mdat/idat later to avoid the copy.
25962635
if loc.extents.len() > 1 {
2597-
*item = Some(AvifItem::with_inline_data(item_id))
2636+
item = Some(AvifItem::with_inline_data(item_id))
25982637
}
25992638

26002639
trace!(
@@ -2607,10 +2646,10 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif
26072646
// true if the extent is successfully added to the AvifItem
26082647
let mut find_and_add_to_item = |extent: &Extent, dat: &DataBox| -> Result<bool> {
26092648
if let Some(extent_slice) = dat.get(extent) {
2610-
match item {
2649+
match &mut item {
26112650
None => {
26122651
trace!("Using IsobmffItem::Location");
2613-
*item = Some(AvifItem {
2652+
item = Some(AvifItem {
26142653
id: item_id,
26152654
image_data: dat.location(extent),
26162655
});
@@ -2670,6 +2709,14 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif
26702709
}
26712710

26722711
assert!(item.is_some());
2712+
2713+
if Some(item_id) == primary_item_id {
2714+
primary_item = item;
2715+
} else if Some(item_id) == alpha_item_id {
2716+
alpha_item = item;
2717+
} else {
2718+
items.insert(item_id, item.unwrap());
2719+
};
26732720
}
26742721

26752722
if (primary_item_id.is_some() && primary_item.is_none())
@@ -2773,6 +2820,21 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif
27732820
check_image_item(&mut primary_item)?;
27742821
check_image_item(&mut alpha_item)?;
27752822

2823+
let mut exif_metadata = None;
2824+
let mut xmp_metadata = None;
2825+
2826+
item_infos.into_iter().for_each(|item| {
2827+
if &item.item_type.to_be_bytes() == b"Exif" {
2828+
exif_metadata = items.remove(&item.item_id);
2829+
} else if &item.item_type.to_be_bytes() == b"mime"
2830+
&& item
2831+
.content_type
2832+
.is_some_and(|v| v.as_slice() == b"application/rdf+xml")
2833+
{
2834+
xmp_metadata = items.remove(&item.item_id);
2835+
}
2836+
});
2837+
27762838
Ok(AvifContext {
27772839
strictness,
27782840
media_storage,
@@ -2783,6 +2845,8 @@ pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<Avif
27832845
item_properties,
27842846
major_brand,
27852847
sequence: image_sequence,
2848+
exif_metadata,
2849+
xmp_metadata,
27862850
unsupported_features,
27872851
})
27882852
}
@@ -2988,15 +3052,30 @@ fn read_infe<T: Read>(
29883052
let item_type = be_u32(src)?;
29893053
debug!("infe {:?} item_type: {}", item_id, U32BE(item_type));
29903054

2991-
// There are some additional fields here, but they're not of interest to us
2992-
skip_box_remain(src)?;
2993-
29943055
if item_protection_index != 0 {
29953056
unsupported_features.insert(Feature::Ipro);
2996-
Ok(None)
2997-
} else {
2998-
Ok(Some(ItemInfoEntry { item_id, item_type }))
3057+
skip_box_remain(src)?;
3058+
return Ok(None);
29993059
}
3060+
3061+
let item_name = src.read_string(strictness)?;
3062+
3063+
let (content_type, content_encoding) = if item_name.as_slice() == b"mime" {
3064+
(
3065+
Some(src.read_string(strictness)?),
3066+
Some(src.read_string(strictness)?),
3067+
)
3068+
} else {
3069+
(None, None)
3070+
};
3071+
3072+
Ok(Some(ItemInfoEntry {
3073+
item_id,
3074+
item_type,
3075+
item_name,
3076+
content_type,
3077+
content_encoding
3078+
}))
30003079
}
30013080

30023081
/// Parse an Item Reference Box

0 commit comments

Comments
 (0)