mirror of
https://github.com/alfg/mp4-rust.git
synced 2024-12-22 20:16:27 +00:00
Feature/writebox trait (#11)
* Add ReadBox trait * Add boxtype macro * Remove offset in BoxHeader * Fix parsing error when box has largesize * Remove duplicated codes reading version and flags * Simplify all box size types as largesize * Add WriteBox trait and improve compatibility with large box * Split large atoms file into smaller ones * Refator Error Co-authored-by: Byungwan Jun <unipro.kr@gmail.com>
This commit is contained in:
parent
95fc64b811
commit
8f56200dd0
22 changed files with 1747 additions and 792 deletions
56
Cargo.lock
generated
56
Cargo.lock
generated
|
@ -10,7 +10,63 @@ name = "mp4"
|
|||
version = "0.4.3"
|
||||
dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[metadata]
|
||||
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
|
||||
"checksum proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12"
|
||||
"checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
|
||||
"checksum syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)" = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250"
|
||||
"checksum thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08"
|
||||
"checksum thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793"
|
||||
"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||
|
|
|
@ -18,4 +18,5 @@ keywords = ["mp4", "isobmff"]
|
|||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
thiserror = "^1.0"
|
||||
byteorder = "1"
|
||||
|
|
|
@ -58,7 +58,7 @@ fn main() {
|
|||
|
||||
println!(" media:");
|
||||
if let Some(ref s) = stts {
|
||||
println!(" sample count: {:?}", s.sample_counts[0]);
|
||||
println!(" sample count: {:?}", s.entries[0].sample_count);
|
||||
}
|
||||
println!(" timescale: {:?}", mdhd.timescale);
|
||||
println!(
|
||||
|
@ -73,7 +73,7 @@ fn main() {
|
|||
if let Some(ref s) = stts {
|
||||
println!(
|
||||
" frame rate: (computed): {:?}",
|
||||
get_framerate(&s.sample_counts, mdhd.duration, mdhd.timescale)
|
||||
get_framerate(s.entries[0].sample_count, mdhd.duration, mdhd.timescale)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -97,18 +97,18 @@ fn get_handler_type(handler: &str) -> mp4::TrackType {
|
|||
return typ;
|
||||
}
|
||||
|
||||
fn get_duration_ms(duration: u32, timescale: u32) -> String {
|
||||
fn get_duration_ms(duration: u64, timescale: u32) -> String {
|
||||
let ms = (duration as f64 / timescale as f64) * 1000.0;
|
||||
return format!("{:.2}", ms.floor());
|
||||
}
|
||||
|
||||
fn get_framerate(sample_counts: &Vec<u32>, duration: u32, timescale: u32) -> String {
|
||||
let sc = (sample_counts[0] as f64) * 1000.0;
|
||||
fn get_framerate(sample_count: u32, duration: u64, timescale: u32) -> String {
|
||||
let sc = (sample_count as f64) * 1000.0;
|
||||
let ms = (duration as f64 / timescale as f64) * 1000.0;
|
||||
return format!("{:.2}", sc / ms.floor());
|
||||
}
|
||||
|
||||
fn creation_time(creation_time: u32) -> u32 {
|
||||
fn creation_time(creation_time: u64) -> u64 {
|
||||
// convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01)
|
||||
if creation_time >= 2082844800 {
|
||||
creation_time - 2082844800
|
||||
|
|
767
src/atoms.rs
767
src/atoms.rs
|
@ -1,767 +0,0 @@
|
|||
use std::fmt;
|
||||
use std::io::{BufReader, SeekFrom, Seek, Read};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use std::char::{decode_utf16, REPLACEMENT_CHARACTER};
|
||||
use crate::{Error, read_box_header, BoxHeader, HEADER_SIZE};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
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 Into<u32> for BoxType {
|
||||
fn into(self) -> u32 {
|
||||
match self {
|
||||
$( BoxType::$name => $value, )*
|
||||
BoxType::UnknownBox(t) => t,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! read_box_header_ext {
|
||||
($r:ident, $v:ident, $ f:ident) => {
|
||||
let $v = $r.read_u8().unwrap();
|
||||
let flags_a = $r.read_u8().unwrap();
|
||||
let flags_b = $r.read_u8().unwrap();
|
||||
let flags_c = $r.read_u8().unwrap();
|
||||
let $f = u32::from(flags_a) << 16 | u32::from(flags_b) << 8 | u32::from(flags_c);
|
||||
}
|
||||
}
|
||||
|
||||
boxtype!{
|
||||
FtypBox => 0x66747970,
|
||||
MvhdBox => 0x6d766864,
|
||||
FreeBox => 0x66726565,
|
||||
MdatBox => 0x6d646174,
|
||||
MoovBox => 0x6d6f6f76,
|
||||
MoofBox => 0x6d6f6f66,
|
||||
TkhdBox => 0x746b6864,
|
||||
EdtsBox => 0x65647473,
|
||||
MdiaBox => 0x6d646961,
|
||||
ElstBox => 0x656c7374,
|
||||
MdhdBox => 0x6d646864,
|
||||
HdlrBox => 0x68646c72,
|
||||
MinfBox => 0x6d696e66,
|
||||
VmhdBox => 0x766d6864,
|
||||
StblBox => 0x7374626c,
|
||||
StsdBox => 0x73747364,
|
||||
SttsBox => 0x73747473,
|
||||
TrakBox => 0x7472616b,
|
||||
UdtaBox => 0x75647461,
|
||||
DinfBox => 0x64696e66,
|
||||
SmhdBox => 0x736d6864,
|
||||
Avc1Box => 0x61766331,
|
||||
Mp4aBox => 0x6d703461
|
||||
}
|
||||
|
||||
impl fmt::Debug for BoxType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let fourcc: FourCC = From::from(self.clone());
|
||||
write!(f, "{}", fourcc)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone)]
|
||||
pub struct FourCC {
|
||||
pub value: String
|
||||
}
|
||||
|
||||
impl From<u32> for FourCC {
|
||||
fn from(number: u32) -> FourCC {
|
||||
let mut box_chars = Vec::new();
|
||||
for x in 0..4 {
|
||||
let c = (number >> (x * 8) & 0x0000_00FF) as u8;
|
||||
box_chars.push(c);
|
||||
}
|
||||
box_chars.reverse();
|
||||
|
||||
let box_string = match String::from_utf8(box_chars) {
|
||||
Ok(t) => t,
|
||||
_ => String::from("null"), // error to retrieve fourcc
|
||||
};
|
||||
|
||||
FourCC {
|
||||
value: box_string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BoxType> for FourCC {
|
||||
fn from(t: BoxType) -> FourCC {
|
||||
let box_num: u32 = Into::into(t);
|
||||
From::from(box_num)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FourCC {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FourCC {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ReadBox<T>: Sized {
|
||||
fn read_box(_: T, size: u32) -> Result<Self>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FtypBox {
|
||||
pub major_brand: FourCC,
|
||||
pub minor_version: u32,
|
||||
pub compatible_brands: Vec<FourCC>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MoovBox {
|
||||
pub mvhd: MvhdBox,
|
||||
pub traks: Vec<TrakBox>,
|
||||
}
|
||||
|
||||
impl MoovBox {
|
||||
pub(crate) fn new() -> MoovBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MvhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub creation_time: u32,
|
||||
pub modification_time: u32,
|
||||
pub timescale: u32,
|
||||
pub duration: u32,
|
||||
pub rate: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TrakBox {
|
||||
pub tkhd: Option<TkhdBox>,
|
||||
pub edts: Option<EdtsBox>,
|
||||
pub mdia: Option<MdiaBox>,
|
||||
}
|
||||
|
||||
impl TrakBox {
|
||||
pub(crate) fn new() -> TrakBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TkhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub creation_time: u32,
|
||||
pub modification_time: u32,
|
||||
pub track_id: u32,
|
||||
pub duration: u64,
|
||||
pub layer: u16,
|
||||
pub alternate_group: u16,
|
||||
pub volume: u16,
|
||||
pub matrix: Matrix,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Matrix {
|
||||
pub a: i32,
|
||||
pub b: i32,
|
||||
pub u: i32,
|
||||
pub c: i32,
|
||||
pub d: i32,
|
||||
pub v: i32,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub w: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct EdtsBox {
|
||||
pub elst: Option<ElstBox>,
|
||||
}
|
||||
|
||||
impl EdtsBox {
|
||||
pub(crate) fn new() -> EdtsBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ElstBox {
|
||||
pub version: u32,
|
||||
pub entry_count: u32,
|
||||
pub entries: Vec<ElstEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ElstEntry {
|
||||
pub segment_duration: u32,
|
||||
pub media_time: u32,
|
||||
pub media_rate: u16,
|
||||
pub media_rate_fraction: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MdiaBox {
|
||||
pub mdhd: Option<MdhdBox>,
|
||||
pub hdlr: Option<HdlrBox>,
|
||||
pub minf: Option<MinfBox>,
|
||||
}
|
||||
|
||||
impl MdiaBox {
|
||||
pub(crate) fn new() -> MdiaBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MdhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub creation_time: u32,
|
||||
pub modification_time: u32,
|
||||
pub timescale: u32,
|
||||
pub duration: u32,
|
||||
pub language: u16,
|
||||
pub language_string: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct HdlrBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub handler_type: FourCC,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MinfBox {
|
||||
pub vmhd: Option<VmhdBox>,
|
||||
pub stbl: Option<StblBox>,
|
||||
}
|
||||
|
||||
impl MinfBox {
|
||||
pub(crate) fn new() -> MinfBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct VmhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub graphics_mode: u16,
|
||||
pub op_color: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct StblBox {
|
||||
pub stts: Option<SttsBox>,
|
||||
pub stsd: Option<StsdBox>,
|
||||
}
|
||||
|
||||
impl StblBox {
|
||||
pub(crate) fn new() -> StblBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SttsBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entry_count: u32,
|
||||
pub sample_counts: Vec<u32>,
|
||||
pub sample_deltas: Vec<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct StsdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for FtypBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let major = reader.read_u32::<BigEndian>().unwrap();
|
||||
let minor = reader.read_u32::<BigEndian>().unwrap();
|
||||
if size % 4 != 0 {
|
||||
return Err(Error::InvalidData("invalid ftyp size"));
|
||||
}
|
||||
let brand_count = (size - 16) / 4; // header + major + minor
|
||||
|
||||
let mut brands = Vec::new();
|
||||
for _ in 0..brand_count {
|
||||
let b = reader.read_u32::<BigEndian>().unwrap();
|
||||
brands.push(From::from(b));
|
||||
}
|
||||
|
||||
Ok(FtypBox {
|
||||
major_brand: From::from(major),
|
||||
minor_version: minor,
|
||||
compatible_brands: brands,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MoovBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let mut moov = MoovBox::new();
|
||||
|
||||
let mut start = 0u64;
|
||||
while start < size as u64 {
|
||||
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start).unwrap();
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::MvhdBox => {
|
||||
moov.mvhd = MvhdBox::read_box(reader, s as u32).unwrap();
|
||||
}
|
||||
BoxType::TrakBox => {
|
||||
let trak = TrakBox::read_box(reader, s as u32).unwrap();
|
||||
moov.traks.push(trak);
|
||||
}
|
||||
BoxType::UdtaBox => {
|
||||
start = (s as u32 - HEADER_SIZE) as u64;
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
Ok(moov)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MvhdBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
|
||||
read_box_header_ext!(reader, version, flags);
|
||||
|
||||
let creation_time = reader.read_u32::<BigEndian>().unwrap();
|
||||
let modification_time = reader.read_u32::<BigEndian>().unwrap();
|
||||
let timescale = reader.read_u32::<BigEndian>().unwrap();
|
||||
let duration = reader.read_u32::<BigEndian>().unwrap();
|
||||
let rate = reader.read_u32::<BigEndian>().unwrap();
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(MvhdBox{
|
||||
version,
|
||||
flags,
|
||||
creation_time,
|
||||
modification_time,
|
||||
timescale,
|
||||
duration,
|
||||
rate,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for TrakBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
let mut trak = TrakBox::new();
|
||||
|
||||
let start = 0u64;
|
||||
while start < size as u64 {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start).unwrap();
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::TkhdBox => {
|
||||
let tkhd = TkhdBox::read_box(reader, s as u32).unwrap();
|
||||
trak.tkhd = Some(tkhd);
|
||||
}
|
||||
BoxType::EdtsBox => {
|
||||
let edts = EdtsBox::read_box(reader, s as u32).unwrap();
|
||||
trak.edts = Some(edts);
|
||||
}
|
||||
BoxType::MdiaBox => {
|
||||
let mdia = MdiaBox::read_box(reader, s as u32).unwrap();
|
||||
trak.mdia = Some(mdia);
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(trak)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for TkhdBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
|
||||
read_box_header_ext!(reader, version, flags);
|
||||
|
||||
let creation_time = reader.read_u32::<BigEndian>().unwrap();
|
||||
let modification_time = reader.read_u32::<BigEndian>().unwrap();
|
||||
let track_id = reader.read_u32::<BigEndian>().unwrap();
|
||||
let duration = reader.read_u64::<BigEndian>().unwrap();
|
||||
reader.read_u64::<BigEndian>().unwrap(); // skip.
|
||||
let layer = reader.read_u16::<BigEndian>().unwrap();
|
||||
let alternate_group = reader.read_u16::<BigEndian>().unwrap();
|
||||
let volume = reader.read_u16::<BigEndian>().unwrap() >> 8;
|
||||
|
||||
reader.read_u8().unwrap(); // skip.
|
||||
let matrix = Matrix{
|
||||
a: reader.read_i32::<byteorder::LittleEndian>().unwrap(),
|
||||
b: reader.read_i32::<BigEndian>().unwrap(),
|
||||
u: reader.read_i32::<BigEndian>().unwrap(),
|
||||
c: reader.read_i32::<BigEndian>().unwrap(),
|
||||
d: reader.read_i32::<BigEndian>().unwrap(),
|
||||
v: reader.read_i32::<BigEndian>().unwrap(),
|
||||
x: reader.read_i32::<BigEndian>().unwrap(),
|
||||
y: reader.read_i32::<BigEndian>().unwrap(),
|
||||
w: reader.read_i32::<BigEndian>().unwrap(),
|
||||
};
|
||||
|
||||
let width = reader.read_u32::<BigEndian>().unwrap() >> 8;
|
||||
let height = reader.read_u32::<BigEndian>().unwrap() >> 8;
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(TkhdBox {
|
||||
version,
|
||||
flags,
|
||||
creation_time,
|
||||
modification_time,
|
||||
track_id,
|
||||
duration,
|
||||
layer,
|
||||
alternate_group,
|
||||
volume,
|
||||
matrix,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for EdtsBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
let mut edts = EdtsBox::new();
|
||||
|
||||
let start = 0u64;
|
||||
while start < size as u64 {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start).unwrap();
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::ElstBox => {
|
||||
let elst = ElstBox::read_box(reader, s as u32).unwrap();
|
||||
edts.elst = Some(elst);
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(edts)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for ElstBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
|
||||
let version = reader.read_u32::<BigEndian>().unwrap();
|
||||
let entry_count = reader.read_u32::<BigEndian>().unwrap();
|
||||
|
||||
let mut entries = Vec::new();
|
||||
|
||||
for _i in 0..entry_count {
|
||||
let entry = ElstEntry{
|
||||
segment_duration: reader.read_u32::<BigEndian>().unwrap(),
|
||||
media_time: reader.read_u32::<BigEndian>().unwrap(),
|
||||
media_rate: reader.read_u16::<BigEndian>().unwrap(),
|
||||
media_rate_fraction: reader.read_u16::<BigEndian>().unwrap(),
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(ElstBox {
|
||||
version,
|
||||
entry_count,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MdiaBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
let mut mdia = MdiaBox::new();
|
||||
|
||||
let start = 0u64;
|
||||
while start < size as u64 {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start).unwrap();
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::MdhdBox => {
|
||||
let mdhd = MdhdBox::read_box(reader, s as u32).unwrap();
|
||||
mdia.mdhd = Some(mdhd);
|
||||
}
|
||||
BoxType::HdlrBox => {
|
||||
let hdlr = HdlrBox::read_box(reader, s as u32).unwrap();
|
||||
mdia.hdlr = Some(hdlr);
|
||||
}
|
||||
BoxType::MinfBox => {
|
||||
let minf = MinfBox::read_box(reader, s as u32).unwrap();
|
||||
mdia.minf = Some(minf);
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(mdia)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MdhdBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
|
||||
read_box_header_ext!(reader, version, flags);
|
||||
|
||||
let creation_time = reader.read_u32::<BigEndian>().unwrap();
|
||||
let modification_time = reader.read_u32::<BigEndian>().unwrap();
|
||||
let timescale = reader.read_u32::<BigEndian>().unwrap();
|
||||
let duration = reader.read_u32::<BigEndian>().unwrap();
|
||||
let language = reader.read_u16::<BigEndian>().unwrap();
|
||||
let language_string = get_language_string(language);
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(MdhdBox {
|
||||
version,
|
||||
flags,
|
||||
creation_time,
|
||||
modification_time,
|
||||
timescale,
|
||||
duration,
|
||||
language,
|
||||
language_string,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_language_string(language: u16) -> String {
|
||||
let mut lang: [u16; 3] = [0; 3];
|
||||
|
||||
lang[0] = ((language >> 10) & 0x1F) + 0x60;
|
||||
lang[1] = ((language >> 5) & 0x1F) + 0x60;
|
||||
lang[2] = ((language) & 0x1F) + 0x60;
|
||||
|
||||
// Decode utf-16 encoded bytes into a string.
|
||||
let lang_str = decode_utf16(lang.iter().cloned())
|
||||
.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
|
||||
.collect::<String>();
|
||||
|
||||
return lang_str;
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for HdlrBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
|
||||
read_box_header_ext!(reader, version, flags);
|
||||
|
||||
reader.read_u32::<BigEndian>().unwrap(); // skip.
|
||||
let handler = reader.read_u32::<BigEndian>().unwrap();
|
||||
|
||||
let n = reader.seek(SeekFrom::Current(12)).unwrap(); // 12 bytes reserved.
|
||||
let buf_size = (size as u64 - (n - current)) - HEADER_SIZE as u64;
|
||||
let mut buf = vec![0u8; buf_size as usize];
|
||||
reader.read_exact(&mut buf).unwrap();
|
||||
|
||||
let handler_string = match String::from_utf8(buf) {
|
||||
Ok(t) => t,
|
||||
_ => String::from("null"),
|
||||
};
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(HdlrBox {
|
||||
version,
|
||||
flags,
|
||||
handler_type: From::from(handler),
|
||||
name: handler_string,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MinfBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
let mut minf = MinfBox::new();
|
||||
|
||||
let mut start = 0u64;
|
||||
while start < size as u64 {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start).unwrap();
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::VmhdBox => {
|
||||
let vmhd = VmhdBox::read_box(reader, s as u32).unwrap();
|
||||
minf.vmhd = Some(vmhd);
|
||||
}
|
||||
BoxType::SmhdBox => {
|
||||
start = (s as u32 - HEADER_SIZE) as u64;
|
||||
}
|
||||
BoxType::DinfBox => {
|
||||
start = (s as u32 - HEADER_SIZE) as u64;
|
||||
}
|
||||
BoxType::StblBox => {
|
||||
let stbl = StblBox::read_box(reader, s as u32).unwrap();
|
||||
minf.stbl = Some(stbl);
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(minf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for VmhdBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
|
||||
read_box_header_ext!(reader, version, flags);
|
||||
|
||||
let graphics_mode = reader.read_u16::<BigEndian>().unwrap();
|
||||
let op_color = reader.read_u16::<BigEndian>().unwrap();
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(VmhdBox {
|
||||
version,
|
||||
flags,
|
||||
graphics_mode,
|
||||
op_color,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for StblBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let mut stbl = StblBox::new();
|
||||
|
||||
let start = 0u64;
|
||||
while start < size as u64 {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start).unwrap();
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::StsdBox => {
|
||||
let stsd = StsdBox::read_box(reader, s as u32).unwrap();
|
||||
stbl.stsd = Some(stsd);
|
||||
}
|
||||
BoxType::SttsBox => {
|
||||
let stts = SttsBox::read_box(reader, s as u32).unwrap();
|
||||
stbl.stts = Some(stts);
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
Ok(stbl)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for SttsBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
|
||||
read_box_header_ext!(reader, version, flags);
|
||||
|
||||
let entry_count = reader.read_u32::<BigEndian>().unwrap();
|
||||
let mut sample_counts = Vec::new();
|
||||
let mut sample_deltas = Vec::new();
|
||||
|
||||
for _i in 0..entry_count {
|
||||
let sc = reader.read_u32::<BigEndian>().unwrap();
|
||||
let sd = reader.read_u32::<BigEndian>().unwrap();
|
||||
sample_counts.push(sc);
|
||||
sample_deltas.push(sd);
|
||||
}
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(SttsBox {
|
||||
version,
|
||||
flags,
|
||||
entry_count,
|
||||
sample_counts,
|
||||
sample_deltas,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for StsdBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
|
||||
read_box_header_ext!(reader, version, flags);
|
||||
|
||||
reader.read_u32::<BigEndian>().unwrap(); // skip.
|
||||
|
||||
let mut start = 0u64;
|
||||
while start < size as u64 {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start).unwrap();
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::Avc1Box => {
|
||||
start = (s as u32 - HEADER_SIZE) as u64;
|
||||
}
|
||||
BoxType::Mp4aBox => {
|
||||
start = (s as u32 - HEADER_SIZE) as u64;
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(StsdBox {
|
||||
version,
|
||||
flags,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn skip<R: Read + Seek>(reader: &mut BufReader<R>, current: u64, size: u32) {
|
||||
let after = reader.seek(SeekFrom::Current(0)).unwrap();
|
||||
let remaining_bytes = (size as u64 - (after - current)) as i64;
|
||||
reader.seek(SeekFrom::Current(remaining_bytes - HEADER_SIZE as i64)).unwrap();
|
||||
}
|
68
src/atoms/edts.rs
Normal file
68
src/atoms/edts.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::elst::ElstBox;
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct EdtsBox {
|
||||
pub elst: Option<ElstBox>,
|
||||
}
|
||||
|
||||
impl EdtsBox {
|
||||
pub(crate) fn new() -> EdtsBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for EdtsBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::EdtsBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(elst) = &self.elst {
|
||||
size += elst.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for EdtsBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
|
||||
let mut edts = EdtsBox::new();
|
||||
|
||||
let start = 0u64;
|
||||
while start < size {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::ElstBox => {
|
||||
let elst = ElstBox::read_box(reader, s)?;
|
||||
edts.elst = Some(elst);
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
skip_read(reader, current, size)?;
|
||||
|
||||
Ok(edts)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut BufWriter<W>> for EdtsBox {
|
||||
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write_box(writer)?;
|
||||
|
||||
if let Some(elst) = &self.elst {
|
||||
elst.write_box(writer)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
104
src/atoms/elst.rs
Normal file
104
src/atoms/elst.rs
Normal file
|
@ -0,0 +1,104 @@
|
|||
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ElstBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entry_count: u32,
|
||||
pub entries: Vec<ElstEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ElstEntry {
|
||||
pub segment_duration: u64,
|
||||
pub media_time: u64,
|
||||
pub media_rate: u16,
|
||||
pub media_rate_fraction: u16,
|
||||
}
|
||||
|
||||
impl Mp4Box for ElstBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::ElstBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
if self.version == 1 {
|
||||
size += self.entry_count as u64 * 20;
|
||||
} else {
|
||||
assert_eq!(self.version, 0);
|
||||
size += self.entry_count as u64 * 12;
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for ElstBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
let mut entries = Vec::with_capacity(entry_count as usize);
|
||||
for _ in 0..entry_count {
|
||||
let (segment_duration, media_time)
|
||||
= if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
|
||||
let entry = ElstEntry{
|
||||
segment_duration,
|
||||
media_time,
|
||||
media_rate: reader.read_u16::<BigEndian>()?,
|
||||
media_rate_fraction: reader.read_u16::<BigEndian>()?,
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
skip_read(reader, current, size)?;
|
||||
|
||||
Ok(ElstBox {
|
||||
version,
|
||||
flags,
|
||||
entry_count,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut BufWriter<W>> for ElstBox {
|
||||
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write_box(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
assert_eq!(self.entry_count as usize, self.entries.len());
|
||||
writer.write_u32::<BigEndian>(self.entry_count)?;
|
||||
for entry in self.entries.iter() {
|
||||
if self.version == 1 {
|
||||
writer.write_u64::<BigEndian>(entry.segment_duration)?;
|
||||
writer.write_u64::<BigEndian>(entry.media_time)?;
|
||||
} else {
|
||||
writer.write_u32::<BigEndian>(entry.segment_duration as u32)?;
|
||||
writer.write_u32::<BigEndian>(entry.media_time as u32)?;
|
||||
}
|
||||
writer.write_u16::<BigEndian>(entry.media_rate)?;
|
||||
writer.write_u16::<BigEndian>(entry.media_rate_fraction)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
97
src/atoms/ftyp.rs
Normal file
97
src/atoms/ftyp.rs
Normal file
|
@ -0,0 +1,97 @@
|
|||
use std::io::{BufReader, Seek, Read, BufWriter, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct FtypBox {
|
||||
pub major_brand: FourCC,
|
||||
pub minor_version: u32,
|
||||
pub compatible_brands: Vec<FourCC>,
|
||||
}
|
||||
|
||||
impl Mp4Box for FtypBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::FtypBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
HEADER_SIZE + 8 + (4 * self.compatible_brands.len() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for FtypBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
|
||||
let major = reader.read_u32::<BigEndian>()?;
|
||||
let minor = reader.read_u32::<BigEndian>()?;
|
||||
if size % 4 != 0 {
|
||||
return Err(Error::InvalidData("invalid ftyp size"));
|
||||
}
|
||||
let brand_count = (size - 16) / 4; // header + major + minor
|
||||
|
||||
let mut brands = Vec::new();
|
||||
for _ in 0..brand_count {
|
||||
let b = reader.read_u32::<BigEndian>()?;
|
||||
brands.push(From::from(b));
|
||||
}
|
||||
|
||||
Ok(FtypBox {
|
||||
major_brand: From::from(major),
|
||||
minor_version: minor,
|
||||
compatible_brands: brands,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut BufWriter<W>> for FtypBox {
|
||||
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write_box(writer)?;
|
||||
|
||||
writer.write_u32::<BigEndian>((&self.major_brand).into())?;
|
||||
writer.write_u32::<BigEndian>(self.minor_version)?;
|
||||
for b in self.compatible_brands.iter() {
|
||||
writer.write_u32::<BigEndian>(b.into())?;
|
||||
}
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::read_box_header;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_ftyp() {
|
||||
let src_box = FtypBox {
|
||||
major_brand: FourCC { value: String::from("isom") },
|
||||
minor_version: 0,
|
||||
compatible_brands: vec![
|
||||
FourCC { value: String::from("isom") },
|
||||
FourCC { value: String::from("iso2") },
|
||||
FourCC { value: String::from("avc1") },
|
||||
FourCC { value: String::from("mp41") },
|
||||
]
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
{
|
||||
let mut writer = BufWriter::new(&mut buf);
|
||||
src_box.write_box(&mut writer).unwrap();
|
||||
}
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
{
|
||||
let mut reader = BufReader::new(Cursor::new(&buf));
|
||||
let header = read_box_header(&mut reader, 0).unwrap();
|
||||
assert_eq!(header.name, BoxType::FtypBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = FtypBox::read_box(&mut reader, header.size).unwrap();
|
||||
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
||||
}
|
79
src/atoms/hdlr.rs
Normal file
79
src/atoms/hdlr.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct HdlrBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub handler_type: FourCC,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl Mp4Box for HdlrBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::HdlrBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 20 + self.name.len() as u64 + 1
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for HdlrBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // pre-defined
|
||||
let handler = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
let n = reader.seek(SeekFrom::Current(12))?; // 12 bytes reserved.
|
||||
|
||||
let buf_size = (size - (n - current)) - HEADER_SIZE;
|
||||
let mut buf = vec![0u8; buf_size as usize];
|
||||
reader.read_exact(&mut buf)?;
|
||||
|
||||
let handler_string = match String::from_utf8(buf) {
|
||||
Ok(t) => {
|
||||
assert_eq!(t.len(), buf_size as usize);
|
||||
t
|
||||
},
|
||||
_ => String::from("null"),
|
||||
};
|
||||
|
||||
skip_read(reader, current, size)?;
|
||||
|
||||
Ok(HdlrBox {
|
||||
version,
|
||||
flags,
|
||||
handler_type: From::from(handler),
|
||||
name: handler_string,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut BufWriter<W>> for HdlrBox {
|
||||
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write_box(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(0)?; // pre-defined
|
||||
writer.write_u32::<BigEndian>((&self.handler_type).into())?;
|
||||
|
||||
// 12 bytes reserved
|
||||
for _ in 0..3 {
|
||||
writer.write_u32::<BigEndian>(0)?;
|
||||
}
|
||||
|
||||
writer.write(self.name.as_bytes())?;
|
||||
writer.write_u8(0)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
119
src/atoms/mdhd.rs
Normal file
119
src/atoms/mdhd.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
|
||||
use std::char::{decode_utf16, REPLACEMENT_CHARACTER};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MdhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub creation_time: u64,
|
||||
pub modification_time: u64,
|
||||
pub timescale: u32,
|
||||
pub duration: u64,
|
||||
pub language: u16,
|
||||
pub language_string: String,
|
||||
}
|
||||
|
||||
impl Mp4Box for MdhdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::MdhdBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
|
||||
if self.version == 1 {
|
||||
size += 28;
|
||||
} else {
|
||||
assert_eq!(self.version, 0);
|
||||
size += 16;
|
||||
}
|
||||
size += 4;
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MdhdBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let (creation_time, modification_time, timescale, duration)
|
||||
= if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
let language = reader.read_u16::<BigEndian>()?;
|
||||
let language_string = get_language_string(language);
|
||||
skip_read(reader, current, size)?;
|
||||
|
||||
Ok(MdhdBox {
|
||||
version,
|
||||
flags,
|
||||
creation_time,
|
||||
modification_time,
|
||||
timescale,
|
||||
duration,
|
||||
language,
|
||||
language_string,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut BufWriter<W>> for MdhdBox {
|
||||
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write_box(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
if self.version == 1 {
|
||||
writer.write_u64::<BigEndian>(self.creation_time)?;
|
||||
writer.write_u64::<BigEndian>(self.modification_time)?;
|
||||
writer.write_u32::<BigEndian>(self.timescale)?;
|
||||
writer.write_u64::<BigEndian>(self.duration)?;
|
||||
} else {
|
||||
assert_eq!(self.version, 0);
|
||||
writer.write_u32::<BigEndian>(self.creation_time as u32)?;
|
||||
writer.write_u32::<BigEndian>(self.modification_time as u32)?;
|
||||
writer.write_u32::<BigEndian>(self.timescale)?;
|
||||
writer.write_u32::<BigEndian>(self.duration as u32)?;
|
||||
}
|
||||
|
||||
writer.write_u16::<BigEndian>(self.language)?;
|
||||
writer.write_u16::<BigEndian>(0)?; // pre-defined
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_language_string(language: u16) -> String {
|
||||
let mut lang: [u16; 3] = [0; 3];
|
||||
|
||||
lang[0] = ((language >> 10) & 0x1F) + 0x60;
|
||||
lang[1] = ((language >> 5) & 0x1F) + 0x60;
|
||||
lang[2] = ((language) & 0x1F) + 0x60;
|
||||
|
||||
// Decode utf-16 encoded bytes into a string.
|
||||
let lang_str = decode_utf16(lang.iter().cloned())
|
||||
.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
|
||||
.collect::<String>();
|
||||
|
||||
return lang_str;
|
||||
}
|
90
src/atoms/mdia.rs
Normal file
90
src/atoms/mdia.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::{mdhd::MdhdBox, hdlr::HdlrBox, minf::MinfBox};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MdiaBox {
|
||||
pub mdhd: Option<MdhdBox>,
|
||||
pub hdlr: Option<HdlrBox>,
|
||||
pub minf: Option<MinfBox>,
|
||||
}
|
||||
|
||||
impl MdiaBox {
|
||||
pub(crate) fn new() -> MdiaBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MdiaBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::MdiaBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(mdhd) = &self.mdhd {
|
||||
size += mdhd.box_size();
|
||||
}
|
||||
if let Some(hdlr) = &self.hdlr {
|
||||
size += hdlr.box_size();
|
||||
}
|
||||
if let Some(minf) = &self.minf {
|
||||
size += minf.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MdiaBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
|
||||
let mut mdia = MdiaBox::new();
|
||||
|
||||
let start = 0u64;
|
||||
while start < size {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::MdhdBox => {
|
||||
let mdhd = MdhdBox::read_box(reader, s)?;
|
||||
mdia.mdhd = Some(mdhd);
|
||||
}
|
||||
BoxType::HdlrBox => {
|
||||
let hdlr = HdlrBox::read_box(reader, s)?;
|
||||
mdia.hdlr = Some(hdlr);
|
||||
}
|
||||
BoxType::MinfBox => {
|
||||
let minf = MinfBox::read_box(reader, s)?;
|
||||
mdia.minf = Some(minf);
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
skip_read(reader, current, size)?;
|
||||
|
||||
Ok(mdia)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut BufWriter<W>> for MdiaBox {
|
||||
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write_box(writer)?;
|
||||
|
||||
if let Some(mdhd) = &self.mdhd {
|
||||
mdhd.write_box(writer)?;
|
||||
}
|
||||
if let Some(hdlr) = &self.hdlr {
|
||||
hdlr.write_box(writer)?;
|
||||
}
|
||||
if let Some(minf) = &self.minf {
|
||||
minf.write_box(writer)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
85
src/atoms/minf.rs
Normal file
85
src/atoms/minf.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::{vmhd::VmhdBox, stbl::StblBox};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MinfBox {
|
||||
pub vmhd: Option<VmhdBox>,
|
||||
pub stbl: Option<StblBox>,
|
||||
}
|
||||
|
||||
impl MinfBox {
|
||||
pub(crate) fn new() -> MinfBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MinfBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::MinfBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(vmhd) = &self.vmhd {
|
||||
size += vmhd.box_size();
|
||||
}
|
||||
if let Some(stbl) = &self.stbl {
|
||||
size += stbl.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MinfBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
|
||||
let mut minf = MinfBox::new();
|
||||
|
||||
let mut start = 0u64;
|
||||
while start < size {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::VmhdBox => {
|
||||
let vmhd = VmhdBox::read_box(reader, s)?;
|
||||
minf.vmhd = Some(vmhd);
|
||||
}
|
||||
BoxType::SmhdBox => {
|
||||
start = s - HEADER_SIZE;
|
||||
}
|
||||
BoxType::DinfBox => {
|
||||
start = s - HEADER_SIZE;
|
||||
}
|
||||
BoxType::StblBox => {
|
||||
let stbl = StblBox::read_box(reader, s)?;
|
||||
minf.stbl = Some(stbl);
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
skip_read(reader, current, size)?;
|
||||
|
||||
Ok(minf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut BufWriter<W>> for MinfBox {
|
||||
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write_box(writer)?;
|
||||
|
||||
if let Some(vmhd) = &self.vmhd {
|
||||
vmhd.write_box(writer)?;
|
||||
}
|
||||
if let Some(stbl) = &self.stbl {
|
||||
stbl.write_box(writer)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
217
src/atoms/mod.rs
Normal file
217
src/atoms/mod.rs
Normal file
|
@ -0,0 +1,217 @@
|
|||
use std::fmt;
|
||||
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::*;
|
||||
|
||||
mod ftyp;
|
||||
mod moov;
|
||||
mod mvhd;
|
||||
mod trak;
|
||||
mod tkhd;
|
||||
mod edts;
|
||||
mod elst;
|
||||
mod mdia;
|
||||
mod mdhd;
|
||||
mod hdlr;
|
||||
mod minf;
|
||||
mod vmhd;
|
||||
mod stbl;
|
||||
mod stts;
|
||||
mod stsd;
|
||||
|
||||
pub use ftyp::FtypBox;
|
||||
pub use moov::MoovBox;
|
||||
|
||||
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 Into<u32> for BoxType {
|
||||
fn into(self) -> u32 {
|
||||
match self {
|
||||
$( BoxType::$name => $value, )*
|
||||
BoxType::UnknownBox(t) => t,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boxtype!{
|
||||
FtypBox => 0x66747970,
|
||||
MvhdBox => 0x6d766864,
|
||||
FreeBox => 0x66726565,
|
||||
MdatBox => 0x6d646174,
|
||||
MoovBox => 0x6d6f6f76,
|
||||
MoofBox => 0x6d6f6f66,
|
||||
TkhdBox => 0x746b6864,
|
||||
EdtsBox => 0x65647473,
|
||||
MdiaBox => 0x6d646961,
|
||||
ElstBox => 0x656c7374,
|
||||
MdhdBox => 0x6d646864,
|
||||
HdlrBox => 0x68646c72,
|
||||
MinfBox => 0x6d696e66,
|
||||
VmhdBox => 0x766d6864,
|
||||
StblBox => 0x7374626c,
|
||||
StsdBox => 0x73747364,
|
||||
SttsBox => 0x73747473,
|
||||
TrakBox => 0x7472616b,
|
||||
UdtaBox => 0x75647461,
|
||||
DinfBox => 0x64696e66,
|
||||
SmhdBox => 0x736d6864,
|
||||
Avc1Box => 0x61766331,
|
||||
Mp4aBox => 0x6d703461
|
||||
}
|
||||
|
||||
impl fmt::Debug for BoxType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let fourcc: FourCC = From::from(self.clone());
|
||||
write!(f, "{}", fourcc)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone)]
|
||||
pub struct FourCC {
|
||||
pub value: String
|
||||
}
|
||||
|
||||
impl From<u32> for FourCC {
|
||||
fn from(number: u32) -> Self {
|
||||
let mut box_chars = Vec::new();
|
||||
for x in 0..4 {
|
||||
let c = (number >> (x * 8) & 0x0000_00FF) as u8;
|
||||
box_chars.push(c);
|
||||
}
|
||||
box_chars.reverse();
|
||||
|
||||
let box_string = match String::from_utf8(box_chars) {
|
||||
Ok(t) => t,
|
||||
_ => String::from("null"), // error to retrieve fourcc
|
||||
};
|
||||
|
||||
FourCC {
|
||||
value: box_string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FourCC> for u32 {
|
||||
fn from(fourcc: FourCC) -> u32 {
|
||||
(&fourcc).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&FourCC> for u32 {
|
||||
fn from(fourcc: &FourCC) -> u32 {
|
||||
let mut b: [u8; 4] = Default::default();
|
||||
b.copy_from_slice(fourcc.value.as_bytes());
|
||||
u32::from_be_bytes(b)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BoxType> for FourCC {
|
||||
fn from(t: BoxType) -> FourCC {
|
||||
let box_num: u32 = Into::into(t);
|
||||
From::from(box_num)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FourCC {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FourCC {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Mp4Box: Sized {
|
||||
fn box_type(&self) -> BoxType;
|
||||
fn box_size(&self) -> u64;
|
||||
}
|
||||
|
||||
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>;
|
||||
}
|
||||
|
||||
pub fn read_box_header_ext<R: Read>(reader: &mut BufReader<R>) -> Result<(u8, u32)> {
|
||||
let version = reader.read_u8()?;
|
||||
let flags_a = reader.read_u8()?;
|
||||
let flags_b = reader.read_u8()?;
|
||||
let flags_c = reader.read_u8()?;
|
||||
let flags = u32::from(flags_a) << 16 | u32::from(flags_b) << 8 | u32::from(flags_c);
|
||||
Ok((version, flags))
|
||||
}
|
||||
|
||||
pub fn write_box_header_ext<W: Write>(w: &mut BufWriter<W>, v: u8, f: u32) -> Result<u64> {
|
||||
let d = u32::from(v) << 24 | f;
|
||||
w.write_u32::<BigEndian>(d)?;
|
||||
Ok(4)
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut BufWriter<W>> for BoxHeader {
|
||||
fn write_box(&self, writer: &mut BufWriter<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 skip_read<R: Read + Seek>(reader: &mut BufReader<R>, current: u64, size: u64) -> Result<i64> {
|
||||
let after = reader.seek(SeekFrom::Current(0))?;
|
||||
let remaining_bytes = (size - (after - current)) as i64;
|
||||
let size = remaining_bytes - HEADER_SIZE as i64;
|
||||
reader.seek(SeekFrom::Current(size))?;
|
||||
Ok(size)
|
||||
}
|
||||
|
||||
pub fn skip_write<W: Write>(writer: &mut BufWriter<W>, size: u64) -> Result<u64> {
|
||||
for _ in 0..size {
|
||||
writer.write_u8(0)?;
|
||||
}
|
||||
Ok(size)
|
||||
}
|
||||
|
||||
#[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, "ftyp");
|
||||
let ftyp_fcc2 = ftyp_value.into();
|
||||
assert_eq!(ftyp_fcc, ftyp_fcc2);
|
||||
}
|
||||
}
|
73
src/atoms/moov.rs
Normal file
73
src/atoms/moov.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use std::io::{BufReader, Seek, Read, BufWriter, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::{mvhd::MvhdBox, trak::TrakBox};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MoovBox {
|
||||
pub mvhd: MvhdBox,
|
||||
pub traks: Vec<TrakBox>,
|
||||
}
|
||||
|
||||
impl MoovBox {
|
||||
pub(crate) fn new() -> MoovBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MoovBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::MoovBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + self.mvhd.box_size();
|
||||
for trak in self.traks.iter() {
|
||||
size += trak.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MoovBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
|
||||
let mut moov = MoovBox::new();
|
||||
|
||||
let mut start = 0u64;
|
||||
while start < size {
|
||||
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::MvhdBox => {
|
||||
moov.mvhd = MvhdBox::read_box(reader, s)?;
|
||||
}
|
||||
BoxType::TrakBox => {
|
||||
let trak = TrakBox::read_box(reader, s)?;
|
||||
moov.traks.push(trak);
|
||||
}
|
||||
BoxType::UdtaBox => {
|
||||
start = s - HEADER_SIZE;
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
Ok(moov)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut BufWriter<W>> for MoovBox {
|
||||
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write_box(writer)?;
|
||||
|
||||
self.mvhd.write_box(writer)?;
|
||||
for trak in self.traks.iter() {
|
||||
trak.write_box(writer)?;
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
}
|
137
src/atoms/mvhd.rs
Normal file
137
src/atoms/mvhd.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct MvhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub creation_time: u64,
|
||||
pub modification_time: u64,
|
||||
pub timescale: u32,
|
||||
pub duration: u64,
|
||||
pub rate: u32,
|
||||
}
|
||||
|
||||
impl Mp4Box for MvhdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::MvhdBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
if self.version == 1 {
|
||||
size += 28;
|
||||
} else {
|
||||
assert_eq!(self.version, 0);
|
||||
size += 16;
|
||||
}
|
||||
size += 80;
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MvhdBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let (creation_time, modification_time, timescale, duration)
|
||||
= if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
let rate = reader.read_u32::<BigEndian>()?;
|
||||
skip_read(reader, current, size)?;
|
||||
|
||||
Ok(MvhdBox{
|
||||
version,
|
||||
flags,
|
||||
creation_time,
|
||||
modification_time,
|
||||
timescale,
|
||||
duration,
|
||||
rate,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut BufWriter<W>> for MvhdBox {
|
||||
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write_box(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
if self.version == 1 {
|
||||
writer.write_u64::<BigEndian>(self.creation_time)?;
|
||||
writer.write_u64::<BigEndian>(self.modification_time)?;
|
||||
writer.write_u32::<BigEndian>(self.timescale)?;
|
||||
writer.write_u64::<BigEndian>(self.duration)?;
|
||||
} else {
|
||||
assert_eq!(self.version, 0);
|
||||
writer.write_u32::<BigEndian>(self.creation_time as u32)?;
|
||||
writer.write_u32::<BigEndian>(self.modification_time as u32)?;
|
||||
writer.write_u32::<BigEndian>(self.timescale)?;
|
||||
writer.write_u32::<BigEndian>(self.duration as u32)?;
|
||||
}
|
||||
writer.write_u32::<BigEndian>(self.rate)?;
|
||||
|
||||
// XXX volume, ...
|
||||
skip_write(writer, 76)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::read_box_header;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_mvhd() {
|
||||
let src_box = MvhdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
creation_time: 100,
|
||||
modification_time: 200,
|
||||
timescale: 1000,
|
||||
duration: 634634,
|
||||
rate: 0x00010000,
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
{
|
||||
let mut writer = BufWriter::new(&mut buf);
|
||||
src_box.write_box(&mut writer).unwrap();
|
||||
}
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
{
|
||||
let mut reader = BufReader::new(Cursor::new(&buf));
|
||||
let header = read_box_header(&mut reader, 0).unwrap();
|
||||
assert_eq!(header.name, BoxType::MvhdBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = MvhdBox::read_box(&mut reader, header.size).unwrap();
|
||||
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
||||
}
|
76
src/atoms/stbl.rs
Normal file
76
src/atoms/stbl.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
use std::io::{BufReader, Seek, Read, BufWriter, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::{stts::SttsBox, stsd::StsdBox};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct StblBox {
|
||||
pub stts: Option<SttsBox>,
|
||||
pub stsd: Option<StsdBox>,
|
||||
}
|
||||
|
||||
impl StblBox {
|
||||
pub(crate) fn new() -> StblBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for StblBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::StblBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(stts) = &self.stts {
|
||||
size += stts.box_size();
|
||||
}
|
||||
if let Some(stsd) = &self.stsd {
|
||||
size += stsd.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for StblBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
|
||||
let mut stbl = StblBox::new();
|
||||
|
||||
let start = 0u64;
|
||||
while start < size {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::SttsBox => {
|
||||
let stts = SttsBox::read_box(reader, s)?;
|
||||
stbl.stts = Some(stts);
|
||||
}
|
||||
BoxType::StsdBox => {
|
||||
let stsd = StsdBox::read_box(reader, s)?;
|
||||
stbl.stsd = Some(stsd);
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
Ok(stbl)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut BufWriter<W>> for StblBox {
|
||||
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write_box(writer)?;
|
||||
|
||||
if let Some(stts) = &self.stts {
|
||||
stts.write_box(writer)?;
|
||||
}
|
||||
if let Some(stsd) = &self.stsd {
|
||||
stsd.write_box(writer)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
61
src/atoms/stsd.rs
Normal file
61
src/atoms/stsd.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
|
||||
use crate::*;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct StsdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entry_count: u32,
|
||||
}
|
||||
|
||||
impl Mp4Box for StsdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::StsdBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
// TODO
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for StsdBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
let mut start = 0u64;
|
||||
while start < size {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::Avc1Box => {}
|
||||
BoxType::Mp4aBox => {}
|
||||
_ => break
|
||||
}
|
||||
start += s - HEADER_SIZE;
|
||||
}
|
||||
skip_read(reader, current, size)?;
|
||||
|
||||
Ok(StsdBox {
|
||||
version,
|
||||
flags,
|
||||
entry_count,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut BufWriter<W>> for StsdBox {
|
||||
fn write_box(&self, _writer: &mut BufWriter<W>) -> Result<u64> {
|
||||
// TODO
|
||||
Ok(0)
|
||||
}
|
||||
}
|
72
src/atoms/stts.rs
Normal file
72
src/atoms/stts.rs
Normal file
|
@ -0,0 +1,72 @@
|
|||
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SttsBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entry_count: u32,
|
||||
pub entries: Vec<SttsEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SttsEntry {
|
||||
pub sample_count: u32,
|
||||
pub sample_delta: u32,
|
||||
}
|
||||
|
||||
impl Mp4Box for SttsBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::SttsBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 4 + (8 * self.entry_count as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for SttsBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
let mut entries = Vec::with_capacity(entry_count as usize);
|
||||
for _i in 0..entry_count {
|
||||
let entry = SttsEntry {
|
||||
sample_count: reader.read_u32::<BigEndian>()?,
|
||||
sample_delta: reader.read_u32::<BigEndian>()?,
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
skip_read(reader, current, size)?;
|
||||
|
||||
Ok(SttsBox {
|
||||
version,
|
||||
flags,
|
||||
entry_count,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut BufWriter<W>> for SttsBox {
|
||||
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write_box(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.entry_count)?;
|
||||
for entry in self.entries.iter() {
|
||||
writer.write_u32::<BigEndian>(entry.sample_count)?;
|
||||
writer.write_u32::<BigEndian>(entry.sample_delta)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
215
src/atoms/tkhd.rs
Normal file
215
src/atoms/tkhd.rs
Normal file
|
@ -0,0 +1,215 @@
|
|||
use std::io::{BufReader, Seek, SeekFrom, Read, BufWriter, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct TkhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub creation_time: u64,
|
||||
pub modification_time: u64,
|
||||
pub track_id: u32,
|
||||
pub duration: u64,
|
||||
pub layer: u16,
|
||||
pub alternate_group: u16,
|
||||
pub volume: u16,
|
||||
pub matrix: Matrix,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq)]
|
||||
pub struct Matrix {
|
||||
pub a: i32,
|
||||
pub b: i32,
|
||||
pub u: i32,
|
||||
pub c: i32,
|
||||
pub d: i32,
|
||||
pub v: i32,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub w: i32,
|
||||
}
|
||||
|
||||
impl Mp4Box for TkhdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::TkhdBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
if self.version == 1 {
|
||||
size += 32;
|
||||
} else {
|
||||
assert_eq!(self.version, 0);
|
||||
size += 20;
|
||||
}
|
||||
size += 60;
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for TkhdBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let (creation_time, modification_time, track_id, _, duration)
|
||||
= if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
assert_eq!(version, 0);
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
reader.read_u64::<BigEndian>()?; // reserved
|
||||
let layer = reader.read_u16::<BigEndian>()?;
|
||||
let alternate_group = reader.read_u16::<BigEndian>()?;
|
||||
let volume = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
reader.read_u16::<BigEndian>()?; // reserved
|
||||
let matrix = Matrix{
|
||||
a: reader.read_i32::<byteorder::LittleEndian>()?,
|
||||
b: reader.read_i32::<BigEndian>()?,
|
||||
u: reader.read_i32::<BigEndian>()?,
|
||||
c: reader.read_i32::<BigEndian>()?,
|
||||
d: reader.read_i32::<BigEndian>()?,
|
||||
v: reader.read_i32::<BigEndian>()?,
|
||||
x: reader.read_i32::<BigEndian>()?,
|
||||
y: reader.read_i32::<BigEndian>()?,
|
||||
w: reader.read_i32::<BigEndian>()?,
|
||||
};
|
||||
|
||||
let width = reader.read_u32::<BigEndian>()? >> 16;
|
||||
let height = reader.read_u32::<BigEndian>()? >> 16;
|
||||
|
||||
skip_read(reader, current, size)?;
|
||||
|
||||
Ok(TkhdBox {
|
||||
version,
|
||||
flags,
|
||||
creation_time,
|
||||
modification_time,
|
||||
track_id,
|
||||
duration,
|
||||
layer,
|
||||
alternate_group,
|
||||
volume,
|
||||
matrix,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut BufWriter<W>> for TkhdBox {
|
||||
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write_box(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
if self.version == 1 {
|
||||
writer.write_u64::<BigEndian>(self.creation_time)?;
|
||||
writer.write_u64::<BigEndian>(self.modification_time)?;
|
||||
writer.write_u32::<BigEndian>(self.track_id)?;
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u64::<BigEndian>(self.duration)?;
|
||||
} else {
|
||||
assert_eq!(self.version, 0);
|
||||
writer.write_u32::<BigEndian>(self.creation_time as u32)?;
|
||||
writer.write_u32::<BigEndian>(self.modification_time as u32)?;
|
||||
writer.write_u32::<BigEndian>(self.track_id)?;
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u32::<BigEndian>(self.duration as u32)?;
|
||||
}
|
||||
|
||||
writer.write_u64::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.layer)?;
|
||||
writer.write_u16::<BigEndian>(self.alternate_group)?;
|
||||
writer.write_u16::<BigEndian>(self.volume)?;
|
||||
|
||||
writer.write_u16::<BigEndian>(0)?; // reserved
|
||||
|
||||
writer.write_i32::<byteorder::LittleEndian>(self.matrix.a)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.b)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.u)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.c)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.d)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.v)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.x)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.y)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.w)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.width << 16)?;
|
||||
writer.write_u32::<BigEndian>(self.height << 16)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::read_box_header;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_tkhd() {
|
||||
let src_box = TkhdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
creation_time: 100,
|
||||
modification_time: 200,
|
||||
track_id: 1,
|
||||
duration: 634634,
|
||||
layer: 0,
|
||||
alternate_group: 0,
|
||||
volume: 0x0100,
|
||||
matrix: Matrix {
|
||||
a: 0x00010000,
|
||||
b: 0,
|
||||
u: 0,
|
||||
c: 0,
|
||||
d: 0x00010000,
|
||||
v: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 0x40000000,
|
||||
},
|
||||
width: 512,
|
||||
height: 288,
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
{
|
||||
let mut writer = BufWriter::new(&mut buf);
|
||||
src_box.write_box(&mut writer).unwrap();
|
||||
}
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
{
|
||||
let mut reader = BufReader::new(Cursor::new(&buf));
|
||||
let header = read_box_header(&mut reader, 0).unwrap();
|
||||
assert_eq!(header.name, BoxType::TkhdBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = TkhdBox::read_box(&mut reader, header.size).unwrap();
|
||||
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
||||
}
|
90
src/atoms/trak.rs
Normal file
90
src/atoms/trak.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
|
||||
|
||||
use crate::*;
|
||||
use crate::atoms::{tkhd::TkhdBox, edts::EdtsBox, mdia::MdiaBox};
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TrakBox {
|
||||
pub tkhd: Option<TkhdBox>,
|
||||
pub edts: Option<EdtsBox>,
|
||||
pub mdia: Option<MdiaBox>,
|
||||
}
|
||||
|
||||
impl TrakBox {
|
||||
pub(crate) fn new() -> TrakBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for TrakBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::TrakBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(tkhd) = &self.tkhd {
|
||||
size += tkhd.box_size();
|
||||
}
|
||||
if let Some(edts) = &self.edts {
|
||||
size += edts.box_size();
|
||||
}
|
||||
if let Some(mdia) = &self.mdia {
|
||||
size += mdia.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for TrakBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
|
||||
let mut trak = TrakBox::new();
|
||||
|
||||
let start = 0u64;
|
||||
while start < size {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start)?;
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::TkhdBox => {
|
||||
let tkhd = TkhdBox::read_box(reader, s)?;
|
||||
trak.tkhd = Some(tkhd);
|
||||
}
|
||||
BoxType::EdtsBox => {
|
||||
let edts = EdtsBox::read_box(reader, s)?;
|
||||
trak.edts = Some(edts);
|
||||
}
|
||||
BoxType::MdiaBox => {
|
||||
let mdia = MdiaBox::read_box(reader, s)?;
|
||||
trak.mdia = Some(mdia);
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
skip_read(reader, current, size)?;
|
||||
|
||||
Ok(trak)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut BufWriter<W>> for TrakBox {
|
||||
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write_box(writer)?;
|
||||
|
||||
if let Some(tkhd) = &self.tkhd {
|
||||
tkhd.write_box(writer)?;
|
||||
}
|
||||
if let Some(edts) = &self.edts {
|
||||
edts.write_box(writer)?;
|
||||
}
|
||||
if let Some(mdia) = &self.mdia {
|
||||
mdia.write_box(writer)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
69
src/atoms/vmhd.rs
Normal file
69
src/atoms/vmhd.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use std::io::{BufReader, SeekFrom, Seek, Read, BufWriter, Write};
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
use crate::*;
|
||||
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct VmhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub graphics_mode: u16,
|
||||
pub op_color: RgbColor,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct RgbColor {
|
||||
pub red: u16,
|
||||
pub green: u16,
|
||||
pub blue: u16,
|
||||
}
|
||||
|
||||
impl Mp4Box for VmhdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::VmhdBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 8
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for VmhdBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u64) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position.
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let graphics_mode = reader.read_u16::<BigEndian>()?;
|
||||
let op_color = RgbColor {
|
||||
red: reader.read_u16::<BigEndian>()?,
|
||||
green: reader.read_u16::<BigEndian>()?,
|
||||
blue: reader.read_u16::<BigEndian>()?,
|
||||
};
|
||||
skip_read(reader, current, size)?;
|
||||
|
||||
Ok(VmhdBox {
|
||||
version,
|
||||
flags,
|
||||
graphics_mode,
|
||||
op_color,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut BufWriter<W>> for VmhdBox {
|
||||
fn write_box(&self, writer: &mut BufWriter<W>) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write_box(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u16::<BigEndian>(self.graphics_mode)?;
|
||||
writer.write_u16::<BigEndian>(self.op_color.red)?;
|
||||
writer.write_u16::<BigEndian>(self.op_color.green)?;
|
||||
writer.write_u16::<BigEndian>(self.op_color.blue)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
9
src/error.rs
Normal file
9
src/error.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("{0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("{0}")]
|
||||
InvalidData(&'static str),
|
||||
}
|
42
src/lib.rs
42
src/lib.rs
|
@ -1,19 +1,17 @@
|
|||
extern crate byteorder;
|
||||
|
||||
use std::io::prelude::*;
|
||||
use std::io::{BufReader, Read, SeekFrom};
|
||||
use std::io::{BufReader, Read, SeekFrom, Seek};
|
||||
use std::fs::File;
|
||||
use std::convert::TryInto;
|
||||
|
||||
mod atoms;
|
||||
use crate::atoms::*;
|
||||
|
||||
const HEADER_SIZE: u32 = 8;
|
||||
mod error;
|
||||
pub use error::Error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidData(&'static str),
|
||||
}
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
// XXX if box has largesize
|
||||
const HEADER_SIZE: u64 = 8;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum TrackType {
|
||||
|
@ -42,16 +40,22 @@ struct BoxHeader {
|
|||
size: u64,
|
||||
}
|
||||
|
||||
impl BoxHeader {
|
||||
fn new(name: BoxType, size: u64) -> Self {
|
||||
Self { name, size }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_mp4(f: File) -> Result<BMFF> {
|
||||
|
||||
// Open file and read boxes.
|
||||
let bmff = read_boxes(f).unwrap();
|
||||
let bmff = read_boxes(f)?;
|
||||
|
||||
Ok(bmff)
|
||||
}
|
||||
|
||||
fn read_boxes(f: File) -> Result<BMFF> {
|
||||
let filesize = f.metadata().unwrap().len();
|
||||
let filesize = f.metadata()?.len();
|
||||
let mut reader = BufReader::new(f);
|
||||
let mut bmff = BMFF::new();
|
||||
bmff.size = filesize;
|
||||
|
@ -60,27 +64,27 @@ fn read_boxes(f: File) -> Result<BMFF> {
|
|||
while start < filesize {
|
||||
|
||||
// Get box header.
|
||||
let header = read_box_header(&mut reader, start).unwrap();
|
||||
let header = read_box_header(&mut reader, start)?;
|
||||
let BoxHeader{ name, size } = header;
|
||||
|
||||
// Match and parse the atom boxes.
|
||||
match name {
|
||||
BoxType::FtypBox => {
|
||||
let ftyp = FtypBox::read_box(&mut reader, size as u32).unwrap();
|
||||
let ftyp = FtypBox::read_box(&mut reader, size)?;
|
||||
bmff.ftyp = ftyp;
|
||||
}
|
||||
BoxType::FreeBox => {
|
||||
start = 0;
|
||||
}
|
||||
BoxType::MdatBox => {
|
||||
start = (size as u32 - HEADER_SIZE) as u64;
|
||||
start = size - HEADER_SIZE;
|
||||
}
|
||||
BoxType::MoovBox => {
|
||||
let moov = MoovBox::read_box(&mut reader, size as u32).unwrap();
|
||||
let moov = MoovBox::read_box(&mut reader, size)?;
|
||||
bmff.moov = Some(moov);
|
||||
}
|
||||
BoxType::MoofBox => {
|
||||
start = (size as u32 - HEADER_SIZE) as u64;
|
||||
start = size - HEADER_SIZE;
|
||||
}
|
||||
_ => {
|
||||
// Skip over unsupported boxes, but stop if the size is zero,
|
||||
|
@ -88,7 +92,7 @@ fn read_boxes(f: File) -> Result<BMFF> {
|
|||
if size == 0 {
|
||||
break;
|
||||
} else {
|
||||
start = (size as u32 - HEADER_SIZE) as u64;
|
||||
start = size - HEADER_SIZE;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -103,7 +107,7 @@ fn read_box_header<R: Read + Seek>(reader: &mut BufReader<R>, start: u64) -> Res
|
|||
|
||||
// Create and read to buf.
|
||||
let mut buf = [0u8;8]; // 8 bytes for box header.
|
||||
reader.read(&mut buf).unwrap();
|
||||
reader.read(&mut buf)?;
|
||||
|
||||
// Get size.
|
||||
let s = buf[0..4].try_into().unwrap();
|
||||
|
@ -115,7 +119,7 @@ fn read_box_header<R: Read + Seek>(reader: &mut BufReader<R>, start: u64) -> Res
|
|||
|
||||
// Get largesize if size is 1
|
||||
if size == 1 {
|
||||
reader.read(&mut buf).unwrap();
|
||||
reader.read(&mut buf)?;
|
||||
let s = buf.try_into().unwrap();
|
||||
let largesize = u64::from_be_bytes(s);
|
||||
|
||||
|
|
Loading…
Reference in a new issue