mirror of
https://github.com/alfg/mp4-rust.git
synced 2024-05-19 16:58:04 +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,
|
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 {
|
impl<R: Read + Seek> ReadBox<&mut R> for DataBox {
|
||||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||||
let start = box_start(reader)?;
|
let start = box_start(reader)?;
|
||||||
|
@ -28,3 +61,58 @@ impl<R: Read + Seek> ReadBox<&mut R> for DataBox {
|
||||||
Ok(DataBox { data, data_type })
|
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>,
|
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 {
|
impl<R: Read + Seek> ReadBox<&mut R> for IlstBox {
|
||||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||||
let start = box_start(reader)?;
|
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)]
|
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||||
pub struct IlstItemBox {
|
pub struct IlstItemBox {
|
||||||
pub data: DataBox,
|
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 {
|
impl<R: Read + Seek> ReadBox<&mut R> for IlstItemBox {
|
||||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||||
let start = box_start(reader)?;
|
let start = box_start(reader)?;
|
||||||
|
@ -130,3 +188,56 @@ fn item_to_u32(item: &IlstItemBox) -> Option<u32> {
|
||||||
_ => None,
|
_ => 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 serde::Serialize;
|
||||||
|
|
||||||
|
use crate::mp4box::hdlr::HdlrBox;
|
||||||
use crate::mp4box::ilst::IlstBox;
|
use crate::mp4box::ilst::IlstBox;
|
||||||
use crate::mp4box::*;
|
use crate::mp4box::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||||
pub struct MetaBox {
|
#[serde(tag = "hdlr")]
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub ilst: Option<IlstBox>,
|
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 {
|
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));
|
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 ilst = None;
|
||||||
|
|
||||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||||
let end = start + size;
|
let end = start + size;
|
||||||
while current < end {
|
|
||||||
// Get box header.
|
|
||||||
let header = BoxHeader::read(reader)?;
|
|
||||||
let BoxHeader { name, size: s } = header;
|
|
||||||
|
|
||||||
match name {
|
match hdlr.handler_type {
|
||||||
BoxType::IlstBox => {
|
MDIR => {
|
||||||
ilst = Some(IlstBox::read_box(reader, s)?);
|
while current < end {
|
||||||
}
|
// Get box header.
|
||||||
_ => {
|
let header = BoxHeader::read(reader)?;
|
||||||
// XXX warn!()
|
let BoxHeader { name, size: s } = header;
|
||||||
skip_box(reader, s)?;
|
|
||||||
|
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 serde::Serialize;
|
||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
|
|
||||||
|
use crate::meta::MetaBox;
|
||||||
use crate::mp4box::*;
|
use crate::mp4box::*;
|
||||||
use crate::mp4box::{mvex::MvexBox, mvhd::MvhdBox, trak::TrakBox, udta::UdtaBox};
|
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 struct MoovBox {
|
||||||
pub mvhd: MvhdBox,
|
pub mvhd: MvhdBox,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub meta: Option<MetaBox>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub mvex: Option<MvexBox>,
|
pub mvex: Option<MvexBox>,
|
||||||
|
|
||||||
|
@ -28,6 +32,12 @@ impl MoovBox {
|
||||||
for trak in self.traks.iter() {
|
for trak in self.traks.iter() {
|
||||||
size += trak.box_size();
|
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
|
size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,6 +66,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
|
||||||
let start = box_start(reader)?;
|
let start = box_start(reader)?;
|
||||||
|
|
||||||
let mut mvhd = None;
|
let mut mvhd = None;
|
||||||
|
let mut meta = None;
|
||||||
let mut udta = None;
|
let mut udta = None;
|
||||||
let mut mvex = None;
|
let mut mvex = None;
|
||||||
let mut traks = Vec::new();
|
let mut traks = Vec::new();
|
||||||
|
@ -71,6 +82,9 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
|
||||||
BoxType::MvhdBox => {
|
BoxType::MvhdBox => {
|
||||||
mvhd = Some(MvhdBox::read_box(reader, s)?);
|
mvhd = Some(MvhdBox::read_box(reader, s)?);
|
||||||
}
|
}
|
||||||
|
BoxType::MetaBox => {
|
||||||
|
meta = Some(MetaBox::read_box(reader, s)?);
|
||||||
|
}
|
||||||
BoxType::MvexBox => {
|
BoxType::MvexBox => {
|
||||||
mvex = Some(MvexBox::read_box(reader, s)?);
|
mvex = Some(MvexBox::read_box(reader, s)?);
|
||||||
}
|
}
|
||||||
|
@ -98,6 +112,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
|
||||||
|
|
||||||
Ok(MoovBox {
|
Ok(MoovBox {
|
||||||
mvhd: mvhd.unwrap(),
|
mvhd: mvhd.unwrap(),
|
||||||
|
meta,
|
||||||
udta,
|
udta,
|
||||||
mvex,
|
mvex,
|
||||||
traks,
|
traks,
|
||||||
|
@ -114,6 +129,59 @@ impl<W: Write> WriteBox<&mut W> for MoovBox {
|
||||||
for trak in self.traks.iter() {
|
for trak in self.traks.iter() {
|
||||||
trak.write_box(writer)?;
|
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)
|
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 serde::Serialize;
|
||||||
use std::io::{Read, Seek, SeekFrom, Write};
|
use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
|
|
||||||
|
use crate::meta::MetaBox;
|
||||||
use crate::mp4box::*;
|
use crate::mp4box::*;
|
||||||
use crate::mp4box::{edts::EdtsBox, mdia::MdiaBox, tkhd::TkhdBox};
|
use crate::mp4box::{edts::EdtsBox, mdia::MdiaBox, tkhd::TkhdBox};
|
||||||
|
|
||||||
|
@ -11,6 +12,9 @@ pub struct TrakBox {
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub edts: Option<EdtsBox>,
|
pub edts: Option<EdtsBox>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub meta: Option<MetaBox>,
|
||||||
|
|
||||||
pub mdia: MdiaBox,
|
pub mdia: MdiaBox,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +59,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrakBox {
|
||||||
|
|
||||||
let mut tkhd = None;
|
let mut tkhd = None;
|
||||||
let mut edts = None;
|
let mut edts = None;
|
||||||
|
let mut meta = None;
|
||||||
let mut mdia = None;
|
let mut mdia = None;
|
||||||
|
|
||||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||||
|
@ -71,6 +76,9 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrakBox {
|
||||||
BoxType::EdtsBox => {
|
BoxType::EdtsBox => {
|
||||||
edts = Some(EdtsBox::read_box(reader, s)?);
|
edts = Some(EdtsBox::read_box(reader, s)?);
|
||||||
}
|
}
|
||||||
|
BoxType::MetaBox => {
|
||||||
|
meta = Some(MetaBox::read_box(reader, s)?);
|
||||||
|
}
|
||||||
BoxType::MdiaBox => {
|
BoxType::MdiaBox => {
|
||||||
mdia = Some(MdiaBox::read_box(reader, s)?);
|
mdia = Some(MdiaBox::read_box(reader, s)?);
|
||||||
}
|
}
|
||||||
|
@ -95,6 +103,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrakBox {
|
||||||
Ok(TrakBox {
|
Ok(TrakBox {
|
||||||
tkhd: tkhd.unwrap(),
|
tkhd: tkhd.unwrap(),
|
||||||
edts,
|
edts,
|
||||||
|
meta,
|
||||||
mdia: mdia.unwrap(),
|
mdia: mdia.unwrap(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,38 @@ pub struct UdtaBox {
|
||||||
pub meta: Option<MetaBox>,
|
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 {
|
impl<R: Read + Seek> ReadBox<&mut R> for UdtaBox {
|
||||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||||
let start = box_start(reader)?;
|
let start = box_start(reader)?;
|
||||||
|
@ -42,3 +74,58 @@ impl<R: Read + Seek> ReadBox<&mut R> for UdtaBox {
|
||||||
Ok(UdtaBox { meta })
|
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::io::{Read, Seek, SeekFrom};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::meta::MetaBox;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -170,9 +171,11 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
||||||
|
|
||||||
impl<R> Mp4Reader<R> {
|
impl<R> Mp4Reader<R> {
|
||||||
pub fn metadata(&self) -> impl Metadata<'_> {
|
pub fn metadata(&self) -> impl Metadata<'_> {
|
||||||
self.moov
|
self.moov.udta.as_ref().and_then(|udta| {
|
||||||
.udta
|
udta.meta.as_ref().and_then(|meta| match meta {
|
||||||
.as_ref()
|
MetaBox::Mdir { ilst } => ilst.as_ref(),
|
||||||
.and_then(|udta| udta.meta.as_ref().and_then(|meta| meta.ilst.as_ref()))
|
_ => None,
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue