Skip to content

Commit 586da4e

Browse files
committed
read metadata from udta
This introduces the 'Metadata' trait to enable access to common video metadata such title, year, cover art and more. Reading 'title' and 'year' metadata is implemented as a proof of concept.
1 parent 00385ba commit 586da4e

File tree

11 files changed

+345
-5
lines changed

11 files changed

+345
-5
lines changed

src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ pub enum Error {
2424
EntryInStblNotFound(u32, BoxType, u32),
2525
#[error("traf[{0}].trun.{1}.entry[{2}] not found")]
2626
EntryInTrunNotFound(u32, BoxType, u32),
27+
#[error("{0} version {1} is not supported")]
28+
UnsupportedBoxVersion(BoxType, u8),
2729
}

src/mp4box/data.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use std::{
2+
convert::TryFrom,
3+
io::{Read, Seek},
4+
};
5+
6+
use serde::Serialize;
7+
8+
use crate::mp4box::*;
9+
10+
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
11+
pub struct DataBox {
12+
pub data: Vec<u8>,
13+
pub data_type: DataType,
14+
}
15+
16+
impl<R: Read + Seek> ReadBox<&mut R> for DataBox {
17+
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
18+
let start = box_start(reader)?;
19+
20+
let data_type = DataType::try_from(reader.read_u32::<BigEndian>()?)?;
21+
22+
reader.read_u32::<BigEndian>()?; // reserved = 0
23+
24+
let current = reader.seek(SeekFrom::Current(0))?;
25+
let mut data = vec![0u8; (start + size - current) as usize];
26+
reader.read_exact(&mut data)?;
27+
28+
Ok(DataBox { data, data_type })
29+
}
30+
}

src/mp4box/ilst.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use std::borrow::Cow;
2+
use std::collections::HashMap;
3+
use std::io::{Read, Seek};
4+
5+
use byteorder::ByteOrder;
6+
use serde::Serialize;
7+
8+
use crate::mp4box::data::DataBox;
9+
use crate::mp4box::*;
10+
11+
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
12+
pub struct IlstBox {
13+
pub items: HashMap<MetadataKey, IlstItemBox>,
14+
}
15+
16+
impl<R: Read + Seek> ReadBox<&mut R> for IlstBox {
17+
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
18+
let start = box_start(reader)?;
19+
20+
let mut items = HashMap::new();
21+
22+
let mut current = reader.seek(SeekFrom::Current(0))?;
23+
let end = start + size;
24+
while current < end {
25+
// Get box header.
26+
let header = BoxHeader::read(reader)?;
27+
let BoxHeader { name, size: s } = header;
28+
29+
match name {
30+
BoxType::NameBox => {
31+
items.insert(MetadataKey::Title, IlstItemBox::read_box(reader, s)?);
32+
}
33+
BoxType::DayBox => {
34+
items.insert(MetadataKey::Year, IlstItemBox::read_box(reader, s)?);
35+
}
36+
_ => {
37+
// XXX warn!()
38+
skip_box(reader, s)?;
39+
}
40+
}
41+
42+
current = reader.seek(SeekFrom::Current(0))?;
43+
}
44+
45+
skip_bytes_to(reader, start + size)?;
46+
47+
Ok(IlstBox { items })
48+
}
49+
}
50+
51+
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
52+
pub struct IlstItemBox {
53+
pub data: DataBox,
54+
}
55+
56+
impl<R: Read + Seek> ReadBox<&mut R> for IlstItemBox {
57+
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
58+
let start = box_start(reader)?;
59+
60+
let mut data = None;
61+
62+
let mut current = reader.seek(SeekFrom::Current(0))?;
63+
let end = start + size;
64+
while current < end {
65+
// Get box header.
66+
let header = BoxHeader::read(reader)?;
67+
let BoxHeader { name, size: s } = header;
68+
69+
match name {
70+
BoxType::DataBox => {
71+
data = Some(DataBox::read_box(reader, s)?);
72+
}
73+
_ => {
74+
// XXX warn!()
75+
skip_box(reader, s)?;
76+
}
77+
}
78+
79+
current = reader.seek(SeekFrom::Current(0))?;
80+
}
81+
82+
if data.is_none() {
83+
return Err(Error::BoxNotFound(BoxType::DataBox));
84+
}
85+
86+
skip_bytes_to(reader, start + size)?;
87+
88+
Ok(IlstItemBox {
89+
data: data.unwrap(),
90+
})
91+
}
92+
}
93+
94+
impl<'a> Metadata<'a> for IlstBox {
95+
fn title(&self) -> Option<Cow<str>> {
96+
self.items.get(&MetadataKey::Title).map(item_to_str)
97+
}
98+
99+
fn year(&self) -> Option<u32> {
100+
self.items.get(&MetadataKey::Year).and_then(item_to_u32)
101+
}
102+
}
103+
104+
fn item_to_str(item: &IlstItemBox) -> Cow<str> {
105+
String::from_utf8_lossy(&item.data.data)
106+
}
107+
108+
fn item_to_u32(item: &IlstItemBox) -> Option<u32> {
109+
match item.data.data_type {
110+
DataType::Binary if item.data.data.len() == 4 => Some(BigEndian::read_u32(&item.data.data)),
111+
DataType::Text => String::from_utf8_lossy(&item.data.data).parse::<u32>().ok(),
112+
_ => None,
113+
}
114+
}

src/mp4box/meta.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
use std::io::{Read, Seek};
2+
3+
use serde::Serialize;
4+
5+
use crate::mp4box::ilst::IlstBox;
6+
use crate::mp4box::*;
7+
8+
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
9+
pub struct MetaBox {
10+
#[serde(skip_serializing_if = "Option::is_none")]
11+
pub ilst: Option<IlstBox>,
12+
}
13+
14+
impl<R: Read + Seek> ReadBox<&mut R> for MetaBox {
15+
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
16+
let (version, _) = read_box_header_ext(reader)?;
17+
if version != 0 {
18+
return Err(Error::UnsupportedBoxVersion(
19+
BoxType::UdtaBox,
20+
version as u8,
21+
));
22+
}
23+
24+
let start = box_start(reader)?;
25+
26+
let mut ilst = None;
27+
28+
let mut current = reader.seek(SeekFrom::Current(0))?;
29+
let end = start + size;
30+
while current < end {
31+
// Get box header.
32+
let header = BoxHeader::read(reader)?;
33+
let BoxHeader { name, size: s } = header;
34+
35+
match name {
36+
BoxType::IlstBox => {
37+
ilst = Some(IlstBox::read_box(reader, s)?);
38+
}
39+
_ => {
40+
// XXX warn!()
41+
skip_box(reader, s)?;
42+
}
43+
}
44+
45+
current = reader.seek(SeekFrom::Current(0))?;
46+
}
47+
48+
skip_bytes_to(reader, start + size)?;
49+
50+
Ok(MetaBox { ilst })
51+
}
52+
}

src/mp4box/mod.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
//! ftyp
1414
//! moov
1515
//! mvhd
16+
//! udta
17+
//! meta
18+
//! ilst
19+
//! data
1620
//! trak
1721
//! tkhd
1822
//! mdia
@@ -60,16 +64,19 @@ use crate::*;
6064
pub(crate) mod avc1;
6165
pub(crate) mod co64;
6266
pub(crate) mod ctts;
67+
pub(crate) mod data;
6368
pub(crate) mod dinf;
6469
pub(crate) mod edts;
6570
pub(crate) mod elst;
6671
pub(crate) mod emsg;
6772
pub(crate) mod ftyp;
6873
pub(crate) mod hdlr;
6974
pub(crate) mod hev1;
75+
pub(crate) mod ilst;
7076
pub(crate) mod mdhd;
7177
pub(crate) mod mdia;
7278
pub(crate) mod mehd;
79+
pub(crate) mod meta;
7380
pub(crate) mod mfhd;
7481
pub(crate) mod minf;
7582
pub(crate) mod moof;
@@ -92,6 +99,7 @@ pub(crate) mod trak;
9299
pub(crate) mod trex;
93100
pub(crate) mod trun;
94101
pub(crate) mod tx3g;
102+
pub(crate) mod udta;
95103
pub(crate) mod vmhd;
96104
pub(crate) mod vp09;
97105
pub(crate) mod vpcc;
@@ -167,6 +175,7 @@ boxtype! {
167175
TrafBox => 0x74726166,
168176
TrunBox => 0x7472756E,
169177
UdtaBox => 0x75647461,
178+
MetaBox => 0x6d657461,
170179
DinfBox => 0x64696e66,
171180
DrefBox => 0x64726566,
172181
UrlBox => 0x75726C20,
@@ -179,7 +188,12 @@ boxtype! {
179188
EsdsBox => 0x65736473,
180189
Tx3gBox => 0x74783367,
181190
VpccBox => 0x76706343,
182-
Vp09Box => 0x76703039
191+
Vp09Box => 0x76703039,
192+
DataBox => 0x64617461,
193+
IlstBox => 0x696c7374,
194+
NameBox => 0xa96e616d,
195+
DayBox => 0xa9646179
196+
183197
}
184198

185199
pub trait Mp4Box: Sized {

src/mp4box/moov.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use serde::Serialize;
22
use std::io::{Read, Seek, SeekFrom, Write};
33

44
use crate::mp4box::*;
5-
use crate::mp4box::{mvex::MvexBox, mvhd::MvhdBox, trak::TrakBox};
5+
use crate::mp4box::{mvex::MvexBox, mvhd::MvhdBox, trak::TrakBox, udta::UdtaBox};
66

77
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
88
pub struct MoovBox {
@@ -13,6 +13,9 @@ pub struct MoovBox {
1313

1414
#[serde(rename = "trak")]
1515
pub traks: Vec<TrakBox>,
16+
17+
#[serde(skip_serializing_if = "Option::is_none")]
18+
pub udta: Option<UdtaBox>,
1619
}
1720

1821
impl MoovBox {
@@ -53,6 +56,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
5356
let start = box_start(reader)?;
5457

5558
let mut mvhd = None;
59+
let mut udta = None;
5660
let mut mvex = None;
5761
let mut traks = Vec::new();
5862

@@ -75,8 +79,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
7579
traks.push(trak);
7680
}
7781
BoxType::UdtaBox => {
78-
// XXX warn!()
79-
skip_box(reader, s)?;
82+
udta = Some(UdtaBox::read_box(reader, s)?);
8083
}
8184
_ => {
8285
// XXX warn!()
@@ -95,6 +98,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
9598

9699
Ok(MoovBox {
97100
mvhd: mvhd.unwrap(),
101+
udta,
98102
mvex,
99103
traks,
100104
})

src/mp4box/udta.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use std::io::{Read, Seek};
2+
3+
use serde::Serialize;
4+
5+
use crate::mp4box::meta::MetaBox;
6+
use crate::mp4box::*;
7+
8+
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
9+
pub struct UdtaBox {
10+
#[serde(skip_serializing_if = "Option::is_none")]
11+
pub meta: Option<MetaBox>,
12+
}
13+
14+
impl<R: Read + Seek> ReadBox<&mut R> for UdtaBox {
15+
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
16+
let start = box_start(reader)?;
17+
18+
let mut meta = None;
19+
20+
let mut current = reader.seek(SeekFrom::Current(0))?;
21+
let end = start + size;
22+
while current < end {
23+
// Get box header.
24+
let header = BoxHeader::read(reader)?;
25+
let BoxHeader { name, size: s } = header;
26+
27+
match name {
28+
BoxType::MetaBox => {
29+
meta = Some(MetaBox::read_box(reader, s)?);
30+
}
31+
_ => {
32+
// XXX warn!()
33+
skip_box(reader, s)?;
34+
}
35+
}
36+
37+
current = reader.seek(SeekFrom::Current(0))?;
38+
}
39+
40+
skip_bytes_to(reader, start + size)?;
41+
42+
Ok(UdtaBox { meta })
43+
}
44+
}

src/reader.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,12 @@ impl<R: Read + Seek> Mp4Reader<R> {
167167
}
168168
}
169169
}
170+
171+
impl<R> Mp4Reader<R> {
172+
pub fn metadata(&self) -> impl Metadata<'_> {
173+
self.moov
174+
.udta
175+
.as_ref()
176+
.and_then(|udta| udta.meta.as_ref().and_then(|meta| meta.ilst.as_ref()))
177+
}
178+
}

0 commit comments

Comments
 (0)