mirror of
https://github.com/alfg/mp4-rust.git
synced 2024-05-20 01:08:06 +00:00
ace2799c75
This introduces the 'Metadata' trait to enable access to common video metadata such title, year, cover art and more. Reading 'title', 'description', 'poster' and 'year' metadata is implemented here.
359 lines
8.8 KiB
Rust
359 lines
8.8 KiB
Rust
//! All ISO-MP4 boxes (atoms) and operations.
|
|
//!
|
|
//! * [ISO/IEC 14496-12](https://en.wikipedia.org/wiki/MPEG-4_Part_14) - ISO Base Media File Format (QuickTime, MPEG-4, etc)
|
|
//! * [ISO/IEC 14496-14](https://en.wikipedia.org/wiki/MPEG-4_Part_14) - MP4 file format
|
|
//! * ISO/IEC 14496-17 - Streaming text format
|
|
//! * [ISO 23009-1](https://www.iso.org/standard/79329.html) -Dynamic adaptive streaming over HTTP (DASH)
|
|
//!
|
|
//! http://developer.apple.com/documentation/QuickTime/QTFF/index.html
|
|
//! http://www.adobe.com/devnet/video/articles/mp4_movie_atom.html
|
|
//! http://mp4ra.org/#/atoms
|
|
//!
|
|
//! Supported Atoms:
|
|
//! ftyp
|
|
//! moov
|
|
//! mvhd
|
|
//! udta
|
|
//! meta
|
|
//! ilst
|
|
//! data
|
|
//! trak
|
|
//! tkhd
|
|
//! mdia
|
|
//! mdhd
|
|
//! hdlr
|
|
//! minf
|
|
//! stbl
|
|
//! stsd
|
|
//! avc1
|
|
//! hev1
|
|
//! mp4a
|
|
//! tx3g
|
|
//! stts
|
|
//! stsc
|
|
//! stsz
|
|
//! stss
|
|
//! stco
|
|
//! co64
|
|
//! ctts
|
|
//! dinf
|
|
//! dref
|
|
//! smhd
|
|
//! vmhd
|
|
//! edts
|
|
//! elst
|
|
//! mvex
|
|
//! mehd
|
|
//! trex
|
|
//! emsg
|
|
//! moof
|
|
//! mfhd
|
|
//! traf
|
|
//! tfhd
|
|
//! trun
|
|
//! mdat
|
|
//! free
|
|
//!
|
|
|
|
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
|
use std::convert::TryInto;
|
|
use std::io::{Read, Seek, SeekFrom, Write};
|
|
|
|
use crate::*;
|
|
|
|
pub(crate) mod avc1;
|
|
pub(crate) mod co64;
|
|
pub(crate) mod ctts;
|
|
pub(crate) mod data;
|
|
pub(crate) mod dinf;
|
|
pub(crate) mod edts;
|
|
pub(crate) mod elst;
|
|
pub(crate) mod emsg;
|
|
pub(crate) mod ftyp;
|
|
pub(crate) mod hdlr;
|
|
pub(crate) mod hev1;
|
|
pub(crate) mod ilst;
|
|
pub(crate) mod mdhd;
|
|
pub(crate) mod mdia;
|
|
pub(crate) mod mehd;
|
|
pub(crate) mod meta;
|
|
pub(crate) mod mfhd;
|
|
pub(crate) mod minf;
|
|
pub(crate) mod moof;
|
|
pub(crate) mod moov;
|
|
pub(crate) mod mp4a;
|
|
pub(crate) mod mvex;
|
|
pub(crate) mod mvhd;
|
|
pub(crate) mod smhd;
|
|
pub(crate) mod stbl;
|
|
pub(crate) mod stco;
|
|
pub(crate) mod stsc;
|
|
pub(crate) mod stsd;
|
|
pub(crate) mod stss;
|
|
pub(crate) mod stsz;
|
|
pub(crate) mod stts;
|
|
pub(crate) mod tfhd;
|
|
pub(crate) mod tkhd;
|
|
pub(crate) mod traf;
|
|
pub(crate) mod trak;
|
|
pub(crate) mod trex;
|
|
pub(crate) mod trun;
|
|
pub(crate) mod tx3g;
|
|
pub(crate) mod udta;
|
|
pub(crate) mod vmhd;
|
|
pub(crate) mod vp09;
|
|
pub(crate) mod vpcc;
|
|
|
|
pub use emsg::EmsgBox;
|
|
pub use ftyp::FtypBox;
|
|
pub use moof::MoofBox;
|
|
pub use moov::MoovBox;
|
|
|
|
pub const HEADER_SIZE: u64 = 8;
|
|
// const HEADER_LARGE_SIZE: u64 = 16;
|
|
pub const HEADER_EXT_SIZE: u64 = 4;
|
|
|
|
macro_rules! boxtype {
|
|
($( $name:ident => $value:expr ),*) => {
|
|
#[derive(Clone, Copy, PartialEq)]
|
|
pub enum BoxType {
|
|
$( $name, )*
|
|
UnknownBox(u32),
|
|
}
|
|
|
|
impl From<u32> for BoxType {
|
|
fn from(t: u32) -> BoxType {
|
|
match t {
|
|
$( $value => BoxType::$name, )*
|
|
_ => BoxType::UnknownBox(t),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<BoxType> for u32 {
|
|
fn from(b: BoxType) -> u32 {
|
|
match b {
|
|
$( BoxType::$name => $value, )*
|
|
BoxType::UnknownBox(t) => t,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
boxtype! {
|
|
FtypBox => 0x66747970,
|
|
MvhdBox => 0x6d766864,
|
|
MfhdBox => 0x6d666864,
|
|
FreeBox => 0x66726565,
|
|
MdatBox => 0x6d646174,
|
|
MoovBox => 0x6d6f6f76,
|
|
MvexBox => 0x6d766578,
|
|
MehdBox => 0x6d656864,
|
|
TrexBox => 0x74726578,
|
|
EmsgBox => 0x656d7367,
|
|
MoofBox => 0x6d6f6f66,
|
|
TkhdBox => 0x746b6864,
|
|
TfhdBox => 0x74666864,
|
|
EdtsBox => 0x65647473,
|
|
MdiaBox => 0x6d646961,
|
|
ElstBox => 0x656c7374,
|
|
MdhdBox => 0x6d646864,
|
|
HdlrBox => 0x68646c72,
|
|
MinfBox => 0x6d696e66,
|
|
VmhdBox => 0x766d6864,
|
|
StblBox => 0x7374626c,
|
|
StsdBox => 0x73747364,
|
|
SttsBox => 0x73747473,
|
|
CttsBox => 0x63747473,
|
|
StssBox => 0x73747373,
|
|
StscBox => 0x73747363,
|
|
StszBox => 0x7374737A,
|
|
StcoBox => 0x7374636F,
|
|
Co64Box => 0x636F3634,
|
|
TrakBox => 0x7472616b,
|
|
TrafBox => 0x74726166,
|
|
TrunBox => 0x7472756E,
|
|
UdtaBox => 0x75647461,
|
|
MetaBox => 0x6d657461,
|
|
DinfBox => 0x64696e66,
|
|
DrefBox => 0x64726566,
|
|
UrlBox => 0x75726C20,
|
|
SmhdBox => 0x736d6864,
|
|
Avc1Box => 0x61766331,
|
|
AvcCBox => 0x61766343,
|
|
Hev1Box => 0x68657631,
|
|
HvcCBox => 0x68766343,
|
|
Mp4aBox => 0x6d703461,
|
|
EsdsBox => 0x65736473,
|
|
Tx3gBox => 0x74783367,
|
|
VpccBox => 0x76706343,
|
|
Vp09Box => 0x76703039,
|
|
DataBox => 0x64617461,
|
|
IlstBox => 0x696c7374,
|
|
NameBox => 0xa96e616d,
|
|
DayBox => 0xa9646179,
|
|
CovrBox => 0x636f7672,
|
|
DescBox => 0x64657363
|
|
}
|
|
|
|
pub trait Mp4Box: Sized {
|
|
fn box_type(&self) -> BoxType;
|
|
fn box_size(&self) -> u64;
|
|
fn to_json(&self) -> Result<String>;
|
|
fn summary(&self) -> Result<String>;
|
|
}
|
|
|
|
pub trait ReadBox<T>: Sized {
|
|
fn read_box(_: T, size: u64) -> Result<Self>;
|
|
}
|
|
|
|
pub trait WriteBox<T>: Sized {
|
|
fn write_box(&self, _: T) -> Result<u64>;
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct BoxHeader {
|
|
pub name: BoxType,
|
|
pub size: u64,
|
|
}
|
|
|
|
impl BoxHeader {
|
|
pub fn new(name: BoxType, size: u64) -> Self {
|
|
Self { name, size }
|
|
}
|
|
|
|
// TODO: if size is 0, then this box is the last one in the file
|
|
pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
|
|
// Create and read to buf.
|
|
let mut buf = [0u8; 8]; // 8 bytes for box header.
|
|
reader.read_exact(&mut buf)?;
|
|
|
|
// Get size.
|
|
let s = buf[0..4].try_into().unwrap();
|
|
let size = u32::from_be_bytes(s);
|
|
|
|
// Get box type string.
|
|
let t = buf[4..8].try_into().unwrap();
|
|
let typ = u32::from_be_bytes(t);
|
|
|
|
// Get largesize if size is 1
|
|
if size == 1 {
|
|
reader.read_exact(&mut buf)?;
|
|
let largesize = u64::from_be_bytes(buf);
|
|
|
|
Ok(BoxHeader {
|
|
name: BoxType::from(typ),
|
|
size: largesize - HEADER_SIZE,
|
|
})
|
|
} else {
|
|
Ok(BoxHeader {
|
|
name: BoxType::from(typ),
|
|
size: size as u64,
|
|
})
|
|
}
|
|
}
|
|
|
|
pub fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
|
|
if self.size > u32::MAX as u64 {
|
|
writer.write_u32::<BigEndian>(1)?;
|
|
writer.write_u32::<BigEndian>(self.name.into())?;
|
|
writer.write_u64::<BigEndian>(self.size)?;
|
|
Ok(16)
|
|
} else {
|
|
writer.write_u32::<BigEndian>(self.size as u32)?;
|
|
writer.write_u32::<BigEndian>(self.name.into())?;
|
|
Ok(8)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn read_box_header_ext<R: Read>(reader: &mut R) -> Result<(u8, u32)> {
|
|
let version = reader.read_u8()?;
|
|
let flags = reader.read_u24::<BigEndian>()?;
|
|
Ok((version, flags))
|
|
}
|
|
|
|
pub fn write_box_header_ext<W: Write>(w: &mut W, v: u8, f: u32) -> Result<u64> {
|
|
w.write_u8(v)?;
|
|
w.write_u24::<BigEndian>(f)?;
|
|
Ok(4)
|
|
}
|
|
|
|
pub fn box_start<R: Seek>(seeker: &mut R) -> Result<u64> {
|
|
Ok(seeker.seek(SeekFrom::Current(0))? - HEADER_SIZE)
|
|
}
|
|
|
|
pub fn skip_bytes<S: Seek>(seeker: &mut S, size: u64) -> Result<()> {
|
|
seeker.seek(SeekFrom::Current(size as i64))?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn skip_bytes_to<S: Seek>(seeker: &mut S, pos: u64) -> Result<()> {
|
|
seeker.seek(SeekFrom::Start(pos))?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn skip_box<S: Seek>(seeker: &mut S, size: u64) -> Result<()> {
|
|
let start = box_start(seeker)?;
|
|
skip_bytes_to(seeker, start + size)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn write_zeros<W: Write>(writer: &mut W, size: u64) -> Result<()> {
|
|
for _ in 0..size {
|
|
writer.write_u8(0)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
mod value_u32 {
|
|
use crate::types::FixedPointU16;
|
|
use serde::{self, Serializer};
|
|
|
|
pub fn serialize<S>(fixed: &FixedPointU16, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
serializer.serialize_u16(fixed.value())
|
|
}
|
|
}
|
|
|
|
mod value_i16 {
|
|
use crate::types::FixedPointI8;
|
|
use serde::{self, Serializer};
|
|
|
|
pub fn serialize<S>(fixed: &FixedPointI8, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
serializer.serialize_i8(fixed.value())
|
|
}
|
|
}
|
|
|
|
mod value_u8 {
|
|
use crate::types::FixedPointU8;
|
|
use serde::{self, Serializer};
|
|
|
|
pub fn serialize<S>(fixed: &FixedPointU8, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: Serializer,
|
|
{
|
|
serializer.serialize_u8(fixed.value())
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_fourcc() {
|
|
let ftyp_fcc = 0x66747970;
|
|
let ftyp_value = FourCC::from(ftyp_fcc);
|
|
assert_eq!(&ftyp_value.value[..], b"ftyp");
|
|
let ftyp_fcc2: u32 = ftyp_value.into();
|
|
assert_eq!(ftyp_fcc, ftyp_fcc2);
|
|
}
|
|
}
|