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"
|
version = "0.4.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"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]
|
[metadata]
|
||||||
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
|
"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"
|
license = "MIT"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
thiserror = "^1.0"
|
||||||
byteorder = "1"
|
byteorder = "1"
|
||||||
|
|
|
@ -58,7 +58,7 @@ fn main() {
|
||||||
|
|
||||||
println!(" media:");
|
println!(" media:");
|
||||||
if let Some(ref s) = stts {
|
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!(" timescale: {:?}", mdhd.timescale);
|
||||||
println!(
|
println!(
|
||||||
|
@ -73,7 +73,7 @@ fn main() {
|
||||||
if let Some(ref s) = stts {
|
if let Some(ref s) = stts {
|
||||||
println!(
|
println!(
|
||||||
" frame rate: (computed): {:?}",
|
" 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;
|
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;
|
let ms = (duration as f64 / timescale as f64) * 1000.0;
|
||||||
return format!("{:.2}", ms.floor());
|
return format!("{:.2}", ms.floor());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_framerate(sample_counts: &Vec<u32>, duration: u32, timescale: u32) -> String {
|
fn get_framerate(sample_count: u32, duration: u64, timescale: u32) -> String {
|
||||||
let sc = (sample_counts[0] as f64) * 1000.0;
|
let sc = (sample_count as f64) * 1000.0;
|
||||||
let ms = (duration as f64 / timescale as f64) * 1000.0;
|
let ms = (duration as f64 / timescale as f64) * 1000.0;
|
||||||
return format!("{:.2}", sc / ms.floor());
|
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)
|
// convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01)
|
||||||
if creation_time >= 2082844800 {
|
if creation_time >= 2082844800 {
|
||||||
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::{BufReader, Read, SeekFrom, Seek};
|
||||||
|
|
||||||
use std::io::prelude::*;
|
|
||||||
use std::io::{BufReader, Read, SeekFrom};
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
mod atoms;
|
mod atoms;
|
||||||
use crate::atoms::*;
|
use crate::atoms::*;
|
||||||
|
|
||||||
const HEADER_SIZE: u32 = 8;
|
mod error;
|
||||||
|
pub use error::Error;
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
pub enum Error {
|
|
||||||
InvalidData(&'static str),
|
// XXX if box has largesize
|
||||||
}
|
const HEADER_SIZE: u64 = 8;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum TrackType {
|
pub enum TrackType {
|
||||||
|
@ -42,16 +40,22 @@ struct BoxHeader {
|
||||||
size: u64,
|
size: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BoxHeader {
|
||||||
|
fn new(name: BoxType, size: u64) -> Self {
|
||||||
|
Self { name, size }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_mp4(f: File) -> Result<BMFF> {
|
pub fn read_mp4(f: File) -> Result<BMFF> {
|
||||||
|
|
||||||
// Open file and read boxes.
|
// Open file and read boxes.
|
||||||
let bmff = read_boxes(f).unwrap();
|
let bmff = read_boxes(f)?;
|
||||||
|
|
||||||
Ok(bmff)
|
Ok(bmff)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_boxes(f: File) -> Result<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 reader = BufReader::new(f);
|
||||||
let mut bmff = BMFF::new();
|
let mut bmff = BMFF::new();
|
||||||
bmff.size = filesize;
|
bmff.size = filesize;
|
||||||
|
@ -60,27 +64,27 @@ fn read_boxes(f: File) -> Result<BMFF> {
|
||||||
while start < filesize {
|
while start < filesize {
|
||||||
|
|
||||||
// Get box header.
|
// 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;
|
let BoxHeader{ name, size } = header;
|
||||||
|
|
||||||
// Match and parse the atom boxes.
|
// Match and parse the atom boxes.
|
||||||
match name {
|
match name {
|
||||||
BoxType::FtypBox => {
|
BoxType::FtypBox => {
|
||||||
let ftyp = FtypBox::read_box(&mut reader, size as u32).unwrap();
|
let ftyp = FtypBox::read_box(&mut reader, size)?;
|
||||||
bmff.ftyp = ftyp;
|
bmff.ftyp = ftyp;
|
||||||
}
|
}
|
||||||
BoxType::FreeBox => {
|
BoxType::FreeBox => {
|
||||||
start = 0;
|
start = 0;
|
||||||
}
|
}
|
||||||
BoxType::MdatBox => {
|
BoxType::MdatBox => {
|
||||||
start = (size as u32 - HEADER_SIZE) as u64;
|
start = size - HEADER_SIZE;
|
||||||
}
|
}
|
||||||
BoxType::MoovBox => {
|
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);
|
bmff.moov = Some(moov);
|
||||||
}
|
}
|
||||||
BoxType::MoofBox => {
|
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,
|
// 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 {
|
if size == 0 {
|
||||||
break;
|
break;
|
||||||
} else {
|
} 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.
|
// Create and read to buf.
|
||||||
let mut buf = [0u8;8]; // 8 bytes for box header.
|
let mut buf = [0u8;8]; // 8 bytes for box header.
|
||||||
reader.read(&mut buf).unwrap();
|
reader.read(&mut buf)?;
|
||||||
|
|
||||||
// Get size.
|
// Get size.
|
||||||
let s = buf[0..4].try_into().unwrap();
|
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
|
// Get largesize if size is 1
|
||||||
if size == 1 {
|
if size == 1 {
|
||||||
reader.read(&mut buf).unwrap();
|
reader.read(&mut buf)?;
|
||||||
let s = buf.try_into().unwrap();
|
let s = buf.try_into().unwrap();
|
||||||
let largesize = u64::from_be_bytes(s);
|
let largesize = u64::from_be_bytes(s);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue