From 8f56200dd0bc8546510cb7d81423887e465d4b11 Mon Sep 17 00:00:00 2001 From: Ian Jun <40660047+ian-spoonradio@users.noreply.github.com> Date: Wed, 29 Jul 2020 01:36:17 +0900 Subject: [PATCH] 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 --- Cargo.lock | 56 ++++ Cargo.toml | 1 + examples/mp4info.rs | 12 +- src/atoms.rs | 767 -------------------------------------------- src/atoms/edts.rs | 68 ++++ src/atoms/elst.rs | 104 ++++++ src/atoms/ftyp.rs | 97 ++++++ src/atoms/hdlr.rs | 79 +++++ src/atoms/mdhd.rs | 119 +++++++ src/atoms/mdia.rs | 90 ++++++ src/atoms/minf.rs | 85 +++++ src/atoms/mod.rs | 217 +++++++++++++ src/atoms/moov.rs | 73 +++++ src/atoms/mvhd.rs | 137 ++++++++ src/atoms/stbl.rs | 76 +++++ src/atoms/stsd.rs | 61 ++++ src/atoms/stts.rs | 72 +++++ src/atoms/tkhd.rs | 215 +++++++++++++ src/atoms/trak.rs | 90 ++++++ src/atoms/vmhd.rs | 69 ++++ src/error.rs | 9 + src/lib.rs | 42 +-- 22 files changed, 1747 insertions(+), 792 deletions(-) delete mode 100644 src/atoms.rs create mode 100644 src/atoms/edts.rs create mode 100644 src/atoms/elst.rs create mode 100644 src/atoms/ftyp.rs create mode 100644 src/atoms/hdlr.rs create mode 100644 src/atoms/mdhd.rs create mode 100644 src/atoms/mdia.rs create mode 100644 src/atoms/minf.rs create mode 100644 src/atoms/mod.rs create mode 100644 src/atoms/moov.rs create mode 100644 src/atoms/mvhd.rs create mode 100644 src/atoms/stbl.rs create mode 100644 src/atoms/stsd.rs create mode 100644 src/atoms/stts.rs create mode 100644 src/atoms/tkhd.rs create mode 100644 src/atoms/trak.rs create mode 100644 src/atoms/vmhd.rs create mode 100644 src/error.rs diff --git a/Cargo.lock b/Cargo.lock index acda5a2..0f7de29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,63 @@ name = "mp4" version = "0.4.3" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proc-macro2" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thiserror" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [metadata] "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +"checksum proc-macro2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" +"checksum quote 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +"checksum syn 1.0.36 (registry+https://github.com/rust-lang/crates.io-index)" = "4cdb98bcb1f9d81d07b536179c269ea15999b5d14ea958196413869445bb5250" +"checksum thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +"checksum thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +"checksum unicode-xid 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" diff --git a/Cargo.toml b/Cargo.toml index 9ae3654..711bcb6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,5 @@ keywords = ["mp4", "isobmff"] license = "MIT" [dependencies] +thiserror = "^1.0" byteorder = "1" diff --git a/examples/mp4info.rs b/examples/mp4info.rs index dc6b906..1269f29 100644 --- a/examples/mp4info.rs +++ b/examples/mp4info.rs @@ -58,7 +58,7 @@ fn main() { println!(" media:"); if let Some(ref s) = stts { - println!(" sample count: {:?}", s.sample_counts[0]); + println!(" sample count: {:?}", s.entries[0].sample_count); } println!(" timescale: {:?}", mdhd.timescale); println!( @@ -73,7 +73,7 @@ fn main() { if let Some(ref s) = stts { println!( " frame rate: (computed): {:?}", - get_framerate(&s.sample_counts, mdhd.duration, mdhd.timescale) + get_framerate(s.entries[0].sample_count, mdhd.duration, mdhd.timescale) ); } } @@ -97,18 +97,18 @@ fn get_handler_type(handler: &str) -> mp4::TrackType { return typ; } -fn get_duration_ms(duration: u32, timescale: u32) -> String { +fn get_duration_ms(duration: u64, timescale: u32) -> String { let ms = (duration as f64 / timescale as f64) * 1000.0; return format!("{:.2}", ms.floor()); } -fn get_framerate(sample_counts: &Vec, duration: u32, timescale: u32) -> String { - let sc = (sample_counts[0] as f64) * 1000.0; +fn get_framerate(sample_count: u32, duration: u64, timescale: u32) -> String { + let sc = (sample_count as f64) * 1000.0; let ms = (duration as f64 / timescale as f64) * 1000.0; return format!("{:.2}", sc / ms.floor()); } -fn creation_time(creation_time: u32) -> u32 { +fn creation_time(creation_time: u64) -> u64 { // convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01) if creation_time >= 2082844800 { creation_time - 2082844800 diff --git a/src/atoms.rs b/src/atoms.rs deleted file mode 100644 index bf35bca..0000000 --- a/src/atoms.rs +++ /dev/null @@ -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 = std::result::Result; - -macro_rules! boxtype { - ($( $name:ident => $value:expr ),*) => { - #[derive(Clone, Copy, PartialEq)] - pub enum BoxType { - $( $name, )* - UnknownBox(u32), - } - - impl From for BoxType { - fn from(t: u32) -> BoxType { - match t { - $( $value => BoxType::$name, )* - _ => BoxType::UnknownBox(t), - } - } - } - - impl Into 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 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 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: Sized { - fn read_box(_: T, size: u32) -> Result; -} - -#[derive(Debug, Default)] -pub struct FtypBox { - pub major_brand: FourCC, - pub minor_version: u32, - pub compatible_brands: Vec, -} - -#[derive(Debug, Default)] -pub struct MoovBox { - pub mvhd: MvhdBox, - pub traks: Vec, -} - -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, - pub edts: Option, - pub mdia: Option, -} - -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, -} - -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, -} - -#[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, - pub hdlr: Option, - pub minf: Option, -} - -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, - pub stbl: Option, -} - -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, - pub stsd: Option, -} - -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, - pub sample_deltas: Vec, -} - -#[derive(Debug, Default)] -pub struct StsdBox { - pub version: u8, - pub flags: u32, -} - -impl ReadBox<&mut BufReader> for FtypBox { - fn read_box(reader: &mut BufReader, size: u32) -> Result { - let major = reader.read_u32::().unwrap(); - let minor = reader.read_u32::().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::().unwrap(); - brands.push(From::from(b)); - } - - Ok(FtypBox { - major_brand: From::from(major), - minor_version: minor, - compatible_brands: brands, - }) - } -} - -impl ReadBox<&mut BufReader> for MoovBox { - fn read_box(reader: &mut BufReader, size: u32) -> Result { - 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 ReadBox<&mut BufReader> for MvhdBox { - fn read_box(reader: &mut BufReader, size: u32) -> Result { - let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position. - - read_box_header_ext!(reader, version, flags); - - let creation_time = reader.read_u32::().unwrap(); - let modification_time = reader.read_u32::().unwrap(); - let timescale = reader.read_u32::().unwrap(); - let duration = reader.read_u32::().unwrap(); - let rate = reader.read_u32::().unwrap(); - skip(reader, current, size); - - Ok(MvhdBox{ - version, - flags, - creation_time, - modification_time, - timescale, - duration, - rate, - }) - } -} - -impl ReadBox<&mut BufReader> for TrakBox { - fn read_box(reader: &mut BufReader, size: u32) -> Result { - 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 ReadBox<&mut BufReader> for TkhdBox { - fn read_box(reader: &mut BufReader, size: u32) -> Result { - let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position. - - read_box_header_ext!(reader, version, flags); - - let creation_time = reader.read_u32::().unwrap(); - let modification_time = reader.read_u32::().unwrap(); - let track_id = reader.read_u32::().unwrap(); - let duration = reader.read_u64::().unwrap(); - reader.read_u64::().unwrap(); // skip. - let layer = reader.read_u16::().unwrap(); - let alternate_group = reader.read_u16::().unwrap(); - let volume = reader.read_u16::().unwrap() >> 8; - - reader.read_u8().unwrap(); // skip. - let matrix = Matrix{ - a: reader.read_i32::().unwrap(), - b: reader.read_i32::().unwrap(), - u: reader.read_i32::().unwrap(), - c: reader.read_i32::().unwrap(), - d: reader.read_i32::().unwrap(), - v: reader.read_i32::().unwrap(), - x: reader.read_i32::().unwrap(), - y: reader.read_i32::().unwrap(), - w: reader.read_i32::().unwrap(), - }; - - let width = reader.read_u32::().unwrap() >> 8; - let height = reader.read_u32::().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 ReadBox<&mut BufReader> for EdtsBox { - fn read_box(reader: &mut BufReader, size: u32) -> Result { - 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 ReadBox<&mut BufReader> for ElstBox { - fn read_box(reader: &mut BufReader, size: u32) -> Result { - let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position. - - let version = reader.read_u32::().unwrap(); - let entry_count = reader.read_u32::().unwrap(); - - let mut entries = Vec::new(); - - for _i in 0..entry_count { - let entry = ElstEntry{ - segment_duration: reader.read_u32::().unwrap(), - media_time: reader.read_u32::().unwrap(), - media_rate: reader.read_u16::().unwrap(), - media_rate_fraction: reader.read_u16::().unwrap(), - }; - entries.push(entry); - } - skip(reader, current, size); - - Ok(ElstBox { - version, - entry_count, - entries, - }) - } -} - -impl ReadBox<&mut BufReader> for MdiaBox { - fn read_box(reader: &mut BufReader, size: u32) -> Result { - 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 ReadBox<&mut BufReader> for MdhdBox { - fn read_box(reader: &mut BufReader, size: u32) -> Result { - let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position. - - read_box_header_ext!(reader, version, flags); - - let creation_time = reader.read_u32::().unwrap(); - let modification_time = reader.read_u32::().unwrap(); - let timescale = reader.read_u32::().unwrap(); - let duration = reader.read_u32::().unwrap(); - let language = reader.read_u16::().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::(); - - return lang_str; -} - -impl ReadBox<&mut BufReader> for HdlrBox { - fn read_box(reader: &mut BufReader, size: u32) -> Result { - let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position. - - read_box_header_ext!(reader, version, flags); - - reader.read_u32::().unwrap(); // skip. - let handler = reader.read_u32::().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 ReadBox<&mut BufReader> for MinfBox { - fn read_box(reader: &mut BufReader, size: u32) -> Result { - 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 ReadBox<&mut BufReader> for VmhdBox { - fn read_box(reader: &mut BufReader, size: u32) -> Result { - let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position. - - read_box_header_ext!(reader, version, flags); - - let graphics_mode = reader.read_u16::().unwrap(); - let op_color = reader.read_u16::().unwrap(); - skip(reader, current, size); - - Ok(VmhdBox { - version, - flags, - graphics_mode, - op_color, - }) - } -} - -impl ReadBox<&mut BufReader> for StblBox { - fn read_box(reader: &mut BufReader, size: u32) -> Result { - 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 ReadBox<&mut BufReader> for SttsBox { - fn read_box(reader: &mut BufReader, size: u32) -> Result { - let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position. - - read_box_header_ext!(reader, version, flags); - - let entry_count = reader.read_u32::().unwrap(); - let mut sample_counts = Vec::new(); - let mut sample_deltas = Vec::new(); - - for _i in 0..entry_count { - let sc = reader.read_u32::().unwrap(); - let sd = reader.read_u32::().unwrap(); - sample_counts.push(sc); - sample_deltas.push(sd); - } - skip(reader, current, size); - - Ok(SttsBox { - version, - flags, - entry_count, - sample_counts, - sample_deltas, - }) - } -} - -impl ReadBox<&mut BufReader> for StsdBox { - fn read_box(reader: &mut BufReader, size: u32) -> Result { - let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position. - - read_box_header_ext!(reader, version, flags); - - reader.read_u32::().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(reader: &mut BufReader, 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(); -} diff --git a/src/atoms/edts.rs b/src/atoms/edts.rs new file mode 100644 index 0000000..193a3d4 --- /dev/null +++ b/src/atoms/edts.rs @@ -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, +} + +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 ReadBox<&mut BufReader> for EdtsBox { + fn read_box(reader: &mut BufReader, size: u64) -> Result { + 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 WriteBox<&mut BufWriter> for EdtsBox { + fn write_box(&self, writer: &mut BufWriter) -> Result { + 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) + } +} diff --git a/src/atoms/elst.rs b/src/atoms/elst.rs new file mode 100644 index 0000000..c120f18 --- /dev/null +++ b/src/atoms/elst.rs @@ -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, +} + +#[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 ReadBox<&mut BufReader> for ElstBox { + fn read_box(reader: &mut BufReader, size: u64) -> Result { + let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. + + let (version, flags) = read_box_header_ext(reader)?; + + let entry_count = reader.read_u32::()?; + 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::()?, + reader.read_u64::()?, + ) + } else { + ( + reader.read_u32::()? as u64, + reader.read_u32::()? as u64, + ) + }; + + let entry = ElstEntry{ + segment_duration, + media_time, + media_rate: reader.read_u16::()?, + media_rate_fraction: reader.read_u16::()?, + }; + entries.push(entry); + } + skip_read(reader, current, size)?; + + Ok(ElstBox { + version, + flags, + entry_count, + entries, + }) + } +} + +impl WriteBox<&mut BufWriter> for ElstBox { + fn write_box(&self, writer: &mut BufWriter) -> Result { + 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::(self.entry_count)?; + for entry in self.entries.iter() { + if self.version == 1 { + writer.write_u64::(entry.segment_duration)?; + writer.write_u64::(entry.media_time)?; + } else { + writer.write_u32::(entry.segment_duration as u32)?; + writer.write_u32::(entry.media_time as u32)?; + } + writer.write_u16::(entry.media_rate)?; + writer.write_u16::(entry.media_rate_fraction)?; + } + + Ok(size) + } +} diff --git a/src/atoms/ftyp.rs b/src/atoms/ftyp.rs new file mode 100644 index 0000000..53c83cc --- /dev/null +++ b/src/atoms/ftyp.rs @@ -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, +} + +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 ReadBox<&mut BufReader> for FtypBox { + fn read_box(reader: &mut BufReader, size: u64) -> Result { + let major = reader.read_u32::()?; + let minor = reader.read_u32::()?; + 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::()?; + brands.push(From::from(b)); + } + + Ok(FtypBox { + major_brand: From::from(major), + minor_version: minor, + compatible_brands: brands, + }) + } +} + +impl WriteBox<&mut BufWriter> for FtypBox { + fn write_box(&self, writer: &mut BufWriter) -> Result { + let size = self.box_size(); + BoxHeader::new(self.box_type(), size).write_box(writer)?; + + writer.write_u32::((&self.major_brand).into())?; + writer.write_u32::(self.minor_version)?; + for b in self.compatible_brands.iter() { + writer.write_u32::(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); + } + } +} diff --git a/src/atoms/hdlr.rs b/src/atoms/hdlr.rs new file mode 100644 index 0000000..be88301 --- /dev/null +++ b/src/atoms/hdlr.rs @@ -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 ReadBox<&mut BufReader> for HdlrBox { + fn read_box(reader: &mut BufReader, size: u64) -> Result { + let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. + + let (version, flags) = read_box_header_ext(reader)?; + + reader.read_u32::()?; // pre-defined + let handler = reader.read_u32::()?; + + 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 WriteBox<&mut BufWriter> for HdlrBox { + fn write_box(&self, writer: &mut BufWriter) -> Result { + 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::(0)?; // pre-defined + writer.write_u32::((&self.handler_type).into())?; + + // 12 bytes reserved + for _ in 0..3 { + writer.write_u32::(0)?; + } + + writer.write(self.name.as_bytes())?; + writer.write_u8(0)?; + + Ok(size) + } +} diff --git a/src/atoms/mdhd.rs b/src/atoms/mdhd.rs new file mode 100644 index 0000000..1c048fb --- /dev/null +++ b/src/atoms/mdhd.rs @@ -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 ReadBox<&mut BufReader> for MdhdBox { + fn read_box(reader: &mut BufReader, size: u64) -> Result { + 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::()?, + reader.read_u64::()?, + reader.read_u32::()?, + reader.read_u64::()?, + ) + } else { + assert_eq!(version, 0); + ( + reader.read_u32::()? as u64, + reader.read_u32::()? as u64, + reader.read_u32::()?, + reader.read_u32::()? as u64, + ) + }; + let language = reader.read_u16::()?; + 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 WriteBox<&mut BufWriter> for MdhdBox { + fn write_box(&self, writer: &mut BufWriter) -> Result { + 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::(self.creation_time)?; + writer.write_u64::(self.modification_time)?; + writer.write_u32::(self.timescale)?; + writer.write_u64::(self.duration)?; + } else { + assert_eq!(self.version, 0); + writer.write_u32::(self.creation_time as u32)?; + writer.write_u32::(self.modification_time as u32)?; + writer.write_u32::(self.timescale)?; + writer.write_u32::(self.duration as u32)?; + } + + writer.write_u16::(self.language)?; + writer.write_u16::(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::(); + + return lang_str; +} diff --git a/src/atoms/mdia.rs b/src/atoms/mdia.rs new file mode 100644 index 0000000..ed3b359 --- /dev/null +++ b/src/atoms/mdia.rs @@ -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, + pub hdlr: Option, + pub minf: Option, +} + +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 ReadBox<&mut BufReader> for MdiaBox { + fn read_box(reader: &mut BufReader, size: u64) -> Result { + 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 WriteBox<&mut BufWriter> for MdiaBox { + fn write_box(&self, writer: &mut BufWriter) -> Result { + 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) + } +} diff --git a/src/atoms/minf.rs b/src/atoms/minf.rs new file mode 100644 index 0000000..5df6356 --- /dev/null +++ b/src/atoms/minf.rs @@ -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, + pub stbl: Option, +} + +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 ReadBox<&mut BufReader> for MinfBox { + fn read_box(reader: &mut BufReader, size: u64) -> Result { + 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 WriteBox<&mut BufWriter> for MinfBox { + fn write_box(&self, writer: &mut BufWriter) -> Result { + 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) + } +} diff --git a/src/atoms/mod.rs b/src/atoms/mod.rs new file mode 100644 index 0000000..29e1f6a --- /dev/null +++ b/src/atoms/mod.rs @@ -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 for BoxType { + fn from(t: u32) -> BoxType { + match t { + $( $value => BoxType::$name, )* + _ => BoxType::UnknownBox(t), + } + } + } + + impl Into 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 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 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 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: Sized { + fn read_box(_: T, size: u64) -> Result; +} + +pub trait WriteBox: Sized { + fn write_box(&self, _: T) -> Result; +} + +pub fn read_box_header_ext(reader: &mut BufReader) -> 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: &mut BufWriter, v: u8, f: u32) -> Result { + let d = u32::from(v) << 24 | f; + w.write_u32::(d)?; + Ok(4) +} + +impl WriteBox<&mut BufWriter> for BoxHeader { + fn write_box(&self, writer: &mut BufWriter) -> Result { + if self.size > u32::MAX as u64 { + writer.write_u32::(1)?; + writer.write_u32::(self.name.into())?; + writer.write_u64::(self.size)?; + Ok(16) + } else { + writer.write_u32::(self.size as u32)?; + writer.write_u32::(self.name.into())?; + Ok(8) + } + } +} + +pub fn skip_read(reader: &mut BufReader, current: u64, size: u64) -> Result { + 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(writer: &mut BufWriter, size: u64) -> Result { + 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); + } +} diff --git a/src/atoms/moov.rs b/src/atoms/moov.rs new file mode 100644 index 0000000..3a5c045 --- /dev/null +++ b/src/atoms/moov.rs @@ -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, +} + +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 ReadBox<&mut BufReader> for MoovBox { + fn read_box(reader: &mut BufReader, size: u64) -> Result { + 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 WriteBox<&mut BufWriter> for MoovBox { + fn write_box(&self, writer: &mut BufWriter) -> Result { + 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) + } +} diff --git a/src/atoms/mvhd.rs b/src/atoms/mvhd.rs new file mode 100644 index 0000000..95f7bde --- /dev/null +++ b/src/atoms/mvhd.rs @@ -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 ReadBox<&mut BufReader> for MvhdBox { + fn read_box(reader: &mut BufReader, size: u64) -> Result { + 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::()?, + reader.read_u64::()?, + reader.read_u32::()?, + reader.read_u64::()?, + ) + } else { + assert_eq!(version, 0); + ( + reader.read_u32::()? as u64, + reader.read_u32::()? as u64, + reader.read_u32::()?, + reader.read_u32::()? as u64, + ) + }; + let rate = reader.read_u32::()?; + skip_read(reader, current, size)?; + + Ok(MvhdBox{ + version, + flags, + creation_time, + modification_time, + timescale, + duration, + rate, + }) + } +} + +impl WriteBox<&mut BufWriter> for MvhdBox { + fn write_box(&self, writer: &mut BufWriter) -> Result { + 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::(self.creation_time)?; + writer.write_u64::(self.modification_time)?; + writer.write_u32::(self.timescale)?; + writer.write_u64::(self.duration)?; + } else { + assert_eq!(self.version, 0); + writer.write_u32::(self.creation_time as u32)?; + writer.write_u32::(self.modification_time as u32)?; + writer.write_u32::(self.timescale)?; + writer.write_u32::(self.duration as u32)?; + } + writer.write_u32::(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); + } + } +} diff --git a/src/atoms/stbl.rs b/src/atoms/stbl.rs new file mode 100644 index 0000000..ab46bf8 --- /dev/null +++ b/src/atoms/stbl.rs @@ -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, + pub stsd: Option, +} + +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 ReadBox<&mut BufReader> for StblBox { + fn read_box(reader: &mut BufReader, size: u64) -> Result { + 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 WriteBox<&mut BufWriter> for StblBox { + fn write_box(&self, writer: &mut BufWriter) -> Result { + 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) + } +} diff --git a/src/atoms/stsd.rs b/src/atoms/stsd.rs new file mode 100644 index 0000000..e53c728 --- /dev/null +++ b/src/atoms/stsd.rs @@ -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 ReadBox<&mut BufReader> for StsdBox { + fn read_box(reader: &mut BufReader, size: u64) -> Result { + let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. + + let (version, flags) = read_box_header_ext(reader)?; + + let entry_count = reader.read_u32::()?; + + 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 WriteBox<&mut BufWriter> for StsdBox { + fn write_box(&self, _writer: &mut BufWriter) -> Result { + // TODO + Ok(0) + } +} diff --git a/src/atoms/stts.rs b/src/atoms/stts.rs new file mode 100644 index 0000000..3143e6e --- /dev/null +++ b/src/atoms/stts.rs @@ -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, +} + +#[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 ReadBox<&mut BufReader> for SttsBox { + fn read_box(reader: &mut BufReader, size: u64) -> Result { + let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. + + let (version, flags) = read_box_header_ext(reader)?; + + let entry_count = reader.read_u32::()?; + let mut entries = Vec::with_capacity(entry_count as usize); + for _i in 0..entry_count { + let entry = SttsEntry { + sample_count: reader.read_u32::()?, + sample_delta: reader.read_u32::()?, + }; + entries.push(entry); + } + skip_read(reader, current, size)?; + + Ok(SttsBox { + version, + flags, + entry_count, + entries, + }) + } +} + +impl WriteBox<&mut BufWriter> for SttsBox { + fn write_box(&self, writer: &mut BufWriter) -> Result { + 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::(self.entry_count)?; + for entry in self.entries.iter() { + writer.write_u32::(entry.sample_count)?; + writer.write_u32::(entry.sample_delta)?; + } + + Ok(size) + } +} diff --git a/src/atoms/tkhd.rs b/src/atoms/tkhd.rs new file mode 100644 index 0000000..c8cc3a0 --- /dev/null +++ b/src/atoms/tkhd.rs @@ -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 ReadBox<&mut BufReader> for TkhdBox { + fn read_box(reader: &mut BufReader, size: u64) -> Result { + 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::()?, + reader.read_u64::()?, + reader.read_u32::()?, + reader.read_u32::()?, + reader.read_u64::()?, + ) + } else { + assert_eq!(version, 0); + ( + reader.read_u32::()? as u64, + reader.read_u32::()? as u64, + reader.read_u32::()?, + reader.read_u32::()?, + reader.read_u32::()? as u64, + ) + }; + reader.read_u64::()?; // reserved + let layer = reader.read_u16::()?; + let alternate_group = reader.read_u16::()?; + let volume = reader.read_u16::()?; + + reader.read_u16::()?; // reserved + let matrix = Matrix{ + a: reader.read_i32::()?, + b: reader.read_i32::()?, + u: reader.read_i32::()?, + c: reader.read_i32::()?, + d: reader.read_i32::()?, + v: reader.read_i32::()?, + x: reader.read_i32::()?, + y: reader.read_i32::()?, + w: reader.read_i32::()?, + }; + + let width = reader.read_u32::()? >> 16; + let height = reader.read_u32::()? >> 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 WriteBox<&mut BufWriter> for TkhdBox { + fn write_box(&self, writer: &mut BufWriter) -> Result { + 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::(self.creation_time)?; + writer.write_u64::(self.modification_time)?; + writer.write_u32::(self.track_id)?; + writer.write_u32::(0)?; // reserved + writer.write_u64::(self.duration)?; + } else { + assert_eq!(self.version, 0); + writer.write_u32::(self.creation_time as u32)?; + writer.write_u32::(self.modification_time as u32)?; + writer.write_u32::(self.track_id)?; + writer.write_u32::(0)?; // reserved + writer.write_u32::(self.duration as u32)?; + } + + writer.write_u64::(0)?; // reserved + writer.write_u16::(self.layer)?; + writer.write_u16::(self.alternate_group)?; + writer.write_u16::(self.volume)?; + + writer.write_u16::(0)?; // reserved + + writer.write_i32::(self.matrix.a)?; + writer.write_i32::(self.matrix.b)?; + writer.write_i32::(self.matrix.u)?; + writer.write_i32::(self.matrix.c)?; + writer.write_i32::(self.matrix.d)?; + writer.write_i32::(self.matrix.v)?; + writer.write_i32::(self.matrix.x)?; + writer.write_i32::(self.matrix.y)?; + writer.write_i32::(self.matrix.w)?; + + writer.write_u32::(self.width << 16)?; + writer.write_u32::(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); + } + } +} diff --git a/src/atoms/trak.rs b/src/atoms/trak.rs new file mode 100644 index 0000000..3d4eac2 --- /dev/null +++ b/src/atoms/trak.rs @@ -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, + pub edts: Option, + pub mdia: Option, +} + +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 ReadBox<&mut BufReader> for TrakBox { + fn read_box(reader: &mut BufReader, size: u64) -> Result { + 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 WriteBox<&mut BufWriter> for TrakBox { + fn write_box(&self, writer: &mut BufWriter) -> Result { + 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) + } +} diff --git a/src/atoms/vmhd.rs b/src/atoms/vmhd.rs new file mode 100644 index 0000000..8e3a656 --- /dev/null +++ b/src/atoms/vmhd.rs @@ -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 ReadBox<&mut BufReader> for VmhdBox { + fn read_box(reader: &mut BufReader, size: u64) -> Result { + let current = reader.seek(SeekFrom::Current(0))?; // Current cursor position. + + let (version, flags) = read_box_header_ext(reader)?; + + let graphics_mode = reader.read_u16::()?; + let op_color = RgbColor { + red: reader.read_u16::()?, + green: reader.read_u16::()?, + blue: reader.read_u16::()?, + }; + skip_read(reader, current, size)?; + + Ok(VmhdBox { + version, + flags, + graphics_mode, + op_color, + }) + } +} + +impl WriteBox<&mut BufWriter> for VmhdBox { + fn write_box(&self, writer: &mut BufWriter) -> Result { + 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::(self.graphics_mode)?; + writer.write_u16::(self.op_color.red)?; + writer.write_u16::(self.op_color.green)?; + writer.write_u16::(self.op_color.blue)?; + + Ok(size) + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..8f38849 --- /dev/null +++ b/src/error.rs @@ -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), +} diff --git a/src/lib.rs b/src/lib.rs index 9af5436..309c1ab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,17 @@ -extern crate byteorder; - -use std::io::prelude::*; -use std::io::{BufReader, Read, SeekFrom}; +use std::io::{BufReader, Read, SeekFrom, Seek}; use std::fs::File; use std::convert::TryInto; mod atoms; use crate::atoms::*; -const HEADER_SIZE: u32 = 8; +mod error; +pub use error::Error; -#[derive(Debug)] -pub enum Error { - InvalidData(&'static str), -} +pub type Result = std::result::Result; + +// XXX if box has largesize +const HEADER_SIZE: u64 = 8; #[derive(Debug, PartialEq)] pub enum TrackType { @@ -42,16 +40,22 @@ struct BoxHeader { size: u64, } +impl BoxHeader { + fn new(name: BoxType, size: u64) -> Self { + Self { name, size } + } +} + pub fn read_mp4(f: File) -> Result { // Open file and read boxes. - let bmff = read_boxes(f).unwrap(); + let bmff = read_boxes(f)?; Ok(bmff) } fn read_boxes(f: File) -> Result { - let filesize = f.metadata().unwrap().len(); + let filesize = f.metadata()?.len(); let mut reader = BufReader::new(f); let mut bmff = BMFF::new(); bmff.size = filesize; @@ -60,27 +64,27 @@ fn read_boxes(f: File) -> Result { while start < filesize { // Get box header. - let header = read_box_header(&mut reader, start).unwrap(); + let header = read_box_header(&mut reader, start)?; let BoxHeader{ name, size } = header; // Match and parse the atom boxes. match name { BoxType::FtypBox => { - let ftyp = FtypBox::read_box(&mut reader, size as u32).unwrap(); + let ftyp = FtypBox::read_box(&mut reader, size)?; bmff.ftyp = ftyp; } BoxType::FreeBox => { start = 0; } BoxType::MdatBox => { - start = (size as u32 - HEADER_SIZE) as u64; + start = size - HEADER_SIZE; } BoxType::MoovBox => { - let moov = MoovBox::read_box(&mut reader, size as u32).unwrap(); + let moov = MoovBox::read_box(&mut reader, size)?; bmff.moov = Some(moov); } BoxType::MoofBox => { - start = (size as u32 - HEADER_SIZE) as u64; + start = size - HEADER_SIZE; } _ => { // Skip over unsupported boxes, but stop if the size is zero, @@ -88,7 +92,7 @@ fn read_boxes(f: File) -> Result { if size == 0 { break; } else { - start = (size as u32 - HEADER_SIZE) as u64; + start = size - HEADER_SIZE; } } }; @@ -103,7 +107,7 @@ fn read_box_header(reader: &mut BufReader, start: u64) -> Res // Create and read to buf. let mut buf = [0u8;8]; // 8 bytes for box header. - reader.read(&mut buf).unwrap(); + reader.read(&mut buf)?; // Get size. let s = buf[0..4].try_into().unwrap(); @@ -115,7 +119,7 @@ fn read_box_header(reader: &mut BufReader, start: u64) -> Res // Get largesize if size is 1 if size == 1 { - reader.read(&mut buf).unwrap(); + reader.read(&mut buf)?; let s = buf.try_into().unwrap(); let largesize = u64::from_be_bytes(s);