mirror of
https://github.com/alfg/mp4-rust.git
synced 2024-05-08 11:32:56 +00:00
Parse unknown metadata and write metadata (#91)
* validate meta box handler type, parse meta with unknown handlers * parse meta in moov and trak position * write meta and udta * fix clippy nit
This commit is contained in:
parent
9c0f6534bd
commit
2a374c57f2
|
@ -13,6 +13,39 @@ pub struct DataBox {
|
|||
pub data_type: DataType,
|
||||
}
|
||||
|
||||
impl DataBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::DataBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
size += 4; // data_type
|
||||
size += 4; // reserved
|
||||
size += self.data.len() as u64;
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for DataBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("type={:?} len={}", self.data_type, self.data.len());
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for DataBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
@ -28,3 +61,58 @@ impl<R: Read + Seek> ReadBox<&mut R> for DataBox {
|
|||
Ok(DataBox { data, data_type })
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for DataBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.data_type.clone() as u32)?;
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved = 0
|
||||
writer.write_all(&self.data)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_data() {
|
||||
let src_box = DataBox {
|
||||
data_type: DataType::Text,
|
||||
data: b"test_data".to_vec(),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::DataBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = DataBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_data_empty() {
|
||||
let src_box = DataBox::default();
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::DataBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = DataBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,39 @@ pub struct IlstBox {
|
|||
pub items: HashMap<MetadataKey, IlstItemBox>,
|
||||
}
|
||||
|
||||
impl IlstBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::IlstBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
for item in self.items.values() {
|
||||
size += item.get_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for IlstBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("item_count={}", self.items.len());
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for IlstBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
@ -54,11 +87,36 @@ impl<R: Read + Seek> ReadBox<&mut R> for IlstBox {
|
|||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for IlstBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
for (key, value) in &self.items {
|
||||
let name = match key {
|
||||
MetadataKey::Title => BoxType::NameBox,
|
||||
MetadataKey::Year => BoxType::DayBox,
|
||||
MetadataKey::Poster => BoxType::CovrBox,
|
||||
MetadataKey::Summary => BoxType::DescBox,
|
||||
};
|
||||
BoxHeader::new(name, value.get_size()).write(writer)?;
|
||||
value.data.write_box(writer)?;
|
||||
}
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct IlstItemBox {
|
||||
pub data: DataBox,
|
||||
}
|
||||
|
||||
impl IlstItemBox {
|
||||
fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + self.data.box_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for IlstItemBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
@ -130,3 +188,56 @@ fn item_to_u32(item: &IlstItemBox) -> Option<u32> {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_ilst() {
|
||||
let src_year = IlstItemBox {
|
||||
data: DataBox {
|
||||
data_type: DataType::Text,
|
||||
data: b"test_year".to_vec(),
|
||||
},
|
||||
};
|
||||
let src_box = IlstBox {
|
||||
items: [
|
||||
(MetadataKey::Title, IlstItemBox::default()),
|
||||
(MetadataKey::Year, src_year),
|
||||
(MetadataKey::Poster, IlstItemBox::default()),
|
||||
(MetadataKey::Summary, IlstItemBox::default()),
|
||||
]
|
||||
.into(),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::IlstBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = IlstBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ilst_empty() {
|
||||
let src_box = IlstBox::default();
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::IlstBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = IlstBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,82 @@ use std::io::{Read, Seek};
|
|||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::mp4box::hdlr::HdlrBox;
|
||||
use crate::mp4box::ilst::IlstBox;
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct MetaBox {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ilst: Option<IlstBox>,
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(tag = "hdlr")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum MetaBox {
|
||||
Mdir {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
ilst: Option<IlstBox>,
|
||||
},
|
||||
|
||||
#[serde(skip)]
|
||||
Unknown {
|
||||
#[serde(skip)]
|
||||
hdlr: HdlrBox,
|
||||
|
||||
#[serde(skip)]
|
||||
data: Vec<u8>,
|
||||
},
|
||||
}
|
||||
|
||||
const MDIR: FourCC = FourCC { value: *b"mdir" };
|
||||
|
||||
impl MetaBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::MetaBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
match self {
|
||||
Self::Mdir { ilst } => {
|
||||
size += HdlrBox::default().box_size();
|
||||
if let Some(ilst) = ilst {
|
||||
size += ilst.box_size();
|
||||
}
|
||||
}
|
||||
Self::Unknown { hdlr, data } => size += hdlr.box_size() + data.len() as u64,
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MetaBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = match self {
|
||||
Self::Mdir { .. } => "hdlr=ilst".to_string(),
|
||||
Self::Unknown { hdlr, data } => {
|
||||
format!("hdlr={} data_len={}", hdlr.handler_type, data.len())
|
||||
}
|
||||
};
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MetaBox {
|
||||
fn default() -> Self {
|
||||
Self::Unknown {
|
||||
hdlr: Default::default(),
|
||||
data: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MetaBox {
|
||||
|
@ -20,30 +89,141 @@ impl<R: Read + Seek> ReadBox<&mut R> for MetaBox {
|
|||
return Err(Error::UnsupportedBoxVersion(BoxType::UdtaBox, version));
|
||||
}
|
||||
|
||||
let hdlr_header = BoxHeader::read(reader)?;
|
||||
if hdlr_header.name != BoxType::HdlrBox {
|
||||
return Err(Error::BoxNotFound(BoxType::HdlrBox));
|
||||
}
|
||||
let hdlr = HdlrBox::read_box(reader, hdlr_header.size)?;
|
||||
|
||||
let mut ilst = None;
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::IlstBox => {
|
||||
ilst = Some(IlstBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
match hdlr.handler_type {
|
||||
MDIR => {
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::IlstBox => {
|
||||
ilst = Some(IlstBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
}
|
||||
|
||||
Ok(MetaBox::Mdir { ilst })
|
||||
}
|
||||
_ => {
|
||||
let mut data = vec![0u8; (end - current) as usize];
|
||||
reader.read_exact(&mut data)?;
|
||||
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
Ok(MetaBox::Unknown { hdlr, data })
|
||||
}
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(MetaBox { ilst })
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for MetaBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, 0, 0)?;
|
||||
|
||||
let hdlr = match self {
|
||||
Self::Mdir { .. } => HdlrBox {
|
||||
handler_type: MDIR,
|
||||
..Default::default()
|
||||
},
|
||||
Self::Unknown { hdlr, .. } => hdlr.clone(),
|
||||
};
|
||||
hdlr.write_box(writer)?;
|
||||
|
||||
match self {
|
||||
Self::Mdir { ilst } => {
|
||||
if let Some(ilst) = ilst {
|
||||
ilst.write_box(writer)?;
|
||||
}
|
||||
}
|
||||
Self::Unknown { data, .. } => writer.write_all(data)?,
|
||||
}
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_meta_mdir_empty() {
|
||||
let src_box = MetaBox::Mdir { ilst: None };
|
||||
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MetaBox);
|
||||
assert_eq!(header.size, src_box.box_size());
|
||||
|
||||
let dst_box = MetaBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(dst_box, src_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_meta_mdir() {
|
||||
let src_box = MetaBox::Mdir {
|
||||
ilst: Some(IlstBox::default()),
|
||||
};
|
||||
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MetaBox);
|
||||
assert_eq!(header.size, src_box.box_size());
|
||||
|
||||
let dst_box = MetaBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(dst_box, src_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_meta_unknown() {
|
||||
let src_hdlr = HdlrBox {
|
||||
handler_type: FourCC::from(*b"test"),
|
||||
..Default::default()
|
||||
};
|
||||
let src_data = b"123";
|
||||
let src_box = MetaBox::Unknown {
|
||||
hdlr: src_hdlr,
|
||||
data: src_data.to_vec(),
|
||||
};
|
||||
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MetaBox);
|
||||
assert_eq!(header.size, src_box.box_size());
|
||||
|
||||
let dst_box = MetaBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(dst_box, src_box);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use crate::meta::MetaBox;
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{mvex::MvexBox, mvhd::MvhdBox, trak::TrakBox, udta::UdtaBox};
|
||||
|
||||
|
@ -8,6 +9,9 @@ use crate::mp4box::{mvex::MvexBox, mvhd::MvhdBox, trak::TrakBox, udta::UdtaBox};
|
|||
pub struct MoovBox {
|
||||
pub mvhd: MvhdBox,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<MetaBox>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mvex: Option<MvexBox>,
|
||||
|
||||
|
@ -28,6 +32,12 @@ impl MoovBox {
|
|||
for trak in self.traks.iter() {
|
||||
size += trak.box_size();
|
||||
}
|
||||
if let Some(meta) = &self.meta {
|
||||
size += meta.box_size();
|
||||
}
|
||||
if let Some(udta) = &self.udta {
|
||||
size += udta.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +66,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
|
|||
let start = box_start(reader)?;
|
||||
|
||||
let mut mvhd = None;
|
||||
let mut meta = None;
|
||||
let mut udta = None;
|
||||
let mut mvex = None;
|
||||
let mut traks = Vec::new();
|
||||
|
@ -71,6 +82,9 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
|
|||
BoxType::MvhdBox => {
|
||||
mvhd = Some(MvhdBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::MetaBox => {
|
||||
meta = Some(MetaBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::MvexBox => {
|
||||
mvex = Some(MvexBox::read_box(reader, s)?);
|
||||
}
|
||||
|
@ -98,6 +112,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
|
|||
|
||||
Ok(MoovBox {
|
||||
mvhd: mvhd.unwrap(),
|
||||
meta,
|
||||
udta,
|
||||
mvex,
|
||||
traks,
|
||||
|
@ -114,6 +129,59 @@ impl<W: Write> WriteBox<&mut W> for MoovBox {
|
|||
for trak in self.traks.iter() {
|
||||
trak.write_box(writer)?;
|
||||
}
|
||||
if let Some(meta) = &self.meta {
|
||||
meta.write_box(writer)?;
|
||||
}
|
||||
if let Some(udta) = &self.udta {
|
||||
udta.write_box(writer)?;
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_moov() {
|
||||
let src_box = MoovBox {
|
||||
mvhd: MvhdBox::default(),
|
||||
mvex: None, // XXX mvex is not written currently
|
||||
traks: vec![],
|
||||
meta: Some(MetaBox::default()),
|
||||
udta: Some(UdtaBox::default()),
|
||||
};
|
||||
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MoovBox);
|
||||
assert_eq!(header.size, src_box.box_size());
|
||||
|
||||
let dst_box = MoovBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(dst_box, src_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_moov_empty() {
|
||||
let src_box = MoovBox::default();
|
||||
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MoovBox);
|
||||
assert_eq!(header.size, src_box.box_size());
|
||||
|
||||
let dst_box = MoovBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(dst_box, src_box);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use crate::meta::MetaBox;
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{edts::EdtsBox, mdia::MdiaBox, tkhd::TkhdBox};
|
||||
|
||||
|
@ -11,6 +12,9 @@ pub struct TrakBox {
|
|||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub edts: Option<EdtsBox>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<MetaBox>,
|
||||
|
||||
pub mdia: MdiaBox,
|
||||
}
|
||||
|
||||
|
@ -55,6 +59,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrakBox {
|
|||
|
||||
let mut tkhd = None;
|
||||
let mut edts = None;
|
||||
let mut meta = None;
|
||||
let mut mdia = None;
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
|
@ -71,6 +76,9 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrakBox {
|
|||
BoxType::EdtsBox => {
|
||||
edts = Some(EdtsBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::MetaBox => {
|
||||
meta = Some(MetaBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::MdiaBox => {
|
||||
mdia = Some(MdiaBox::read_box(reader, s)?);
|
||||
}
|
||||
|
@ -95,6 +103,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrakBox {
|
|||
Ok(TrakBox {
|
||||
tkhd: tkhd.unwrap(),
|
||||
edts,
|
||||
meta,
|
||||
mdia: mdia.unwrap(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -11,6 +11,38 @@ pub struct UdtaBox {
|
|||
pub meta: Option<MetaBox>,
|
||||
}
|
||||
|
||||
impl UdtaBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::UdtaBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(meta) = &self.meta {
|
||||
size += meta.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for UdtaBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
Ok(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for UdtaBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
@ -42,3 +74,58 @@ impl<R: Read + Seek> ReadBox<&mut R> for UdtaBox {
|
|||
Ok(UdtaBox { meta })
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for UdtaBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
if let Some(meta) = &self.meta {
|
||||
meta.write_box(writer)?;
|
||||
}
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_udta_empty() {
|
||||
let src_box = UdtaBox { meta: None };
|
||||
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::UdtaBox);
|
||||
assert_eq!(header.size, src_box.box_size());
|
||||
|
||||
let dst_box = UdtaBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(dst_box, src_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_udta() {
|
||||
let src_box = UdtaBox {
|
||||
meta: Some(MetaBox::default()),
|
||||
};
|
||||
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::UdtaBox);
|
||||
assert_eq!(header.size, src_box.box_size());
|
||||
|
||||
let dst_box = UdtaBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(dst_box, src_box);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::collections::HashMap;
|
|||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::meta::MetaBox;
|
||||
use crate::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -170,9 +171,11 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
|
||||
impl<R> Mp4Reader<R> {
|
||||
pub fn metadata(&self) -> impl Metadata<'_> {
|
||||
self.moov
|
||||
.udta
|
||||
.as_ref()
|
||||
.and_then(|udta| udta.meta.as_ref().and_then(|meta| meta.ilst.as_ref()))
|
||||
self.moov.udta.as_ref().and_then(|udta| {
|
||||
udta.meta.as_ref().and_then(|meta| match meta {
|
||||
MetaBox::Mdir { ilst } => ilst.as_ref(),
|
||||
_ => None,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue