├── benches └── bench.rs ├── docs ├── mp4.pdf ├── 29n5245t.doc ├── ISO_IEC_14496-14_2003-11-15.pdf └── c061988_ISO_IEC_14496-12_2012.pdf ├── src ├── main.rs ├── atom │ ├── ignore.rs │ ├── unrecognized.rs │ ├── uuid.rs │ ├── mdat.rs │ ├── pdin.rs │ ├── freespace.rs │ ├── mfra.rs │ ├── meta.rs │ ├── meco.rs │ ├── ftyp.rs │ ├── kind.rs │ ├── moof.rs │ ├── mod.rs │ └── moov.rs └── lib.rs ├── examples └── parse.rs ├── tests └── test.rs ├── .gitignore ├── Cargo.toml ├── README.rst └── LICENSE /benches/bench.rs: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/mp4.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenAnsible/rust-mp4/HEAD/docs/mp4.pdf -------------------------------------------------------------------------------- /docs/29n5245t.doc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenAnsible/rust-mp4/HEAD/docs/29n5245t.doc -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | 2 | // mod lib; 3 | 4 | fn main(){ 5 | // lib::parse_file("test.mp4"); 6 | } -------------------------------------------------------------------------------- /examples/parse.rs: -------------------------------------------------------------------------------- 1 | extern crate mp4; 2 | 3 | fn main (){ 4 | mp4::parse_file("test.mp4"); 5 | } -------------------------------------------------------------------------------- /tests/test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | #[test] 4 | fn it_works() { 5 | } 6 | } -------------------------------------------------------------------------------- /docs/ISO_IEC_14496-14_2003-11-15.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenAnsible/rust-mp4/HEAD/docs/ISO_IEC_14496-14_2003-11-15.pdf -------------------------------------------------------------------------------- /docs/c061988_ISO_IEC_14496-12_2012.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OpenAnsible/rust-mp4/HEAD/docs/c061988_ISO_IEC_14496-12_2012.pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock 7 | Cargo.lock 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mp4" 3 | version = "0.1.0" 4 | authors = ["Luo "] 5 | 6 | [lib] 7 | name = "mp4" 8 | path = "src/lib.rs" 9 | 10 | # [[bin]] 11 | # name = "mp4" 12 | # path = "src/main.rs" 13 | 14 | [dependencies] 15 | byteorder = "0.5.3" 16 | -------------------------------------------------------------------------------- /src/atom/ignore.rs: -------------------------------------------------------------------------------- 1 | use super::{Mp4File, Kind, Header, Atom}; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct Ignore { 5 | header: Header 6 | } 7 | 8 | impl Ignore { 9 | pub fn parse(f: &mut Mp4File, header: Header) -> Result{ 10 | let curr_offset = f.offset(); 11 | f.seek(curr_offset+header.data_size); 12 | f.offset_inc(header.data_size); 13 | Ok(Ignore{ 14 | header: header, 15 | }) 16 | } 17 | } -------------------------------------------------------------------------------- /src/atom/unrecognized.rs: -------------------------------------------------------------------------------- 1 | 2 | use super::{Mp4File, Kind, Header, Atom}; 3 | 4 | #[derive(Debug, Clone)] 5 | pub struct Unrecognized { 6 | header: Header 7 | } 8 | 9 | impl Unrecognized { 10 | pub fn parse(f: &mut Mp4File, header: Header) -> Result{ 11 | let curr_offset = f.offset(); 12 | f.seek(curr_offset+header.data_size); 13 | f.offset_inc(header.data_size); 14 | Ok(Unrecognized{ 15 | header: header, 16 | }) 17 | } 18 | } -------------------------------------------------------------------------------- /src/atom/uuid.rs: -------------------------------------------------------------------------------- 1 | use super::{Mp4File, Kind, Header, Atom}; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct Uuid { 5 | header: Header 6 | } 7 | 8 | impl Uuid { 9 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 10 | header.parse_usertype(f); 11 | 12 | let curr_offset = f.offset(); 13 | f.seek(curr_offset+header.data_size); 14 | f.offset_inc(header.data_size); 15 | Ok(Uuid{ 16 | header: header 17 | }) 18 | } 19 | } -------------------------------------------------------------------------------- /src/atom/mdat.rs: -------------------------------------------------------------------------------- 1 | 2 | use super::{Mp4File, Kind, Header, Atom}; 3 | 4 | /** 5 | Box Type: ‘mdat’ 6 | Container: File 7 | Mandatory: No 8 | Quantity: Zero or more 9 | 10 | **/ 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct Mdat { 14 | header: Header 15 | 16 | } 17 | 18 | impl Mdat { 19 | pub fn parse(f: &mut Mp4File, header: Header) -> Result{ 20 | let curr_offset = f.offset(); 21 | f.seek(curr_offset+header.data_size); 22 | f.offset_inc(header.data_size); 23 | Ok(Mdat{ 24 | header: header 25 | }) 26 | } 27 | // pub fn read(&self, buf: &mut [u8]) -> Result{ 28 | 29 | // } 30 | // pub fn read_to_end(&self, buf: &mut Vec) -> Result{ 31 | 32 | // } 33 | } -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Rust MP4 编解码实现 2 | ======================= 3 | 4 | :Date: 12/14 2016 5 | 6 | .. contents:: 7 | 8 | 9 | 介绍 10 | ------ 11 | 12 | `MPEG-4` (MP4) 容器格式的 `Rust` 代码实现。 13 | 14 | 15 | 16 | 测试 17 | ------ 18 | 19 | .. code:: bash 20 | 21 | wget "http://az29176.vo.msecnd.net/videocontent/GrizzlyPeakSF_NimiaRM_135375_1080_HD_ZH-CN.mp4"\ 22 | -O "test_adobe.mp4" 23 | ffmpeg -i test_adobe.mp4 test.mp4 24 | cargo run --example parse 25 | 26 | 27 | 参考 28 | ------- 29 | 30 | * `MPEG-4 `_ 31 | * `ISO/IEC 14496-1:2010 `_ , Information technology -- Coding of audio-visual objects -- Part 1: Systems 32 | * `MP4 Ftyps `_ 33 | * `MP4 Atoms `_ 34 | * `QuickTime Container `_ 35 | * `Apple QuickTime `_ 36 | * `Adobe F4V `_ 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 寧靜 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/atom/pdin.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | use super::{Mp4File, Kind, Header, Atom}; 4 | 5 | /** 6 | BoxTypes : `pdin` 7 | Container: File 8 | Mandatory: No 9 | Quantity : Zero or One 10 | 11 | 8.1.3.2 Syntax 12 | 13 | aligned(8) class ProgressiveDownloadInfoBox extends FullBox(‘pdin’, version = 0, 0) { 14 | for(i=0;;i++){ //to end of box 15 | unsigned int(32) rate; 16 | unsigned int(32) initial_delay; 17 | } 18 | } 19 | 20 | 8.1.3.3 Semantics 21 | 22 | `rate` is a download rate expressed in bytes/second 23 | `initial_delay` is the suggested delay to use when playing the file, 24 | such that if download continues at the given rate, 25 | all data within the file will arrive in time for its use and playback should not need to stall. 26 | 27 | **/ 28 | 29 | #[derive(Debug, Clone)] 30 | pub struct Pdin { 31 | header: Header, 32 | rate: u32, 33 | initial_delay: u32 34 | } 35 | 36 | impl Pdin { 37 | pub fn parse(f: &mut Mp4File, header: Header) -> Result{ 38 | let curr_offset = f.offset(); 39 | let rate = f.read_u32().unwrap(); 40 | let initial_delay = f.read_u32().unwrap(); 41 | // f.seek(curr_offset+header.data_size); 42 | f.offset_inc(header.data_size); 43 | Ok(Pdin{ 44 | header: header, 45 | rate: rate, 46 | initial_delay: initial_delay 47 | }) 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /src/atom/freespace.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | use super::{Mp4File, Kind, Header, Atom}; 4 | 5 | /** 6 | 7 | skip 8 | udta 9 | cprt 10 | tsel 11 | strk 12 | stri 13 | strd 14 | 15 | BoxTypes : ‘free’,‘skip’ 16 | Container: File or other box 17 | Mandatory: No 18 | Quantity : Zero or more 19 | 20 | The contents of a free-space box are irrelevant and may be ignored, 21 | or the object deleted, without affecting the presentation. (Care should be exercised 22 | when deleting the object, as this may invalidate the offsets used in the sample table, 23 | unless this object is after all the media data). 24 | 25 | 8.1.2.2 Syntax 26 | 27 | aligned(8) class FreeSpaceBox extends Box(free_type) { 28 | unsigned int(8) data[]; 29 | } 30 | 31 | 8.1.2.3 Semantics 32 | 33 | `free_type` may be ‘free’ or ‘skip’. 34 | 35 | **/ 36 | 37 | #[derive(Debug, Clone)] 38 | pub struct Skip { 39 | header: Header 40 | } 41 | 42 | impl Skip { 43 | pub fn parse(f: &mut Mp4File, header: Header) -> Result{ 44 | let curr_offset = f.offset(); 45 | f.seek(curr_offset+header.data_size); 46 | f.offset_inc(header.data_size); 47 | Ok(Skip{ 48 | header: header, 49 | }) 50 | } 51 | } 52 | 53 | #[derive(Debug, Clone)] 54 | pub struct Free { 55 | header: Header 56 | } 57 | 58 | impl Free { 59 | pub fn parse(f: &mut Mp4File, header: Header) -> Result{ 60 | let curr_offset = f.offset(); 61 | f.seek(curr_offset+header.data_size); 62 | f.offset_inc(header.data_size); 63 | Ok(Free{ 64 | header: header, 65 | }) 66 | } 67 | } -------------------------------------------------------------------------------- /src/atom/mfra.rs: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | mfra 4 | tfra 5 | mfro 6 | 7 | **/ 8 | 9 | use super::{Mp4File, Kind, Header, Atom}; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct Mfra { 13 | header: Header, 14 | children: Vec 15 | } 16 | 17 | impl Mfra { 18 | pub fn parse(f: &mut Mp4File, header: Header) -> Result{ 19 | let children: Vec = Atom::parse_children(f); 20 | Ok(Mfra{ 21 | header: header, 22 | children: children 23 | }) 24 | } 25 | } 26 | 27 | /** 28 | aligned(8) class TrackFragmentRandomAccessBox extends FullBox(‘tfra’, version, 0) { 29 | unsigned int(32) track_ID; 30 | const unsigned int(26) reserved = 0; 31 | unsigned int(2) length_size_of_traf_num; 32 | unsigned int(2) length_size_of_trun_num; 33 | unsigned int(2) length_size_of_sample_num; 34 | unsigned int(32) number_of_entry; 35 | for(i=1; i <= number_of_entry; i++){ 36 | if(version==1){ 37 | unsigned int(64) time; 38 | unsigned int(64) moof_offset; 39 | }else{ 40 | unsigned int(32) time; 41 | unsigned int(32) moof_offset; 42 | } 43 | } 44 | unsigned int((length_size_of_traf_num+1)*8) traf_number; 45 | unsigned int((length_size_of_trun_num+1)*8) trun_number; 46 | unsigned int((length_size_of_sample_num+1) * 8) sample_number; 47 | 48 | `track_ID` is an integer identifying the track_ID. 49 | `length_size_of_traf_num` indicates the length in byte of the traf_number field minus one. 50 | `length_size_of_trun_num` indicates the length in byte of the trun_number field minus one. 51 | `length_size_of_sample_num` indicates the length in byte of the sample_number field minus one. 52 | `number_of_entry` is an integer that gives the number of the entries for this track. 53 | If this value is zero, it indicates that every sample is a sync sample and no table entry follows. 54 | `time` is 32 or 64 bits integer that indicates the presentation time of the sync sample in units defined in 55 | the ‘mdhd’ of the associated track. 56 | `moof_offset` is 32 or 64 bits integer that gives the offset of the ‘moof’ used in this entry. 57 | Offset is the byte-offset between the beginning of the file and the beginning of the ‘moof’. 58 | `traf_number` indicates the ‘traf’ number that contains the sync sample. The number ranges from 1 59 | (the first ‘traf’ is numbered 1) in each ‘moof’. 60 | `trun_number` indicates the ‘trun’ number that contains the sync sample. The number ranges from 1 in each ‘traf’. 61 | `sample_number` indicates the sample number of the sync sample. The number ranges from 1 in each ‘trun’. 62 | 63 | **/ 64 | 65 | #[derive(Debug, Clone)] 66 | pub struct Tfra { 67 | header: Header, 68 | sequence_number: u32 69 | } 70 | 71 | impl Tfra { 72 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 73 | header.parse_version(f); 74 | header.parse_flags(f); 75 | let curr_offset = f.offset(); 76 | // f.seek(curr_offset+header.data_size); 77 | let sequence_number: u32 = f.read_u32().unwrap(); 78 | f.offset_inc(header.data_size); 79 | Ok(Tfra{ 80 | header: header, 81 | sequence_number: sequence_number 82 | }) 83 | } 84 | } 85 | 86 | /** 87 | 8.8.11.1 Definition 88 | Box Type : ‘mfro’ 89 | Container: Movie Fragment Random Access Box (‘mfra’) 90 | Mandatory: Yes 91 | Quantity : Exactly one 92 | 93 | The Movie Fragment Random Access Offset Box provides a copy of the length 94 | field from the enclosing Movie Fragment Random Access Box. It is placed last 95 | within that box, so that the size field is also last in the enclosing 96 | Movie Fragment Random Access Box. When the Movie Fragment Random Access Box is 97 | also last in the file this permits its easy location. The size field here must be correct. 98 | However, neither the presence of the Movie Fragment Random Access Box, nor its placement 99 | last in the file, are assured. 100 | 101 | 8.8.11.2 Syntax 102 | 103 | aligned(8) class MovieFragmentRandomAccessOffsetBox extends FullBox(‘mfro’, version, 0) { 104 | unsigned int(32) size; 105 | } 106 | 107 | 8.8.11.3 Semantics 108 | `size` is an integer gives the number of bytes of the enclosing ‘mfra’ box. 109 | This field is placed at the last of the enclosing box to assist readers scanning 110 | from the end of the file in finding the ‘mfra’ box. 111 | 112 | **/ 113 | 114 | #[derive(Debug, Clone)] 115 | pub struct Mfro { 116 | header: Header, 117 | size : u32 118 | } 119 | 120 | impl Mfro { 121 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 122 | header.parse_version(f); 123 | header.parse_flags(f); 124 | // let curr_offset = f.offset(); 125 | // f.seek(curr_offset+header.data_size); 126 | let size: u32 = f.read_u32().unwrap(); 127 | f.offset_inc(header.data_size); 128 | Ok(Mfro{ 129 | header: header, 130 | size : size 131 | }) 132 | } 133 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code, unused_imports, unused_variables, unused_mut, non_camel_case_types)] 2 | #![allow(unused_must_use, unreachable_code, non_snake_case, unused_assignments, unused_parens)] 3 | 4 | extern crate byteorder; 5 | 6 | use std::fs; 7 | use std::fs::File; 8 | use std::io::{Write, Read, ErrorKind, Error, SeekFrom, Seek}; 9 | use byteorder::{BigEndian, ReadBytesExt}; 10 | 11 | pub mod atom; 12 | 13 | #[derive(Debug)] 14 | pub struct Mp4File { 15 | file : File, 16 | file_size: u64, 17 | offset : u64, 18 | atoms : Vec 19 | } 20 | 21 | #[derive(Debug, Clone)] 22 | pub struct Matrix { 23 | a: f64, 24 | b: f64, 25 | u: f64, 26 | c: f64, 27 | d: f64, 28 | v: f64, 29 | x: f64, 30 | y: f64, 31 | w: f64 32 | } 33 | 34 | impl Mp4File { 35 | pub fn new(filename: &str) -> Result { 36 | let mut file = fs::OpenOptions::new().read(true).write(false) 37 | .create(false).open(filename).unwrap(); 38 | let file_size = file.metadata().unwrap().len(); 39 | 40 | let mp4 = Mp4File { 41 | file: file, 42 | file_size: file_size, 43 | offset: 0, 44 | atoms: vec![] 45 | }; 46 | Ok(mp4) 47 | } 48 | pub fn file(&self) -> &File { 49 | &self.file 50 | } 51 | pub fn file_size(&self) -> u64 { 52 | self.file_size 53 | } 54 | pub fn offset(&self) -> u64 { 55 | self.offset 56 | } 57 | pub fn offset_inc(&mut self, num: u64) -> u64 { 58 | self.offset += num; 59 | self.offset 60 | } 61 | pub fn atoms(&self) -> &Vec { 62 | &self.atoms 63 | } 64 | pub fn parse(&mut self) { 65 | let atoms = atom::Atom::parse_children(self); 66 | self.atoms = atoms; 67 | } 68 | // File Seek 69 | pub fn seek(&mut self, offset: u64) -> Result { 70 | self.file.seek(SeekFrom::Start(offset)) 71 | } 72 | // Byte Reader 73 | pub fn read_u8(&mut self)-> Result { 74 | self.file.read_u8() 75 | } 76 | pub fn read_u16(&mut self)-> Result { 77 | self.file.read_u16::() 78 | } 79 | pub fn read_u32(&mut self)-> Result { 80 | self.file.read_u32::() 81 | } 82 | pub fn read_u64(&mut self)-> Result { 83 | self.file.read_u64::() 84 | } 85 | 86 | pub fn read_i8(&mut self)-> Result { 87 | self.file.read_i8() 88 | } 89 | pub fn read_i16(&mut self)-> Result { 90 | self.file.read_i16::() 91 | } 92 | pub fn read_i32(&mut self)-> Result { 93 | self.file.read_i32::() 94 | } 95 | pub fn read_i64(&mut self)-> Result { 96 | self.file.read_i64::() 97 | } 98 | 99 | pub fn read_f32(&mut self)-> Result { 100 | self.file.read_f32::() 101 | } 102 | pub fn read_f64(&mut self)-> Result { 103 | self.file.read_f64::() 104 | } 105 | pub fn read_fixed_point(&mut self, integerLength: usize, fractionalLength: usize) -> Result{ 106 | // https://en.wikipedia.org/wiki/Fixed_point_(mathematics) 107 | if integerLength + fractionalLength == 16 { 108 | let n = self.read_u16().unwrap(); 109 | let integer: u16 = n >> fractionalLength as u16; 110 | let fractional_mask: u16 = 2u16.pow(fractionalLength as u32) - 1; 111 | let fractional: u16 = (n&fractional_mask) / (1 << (fractionalLength as u16)); 112 | let result = (integer + fractional) as f64; 113 | Ok(result) 114 | } else { 115 | let n = self.read_u32().unwrap(); 116 | let integer: u32 = n >> fractionalLength as u32; 117 | let fractional_mask: u32 = 2u32.pow(fractionalLength as u32) - 1; 118 | let fractional: u32 = (n&fractional_mask) / (1 << (fractionalLength as u32)); 119 | let result = (integer + fractional) as f64; 120 | Ok(result) 121 | } 122 | } 123 | pub fn read_matrix(&mut self) -> Result{ 124 | // length: u32 * 9 ( 4*9 = 36 Bytes ) 125 | let a = self.read_fixed_point( 16, 16 ).unwrap(); 126 | let b = self.read_fixed_point( 16, 16 ).unwrap(); 127 | let u = self.read_fixed_point( 2, 30 ).unwrap(); 128 | let c = self.read_fixed_point( 16, 16 ).unwrap(); 129 | let d = self.read_fixed_point( 16, 16 ).unwrap(); 130 | let v = self.read_fixed_point( 2, 30 ).unwrap(); 131 | let x = self.read_fixed_point( 16, 16 ).unwrap(); 132 | let y = self.read_fixed_point( 16, 16 ).unwrap(); 133 | let w = self.read_fixed_point( 2, 30 ).unwrap(); 134 | Ok(Matrix { 135 | a: a, b: b, u: u, 136 | c: c, d: d, v: v, 137 | x: x, y: y, w: w 138 | }) 139 | } 140 | pub fn read_iso639_code(&mut self) -> Result { 141 | // Note: 142 | // pad : 1 Bit 143 | // string: 15 Bit 144 | let mut s = String::new(); 145 | let n = self.read_u16().unwrap(); 146 | let mut c1 = ( n & 0x7C00 ) >> 10; // Mask is 0111 1100 0000 0000 147 | let mut c2 = ( n & 0x03E0 ) >> 5; // Mask is 0000 0011 1110 0000 148 | let mut c3 = ( n & 0x001F ); // Mask is 0000 0000 0001 1111 149 | c1 += 0x60; 150 | c2 += 0x60; 151 | c3 += 0x60; 152 | 153 | s.push((c1 as u8) as char); 154 | s.push((c2 as u8) as char); 155 | s.push((c3 as u8) as char); 156 | Ok(s) 157 | } 158 | } 159 | 160 | pub fn parse_file(filename: &str) -> Result{ 161 | let mut mp4 = Mp4File::new(filename).unwrap(); 162 | mp4.parse(); 163 | for atom in mp4.atoms() { 164 | println!("Atom: \n\t{:?}", atom); 165 | } 166 | Ok(mp4) 167 | } 168 | 169 | -------------------------------------------------------------------------------- /src/atom/meta.rs: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | meta 4 | hdlr 5 | dinf 6 | dref 7 | ipmc 8 | iloc 9 | ipro 10 | sinf 11 | frma 12 | imif 13 | schm 14 | schi 15 | iinf 16 | xml 17 | bxml 18 | pitm 19 | fiin 20 | paen 21 | fpar 22 | fecr 23 | segr 24 | gitn 25 | tsel 26 | 27 | **/ 28 | 29 | use std::string::String; 30 | use super::{Mp4File, Kind, Header, Atom}; 31 | 32 | /** 33 | 8.11.1 The Meta box 34 | 8.11.1.1 Definition 35 | 36 | Box Type : `meta` 37 | Container: File, Movie Box (‘moov’), Track Box (‘trak’), or Additional Metadata Container Box (‘meco’) 38 | Mandatory: No 39 | Quantity : Zero or one (in File, ‘moov’, and ‘trak’), One or more (in ‘meco’) 40 | 41 | A meta box contains descriptive or annotative metadata. The 'meta' box is required to contain a ‘hdlr’ 42 | box indicating the structure or format of the ‘meta’ box contents. 43 | That metadata is located either within a box within this box (e.g. an XML box), 44 | or is located by the item identified by a primary item box. 45 | 46 | All other contained boxes are specific to the format specified by the handler box. 47 | The other boxes defined here may be defined as optional or mandatory for a given format. 48 | If they are used, then they must take the form specified here. These optional boxes include 49 | a data-information box, which documents other files in which metadata values (e.g. pictures) are placed, and a item location box, which documents where in those files each item is located (e.g. in the common case of multiple pictures stored in the same file). At most one meta box may occur at each of the file level, movie level, or track level, unless they are contained in an additional metadata container box (‘meco’). 50 | If an Item Protection Box occurs, then some or all of the meta-data, including possibly the primary resource, may have been protected and be un-readable unless the protection system is taken into account. 51 | 52 | 8.11.1.2 Syntax 53 | aligned(8) class MetaBox (handler_type) extends FullBox(‘meta’, version = 0, 0) { 54 | HandlerBox(handler_type) theHandler; 55 | PrimaryItemBox primary_resource; // optional 56 | DataInformationBox file_locations; // optional 57 | ItemLocationBox item_locations; // optional 58 | ItemProtectionBox protections; // optional 59 | ItemInfoBox item_infos; // optional 60 | IPMPControlBox IPMP_control; // optional 61 | ItemReferenceBox item_refs; // optional 62 | ItemDataBox item_data; // optional 63 | Box other_boxes[]; 64 | } 65 | 66 | 8.11.1.3 Semantics 67 | 68 | The structure or format of the metadata is declared by the handler. 69 | In the case that the primary data is identified by a primary item, 70 | and that primary item has an item information entry with an item_type, 71 | the handler type may be the same as the item_type. 72 | 73 | **/ 74 | 75 | #[derive(Debug, Clone)] 76 | pub struct Meta { 77 | header: Header 78 | } 79 | 80 | impl Meta { 81 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 82 | header.parse_version(f); 83 | header.parse_flags(f); 84 | let curr_offset = f.offset(); 85 | f.seek(curr_offset+header.data_size); 86 | f.offset_inc(header.data_size); 87 | Ok(Meta{ 88 | header: header 89 | }) 90 | } 91 | } 92 | 93 | /** 94 | 95 | 8.11.2 XML Boxes 96 | 8.11.2.1 Definition 97 | 98 | Box Type : `xml ` or `bxml` 99 | Container: Meta box (`meta`) 100 | Mandatory: No 101 | Quantity : Zero or one 102 | 103 | When the primary data is in XML format and it is desired that the XML be stored directly 104 | in the meta-box, one of these forms may be used. The Binary XML Box may only be used 105 | when there is a single well-defined binarization of the XML for that defined format as 106 | identified by the handler. 107 | 108 | Within an XML box the data is in UTF-8 format unless the data starts with 109 | a byte-order-mark (BOM), which indicates that the data is in UTF-16 format. 110 | 111 | 8.11.2.2 Syntax 112 | 113 | aligned(8) class XMLBox extends FullBox(‘xml ’, version = 0, 0) { 114 | string xml; 115 | } 116 | 117 | aligned(8) class BinaryXMLBox extends FullBox(‘bxml’, version = 0, 0) { 118 | unsigned int(8) data[]; // to end of box 119 | } 120 | 121 | **/ 122 | 123 | #[derive(Debug, Clone)] 124 | pub struct Xml { 125 | header: Header, 126 | xml : String 127 | } 128 | 129 | impl Xml { 130 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 131 | header.parse_version(f); 132 | header.parse_flags(f); 133 | 134 | // let curr_offset = f.offset(); 135 | // f.seek(curr_offset+header.data_size); 136 | let mut xml_bytes: Vec = Vec::new(); 137 | for _ in 0..header.data_size { 138 | xml_bytes.push(f.read_u8().unwrap()); 139 | } 140 | let xml: String = String::from_utf8(xml_bytes).unwrap(); 141 | 142 | f.offset_inc(header.data_size); 143 | Ok(Xml{ 144 | header: header, 145 | xml : xml 146 | }) 147 | } 148 | } 149 | 150 | #[derive(Debug, Clone)] 151 | pub struct Bxml { 152 | header: Header, 153 | data : Vec 154 | } 155 | 156 | impl Bxml { 157 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 158 | header.parse_version(f); 159 | header.parse_flags(f); 160 | 161 | // let curr_offset = f.offset(); 162 | // f.seek(curr_offset+header.data_size); 163 | let mut data: Vec = Vec::new(); 164 | for _ in 0..header.data_size { 165 | data.push(f.read_u8().unwrap()); 166 | } 167 | // let xml: String = String::from_utf8(xml_bytes).unwrap(); 168 | 169 | f.offset_inc(header.data_size); 170 | Ok(Bxml{ 171 | header: header, 172 | data : data 173 | }) 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/atom/meco.rs: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | meco 4 | mere 5 | 6 | **/ 7 | 8 | use std::string::String; 9 | use super::{Mp4File, Kind, Header, Atom}; 10 | 11 | /** 12 | 13 | 8.11.7 Additional Metadata Container Box 14 | 8.11.7.1 Definition 15 | 16 | Box Type : `meco` 17 | Container: File, Movie Box (‘moov’), or Track Box (‘trak’) 18 | Mandatory: No 19 | Quantity : Zero or one 20 | 21 | 8.11.7.2 Syntax 22 | 23 | aligned(8) class AdditionalMetadataContainerBox extends Box('meco') { 24 | 25 | } 26 | **/ 27 | 28 | #[derive(Debug, Clone)] 29 | pub struct Meco { 30 | header: Header, 31 | children : Vec 32 | 33 | } 34 | 35 | impl Meco { 36 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 37 | let children: Vec = Atom::parse_children(f); 38 | Ok(Meco{ 39 | header: header, 40 | children: children 41 | }) 42 | } 43 | } 44 | 45 | /** 46 | 47 | 8.11.8 Metabox Relation Box 48 | 8.11.8.1 Definition 49 | 50 | Box Type : ‘mere’ 51 | Container: Additional Metadata Container Box (‘meco’) 52 | Mandatory: No 53 | Quantity : Zero or more 54 | 55 | The metabox relation box indicates a relation between two meta boxes at the same level, 56 | i.e., the top level of the file, the Movie Box, or Track Box. The relation between 57 | two meta boxes is unspecified if there is no metabox relation box for those meta boxes. 58 | Meta boxes are referenced by specifying their handler types. 59 | 60 | 8.11.8.2 Syntax 61 | 62 | aligned(8) class MetaboxRelationBox extends FullBox('mere', version=0, 0) { 63 | unsigned int(32) first_metabox_handler_type; 64 | unsigned int(32) second_metabox_handler_type; 65 | unsigned int(8) metabox_relation; 66 | } 67 | 68 | 8.11.8.3 Semantics 69 | 70 | `first_metabox_handler_type` indicates the first meta box to be related. 71 | `second_metabox_handler_type` indicates the second meta box to be related. 72 | `metabox_relation` indicates the relation between the two meta boxes. The following values are defined: 73 | 1 The relationship between the boxes is unknown (which is the default when this box is not present); 74 | 2 the two boxes are semantically un-related (e.g., one is presentation, the other annotation); 75 | 3 the two boxes are semantically related but complementary (e.g., two disjoint sets of meta-data expressed in two different meta-data systems); 76 | 4 the two boxes are semantically related but overlap (e.g., two sets of meta-data neither of which is a subset of the other); neither is ‘preferred’ to the other; 77 | 5 the two boxes are semantically related but the second is a proper subset or weaker version of the first; the first is preferred; 78 | 6 the two boxes are semantically related and equivalent (e.g., two essentially identical sets of meta-data expressed in two different meta-data systems). 79 | 80 | 8.11.9 URL Forms for meta boxes 81 | 82 | When a meta-box is used, then URLs may be used to refer to items in the meta-box, 83 | either using an absolute URL, or using a relative URL. Absolute URLs may only be used 84 | to refer to items in a file-level meta box. 85 | 86 | When interpreting data that is in the context of a meta-box (i.e. the file for a file-level meta-box, 87 | the presentation for a movie-level meta-box, or the track for a track-level meta-box), 88 | the items in the meta-box are treated as shadowing files in the same location as that from which 89 | the container file came. This shadowing means that a reference to another file in the same location 90 | as the container file may be resolved to an item within the container file itself. Items can be 91 | addressed within the container file by appending a fragment to the URL for the container file itself. 92 | That fragment starts with the “#” character and consists of either: 93 | 94 | a) item_ID=, identifying the item by its ID (the ID may be 0 for the primary resource); 95 | b) item_name=, when the item information box is used. 96 | 97 | If a fragment within the contained item must be addressed, then the initial “#” character of 98 | that fragment is replaced by “*”. 99 | 100 | Consider the following example: . 101 | We assume that v.qrv is a file with a meta-box at the file level. First, the client strips the 102 | fragment and fetches v.qrv from a.com using HTTP. It then inspects the top-level meta box and 103 | adds the items in it, logically, to its cache of the directory “d” on a.com. It then re-forms 104 | the URL as . Note that the fragment has been elevated to a 105 | full file name, and the first “*” has been transformed back into a “#”. The client then either 106 | finds an item named tree.html in the meta box, or fetches tree.html from a.com, and it then 107 | finds the anchor “branch1” within tree.html. If within that html, a file was referenced 108 | using a relative URL, e.g. “flower.gif”, then the client converts this to an absolute URL 109 | using the normal rules: and again it checks to see if flower.gif is 110 | a named item (and hence shadowing a separate file of this name), and then if it is not, fetches flower.gif 111 | from a.com. 112 | 113 | 8.11.10 Static Metadata 114 | 115 | This section defines the storage of static (un-timed) metadata in the ISO file format family. 116 | Reader support for metadata in general is optional, and therefore it is also optional for 117 | the formats defined here or elsewhere, unless made mandatory by a derived specification. 118 | 119 | 8.11.10.1 Simple textual 120 | 121 | There is existing support for simple textual tags in the form of the user-data boxes; 122 | currently only one is defined – the copyright notice. Other metadata is permitted using this simple form if: 123 | a) it uses a registered box-type or it uses the UUID escape (the latter is permitted today); 124 | b) it uses a registered tag, the equivalent MPEG-7 construct must be documented as part of the registration. 125 | 126 | 8.11.10.2 Other forms 127 | 128 | When other forms of metadata are desired, then a ‘meta’ box as defined above may be 129 | included at the appropriate level of the document. If the document is intended to 130 | be primarily a metadata document per se, then the meta box is at file level. 131 | If the metadata annotates an entire presentation, then the meta box is at the movie level; 132 | an entire stream, at the track level. 133 | 134 | 8.11.10.3 MPEG-7 metadata 135 | 136 | MPEG-7 metadata is stored in meta boxes to this specification. 137 | 138 | 1) The handler-type is ‘mp7t’ for textual metadata in Unicode format; 139 | 2) The handler-type is ‘mp7b’ for binary metadata compressed in the BIM format. 140 | In this case, the binary XML box contains the configuration information 141 | immediately followed by the binarized XML. 142 | 3) When the format is textual, there is either another box in the metadata container ‘meta’, 143 | called ‘xml’, which contains the textual MPEG-7 document, or there is a primary item box 144 | identifying the item containing the MPEG-7 XML. 145 | 4) When the format is binary, there is either another box in the metadata container ‘meta’, 146 | called ‘bxml’, which contains the binary MPEG-7 document, or a primary item box identifying 147 | the item containing the MPEG-7 binarized XML. 148 | 5) If an MPEG-7 box is used at the file level, then the brand ‘mp71’ should be a member of 149 | the compatible-brands list in the file-type box. 150 | 151 | **/ 152 | 153 | #[derive(Debug, Clone)] 154 | pub struct Mere { 155 | header: Header, 156 | first_metabox_handler_type : u32, 157 | second_metabox_handler_type: u32, 158 | metabox_relation : u8 159 | } 160 | 161 | impl Mere { 162 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 163 | header.parse_version(f); 164 | header.parse_flags(f); 165 | 166 | // let curr_offset = f.offset(); 167 | // f.seek(curr_offset+header.data_size); 168 | let first_metabox_handler_type = f.read_u32().unwrap(); 169 | let second_metabox_handler_type = f.read_u32().unwrap(); 170 | let metabox_relation = f.read_u8().unwrap(); 171 | 172 | f.offset_inc(header.data_size); 173 | Ok(Mere{ 174 | header: header, 175 | first_metabox_handler_type: first_metabox_handler_type, 176 | second_metabox_handler_type: second_metabox_handler_type, 177 | metabox_relation: metabox_relation 178 | }) 179 | } 180 | } -------------------------------------------------------------------------------- /src/atom/ftyp.rs: -------------------------------------------------------------------------------- 1 | 2 | // Complete List of all known MP4 / QuickTime 'ftyp' designations: 3 | // http://www.fileType.com 4 | 5 | /** 6 | avc1 MP4 Base w/ AVC ext [ISO 14496-12:2005] ISO YES video/mp4 [11] 7 | iso2 MP4 Base Media v2 [ISO 14496-12:2005] ISO YES video/mp4 [6] 8 | isom MP4 Base Media v1 [IS0 14496-12:2003] ISO YES video/mp4 [5] 9 | mp21 MPEG-21 [ISO/IEC 21000-9] ISO YES various 10 | mp41 MP4 v1 [ISO 14496-1:ch13] ISO YES video/mp4 11 | mp42 MP4 v2 [ISO 14496-14] ISO YES video/mp4 12 | 13 | qt Apple QuickTime (.MOV/QT) Apple YES video/quicktime 14 | M4B Apple iTunes AAC-LC (.M4B) Audio Book Apple YES audio/mp4 [9] 15 | M4P Apple iTunes AAC-LC (.M4P) AES Protected Audio Apple YES audio/mp4 [9] 16 | M4A Apple iTunes AAC-LC (.M4A) Audio Apple YES audio/x-m4a [9] 17 | M4V Apple iTunes Video (.M4V) Video Apple YES video/x-m4v [9] 18 | M4VH Apple TV (.M4V) Apple NO video/x-m4v 19 | M4VP Apple iPhone (.M4V) Apple NO video/x-m4v 20 | 21 | F4V Video for Adobe Flash Player 9+ (.F4V) Adobe NO video/mp4 22 | F4P Protected Video for Adobe Flash Player 9+ (.F4P) Adobe NO video/mp4 23 | F4A Audio for Adobe Flash Player 9+ (.F4A) Adobe NO audio/mp4 24 | F4B Audio Book for Adobe Flash Player 9+ (.F4B) Adobe NO audio/mp4 25 | 26 | mmp4 MPEG-4/3GPP Mobile Profile(.MP4/.3GP)(for NTT) 3GPP/GSM NO video/mp4 27 | 28 | All ftyp's must contain 4 characters. 29 | If three characters are shown in the table, 30 | a trailing blank (i.e. a space character; ASCII 0x20) is implied. 31 | Similarly, if only two characters are shown, 32 | two trailing blanks are implied. 33 | For example, "qt" is really "qt " - note the two trailing spaces 34 | **/ 35 | 36 | use std::str; 37 | use std::str::FromStr; 38 | use std::string::ToString; 39 | use std::fs::File; 40 | 41 | use super::{Mp4File, Kind, Header, Atom}; 42 | 43 | #[derive(Debug, Clone)] 44 | pub enum FileType{ 45 | // ISO 46 | avc1, 47 | iso2, 48 | isom, 49 | mp21, 50 | mp41, 51 | mp42, 52 | // Apple 53 | qt, 54 | M4B, 55 | M4P, 56 | M4A, 57 | M4V, 58 | M4VH, 59 | M4VP, 60 | // Adobe 61 | F4V, 62 | F4P, 63 | F4A, 64 | F4B, 65 | // 3GPP/GSM 66 | mmp4 67 | } 68 | 69 | impl FromStr for FileType { 70 | type Err = &'static str; 71 | fn from_str(s: &str) -> Result{ 72 | match s { 73 | "avc1" => Ok(FileType::avc1), 74 | "iso2" => Ok(FileType::iso2), 75 | "isom" => Ok(FileType::isom), 76 | "mp21" => Ok(FileType::mp21), 77 | "mp41" => Ok(FileType::mp41), 78 | "mp42" => Ok(FileType::mp42), 79 | "qt" | "qt\u{0}\u{0}" => Ok(FileType::qt), 80 | "M4B" | "M4B\u{0}" => Ok(FileType::M4B), 81 | "M4P" | "M4P\u{0}" => Ok(FileType::M4P), 82 | "M4A" | "M4A\u{0}" => Ok(FileType::M4A), 83 | "M4V" | "M4V\u{0}" => Ok(FileType::M4V), 84 | "M4VH" => Ok(FileType::M4VH), 85 | "M4VP" => Ok(FileType::M4VP), 86 | "F4V" | "F4V\u{0}" => Ok(FileType::F4V), 87 | "F4P" | "F4P\u{0}" => Ok(FileType::F4P), 88 | "F4A" | "F4A\u{0}" => Ok(FileType::F4A), 89 | "F4B" | "F4B\u{0}" => Ok(FileType::F4B), 90 | "mmp4" => Ok(FileType::mmp4), 91 | _ => Err("unknow fileType") 92 | } 93 | } 94 | } 95 | 96 | impl ToString for FileType { 97 | fn to_string(&self) -> String { 98 | match *self { 99 | FileType::avc1 => "avc1".to_owned(), 100 | FileType::iso2 => "iso2".to_owned(), 101 | FileType::isom => "isom".to_owned(), 102 | FileType::mp21 => "mp21".to_owned(), 103 | FileType::mp41 => "mp41".to_owned(), 104 | FileType::mp42 => "mp42".to_owned(), 105 | FileType::qt => "qt\u{0}\u{0}".to_owned(), 106 | FileType::M4B => "M4B\u{0}".to_owned(), 107 | FileType::M4P => "M4P\u{0}".to_owned(), 108 | FileType::M4A => "M4A\u{0}".to_owned(), 109 | FileType::M4V => "M4V\u{0}".to_owned(), 110 | FileType::M4VH => "M4VH".to_owned(), 111 | FileType::M4VP => "M4VP".to_owned(), 112 | FileType::F4V => "F4V\u{0}".to_owned(), 113 | FileType::F4P => "F4P\u{0}".to_owned(), 114 | FileType::F4A => "F4A\u{0}".to_owned(), 115 | FileType::F4B => "F4B\u{0}".to_owned(), 116 | FileType::mmp4 => "mmp4".to_owned() 117 | } 118 | } 119 | } 120 | 121 | impl FileType { 122 | pub fn from_bytes(bytes: &[u8; 4]) -> Result { 123 | let kind_str = match str::from_utf8(bytes) { 124 | Ok(s) => s, 125 | Err(_) => { 126 | println!("ftyp ({:?}) parse error.", bytes); 127 | return Err("ftyp parse error."); 128 | } 129 | }; 130 | FileType::from_str(kind_str) 131 | } 132 | pub fn into_bytes(&self) -> Vec { 133 | self.to_string().into_bytes() 134 | } 135 | } 136 | 137 | /** 138 | 139 | Box Type: `ftyp’ 140 | Container: File 141 | Mandatory: Yes 142 | Quantity: Exactly one (but see below) 143 | 144 | Files written to this version of this specification must contain a file-type box. 145 | For compatibility with an earlier version of this specification, 146 | files may be conformant to this specification and not contain a file-type box. 147 | Files with no file-type box should be read as 148 | if they contained an FTYP box with Major_brand='mp41', minor_version=0, 149 | and the single compatible brand 'mp41'. 150 | 151 | A media-file structured to this part of this specification may be compatible with 152 | more than one detailed specification, and it is therefore not always possible to 153 | speak of a single ‘type’ or ‘brand’ for the file. This means that the utility of 154 | the file name extension and Multipurpose Internet Mail Extension (MIME) type are somewhat reduced. 155 | 156 | This box must be placed as early as possible in the file (e.g. after any obligatory signature, 157 | but before any significant variable-size boxes such as a Movie Box, Media Data Box, or Free Space). 158 | It identifies which specification is the ‘best use’ of the file, and a minor version of that specification; 159 | and also a set of other specifications to which the file complies. Readers implementing this format 160 | should attempt to read files that are marked as compatible with any of the specifications that 161 | the reader implements. Any incompatible change in a specification should therefore register 162 | a new ‘brand’ identifier to identify files conformant to the new specification. 163 | 164 | The minor version is informative only. It does not appear for compatible-brands, 165 | and must not be used to determine the conformance of a file to a standard. It may allow 166 | more precise identification of the major specification, for inspection, debugging, or improved decoding. 167 | 168 | Files would normally be externally identified (e.g. with a file extension or mime type) 169 | that identifies the ‘best use’ (major brand), or the brand that the author believes will 170 | provide the greatest compatibility. 171 | 172 | This section of this specification does not define any brands. However, 173 | see subclause 6.3 below for brands for files conformant to the whole specification and 174 | not just this section. All file format brands defined in this specification are included 175 | in Annex E with a summary of which features they require. 176 | 177 | 178 | 4.3.2 Syntax 179 | 180 | aligned(8) class FileTypeBox 181 | extends Box(‘ftyp’) { 182 | unsigned int(32) major_brand; 183 | unsigned int(32) minor_version; 184 | unsigned int(32) compatible_brands[]; // to end of the box 185 | } 186 | 187 | 4.3.3 Semantics 188 | 189 | This box identifies the specifications to which this file complies. 190 | Each brand is a printable four-character code, registered with ISO, 191 | that identifies a precise specification. 192 | 193 | `major_brand` – is a brand identifier 194 | `minor_version` – is an informative integer for the minor version of the major brand 195 | `compatible_brands` – is a list, to the end of the box, of brands 196 | 197 | **/ 198 | 199 | #[derive(Debug, Clone)] 200 | pub struct Ftyp { 201 | header: Header, 202 | major_brand : FileType, 203 | minor_version: u32, 204 | compatible_brands: Vec 205 | } 206 | 207 | impl Ftyp { 208 | fn parse_filetype(f: &mut Mp4File) -> Result{ 209 | let ft_bytes: [u8; 4] = [ 210 | f.read_u8().unwrap(), f.read_u8().unwrap(), 211 | f.read_u8().unwrap(), f.read_u8().unwrap() 212 | ]; 213 | FileType::from_bytes(&ft_bytes) 214 | } 215 | pub fn parse(f: &mut Mp4File, header: Header) -> Result{ 216 | let major_brand = Ftyp::parse_filetype(f).unwrap(); 217 | let minor_version = f.read_u32().unwrap(); 218 | let mut compatible_brands: Vec = Vec::new(); 219 | let mut idx = (header.data_size - 8) / 4; 220 | while idx > 0 { 221 | compatible_brands.push(Ftyp::parse_filetype(f).unwrap()); 222 | idx -= 1; 223 | } 224 | f.offset_inc(header.data_size); 225 | Ok(Ftyp{ 226 | header: header, 227 | major_brand: major_brand, 228 | minor_version: minor_version, 229 | compatible_brands: compatible_brands 230 | }) 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/atom/kind.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::str; 3 | use std::str::FromStr; 4 | use std::string::ToString; 5 | /** 6 | Atom Types: 7 | Atoms: 8 | 9 | ftyp 10 | pdin 11 | moov 12 | mvhd 13 | trak 14 | tkhd 15 | mdia 16 | mdhd 17 | hdlr 18 | minf 19 | stbl 20 | stsd 21 | stts 22 | stsc 23 | stsz 24 | stz2 25 | stss 26 | stco 27 | co64 28 | 29 | ctts 30 | stsh 31 | padb 32 | stdp 33 | sdtp 34 | sbgp 35 | sgpd 36 | subs 37 | dinf 38 | dref 39 | nmhd 40 | hmhd 41 | smhd 42 | vmhd 43 | tref 44 | edts 45 | elst 46 | mvex 47 | mehd 48 | trex 49 | ipmc 50 | moof 51 | mfhd 52 | traf 53 | tfhd 54 | trun 55 | sdtp 56 | sbgp 57 | subs 58 | mfra 59 | tfra 60 | mfro 61 | mdat 62 | free 63 | skip 64 | udta 65 | cprt 66 | tsel 67 | strk 68 | stri 69 | strd 70 | meta 71 | hdlr 72 | dinf 73 | dref 74 | ipmc 75 | iloc 76 | ipro 77 | sinf 78 | frma 79 | imif 80 | schm 81 | schi 82 | iinf 83 | xml 84 | bxml 85 | pitm 86 | fiin 87 | paen 88 | fpar 89 | fecr 90 | segr 91 | gitn 92 | tsel 93 | meco 94 | mere 95 | 96 | 97 | [ 98 | 'ftyp', 'pdin', 'moov', 'mvhd', 'trak', 'tkhd', 'mdia', 99 | 'mdhd', 'hdlr', 'minf', 'stbl', 'stsd', 'stts', 'stsc', 100 | 'stsz', 'stz2', 'stss', 'stco', 'co64', 'ctts', 'stsh', 101 | 'padb', 'stdp', 'sdtp', 'sbgp', 'sgpd', 'subs', 'dinf', 102 | 'dref', 'nmhd', 'hmhd', 'smhd', 'vmhd', 'tref', 'edts', 103 | 'elst', 'mvex', 'mehd', 'trex', 'ipmc', 'moof', 'mfhd', 104 | 'traf', 'tfhd', 'trun', 'sdtp', 'sbgp', 'subs', 'mfra', 105 | 'tfra', 'mfro', 'mdat', 'free', 'skip', 'udta', 'cprt', 106 | 'meta', 'hdlr', 'dinf', 'dref', 'ipmc', 'iloc', 'ipro', 107 | 'sinf', 'frma', 'imif', 'schm', 'schi', 'iinf', 'xml', 108 | 'bxml', 'pitm', 'fiin', 'paen', 'fpar', 'fecr', 'itn', 109 | 'tsel', 'meco', 'mere', 'strk', 'stri', 'strd', 110 | ] 111 | 112 | atoms.sort() 113 | atoms = list(set(atoms)) 114 | 115 | xml, itn 116 | 117 | Python Script: 118 | sss = [] 119 | for atom in atoms: 120 | if len(atom) == 4: 121 | sss.append("\"%s\" => Ok(Kind::%s)," % (atom, atom) ) 122 | elif len(atom) == 3: 123 | sss.append("\"%s\" | \"%s\\u{0}\" => Ok(Kind::%s)," % (atom, atom, atom) ) 124 | else: 125 | print "error" 126 | print "\n".join(sss) 127 | 128 | 129 | sss = [] 130 | for atom in atoms: 131 | if len(atom) == 4: 132 | sss.append("Kind::%s => \"%s\".to_owned()," % (atom, atom) ) 133 | elif len(atom) == 3: 134 | sss.append("Kind::%s => \"%s\u{0}\".to_owned()," % (atom, atom) ) 135 | else: 136 | print "error" 137 | print "\n".join(sss) 138 | 139 | 140 | Container atom types: 141 | 142 | dinf 143 | edts 144 | ipro 145 | mdia 146 | meta 147 | mfra 148 | minf 149 | moof 150 | moov 151 | mvex 152 | sinf 153 | skip 154 | stbl 155 | traf 156 | trak 157 | 158 | **/ 159 | 160 | #[derive(Debug, Clone)] 161 | pub enum Kind{ 162 | bxml, 163 | co64, 164 | cprt, 165 | ctts, 166 | cslg, 167 | dinf, 168 | dref, 169 | edts, 170 | elst, 171 | fecr, 172 | fiin, 173 | fpar, 174 | free, 175 | frma, 176 | ftyp, 177 | hdlr, 178 | hmhd, 179 | iinf, 180 | iloc, 181 | imif, 182 | ipmc, 183 | ipro, 184 | itn, 185 | mdat, 186 | mdhd, 187 | mdia, 188 | meco, 189 | mehd, 190 | mere, 191 | meta, 192 | mfhd, 193 | mfra, 194 | mfro, 195 | minf, 196 | moof, 197 | moov, 198 | mvex, 199 | mvhd, 200 | nmhd, 201 | padb, 202 | paen, 203 | pdin, 204 | pitm, 205 | sbgp, 206 | schi, 207 | schm, 208 | sdtp, 209 | sgpd, 210 | sinf, 211 | skip, 212 | smhd, 213 | stbl, 214 | stco, 215 | stdp, 216 | stsc, 217 | stsd, 218 | stsh, 219 | stss, 220 | stsz, 221 | stts, 222 | stz2, 223 | subs, 224 | tfhd, 225 | tfra, 226 | tkhd, 227 | traf, 228 | trak, 229 | tref, 230 | trex, 231 | trun, 232 | tsel, 233 | udta, 234 | uuid, 235 | vmhd, 236 | xml, 237 | strk, 238 | stri, 239 | strd, 240 | Unrecognized(String) 241 | } 242 | 243 | impl FromStr for Kind { 244 | type Err = &'static str; 245 | fn from_str(s: &str) -> Result{ 246 | match s { 247 | "bxml" => Ok(Kind::bxml), 248 | "co64" => Ok(Kind::co64), 249 | "cprt" => Ok(Kind::cprt), 250 | "ctts" => Ok(Kind::ctts), 251 | "cslg" => Ok(Kind::cslg), 252 | "dinf" => Ok(Kind::dinf), 253 | "dref" => Ok(Kind::dref), 254 | "edts" => Ok(Kind::edts), 255 | "elst" => Ok(Kind::elst), 256 | "fecr" => Ok(Kind::fecr), 257 | "fiin" => Ok(Kind::fiin), 258 | "fpar" => Ok(Kind::fpar), 259 | "free" => Ok(Kind::free), 260 | "frma" => Ok(Kind::frma), 261 | "ftyp" => Ok(Kind::ftyp), 262 | "hdlr" => Ok(Kind::hdlr), 263 | "hmhd" => Ok(Kind::hmhd), 264 | "iinf" => Ok(Kind::iinf), 265 | "iloc" => Ok(Kind::iloc), 266 | "imif" => Ok(Kind::imif), 267 | "ipmc" => Ok(Kind::ipmc), 268 | "ipro" => Ok(Kind::ipro), 269 | "itn" | "itn\u{0}" => Ok(Kind::itn), 270 | "mdat" => Ok(Kind::mdat), 271 | "mdhd" => Ok(Kind::mdhd), 272 | "mdia" => Ok(Kind::mdia), 273 | "meco" => Ok(Kind::meco), 274 | "mehd" => Ok(Kind::mehd), 275 | "mere" => Ok(Kind::mere), 276 | "meta" => Ok(Kind::meta), 277 | "mfhd" => Ok(Kind::mfhd), 278 | "mfra" => Ok(Kind::mfra), 279 | "mfro" => Ok(Kind::mfro), 280 | "minf" => Ok(Kind::minf), 281 | "moof" => Ok(Kind::moof), 282 | "moov" => Ok(Kind::moov), 283 | "mvex" => Ok(Kind::mvex), 284 | "mvhd" => Ok(Kind::mvhd), 285 | "nmhd" => Ok(Kind::nmhd), 286 | "padb" => Ok(Kind::padb), 287 | "paen" => Ok(Kind::paen), 288 | "pdin" => Ok(Kind::pdin), 289 | "pitm" => Ok(Kind::pitm), 290 | "sbgp" => Ok(Kind::sbgp), 291 | "schi" => Ok(Kind::schi), 292 | "schm" => Ok(Kind::schm), 293 | "sdtp" => Ok(Kind::sdtp), 294 | "sgpd" => Ok(Kind::sgpd), 295 | "sinf" => Ok(Kind::sinf), 296 | "skip" => Ok(Kind::skip), 297 | "smhd" => Ok(Kind::smhd), 298 | "stbl" => Ok(Kind::stbl), 299 | "stco" => Ok(Kind::stco), 300 | "stdp" => Ok(Kind::stdp), 301 | "stsc" => Ok(Kind::stsc), 302 | "stsd" => Ok(Kind::stsd), 303 | "stsh" => Ok(Kind::stsh), 304 | "stss" => Ok(Kind::stss), 305 | "stsz" => Ok(Kind::stsz), 306 | "stts" => Ok(Kind::stts), 307 | "stz2" => Ok(Kind::stz2), 308 | "subs" => Ok(Kind::subs), 309 | "tfhd" => Ok(Kind::tfhd), 310 | "tfra" => Ok(Kind::tfra), 311 | "tkhd" => Ok(Kind::tkhd), 312 | "traf" => Ok(Kind::traf), 313 | "trak" => Ok(Kind::trak), 314 | "tref" => Ok(Kind::tref), 315 | "trex" => Ok(Kind::trex), 316 | "trun" => Ok(Kind::trun), 317 | "tsel" => Ok(Kind::tsel), 318 | "udta" => Ok(Kind::udta), 319 | "uuid" => Ok(Kind::uuid), 320 | "vmhd" => Ok(Kind::vmhd), 321 | "xml" | "xml\u{0}" => Ok(Kind::xml), 322 | "strk" => Ok(Kind::strk), 323 | "stri" => Ok(Kind::stri), 324 | "strd" => Ok(Kind::strd), 325 | _ => Ok(Kind::Unrecognized(s.to_owned())) 326 | } 327 | } 328 | } 329 | 330 | impl ToString for Kind { 331 | fn to_string(&self) -> String { 332 | match *self { 333 | Kind::bxml => "bxml".to_owned(), 334 | Kind::co64 => "co64".to_owned(), 335 | Kind::cprt => "cprt".to_owned(), 336 | Kind::ctts => "ctts".to_owned(), 337 | Kind::cslg => "cslg".to_owned(), 338 | Kind::dinf => "dinf".to_owned(), 339 | Kind::dref => "dref".to_owned(), 340 | Kind::edts => "edts".to_owned(), 341 | Kind::elst => "elst".to_owned(), 342 | Kind::fecr => "fecr".to_owned(), 343 | Kind::fiin => "fiin".to_owned(), 344 | Kind::fpar => "fpar".to_owned(), 345 | Kind::free => "free".to_owned(), 346 | Kind::frma => "frma".to_owned(), 347 | Kind::ftyp => "ftyp".to_owned(), 348 | Kind::hdlr => "hdlr".to_owned(), 349 | Kind::hmhd => "hmhd".to_owned(), 350 | Kind::iinf => "iinf".to_owned(), 351 | Kind::iloc => "iloc".to_owned(), 352 | Kind::imif => "imif".to_owned(), 353 | Kind::ipmc => "ipmc".to_owned(), 354 | Kind::ipro => "ipro".to_owned(), 355 | Kind::itn => "itn\u{0}".to_owned(), 356 | Kind::mdat => "mdat".to_owned(), 357 | Kind::mdhd => "mdhd".to_owned(), 358 | Kind::mdia => "mdia".to_owned(), 359 | Kind::meco => "meco".to_owned(), 360 | Kind::mehd => "mehd".to_owned(), 361 | Kind::mere => "mere".to_owned(), 362 | Kind::meta => "meta".to_owned(), 363 | Kind::mfhd => "mfhd".to_owned(), 364 | Kind::mfra => "mfra".to_owned(), 365 | Kind::mfro => "mfro".to_owned(), 366 | Kind::minf => "minf".to_owned(), 367 | Kind::moof => "moof".to_owned(), 368 | Kind::moov => "moov".to_owned(), 369 | Kind::mvex => "mvex".to_owned(), 370 | Kind::mvhd => "mvhd".to_owned(), 371 | Kind::nmhd => "nmhd".to_owned(), 372 | Kind::padb => "padb".to_owned(), 373 | Kind::paen => "paen".to_owned(), 374 | Kind::pdin => "pdin".to_owned(), 375 | Kind::pitm => "pitm".to_owned(), 376 | Kind::sbgp => "sbgp".to_owned(), 377 | Kind::schi => "schi".to_owned(), 378 | Kind::schm => "schm".to_owned(), 379 | Kind::sdtp => "sdtp".to_owned(), 380 | Kind::sgpd => "sgpd".to_owned(), 381 | Kind::sinf => "sinf".to_owned(), 382 | Kind::skip => "skip".to_owned(), 383 | Kind::smhd => "smhd".to_owned(), 384 | Kind::stbl => "stbl".to_owned(), 385 | Kind::stco => "stco".to_owned(), 386 | Kind::stdp => "stdp".to_owned(), 387 | Kind::stsc => "stsc".to_owned(), 388 | Kind::stsd => "stsd".to_owned(), 389 | Kind::stsh => "stsh".to_owned(), 390 | Kind::stss => "stss".to_owned(), 391 | Kind::stsz => "stsz".to_owned(), 392 | Kind::stts => "stts".to_owned(), 393 | Kind::stz2 => "stz2".to_owned(), 394 | Kind::subs => "subs".to_owned(), 395 | Kind::tfhd => "tfhd".to_owned(), 396 | Kind::tfra => "tfra".to_owned(), 397 | Kind::tkhd => "tkhd".to_owned(), 398 | Kind::traf => "traf".to_owned(), 399 | Kind::trak => "trak".to_owned(), 400 | Kind::tref => "tref".to_owned(), 401 | Kind::trex => "trex".to_owned(), 402 | Kind::trun => "trun".to_owned(), 403 | Kind::tsel => "tsel".to_owned(), 404 | Kind::udta => "udta".to_owned(), 405 | Kind::uuid => "uuid".to_owned(), 406 | Kind::vmhd => "vmhd".to_owned(), 407 | Kind::xml => "xml\u{0}".to_owned(), 408 | Kind::strk => "strk".to_owned(), 409 | Kind::stri => "stri".to_owned(), 410 | Kind::strd => "strd".to_owned(), 411 | Kind::Unrecognized(ref s) => s.to_owned() 412 | } 413 | } 414 | } 415 | 416 | impl Kind { 417 | pub fn from_bytes(bytes: &[u8; 4]) -> Result { 418 | let kind_str = match str::from_utf8(bytes) { 419 | Ok(s) => s, 420 | Err(_) => { 421 | println!("Atom Kind ({:?}) parse error.", bytes); 422 | return Err("Atom Kind parse error."); 423 | } 424 | }; 425 | Kind::from_str(kind_str) 426 | } 427 | pub fn into_bytes(&self) -> Vec { 428 | self.to_string().into_bytes() 429 | } 430 | } -------------------------------------------------------------------------------- /src/atom/moof.rs: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | moof 4 | mfhd 5 | traf 6 | tfhd 7 | trun 8 | sdtp 9 | sbgp 10 | subs 11 | 12 | **/ 13 | 14 | use super::{Mp4File, Kind, Header, Atom, Sample}; 15 | 16 | #[derive(Debug, Clone)] 17 | pub struct Moof { 18 | header: Header, 19 | children: Vec 20 | } 21 | 22 | impl Moof { 23 | pub fn parse(f: &mut Mp4File, header: Header) -> Result{ 24 | let children: Vec = Atom::parse_children(f); 25 | Ok(Moof{ 26 | header: header, 27 | children: children 28 | }) 29 | } 30 | } 31 | 32 | /** 33 | 34 | 35 | aligned(8) class MovieFragmentHeaderBox extends FullBox(‘mfhd’, 0, 0){ 36 | unsigned int(32) sequence_number; 37 | } 38 | 39 | **/ 40 | 41 | #[derive(Debug, Clone)] 42 | pub struct Mfhd { 43 | header: Header, 44 | sequence_number: u32 45 | } 46 | 47 | impl Mfhd { 48 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 49 | header.parse_version(f); 50 | header.parse_flags(f); 51 | let curr_offset = f.offset(); 52 | // f.seek(curr_offset+header.data_size); 53 | let sequence_number: u32 = f.read_u32().unwrap(); 54 | f.offset_inc(header.data_size); 55 | Ok(Mfhd{ 56 | header: header, 57 | sequence_number: sequence_number 58 | }) 59 | } 60 | } 61 | 62 | #[derive(Debug, Clone)] 63 | pub struct Traf { 64 | header: Header, 65 | children: Vec 66 | } 67 | 68 | impl Traf { 69 | pub fn parse(f: &mut Mp4File, header: Header) -> Result{ 70 | let children: Vec = Atom::parse_children(f); 71 | Ok(Traf{ 72 | header: header, 73 | children: children 74 | }) 75 | } 76 | } 77 | 78 | /** 79 | 8.8.7 Track Fragment Header Box 80 | 8.8.7.1 Definition 81 | Box Type : ‘tfhd’ 82 | Container: Track Fragment Box ('traf') 83 | Mandatory: Yes 84 | Quantity : Exactly one 85 | 86 | Each movie fragment can add zero or more fragments to each track; 87 | and a track fragment can add zero or more contiguous runs of samples. 88 | The track fragment header sets up information and defaults used for those runs of samples. 89 | 90 | The following flags are defined in the tf_flags: 91 | 0x000001 base-data-offset-present: indicates the presence of the base-data-offset field. 92 | This provides an explicit anchor for the data offsets in each track run (see below). 93 | If not provided, the base-data-offset for the first track in the movie fragment is 94 | the position of the first byte of the enclosing Movie Fragment Box, and for second 95 | and subsequent track fragments, the default is the end of the data defined by 96 | the preceding fragment. Fragments 'inheriting' their offset in this way must all use 97 | the same data-reference (i.e., the data for these tracks must be in the same file). 98 | 0x000002 sample-description-index-present: indicates the presence of this field, which over-rides, 99 | in this fragment, the default set up in the Track Extends Box. 100 | 0x000008 default-sample-duration-present 101 | 0x000010 default-sample-size-present 102 | 0x000020 default-sample-flags-present 103 | 0x010000 duration-is-empty: this indicates that the duration provided in either default-sample-duration, 104 | or by the default-duration in the Track Extends Box, is empty, i.e. that there are no samples 105 | for this time interval. It is an error to make a presentation that has both edit lists in 106 | the Movie Box, and empty- duration fragments. 107 | 0x020000 default-base-is-moof: if base-data-offset-present is zero, this indicates that the base-data-offset 108 | for this track fragment is the position of the first byte of the enclosing Movie Fragment Box. 109 | Support for the default-base-is-moof flag is required under the ‘iso5’ brand, and it shall not 110 | be used in brands or compatible brands earlier than iso5. 111 | 112 | NOTE: 113 | The use of the default-base-is-moof flag breaks the compatibility to earlier brands of the file format, 114 | because it sets the anchor point for offset calculation differently than earlier. Therefore, 115 | the default-base-is-moof flag cannot be set when earlier brands are included in the File Type box. 116 | 117 | 8.8.7.2 Syntax 118 | aligned(8) class TrackFragmentHeaderBox extends FullBox(‘tfhd’, 0, tf_flags){ 119 | unsigned int(32) track_ID; 120 | // all the following are optional fields 121 | unsigned int(64) base_data_offset; 122 | unsigned int(32) sample_description_index; 123 | unsigned int(32) default_sample_duration; 124 | unsigned int(32) default_sample_size; 125 | unsigned int(32) default_sample_flags 126 | } 127 | 128 | 8.8.7.3 Semantics 129 | `base_data_offset` the base offset to use when calculating data offsets 130 | **/ 131 | 132 | #[derive(Debug, Clone)] 133 | pub struct Tfhd { 134 | header : Header, 135 | track_id: u32, 136 | // all the following are optional fields 137 | base_data_offset: Option, 138 | sample: Sample 139 | } 140 | 141 | impl Tfhd { 142 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 143 | header.parse_version(f); 144 | header.parse_flags(f); 145 | 146 | let base_data_offset_present: [u8; 3] = [0x00, 0x00, 0x01]; // 0x000001 147 | let sample_description_index_present: [u8; 3] = [0x00, 0x00, 0x02]; // 0x000002 148 | let default_sample_duration_present: [u8; 3] = [0x00, 0x00, 0x08]; // 0x000008 149 | 150 | let default_sample_size_present: [u8; 3] = [0x00, 0x00, 0x10]; // 0x000010 151 | let default_sample_flags_present: [u8; 3] = [0x00, 0x00, 0x20]; // 0x000020 152 | let duration_is_empty: [u8; 3] = [0x01, 0x00, 0x00]; // 0x010000 153 | let default_base_is_moof: [u8; 3] = [0x20, 0x00, 0x00]; //0x020000 154 | 155 | // let curr_offset = f.offset(); 156 | // f.seek(curr_offset+header.data_size); 157 | let track_id: u32 = f.read_u32().unwrap(); 158 | 159 | let mut base_data_offset: Option = None; 160 | let mut sample_description_index: Option = None; 161 | let mut default_sample_duration: Option = None; 162 | let mut default_sample_size: Option = None; 163 | let mut default_sample_flags: Option = None; 164 | 165 | if header.flags.is_some() { 166 | let flags = header.flags.unwrap(); 167 | if flags == base_data_offset_present { 168 | base_data_offset = Some(f.read_u64().unwrap()); 169 | } else if flags == sample_description_index_present { 170 | sample_description_index = Some(f.read_u32().unwrap()); 171 | } else if flags == default_sample_duration_present { 172 | default_sample_duration = Some(f.read_u32().unwrap()); 173 | } else if flags == default_sample_size_present { 174 | default_sample_size = Some(f.read_u32().unwrap()); 175 | } else if flags == default_sample_flags_present { 176 | default_sample_flags = Some(f.read_u32().unwrap()); 177 | } else if flags == duration_is_empty { 178 | 179 | } else if flags == default_base_is_moof { 180 | 181 | } else { 182 | // unknow. 183 | } 184 | } 185 | f.offset_inc(header.data_size); 186 | Ok(Tfhd{ 187 | header : header, 188 | track_id: track_id, 189 | base_data_offset: base_data_offset, 190 | sample : Sample { 191 | duration: default_sample_duration, 192 | size : default_sample_size, 193 | flags : default_sample_flags, 194 | composition_time_offset: None, 195 | description_index : sample_description_index 196 | } 197 | }) 198 | } 199 | } 200 | 201 | 202 | /** 203 | 204 | 8.8.8.1 Definition 205 | Box Type : ‘trun’ 206 | Container: Track Fragment Box ('traf') 207 | Mandatory: No 208 | Quantity : Zero or more 209 | 210 | 211 | Within the Track Fragment Box, there are zero or more Track Run Boxes. 212 | If the duration-is-empty flag is set in the `tf_flags`, there are no track runs. 213 | A track run documents a contiguous set of samples for a track. 214 | 215 | The number of optional fields is determined from the number of bits set in the lower byte of the flags, 216 | and the size of a record from the bits set in the second byte of the flags. This procedure shall be followed, 217 | to allow for new fields to be defined. 218 | 219 | If the data-offset is not present, then the data for this run starts immediately after 220 | the data of the previous run, or at the base-data-offset defined by the track fragment header 221 | if this is the first run in a track fragment, If the data-offset is present, it is relative 222 | to the base-data-offset established in the track fragment header. 223 | The following flags are defined: 224 | 225 | 0x000001 data-offset-present. 226 | 0x000004 first-sample-flags-present; this over-rides the default flags for the first sample only. 227 | This makes it possible to record a group of frames where the first is a key and the rest 228 | are difference frames, without supplying explicit flags for every sample. If this flag 229 | and field are used, sample-flags shall not be present. 230 | 0x000100 sample-duration-present: indicates that each sample has its own duration, otherwise the default is used. 231 | 0x000200 sample-size-present: each sample has its own size, otherwise the default is used. 232 | 0x000400 sample-flags-present; each sample has its own flags, otherwise the default is used. 233 | 0x000800 sample-composition-time-offsets-present; 234 | each sample has a composition time offset (e.g. as used for I/P/B video in MPEG). 235 | 236 | The composition offset values in the composition time-to-sample box and in the track run box may be signed 237 | or unsigned. The recommendations given in the composition time-to-sample box concerning the use of 238 | signed composition offsets also apply here. 239 | 240 | 241 | aligned(8) class TrackRunBox extends FullBox(‘trun’, version, tr_flags) { 242 | unsigned int(32) sample_count; 243 | // the following are optional fields 244 | signed int(32) data_offset; 245 | unsigned int(32) first_sample_flags; 246 | // all fields in the following array are optional 247 | { 248 | unsigned int(32) sample_duration; 249 | unsigned int(32) sample_size; 250 | unsigned int(32) sample_flags 251 | if (version == 0) { 252 | unsigned int(32) sample_composition_time_offset; 253 | } else { 254 | signed int(32) sample_composition_time_offset; 255 | }[ sample_count ] 256 | } 257 | } 258 | 259 | `sample_count` the number of samples being added in this run; 260 | also the number of rows in the following table (the rows can be empty) 261 | `data_offset` is added to the implicit or explicit data_offset established in the track fragment header. 262 | `first_sample_flags` provides a set of flags for the first sample only of this run. 263 | 264 | **/ 265 | 266 | 267 | #[derive(Debug, Clone)] 268 | pub struct Trun { 269 | header: Header, 270 | sample_count: u32, 271 | // the following are optional fields 272 | data_offset: Option, 273 | first_sample_flags: Option, 274 | // all fields in the following array are optional 275 | samples: Vec 276 | } 277 | 278 | impl Trun { 279 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 280 | header.parse_version(f); 281 | header.parse_flags(f); 282 | // let curr_offset = f.offset(); 283 | 284 | let data_offset_present: [u8; 3] = [0x00, 0x00, 0x01]; // 0x000001 285 | let first_sample_flags_present: [u8; 3] = [0x00, 0x00, 0x04]; // 0x000004 286 | let sample_duration_present: [u8; 3] = [0x00, 0x01, 0x00]; // 0x000100 287 | let sample_size_present: [u8; 3] = [0x00, 0x02, 0x00]; // 0x000200 288 | let sample_flags_present: [u8; 3] = [0x00, 0x04, 0x00]; // 0x000400 289 | let sample_composition_time_offsets_present: [u8; 3] = [0x00, 0x08, 0x00]; // 0x000800 290 | 291 | let sample_count: u32 = f.read_u32().unwrap(); 292 | let mut data_offset: Option = None; 293 | let mut first_sample_flags: Option = None; 294 | let mut samples: Vec = Vec::with_capacity(sample_count as usize); 295 | 296 | assert!(header.flags.is_some()); 297 | 298 | let flags = header.flags.unwrap(); 299 | if flags == data_offset_present { 300 | data_offset = Some(f.read_i32().unwrap()); 301 | } else if flags == first_sample_flags_present { 302 | first_sample_flags = Some(f.read_u32().unwrap()); 303 | } 304 | // parse samples 305 | for _ in 0..sample_count { 306 | let sample_duration = if flags == sample_duration_present { 307 | Some(f.read_u32().unwrap()) 308 | } else { 309 | None 310 | }; 311 | let sample_flags = if flags == sample_flags_present { 312 | Some(f.read_u32().unwrap()) 313 | } else { 314 | None 315 | }; 316 | let sample_size = if flags == sample_size_present { 317 | Some(f.read_u32().unwrap()) 318 | } else { 319 | None 320 | }; 321 | let sample_composition_time_offset = if flags == sample_composition_time_offsets_present { 322 | if header.version.unwrap() == 0u8 { 323 | Some(f.read_u32().unwrap() as i32) 324 | } else { 325 | Some(f.read_i32().unwrap()) 326 | } 327 | } else { 328 | None 329 | }; 330 | let sample_description_index = None; 331 | samples.push(Sample{ 332 | duration: sample_duration, 333 | size : sample_flags, 334 | flags : sample_flags, 335 | composition_time_offset: sample_composition_time_offset, 336 | description_index : sample_description_index 337 | }); 338 | } 339 | // f.seek(curr_offset+header.data_size); 340 | 341 | f.offset_inc(header.data_size); 342 | Ok(Trun{ 343 | header: header, 344 | sample_count: sample_count, 345 | data_offset : data_offset, 346 | 347 | first_sample_flags: first_sample_flags, 348 | samples: samples 349 | }) 350 | } 351 | } 352 | -------------------------------------------------------------------------------- /src/atom/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | // https://wiki.multimedia.cx/index.php/QuickTime_container 3 | // http://developer.apple.com/documentation/QuickTime/QTFF/index.html 4 | // http://www.adobe.com/devnet/video/articles/mp4_movie_atom.html 5 | // http://mpeg.chiariglione.org/standards/mpeg-4/mpeg-4.htm 6 | 7 | // http://www.adobe.com/devnet/f4v.html 8 | 9 | // box types: http://mp4ra.org/atoms.html 10 | 11 | /** 12 | Box Struct: 13 | 14 | size(u32), type(u32), largesize(u64), 15 | data 16 | 17 | 其中, `size` 指明了整个 `box` 的大小, 包括 `header` 部分. 18 | 如果 `box` 大小超过了 `u32` 的最大数值, `size` 就被设置为 `1` , 19 | 并用接下来的 `8位` u64 来存放大小。 20 | 21 | Atoms: 22 | 23 | ftyp 24 | pdin 25 | moov 26 | mvhd 27 | trak 28 | tkhd 29 | mdia 30 | mdhd 31 | hdlr 32 | minf 33 | stbl 34 | stsd 35 | stts 36 | stsc 37 | stsz 38 | stz2 39 | stss 40 | stco 41 | co64 42 | 43 | ctts 44 | stsh 45 | padb 46 | stdp 47 | sdtp 48 | sbgp 49 | sgpd 50 | subs 51 | dinf 52 | dref 53 | nmhd 54 | hmhd 55 | smhd 56 | vmhd 57 | tref 58 | edts 59 | elst 60 | mvex 61 | mehd 62 | trex 63 | ipmc 64 | moof 65 | mfhd 66 | traf 67 | tfhd 68 | trun 69 | sdtp 70 | sbgp 71 | subs 72 | mfra 73 | tfra 74 | mfro 75 | mdat 76 | free 77 | skip 78 | udta 79 | cprt 80 | tsel 81 | strk 82 | stri 83 | strd 84 | meta 85 | hdlr 86 | dinf 87 | dref 88 | ipmc 89 | iloc 90 | ipro 91 | sinf 92 | frma 93 | imif 94 | schm 95 | schi 96 | iinf 97 | xml 98 | bxml 99 | pitm 100 | fiin 101 | paen 102 | fpar 103 | fecr 104 | segr 105 | gitn 106 | tsel 107 | meco 108 | mere 109 | 110 | 111 | Top level Atoms: 112 | 113 | ftyp 114 | pdin 115 | moov 116 | mfra 117 | mdat 118 | free 119 | skip 120 | meta 121 | meco 122 | 123 | Fragment 124 | 125 | Initialization Segments 126 | ftyp 127 | moov 128 | moof 129 | mdat 130 | 131 | Media Segments 132 | 133 | moof 134 | mfhd 135 | traf 136 | tfhd 137 | trun 138 | sdtp 139 | traf 140 | tfhd 141 | trun 142 | sdtp 143 | mdat 144 | **/ 145 | 146 | use std::str; 147 | use std::string::String; 148 | use std::mem::transmute; 149 | use std::fs::File; 150 | 151 | use std::str::FromStr; 152 | use std::string::ToString; 153 | use std::convert::AsRef; 154 | 155 | use std::io::{Write, Read, ErrorKind, SeekFrom, Seek}; 156 | use ::byteorder::{BigEndian, ReadBytesExt}; 157 | pub use super::Mp4File; 158 | 159 | mod kind; 160 | 161 | mod ftyp; 162 | mod mdat; 163 | mod pdin; 164 | mod freespace; 165 | mod uuid; 166 | mod meco; 167 | mod meta; 168 | mod mfra; 169 | mod moof; 170 | mod moov; 171 | mod ignore; 172 | mod unrecognized; 173 | 174 | pub use self::kind::Kind; 175 | 176 | use self::ftyp::Ftyp; 177 | use self::freespace::{Free, Skip}; 178 | use self::mdat::Mdat; 179 | use self::pdin::Pdin; 180 | use self::uuid::Uuid; 181 | 182 | use self::moov::{ 183 | Moov, Mvhd, Trak, Tkhd, Tref, Mdia, Mdhd, Hdlr, 184 | Minf, Vmhd, Smhd, Hmhd, Nmhd, Stbl, Stsd, Stdp, 185 | Stts, Ctts, Cslg, Stss, Stsh, Sdtp, Stsc, Stsz, 186 | Stz2, Stco, Co64, Padb, 187 | Mvex, Mehd, Trex 188 | }; 189 | use self::moof::{ 190 | Moof, Mfhd, Traf, Tfhd, Trun, 191 | }; 192 | use self::mfra::{ 193 | Mfra, Tfra, Mfro 194 | }; 195 | use self::meta::{ 196 | Meta, Xml, Bxml 197 | }; 198 | use self::meco::{ 199 | Meco, Mere 200 | }; 201 | use self::ignore::Ignore; 202 | use self::unrecognized::Unrecognized; 203 | 204 | #[derive(Debug, Clone)] 205 | pub struct Entry { 206 | first_chunk : u32, 207 | samples_per_chunk : u32, 208 | sample_description_index: u32 209 | } 210 | 211 | #[derive(Debug, Clone)] 212 | pub struct Sample { 213 | duration: Option, 214 | size : Option, 215 | flags : Option, 216 | composition_time_offset: Option, 217 | description_index : Option 218 | } 219 | 220 | /** 221 | aligned(8) class Box (unsigned int(32) boxtype, 222 | optional unsigned int(8)[16] extended_type) { 223 | unsigned int(32) size; 224 | unsigned int(32) type = boxtype; 225 | if (size==1) { 226 | unsigned int(64) largesize; 227 | } else if (size==0) { 228 | // box extends to end of file 229 | } 230 | if (boxtype==‘uuid’) { 231 | unsigned int(8)[16] usertype = extended_type; 232 | } 233 | } 234 | 235 | The semantics of these two fields are: 236 | 237 | `size` is an integer that specifies the number of bytes in this box, 238 | including all its fields and contained boxes; if size is 1 then the actual size is 239 | in the field largesize; if size is 0, then this box is the last one in the file, 240 | and its contents extend to the end of the file (normally only used for a Media Data Box) 241 | `type` identifies the box type; standard boxes use a compact type, 242 | which is normally four printable characters, to permit ease of identification, 243 | and is shown so in the boxes below. User extensions use an extended type; in this case, 244 | the type field is set to ‘uuid’. 245 | 246 | Boxes with an unrecognized type shall be ignored and skipped. 247 | 248 | Many objects also contain a version number and flags field: 249 | 250 | aligned(8) class FullBox(unsigned int(32) boxtype, 251 | unsigned int(8) v, 252 | bit(24) f) extends Box(boxtype) { 253 | unsigned int(8) version = v; 254 | bit(24) flags = f; 255 | } 256 | 257 | The semantics of these two fields are: 258 | `version` is an integer that specifies the version of this format of the box. 259 | `flags` is a map of flags 260 | 261 | Boxes with an unrecognized version shall be ignored and skipped. 262 | 263 | 简单来说,Box Header 有两个版本的格式: 264 | 265 | pub struct Box { 266 | type: BoxType, 267 | size: u32, 268 | // if size == 0 { 269 | // box extends to end of file 270 | // } else if size == 1 { 271 | // largesize = u64 272 | // } 273 | largesize: Option, 274 | usertype: Option> // length 16. if type === 'uuid', usertype is active. 275 | } 276 | 277 | pub struct FullBox { 278 | type: BoxType, 279 | size: u32, 280 | // if size == 0 { 281 | // box extends to end of file 282 | // } else if size == 1 { 283 | // largesize = u64 284 | // } 285 | largesize: Option, 286 | // if type === 'uuid', then usertype is active 287 | usertype: Option<[u8; 16]>, // length 16. 288 | 289 | version: u8, 290 | flags : [u8; 3], // 24 Bits 291 | } 292 | **/ 293 | 294 | #[derive(Debug, Clone)] 295 | pub struct Header { 296 | size : u32, 297 | kind : Kind, // atom type 298 | 299 | // Optional 300 | largesize : Option, 301 | usertype : Option<[u8; 16]>, 302 | version : Option, 303 | flags : Option<[u8; 3]>, // 24 Bits 304 | // 自定义抽象 305 | atom_size : u64, // atom size , include header and data. 306 | header_size: u64, // atom header size, not include data size. 307 | data_size : u64, // atom data size , not include header size. 308 | offset : u64, // file offset. 309 | } 310 | 311 | impl Header { 312 | pub fn parse(f: &mut Mp4File) -> Result{ 313 | let curr_offset = f.offset(); 314 | let size: u32 = f.read_u32().unwrap(); 315 | 316 | let kind_bytes: [u8; 4] = [ 317 | f.read_u8().unwrap(), f.read_u8().unwrap(), 318 | f.read_u8().unwrap(), f.read_u8().unwrap(), 319 | ]; 320 | let kind = Kind::from_bytes(&kind_bytes).unwrap(); 321 | 322 | let header_size = 8u64; 323 | let atom_size = size as u64; 324 | // let data_size = atom_size - header_size; 325 | let data_size = 0u64; 326 | 327 | f.offset_inc(header_size); 328 | 329 | let mut header = Header{ 330 | size: size, 331 | kind: kind, 332 | 333 | largesize : None, 334 | usertype : None, 335 | version : None, 336 | flags : None, 337 | 338 | atom_size : atom_size, // atom size , include header and data. 339 | header_size: header_size, // atom header size, not include data size. 340 | data_size : data_size, // atom data size , not include header size. 341 | offset : curr_offset, // file offset. 342 | }; 343 | if size == 1u32 { 344 | header.parse_largesize(f); 345 | } else if size < 1u32 { 346 | return Err("can not parse this mp4 file."); 347 | } else { 348 | header.data_size = atom_size - header_size; 349 | } 350 | Ok(header) 351 | } 352 | pub fn parse_largesize(&mut self, f: &mut Mp4File){ 353 | assert_eq!(self.size, 1u32); 354 | 355 | let largesize = f.read_u64().unwrap(); 356 | self.atom_size = largesize; 357 | self.header_size = self.header_size + 8; 358 | self.data_size = largesize - self.header_size; 359 | 360 | self.largesize = Some(largesize); 361 | f.offset_inc(8); 362 | } 363 | pub fn parse_usertype(&mut self, f: &mut Mp4File){ 364 | let usertype: [u8; 16] = [ 365 | f.read_u8().unwrap(), f.read_u8().unwrap(), 366 | f.read_u8().unwrap(), f.read_u8().unwrap(), 367 | f.read_u8().unwrap(), f.read_u8().unwrap(), 368 | f.read_u8().unwrap(), f.read_u8().unwrap(), 369 | f.read_u8().unwrap(), f.read_u8().unwrap(), 370 | f.read_u8().unwrap(), f.read_u8().unwrap(), 371 | f.read_u8().unwrap(), f.read_u8().unwrap(), 372 | f.read_u8().unwrap(), f.read_u8().unwrap() 373 | ]; 374 | self.usertype = Some(usertype); 375 | 376 | self.header_size = self.header_size + 16; 377 | assert!((self.atom_size - self.header_size) >= 0); 378 | self.data_size = self.atom_size - self.header_size; 379 | f.offset_inc(16); 380 | } 381 | pub fn parse_version(&mut self, f: &mut Mp4File){ 382 | let version = f.read_u8().unwrap(); 383 | self.version = Some(version); 384 | 385 | self.header_size = self.header_size + 1; 386 | assert!((self.atom_size - self.header_size) >= 0); 387 | self.data_size = self.atom_size - self.header_size; 388 | f.offset_inc(1); 389 | } 390 | pub fn parse_flags(&mut self, f: &mut Mp4File){ 391 | let flags: [u8; 3] = [ 392 | f.read_u8().unwrap(), f.read_u8().unwrap(), 393 | f.read_u8().unwrap() 394 | ]; 395 | self.flags = Some(flags); 396 | 397 | self.header_size = self.header_size + 3; 398 | assert!((self.atom_size - self.header_size) >= 0); 399 | self.data_size = self.atom_size - self.header_size; 400 | f.offset_inc(3); 401 | } 402 | } 403 | 404 | #[derive(Debug, Clone)] 405 | pub enum Atom { 406 | ftyp(Ftyp), 407 | free(Free), 408 | skip(Skip), 409 | mdat(Mdat), 410 | pdin(Pdin), 411 | uuid(Uuid), 412 | // MOOV 413 | moov(Moov), 414 | mvhd(Mvhd), 415 | trak(Trak), 416 | tkhd(Tkhd), 417 | tref(Tref), 418 | mdia(Mdia), 419 | mdhd(Mdhd), 420 | hdlr(Hdlr), 421 | minf(Minf), 422 | vmhd(Vmhd), 423 | smhd(Smhd), 424 | hmhd(Hmhd), 425 | nmhd(Nmhd), 426 | 427 | mvex(Mvex), 428 | mehd(Mehd), 429 | trex(Trex), 430 | 431 | // STBL 432 | stbl(Stbl), 433 | stsc(Stsc), 434 | stsz(Stsz), 435 | stz2(Stz2), 436 | stco(Stco), 437 | co64(Co64), 438 | stsd(Stsd), 439 | stdp(Stdp), 440 | stts(Stts), 441 | ctts(Ctts), 442 | cslg(Cslg), 443 | stss(Stss), 444 | stsh(Stsh), 445 | sdtp(Sdtp), 446 | padb(Padb), 447 | 448 | // MOOF 449 | moof(Moof), 450 | mfhd(Mfhd), 451 | traf(Traf), 452 | tfhd(Tfhd), 453 | trun(Trun), 454 | // MFRA 455 | mfra(Mfra), 456 | tfra(Tfra), 457 | mfro(Mfro), 458 | // Meta 459 | meta(Meta), 460 | xml(Xml), 461 | bxml(Bxml), 462 | // Meco 463 | meco(Meco), 464 | mere(Mere), 465 | 466 | ignore(Ignore), 467 | unrecognized(Unrecognized) 468 | } 469 | 470 | impl Atom { 471 | fn parse_kind(f: &mut Mp4File) -> Result { 472 | let kind_bytes: [u8; 4] = [ 473 | f.read_u8().unwrap(), f.read_u8().unwrap(), 474 | f.read_u8().unwrap(), f.read_u8().unwrap(), 475 | ]; 476 | Kind::from_bytes(&kind_bytes) 477 | } 478 | 479 | pub fn parse(f: &mut Mp4File) -> Result { 480 | let mut header = Header::parse(f).unwrap(); 481 | // println!("DO: \n{:?}", header); 482 | let data = match header.kind { 483 | Kind::bxml => Ok(Atom::bxml(Bxml::parse(f, header).unwrap())), 484 | Kind::co64 => Ok(Atom::co64(Co64::parse(f, header).unwrap())), 485 | Kind::cslg => Ok(Atom::cslg(Cslg::parse(f, header).unwrap())), 486 | // Kind::cprt => , 487 | Kind::ctts => Ok(Atom::ctts(Ctts::parse(f, header).unwrap())), 488 | // Kind::dinf => , 489 | // Kind::dref => , 490 | // Kind::edts => , 491 | // Kind::elst => , 492 | // Kind::fecr => , 493 | // Kind::fiin => , 494 | // Kind::fpar => , 495 | Kind::free => Ok(Atom::free(Free::parse(f, header).unwrap())), 496 | // Kind::frma => , 497 | Kind::ftyp => Ok(Atom::ftyp(Ftyp::parse(f, header).unwrap())), 498 | Kind::hdlr => Ok(Atom::hdlr(Hdlr::parse(f, header).unwrap())), 499 | Kind::hmhd => Ok(Atom::hmhd(Hmhd::parse(f, header).unwrap())), 500 | // Kind::iinf => , 501 | // Kind::iloc => , 502 | // Kind::imif => , 503 | // Kind::ipmc => , 504 | // Kind::ipro => , 505 | // Kind::itn => , 506 | Kind::mdat => Ok(Atom::mdat(Mdat::parse(f, header).unwrap())), 507 | Kind::mdhd => Ok(Atom::mdhd(Mdhd::parse(f, header).unwrap())), 508 | Kind::mdia => Ok(Atom::mdia(Mdia::parse(f, header).unwrap())), 509 | Kind::meco => Ok(Atom::meco(Meco::parse(f, header).unwrap())), 510 | Kind::mehd => Ok(Atom::mehd(Mehd::parse(f, header).unwrap())), 511 | Kind::mere => Ok(Atom::mere(Mere::parse(f, header).unwrap())), 512 | Kind::meta => Ok(Atom::meta(Meta::parse(f, header).unwrap())), 513 | Kind::mfhd => Ok(Atom::mfhd(Mfhd::parse(f, header).unwrap())), 514 | Kind::mfra => Ok(Atom::mfra(Mfra::parse(f, header).unwrap())), 515 | Kind::mfro => Ok(Atom::mfro(Mfro::parse(f, header).unwrap())), 516 | Kind::minf => Ok(Atom::minf(Minf::parse(f, header).unwrap())), 517 | Kind::moof => Ok(Atom::moof(Moof::parse(f, header).unwrap())), 518 | Kind::moov => Ok(Atom::moov(Moov::parse(f, header).unwrap())), 519 | Kind::mvex => Ok(Atom::mvex(Mvex::parse(f, header).unwrap())), 520 | Kind::mvhd => Ok(Atom::mvhd(Mvhd::parse(f, header).unwrap())), 521 | Kind::nmhd => Ok(Atom::nmhd(Nmhd::parse(f, header).unwrap())), 522 | Kind::padb => Ok(Atom::padb(Padb::parse(f, header).unwrap())), 523 | // Kind::paen => , 524 | Kind::pdin => Ok(Atom::pdin(Pdin::parse(f, header).unwrap())), 525 | // Kind::pitm => , 526 | // Kind::sbgp => , 527 | // Kind::schi => , 528 | // Kind::schm => , 529 | Kind::sdtp => Ok(Atom::sdtp(Sdtp::parse(f, header).unwrap())), 530 | // Kind::sgpd => , 531 | // Kind::sinf => , 532 | Kind::skip => Ok(Atom::skip(Skip::parse(f, header).unwrap())), 533 | Kind::smhd => Ok(Atom::smhd(Smhd::parse(f, header).unwrap())), 534 | Kind::stbl => Ok(Atom::stbl(Stbl::parse(f, header).unwrap())), 535 | Kind::stco => Ok(Atom::stco(Stco::parse(f, header).unwrap())), 536 | Kind::stdp => Ok(Atom::stdp(Stdp::parse(f, header).unwrap())), 537 | Kind::stsc => Ok(Atom::stsc(Stsc::parse(f, header).unwrap())), 538 | Kind::stsd => Ok(Atom::stsd(Stsd::parse(f, header).unwrap())), 539 | Kind::stsh => Ok(Atom::stsh(Stsh::parse(f, header).unwrap())), 540 | Kind::stss => Ok(Atom::stss(Stss::parse(f, header).unwrap())), 541 | Kind::stsz => Ok(Atom::stsz(Stsz::parse(f, header).unwrap())), 542 | Kind::stts => Ok(Atom::stts(Stts::parse(f, header).unwrap())), 543 | Kind::stz2 => Ok(Atom::stz2(Stz2::parse(f, header).unwrap())), 544 | // Kind::subs => , 545 | Kind::tfhd => Ok(Atom::tfhd(Tfhd::parse(f, header).unwrap())), 546 | Kind::tfra => Ok(Atom::tfra(Tfra::parse(f, header).unwrap())), 547 | Kind::tkhd => Ok(Atom::tkhd(Tkhd::parse(f, header).unwrap())), 548 | Kind::traf => Ok(Atom::traf(Traf::parse(f, header).unwrap())), 549 | Kind::trak => Ok(Atom::trak(Trak::parse(f, header).unwrap())), 550 | Kind::tref => Ok(Atom::tref(Tref::parse(f, header).unwrap())), 551 | Kind::trex => Ok(Atom::trex(Trex::parse(f, header).unwrap())), 552 | Kind::trun => Ok(Atom::trun(Trun::parse(f, header).unwrap())), 553 | // Kind::tsel => , 554 | // Kind::udta => , 555 | Kind::uuid => Ok(Atom::uuid(Uuid::parse(f, header).unwrap())), 556 | Kind::vmhd => Ok(Atom::vmhd(Vmhd::parse(f, header).unwrap())), 557 | Kind::xml => Ok(Atom::xml(Xml::parse(f, header).unwrap())), 558 | // Kind::strk => , 559 | // Kind::stri => , 560 | // Kind::strd => 561 | 562 | Kind::Unrecognized(_) => Ok(Atom::unrecognized(Unrecognized::parse(f, header).unwrap())), 563 | _ => Ok(Atom::ignore(Ignore::parse(f, header).unwrap())) 564 | }; 565 | data 566 | } 567 | pub fn parse_children(f: &mut Mp4File) -> Vec { 568 | let mut atoms: Vec = Vec::new(); 569 | loop { 570 | if f.offset() == f.file_size() { 571 | break; 572 | } 573 | match Atom::parse(f) { 574 | Ok(atom) => { 575 | atoms.push(atom); 576 | }, 577 | Err(e) => { 578 | println!("[ERROR] ATOM parse error ({:?})", e); 579 | break; 580 | } 581 | } 582 | } 583 | atoms 584 | } 585 | } 586 | 587 | -------------------------------------------------------------------------------- /src/atom/moov.rs: -------------------------------------------------------------------------------- 1 | 2 | 3 | // Metadata container 4 | 5 | /** 6 | 7 | moov 8 | mvhd 9 | trak 10 | tkhd 11 | mdia 12 | mdhd 13 | hdlr 14 | minf 15 | stbl 16 | stsd 17 | stts 18 | stsc 19 | stsz 20 | stz2 21 | stss 22 | stco 23 | co64 24 | 25 | ctts 26 | stsh 27 | padb 28 | stdp 29 | sdtp 30 | sbgp 31 | sgpd 32 | subs 33 | dinf 34 | dref 35 | nmhd 36 | hmhd 37 | smhd 38 | vmhd 39 | tref 40 | edts 41 | elst 42 | mvex 43 | mehd 44 | trex 45 | ipmc 46 | 47 | 参考: 48 | http://blog.csdn.net/yu_yuan_1314/article/details/9078287 49 | http://www.cnblogs.com/tocy/p/media_container_3_mp4.html 50 | 51 | 在MOV和MP4文件格式中包括几个重要的Table,对应的atoms分别为: 52 | 53 | stts, ctts, stss, stsc, stsz, stco/co64 54 | 55 | 0. 节目时间计算(MVHD/MDHD) 56 | t = duration / timescale (MVHD/MDHD) 57 | 58 | 1. Sample时间表(stts) 59 | Time-To-Sample Atoms,存储了媒体sample的时常信息,提供了时间和相关sample之间的映射关系。 60 | 该atom包含了一个表,关于time和sample号之间的索引关系。 61 | 表的每个entry给出了具有相同时间间隔的连续的sample的个数和这些sample的时间间隔值。 62 | 将这些时间间隔相加在一起,就可以得到一个完整的time与sample之间的映射。 63 | 将所有的时间间隔相加在一起,就可以得到该track的时间总长。 64 | 65 | 每个sample的显示时间可以通过如下的公式得到: 66 | D(n+1) = D(n) + STTS(n) 67 | 其中,STTS(n)是sample n的时间间隔,包含在表格中;D(n)是sample n的显示时间。 68 | 69 | 2. 时间合成偏移表(ctts) 70 | Composition Offset Atom。每一个视频sample都有一个解码顺序和一个显示顺序。 71 | 对于一个sample来说,解码顺序和显示顺序可能不一致,比如H.264格式,因此, 72 | Composition Offset Atom就是在这种情况下被使用的。 73 | (1)如果解码顺序和显示顺序是一致的,Composition Offset Atom就不会出现。 74 | Time-To-Sample Atoms既提供了解码顺序也提供了显示顺序, 75 | 并能够计算出每个sample的开始时间和结束时间。 76 | (2)如果解码顺序和显示顺序不一致,那么Time-To-Sample Atoms既提供解码顺序, 77 | Composition Offset Atom则通过差值的形式来提供显示时间。 78 | 79 | Composition Offset Atom提供了一个从解码时间到显示时间的sample一对一的映射,具有如下的映射关系: 80 | CT(n) = DT(n) + CTTS(n) 81 | 其中,CTTS(n)是sample n在table中的entry(这里假设一个entry只对应一个sample)可以是正值也可是负值; 82 | DT(n)是sample n的解码时间,通过Time-To-Sample Atoms计算获得;CT(n)便是sample n的显示时间。 83 | 84 | 3. 同步Sample表(stss) 85 | Sync Sample Atom,标识了媒体流中的关键帧,提供了随机访问点标记。 86 | Sync Sample Atom包含了一个table,table的每个entry标识了一个sample,该sample是媒体流的关键帧。 87 | Table中的sample号是严格按照增长的顺序排列的,如果该table不存在,那么每一个sample都可以作为随机访问点。 88 | 换句话说,如果Sync Sample Atom不存在,那么所有的sample都是关键帧。 89 | 90 | 4. Chunk中的Sample信息表(stsc) 91 | Sample-To-Chunk Atom。为了优化数据访问,通常把sample封装到chunk中, 92 | 一个chunk可能会包含一个或者几个sample。每个chunk会有不同的size, 93 | 每个chunk中的sample也会有不同的size。 94 | 在Sample-To-Chunk Atom中包含了个table,这个table提供了从sample到chunk的一个映射, 95 | 每个table entry可能包含一个或者多个chunk。 96 | Table entry包含的内容包括第一个chunk号、每个chunk包含的sample的个数以及sample的描述ID。 97 | 98 | 5. Sample大小表(stsz) 99 | Sample Size Atom,指定了每个sample的size。Sample Size Atom给出了sample的总数和一张表, 100 | 这个表包含了每个sample的size。如果指定了默认的sampe size,那么这个table就不存在了。 101 | 即每个sample使用这个默认的sample size。 102 | 103 | 6. Chunk的偏移量表(stco/co64) 104 | Chunk Offset Atom,指定了每个chunk在文件中的位置。 105 | Chunk Offset Atom包含了一个table,表中的每个entry给出了每个chunk在文件中的位置。 106 | 有两种形式来表示每个entry的值,即chunk的偏移量,32位和64位。 107 | 如果Chunk Offset Atom的类型为stco,则使用的是32位的, 108 | 如果是co64,那么使用的就是64位的。 109 | **/ 110 | 111 | use std::string::String; 112 | use std::mem; 113 | use ::Matrix; 114 | use super::{Mp4File, Kind, Header, Atom, Entry}; 115 | 116 | /** 117 | 118 | Box Type : ‘moov’ 119 | Container: File 120 | Mandatory: Yes 121 | Quantity : Exactly one 122 | 123 | The metadata for a presentation is stored in the single Movie Box 124 | which occurs at the top-level of a file. Normally this box is close 125 | to the beginning or end of the file, though this is not required. 126 | 127 | 128 | **/ 129 | 130 | #[derive(Debug, Clone)] 131 | pub struct Moov { 132 | header: Header, 133 | children : Vec 134 | 135 | } 136 | 137 | impl Moov { 138 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 139 | let children: Vec = Atom::parse_children(f); 140 | Ok(Moov{ 141 | header: header, 142 | children: children 143 | }) 144 | } 145 | } 146 | 147 | 148 | /** 149 | 150 | Box Type : ‘mvhd’ 151 | Container: Movie Box (‘moov’) 152 | Mandatory: Yes 153 | Quantity : Exactly one 154 | 155 | This box defines overall information which is media-independent, 156 | and relevant to the entire presentation considered as a whole. 157 | 158 | aligned(8) class MovieHeaderBox extends FullBox(‘mvhd’, version, 0) { 159 | if (version==1) { 160 | unsigned int(64) creation_time; 161 | unsigned int(64) modification_time; 162 | unsigned int(32) timescale; 163 | unsigned int(64) duration; 164 | } else { // version==0 165 | unsigned int(32) creation_time; 166 | unsigned int(32) modification_time; 167 | unsigned int(32) timescale; 168 | unsigned int(32) duration; 169 | } 170 | template int(32) rate = 0x00010000; // typically 1.0 171 | template int(16) volume = 0x0100; // typically, full volume 172 | const bit(16) reserved = 0; 173 | const unsigned int(32)[2] reserved = 0; 174 | // Unity matrix 175 | template int(32)[9] matrix = { 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 }; 176 | bit(32)[6] pre_defined = 0; 177 | unsigned int(32) next_track_ID; 178 | } 179 | 180 | 8.2.2.3 Semantics 181 | 182 | `version` is an integer that specifies the version of this box (0 or 1 in this specification) 183 | `creation_time` is an integer that declares the creation time of the 184 | presentation (in seconds since midnight, Jan. 1, 1904, in UTC time) 185 | `modification_time` is an integer that declares the most recent time the 186 | presentation was modified (in seconds since midnight, Jan. 1, 1904, in UTC time) 187 | `timescale` is an integer that specifies the time-scale for the entire presentation; 188 | this is the number of time units that pass in one second. For example, 189 | a time coordinate system that measures time in sixtieths of a second has a time scale of 60. 190 | `duration` is an integer that declares length of the presentation (in the indicated timescale). 191 | This property is derived from the presentation’s tracks: 192 | the value of this field corresponds to the duration of the longest track in 193 | the presentation. If the duration cannot be determined then duration is set 194 | to all 1s. 195 | `rate` is a fixed point 16.16 number that indicates the preferred rate to play 196 | the presentation; 1.0 (0x00010000) is normal forward playback 197 | 198 | `volume` is a fixed point 8.8 number that indicates the preferred playback volume. 199 | 1.0 (0x0100) is full volume. 200 | `matrix` provides a transformation matrix for the video; (u,v,w) are restricted here to (0,0,1), 201 | hex values (0,0,0x40000000). 202 | `next_track_ID` is a non-zero integer that indicates a value to use for the track ID of 203 | the next track to be added to this presentation. Zero is not a valid track ID value. 204 | The value of next_track_ID shall be larger than the largest track-ID in use. 205 | If this value is equal to all 1s (32-bit maxint), and a new media track is to be added, 206 | then a search must be made in the file for an unused track identifier. 207 | **/ 208 | 209 | #[derive(Debug, Clone)] 210 | pub struct Mvhd { 211 | header: Header, 212 | creation_time: u64, 213 | modification_time: u64, 214 | timescale: u32, 215 | duration: u64, 216 | 217 | rate: f64, 218 | volume: f64, 219 | matrix: Matrix, 220 | next_track_id: u32 221 | } 222 | 223 | impl Mvhd { 224 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 225 | header.parse_version(f); 226 | header.parse_flags(f); 227 | 228 | let curr_offset = f.offset(); 229 | 230 | let mut length = 0u64; 231 | 232 | let mut creation_time = 0u64; 233 | let mut modification_time = 0u64; 234 | let mut timescale = 0u32; 235 | let mut duration = 0u64; 236 | assert!(header.version.is_some()); 237 | 238 | if header.version.unwrap() == 1u8 { 239 | creation_time = f.read_u64().unwrap(); 240 | modification_time = f.read_u64().unwrap(); 241 | timescale = f.read_u32().unwrap(); 242 | duration = f.read_u64().unwrap(); 243 | length += 28; 244 | } else { 245 | // header version == 0 246 | creation_time = f.read_u32().unwrap() as u64; 247 | modification_time = f.read_u32().unwrap() as u64; 248 | timescale = f.read_u32().unwrap(); 249 | duration = f.read_u32().unwrap() as u64; 250 | length += 16; 251 | } 252 | // fixed point 16.16 number 253 | let rate = f.read_fixed_point(16, 16).unwrap(); // u32 254 | length += 4; 255 | 256 | // fixed point 8.8 number 257 | let volume = f.read_fixed_point(8, 8).unwrap(); // u16 258 | length += 2; 259 | 260 | // 10 Bytes reserved 261 | length += 10; 262 | 263 | f.seek(curr_offset+length); 264 | // matrix 265 | let matrix: Matrix = f.read_matrix().unwrap(); // 36 Bytes 266 | length += 36; 267 | 268 | // 24 Bytes 269 | length += 24; 270 | f.seek(curr_offset+length); 271 | 272 | let next_track_id = f.read_u32().unwrap(); 273 | length += 4; 274 | 275 | f.offset_inc(length); 276 | 277 | Ok(Mvhd{ 278 | header: header, 279 | creation_time: creation_time, 280 | modification_time: modification_time, 281 | timescale: timescale, 282 | duration: duration, 283 | 284 | rate: rate, 285 | volume: volume, 286 | matrix: matrix, 287 | next_track_id: next_track_id 288 | }) 289 | } 290 | 291 | } 292 | 293 | /** 294 | 295 | Box Type : ‘trak’ 296 | Container: Movie Box (‘moov’) 297 | Mandatory: Yes 298 | Quantity : One or more 299 | 300 | This is a container box for a single track of a presentation. 301 | A presentation consists of one or more tracks. 302 | Each track is independent of the other tracks in the presentation 303 | and carries its own temporal and spatial information. 304 | Each track will contain its associated Media Box. 305 | 306 | Tracks are used for two purposes: (a) to contain media data (media tracks) 307 | and (b) to contain packetization information for streaming protocols (hint tracks). 308 | 309 | There shall be at least one media track within an ISO file, 310 | and all the media tracks that contributed to the hint tracks shall remain in the file, 311 | even if the media data within them is not referenced by the hint tracks; 312 | after deleting all hint tracks, the entire un-hinted presentation shall remain. 313 | 314 | 8.3.1.2 Syntax 315 | 316 | aligned(8) class TrackBox extends Box(‘trak’) { 317 | 318 | } 319 | 320 | **/ 321 | 322 | #[derive(Debug, Clone)] 323 | pub struct Trak { 324 | header: Header, 325 | children: Vec 326 | } 327 | 328 | impl Trak { 329 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 330 | let children: Vec = Atom::parse_children(f); 331 | Ok(Trak{ 332 | header: header, 333 | children: children 334 | }) 335 | } 336 | } 337 | 338 | /** 339 | 8.3.2.1 340 | 341 | Box Type : ‘tkhd’ 342 | Container: Track Box (‘trak’) 343 | Mandatory: Yes 344 | Quantity : Exactly one 345 | 346 | 347 | This box specifies the characteristics of a single track. Exactly one 348 | Track Header Box is contained in a track. 349 | 350 | In the absence of an edit list, the presentation of a track starts 351 | at the beginning of the overall presentation. An empty edit is used 352 | to offset the start time of a track. 353 | 354 | The default value of the track header flags for media tracks 355 | is 7 (track_enabled, track_in_movie, track_in_preview). 356 | If in a presentation all tracks have neither track_in_movie nor track_in_preview set, 357 | then all tracks shall be treated as if both flags were set on all tracks. 358 | Server hint tracks should have the `track_in_movie` and `track_in_preview` set to 0, 359 | so that they are ignored for local playback and preview. 360 | 361 | Under the ‘iso3’ brand or brands that share its requirements, 362 | the width and height in the track header are measured on a notional 'square' (uniform) grid. 363 | Track video data is normalized to these dimensions (logically) before any transformation 364 | or placement caused by a layup or composition system. Track (and movie) matrices, if used, 365 | also operate in this uniformly-scaled space. 366 | 367 | 8.3.2.2 Syntax 368 | 369 | aligned(8) class TrackHeaderBox 370 | extends FullBox(‘tkhd’, version, flags){ 371 | if (version==1) { 372 | unsigned int(64) creation_time; 373 | unsigned int(64) modification_time; 374 | unsigned int(32) track_ID; 375 | const unsigned int(32) reserved = 0; 376 | unsigned int(64) duration; 377 | } else { // version==0 378 | unsigned int(32) creation_time; 379 | unsigned int(32) modification_time; 380 | unsigned int(32) track_ID; 381 | const unsigned int(32) reserved = 0; 382 | unsigned int(32) duration; 383 | } 384 | const unsigned int(32)[2] reserved = 0; 385 | template int(16) layer = 0; 386 | template int(16) alternate_group = 0; 387 | template int(16) volume = {if track_is_audio 0x0100 else 0}; 388 | const unsigned int(16) reserved = 0; 389 | // unity matrix 390 | template int(32)[9] matrix = { 0x00010000,0,0,0,0x00010000,0,0,0,0x40000000 }; 391 | 392 | unsigned int(32) width; 393 | unsigned int(32) height; 394 | } 395 | 396 | 8.3.2.3 Semantics 397 | 398 | `version` is an integer that specifies the version of this box (0 or 1 in this specification) 399 | `flags` is a 24-bit integer with flags; the following values are defined: 400 | Track_enabled: Indicates that the track is enabled. 401 | Flag value is 0x000001. 402 | A disabled track (the low bit is zero) is treated as if it were not present. 403 | Track_in_movie: Indicates that the track is used in the presentation. 404 | Flag value is 0x000002. 405 | Track_in_preview: Indicates that the track is used when previewing 406 | the presentation. Flag value is 0x000004. 407 | 408 | `creation_time` is an integer that declares the creation time of 409 | this track (in seconds since midnight, Jan. 1, 1904, in UTC time) 410 | `modification_time` is an integer that declares the most recent time 411 | the track was modified (in seconds since midnight, Jan. 1, 1904, in UTC time) 412 | `track_ID` is an integer that uniquely identifies this track over 413 | the entire life-time of this presentation. 414 | Track IDs are never re-used and cannot be zero. 415 | `duration` is an integer that indicates the duration of 416 | this track (in the timescale indicated in the Movie Header Box). 417 | The value of this field is equal to the sum of the durations 418 | of all of the track’s edits. If there is no edit list, 419 | then the duration is the sum of the sample durations, 420 | converted into the timescale in the Movie Header Box. 421 | If the duration of this track cannot be determined then duration is set to all 1s. 422 | `layer` specifies the front-to-back ordering of video tracks; 423 | tracks with lower numbers are closer to the viewer. 0 is the normal value, 424 | and -1 would be in front of track 0, and so on. 425 | `alternate_group` is an integer that specifies a group or collection of tracks. 426 | If this field is 0 there is no information on possible relations to other tracks. 427 | If this field is not 0, it should be the same for tracks that contain alternate 428 | data for one another and different for tracks belonging to different such groups. 429 | Only one track within an alternate group should be played or streamed at any one time, 430 | and must be distinguishable from other tracks in the group via attributes such as bitrate, 431 | codec, language, packet size etc. A group may have only one member. 432 | `volume` is a fixed 8.8 value specifying the track's relative audio volume. 433 | Full volume is 1.0 (0x0100) and is the normal value. 434 | Its value is irrelevant for a purely visual track. 435 | Tracks may be composed by combining them according to their volume, 436 | and then using the overall Movie Header Box volume setting; 437 | or more complex audio composition (e.g. MPEG-4 BIFS) may be used. 438 | `matrix` provides a transformation matrix for the video; (u,v,w) are restricted here 439 | to (0,0,1), hex (0,0,0x40000000). 440 | `width` and `height` specify the track's visual presentation size as fixed-point 16.16 values. 441 | These need not be the same as the pixel dimensions of the images, 442 | which is documented in the sample description(s); 443 | all images in the sequence are scaled to this size, 444 | before any overall transformation of the track represented by the matrix. 445 | The pixel dimensions of the images are the default values. 446 | **/ 447 | 448 | #[derive(Debug, Clone)] 449 | pub struct Tkhd { 450 | header: Header 451 | // creation_time: u64, 452 | // modification_time: u64, 453 | // track_id: u32, 454 | // duration: u64, 455 | 456 | // layer: i16, 457 | // alternate_group: i16, 458 | // // fixed 8.8 value 459 | // volume: f64, // {if track_is_audio 0x0100 else 0}; 460 | 461 | // matrix: Matrix, 462 | // // fixed-point 16.16 values 463 | // width: f64, 464 | // height: f64 465 | } 466 | 467 | impl Tkhd { 468 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 469 | header.parse_version(f); 470 | header.parse_flags(f); 471 | 472 | let curr_offset = f.offset(); 473 | f.seek(curr_offset+header.data_size); 474 | f.offset_inc(header.data_size); 475 | Ok(Tkhd{ 476 | header: header 477 | }) 478 | } 479 | } 480 | 481 | /** 482 | Box Type : `tref` 483 | Container: Track Box(`trak`) 484 | Mandatory: No 485 | Quantity : Zero or one 486 | 487 | 8.3.3.2 Syntax 488 | 489 | aligned(8) class TrackReferenceBox extends Box(‘tref’) { 490 | 491 | } 492 | aligned(8) class TrackReferenceTypeBox (unsigned int(32) reference_type) extends Box(reference_type) { 493 | unsigned int(32) track_IDs[]; 494 | } 495 | 496 | 8.3.3.3 Semantics 497 | 498 | The Track Reference Box contains track reference type boxes. 499 | 500 | `track_ID` is an integer that provides a reference from the containing track 501 | to another track in the presentation. track_IDs are never re-used and cannot be equal to zero. 502 | The `reference_type` shall be set to one of the following values, or a value registered 503 | or from a derived specification or registration: 504 | * `hint` the referenced track(s) contain the original media for this hint track 505 | * `cdsc` this track describes the referenced track. 506 | * `hind` this track depends on the referenced hint track, i.e., 507 | it should only be used if the referenced hint track is used. 508 | * `vdep` this track contains auxiliary depth video information for the referenced video track 509 | * `vplx` this track contains auxiliary parallax video information for the referenced video track 510 | 511 | **/ 512 | 513 | #[derive(Debug, Clone)] 514 | pub struct Tref { 515 | header: Header 516 | } 517 | 518 | impl Tref { 519 | pub fn parse(f: &mut Mp4File, header: Header) -> Result{ 520 | let curr_offset = f.offset(); 521 | f.seek(curr_offset+header.data_size); 522 | f.offset_inc(header.data_size); 523 | Ok(Tref{ 524 | header: header 525 | }) 526 | } 527 | } 528 | 529 | /** 530 | Box Type : ‘trgr’ 531 | Container: Track Box (‘trak’) 532 | Mandatory: No 533 | Quantity : Zero or one 534 | 535 | **/ 536 | 537 | #[derive(Debug, Clone)] 538 | pub struct Trgr { 539 | header: Header 540 | } 541 | 542 | impl Trgr { 543 | pub fn parse(f: &mut Mp4File, header: Header) -> Result{ 544 | let curr_offset = f.offset(); 545 | f.seek(curr_offset+header.data_size); 546 | f.offset_inc(header.data_size); 547 | Ok(Trgr{ 548 | header: header 549 | }) 550 | } 551 | } 552 | 553 | /** 554 | Box Type : `mdia` 555 | Container: Track Box (‘trak’) 556 | Mandatory: Yes 557 | Quantity : Exactly One 558 | 559 | **/ 560 | 561 | 562 | #[derive(Debug, Clone)] 563 | pub struct Mdia { 564 | header: Header, 565 | children: Vec 566 | } 567 | 568 | impl Mdia { 569 | pub fn parse(f: &mut Mp4File, header: Header) -> Result{ 570 | let children: Vec = Atom::parse_children(f); 571 | Ok(Mdia{ 572 | header: header, 573 | children: children 574 | }) 575 | } 576 | } 577 | 578 | /** 579 | Box Type : `mdhd` 580 | Container: Media Box(`mdia`) 581 | Mandatory: Yes 582 | Quantity : Exactly one 583 | 584 | 8.4.2.2 Syntax 585 | aligned(8) class MediaHeaderBox extends FullBox(‘mdhd’, version, 0) { 586 | if (version==1) { 587 | unsigned int(64) creation_time; 588 | unsigned int(64) modification_time; 589 | unsigned int(32) timescale; 590 | unsigned int(64) duration; 591 | } else { // version==0 592 | unsigned int(32) creation_time; 593 | unsigned int(32) modification_time; 594 | unsigned int(32) timescale; 595 | unsigned int(32) duration; 596 | } 597 | bit(1) pad = 0; 598 | unsigned int(5)[3] language; // ISO-639-2/T language code 599 | unsigned int(16) pre_defined = 0; 600 | } 601 | 602 | **/ 603 | 604 | #[derive(Debug, Clone)] 605 | pub struct Mdhd { 606 | header: Header, 607 | creation_time: u64, 608 | modification_time: u64, 609 | timescale: u32, 610 | duration: u64, 611 | 612 | language: String 613 | } 614 | 615 | impl Mdhd { 616 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 617 | header.parse_version(f); 618 | header.parse_flags(f); 619 | 620 | let curr_offset = f.offset(); 621 | 622 | let mut length = 0u64; 623 | 624 | let mut creation_time = 0u64; 625 | let mut modification_time = 0u64; 626 | let mut timescale = 0u32; 627 | let mut duration = 0u64; 628 | assert!(header.version.is_some()); 629 | 630 | if header.version.unwrap() == 1u8 { 631 | creation_time = f.read_u64().unwrap(); 632 | modification_time = f.read_u64().unwrap(); 633 | timescale = f.read_u32().unwrap(); 634 | duration = f.read_u64().unwrap(); 635 | length += 28; 636 | } else { 637 | // header version == 0 638 | creation_time = f.read_u32().unwrap() as u64; 639 | modification_time = f.read_u32().unwrap() as u64; 640 | timescale = f.read_u32().unwrap(); 641 | duration = f.read_u32().unwrap() as u64; 642 | length += 16; 643 | } 644 | 645 | // 16 Bytes 646 | // pad: 1 Bit 647 | // language: 15 Bit; 648 | let language = f.read_iso639_code().unwrap(); // 2 Bytes, u16 649 | length += 2; 650 | 651 | // unsigned int(16) pre_defined = 0; 652 | length += 2; 653 | f.seek(curr_offset+length); 654 | f.offset_inc(length); 655 | 656 | Ok(Mdhd{ 657 | header: header, 658 | creation_time: creation_time, 659 | modification_time: modification_time, 660 | timescale: timescale, 661 | duration: duration, 662 | language: language 663 | }) 664 | } 665 | } 666 | 667 | /** 668 | 8.4.3.2 Syntax 669 | 670 | aligned(8) class HandlerBox extends FullBox(‘hdlr’, version = 0, 0) { 671 | unsigned int(32) pre_defined = 0; 672 | unsigned int(32) handler_type; 673 | const unsigned int(32)[3] reserved = 0; 674 | string name; 675 | } 676 | 677 | 8.4.3.3 Semantics 678 | 679 | `version` is an integer that specifies the version of this box 680 | `handler_type` when present in a media box, is an integer containing one of the following values, 681 | or a value from a derived specification: 682 | ‘vide’ Video track 683 | ‘soun’ Audio track 684 | ‘hint’ Hint track 685 | ‘meta’ Timed Metadata track 686 | ‘auxv’ Auxiliary Video track 687 | 688 | `handler_type` when present in a meta box, contains an appropriate value to 689 | indicate the format of the meta box contents. The value ‘null’ can be 690 | used in the primary meta box to indicate that it is merely being used to hold resources. 691 | `name` is a null-terminated string in UTF-8 characters which gives a human-readable name 692 | for the track type (for debugging and inspection purposes). 693 | **/ 694 | 695 | #[derive(Debug, Clone)] 696 | pub struct Hdlr { 697 | header: Header, 698 | handler_type: String, 699 | name: String 700 | } 701 | 702 | impl Hdlr { 703 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 704 | header.parse_version(f); 705 | header.parse_flags(f); 706 | 707 | // let curr_offset = f.offset(); 708 | // f.seek(curr_offset+header.data_size); 709 | 710 | let pre_defined = f.read_u32().unwrap(); 711 | // u32 = [u8, u8, u8, u8] 712 | let handler_type_bytes: [u8; 4] = [ 713 | f.read_u8().unwrap(), f.read_u8().unwrap(), 714 | f.read_u8().unwrap(), f.read_u8().unwrap() 715 | ]; 716 | let handler_type = String::from_utf8(handler_type_bytes.to_vec()).unwrap(); 717 | // reserved 718 | f.read_u32().unwrap(); 719 | f.read_u32().unwrap(); 720 | f.read_u32().unwrap(); 721 | 722 | let name_length = header.data_size - 20; 723 | let mut name_bytes = Vec::new(); 724 | for _ in 0..name_length { 725 | name_bytes.push(f.read_u8().unwrap()); 726 | } 727 | let name = String::from_utf8(name_bytes).unwrap(); 728 | 729 | f.offset_inc(header.data_size); 730 | Ok(Hdlr{ 731 | header: header, 732 | handler_type: handler_type, 733 | name: name 734 | }) 735 | } 736 | } 737 | 738 | #[derive(Debug, Clone)] 739 | pub struct Minf { 740 | header: Header, 741 | children: Vec // Box Types: ‘vmhd’, ‘smhd’, ’hmhd’, ‘nmhd’ 742 | } 743 | 744 | impl Minf { 745 | pub fn parse(f: &mut Mp4File, header: Header) -> Result{ 746 | let children: Vec = Atom::parse_children(f); 747 | Ok(Minf{ 748 | header: header, 749 | children: children 750 | }) 751 | } 752 | } 753 | 754 | #[derive(Debug, Clone)] 755 | pub struct Vmhd { 756 | header: Header, 757 | graphicsmode: u16, 758 | opcolor: [u16; 3] 759 | } 760 | 761 | impl Vmhd { 762 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 763 | header.parse_version(f); 764 | header.parse_flags(f); 765 | 766 | let curr_offset = f.offset(); 767 | 768 | let graphicsmode = f.read_u16().unwrap(); 769 | // red, greenm blue 770 | let opcolor: [u16; 3] = [ 771 | f.read_u16().unwrap(), f.read_u16().unwrap(), 772 | f.read_u16().unwrap() 773 | ]; 774 | 775 | f.offset_inc(8); 776 | 777 | Ok(Vmhd{ 778 | header: header, 779 | graphicsmode: graphicsmode, 780 | opcolor: opcolor 781 | }) 782 | } 783 | } 784 | 785 | #[derive(Debug, Clone)] 786 | pub struct Smhd { 787 | header: Header, 788 | balance: f64 // fixed-point 8.8 number 789 | } 790 | 791 | impl Smhd { 792 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 793 | header.parse_version(f); 794 | header.parse_flags(f); 795 | 796 | let curr_offset = f.offset(); 797 | 798 | let balance = f.read_fixed_point(8, 8).unwrap(); // 2 Bytes 799 | // reserved 800 | f.read_u16().unwrap(); 801 | 802 | f.offset_inc(4); 803 | 804 | Ok(Smhd{ 805 | header: header, 806 | balance: balance 807 | }) 808 | } 809 | } 810 | 811 | #[derive(Debug, Clone)] 812 | pub struct Hmhd { 813 | header: Header, 814 | max_pdu_size: u16, 815 | avg_pdu_size: u16, 816 | max_bitrate : u32, 817 | avg_bitrate : u32 818 | } 819 | 820 | impl Hmhd { 821 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 822 | header.parse_version(f); 823 | header.parse_flags(f); 824 | 825 | let curr_offset = f.offset(); 826 | 827 | let max_pdu_size = f.read_u16().unwrap(); 828 | let avg_pdu_size = f.read_u16().unwrap(); 829 | let max_bitrate = f.read_u32().unwrap(); 830 | let avg_bitrate = f.read_u32().unwrap(); 831 | // reserved 832 | f.read_u32().unwrap(); 833 | 834 | f.offset_inc(16); 835 | 836 | Ok(Hmhd{ 837 | header: header, 838 | max_pdu_size: max_pdu_size, 839 | avg_pdu_size: avg_pdu_size, 840 | max_bitrate: max_bitrate, 841 | avg_bitrate: avg_bitrate 842 | }) 843 | } 844 | } 845 | 846 | #[derive(Debug, Clone)] 847 | pub struct Nmhd { 848 | header: Header 849 | } 850 | 851 | impl Nmhd { 852 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 853 | header.parse_version(f); 854 | header.parse_flags(f); 855 | 856 | Ok(Nmhd{ 857 | header: header 858 | }) 859 | } 860 | } 861 | 862 | #[derive(Debug, Clone)] 863 | pub struct Stbl { 864 | header: Header, 865 | children: Vec 866 | } 867 | 868 | impl Stbl { 869 | pub fn parse(f: &mut Mp4File, header: Header) -> Result{ 870 | let children: Vec = Atom::parse_children(f); 871 | Ok(Stbl{ 872 | header: header, 873 | children: children 874 | }) 875 | } 876 | } 877 | 878 | #[derive(Debug, Clone)] 879 | pub struct Stsz { 880 | header: Header, 881 | sample_size: u32, 882 | sample_count: u32, 883 | entry_size: Option> 884 | } 885 | 886 | impl Stsz { 887 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 888 | header.parse_version(f); 889 | header.parse_flags(f); 890 | 891 | // let curr_offset = f.offset(); 892 | // f.seek(curr_offset+header.data_size); 893 | let sample_size : u32 = f.read_u32().unwrap(); 894 | let sample_count: u32 = f.read_u32().unwrap(); 895 | let mut entry_size = None; 896 | 897 | if sample_size == 0u32 { 898 | let mut _entry_size: Vec = Vec::new(); 899 | for _ in 0..sample_count { 900 | _entry_size.push(f.read_u32().unwrap()); 901 | } 902 | entry_size = Some(_entry_size); 903 | } 904 | 905 | f.offset_inc(header.data_size); 906 | 907 | Ok(Stsz{ 908 | header: header, 909 | sample_size: sample_size, 910 | sample_count: sample_count, 911 | entry_size: entry_size 912 | }) 913 | } 914 | } 915 | 916 | /** 917 | aligned(8) class CompactSampleSizeBox extends FullBox(‘stz2’, version = 0, 0) { 918 | unsigned int(24) reserved = 0; 919 | unisgned int(8) field_size; 920 | unsigned int(32) sample_count; 921 | for (i=1; i <= sample_count; i++) { 922 | unsigned int(field_size) entry_size; 923 | } 924 | } 925 | 926 | 8.7.3.3.2 Semantics 927 | 928 | `version` is an integer that specifies the version of this box 929 | `field_size` is an integer specifying the size in bits of the entries in the following table; 930 | it shall take the value 4, 8 or 16. 931 | If the value 4 is used, then each byte contains two values: entry[i]<<4 + entry[i+1]; 932 | if the sizes do not fill an integral number of bytes, the last byte is padded with zeros. 933 | `sample_count` is an integer that gives the number of entries in the following table 934 | `entry_size` is an integer specifying the size of a sample, indexed by its number. 935 | 936 | **/ 937 | 938 | #[derive(Debug, Clone)] 939 | pub struct Stz2 { 940 | header: Header, 941 | field_size: u8, 942 | sample_count: u32, 943 | entry_size: Vec 944 | } 945 | 946 | impl Stz2 { 947 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 948 | header.parse_version(f); 949 | header.parse_flags(f); 950 | // let curr_offset = f.offset(); 951 | // f.seek(curr_offset+header.data_size); 952 | let _ = f.read_u32().unwrap(); 953 | let field_size = f.read_u8().unwrap(); 954 | let sample_count = f.read_u32().unwrap(); 955 | // value 4, 8 or 16. 956 | assert!(field_size == 4u8 || field_size == 8u8 || field_size == 16u8); 957 | 958 | let mut entry_size: Vec = Vec::new(); 959 | 960 | let mut next_val: Option = None; 961 | 962 | for _ in 0..sample_count { 963 | if field_size == 4u8 { 964 | if next_val.is_some() { 965 | entry_size.push(next_val.unwrap()); 966 | next_val = None; 967 | } else { 968 | let bits = format!("{:08b}", f.read_u8().unwrap()); 969 | entry_size.push(u32::from_str_radix(&bits[0..4], 2).unwrap()); 970 | next_val = Some(u32::from_str_radix(&bits[4..8], 2).unwrap()); 971 | } 972 | } else if field_size == 8u8 { 973 | entry_size.push(f.read_u8().unwrap() as u32); 974 | } else if field_size == 16u8 { 975 | entry_size.push(f.read_u16().unwrap() as u32); 976 | } else { 977 | panic!("STZ2 parse error."); 978 | } 979 | } 980 | 981 | 982 | f.offset_inc(header.data_size); 983 | Ok(Stz2{ 984 | header: header, 985 | field_size : field_size, 986 | sample_count: sample_count, 987 | entry_size : entry_size 988 | }) 989 | } 990 | } 991 | 992 | /** 993 | 994 | 8.7.4.2 Syntax 995 | 996 | aligned(8) class SampleToChunkBox extends FullBox(‘stsc’, version = 0, 0) { 997 | unsigned int(32) entry_count; 998 | for (i=1; i <= entry_count; i++) { 999 | unsigned int(32) first_chunk; 1000 | unsigned int(32) samples_per_chunk; unsigned int(32) sample_description_index; 1001 | } 1002 | } 1003 | 1004 | 8.7.4.3 Semantics 1005 | `version` is an integer that specifies the version of this box 1006 | `entry_count` is an integer that gives the number of entries in the following table 1007 | `first_chunk` is an integer that gives the index of the first chunk in this run of chunks 1008 | that share the same samples-per-chunk and sample-description-index; 1009 | the index of the first chunk in a track has the value 1 (the `first_chunk` field in the 1010 | first record of this box has the value 1, identifying that the first sample maps to the first chunk). 1011 | `samples_per_chunk` is an integer that gives the number of samples in each of these chunks 1012 | sample_description_index is an integer that gives the index of the sample entry 1013 | that describes the samples in this chunk. The index ranges from 1 to the number 1014 | of sample entries in the Sample Description Box 1015 | 1016 | 1017 | **/ 1018 | 1019 | #[derive(Debug, Clone)] 1020 | pub struct Stsc { 1021 | header: Header, 1022 | entry_count: u32, 1023 | entries: Vec 1024 | } 1025 | 1026 | impl Stsc { 1027 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 1028 | header.parse_version(f); 1029 | header.parse_flags(f); 1030 | // let curr_offset = f.offset(); 1031 | // f.seek(curr_offset+header.data_size); 1032 | 1033 | let entry_count = f.read_u32().unwrap(); 1034 | let mut entries: Vec = Vec::new(); 1035 | for _ in 0..entry_count { 1036 | let entry = Entry { 1037 | first_chunk: f.read_u32().unwrap(), 1038 | samples_per_chunk: f.read_u32().unwrap(), 1039 | sample_description_index: f.read_u32().unwrap(), 1040 | }; 1041 | entries.push(entry); 1042 | } 1043 | 1044 | f.offset_inc(header.data_size); 1045 | Ok(Stsc{ 1046 | header : header, 1047 | entry_count: entry_count, 1048 | entries : entries 1049 | }) 1050 | } 1051 | } 1052 | 1053 | /** 1054 | 8.7.5 Chunk Offset Box 1055 | 8.7.5.1 Definition 1056 | 1057 | 1058 | Box Type : ‘stco’, ‘co64’ 1059 | Container: Sample Table Box (‘stbl’) 1060 | Mandatory: Yes 1061 | Quantity : Exactly one variant must be present 1062 | 1063 | The chunk offset table gives the index of each chunk into the containing file. 1064 | There are two variants, permitting the use of 32-bit or 64-bit offsets. 1065 | The latter is useful when managing very large presentations. At most one of these 1066 | variants will occur in any single instance of a sample table. 1067 | 1068 | Offsets are file offsets, not the offset into any box within the file (e.g. Media Data Box). 1069 | This permits referring to media data in files without any box structure. It does also mean 1070 | that care must be taken when constructing a self-contained ISO file with its metadata (Movie Box) 1071 | at the front, as the size of the Movie Box will affect the chunk offsets to the media data. 1072 | 1073 | 8.7.5.2 Syntax 1074 | aligned(8) class ChunkOffsetBox 1075 | extends FullBox(‘stco’, version = 0, 0) { 1076 | unsigned int(32) entry_count; 1077 | for (i=1; i <= entry_count; i++) { 1078 | unsigned int(32) chunk_offset; 1079 | } 1080 | } 1081 | aligned(8) class ChunkLargeOffsetBox 1082 | extends FullBox(‘co64’, version = 0, 0) { 1083 | unsigned int(32) entry_count; 1084 | for (i=1; i <= entry_count; i++) { 1085 | unsigned int(64) chunk_offset; 1086 | } 1087 | } 1088 | 1089 | 8.7.5.3 Semantics 1090 | `version` is an integer that specifies the version of this box 1091 | `entry_count` is an integer that gives the number of entries in the following table 1092 | `chunk_offset` is a 32 or 64 bit integer that gives the offset of the start 1093 | of a chunk into its containing media file. 1094 | 1095 | **/ 1096 | 1097 | #[derive(Debug, Clone)] 1098 | pub struct Stco { 1099 | header: Header, 1100 | entry_count: u32, 1101 | chunks: Vec 1102 | } 1103 | 1104 | impl Stco { 1105 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 1106 | header.parse_version(f); 1107 | header.parse_flags(f); 1108 | // let curr_offset = f.offset(); 1109 | // f.seek(curr_offset+header.data_size); 1110 | 1111 | let entry_count = f.read_u32().unwrap(); 1112 | let mut chunks: Vec = Vec::new(); 1113 | 1114 | for _ in 0..entry_count { 1115 | chunks.push(f.read_u32().unwrap()); 1116 | } 1117 | 1118 | f.offset_inc(header.data_size); 1119 | Ok(Stco{ 1120 | header : header, 1121 | entry_count: entry_count, 1122 | chunks : chunks 1123 | }) 1124 | } 1125 | } 1126 | 1127 | #[derive(Debug, Clone)] 1128 | pub struct Co64 { 1129 | header: Header, 1130 | entry_count: u32, 1131 | chunks: Vec 1132 | } 1133 | 1134 | impl Co64 { 1135 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 1136 | header.parse_version(f); 1137 | header.parse_flags(f); 1138 | // let curr_offset = f.offset(); 1139 | // f.seek(curr_offset+header.data_size); 1140 | 1141 | let entry_count = f.read_u32().unwrap(); 1142 | let mut chunks: Vec = Vec::new(); 1143 | 1144 | for _ in 0..entry_count { 1145 | chunks.push(f.read_u64().unwrap()); 1146 | } 1147 | 1148 | f.offset_inc(header.data_size); 1149 | Ok(Co64{ 1150 | header : header, 1151 | entry_count: entry_count, 1152 | chunks : chunks 1153 | }) 1154 | } 1155 | } 1156 | 1157 | /** 1158 | Box Type : `padb` 1159 | Container: Sample Table (‘stbl’) 1160 | Mandatory: No 1161 | Quantity : Zero or one 1162 | 1163 | 1164 | 8.7.6.3 Semantics 1165 | 1166 | In some streams the media samples do not occupy all bits of the bytes given by the sample size, 1167 | and are padded at the end to a byte boundary. In some cases, it is necessary to record 1168 | externally the number of padding bits used. This table supplies that information. 1169 | 1170 | 8.7.6.2 Syntax 1171 | 1172 | aligned(8) class PaddingBitsBox extends FullBox(‘padb’, version = 0, 0) { 1173 | unsigned int(32) sample_count; 1174 | int i; 1175 | for (i=0; i < ((sample_count + 1)/2); i++) { 1176 | bit(1) reserved = 0; 1177 | bit(3) pad1; 1178 | bit(1) reserved = 0; 1179 | bit(3) pad2; 1180 | } 1181 | } 1182 | 1183 | `sample_count` – counts the number of samples in the track; 1184 | it should match the count in other tables 1185 | `pad1` – a value from 0 to 7, indicating the number of 1186 | bits at the end of sample (i*2)+1. 1187 | `pad2` – a value from 0 to 7, indicating the number of 1188 | bits at the end of sample (i*2)+2 1189 | 1190 | **/ 1191 | 1192 | #[derive(Debug, Clone)] 1193 | pub struct Padb { 1194 | header: Header, 1195 | sample_count: u32 1196 | } 1197 | 1198 | impl Padb { 1199 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 1200 | header.parse_version(f); 1201 | header.parse_flags(f); 1202 | let curr_offset = f.offset(); 1203 | 1204 | let sample_count = f.read_u32().unwrap(); 1205 | // f.offset_inc(4); 1206 | // for i in 0..((sample_count+1)/2) { 1207 | // let bits = format!("{:08b}", f.read_u8().unwrap()); 1208 | // let pad1 = u32::from_str_radix(&bits[1..4], 2).unwrap(); 1209 | // let pad2 = u32::from_str_radix(&bits[5..8], 2).unwrap(); 1210 | // } 1211 | 1212 | f.seek(curr_offset+header.data_size); 1213 | f.offset_inc(header.data_size); 1214 | Ok(Padb{ 1215 | header : header, 1216 | sample_count: sample_count 1217 | }) 1218 | } 1219 | } 1220 | 1221 | #[derive(Debug, Clone)] 1222 | pub struct Stsd { 1223 | header: Header 1224 | } 1225 | 1226 | impl Stsd { 1227 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 1228 | header.parse_version(f); 1229 | header.parse_flags(f); 1230 | let curr_offset = f.offset(); 1231 | f.seek(curr_offset+header.data_size); 1232 | f.offset_inc(header.data_size); 1233 | Ok(Stsd{ 1234 | header: header 1235 | }) 1236 | } 1237 | } 1238 | 1239 | #[derive(Debug, Clone)] 1240 | pub struct Stdp { 1241 | header: Header 1242 | } 1243 | 1244 | impl Stdp { 1245 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 1246 | header.parse_version(f); 1247 | header.parse_flags(f); 1248 | let curr_offset = f.offset(); 1249 | f.seek(curr_offset+header.data_size); 1250 | f.offset_inc(header.data_size); 1251 | Ok(Stdp{ 1252 | header: header 1253 | }) 1254 | } 1255 | } 1256 | 1257 | /** 1258 | 1259 | 8.6.1.2 1260 | 8.6.1.2.1 Decoding Time to Sample Box Definition 1261 | Box Type : `stts` 1262 | Container: Sample Table Box (‘stbl’) 1263 | Mandatory: Yes 1264 | Quantity : Exactly one 1265 | 1266 | This box contains a compact version of a table that allows indexing from decoding time to sample number. 1267 | Other tables give sample sizes and pointers, from the sample number. Each entry in the table gives 1268 | the number of consecutive samples with the same time delta, and the delta of those samples. 1269 | By adding the deltas a complete time-to-sample map may be built. 1270 | 1271 | The Decoding Time to Sample Box contains decode time delta's: 1272 | DT(n+1) = DT(n) + STTS(n) where STTS(n) is the (uncompressed) table entry for sample n. 1273 | 1274 | The sample entries are ordered by decoding time stamps; therefore the deltas are all non-negative. 1275 | 1276 | The DT axis has a zero origin; DT(i) = SUM(for j=0 to i-1 of delta(j)), 1277 | and the sum of all deltas gives the length of the media in 1278 | the track (not mapped to the overall timescale, and not considering any edit list). 1279 | 1280 | The Edit List Box provides the initial CT value if it is non-empty (non-zero). 1281 | 1282 | 8.6.1.2.2 Syntax 1283 | 1284 | aligned(8) class TimeToSampleBox extends FullBox(’stts’, version = 0, 0) { 1285 | unsigned int(32) entry_count; 1286 | int i; 1287 | for (i=0; i < entry_count; i++) { 1288 | unsigned int(32) sample_count; 1289 | unsigned int(32) sample_delta; 1290 | } 1291 | } 1292 | 1293 | For example with Table 2, the entry would be: 1294 | 1295 | +++++++++++++++++++++++++++++++ 1296 | | Sample count | Sample-delta | 1297 | +++++++++++++++++++++++++++++++ 1298 | | 14 | 10 | 1299 | +++++++++++++++++++++++++++++++ 1300 | 1301 | 1302 | 8.6.1.2.3 Semantics 1303 | 1304 | `version` - is an integer that specifies the version of this box. 1305 | `entry_count` - is an integer that gives the number of entries in the following table. 1306 | `sample_count` - is an integer that counts the number of consecutive samples that have the given 1307 | duration. 1308 | `sample_delta` - is an integer that gives the delta of these samples in the time-scale of the media. 1309 | 1310 | 1311 | **/ 1312 | 1313 | #[derive(Debug, Clone)] 1314 | pub struct STTS_Entry { 1315 | sample_count: u32, 1316 | sample_delta: u32 1317 | } 1318 | 1319 | #[derive(Debug, Clone)] 1320 | pub struct Stts { 1321 | header: Header, 1322 | entry_count: u32, 1323 | entries: Vec 1324 | } 1325 | 1326 | impl Stts { 1327 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 1328 | header.parse_version(f); 1329 | header.parse_flags(f); 1330 | // let curr_offset = f.offset(); 1331 | // f.seek(curr_offset+header.data_size); 1332 | let entry_count = f.read_u32().unwrap(); 1333 | let mut entries = Vec::new(); 1334 | 1335 | for _ in 0..entry_count { 1336 | let sample_count: u32 = f.read_u32().unwrap(); 1337 | let sample_delta: u32 = f.read_u32().unwrap(); 1338 | entries.push(STTS_Entry{ 1339 | sample_count: sample_count, 1340 | sample_delta: sample_delta 1341 | }); 1342 | } 1343 | 1344 | f.offset_inc(header.data_size); 1345 | Ok(Stts{ 1346 | header: header, 1347 | entry_count: entry_count, 1348 | entries: entries 1349 | }) 1350 | } 1351 | } 1352 | 1353 | /** 1354 | 8.6.1.3 1355 | 8.6.1.3.1 Composition Time to Sample Box Definition 1356 | Box Type : `ctts` 1357 | Container: Sample Table Box (‘stbl’) 1358 | Mandatory: No 1359 | Quantity : Zero or one 1360 | 1361 | 1362 | This box provides the offset between decoding time and composition time. 1363 | 1364 | In version 0 of this box the decoding time must be less than the composition time, 1365 | and the offsets are expressed as unsigned numbers such that CT(n) = DT(n) + CTTS(n) 1366 | where CTTS(n) is the (uncompressed) table entry for sample n. 1367 | 1368 | In version 1 of this box, the composition timeline and the decoding timeline are 1369 | still derived from each other, but the offsets are signed. 1370 | It is recommended that for the computed composition timestamps, 1371 | there is exactly one with the value 0 (zero). 1372 | 1373 | For either version of the box, each sample must have a unique composition timestamp value, 1374 | that is, the timestamp for two samples shall never be the same. 1375 | 1376 | It may be true that there is no frame to compose at time 0; the handling of 1377 | this is unspecified (systems might display the first frame for longer, or a suitable fill colour). 1378 | 1379 | When version 1 of this box is used, the CompositionToDecodeBox may also be present in 1380 | the sample table to relate the composition and decoding timelines. 1381 | When backwards-compatibility or compatibility with an unknown set 1382 | of readers is desired, version 0 of this box should be used when possible. 1383 | In either version of this box, but particularly under version 0, 1384 | if it is desired that the media start at track time 0, and the first media 1385 | sample does not have a composition time of 0, an edit list may be used to ‘shift’ the media to time 0. 1386 | 1387 | The composition time to sample table is optional and must only be present 1388 | if DT and CT differ for any samples. 1389 | 1390 | Hint tracks do not use this box. 1391 | 1392 | For example in Table 2 1393 | 1394 | +++++++++++++++++++++++++++++++ 1395 | | Sample count | Sample_offset| 1396 | +++++++++++++++++++++++++++++++ 1397 | | 1 | 10 | 1398 | ------------------------------- 1399 | | 1 | 30 | 1400 | ------------------------------- 1401 | | 2 | 0 | 1402 | ------------------------------- 1403 | | 1 | 30 | 1404 | ------------------------------- 1405 | | 2 | 0 | 1406 | ------------------------------- 1407 | | 1 | 10 | 1408 | ------------------------------- 1409 | | 1 | 30 | 1410 | ------------------------------- 1411 | | 2 | 0 | 1412 | ------------------------------- 1413 | | 1 | 30 | 1414 | ------------------------------- 1415 | | 2 | 0 | 1416 | +++++++++++++++++++++++++++++++ 1417 | 1418 | 8.6.1.3.2 Syntax 1419 | 1420 | aligned(8) class CompositionOffsetBox extends FullBox(‘ctts’, version = 0, 0) { 1421 | unsigned int(32) entry_count; 1422 | int i; 1423 | if (version==0) { 1424 | for (i=0; i < entry_count; i++) { 1425 | unsigned int(32) sample_count; 1426 | unsigned int(32) sample_offset; 1427 | } 1428 | } else if (version == 1) { 1429 | for (i=0; i < entry_count; i++) { 1430 | unsigned int(32) sample_count; 1431 | signed int(32) sample_offset; 1432 | } 1433 | } 1434 | } 1435 | 1436 | 8.6.1.3.3 Semantics 1437 | 1438 | `version` - is an integer that specifies the version of this box. 1439 | `entry_count` is an integer that gives the number of entries in the following table. 1440 | `sample_count` is an integer that counts the number of consecutive samples that have the given offset. 1441 | sample_offset is an integer that gives the offset between CT and DT, 1442 | such that CT(n) = DT(n) + CTTS(n). 1443 | 1444 | **/ 1445 | 1446 | #[derive(Debug, Clone)] 1447 | pub struct CTTS_Entry_Offset { 1448 | sample_count: u32, 1449 | sample_offset: i32 1450 | } 1451 | 1452 | #[derive(Debug, Clone)] 1453 | pub struct Ctts { 1454 | header: Header, 1455 | entry_count: u32, 1456 | entries: Vec 1457 | } 1458 | 1459 | impl Ctts { 1460 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 1461 | header.parse_version(f); 1462 | header.parse_flags(f); 1463 | // let curr_offset = f.offset(); 1464 | // f.seek(curr_offset+header.data_size); 1465 | 1466 | let version: u8 = header.version.unwrap(); 1467 | 1468 | let entry_count = f.read_u32().unwrap(); 1469 | let mut entries = Vec::new(); 1470 | 1471 | for _ in 0..entry_count { 1472 | let sample_count: u32 = f.read_u32().unwrap(); 1473 | let mut sample_offset: i32 = 0; 1474 | 1475 | if version == 0u8 { 1476 | sample_offset = f.read_u32().unwrap() as i32; 1477 | } else { 1478 | sample_offset = f.read_i32().unwrap(); 1479 | } 1480 | 1481 | entries.push(CTTS_Entry_Offset{ 1482 | sample_count: sample_count, 1483 | sample_offset: sample_offset 1484 | }); 1485 | } 1486 | 1487 | f.offset_inc(header.data_size); 1488 | Ok(Ctts{ 1489 | header: header, 1490 | entry_count: entry_count, 1491 | entries: entries 1492 | }) 1493 | } 1494 | } 1495 | 1496 | #[derive(Debug, Clone)] 1497 | pub struct Cslg { 1498 | header: Header 1499 | } 1500 | 1501 | impl Cslg { 1502 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 1503 | header.parse_version(f); 1504 | header.parse_flags(f); 1505 | let curr_offset = f.offset(); 1506 | f.seek(curr_offset+header.data_size); 1507 | f.offset_inc(header.data_size); 1508 | Ok(Cslg{ 1509 | header: header 1510 | }) 1511 | } 1512 | } 1513 | 1514 | #[derive(Debug, Clone)] 1515 | pub struct Stss { 1516 | header: Header 1517 | } 1518 | 1519 | impl Stss { 1520 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 1521 | header.parse_version(f); 1522 | header.parse_flags(f); 1523 | let curr_offset = f.offset(); 1524 | f.seek(curr_offset+header.data_size); 1525 | f.offset_inc(header.data_size); 1526 | Ok(Stss{ 1527 | header: header 1528 | }) 1529 | } 1530 | } 1531 | 1532 | #[derive(Debug, Clone)] 1533 | pub struct Stsh { 1534 | header: Header 1535 | } 1536 | 1537 | impl Stsh { 1538 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 1539 | header.parse_version(f); 1540 | header.parse_flags(f); 1541 | let curr_offset = f.offset(); 1542 | f.seek(curr_offset+header.data_size); 1543 | f.offset_inc(header.data_size); 1544 | Ok(Stsh{ 1545 | header: header 1546 | }) 1547 | } 1548 | } 1549 | 1550 | #[derive(Debug, Clone)] 1551 | pub struct Sdtp { 1552 | header: Header 1553 | } 1554 | 1555 | impl Sdtp { 1556 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 1557 | header.parse_version(f); 1558 | header.parse_flags(f); 1559 | let curr_offset = f.offset(); 1560 | f.seek(curr_offset+header.data_size); 1561 | f.offset_inc(header.data_size); 1562 | Ok(Sdtp{ 1563 | header: header 1564 | }) 1565 | } 1566 | } 1567 | 1568 | 1569 | #[derive(Debug, Clone)] 1570 | pub struct Mvex { 1571 | header: Header, 1572 | children: Vec 1573 | } 1574 | 1575 | impl Mvex { 1576 | pub fn parse(f: &mut Mp4File, header: Header) -> Result{ 1577 | let children: Vec = Atom::parse_children(f); 1578 | Ok(Mvex{ 1579 | header: header, 1580 | children: children 1581 | }) 1582 | } 1583 | } 1584 | 1585 | /** 1586 | Box Type : ‘mehd’ 1587 | Container: Movie Extends Box(‘mvex’) 1588 | Mandatory: No 1589 | Quantity : Zero or one 1590 | 1591 | The Movie Extends Header is optional, and provides the overall duration, 1592 | including fragments, of a fragmented movie. If this box is not present, 1593 | the overall duration must be computed by examining each fragment. 1594 | 1595 | aligned(8) class MovieExtendsHeaderBox extends FullBox(‘mehd’, version, 0) { 1596 | if (version==1) { 1597 | unsigned int(64) fragment_duration; 1598 | } else { // version==0 1599 | unsigned int(32) fragment_duration; 1600 | } 1601 | } 1602 | **/ 1603 | 1604 | #[derive(Debug, Clone)] 1605 | pub struct Mehd { 1606 | header: Header, 1607 | fragment_duration: u64 1608 | } 1609 | 1610 | impl Mehd { 1611 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 1612 | header.parse_version(f); 1613 | header.parse_flags(f); 1614 | // let curr_offset = f.offset(); 1615 | let mut fragment_duration: u64 = 0; 1616 | if header.version.unwrap() == 1u8 { 1617 | fragment_duration = f.read_u64().unwrap(); 1618 | } else { 1619 | fragment_duration = f.read_u32().unwrap() as u64; 1620 | } 1621 | // f.seek(curr_offset+header.data_size); 1622 | f.offset_inc(header.data_size); 1623 | Ok(Mehd{ 1624 | header: header, 1625 | fragment_duration: fragment_duration 1626 | }) 1627 | } 1628 | } 1629 | 1630 | #[derive(Debug, Clone)] 1631 | pub struct Trex { 1632 | header: Header 1633 | } 1634 | 1635 | impl Trex { 1636 | pub fn parse(f: &mut Mp4File, mut header: Header) -> Result{ 1637 | header.parse_version(f); 1638 | header.parse_flags(f); 1639 | let curr_offset = f.offset(); 1640 | f.seek(curr_offset+header.data_size); 1641 | f.offset_inc(header.data_size); 1642 | Ok(Trex{ 1643 | header: header 1644 | }) 1645 | } 1646 | } 1647 | 1648 | --------------------------------------------------------------------------------