├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── benches └── bench_main.rs ├── examples ├── mp4copy.rs ├── mp4dump.rs ├── mp4info.rs ├── mp4sample.rs ├── mp4writer.rs ├── mpeg_aac_decoder │ ├── .gitignore │ ├── Cargo.toml │ ├── audio_aac.m4a │ └── src │ │ └── main.rs └── simple.rs ├── src ├── error.rs ├── lib.rs ├── mp4box │ ├── avc1.rs │ ├── co64.rs │ ├── ctts.rs │ ├── data.rs │ ├── dinf.rs │ ├── edts.rs │ ├── elst.rs │ ├── emsg.rs │ ├── ftyp.rs │ ├── hdlr.rs │ ├── hev1.rs │ ├── ilst.rs │ ├── mdhd.rs │ ├── mdia.rs │ ├── mehd.rs │ ├── meta.rs │ ├── mfhd.rs │ ├── minf.rs │ ├── mod.rs │ ├── moof.rs │ ├── moov.rs │ ├── mp4a.rs │ ├── mvex.rs │ ├── mvhd.rs │ ├── smhd.rs │ ├── stbl.rs │ ├── stco.rs │ ├── stsc.rs │ ├── stsd.rs │ ├── stss.rs │ ├── stsz.rs │ ├── stts.rs │ ├── tfdt.rs │ ├── tfhd.rs │ ├── tkhd.rs │ ├── traf.rs │ ├── trak.rs │ ├── trex.rs │ ├── trun.rs │ ├── tx3g.rs │ ├── udta.rs │ ├── vmhd.rs │ ├── vp09.rs │ └── vpcc.rs ├── reader.rs ├── track.rs ├── types.rs └── writer.rs └── tests ├── lib.rs └── samples ├── big_buck_bunny.jpg ├── big_buck_bunny_metadata.m4v ├── extended_audio_object_type.mp4 ├── minimal.mp4 ├── minimal_fragment.m4s └── minimal_init.mp4 /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout sources 17 | uses: actions/checkout@v2 18 | 19 | - name: Install rust toolchain 20 | uses: actions-rs/toolchain@v1 21 | with: 22 | profile: minimal 23 | toolchain: stable 24 | override: true 25 | components: rustfmt, clippy 26 | 27 | - name: Setup rust smart caching 28 | uses: Swatinem/rust-cache@v1.3.0 29 | 30 | - name: Cargo fmt 31 | uses: actions-rs/cargo@v1 32 | with: 33 | command: fmt 34 | args: --all -- --check 35 | 36 | - name: Cargo clippy 37 | uses: actions-rs/cargo@v1 38 | with: 39 | command: clippy 40 | args: --no-deps -- -D warnings 41 | 42 | - name: Cargo build 43 | uses: actions-rs/cargo@v1 44 | with: 45 | command: build 46 | 47 | - name: Cargo test 48 | uses: actions-rs/cargo@v1 49 | with: 50 | command: test 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Cargo.lock 2 | /target 3 | **/*.rs.bk 4 | *.exe 5 | *.pdb 6 | *.mp4 7 | .idea/ 8 | .vscode/ 9 | !tests/samples/*.mp4 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mp4" 3 | version = "0.14.0" 4 | authors = ["Alf "] 5 | edition = "2018" 6 | description = "MP4 reader and writer library in Rust." 7 | documentation = "https://docs.rs/mp4" 8 | readme = "README.md" 9 | homepage = "https://github.com/alfg/mp4-rust" 10 | repository = "https://github.com/alfg/mp4-rust" 11 | keywords = ["mp4", "iso-mp4", "isobmff", "video", "multimedia"] 12 | license = "MIT" 13 | include = ["src", "benches", "Cargo.toml", "README", "LICENSE"] 14 | 15 | [dependencies] 16 | thiserror = "^1.0" 17 | byteorder = "1" 18 | bytes = "1.1.0" 19 | num-rational = { version = "0.4.0", features = ["serde"] } 20 | serde = { version = "1.0", features = ["derive"] } 21 | serde_json = "1.0" 22 | 23 | [dev-dependencies] 24 | criterion = "0.3" 25 | 26 | [[bench]] 27 | name = "bench_main" 28 | harness = false 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Alfred Gutierrez 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mp4 2 | > MP4 Reader and Writer in Rust 🦀 3 | 4 | `mp4` is a Rust library to read and write ISO-MP4 files. This package contains MPEG-4 specifications defined in parts: 5 | * [ISO/IEC 14496-12](https://en.wikipedia.org/wiki/ISO/IEC_base_media_file_format) - ISO Base Media File Format (QuickTime, MPEG-4, etc) 6 | * [ISO/IEC 14496-14](https://en.wikipedia.org/wiki/MPEG-4_Part_14) - MP4 file format 7 | * ISO/IEC 14496-17 - Streaming text format 8 | 9 | https://crates.io/crates/mp4 10 | 11 | [![Crates.io](https://img.shields.io/crates/v/mp4)](https://crates.io/crates/mp4) 12 | [![Crates.io](https://img.shields.io/crates/d/mp4)](https://crates.io/crates/mp4) 13 | [![Docs](https://img.shields.io/badge/docs-online-5023dd.svg?style=flat-square)](https://docs.rs/mp4) 14 | [![Rust](https://github.com/alfg/mp4-rust/workflows/Rust/badge.svg)](https://github.com/alfg/mp4-rust/actions) 15 | 16 | #### Example 17 | ```rust 18 | use std::fs::File; 19 | use std::io::{BufReader}; 20 | use mp4::{Result}; 21 | 22 | fn main() -> Result<()> { 23 | let f = File::open("tests/samples/minimal.mp4").unwrap(); 24 | let size = f.metadata()?.len(); 25 | let reader = BufReader::new(f); 26 | 27 | let mp4 = mp4::Mp4Reader::read_header(reader, size)?; 28 | 29 | // Print boxes. 30 | println!("major brand: {}", mp4.ftyp.major_brand); 31 | println!("timescale: {}", mp4.moov.mvhd.timescale); 32 | 33 | // Use available methods. 34 | println!("size: {}", mp4.size()); 35 | 36 | let mut compatible_brands = String::new(); 37 | for brand in mp4.compatible_brands().iter() { 38 | compatible_brands.push_str(&brand.to_string()); 39 | compatible_brands.push_str(","); 40 | } 41 | println!("compatible brands: {}", compatible_brands); 42 | println!("duration: {:?}", mp4.duration()); 43 | 44 | // Track info. 45 | for track in mp4.tracks().values() { 46 | println!( 47 | "track: #{}({}) {} : {}", 48 | track.track_id(), 49 | track.language(), 50 | track.track_type()?, 51 | track.box_type()?, 52 | ); 53 | } 54 | Ok(()) 55 | } 56 | ``` 57 | 58 | See [examples/](examples/) for more examples. 59 | 60 | #### Install 61 | ``` 62 | cargo add mp4 63 | ``` 64 | or add to your `Cargo.toml`: 65 | ```toml 66 | mp4 = "0.14.0" 67 | ``` 68 | 69 | #### Documentation 70 | * https://docs.rs/mp4/ 71 | 72 | ## Development 73 | 74 | #### Requirements 75 | * [Rust](https://www.rust-lang.org/) 76 | 77 | #### Build 78 | ``` 79 | cargo build 80 | ``` 81 | 82 | #### Lint and Format 83 | ``` 84 | cargo clippy --fix 85 | cargo fmt --all 86 | ``` 87 | 88 | #### Run Examples 89 | * `mp4info` 90 | ``` 91 | cargo run --example mp4info 92 | ``` 93 | 94 | * `mp4dump` 95 | ``` 96 | cargo run --example mp4dump 97 | ``` 98 | 99 | #### Run Tests 100 | ``` 101 | cargo test 102 | ``` 103 | 104 | With print statement output. 105 | ``` 106 | cargo test -- --nocapture 107 | ``` 108 | 109 | #### Run Cargo fmt 110 | Run fmt to catch formatting errors. 111 | 112 | ``` 113 | rustup component add rustfmt 114 | cargo fmt --all -- --check 115 | ``` 116 | 117 | #### Run Clippy 118 | Run Clippy tests to catch common lints and mistakes. 119 | 120 | ``` 121 | rustup component add clippy 122 | cargo clippy --no-deps -- -D warnings 123 | ``` 124 | 125 | #### Run Benchmark Tests 126 | ``` 127 | cargo bench 128 | ``` 129 | 130 | View HTML report at `target/criterion/report/index.html` 131 | 132 | #### Generate Docs 133 | ``` 134 | cargo docs 135 | ``` 136 | 137 | View at `target/doc/mp4/index.html` 138 | 139 | ## Web Assembly 140 | See the [mp4-inspector](https://github.com/alfg/mp4-inspector) project as a reference for using this library in Javascript via Web Assembly. 141 | 142 | ## Related Projects 143 | * https://github.com/mozilla/mp4parse-rust 144 | * https://github.com/pcwalton/rust-media 145 | * https://github.com/alfg/mp4 146 | 147 | ## License 148 | MIT 149 | 150 | [docs]: https://docs.rs/mp4 151 | [docs-badge]: https://img.shields.io/badge/docs-online-5023dd.svg?style=flat-square 152 | -------------------------------------------------------------------------------- /benches/bench_main.rs: -------------------------------------------------------------------------------- 1 | use criterion::BenchmarkId; 2 | use criterion::{criterion_group, criterion_main, Criterion}; 3 | 4 | use std::fs::File; 5 | 6 | fn read_mp4(filename: &str) -> u64 { 7 | let f = File::open(filename).unwrap(); 8 | let m = mp4::read_mp4(f).unwrap(); 9 | 10 | m.size() 11 | } 12 | 13 | fn criterion_benchmark(c: &mut Criterion) { 14 | let filename = "tests/samples/minimal.mp4"; 15 | 16 | c.bench_with_input( 17 | BenchmarkId::new("input_example", filename), 18 | &filename, 19 | |b, &s| { 20 | b.iter(|| read_mp4(s)); 21 | }, 22 | ); 23 | } 24 | 25 | criterion_group!(benches, criterion_benchmark); 26 | criterion_main!(benches); 27 | -------------------------------------------------------------------------------- /examples/mp4copy.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::io::prelude::*; 4 | use std::io::{self, BufReader, BufWriter}; 5 | use std::path::Path; 6 | 7 | use mp4::{ 8 | AacConfig, AvcConfig, HevcConfig, MediaConfig, MediaType, Mp4Config, Result, TrackConfig, 9 | TtxtConfig, Vp9Config, 10 | }; 11 | 12 | fn main() { 13 | let args: Vec = env::args().collect(); 14 | 15 | if args.len() < 3 { 16 | println!("Usage: mp4copy "); 17 | std::process::exit(1); 18 | } 19 | 20 | if let Err(err) = copy(&args[1], &args[2]) { 21 | let _ = writeln!(io::stderr(), "{}", err); 22 | } 23 | } 24 | 25 | fn copy>(src_filename: &P, dst_filename: &P) -> Result<()> { 26 | let src_file = File::open(src_filename)?; 27 | let size = src_file.metadata()?.len(); 28 | let reader = BufReader::new(src_file); 29 | 30 | let dst_file = File::create(dst_filename)?; 31 | let writer = BufWriter::new(dst_file); 32 | 33 | let mut mp4_reader = mp4::Mp4Reader::read_header(reader, size)?; 34 | let mut mp4_writer = mp4::Mp4Writer::write_start( 35 | writer, 36 | &Mp4Config { 37 | major_brand: *mp4_reader.major_brand(), 38 | minor_version: mp4_reader.minor_version(), 39 | compatible_brands: mp4_reader.compatible_brands().to_vec(), 40 | timescale: mp4_reader.timescale(), 41 | }, 42 | )?; 43 | 44 | // TODO interleaving 45 | for track in mp4_reader.tracks().values() { 46 | let media_conf = match track.media_type()? { 47 | MediaType::H264 => MediaConfig::AvcConfig(AvcConfig { 48 | width: track.width(), 49 | height: track.height(), 50 | seq_param_set: track.sequence_parameter_set()?.to_vec(), 51 | pic_param_set: track.picture_parameter_set()?.to_vec(), 52 | }), 53 | MediaType::H265 => MediaConfig::HevcConfig(HevcConfig { 54 | width: track.width(), 55 | height: track.height(), 56 | }), 57 | MediaType::VP9 => MediaConfig::Vp9Config(Vp9Config { 58 | width: track.width(), 59 | height: track.height(), 60 | }), 61 | MediaType::AAC => MediaConfig::AacConfig(AacConfig { 62 | bitrate: track.bitrate(), 63 | profile: track.audio_profile()?, 64 | freq_index: track.sample_freq_index()?, 65 | chan_conf: track.channel_config()?, 66 | }), 67 | MediaType::TTXT => MediaConfig::TtxtConfig(TtxtConfig {}), 68 | }; 69 | 70 | let track_conf = TrackConfig { 71 | track_type: track.track_type()?, 72 | timescale: track.timescale(), 73 | language: track.language().to_string(), 74 | media_conf, 75 | }; 76 | 77 | mp4_writer.add_track(&track_conf)?; 78 | } 79 | 80 | for track_id in mp4_reader.tracks().keys().copied().collect::>() { 81 | let sample_count = mp4_reader.sample_count(track_id)?; 82 | for sample_idx in 0..sample_count { 83 | let sample_id = sample_idx + 1; 84 | let sample = mp4_reader.read_sample(track_id, sample_id)?.unwrap(); 85 | mp4_writer.write_sample(track_id, &sample)?; 86 | // println!("copy {}:({})", sample_id, sample); 87 | } 88 | } 89 | 90 | mp4_writer.write_end()?; 91 | 92 | Ok(()) 93 | } 94 | -------------------------------------------------------------------------------- /examples/mp4dump.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::io::prelude::*; 4 | use std::io::{self, BufReader}; 5 | use std::path::Path; 6 | 7 | use mp4::{Mp4Box, Result}; 8 | 9 | fn main() { 10 | let args: Vec = env::args().collect(); 11 | 12 | if args.len() < 2 { 13 | println!("Usage: mp4dump "); 14 | std::process::exit(1); 15 | } 16 | 17 | if let Err(err) = dump(&args[1]) { 18 | let _ = writeln!(io::stderr(), "{}", err); 19 | } 20 | } 21 | 22 | fn dump>(filename: &P) -> Result<()> { 23 | let f = File::open(filename)?; 24 | let boxes = get_boxes(f)?; 25 | 26 | // print out boxes 27 | for b in boxes.iter() { 28 | println!("[{}] size={} {}", b.name, b.size, b.summary); 29 | } 30 | 31 | Ok(()) 32 | } 33 | 34 | #[derive(Debug, Clone, PartialEq, Default)] 35 | pub struct Box { 36 | name: String, 37 | size: u64, 38 | summary: String, 39 | indent: u32, 40 | } 41 | 42 | fn get_boxes(file: File) -> Result> { 43 | let size = file.metadata()?.len(); 44 | let reader = BufReader::new(file); 45 | let mp4 = mp4::Mp4Reader::read_header(reader, size)?; 46 | 47 | // collect known boxes 48 | let mut boxes = vec![ 49 | build_box(&mp4.ftyp), 50 | build_box(&mp4.moov), 51 | build_box(&mp4.moov.mvhd), 52 | ]; 53 | 54 | if let Some(ref mvex) = &mp4.moov.mvex { 55 | boxes.push(build_box(mvex)); 56 | if let Some(mehd) = &mvex.mehd { 57 | boxes.push(build_box(mehd)); 58 | } 59 | boxes.push(build_box(&mvex.trex)); 60 | } 61 | 62 | // trak. 63 | for track in mp4.tracks().values() { 64 | boxes.push(build_box(&track.trak)); 65 | boxes.push(build_box(&track.trak.tkhd)); 66 | if let Some(ref edts) = track.trak.edts { 67 | boxes.push(build_box(edts)); 68 | if let Some(ref elst) = edts.elst { 69 | boxes.push(build_box(elst)); 70 | } 71 | } 72 | 73 | // trak.mdia 74 | let mdia = &track.trak.mdia; 75 | boxes.push(build_box(mdia)); 76 | boxes.push(build_box(&mdia.mdhd)); 77 | boxes.push(build_box(&mdia.hdlr)); 78 | boxes.push(build_box(&track.trak.mdia.minf)); 79 | 80 | // trak.mdia.minf 81 | let minf = &track.trak.mdia.minf; 82 | if let Some(ref vmhd) = &minf.vmhd { 83 | boxes.push(build_box(vmhd)); 84 | } 85 | if let Some(ref smhd) = &minf.smhd { 86 | boxes.push(build_box(smhd)); 87 | } 88 | 89 | // trak.mdia.minf.stbl 90 | let stbl = &track.trak.mdia.minf.stbl; 91 | boxes.push(build_box(stbl)); 92 | boxes.push(build_box(&stbl.stsd)); 93 | if let Some(ref avc1) = &stbl.stsd.avc1 { 94 | boxes.push(build_box(avc1)); 95 | } 96 | if let Some(ref hev1) = &stbl.stsd.hev1 { 97 | boxes.push(build_box(hev1)); 98 | } 99 | if let Some(ref mp4a) = &stbl.stsd.mp4a { 100 | boxes.push(build_box(mp4a)); 101 | } 102 | boxes.push(build_box(&stbl.stts)); 103 | if let Some(ref ctts) = &stbl.ctts { 104 | boxes.push(build_box(ctts)); 105 | } 106 | if let Some(ref stss) = &stbl.stss { 107 | boxes.push(build_box(stss)); 108 | } 109 | boxes.push(build_box(&stbl.stsc)); 110 | boxes.push(build_box(&stbl.stsz)); 111 | if let Some(ref stco) = &stbl.stco { 112 | boxes.push(build_box(stco)); 113 | } 114 | if let Some(ref co64) = &stbl.co64 { 115 | boxes.push(build_box(co64)); 116 | } 117 | } 118 | 119 | // If fragmented, add moof boxes. 120 | for moof in mp4.moofs.iter() { 121 | boxes.push(build_box(moof)); 122 | boxes.push(build_box(&moof.mfhd)); 123 | for traf in moof.trafs.iter() { 124 | boxes.push(build_box(traf)); 125 | boxes.push(build_box(&traf.tfhd)); 126 | if let Some(ref trun) = &traf.trun { 127 | boxes.push(build_box(trun)); 128 | } 129 | } 130 | } 131 | 132 | Ok(boxes) 133 | } 134 | 135 | fn build_box(m: &M) -> Box { 136 | Box { 137 | name: m.box_type().to_string(), 138 | size: m.box_size(), 139 | summary: m.summary().unwrap(), 140 | indent: 0, 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /examples/mp4info.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::io::prelude::*; 4 | use std::io::{self, BufReader}; 5 | use std::path::Path; 6 | 7 | use mp4::{Error, Mp4Track, Result, TrackType}; 8 | 9 | fn main() { 10 | let args: Vec = env::args().collect(); 11 | 12 | if args.len() < 2 { 13 | println!("Usage: mp4info "); 14 | std::process::exit(1); 15 | } 16 | 17 | if let Err(err) = info(&args[1]) { 18 | let _ = writeln!(io::stderr(), "{}", err); 19 | } 20 | } 21 | 22 | fn info>(filename: &P) -> Result<()> { 23 | let f = File::open(filename)?; 24 | let size = f.metadata()?.len(); 25 | let reader = BufReader::new(f); 26 | 27 | let mp4 = mp4::Mp4Reader::read_header(reader, size)?; 28 | 29 | println!("File:"); 30 | println!(" file size: {}", mp4.size()); 31 | println!(" major_brand: {}", mp4.major_brand()); 32 | let mut compatible_brands = String::new(); 33 | for brand in mp4.compatible_brands().iter() { 34 | compatible_brands.push_str(&brand.to_string()); 35 | compatible_brands.push(' '); 36 | } 37 | println!(" compatible_brands: {}\n", compatible_brands); 38 | 39 | println!("Movie:"); 40 | println!(" version: {}", mp4.moov.mvhd.version); 41 | println!( 42 | " creation time: {}", 43 | creation_time(mp4.moov.mvhd.creation_time) 44 | ); 45 | println!(" duration: {:?}", mp4.duration()); 46 | println!(" fragments: {:?}", mp4.is_fragmented()); 47 | println!(" timescale: {:?}\n", mp4.timescale()); 48 | 49 | println!("Found {} Tracks", mp4.tracks().len()); 50 | for track in mp4.tracks().values() { 51 | let media_info = match track.track_type()? { 52 | TrackType::Video => video_info(track), 53 | TrackType::Audio => audio_info(track), 54 | TrackType::Subtitle => subtitle_info(track), 55 | }; 56 | 57 | println!( 58 | " Track: #{}({}) {}: {}", 59 | track.track_id(), 60 | track.language(), 61 | track.track_type()?, 62 | media_info.unwrap_or_else(|e| e.to_string()) 63 | ); 64 | } 65 | Ok(()) 66 | } 67 | 68 | fn video_info(track: &Mp4Track) -> Result { 69 | if track.trak.mdia.minf.stbl.stsd.avc1.is_some() { 70 | Ok(format!( 71 | "{} ({}) ({:?}), {}x{}, {} kb/s, {:.2} fps", 72 | track.media_type()?, 73 | track.video_profile()?, 74 | track.box_type()?, 75 | track.width(), 76 | track.height(), 77 | track.bitrate() / 1000, 78 | track.frame_rate() 79 | )) 80 | } else { 81 | Ok(format!( 82 | "{} ({:?}), {}x{}, {} kb/s, {:.2} fps", 83 | track.media_type()?, 84 | track.box_type()?, 85 | track.width(), 86 | track.height(), 87 | track.bitrate() / 1000, 88 | track.frame_rate() 89 | )) 90 | } 91 | } 92 | 93 | fn audio_info(track: &Mp4Track) -> Result { 94 | if let Some(ref mp4a) = track.trak.mdia.minf.stbl.stsd.mp4a { 95 | if mp4a.esds.is_some() { 96 | let profile = match track.audio_profile() { 97 | Ok(val) => val.to_string(), 98 | _ => "-".to_string(), 99 | }; 100 | 101 | let channel_config = match track.channel_config() { 102 | Ok(val) => val.to_string(), 103 | _ => "-".to_string(), 104 | }; 105 | 106 | Ok(format!( 107 | "{} ({}) ({:?}), {} Hz, {}, {} kb/s", 108 | track.media_type()?, 109 | profile, 110 | track.box_type()?, 111 | track.sample_freq_index()?.freq(), 112 | channel_config, 113 | track.bitrate() / 1000 114 | )) 115 | } else { 116 | Ok(format!( 117 | "{} ({:?}), {} kb/s", 118 | track.media_type()?, 119 | track.box_type()?, 120 | track.bitrate() / 1000 121 | )) 122 | } 123 | } else { 124 | Err(Error::InvalidData("mp4a box not found")) 125 | } 126 | } 127 | 128 | fn subtitle_info(track: &Mp4Track) -> Result { 129 | if track.trak.mdia.minf.stbl.stsd.tx3g.is_some() { 130 | Ok(format!("{} ({:?})", track.media_type()?, track.box_type()?,)) 131 | } else { 132 | Err(Error::InvalidData("tx3g box not found")) 133 | } 134 | } 135 | 136 | fn creation_time(creation_time: u64) -> u64 { 137 | // convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01) 138 | if creation_time >= 2082844800 { 139 | creation_time - 2082844800 140 | } else { 141 | creation_time 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /examples/mp4sample.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::io::prelude::*; 4 | use std::io::{self, BufReader}; 5 | use std::path::Path; 6 | 7 | use mp4::Result; 8 | 9 | fn main() { 10 | let args: Vec = env::args().collect(); 11 | 12 | if args.len() < 2 { 13 | println!("Usage: mp4sample "); 14 | std::process::exit(1); 15 | } 16 | 17 | if let Err(err) = samples(&args[1]) { 18 | let _ = writeln!(io::stderr(), "{}", err); 19 | } 20 | } 21 | 22 | fn samples>(filename: &P) -> Result<()> { 23 | let f = File::open(filename)?; 24 | let size = f.metadata()?.len(); 25 | let reader = BufReader::new(f); 26 | 27 | let mut mp4 = mp4::Mp4Reader::read_header(reader, size)?; 28 | 29 | for track_id in mp4.tracks().keys().copied().collect::>() { 30 | let sample_count = mp4.sample_count(track_id).unwrap(); 31 | 32 | for sample_idx in 0..sample_count { 33 | let sample_id = sample_idx + 1; 34 | let sample = mp4.read_sample(track_id, sample_id); 35 | 36 | if let Some(ref samp) = sample.unwrap() { 37 | println!( 38 | "[{}] start_time={} duration={} rendering_offset={} size={} is_sync={}", 39 | sample_id, 40 | samp.start_time, 41 | samp.duration, 42 | samp.rendering_offset, 43 | samp.bytes.len(), 44 | samp.is_sync, 45 | ); 46 | } 47 | } 48 | } 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /examples/mp4writer.rs: -------------------------------------------------------------------------------- 1 | use mp4::{Mp4Config, Mp4Writer}; 2 | use std::io::Cursor; 3 | 4 | fn main() -> mp4::Result<()> { 5 | let config = Mp4Config { 6 | major_brand: str::parse("isom").unwrap(), 7 | minor_version: 512, 8 | compatible_brands: vec![ 9 | str::parse("isom").unwrap(), 10 | str::parse("iso2").unwrap(), 11 | str::parse("avc1").unwrap(), 12 | str::parse("mp41").unwrap(), 13 | ], 14 | timescale: 1000, 15 | }; 16 | 17 | let data = Cursor::new(Vec::::new()); 18 | let mut writer = Mp4Writer::write_start(data, &config)?; 19 | writer.write_end()?; 20 | 21 | let data: Vec = writer.into_writer().into_inner(); 22 | println!("{:?}", data); 23 | Ok(()) 24 | } 25 | -------------------------------------------------------------------------------- /examples/mpeg_aac_decoder/.gitignore: -------------------------------------------------------------------------------- 1 | /Cargo.lock 2 | /target 3 | -------------------------------------------------------------------------------- /examples/mpeg_aac_decoder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "mpeg_aac_decoder" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | mp4 = "0.8.1" 8 | fdk-aac = "0.4.0" 9 | rodio = { version = "0.13.0", default-features = false } 10 | -------------------------------------------------------------------------------- /examples/mpeg_aac_decoder/audio_aac.m4a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/mp4-rust/35560e94f5e871a2b2d88bfe964013b39af131e8/examples/mpeg_aac_decoder/audio_aac.m4a -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | 4 | fn main() { 5 | let args: Vec = env::args().collect(); 6 | 7 | if args.len() < 2 { 8 | println!("Usage: simple "); 9 | std::process::exit(1); 10 | } 11 | 12 | let filename = &args[1]; 13 | let f = File::open(filename).unwrap(); 14 | let mp4 = mp4::read_mp4(f).unwrap(); 15 | 16 | println!("Major Brand: {}", mp4.major_brand()); 17 | 18 | for track in mp4.tracks().values() { 19 | println!( 20 | "Track: #{}({}) {} {}", 21 | track.track_id(), 22 | track.language(), 23 | track.track_type().unwrap(), 24 | track.box_type().unwrap(), 25 | ); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use crate::mp4box::BoxType; 4 | 5 | #[derive(Error, Debug)] 6 | pub enum Error { 7 | #[error("{0}")] 8 | IoError(#[from] std::io::Error), 9 | #[error("{0}")] 10 | InvalidData(&'static str), 11 | #[error("{0} not found")] 12 | BoxNotFound(BoxType), 13 | #[error("{0} and {1} not found")] 14 | Box2NotFound(BoxType, BoxType), 15 | #[error("trak[{0}] not found")] 16 | TrakNotFound(u32), 17 | #[error("trak[{0}].{1} not found")] 18 | BoxInTrakNotFound(u32, BoxType), 19 | #[error("traf[{0}].{1} not found")] 20 | BoxInTrafNotFound(u32, BoxType), 21 | #[error("trak[{0}].stbl.{1} not found")] 22 | BoxInStblNotFound(u32, BoxType), 23 | #[error("trak[{0}].stbl.{1}.entry[{2}] not found")] 24 | EntryInStblNotFound(u32, BoxType, u32), 25 | #[error("traf[{0}].trun.{1}.entry[{2}] not found")] 26 | EntryInTrunNotFound(u32, BoxType, u32), 27 | #[error("{0} version {1} is not supported")] 28 | UnsupportedBoxVersion(BoxType, u8), 29 | } 30 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! `mp4` is a Rust library to read and write ISO-MP4 files. 2 | //! 3 | //! This package contains MPEG-4 specifications defined in parts: 4 | //! * ISO/IEC 14496-12 - ISO Base Media File Format (QuickTime, MPEG-4, etc) 5 | //! * ISO/IEC 14496-14 - MP4 file format 6 | //! * ISO/IEC 14496-17 - Streaming text format 7 | //! 8 | //! See: [mp4box] for supported MP4 atoms. 9 | //! 10 | //! ### Example 11 | //! 12 | //! ``` 13 | //! use std::fs::File; 14 | //! use std::io::{BufReader}; 15 | //! use mp4::{Result}; 16 | //! 17 | //! fn main() -> Result<()> { 18 | //! let f = File::open("tests/samples/minimal.mp4").unwrap(); 19 | //! let size = f.metadata()?.len(); 20 | //! let reader = BufReader::new(f); 21 | //! 22 | //! let mp4 = mp4::Mp4Reader::read_header(reader, size)?; 23 | //! 24 | //! // Print boxes. 25 | //! println!("major brand: {}", mp4.ftyp.major_brand); 26 | //! println!("timescale: {}", mp4.moov.mvhd.timescale); 27 | //! 28 | //! // Use available methods. 29 | //! println!("size: {}", mp4.size()); 30 | //! 31 | //! let mut compatible_brands = String::new(); 32 | //! for brand in mp4.compatible_brands().iter() { 33 | //! compatible_brands.push_str(&brand.to_string()); 34 | //! compatible_brands.push_str(","); 35 | //! } 36 | //! println!("compatible brands: {}", compatible_brands); 37 | //! println!("duration: {:?}", mp4.duration()); 38 | //! 39 | //! // Track info. 40 | //! for track in mp4.tracks().values() { 41 | //! println!( 42 | //! "track: #{}({}) {} : {}", 43 | //! track.track_id(), 44 | //! track.language(), 45 | //! track.track_type()?, 46 | //! track.box_type()?, 47 | //! ); 48 | //! } 49 | //! Ok(()) 50 | //! } 51 | //! ``` 52 | //! 53 | //! See [examples] for more examples. 54 | //! 55 | //! # Installation 56 | //! 57 | //! Add the following to your `Cargo.toml` file: 58 | //! 59 | //! ```toml 60 | //! [dependencies] 61 | //! mp4 = "0.7.0" 62 | //! ``` 63 | //! 64 | //! [mp4box]: https://github.com/alfg/mp4-rust/blob/master/src/mp4box/mod.rs 65 | //! [examples]: https://github.com/alfg/mp4-rust/tree/master/examples 66 | #![doc(html_root_url = "https://docs.rs/mp4/*")] 67 | 68 | use std::fs::File; 69 | use std::io::BufReader; 70 | 71 | mod error; 72 | pub use error::Error; 73 | 74 | pub type Result = std::result::Result; 75 | 76 | mod types; 77 | pub use types::*; 78 | 79 | mod mp4box; 80 | pub use mp4box::*; 81 | 82 | mod track; 83 | pub use track::{Mp4Track, TrackConfig}; 84 | 85 | mod reader; 86 | pub use reader::Mp4Reader; 87 | 88 | mod writer; 89 | pub use writer::{Mp4Config, Mp4Writer}; 90 | 91 | pub fn read_mp4(f: File) -> Result>> { 92 | let size = f.metadata()?.len(); 93 | let reader = BufReader::new(f); 94 | let mp4 = reader::Mp4Reader::read_header(reader, size)?; 95 | Ok(mp4) 96 | } 97 | -------------------------------------------------------------------------------- /src/mp4box/co64.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | use std::mem::size_of; 5 | 6 | use crate::mp4box::*; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 9 | pub struct Co64Box { 10 | pub version: u8, 11 | pub flags: u32, 12 | 13 | #[serde(skip_serializing)] 14 | pub entries: Vec, 15 | } 16 | 17 | impl Co64Box { 18 | pub fn get_type(&self) -> BoxType { 19 | BoxType::Co64Box 20 | } 21 | 22 | pub fn get_size(&self) -> u64 { 23 | HEADER_SIZE + HEADER_EXT_SIZE + 4 + (8 * self.entries.len() as u64) 24 | } 25 | } 26 | 27 | impl Mp4Box for Co64Box { 28 | fn box_type(&self) -> BoxType { 29 | self.get_type() 30 | } 31 | 32 | fn box_size(&self) -> u64 { 33 | self.get_size() 34 | } 35 | 36 | fn to_json(&self) -> Result { 37 | Ok(serde_json::to_string(&self).unwrap()) 38 | } 39 | 40 | fn summary(&self) -> Result { 41 | let s = format!("entries_count={}", self.entries.len()); 42 | Ok(s) 43 | } 44 | } 45 | 46 | impl ReadBox<&mut R> for Co64Box { 47 | fn read_box(reader: &mut R, size: u64) -> Result { 48 | let start = box_start(reader)?; 49 | 50 | let (version, flags) = read_box_header_ext(reader)?; 51 | 52 | let header_size = HEADER_SIZE + HEADER_EXT_SIZE; 53 | let other_size = size_of::(); // entry_count 54 | let entry_size = size_of::(); // chunk_offset 55 | let entry_count = reader.read_u32::()?; 56 | if u64::from(entry_count) 57 | > size 58 | .saturating_sub(header_size) 59 | .saturating_sub(other_size as u64) 60 | / entry_size as u64 61 | { 62 | return Err(Error::InvalidData( 63 | "co64 entry_count indicates more entries than could fit in the box", 64 | )); 65 | } 66 | let mut entries = Vec::with_capacity(entry_count as usize); 67 | for _i in 0..entry_count { 68 | let chunk_offset = reader.read_u64::()?; 69 | entries.push(chunk_offset); 70 | } 71 | 72 | skip_bytes_to(reader, start + size)?; 73 | 74 | Ok(Co64Box { 75 | version, 76 | flags, 77 | entries, 78 | }) 79 | } 80 | } 81 | 82 | impl WriteBox<&mut W> for Co64Box { 83 | fn write_box(&self, writer: &mut W) -> Result { 84 | let size = self.box_size(); 85 | BoxHeader::new(self.box_type(), size).write(writer)?; 86 | 87 | write_box_header_ext(writer, self.version, self.flags)?; 88 | 89 | writer.write_u32::(self.entries.len() as u32)?; 90 | for chunk_offset in self.entries.iter() { 91 | writer.write_u64::(*chunk_offset)?; 92 | } 93 | 94 | Ok(size) 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::*; 101 | use crate::mp4box::BoxHeader; 102 | use std::io::Cursor; 103 | 104 | #[test] 105 | fn test_co64() { 106 | let src_box = Co64Box { 107 | version: 0, 108 | flags: 0, 109 | entries: vec![267, 1970, 2535, 2803, 11843, 22223, 33584], 110 | }; 111 | let mut buf = Vec::new(); 112 | src_box.write_box(&mut buf).unwrap(); 113 | assert_eq!(buf.len(), src_box.box_size() as usize); 114 | 115 | let mut reader = Cursor::new(&buf); 116 | let header = BoxHeader::read(&mut reader).unwrap(); 117 | assert_eq!(header.name, BoxType::Co64Box); 118 | assert_eq!(src_box.box_size(), header.size); 119 | 120 | let dst_box = Co64Box::read_box(&mut reader, header.size).unwrap(); 121 | assert_eq!(src_box, dst_box); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/mp4box/ctts.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | use std::mem::size_of; 5 | 6 | use crate::mp4box::*; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 9 | pub struct CttsBox { 10 | pub version: u8, 11 | pub flags: u32, 12 | 13 | #[serde(skip_serializing)] 14 | pub entries: Vec, 15 | } 16 | 17 | impl CttsBox { 18 | pub fn get_type(&self) -> BoxType { 19 | BoxType::CttsBox 20 | } 21 | 22 | pub fn get_size(&self) -> u64 { 23 | HEADER_SIZE + HEADER_EXT_SIZE + 4 + (8 * self.entries.len() as u64) 24 | } 25 | } 26 | 27 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 28 | pub struct CttsEntry { 29 | pub sample_count: u32, 30 | pub sample_offset: i32, 31 | } 32 | 33 | impl Mp4Box for CttsBox { 34 | fn box_type(&self) -> BoxType { 35 | self.get_type() 36 | } 37 | 38 | fn box_size(&self) -> u64 { 39 | self.get_size() 40 | } 41 | 42 | fn to_json(&self) -> Result { 43 | Ok(serde_json::to_string(&self).unwrap()) 44 | } 45 | 46 | fn summary(&self) -> Result { 47 | let s = format!("entries_count={}", self.entries.len()); 48 | Ok(s) 49 | } 50 | } 51 | 52 | impl ReadBox<&mut R> for CttsBox { 53 | fn read_box(reader: &mut R, size: u64) -> Result { 54 | let start = box_start(reader)?; 55 | 56 | let (version, flags) = read_box_header_ext(reader)?; 57 | 58 | let header_size = HEADER_SIZE + HEADER_EXT_SIZE; 59 | let entry_count = reader.read_u32::()?; 60 | let entry_size = size_of::() + size_of::(); // sample_count + sample_offset 61 | // (sample_offset might be a u32, but the size is the same.) 62 | let other_size = size_of::(); // entry_count 63 | if u64::from(entry_count) 64 | > size 65 | .saturating_sub(header_size) 66 | .saturating_sub(other_size as u64) 67 | / entry_size as u64 68 | { 69 | return Err(Error::InvalidData( 70 | "ctts entry_count indicates more entries than could fit in the box", 71 | )); 72 | } 73 | let mut entries = Vec::with_capacity(entry_count as usize); 74 | for _ in 0..entry_count { 75 | let entry = CttsEntry { 76 | sample_count: reader.read_u32::()?, 77 | sample_offset: reader.read_i32::()?, 78 | }; 79 | entries.push(entry); 80 | } 81 | 82 | skip_bytes_to(reader, start + size)?; 83 | 84 | Ok(CttsBox { 85 | version, 86 | flags, 87 | entries, 88 | }) 89 | } 90 | } 91 | 92 | impl WriteBox<&mut W> for CttsBox { 93 | fn write_box(&self, writer: &mut W) -> Result { 94 | let size = self.box_size(); 95 | BoxHeader::new(self.box_type(), size).write(writer)?; 96 | 97 | write_box_header_ext(writer, self.version, self.flags)?; 98 | 99 | writer.write_u32::(self.entries.len() as u32)?; 100 | for entry in self.entries.iter() { 101 | writer.write_u32::(entry.sample_count)?; 102 | writer.write_i32::(entry.sample_offset)?; 103 | } 104 | 105 | Ok(size) 106 | } 107 | } 108 | 109 | #[cfg(test)] 110 | mod tests { 111 | use super::*; 112 | use crate::mp4box::BoxHeader; 113 | use std::io::Cursor; 114 | 115 | #[test] 116 | fn test_ctts() { 117 | let src_box = CttsBox { 118 | version: 0, 119 | flags: 0, 120 | entries: vec![ 121 | CttsEntry { 122 | sample_count: 1, 123 | sample_offset: 200, 124 | }, 125 | CttsEntry { 126 | sample_count: 2, 127 | sample_offset: -100, 128 | }, 129 | ], 130 | }; 131 | let mut buf = Vec::new(); 132 | src_box.write_box(&mut buf).unwrap(); 133 | assert_eq!(buf.len(), src_box.box_size() as usize); 134 | 135 | let mut reader = Cursor::new(&buf); 136 | let header = BoxHeader::read(&mut reader).unwrap(); 137 | assert_eq!(header.name, BoxType::CttsBox); 138 | assert_eq!(src_box.box_size(), header.size); 139 | 140 | let dst_box = CttsBox::read_box(&mut reader, header.size).unwrap(); 141 | assert_eq!(src_box, dst_box); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/mp4box/data.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | convert::TryFrom, 3 | io::{Read, Seek}, 4 | }; 5 | 6 | use serde::Serialize; 7 | 8 | use crate::mp4box::*; 9 | 10 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 11 | pub struct DataBox { 12 | pub data: Vec, 13 | pub data_type: DataType, 14 | } 15 | 16 | impl DataBox { 17 | pub fn get_type(&self) -> BoxType { 18 | BoxType::DataBox 19 | } 20 | 21 | pub fn get_size(&self) -> u64 { 22 | let mut size = HEADER_SIZE; 23 | size += 4; // data_type 24 | size += 4; // reserved 25 | size += self.data.len() as u64; 26 | size 27 | } 28 | } 29 | 30 | impl Mp4Box for DataBox { 31 | fn box_type(&self) -> BoxType { 32 | self.get_type() 33 | } 34 | 35 | fn box_size(&self) -> u64 { 36 | self.get_size() 37 | } 38 | 39 | fn to_json(&self) -> Result { 40 | Ok(serde_json::to_string(&self).unwrap()) 41 | } 42 | 43 | fn summary(&self) -> Result { 44 | let s = format!("type={:?} len={}", self.data_type, self.data.len()); 45 | Ok(s) 46 | } 47 | } 48 | 49 | impl ReadBox<&mut R> for DataBox { 50 | fn read_box(reader: &mut R, size: u64) -> Result { 51 | let start = box_start(reader)?; 52 | 53 | let data_type = DataType::try_from(reader.read_u32::()?)?; 54 | 55 | reader.read_u32::()?; // reserved = 0 56 | 57 | let current = reader.stream_position()?; 58 | let mut data = vec![0u8; (start + size - current) as usize]; 59 | reader.read_exact(&mut data)?; 60 | 61 | Ok(DataBox { data, data_type }) 62 | } 63 | } 64 | 65 | impl WriteBox<&mut W> for DataBox { 66 | fn write_box(&self, writer: &mut W) -> Result { 67 | let size = self.box_size(); 68 | BoxHeader::new(self.box_type(), size).write(writer)?; 69 | 70 | writer.write_u32::(self.data_type.clone() as u32)?; 71 | writer.write_u32::(0)?; // reserved = 0 72 | writer.write_all(&self.data)?; 73 | 74 | Ok(size) 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use super::*; 81 | use crate::mp4box::BoxHeader; 82 | use std::io::Cursor; 83 | 84 | #[test] 85 | fn test_data() { 86 | let src_box = DataBox { 87 | data_type: DataType::Text, 88 | data: b"test_data".to_vec(), 89 | }; 90 | let mut buf = Vec::new(); 91 | src_box.write_box(&mut buf).unwrap(); 92 | assert_eq!(buf.len(), src_box.box_size() as usize); 93 | 94 | let mut reader = Cursor::new(&buf); 95 | let header = BoxHeader::read(&mut reader).unwrap(); 96 | assert_eq!(header.name, BoxType::DataBox); 97 | assert_eq!(src_box.box_size(), header.size); 98 | 99 | let dst_box = DataBox::read_box(&mut reader, header.size).unwrap(); 100 | assert_eq!(src_box, dst_box); 101 | } 102 | 103 | #[test] 104 | fn test_data_empty() { 105 | let src_box = DataBox::default(); 106 | let mut buf = Vec::new(); 107 | src_box.write_box(&mut buf).unwrap(); 108 | assert_eq!(buf.len(), src_box.box_size() as usize); 109 | 110 | let mut reader = Cursor::new(&buf); 111 | let header = BoxHeader::read(&mut reader).unwrap(); 112 | assert_eq!(header.name, BoxType::DataBox); 113 | assert_eq!(src_box.box_size(), header.size); 114 | 115 | let dst_box = DataBox::read_box(&mut reader, header.size).unwrap(); 116 | assert_eq!(src_box, dst_box); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/mp4box/dinf.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::io::{Read, Seek, Write}; 3 | 4 | use crate::mp4box::*; 5 | 6 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 7 | pub struct DinfBox { 8 | dref: DrefBox, 9 | } 10 | 11 | impl DinfBox { 12 | pub fn get_type(&self) -> BoxType { 13 | BoxType::DinfBox 14 | } 15 | 16 | pub fn get_size(&self) -> u64 { 17 | HEADER_SIZE + self.dref.box_size() 18 | } 19 | } 20 | 21 | impl Mp4Box for DinfBox { 22 | fn box_type(&self) -> BoxType { 23 | self.get_type() 24 | } 25 | 26 | fn box_size(&self) -> u64 { 27 | self.get_size() 28 | } 29 | 30 | fn to_json(&self) -> Result { 31 | Ok(serde_json::to_string(&self).unwrap()) 32 | } 33 | 34 | fn summary(&self) -> Result { 35 | let s = String::new(); 36 | Ok(s) 37 | } 38 | } 39 | 40 | impl ReadBox<&mut R> for DinfBox { 41 | fn read_box(reader: &mut R, size: u64) -> Result { 42 | let start = box_start(reader)?; 43 | 44 | let mut dref = None; 45 | 46 | let mut current = reader.stream_position()?; 47 | let end = start + size; 48 | while current < end { 49 | // Get box header. 50 | let header = BoxHeader::read(reader)?; 51 | let BoxHeader { name, size: s } = header; 52 | if s > size { 53 | return Err(Error::InvalidData( 54 | "dinf box contains a box with a larger size than it", 55 | )); 56 | } 57 | 58 | match name { 59 | BoxType::DrefBox => { 60 | dref = Some(DrefBox::read_box(reader, s)?); 61 | } 62 | _ => { 63 | // XXX warn!() 64 | skip_box(reader, s)?; 65 | } 66 | } 67 | 68 | current = reader.stream_position()?; 69 | } 70 | 71 | if dref.is_none() { 72 | return Err(Error::BoxNotFound(BoxType::DrefBox)); 73 | } 74 | 75 | skip_bytes_to(reader, start + size)?; 76 | 77 | Ok(DinfBox { 78 | dref: dref.unwrap(), 79 | }) 80 | } 81 | } 82 | 83 | impl WriteBox<&mut W> for DinfBox { 84 | fn write_box(&self, writer: &mut W) -> Result { 85 | let size = self.box_size(); 86 | BoxHeader::new(self.box_type(), size).write(writer)?; 87 | self.dref.write_box(writer)?; 88 | Ok(size) 89 | } 90 | } 91 | 92 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 93 | pub struct DrefBox { 94 | pub version: u8, 95 | pub flags: u32, 96 | 97 | #[serde(skip_serializing_if = "Option::is_none")] 98 | pub url: Option, 99 | } 100 | 101 | impl Default for DrefBox { 102 | fn default() -> Self { 103 | DrefBox { 104 | version: 0, 105 | flags: 0, 106 | url: Some(UrlBox::default()), 107 | } 108 | } 109 | } 110 | 111 | impl DrefBox { 112 | pub fn get_type(&self) -> BoxType { 113 | BoxType::DrefBox 114 | } 115 | 116 | pub fn get_size(&self) -> u64 { 117 | let mut size = HEADER_SIZE + HEADER_EXT_SIZE + 4; 118 | if let Some(ref url) = self.url { 119 | size += url.box_size(); 120 | } 121 | size 122 | } 123 | } 124 | 125 | impl Mp4Box for DrefBox { 126 | fn box_type(&self) -> BoxType { 127 | self.get_type() 128 | } 129 | 130 | fn box_size(&self) -> u64 { 131 | self.get_size() 132 | } 133 | 134 | fn to_json(&self) -> Result { 135 | Ok(serde_json::to_string(&self).unwrap()) 136 | } 137 | 138 | fn summary(&self) -> Result { 139 | let s = String::new(); 140 | Ok(s) 141 | } 142 | } 143 | 144 | impl ReadBox<&mut R> for DrefBox { 145 | fn read_box(reader: &mut R, size: u64) -> Result { 146 | let start = box_start(reader)?; 147 | 148 | let mut current = reader.stream_position()?; 149 | 150 | let (version, flags) = read_box_header_ext(reader)?; 151 | let end = start + size; 152 | 153 | let mut url = None; 154 | 155 | let entry_count = reader.read_u32::()?; 156 | for _i in 0..entry_count { 157 | if current >= end { 158 | break; 159 | } 160 | 161 | // Get box header. 162 | let header = BoxHeader::read(reader)?; 163 | let BoxHeader { name, size: s } = header; 164 | if s > size { 165 | return Err(Error::InvalidData( 166 | "dinf box contains a box with a larger size than it", 167 | )); 168 | } 169 | 170 | match name { 171 | BoxType::UrlBox => { 172 | url = Some(UrlBox::read_box(reader, s)?); 173 | } 174 | _ => { 175 | skip_box(reader, s)?; 176 | } 177 | } 178 | 179 | current = reader.stream_position()?; 180 | } 181 | 182 | skip_bytes_to(reader, start + size)?; 183 | 184 | Ok(DrefBox { 185 | version, 186 | flags, 187 | url, 188 | }) 189 | } 190 | } 191 | 192 | impl WriteBox<&mut W> for DrefBox { 193 | fn write_box(&self, writer: &mut W) -> Result { 194 | let size = self.box_size(); 195 | BoxHeader::new(self.box_type(), size).write(writer)?; 196 | 197 | write_box_header_ext(writer, self.version, self.flags)?; 198 | 199 | writer.write_u32::(1)?; 200 | 201 | if let Some(ref url) = self.url { 202 | url.write_box(writer)?; 203 | } 204 | 205 | Ok(size) 206 | } 207 | } 208 | 209 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 210 | pub struct UrlBox { 211 | pub version: u8, 212 | pub flags: u32, 213 | pub location: String, 214 | } 215 | 216 | impl Default for UrlBox { 217 | fn default() -> Self { 218 | UrlBox { 219 | version: 0, 220 | flags: 1, 221 | location: String::default(), 222 | } 223 | } 224 | } 225 | 226 | impl UrlBox { 227 | pub fn get_type(&self) -> BoxType { 228 | BoxType::UrlBox 229 | } 230 | 231 | pub fn get_size(&self) -> u64 { 232 | let mut size = HEADER_SIZE + HEADER_EXT_SIZE; 233 | 234 | if !self.location.is_empty() { 235 | size += self.location.bytes().len() as u64 + 1; 236 | } 237 | 238 | size 239 | } 240 | } 241 | 242 | impl Mp4Box for UrlBox { 243 | fn box_type(&self) -> BoxType { 244 | self.get_type() 245 | } 246 | 247 | fn box_size(&self) -> u64 { 248 | self.get_size() 249 | } 250 | 251 | fn to_json(&self) -> Result { 252 | Ok(serde_json::to_string(&self).unwrap()) 253 | } 254 | 255 | fn summary(&self) -> Result { 256 | let s = format!("location={}", self.location); 257 | Ok(s) 258 | } 259 | } 260 | 261 | impl ReadBox<&mut R> for UrlBox { 262 | fn read_box(reader: &mut R, size: u64) -> Result { 263 | let start = box_start(reader)?; 264 | 265 | let (version, flags) = read_box_header_ext(reader)?; 266 | 267 | let buf_size = size 268 | .checked_sub(HEADER_SIZE + HEADER_EXT_SIZE) 269 | .ok_or(Error::InvalidData("url size too small"))?; 270 | 271 | let mut buf = vec![0u8; buf_size as usize]; 272 | reader.read_exact(&mut buf)?; 273 | if let Some(end) = buf.iter().position(|&b| b == b'\0') { 274 | buf.truncate(end); 275 | } 276 | let location = String::from_utf8(buf).unwrap_or_default(); 277 | 278 | skip_bytes_to(reader, start + size)?; 279 | 280 | Ok(UrlBox { 281 | version, 282 | flags, 283 | location, 284 | }) 285 | } 286 | } 287 | 288 | impl WriteBox<&mut W> for UrlBox { 289 | fn write_box(&self, writer: &mut W) -> Result { 290 | let size = self.box_size(); 291 | BoxHeader::new(self.box_type(), size).write(writer)?; 292 | 293 | write_box_header_ext(writer, self.version, self.flags)?; 294 | 295 | if !self.location.is_empty() { 296 | writer.write_all(self.location.as_bytes())?; 297 | writer.write_u8(0)?; 298 | } 299 | 300 | Ok(size) 301 | } 302 | } 303 | -------------------------------------------------------------------------------- /src/mp4box/edts.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::io::{Read, Seek, Write}; 3 | 4 | use crate::mp4box::elst::ElstBox; 5 | use crate::mp4box::*; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 8 | pub struct EdtsBox { 9 | pub elst: Option, 10 | } 11 | 12 | impl EdtsBox { 13 | pub(crate) fn new() -> EdtsBox { 14 | Default::default() 15 | } 16 | 17 | pub fn get_type(&self) -> BoxType { 18 | BoxType::EdtsBox 19 | } 20 | 21 | pub fn get_size(&self) -> u64 { 22 | let mut size = HEADER_SIZE; 23 | if let Some(ref elst) = self.elst { 24 | size += elst.box_size(); 25 | } 26 | size 27 | } 28 | } 29 | 30 | impl Mp4Box for EdtsBox { 31 | fn box_type(&self) -> BoxType { 32 | self.get_type() 33 | } 34 | 35 | fn box_size(&self) -> u64 { 36 | self.get_size() 37 | } 38 | 39 | fn to_json(&self) -> Result { 40 | Ok(serde_json::to_string(&self).unwrap()) 41 | } 42 | 43 | fn summary(&self) -> Result { 44 | let s = String::new(); 45 | Ok(s) 46 | } 47 | } 48 | 49 | impl ReadBox<&mut R> for EdtsBox { 50 | fn read_box(reader: &mut R, size: u64) -> Result { 51 | let start = box_start(reader)?; 52 | 53 | let mut edts = EdtsBox::new(); 54 | 55 | let header = BoxHeader::read(reader)?; 56 | let BoxHeader { name, size: s } = header; 57 | if s > size { 58 | return Err(Error::InvalidData( 59 | "edts box contains a box with a larger size than it", 60 | )); 61 | } 62 | 63 | if let BoxType::ElstBox = name { 64 | let elst = ElstBox::read_box(reader, s)?; 65 | edts.elst = Some(elst); 66 | } 67 | 68 | skip_bytes_to(reader, start + size)?; 69 | 70 | Ok(edts) 71 | } 72 | } 73 | 74 | impl WriteBox<&mut W> for EdtsBox { 75 | fn write_box(&self, writer: &mut W) -> Result { 76 | let size = self.box_size(); 77 | BoxHeader::new(self.box_type(), size).write(writer)?; 78 | 79 | if let Some(ref elst) = self.elst { 80 | elst.write_box(writer)?; 81 | } 82 | 83 | Ok(size) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/mp4box/elst.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | use std::mem::size_of; 5 | 6 | use crate::mp4box::*; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 9 | pub struct ElstBox { 10 | pub version: u8, 11 | pub flags: u32, 12 | 13 | #[serde(skip_serializing)] 14 | pub entries: Vec, 15 | } 16 | 17 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 18 | pub struct ElstEntry { 19 | pub segment_duration: u64, 20 | pub media_time: u64, 21 | pub media_rate: u16, 22 | pub media_rate_fraction: u16, 23 | } 24 | 25 | impl ElstBox { 26 | pub fn get_type(&self) -> BoxType { 27 | BoxType::ElstBox 28 | } 29 | 30 | pub fn get_size(&self) -> u64 { 31 | let mut size = HEADER_SIZE + HEADER_EXT_SIZE + 4; 32 | if self.version == 1 { 33 | size += self.entries.len() as u64 * 20; 34 | } else if self.version == 0 { 35 | size += self.entries.len() as u64 * 12; 36 | } 37 | size 38 | } 39 | } 40 | 41 | impl Mp4Box for ElstBox { 42 | fn box_type(&self) -> BoxType { 43 | self.get_type() 44 | } 45 | 46 | fn box_size(&self) -> u64 { 47 | self.get_size() 48 | } 49 | 50 | fn to_json(&self) -> Result { 51 | Ok(serde_json::to_string(&self).unwrap()) 52 | } 53 | 54 | fn summary(&self) -> Result { 55 | let s = format!("elst_entries={}", self.entries.len()); 56 | Ok(s) 57 | } 58 | } 59 | 60 | impl ReadBox<&mut R> for ElstBox { 61 | fn read_box(reader: &mut R, size: u64) -> Result { 62 | let start = box_start(reader)?; 63 | 64 | let (version, flags) = read_box_header_ext(reader)?; 65 | 66 | let header_size = HEADER_SIZE + HEADER_EXT_SIZE; 67 | let entry_count = reader.read_u32::()?; 68 | let other_size = size_of::(); // entry_count 69 | let entry_size = { 70 | let mut entry_size = 0; 71 | entry_size += if version == 1 { 72 | size_of::() + size_of::() // segment_duration + media_time 73 | } else { 74 | size_of::() + size_of::() // segment_duration + media_time 75 | }; 76 | entry_size += size_of::() + size_of::(); // media_rate_integer + media_rate_fraction 77 | entry_size 78 | }; 79 | if u64::from(entry_count) 80 | > size 81 | .saturating_sub(header_size) 82 | .saturating_sub(other_size as u64) 83 | / entry_size as u64 84 | { 85 | return Err(Error::InvalidData( 86 | "elst entry_count indicates more entries than could fit in the box", 87 | )); 88 | } 89 | let mut entries = Vec::with_capacity(entry_count as usize); 90 | for _ in 0..entry_count { 91 | let (segment_duration, media_time) = if version == 1 { 92 | ( 93 | reader.read_u64::()?, 94 | reader.read_u64::()?, 95 | ) 96 | } else { 97 | ( 98 | reader.read_u32::()? as u64, 99 | reader.read_u32::()? as u64, 100 | ) 101 | }; 102 | 103 | let entry = ElstEntry { 104 | segment_duration, 105 | media_time, 106 | media_rate: reader.read_u16::()?, 107 | media_rate_fraction: reader.read_u16::()?, 108 | }; 109 | entries.push(entry); 110 | } 111 | 112 | skip_bytes_to(reader, start + size)?; 113 | 114 | Ok(ElstBox { 115 | version, 116 | flags, 117 | entries, 118 | }) 119 | } 120 | } 121 | 122 | impl WriteBox<&mut W> for ElstBox { 123 | fn write_box(&self, writer: &mut W) -> Result { 124 | let size = self.box_size(); 125 | BoxHeader::new(self.box_type(), size).write(writer)?; 126 | 127 | write_box_header_ext(writer, self.version, self.flags)?; 128 | 129 | writer.write_u32::(self.entries.len() as u32)?; 130 | for entry in self.entries.iter() { 131 | if self.version == 1 { 132 | writer.write_u64::(entry.segment_duration)?; 133 | writer.write_u64::(entry.media_time)?; 134 | } else { 135 | writer.write_u32::(entry.segment_duration as u32)?; 136 | writer.write_u32::(entry.media_time as u32)?; 137 | } 138 | writer.write_u16::(entry.media_rate)?; 139 | writer.write_u16::(entry.media_rate_fraction)?; 140 | } 141 | 142 | Ok(size) 143 | } 144 | } 145 | 146 | #[cfg(test)] 147 | mod tests { 148 | use super::*; 149 | use crate::mp4box::BoxHeader; 150 | use std::io::Cursor; 151 | 152 | #[test] 153 | fn test_elst32() { 154 | let src_box = ElstBox { 155 | version: 0, 156 | flags: 0, 157 | entries: vec![ElstEntry { 158 | segment_duration: 634634, 159 | media_time: 0, 160 | media_rate: 1, 161 | media_rate_fraction: 0, 162 | }], 163 | }; 164 | let mut buf = Vec::new(); 165 | src_box.write_box(&mut buf).unwrap(); 166 | assert_eq!(buf.len(), src_box.box_size() as usize); 167 | 168 | let mut reader = Cursor::new(&buf); 169 | let header = BoxHeader::read(&mut reader).unwrap(); 170 | assert_eq!(header.name, BoxType::ElstBox); 171 | assert_eq!(src_box.box_size(), header.size); 172 | 173 | let dst_box = ElstBox::read_box(&mut reader, header.size).unwrap(); 174 | assert_eq!(src_box, dst_box); 175 | } 176 | 177 | #[test] 178 | fn test_elst64() { 179 | let src_box = ElstBox { 180 | version: 1, 181 | flags: 0, 182 | entries: vec![ElstEntry { 183 | segment_duration: 634634, 184 | media_time: 0, 185 | media_rate: 1, 186 | media_rate_fraction: 0, 187 | }], 188 | }; 189 | let mut buf = Vec::new(); 190 | src_box.write_box(&mut buf).unwrap(); 191 | assert_eq!(buf.len(), src_box.box_size() as usize); 192 | 193 | let mut reader = Cursor::new(&buf); 194 | let header = BoxHeader::read(&mut reader).unwrap(); 195 | assert_eq!(header.name, BoxType::ElstBox); 196 | assert_eq!(src_box.box_size(), header.size); 197 | 198 | let dst_box = ElstBox::read_box(&mut reader, header.size).unwrap(); 199 | assert_eq!(src_box, dst_box); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/mp4box/emsg.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | use std::io::{Read, Seek, Write}; 3 | 4 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 5 | use serde::Serialize; 6 | 7 | use crate::mp4box::*; 8 | 9 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 10 | pub struct EmsgBox { 11 | pub version: u8, 12 | pub flags: u32, 13 | pub timescale: u32, 14 | pub presentation_time: Option, 15 | pub presentation_time_delta: Option, 16 | pub event_duration: u32, 17 | pub id: u32, 18 | pub scheme_id_uri: String, 19 | pub value: String, 20 | pub message_data: Vec, 21 | } 22 | 23 | impl EmsgBox { 24 | fn size_without_message(version: u8, scheme_id_uri: &str, value: &str) -> u64 { 25 | HEADER_SIZE + HEADER_EXT_SIZE + 26 | 4 + // id 27 | Self::time_size(version) + 28 | (scheme_id_uri.len() + 1) as u64 + 29 | (value.len() as u64 + 1) 30 | } 31 | 32 | fn time_size(version: u8) -> u64 { 33 | match version { 34 | 0 => 12, 35 | 1 => 16, 36 | _ => panic!("version must be 0 or 1"), 37 | } 38 | } 39 | } 40 | 41 | impl Mp4Box for EmsgBox { 42 | fn box_type(&self) -> BoxType { 43 | BoxType::EmsgBox 44 | } 45 | 46 | fn box_size(&self) -> u64 { 47 | Self::size_without_message(self.version, &self.scheme_id_uri, &self.value) 48 | + self.message_data.len() as u64 49 | } 50 | 51 | fn to_json(&self) -> Result { 52 | Ok(serde_json::to_string(&self).unwrap()) 53 | } 54 | 55 | fn summary(&self) -> Result { 56 | let s = format!("id={} value={}", self.id, self.value); 57 | Ok(s) 58 | } 59 | } 60 | 61 | impl ReadBox<&mut R> for EmsgBox { 62 | fn read_box(reader: &mut R, size: u64) -> Result { 63 | let start = box_start(reader)?; 64 | let (version, flags) = read_box_header_ext(reader)?; 65 | 66 | let ( 67 | timescale, 68 | presentation_time, 69 | presentation_time_delta, 70 | event_duration, 71 | id, 72 | scheme_id_uri, 73 | value, 74 | ) = match version { 75 | 0 => { 76 | let scheme_id_uri = read_null_terminated_utf8_string(reader)?; 77 | let value = read_null_terminated_utf8_string(reader)?; 78 | ( 79 | reader.read_u32::()?, 80 | None, 81 | Some(reader.read_u32::()?), 82 | reader.read_u32::()?, 83 | reader.read_u32::()?, 84 | scheme_id_uri, 85 | value, 86 | ) 87 | } 88 | 1 => ( 89 | reader.read_u32::()?, 90 | Some(reader.read_u64::()?), 91 | None, 92 | reader.read_u32::()?, 93 | reader.read_u32::()?, 94 | read_null_terminated_utf8_string(reader)?, 95 | read_null_terminated_utf8_string(reader)?, 96 | ), 97 | _ => return Err(Error::InvalidData("version must be 0 or 1")), 98 | }; 99 | 100 | let message_size = size - Self::size_without_message(version, &scheme_id_uri, &value); 101 | let mut message_data = Vec::with_capacity(message_size as usize); 102 | for _ in 0..message_size { 103 | message_data.push(reader.read_u8()?); 104 | } 105 | 106 | skip_bytes_to(reader, start + size)?; 107 | 108 | Ok(EmsgBox { 109 | version, 110 | flags, 111 | timescale, 112 | presentation_time, 113 | presentation_time_delta, 114 | event_duration, 115 | id, 116 | scheme_id_uri, 117 | value, 118 | message_data, 119 | }) 120 | } 121 | } 122 | 123 | impl WriteBox<&mut W> for EmsgBox { 124 | fn write_box(&self, writer: &mut W) -> Result { 125 | let size = self.box_size(); 126 | BoxHeader::new(self.box_type(), size).write(writer)?; 127 | 128 | write_box_header_ext(writer, self.version, self.flags)?; 129 | match self.version { 130 | 0 => { 131 | write_null_terminated_str(writer, &self.scheme_id_uri)?; 132 | write_null_terminated_str(writer, &self.value)?; 133 | writer.write_u32::(self.timescale)?; 134 | writer.write_u32::(self.presentation_time_delta.unwrap())?; 135 | writer.write_u32::(self.event_duration)?; 136 | writer.write_u32::(self.id)?; 137 | } 138 | 1 => { 139 | writer.write_u32::(self.timescale)?; 140 | writer.write_u64::(self.presentation_time.unwrap())?; 141 | writer.write_u32::(self.event_duration)?; 142 | writer.write_u32::(self.id)?; 143 | write_null_terminated_str(writer, &self.scheme_id_uri)?; 144 | write_null_terminated_str(writer, &self.value)?; 145 | } 146 | _ => return Err(Error::InvalidData("version must be 0 or 1")), 147 | } 148 | 149 | for &byte in &self.message_data { 150 | writer.write_u8(byte)?; 151 | } 152 | 153 | Ok(size) 154 | } 155 | } 156 | 157 | fn read_null_terminated_utf8_string(reader: &mut R) -> Result { 158 | let mut bytes = Vec::new(); 159 | loop { 160 | let byte = reader.read_u8()?; 161 | bytes.push(byte); 162 | if byte == 0 { 163 | break; 164 | } 165 | } 166 | if let Ok(str) = unsafe { CStr::from_bytes_with_nul_unchecked(&bytes) }.to_str() { 167 | Ok(str.to_string()) 168 | } else { 169 | Err(Error::InvalidData("invalid utf8")) 170 | } 171 | } 172 | 173 | fn write_null_terminated_str(writer: &mut W, string: &str) -> Result<()> { 174 | for byte in string.bytes() { 175 | writer.write_u8(byte)?; 176 | } 177 | writer.write_u8(0)?; 178 | Ok(()) 179 | } 180 | 181 | #[cfg(test)] 182 | mod tests { 183 | use std::io::Cursor; 184 | 185 | use crate::mp4box::BoxHeader; 186 | 187 | use super::*; 188 | 189 | #[test] 190 | fn test_emsg_version0() { 191 | let src_box = EmsgBox { 192 | version: 0, 193 | flags: 0, 194 | timescale: 48000, 195 | presentation_time: None, 196 | presentation_time_delta: Some(100), 197 | event_duration: 200, 198 | id: 8, 199 | scheme_id_uri: String::from("foo"), 200 | value: String::from("foo"), 201 | message_data: vec![1, 2, 3], 202 | }; 203 | let mut buf = Vec::new(); 204 | src_box.write_box(&mut buf).unwrap(); 205 | assert_eq!(buf.len(), src_box.box_size() as usize); 206 | 207 | let mut reader = Cursor::new(&buf); 208 | let header = BoxHeader::read(&mut reader).unwrap(); 209 | assert_eq!(header.name, BoxType::EmsgBox); 210 | assert_eq!(src_box.box_size(), header.size); 211 | 212 | let dst_box = EmsgBox::read_box(&mut reader, header.size).unwrap(); 213 | assert_eq!(src_box, dst_box); 214 | } 215 | 216 | #[test] 217 | fn test_emsg_version1() { 218 | let src_box = EmsgBox { 219 | version: 1, 220 | flags: 0, 221 | timescale: 48000, 222 | presentation_time: Some(50000), 223 | presentation_time_delta: None, 224 | event_duration: 200, 225 | id: 8, 226 | scheme_id_uri: String::from("foo"), 227 | value: String::from("foo"), 228 | message_data: vec![3, 2, 1], 229 | }; 230 | let mut buf = Vec::new(); 231 | src_box.write_box(&mut buf).unwrap(); 232 | assert_eq!(buf.len(), src_box.box_size() as usize); 233 | 234 | let mut reader = Cursor::new(&buf); 235 | let header = BoxHeader::read(&mut reader).unwrap(); 236 | assert_eq!(header.name, BoxType::EmsgBox); 237 | assert_eq!(src_box.box_size(), header.size); 238 | 239 | let dst_box = EmsgBox::read_box(&mut reader, header.size).unwrap(); 240 | assert_eq!(src_box, dst_box); 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /src/mp4box/ftyp.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | 5 | use crate::mp4box::*; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 8 | pub struct FtypBox { 9 | pub major_brand: FourCC, 10 | pub minor_version: u32, 11 | pub compatible_brands: Vec, 12 | } 13 | 14 | impl FtypBox { 15 | pub fn get_type(&self) -> BoxType { 16 | BoxType::FtypBox 17 | } 18 | 19 | pub fn get_size(&self) -> u64 { 20 | HEADER_SIZE + 8 + (4 * self.compatible_brands.len() as u64) 21 | } 22 | } 23 | 24 | impl Mp4Box for FtypBox { 25 | fn box_type(&self) -> BoxType { 26 | self.get_type() 27 | } 28 | 29 | fn box_size(&self) -> u64 { 30 | self.get_size() 31 | } 32 | 33 | fn to_json(&self) -> Result { 34 | Ok(serde_json::to_string(&self).unwrap()) 35 | } 36 | 37 | fn summary(&self) -> Result { 38 | let mut compatible_brands = Vec::new(); 39 | for brand in self.compatible_brands.iter() { 40 | compatible_brands.push(brand.to_string()); 41 | } 42 | let s = format!( 43 | "major_brand={} minor_version={} compatible_brands={}", 44 | self.major_brand, 45 | self.minor_version, 46 | compatible_brands.join("-") 47 | ); 48 | Ok(s) 49 | } 50 | } 51 | 52 | impl ReadBox<&mut R> for FtypBox { 53 | fn read_box(reader: &mut R, size: u64) -> Result { 54 | let start = box_start(reader)?; 55 | 56 | if size < 16 || size % 4 != 0 { 57 | return Err(Error::InvalidData("ftyp size too small or not aligned")); 58 | } 59 | let brand_count = (size - 16) / 4; // header + major + minor 60 | let major = reader.read_u32::()?; 61 | let minor = reader.read_u32::()?; 62 | 63 | let mut brands = Vec::new(); 64 | for _ in 0..brand_count { 65 | let b = reader.read_u32::()?; 66 | brands.push(From::from(b)); 67 | } 68 | 69 | skip_bytes_to(reader, start + size)?; 70 | 71 | Ok(FtypBox { 72 | major_brand: From::from(major), 73 | minor_version: minor, 74 | compatible_brands: brands, 75 | }) 76 | } 77 | } 78 | 79 | impl WriteBox<&mut W> for FtypBox { 80 | fn write_box(&self, writer: &mut W) -> Result { 81 | let size = self.box_size(); 82 | BoxHeader::new(self.box_type(), size).write(writer)?; 83 | 84 | writer.write_u32::((&self.major_brand).into())?; 85 | writer.write_u32::(self.minor_version)?; 86 | for b in self.compatible_brands.iter() { 87 | writer.write_u32::(b.into())?; 88 | } 89 | Ok(size) 90 | } 91 | } 92 | 93 | #[cfg(test)] 94 | mod tests { 95 | use super::*; 96 | use crate::mp4box::BoxHeader; 97 | use std::io::Cursor; 98 | 99 | #[test] 100 | fn test_ftyp() { 101 | let src_box = FtypBox { 102 | major_brand: str::parse("isom").unwrap(), 103 | minor_version: 0, 104 | compatible_brands: vec![ 105 | str::parse("isom").unwrap(), 106 | str::parse("iso2").unwrap(), 107 | str::parse("avc1").unwrap(), 108 | str::parse("mp41").unwrap(), 109 | ], 110 | }; 111 | let mut buf = Vec::new(); 112 | src_box.write_box(&mut buf).unwrap(); 113 | assert_eq!(buf.len(), src_box.box_size() as usize); 114 | 115 | let mut reader = Cursor::new(&buf); 116 | let header = BoxHeader::read(&mut reader).unwrap(); 117 | assert_eq!(header.name, BoxType::FtypBox); 118 | assert_eq!(src_box.box_size(), header.size); 119 | 120 | let dst_box = FtypBox::read_box(&mut reader, header.size).unwrap(); 121 | assert_eq!(src_box, dst_box); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/mp4box/hdlr.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | 5 | use crate::mp4box::*; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 8 | pub struct HdlrBox { 9 | pub version: u8, 10 | pub flags: u32, 11 | pub handler_type: FourCC, 12 | pub name: String, 13 | } 14 | 15 | impl HdlrBox { 16 | pub fn get_type(&self) -> BoxType { 17 | BoxType::HdlrBox 18 | } 19 | 20 | pub fn get_size(&self) -> u64 { 21 | HEADER_SIZE + HEADER_EXT_SIZE + 20 + self.name.len() as u64 + 1 22 | } 23 | } 24 | 25 | impl Mp4Box for HdlrBox { 26 | fn box_type(&self) -> BoxType { 27 | self.get_type() 28 | } 29 | 30 | fn box_size(&self) -> u64 { 31 | self.get_size() 32 | } 33 | 34 | fn to_json(&self) -> Result { 35 | Ok(serde_json::to_string(&self).unwrap()) 36 | } 37 | 38 | fn summary(&self) -> Result { 39 | let s = format!("handler_type={} name={}", self.handler_type, self.name); 40 | Ok(s) 41 | } 42 | } 43 | 44 | impl ReadBox<&mut R> for HdlrBox { 45 | fn read_box(reader: &mut R, size: u64) -> Result { 46 | let start = box_start(reader)?; 47 | 48 | let (version, flags) = read_box_header_ext(reader)?; 49 | 50 | reader.read_u32::()?; // pre-defined 51 | let handler = reader.read_u32::()?; 52 | 53 | skip_bytes(reader, 12)?; // reserved 54 | 55 | let buf_size = size 56 | .checked_sub(HEADER_SIZE + HEADER_EXT_SIZE + 20) 57 | .ok_or(Error::InvalidData("hdlr size too small"))?; 58 | 59 | let mut buf = vec![0u8; buf_size as usize]; 60 | reader.read_exact(&mut buf)?; 61 | if let Some(end) = buf.iter().position(|&b| b == b'\0') { 62 | buf.truncate(end); 63 | } 64 | let handler_string = String::from_utf8(buf).unwrap_or_default(); 65 | 66 | skip_bytes_to(reader, start + size)?; 67 | 68 | Ok(HdlrBox { 69 | version, 70 | flags, 71 | handler_type: From::from(handler), 72 | name: handler_string, 73 | }) 74 | } 75 | } 76 | 77 | impl WriteBox<&mut W> for HdlrBox { 78 | fn write_box(&self, writer: &mut W) -> Result { 79 | let size = self.box_size(); 80 | BoxHeader::new(self.box_type(), size).write(writer)?; 81 | 82 | write_box_header_ext(writer, self.version, self.flags)?; 83 | 84 | writer.write_u32::(0)?; // pre-defined 85 | writer.write_u32::((&self.handler_type).into())?; 86 | 87 | // 12 bytes reserved 88 | for _ in 0..3 { 89 | writer.write_u32::(0)?; 90 | } 91 | 92 | writer.write_all(self.name.as_bytes())?; 93 | writer.write_u8(0)?; 94 | 95 | Ok(size) 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use super::*; 102 | use crate::mp4box::BoxHeader; 103 | use std::io::Cursor; 104 | 105 | #[test] 106 | fn test_hdlr() { 107 | let src_box = HdlrBox { 108 | version: 0, 109 | flags: 0, 110 | handler_type: str::parse::("vide").unwrap(), 111 | name: String::from("VideoHandler"), 112 | }; 113 | let mut buf = Vec::new(); 114 | src_box.write_box(&mut buf).unwrap(); 115 | assert_eq!(buf.len(), src_box.box_size() as usize); 116 | 117 | let mut reader = Cursor::new(&buf); 118 | let header = BoxHeader::read(&mut reader).unwrap(); 119 | assert_eq!(header.name, BoxType::HdlrBox); 120 | assert_eq!(src_box.box_size(), header.size); 121 | 122 | let dst_box = HdlrBox::read_box(&mut reader, header.size).unwrap(); 123 | assert_eq!(src_box, dst_box); 124 | } 125 | 126 | #[test] 127 | fn test_hdlr_empty() { 128 | let src_box = HdlrBox { 129 | version: 0, 130 | flags: 0, 131 | handler_type: str::parse::("vide").unwrap(), 132 | name: String::new(), 133 | }; 134 | let mut buf = Vec::new(); 135 | src_box.write_box(&mut buf).unwrap(); 136 | assert_eq!(buf.len(), src_box.box_size() as usize); 137 | 138 | let mut reader = Cursor::new(&buf); 139 | let header = BoxHeader::read(&mut reader).unwrap(); 140 | assert_eq!(header.name, BoxType::HdlrBox); 141 | assert_eq!(src_box.box_size(), header.size); 142 | 143 | let dst_box = HdlrBox::read_box(&mut reader, header.size).unwrap(); 144 | assert_eq!(src_box, dst_box); 145 | } 146 | 147 | #[test] 148 | fn test_hdlr_extra() { 149 | let real_src_box = HdlrBox { 150 | version: 0, 151 | flags: 0, 152 | handler_type: str::parse::("vide").unwrap(), 153 | name: String::from("Good"), 154 | }; 155 | let src_box = HdlrBox { 156 | version: 0, 157 | flags: 0, 158 | handler_type: str::parse::("vide").unwrap(), 159 | name: String::from_utf8(b"Good\0Bad".to_vec()).unwrap(), 160 | }; 161 | let mut buf = Vec::new(); 162 | src_box.write_box(&mut buf).unwrap(); 163 | assert_eq!(buf.len(), src_box.box_size() as usize); 164 | 165 | let mut reader = Cursor::new(&buf); 166 | let header = BoxHeader::read(&mut reader).unwrap(); 167 | assert_eq!(header.name, BoxType::HdlrBox); 168 | assert_eq!(src_box.box_size(), header.size); 169 | 170 | let dst_box = HdlrBox::read_box(&mut reader, header.size).unwrap(); 171 | assert_eq!(real_src_box, dst_box); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/mp4box/ilst.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::collections::HashMap; 3 | use std::io::{Read, Seek}; 4 | 5 | use byteorder::ByteOrder; 6 | use serde::Serialize; 7 | 8 | use crate::mp4box::data::DataBox; 9 | use crate::mp4box::*; 10 | 11 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 12 | pub struct IlstBox { 13 | pub items: HashMap, 14 | } 15 | 16 | impl IlstBox { 17 | pub fn get_type(&self) -> BoxType { 18 | BoxType::IlstBox 19 | } 20 | 21 | pub fn get_size(&self) -> u64 { 22 | let mut size = HEADER_SIZE; 23 | for item in self.items.values() { 24 | size += item.get_size(); 25 | } 26 | size 27 | } 28 | } 29 | 30 | impl Mp4Box for IlstBox { 31 | fn box_type(&self) -> BoxType { 32 | self.get_type() 33 | } 34 | 35 | fn box_size(&self) -> u64 { 36 | self.get_size() 37 | } 38 | 39 | fn to_json(&self) -> Result { 40 | Ok(serde_json::to_string(&self).unwrap()) 41 | } 42 | 43 | fn summary(&self) -> Result { 44 | let s = format!("item_count={}", self.items.len()); 45 | Ok(s) 46 | } 47 | } 48 | 49 | impl ReadBox<&mut R> for IlstBox { 50 | fn read_box(reader: &mut R, size: u64) -> Result { 51 | let start = box_start(reader)?; 52 | 53 | let mut items = HashMap::new(); 54 | 55 | let mut current = reader.stream_position()?; 56 | let end = start + size; 57 | while current < end { 58 | // Get box header. 59 | let header = BoxHeader::read(reader)?; 60 | let BoxHeader { name, size: s } = header; 61 | if s > size { 62 | return Err(Error::InvalidData( 63 | "ilst box contains a box with a larger size than it", 64 | )); 65 | } 66 | 67 | match name { 68 | BoxType::NameBox => { 69 | items.insert(MetadataKey::Title, IlstItemBox::read_box(reader, s)?); 70 | } 71 | BoxType::DayBox => { 72 | items.insert(MetadataKey::Year, IlstItemBox::read_box(reader, s)?); 73 | } 74 | BoxType::CovrBox => { 75 | items.insert(MetadataKey::Poster, IlstItemBox::read_box(reader, s)?); 76 | } 77 | BoxType::DescBox => { 78 | items.insert(MetadataKey::Summary, IlstItemBox::read_box(reader, s)?); 79 | } 80 | _ => { 81 | // XXX warn!() 82 | skip_box(reader, s)?; 83 | } 84 | } 85 | 86 | current = reader.stream_position()?; 87 | } 88 | 89 | skip_bytes_to(reader, start + size)?; 90 | 91 | Ok(IlstBox { items }) 92 | } 93 | } 94 | 95 | impl WriteBox<&mut W> for IlstBox { 96 | fn write_box(&self, writer: &mut W) -> Result { 97 | let size = self.box_size(); 98 | BoxHeader::new(self.box_type(), size).write(writer)?; 99 | 100 | for (key, value) in &self.items { 101 | let name = match key { 102 | MetadataKey::Title => BoxType::NameBox, 103 | MetadataKey::Year => BoxType::DayBox, 104 | MetadataKey::Poster => BoxType::CovrBox, 105 | MetadataKey::Summary => BoxType::DescBox, 106 | }; 107 | BoxHeader::new(name, value.get_size()).write(writer)?; 108 | value.data.write_box(writer)?; 109 | } 110 | Ok(size) 111 | } 112 | } 113 | 114 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 115 | pub struct IlstItemBox { 116 | pub data: DataBox, 117 | } 118 | 119 | impl IlstItemBox { 120 | fn get_size(&self) -> u64 { 121 | HEADER_SIZE + self.data.box_size() 122 | } 123 | } 124 | 125 | impl ReadBox<&mut R> for IlstItemBox { 126 | fn read_box(reader: &mut R, size: u64) -> Result { 127 | let start = box_start(reader)?; 128 | 129 | let mut data = None; 130 | 131 | let mut current = reader.stream_position()?; 132 | let end = start + size; 133 | while current < end { 134 | // Get box header. 135 | let header = BoxHeader::read(reader)?; 136 | let BoxHeader { name, size: s } = header; 137 | if s > size { 138 | return Err(Error::InvalidData( 139 | "ilst item box contains a box with a larger size than it", 140 | )); 141 | } 142 | 143 | match name { 144 | BoxType::DataBox => { 145 | data = Some(DataBox::read_box(reader, s)?); 146 | } 147 | _ => { 148 | // XXX warn!() 149 | skip_box(reader, s)?; 150 | } 151 | } 152 | 153 | current = reader.stream_position()?; 154 | } 155 | 156 | if data.is_none() { 157 | return Err(Error::BoxNotFound(BoxType::DataBox)); 158 | } 159 | 160 | skip_bytes_to(reader, start + size)?; 161 | 162 | Ok(IlstItemBox { 163 | data: data.unwrap(), 164 | }) 165 | } 166 | } 167 | 168 | impl<'a> Metadata<'a> for IlstBox { 169 | fn title(&self) -> Option> { 170 | self.items.get(&MetadataKey::Title).map(item_to_str) 171 | } 172 | 173 | fn year(&self) -> Option { 174 | self.items.get(&MetadataKey::Year).and_then(item_to_u32) 175 | } 176 | 177 | fn poster(&self) -> Option<&[u8]> { 178 | self.items.get(&MetadataKey::Poster).map(item_to_bytes) 179 | } 180 | 181 | fn summary(&self) -> Option> { 182 | self.items.get(&MetadataKey::Summary).map(item_to_str) 183 | } 184 | } 185 | 186 | fn item_to_bytes(item: &IlstItemBox) -> &[u8] { 187 | &item.data.data 188 | } 189 | 190 | fn item_to_str(item: &IlstItemBox) -> Cow { 191 | String::from_utf8_lossy(&item.data.data) 192 | } 193 | 194 | fn item_to_u32(item: &IlstItemBox) -> Option { 195 | match item.data.data_type { 196 | DataType::Binary if item.data.data.len() == 4 => Some(BigEndian::read_u32(&item.data.data)), 197 | DataType::Text => String::from_utf8_lossy(&item.data.data).parse::().ok(), 198 | _ => None, 199 | } 200 | } 201 | 202 | #[cfg(test)] 203 | mod tests { 204 | use super::*; 205 | use crate::mp4box::BoxHeader; 206 | use std::io::Cursor; 207 | 208 | #[test] 209 | fn test_ilst() { 210 | let src_year = IlstItemBox { 211 | data: DataBox { 212 | data_type: DataType::Text, 213 | data: b"test_year".to_vec(), 214 | }, 215 | }; 216 | let src_box = IlstBox { 217 | items: [ 218 | (MetadataKey::Title, IlstItemBox::default()), 219 | (MetadataKey::Year, src_year), 220 | (MetadataKey::Poster, IlstItemBox::default()), 221 | (MetadataKey::Summary, IlstItemBox::default()), 222 | ] 223 | .into(), 224 | }; 225 | let mut buf = Vec::new(); 226 | src_box.write_box(&mut buf).unwrap(); 227 | assert_eq!(buf.len(), src_box.box_size() as usize); 228 | 229 | let mut reader = Cursor::new(&buf); 230 | let header = BoxHeader::read(&mut reader).unwrap(); 231 | assert_eq!(header.name, BoxType::IlstBox); 232 | assert_eq!(src_box.box_size(), header.size); 233 | 234 | let dst_box = IlstBox::read_box(&mut reader, header.size).unwrap(); 235 | assert_eq!(src_box, dst_box); 236 | } 237 | 238 | #[test] 239 | fn test_ilst_empty() { 240 | let src_box = IlstBox::default(); 241 | let mut buf = Vec::new(); 242 | src_box.write_box(&mut buf).unwrap(); 243 | assert_eq!(buf.len(), src_box.box_size() as usize); 244 | 245 | let mut reader = Cursor::new(&buf); 246 | let header = BoxHeader::read(&mut reader).unwrap(); 247 | assert_eq!(header.name, BoxType::IlstBox); 248 | assert_eq!(src_box.box_size(), header.size); 249 | 250 | let dst_box = IlstBox::read_box(&mut reader, header.size).unwrap(); 251 | assert_eq!(src_box, dst_box); 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/mp4box/mdhd.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::char::{decode_utf16, REPLACEMENT_CHARACTER}; 4 | use std::io::{Read, Seek, Write}; 5 | 6 | use crate::mp4box::*; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 9 | pub struct MdhdBox { 10 | pub version: u8, 11 | pub flags: u32, 12 | pub creation_time: u64, 13 | pub modification_time: u64, 14 | pub timescale: u32, 15 | pub duration: u64, 16 | pub language: String, 17 | } 18 | 19 | impl MdhdBox { 20 | pub fn get_type(&self) -> BoxType { 21 | BoxType::MdhdBox 22 | } 23 | 24 | pub fn get_size(&self) -> u64 { 25 | let mut size = HEADER_SIZE + HEADER_EXT_SIZE; 26 | 27 | if self.version == 1 { 28 | size += 28; 29 | } else if self.version == 0 { 30 | size += 16; 31 | } 32 | size += 4; 33 | size 34 | } 35 | } 36 | 37 | impl Default for MdhdBox { 38 | fn default() -> Self { 39 | MdhdBox { 40 | version: 0, 41 | flags: 0, 42 | creation_time: 0, 43 | modification_time: 0, 44 | timescale: 1000, 45 | duration: 0, 46 | language: String::from("und"), 47 | } 48 | } 49 | } 50 | 51 | impl Mp4Box for MdhdBox { 52 | fn box_type(&self) -> BoxType { 53 | self.get_type() 54 | } 55 | 56 | fn box_size(&self) -> u64 { 57 | self.get_size() 58 | } 59 | 60 | fn to_json(&self) -> Result { 61 | Ok(serde_json::to_string(&self).unwrap()) 62 | } 63 | 64 | fn summary(&self) -> Result { 65 | let s = format!( 66 | "creation_time={} timescale={} duration={} language={}", 67 | self.creation_time, self.timescale, self.duration, self.language 68 | ); 69 | Ok(s) 70 | } 71 | } 72 | 73 | impl ReadBox<&mut R> for MdhdBox { 74 | fn read_box(reader: &mut R, size: u64) -> Result { 75 | let start = box_start(reader)?; 76 | 77 | let (version, flags) = read_box_header_ext(reader)?; 78 | 79 | let (creation_time, modification_time, timescale, duration) = if version == 1 { 80 | ( 81 | reader.read_u64::()?, 82 | reader.read_u64::()?, 83 | reader.read_u32::()?, 84 | reader.read_u64::()?, 85 | ) 86 | } else if version == 0 { 87 | ( 88 | reader.read_u32::()? as u64, 89 | reader.read_u32::()? as u64, 90 | reader.read_u32::()?, 91 | reader.read_u32::()? as u64, 92 | ) 93 | } else { 94 | return Err(Error::InvalidData("version must be 0 or 1")); 95 | }; 96 | let language_code = reader.read_u16::()?; 97 | let language = language_string(language_code); 98 | 99 | skip_bytes_to(reader, start + size)?; 100 | 101 | Ok(MdhdBox { 102 | version, 103 | flags, 104 | creation_time, 105 | modification_time, 106 | timescale, 107 | duration, 108 | language, 109 | }) 110 | } 111 | } 112 | 113 | impl WriteBox<&mut W> for MdhdBox { 114 | fn write_box(&self, writer: &mut W) -> Result { 115 | let size = self.box_size(); 116 | BoxHeader::new(self.box_type(), size).write(writer)?; 117 | 118 | write_box_header_ext(writer, self.version, self.flags)?; 119 | 120 | if self.version == 1 { 121 | writer.write_u64::(self.creation_time)?; 122 | writer.write_u64::(self.modification_time)?; 123 | writer.write_u32::(self.timescale)?; 124 | writer.write_u64::(self.duration)?; 125 | } else if self.version == 0 { 126 | writer.write_u32::(self.creation_time as u32)?; 127 | writer.write_u32::(self.modification_time as u32)?; 128 | writer.write_u32::(self.timescale)?; 129 | writer.write_u32::(self.duration as u32)?; 130 | } else { 131 | return Err(Error::InvalidData("version must be 0 or 1")); 132 | } 133 | 134 | let language_code = language_code(&self.language); 135 | writer.write_u16::(language_code)?; 136 | writer.write_u16::(0)?; // pre-defined 137 | 138 | Ok(size) 139 | } 140 | } 141 | 142 | fn language_string(language: u16) -> String { 143 | let mut lang: [u16; 3] = [0; 3]; 144 | 145 | lang[0] = ((language >> 10) & 0x1F) + 0x60; 146 | lang[1] = ((language >> 5) & 0x1F) + 0x60; 147 | lang[2] = ((language) & 0x1F) + 0x60; 148 | 149 | // Decode utf-16 encoded bytes into a string. 150 | let lang_str = decode_utf16(lang.iter().cloned()) 151 | .map(|r| r.unwrap_or(REPLACEMENT_CHARACTER)) 152 | .collect::(); 153 | 154 | lang_str 155 | } 156 | 157 | fn language_code(language: &str) -> u16 { 158 | let mut lang = language.encode_utf16(); 159 | let mut code = (lang.next().unwrap_or(0) & 0x1F) << 10; 160 | code += (lang.next().unwrap_or(0) & 0x1F) << 5; 161 | code += lang.next().unwrap_or(0) & 0x1F; 162 | code 163 | } 164 | 165 | #[cfg(test)] 166 | mod tests { 167 | use super::*; 168 | use crate::mp4box::BoxHeader; 169 | use std::io::Cursor; 170 | 171 | fn test_language_code(lang: &str) { 172 | let code = language_code(lang); 173 | let lang2 = language_string(code); 174 | assert_eq!(lang, lang2); 175 | } 176 | 177 | #[test] 178 | fn test_language_codes() { 179 | test_language_code("und"); 180 | test_language_code("eng"); 181 | test_language_code("kor"); 182 | } 183 | 184 | #[test] 185 | fn test_mdhd32() { 186 | let src_box = MdhdBox { 187 | version: 0, 188 | flags: 0, 189 | creation_time: 100, 190 | modification_time: 200, 191 | timescale: 48000, 192 | duration: 30439936, 193 | language: String::from("und"), 194 | }; 195 | let mut buf = Vec::new(); 196 | src_box.write_box(&mut buf).unwrap(); 197 | assert_eq!(buf.len(), src_box.box_size() as usize); 198 | 199 | let mut reader = Cursor::new(&buf); 200 | let header = BoxHeader::read(&mut reader).unwrap(); 201 | assert_eq!(header.name, BoxType::MdhdBox); 202 | assert_eq!(src_box.box_size(), header.size); 203 | 204 | let dst_box = MdhdBox::read_box(&mut reader, header.size).unwrap(); 205 | assert_eq!(src_box, dst_box); 206 | } 207 | 208 | #[test] 209 | fn test_mdhd64() { 210 | let src_box = MdhdBox { 211 | version: 0, 212 | flags: 0, 213 | creation_time: 100, 214 | modification_time: 200, 215 | timescale: 48000, 216 | duration: 30439936, 217 | language: String::from("eng"), 218 | }; 219 | let mut buf = Vec::new(); 220 | src_box.write_box(&mut buf).unwrap(); 221 | assert_eq!(buf.len(), src_box.box_size() as usize); 222 | 223 | let mut reader = Cursor::new(&buf); 224 | let header = BoxHeader::read(&mut reader).unwrap(); 225 | assert_eq!(header.name, BoxType::MdhdBox); 226 | assert_eq!(src_box.box_size(), header.size); 227 | 228 | let dst_box = MdhdBox::read_box(&mut reader, header.size).unwrap(); 229 | assert_eq!(src_box, dst_box); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/mp4box/mdia.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::io::{Read, Seek, Write}; 3 | 4 | use crate::mp4box::*; 5 | use crate::mp4box::{hdlr::HdlrBox, mdhd::MdhdBox, minf::MinfBox}; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 8 | pub struct MdiaBox { 9 | pub mdhd: MdhdBox, 10 | pub hdlr: HdlrBox, 11 | pub minf: MinfBox, 12 | } 13 | 14 | impl MdiaBox { 15 | pub fn get_type(&self) -> BoxType { 16 | BoxType::MdiaBox 17 | } 18 | 19 | pub fn get_size(&self) -> u64 { 20 | HEADER_SIZE + self.mdhd.box_size() + self.hdlr.box_size() + self.minf.box_size() 21 | } 22 | } 23 | 24 | impl Mp4Box for MdiaBox { 25 | fn box_type(&self) -> BoxType { 26 | self.get_type() 27 | } 28 | 29 | fn box_size(&self) -> u64 { 30 | self.get_size() 31 | } 32 | 33 | fn to_json(&self) -> Result { 34 | Ok(serde_json::to_string(&self).unwrap()) 35 | } 36 | 37 | fn summary(&self) -> Result { 38 | let s = String::new(); 39 | Ok(s) 40 | } 41 | } 42 | 43 | impl ReadBox<&mut R> for MdiaBox { 44 | fn read_box(reader: &mut R, size: u64) -> Result { 45 | let start = box_start(reader)?; 46 | 47 | let mut mdhd = None; 48 | let mut hdlr = None; 49 | let mut minf = None; 50 | 51 | let mut current = reader.stream_position()?; 52 | let end = start + size; 53 | while current < end { 54 | // Get box header. 55 | let header = BoxHeader::read(reader)?; 56 | let BoxHeader { name, size: s } = header; 57 | if s > size { 58 | return Err(Error::InvalidData( 59 | "mdia box contains a box with a larger size than it", 60 | )); 61 | } 62 | 63 | match name { 64 | BoxType::MdhdBox => { 65 | mdhd = Some(MdhdBox::read_box(reader, s)?); 66 | } 67 | BoxType::HdlrBox => { 68 | hdlr = Some(HdlrBox::read_box(reader, s)?); 69 | } 70 | BoxType::MinfBox => { 71 | minf = Some(MinfBox::read_box(reader, s)?); 72 | } 73 | _ => { 74 | // XXX warn!() 75 | skip_box(reader, s)?; 76 | } 77 | } 78 | 79 | current = reader.stream_position()?; 80 | } 81 | 82 | if mdhd.is_none() { 83 | return Err(Error::BoxNotFound(BoxType::MdhdBox)); 84 | } 85 | if hdlr.is_none() { 86 | return Err(Error::BoxNotFound(BoxType::HdlrBox)); 87 | } 88 | if minf.is_none() { 89 | return Err(Error::BoxNotFound(BoxType::MinfBox)); 90 | } 91 | 92 | skip_bytes_to(reader, start + size)?; 93 | 94 | Ok(MdiaBox { 95 | mdhd: mdhd.unwrap(), 96 | hdlr: hdlr.unwrap(), 97 | minf: minf.unwrap(), 98 | }) 99 | } 100 | } 101 | 102 | impl WriteBox<&mut W> for MdiaBox { 103 | fn write_box(&self, writer: &mut W) -> Result { 104 | let size = self.box_size(); 105 | BoxHeader::new(self.box_type(), size).write(writer)?; 106 | 107 | self.mdhd.write_box(writer)?; 108 | self.hdlr.write_box(writer)?; 109 | self.minf.write_box(writer)?; 110 | 111 | Ok(size) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/mp4box/mehd.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | 5 | use crate::mp4box::*; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)] 8 | pub struct MehdBox { 9 | pub version: u8, 10 | pub flags: u32, 11 | pub fragment_duration: u64, 12 | } 13 | 14 | impl MehdBox { 15 | pub fn get_type(&self) -> BoxType { 16 | BoxType::MehdBox 17 | } 18 | 19 | pub fn get_size(&self) -> u64 { 20 | let mut size = HEADER_SIZE + HEADER_EXT_SIZE; 21 | 22 | if self.version == 1 { 23 | size += 8; 24 | } else if self.version == 0 { 25 | size += 4; 26 | } 27 | size 28 | } 29 | } 30 | 31 | impl Mp4Box for MehdBox { 32 | fn box_type(&self) -> BoxType { 33 | self.get_type() 34 | } 35 | 36 | fn box_size(&self) -> u64 { 37 | self.get_size() 38 | } 39 | 40 | fn to_json(&self) -> Result { 41 | Ok(serde_json::to_string(&self).unwrap()) 42 | } 43 | 44 | fn summary(&self) -> Result { 45 | let s = format!("fragment_duration={}", self.fragment_duration); 46 | Ok(s) 47 | } 48 | } 49 | 50 | impl ReadBox<&mut R> for MehdBox { 51 | fn read_box(reader: &mut R, size: u64) -> Result { 52 | let start = box_start(reader)?; 53 | 54 | let (version, flags) = read_box_header_ext(reader)?; 55 | 56 | let fragment_duration = if version == 1 { 57 | reader.read_u64::()? 58 | } else if version == 0 { 59 | reader.read_u32::()? as u64 60 | } else { 61 | return Err(Error::InvalidData("version must be 0 or 1")); 62 | }; 63 | skip_bytes_to(reader, start + size)?; 64 | 65 | Ok(MehdBox { 66 | version, 67 | flags, 68 | fragment_duration, 69 | }) 70 | } 71 | } 72 | 73 | impl WriteBox<&mut W> for MehdBox { 74 | fn write_box(&self, writer: &mut W) -> Result { 75 | let size = self.box_size(); 76 | BoxHeader::new(self.box_type(), size).write(writer)?; 77 | 78 | write_box_header_ext(writer, self.version, self.flags)?; 79 | 80 | if self.version == 1 { 81 | writer.write_u64::(self.fragment_duration)?; 82 | } else if self.version == 0 { 83 | writer.write_u32::(self.fragment_duration as u32)?; 84 | } else { 85 | return Err(Error::InvalidData("version must be 0 or 1")); 86 | } 87 | 88 | Ok(size) 89 | } 90 | } 91 | 92 | #[cfg(test)] 93 | mod tests { 94 | use super::*; 95 | use crate::mp4box::BoxHeader; 96 | use std::io::Cursor; 97 | 98 | #[test] 99 | fn test_mehd32() { 100 | let src_box = MehdBox { 101 | version: 0, 102 | flags: 0, 103 | fragment_duration: 32, 104 | }; 105 | let mut buf = Vec::new(); 106 | src_box.write_box(&mut buf).unwrap(); 107 | assert_eq!(buf.len(), src_box.box_size() as usize); 108 | 109 | let mut reader = Cursor::new(&buf); 110 | let header = BoxHeader::read(&mut reader).unwrap(); 111 | assert_eq!(header.name, BoxType::MehdBox); 112 | assert_eq!(src_box.box_size(), header.size); 113 | 114 | let dst_box = MehdBox::read_box(&mut reader, header.size).unwrap(); 115 | assert_eq!(src_box, dst_box); 116 | } 117 | 118 | #[test] 119 | fn test_mehd64() { 120 | let src_box = MehdBox { 121 | version: 0, 122 | flags: 0, 123 | fragment_duration: 30439936, 124 | }; 125 | let mut buf = Vec::new(); 126 | src_box.write_box(&mut buf).unwrap(); 127 | assert_eq!(buf.len(), src_box.box_size() as usize); 128 | 129 | let mut reader = Cursor::new(&buf); 130 | let header = BoxHeader::read(&mut reader).unwrap(); 131 | assert_eq!(header.name, BoxType::MehdBox); 132 | assert_eq!(src_box.box_size(), header.size); 133 | 134 | let dst_box = MehdBox::read_box(&mut reader, header.size).unwrap(); 135 | assert_eq!(src_box, dst_box); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/mp4box/mfhd.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | 5 | use crate::mp4box::*; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 8 | pub struct MfhdBox { 9 | pub version: u8, 10 | pub flags: u32, 11 | pub sequence_number: u32, 12 | } 13 | 14 | impl Default for MfhdBox { 15 | fn default() -> Self { 16 | MfhdBox { 17 | version: 0, 18 | flags: 0, 19 | sequence_number: 1, 20 | } 21 | } 22 | } 23 | 24 | impl MfhdBox { 25 | pub fn get_type(&self) -> BoxType { 26 | BoxType::MfhdBox 27 | } 28 | 29 | pub fn get_size(&self) -> u64 { 30 | HEADER_SIZE + HEADER_EXT_SIZE + 4 31 | } 32 | } 33 | 34 | impl Mp4Box for MfhdBox { 35 | fn box_type(&self) -> BoxType { 36 | self.get_type() 37 | } 38 | 39 | fn box_size(&self) -> u64 { 40 | self.get_size() 41 | } 42 | 43 | fn to_json(&self) -> Result { 44 | Ok(serde_json::to_string(&self).unwrap()) 45 | } 46 | 47 | fn summary(&self) -> Result { 48 | let s = format!("sequence_number={}", self.sequence_number); 49 | Ok(s) 50 | } 51 | } 52 | 53 | impl ReadBox<&mut R> for MfhdBox { 54 | fn read_box(reader: &mut R, size: u64) -> Result { 55 | let start = box_start(reader)?; 56 | 57 | let (version, flags) = read_box_header_ext(reader)?; 58 | let sequence_number = reader.read_u32::()?; 59 | 60 | skip_bytes_to(reader, start + size)?; 61 | 62 | Ok(MfhdBox { 63 | version, 64 | flags, 65 | sequence_number, 66 | }) 67 | } 68 | } 69 | 70 | impl WriteBox<&mut W> for MfhdBox { 71 | fn write_box(&self, writer: &mut W) -> Result { 72 | let size = self.box_size(); 73 | BoxHeader::new(self.box_type(), size).write(writer)?; 74 | 75 | write_box_header_ext(writer, self.version, self.flags)?; 76 | writer.write_u32::(self.sequence_number)?; 77 | 78 | Ok(size) 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | use super::*; 85 | use crate::mp4box::BoxHeader; 86 | use std::io::Cursor; 87 | 88 | #[test] 89 | fn test_mfhd() { 90 | let src_box = MfhdBox { 91 | version: 0, 92 | flags: 0, 93 | sequence_number: 1, 94 | }; 95 | let mut buf = Vec::new(); 96 | src_box.write_box(&mut buf).unwrap(); 97 | assert_eq!(buf.len(), src_box.box_size() as usize); 98 | 99 | let mut reader = Cursor::new(&buf); 100 | let header = BoxHeader::read(&mut reader).unwrap(); 101 | assert_eq!(header.name, BoxType::MfhdBox); 102 | assert_eq!(src_box.box_size(), header.size); 103 | 104 | let dst_box = MfhdBox::read_box(&mut reader, header.size).unwrap(); 105 | assert_eq!(src_box, dst_box); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/mp4box/minf.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::io::{Read, Seek, Write}; 3 | 4 | use crate::mp4box::*; 5 | use crate::mp4box::{dinf::DinfBox, smhd::SmhdBox, stbl::StblBox, vmhd::VmhdBox}; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 8 | pub struct MinfBox { 9 | #[serde(skip_serializing_if = "Option::is_none")] 10 | pub vmhd: Option, 11 | 12 | #[serde(skip_serializing_if = "Option::is_none")] 13 | pub smhd: Option, 14 | 15 | pub dinf: DinfBox, 16 | pub stbl: StblBox, 17 | } 18 | 19 | impl MinfBox { 20 | pub fn get_type(&self) -> BoxType { 21 | BoxType::MinfBox 22 | } 23 | 24 | pub fn get_size(&self) -> u64 { 25 | let mut size = HEADER_SIZE; 26 | if let Some(ref vmhd) = self.vmhd { 27 | size += vmhd.box_size(); 28 | } 29 | if let Some(ref smhd) = self.smhd { 30 | size += smhd.box_size(); 31 | } 32 | size += self.dinf.box_size(); 33 | size += self.stbl.box_size(); 34 | size 35 | } 36 | } 37 | 38 | impl Mp4Box for MinfBox { 39 | fn box_type(&self) -> BoxType { 40 | self.get_type() 41 | } 42 | 43 | fn box_size(&self) -> u64 { 44 | self.get_size() 45 | } 46 | 47 | fn to_json(&self) -> Result { 48 | Ok(serde_json::to_string(&self).unwrap()) 49 | } 50 | 51 | fn summary(&self) -> Result { 52 | let s = String::new(); 53 | Ok(s) 54 | } 55 | } 56 | 57 | impl ReadBox<&mut R> for MinfBox { 58 | fn read_box(reader: &mut R, size: u64) -> Result { 59 | let start = box_start(reader)?; 60 | 61 | let mut vmhd = None; 62 | let mut smhd = None; 63 | let mut dinf = None; 64 | let mut stbl = None; 65 | 66 | let mut current = reader.stream_position()?; 67 | let end = start + size; 68 | while current < end { 69 | // Get box header. 70 | let header = BoxHeader::read(reader)?; 71 | let BoxHeader { name, size: s } = header; 72 | if s > size { 73 | return Err(Error::InvalidData( 74 | "minf box contains a box with a larger size than it", 75 | )); 76 | } 77 | 78 | match name { 79 | BoxType::VmhdBox => { 80 | vmhd = Some(VmhdBox::read_box(reader, s)?); 81 | } 82 | BoxType::SmhdBox => { 83 | smhd = Some(SmhdBox::read_box(reader, s)?); 84 | } 85 | BoxType::DinfBox => { 86 | dinf = Some(DinfBox::read_box(reader, s)?); 87 | } 88 | BoxType::StblBox => { 89 | stbl = Some(StblBox::read_box(reader, s)?); 90 | } 91 | _ => { 92 | // XXX warn!() 93 | skip_box(reader, s)?; 94 | } 95 | } 96 | 97 | current = reader.stream_position()?; 98 | } 99 | 100 | if dinf.is_none() { 101 | return Err(Error::BoxNotFound(BoxType::DinfBox)); 102 | } 103 | if stbl.is_none() { 104 | return Err(Error::BoxNotFound(BoxType::StblBox)); 105 | } 106 | 107 | skip_bytes_to(reader, start + size)?; 108 | 109 | Ok(MinfBox { 110 | vmhd, 111 | smhd, 112 | dinf: dinf.unwrap(), 113 | stbl: stbl.unwrap(), 114 | }) 115 | } 116 | } 117 | 118 | impl WriteBox<&mut W> for MinfBox { 119 | fn write_box(&self, writer: &mut W) -> Result { 120 | let size = self.box_size(); 121 | BoxHeader::new(self.box_type(), size).write(writer)?; 122 | 123 | if let Some(ref vmhd) = self.vmhd { 124 | vmhd.write_box(writer)?; 125 | } 126 | if let Some(ref smhd) = self.smhd { 127 | smhd.write_box(writer)?; 128 | } 129 | self.dinf.write_box(writer)?; 130 | self.stbl.write_box(writer)?; 131 | 132 | Ok(size) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/mp4box/moof.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::io::{Read, Seek, Write}; 3 | 4 | use crate::mp4box::*; 5 | use crate::mp4box::{mfhd::MfhdBox, traf::TrafBox}; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 8 | pub struct MoofBox { 9 | pub mfhd: MfhdBox, 10 | 11 | #[serde(rename = "traf")] 12 | pub trafs: Vec, 13 | } 14 | 15 | impl MoofBox { 16 | pub fn get_type(&self) -> BoxType { 17 | BoxType::MoofBox 18 | } 19 | 20 | pub fn get_size(&self) -> u64 { 21 | let mut size = HEADER_SIZE + self.mfhd.box_size(); 22 | for traf in self.trafs.iter() { 23 | size += traf.box_size(); 24 | } 25 | size 26 | } 27 | } 28 | 29 | impl Mp4Box for MoofBox { 30 | fn box_type(&self) -> BoxType { 31 | self.get_type() 32 | } 33 | 34 | fn box_size(&self) -> u64 { 35 | self.get_size() 36 | } 37 | 38 | fn to_json(&self) -> Result { 39 | Ok(serde_json::to_string(&self).unwrap()) 40 | } 41 | 42 | fn summary(&self) -> Result { 43 | let s = format!("trafs={}", self.trafs.len()); 44 | Ok(s) 45 | } 46 | } 47 | 48 | impl ReadBox<&mut R> for MoofBox { 49 | fn read_box(reader: &mut R, size: u64) -> Result { 50 | let start = box_start(reader)?; 51 | 52 | let mut mfhd = None; 53 | let mut trafs = Vec::new(); 54 | 55 | let mut current = reader.stream_position()?; 56 | let end = start + size; 57 | while current < end { 58 | // Get box header. 59 | let header = BoxHeader::read(reader)?; 60 | let BoxHeader { name, size: s } = header; 61 | if s > size { 62 | return Err(Error::InvalidData( 63 | "moof box contains a box with a larger size than it", 64 | )); 65 | } 66 | 67 | match name { 68 | BoxType::MfhdBox => { 69 | mfhd = Some(MfhdBox::read_box(reader, s)?); 70 | } 71 | BoxType::TrafBox => { 72 | let traf = TrafBox::read_box(reader, s)?; 73 | trafs.push(traf); 74 | } 75 | _ => { 76 | // XXX warn!() 77 | skip_box(reader, s)?; 78 | } 79 | } 80 | current = reader.stream_position()?; 81 | } 82 | 83 | if mfhd.is_none() { 84 | return Err(Error::BoxNotFound(BoxType::MfhdBox)); 85 | } 86 | 87 | skip_bytes_to(reader, start + size)?; 88 | 89 | Ok(MoofBox { 90 | mfhd: mfhd.unwrap(), 91 | trafs, 92 | }) 93 | } 94 | } 95 | 96 | impl WriteBox<&mut W> for MoofBox { 97 | fn write_box(&self, writer: &mut W) -> Result { 98 | let size = self.box_size(); 99 | BoxHeader::new(self.box_type(), size).write(writer)?; 100 | 101 | self.mfhd.write_box(writer)?; 102 | for traf in self.trafs.iter() { 103 | traf.write_box(writer)?; 104 | } 105 | Ok(0) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/mp4box/moov.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::io::{Read, Seek, Write}; 3 | 4 | use crate::meta::MetaBox; 5 | use crate::mp4box::*; 6 | use crate::mp4box::{mvex::MvexBox, mvhd::MvhdBox, trak::TrakBox, udta::UdtaBox}; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 9 | pub struct MoovBox { 10 | pub mvhd: MvhdBox, 11 | 12 | #[serde(skip_serializing_if = "Option::is_none")] 13 | pub meta: Option, 14 | 15 | #[serde(skip_serializing_if = "Option::is_none")] 16 | pub mvex: Option, 17 | 18 | #[serde(rename = "trak")] 19 | pub traks: Vec, 20 | 21 | #[serde(skip_serializing_if = "Option::is_none")] 22 | pub udta: Option, 23 | } 24 | 25 | impl MoovBox { 26 | pub fn get_type(&self) -> BoxType { 27 | BoxType::MoovBox 28 | } 29 | 30 | pub fn get_size(&self) -> u64 { 31 | let mut size = HEADER_SIZE + self.mvhd.box_size(); 32 | for trak in self.traks.iter() { 33 | size += trak.box_size(); 34 | } 35 | if let Some(meta) = &self.meta { 36 | size += meta.box_size(); 37 | } 38 | if let Some(udta) = &self.udta { 39 | size += udta.box_size(); 40 | } 41 | size 42 | } 43 | } 44 | 45 | impl Mp4Box for MoovBox { 46 | fn box_type(&self) -> BoxType { 47 | self.get_type() 48 | } 49 | 50 | fn box_size(&self) -> u64 { 51 | self.get_size() 52 | } 53 | 54 | fn to_json(&self) -> Result { 55 | Ok(serde_json::to_string(&self).unwrap()) 56 | } 57 | 58 | fn summary(&self) -> Result { 59 | let s = format!("traks={}", self.traks.len()); 60 | Ok(s) 61 | } 62 | } 63 | 64 | impl ReadBox<&mut R> for MoovBox { 65 | fn read_box(reader: &mut R, size: u64) -> Result { 66 | let start = box_start(reader)?; 67 | 68 | let mut mvhd = None; 69 | let mut meta = None; 70 | let mut udta = None; 71 | let mut mvex = None; 72 | let mut traks = Vec::new(); 73 | 74 | let mut current = reader.stream_position()?; 75 | let end = start + size; 76 | while current < end { 77 | // Get box header. 78 | let header = BoxHeader::read(reader)?; 79 | let BoxHeader { name, size: s } = header; 80 | if s > size { 81 | return Err(Error::InvalidData( 82 | "moov box contains a box with a larger size than it", 83 | )); 84 | } 85 | 86 | match name { 87 | BoxType::MvhdBox => { 88 | mvhd = Some(MvhdBox::read_box(reader, s)?); 89 | } 90 | BoxType::MetaBox => { 91 | meta = Some(MetaBox::read_box(reader, s)?); 92 | } 93 | BoxType::MvexBox => { 94 | mvex = Some(MvexBox::read_box(reader, s)?); 95 | } 96 | BoxType::TrakBox => { 97 | let trak = TrakBox::read_box(reader, s)?; 98 | traks.push(trak); 99 | } 100 | BoxType::UdtaBox => { 101 | udta = Some(UdtaBox::read_box(reader, s)?); 102 | } 103 | _ => { 104 | // XXX warn!() 105 | skip_box(reader, s)?; 106 | } 107 | } 108 | 109 | current = reader.stream_position()?; 110 | } 111 | 112 | if mvhd.is_none() { 113 | return Err(Error::BoxNotFound(BoxType::MvhdBox)); 114 | } 115 | 116 | skip_bytes_to(reader, start + size)?; 117 | 118 | Ok(MoovBox { 119 | mvhd: mvhd.unwrap(), 120 | meta, 121 | udta, 122 | mvex, 123 | traks, 124 | }) 125 | } 126 | } 127 | 128 | impl WriteBox<&mut W> for MoovBox { 129 | fn write_box(&self, writer: &mut W) -> Result { 130 | let size = self.box_size(); 131 | BoxHeader::new(self.box_type(), size).write(writer)?; 132 | 133 | self.mvhd.write_box(writer)?; 134 | for trak in self.traks.iter() { 135 | trak.write_box(writer)?; 136 | } 137 | if let Some(meta) = &self.meta { 138 | meta.write_box(writer)?; 139 | } 140 | if let Some(udta) = &self.udta { 141 | udta.write_box(writer)?; 142 | } 143 | Ok(0) 144 | } 145 | } 146 | 147 | #[cfg(test)] 148 | mod tests { 149 | use super::*; 150 | use crate::mp4box::BoxHeader; 151 | use std::io::Cursor; 152 | 153 | #[test] 154 | fn test_moov() { 155 | let src_box = MoovBox { 156 | mvhd: MvhdBox::default(), 157 | mvex: None, // XXX mvex is not written currently 158 | traks: vec![], 159 | meta: Some(MetaBox::default()), 160 | udta: Some(UdtaBox::default()), 161 | }; 162 | 163 | let mut buf = Vec::new(); 164 | src_box.write_box(&mut buf).unwrap(); 165 | assert_eq!(buf.len(), src_box.box_size() as usize); 166 | 167 | let mut reader = Cursor::new(&buf); 168 | let header = BoxHeader::read(&mut reader).unwrap(); 169 | assert_eq!(header.name, BoxType::MoovBox); 170 | assert_eq!(header.size, src_box.box_size()); 171 | 172 | let dst_box = MoovBox::read_box(&mut reader, header.size).unwrap(); 173 | assert_eq!(dst_box, src_box); 174 | } 175 | 176 | #[test] 177 | fn test_moov_empty() { 178 | let src_box = MoovBox::default(); 179 | 180 | let mut buf = Vec::new(); 181 | src_box.write_box(&mut buf).unwrap(); 182 | assert_eq!(buf.len(), src_box.box_size() as usize); 183 | 184 | let mut reader = Cursor::new(&buf); 185 | let header = BoxHeader::read(&mut reader).unwrap(); 186 | assert_eq!(header.name, BoxType::MoovBox); 187 | assert_eq!(header.size, src_box.box_size()); 188 | 189 | let dst_box = MoovBox::read_box(&mut reader, header.size).unwrap(); 190 | assert_eq!(dst_box, src_box); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/mp4box/mvex.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::io::{Read, Seek, Write}; 3 | 4 | use crate::mp4box::*; 5 | use crate::mp4box::{mehd::MehdBox, trex::TrexBox}; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 8 | pub struct MvexBox { 9 | pub mehd: Option, 10 | pub trex: TrexBox, 11 | } 12 | 13 | impl MvexBox { 14 | pub fn get_type(&self) -> BoxType { 15 | BoxType::MdiaBox 16 | } 17 | 18 | pub fn get_size(&self) -> u64 { 19 | HEADER_SIZE + self.mehd.as_ref().map(|x| x.box_size()).unwrap_or(0) + self.trex.box_size() 20 | } 21 | } 22 | 23 | impl Mp4Box for MvexBox { 24 | fn box_type(&self) -> BoxType { 25 | self.get_type() 26 | } 27 | 28 | fn box_size(&self) -> u64 { 29 | self.get_size() 30 | } 31 | 32 | fn to_json(&self) -> Result { 33 | Ok(serde_json::to_string(&self).unwrap()) 34 | } 35 | 36 | fn summary(&self) -> Result { 37 | let s = String::new(); 38 | Ok(s) 39 | } 40 | } 41 | 42 | impl ReadBox<&mut R> for MvexBox { 43 | fn read_box(reader: &mut R, size: u64) -> Result { 44 | let start = box_start(reader)?; 45 | 46 | let mut mehd = None; 47 | let mut trex = None; 48 | 49 | let mut current = reader.stream_position()?; 50 | let end = start + size; 51 | while current < end { 52 | // Get box header. 53 | let header = BoxHeader::read(reader)?; 54 | let BoxHeader { name, size: s } = header; 55 | if s > size { 56 | return Err(Error::InvalidData( 57 | "mvex box contains a box with a larger size than it", 58 | )); 59 | } 60 | 61 | match name { 62 | BoxType::MehdBox => { 63 | mehd = Some(MehdBox::read_box(reader, s)?); 64 | } 65 | BoxType::TrexBox => { 66 | trex = Some(TrexBox::read_box(reader, s)?); 67 | } 68 | _ => { 69 | // XXX warn!() 70 | skip_box(reader, s)?; 71 | } 72 | } 73 | 74 | current = reader.stream_position()?; 75 | } 76 | 77 | if trex.is_none() { 78 | return Err(Error::BoxNotFound(BoxType::TrexBox)); 79 | } 80 | 81 | skip_bytes_to(reader, start + size)?; 82 | 83 | Ok(MvexBox { 84 | mehd, 85 | trex: trex.unwrap(), 86 | }) 87 | } 88 | } 89 | 90 | impl WriteBox<&mut W> for MvexBox { 91 | fn write_box(&self, writer: &mut W) -> Result { 92 | let size = self.box_size(); 93 | BoxHeader::new(self.box_type(), size).write(writer)?; 94 | 95 | if let Some(mehd) = &self.mehd { 96 | mehd.write_box(writer)?; 97 | } 98 | self.trex.write_box(writer)?; 99 | 100 | Ok(size) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/mp4box/smhd.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | 5 | use crate::mp4box::*; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 8 | pub struct SmhdBox { 9 | pub version: u8, 10 | pub flags: u32, 11 | 12 | #[serde(with = "value_i16")] 13 | pub balance: FixedPointI8, 14 | } 15 | 16 | impl SmhdBox { 17 | pub fn get_type(&self) -> BoxType { 18 | BoxType::SmhdBox 19 | } 20 | 21 | pub fn get_size(&self) -> u64 { 22 | HEADER_SIZE + HEADER_EXT_SIZE + 4 23 | } 24 | } 25 | 26 | impl Default for SmhdBox { 27 | fn default() -> Self { 28 | SmhdBox { 29 | version: 0, 30 | flags: 0, 31 | balance: FixedPointI8::new_raw(0), 32 | } 33 | } 34 | } 35 | 36 | impl Mp4Box for SmhdBox { 37 | fn box_type(&self) -> BoxType { 38 | self.get_type() 39 | } 40 | 41 | fn box_size(&self) -> u64 { 42 | self.get_size() 43 | } 44 | 45 | fn to_json(&self) -> Result { 46 | Ok(serde_json::to_string(&self).unwrap()) 47 | } 48 | 49 | fn summary(&self) -> Result { 50 | let s = format!("balance={}", self.balance.value()); 51 | Ok(s) 52 | } 53 | } 54 | 55 | impl ReadBox<&mut R> for SmhdBox { 56 | fn read_box(reader: &mut R, size: u64) -> Result { 57 | let start = box_start(reader)?; 58 | 59 | let (version, flags) = read_box_header_ext(reader)?; 60 | 61 | let balance = FixedPointI8::new_raw(reader.read_i16::()?); 62 | 63 | skip_bytes_to(reader, start + size)?; 64 | 65 | Ok(SmhdBox { 66 | version, 67 | flags, 68 | balance, 69 | }) 70 | } 71 | } 72 | 73 | impl WriteBox<&mut W> for SmhdBox { 74 | fn write_box(&self, writer: &mut W) -> Result { 75 | let size = self.box_size(); 76 | BoxHeader::new(self.box_type(), size).write(writer)?; 77 | 78 | write_box_header_ext(writer, self.version, self.flags)?; 79 | 80 | writer.write_i16::(self.balance.raw_value())?; 81 | writer.write_u16::(0)?; // reserved 82 | 83 | Ok(size) 84 | } 85 | } 86 | 87 | #[cfg(test)] 88 | mod tests { 89 | use super::*; 90 | use crate::mp4box::BoxHeader; 91 | use std::io::Cursor; 92 | 93 | #[test] 94 | fn test_smhd() { 95 | let src_box = SmhdBox { 96 | version: 0, 97 | flags: 0, 98 | balance: FixedPointI8::new_raw(-1), 99 | }; 100 | let mut buf = Vec::new(); 101 | src_box.write_box(&mut buf).unwrap(); 102 | assert_eq!(buf.len(), src_box.box_size() as usize); 103 | 104 | let mut reader = Cursor::new(&buf); 105 | let header = BoxHeader::read(&mut reader).unwrap(); 106 | assert_eq!(header.name, BoxType::SmhdBox); 107 | assert_eq!(src_box.box_size(), header.size); 108 | 109 | let dst_box = SmhdBox::read_box(&mut reader, header.size).unwrap(); 110 | assert_eq!(src_box, dst_box); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/mp4box/stbl.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::io::{Read, Seek, Write}; 3 | 4 | use crate::mp4box::*; 5 | use crate::mp4box::{ 6 | co64::Co64Box, ctts::CttsBox, stco::StcoBox, stsc::StscBox, stsd::StsdBox, stss::StssBox, 7 | stsz::StszBox, stts::SttsBox, 8 | }; 9 | 10 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 11 | pub struct StblBox { 12 | pub stsd: StsdBox, 13 | pub stts: SttsBox, 14 | 15 | #[serde(skip_serializing_if = "Option::is_none")] 16 | pub ctts: Option, 17 | 18 | #[serde(skip_serializing_if = "Option::is_none")] 19 | pub stss: Option, 20 | pub stsc: StscBox, 21 | pub stsz: StszBox, 22 | 23 | #[serde(skip_serializing_if = "Option::is_none")] 24 | pub stco: Option, 25 | 26 | #[serde(skip_serializing_if = "Option::is_none")] 27 | pub co64: Option, 28 | } 29 | 30 | impl StblBox { 31 | pub fn get_type(&self) -> BoxType { 32 | BoxType::StblBox 33 | } 34 | 35 | pub fn get_size(&self) -> u64 { 36 | let mut size = HEADER_SIZE; 37 | size += self.stsd.box_size(); 38 | size += self.stts.box_size(); 39 | if let Some(ref ctts) = self.ctts { 40 | size += ctts.box_size(); 41 | } 42 | if let Some(ref stss) = self.stss { 43 | size += stss.box_size(); 44 | } 45 | size += self.stsc.box_size(); 46 | size += self.stsz.box_size(); 47 | if let Some(ref stco) = self.stco { 48 | size += stco.box_size(); 49 | } 50 | if let Some(ref co64) = self.co64 { 51 | size += co64.box_size(); 52 | } 53 | size 54 | } 55 | } 56 | 57 | impl Mp4Box for StblBox { 58 | fn box_type(&self) -> BoxType { 59 | self.get_type() 60 | } 61 | 62 | fn box_size(&self) -> u64 { 63 | self.get_size() 64 | } 65 | 66 | fn to_json(&self) -> Result { 67 | Ok(serde_json::to_string(&self).unwrap()) 68 | } 69 | 70 | fn summary(&self) -> Result { 71 | let s = String::new(); 72 | Ok(s) 73 | } 74 | } 75 | 76 | impl ReadBox<&mut R> for StblBox { 77 | fn read_box(reader: &mut R, size: u64) -> Result { 78 | let start = box_start(reader)?; 79 | 80 | let mut stsd = None; 81 | let mut stts = None; 82 | let mut ctts = None; 83 | let mut stss = None; 84 | let mut stsc = None; 85 | let mut stsz = None; 86 | let mut stco = None; 87 | let mut co64 = None; 88 | 89 | let mut current = reader.stream_position()?; 90 | let end = start + size; 91 | while current < end { 92 | // Get box header. 93 | let header = BoxHeader::read(reader)?; 94 | let BoxHeader { name, size: s } = header; 95 | if s > size { 96 | return Err(Error::InvalidData( 97 | "stbl box contains a box with a larger size than it", 98 | )); 99 | } 100 | 101 | match name { 102 | BoxType::StsdBox => { 103 | stsd = Some(StsdBox::read_box(reader, s)?); 104 | } 105 | BoxType::SttsBox => { 106 | stts = Some(SttsBox::read_box(reader, s)?); 107 | } 108 | BoxType::CttsBox => { 109 | ctts = Some(CttsBox::read_box(reader, s)?); 110 | } 111 | BoxType::StssBox => { 112 | stss = Some(StssBox::read_box(reader, s)?); 113 | } 114 | BoxType::StscBox => { 115 | stsc = Some(StscBox::read_box(reader, s)?); 116 | } 117 | BoxType::StszBox => { 118 | stsz = Some(StszBox::read_box(reader, s)?); 119 | } 120 | BoxType::StcoBox => { 121 | stco = Some(StcoBox::read_box(reader, s)?); 122 | } 123 | BoxType::Co64Box => { 124 | co64 = Some(Co64Box::read_box(reader, s)?); 125 | } 126 | _ => { 127 | // XXX warn!() 128 | skip_box(reader, s)?; 129 | } 130 | } 131 | current = reader.stream_position()?; 132 | } 133 | 134 | if stsd.is_none() { 135 | return Err(Error::BoxNotFound(BoxType::StsdBox)); 136 | } 137 | if stts.is_none() { 138 | return Err(Error::BoxNotFound(BoxType::SttsBox)); 139 | } 140 | if stsc.is_none() { 141 | return Err(Error::BoxNotFound(BoxType::StscBox)); 142 | } 143 | if stsz.is_none() { 144 | return Err(Error::BoxNotFound(BoxType::StszBox)); 145 | } 146 | if stco.is_none() && co64.is_none() { 147 | return Err(Error::Box2NotFound(BoxType::StcoBox, BoxType::Co64Box)); 148 | } 149 | 150 | skip_bytes_to(reader, start + size)?; 151 | 152 | Ok(StblBox { 153 | stsd: stsd.unwrap(), 154 | stts: stts.unwrap(), 155 | ctts, 156 | stss, 157 | stsc: stsc.unwrap(), 158 | stsz: stsz.unwrap(), 159 | stco, 160 | co64, 161 | }) 162 | } 163 | } 164 | 165 | impl WriteBox<&mut W> for StblBox { 166 | fn write_box(&self, writer: &mut W) -> Result { 167 | let size = self.box_size(); 168 | BoxHeader::new(self.box_type(), size).write(writer)?; 169 | 170 | self.stsd.write_box(writer)?; 171 | self.stts.write_box(writer)?; 172 | if let Some(ref ctts) = self.ctts { 173 | ctts.write_box(writer)?; 174 | } 175 | if let Some(ref stss) = self.stss { 176 | stss.write_box(writer)?; 177 | } 178 | self.stsc.write_box(writer)?; 179 | self.stsz.write_box(writer)?; 180 | if let Some(ref stco) = self.stco { 181 | stco.write_box(writer)?; 182 | } 183 | if let Some(ref co64) = self.co64 { 184 | co64.write_box(writer)?; 185 | } 186 | 187 | Ok(size) 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/mp4box/stco.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | use std::mem::size_of; 5 | 6 | use crate::mp4box::*; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 9 | pub struct StcoBox { 10 | pub version: u8, 11 | pub flags: u32, 12 | 13 | #[serde(skip_serializing)] 14 | pub entries: Vec, 15 | } 16 | 17 | impl StcoBox { 18 | pub fn get_type(&self) -> BoxType { 19 | BoxType::StcoBox 20 | } 21 | 22 | pub fn get_size(&self) -> u64 { 23 | HEADER_SIZE + HEADER_EXT_SIZE + 4 + (4 * self.entries.len() as u64) 24 | } 25 | } 26 | 27 | impl Mp4Box for StcoBox { 28 | fn box_type(&self) -> BoxType { 29 | self.get_type() 30 | } 31 | 32 | fn box_size(&self) -> u64 { 33 | self.get_size() 34 | } 35 | 36 | fn to_json(&self) -> Result { 37 | Ok(serde_json::to_string(&self).unwrap()) 38 | } 39 | 40 | fn summary(&self) -> Result { 41 | let s = format!("entries={}", self.entries.len()); 42 | Ok(s) 43 | } 44 | } 45 | 46 | impl ReadBox<&mut R> for StcoBox { 47 | fn read_box(reader: &mut R, size: u64) -> Result { 48 | let start = box_start(reader)?; 49 | 50 | let (version, flags) = read_box_header_ext(reader)?; 51 | 52 | let header_size = HEADER_SIZE + HEADER_EXT_SIZE; 53 | let other_size = size_of::(); // entry_count 54 | let entry_size = size_of::(); // chunk_offset 55 | let entry_count = reader.read_u32::()?; 56 | if u64::from(entry_count) 57 | > size 58 | .saturating_sub(header_size) 59 | .saturating_sub(other_size as u64) 60 | / entry_size as u64 61 | { 62 | return Err(Error::InvalidData( 63 | "stco entry_count indicates more entries than could fit in the box", 64 | )); 65 | } 66 | let mut entries = Vec::with_capacity(entry_count as usize); 67 | for _i in 0..entry_count { 68 | let chunk_offset = reader.read_u32::()?; 69 | entries.push(chunk_offset); 70 | } 71 | 72 | skip_bytes_to(reader, start + size)?; 73 | 74 | Ok(StcoBox { 75 | version, 76 | flags, 77 | entries, 78 | }) 79 | } 80 | } 81 | 82 | impl WriteBox<&mut W> for StcoBox { 83 | fn write_box(&self, writer: &mut W) -> Result { 84 | let size = self.box_size(); 85 | BoxHeader::new(self.box_type(), size).write(writer)?; 86 | 87 | write_box_header_ext(writer, self.version, self.flags)?; 88 | 89 | writer.write_u32::(self.entries.len() as u32)?; 90 | for chunk_offset in self.entries.iter() { 91 | writer.write_u32::(*chunk_offset)?; 92 | } 93 | 94 | Ok(size) 95 | } 96 | } 97 | 98 | impl std::convert::TryFrom<&co64::Co64Box> for StcoBox { 99 | type Error = std::num::TryFromIntError; 100 | 101 | fn try_from(co64: &co64::Co64Box) -> std::result::Result { 102 | let entries = co64 103 | .entries 104 | .iter() 105 | .copied() 106 | .map(u32::try_from) 107 | .collect::, _>>()?; 108 | Ok(Self { 109 | version: 0, 110 | flags: 0, 111 | entries, 112 | }) 113 | } 114 | } 115 | 116 | #[cfg(test)] 117 | mod tests { 118 | use super::*; 119 | use crate::mp4box::BoxHeader; 120 | use std::io::Cursor; 121 | 122 | #[test] 123 | fn test_stco() { 124 | let src_box = StcoBox { 125 | version: 0, 126 | flags: 0, 127 | entries: vec![267, 1970, 2535, 2803, 11843, 22223, 33584], 128 | }; 129 | let mut buf = Vec::new(); 130 | src_box.write_box(&mut buf).unwrap(); 131 | assert_eq!(buf.len(), src_box.box_size() as usize); 132 | 133 | let mut reader = Cursor::new(&buf); 134 | let header = BoxHeader::read(&mut reader).unwrap(); 135 | assert_eq!(header.name, BoxType::StcoBox); 136 | assert_eq!(src_box.box_size(), header.size); 137 | 138 | let dst_box = StcoBox::read_box(&mut reader, header.size).unwrap(); 139 | assert_eq!(src_box, dst_box); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/mp4box/stsc.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | use std::mem::size_of; 5 | 6 | use crate::mp4box::*; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 9 | pub struct StscBox { 10 | pub version: u8, 11 | pub flags: u32, 12 | 13 | #[serde(skip_serializing)] 14 | pub entries: Vec, 15 | } 16 | 17 | impl StscBox { 18 | pub fn get_type(&self) -> BoxType { 19 | BoxType::StscBox 20 | } 21 | 22 | pub fn get_size(&self) -> u64 { 23 | HEADER_SIZE + HEADER_EXT_SIZE + 4 + (12 * self.entries.len() as u64) 24 | } 25 | } 26 | 27 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 28 | pub struct StscEntry { 29 | pub first_chunk: u32, 30 | pub samples_per_chunk: u32, 31 | pub sample_description_index: u32, 32 | pub first_sample: u32, 33 | } 34 | 35 | impl Mp4Box for StscBox { 36 | fn box_type(&self) -> BoxType { 37 | self.get_type() 38 | } 39 | 40 | fn box_size(&self) -> u64 { 41 | self.get_size() 42 | } 43 | 44 | fn to_json(&self) -> Result { 45 | Ok(serde_json::to_string(&self).unwrap()) 46 | } 47 | 48 | fn summary(&self) -> Result { 49 | let s = format!("entries={}", self.entries.len()); 50 | Ok(s) 51 | } 52 | } 53 | 54 | impl ReadBox<&mut R> for StscBox { 55 | fn read_box(reader: &mut R, size: u64) -> Result { 56 | let start = box_start(reader)?; 57 | 58 | let (version, flags) = read_box_header_ext(reader)?; 59 | 60 | let header_size = HEADER_SIZE + HEADER_EXT_SIZE; 61 | let other_size = size_of::(); // entry_count 62 | let entry_size = size_of::() + size_of::() + size_of::(); // first_chunk + samples_per_chunk + sample_description_index 63 | let entry_count = reader.read_u32::()?; 64 | if u64::from(entry_count) 65 | > size 66 | .saturating_sub(header_size) 67 | .saturating_sub(other_size as u64) 68 | / entry_size as u64 69 | { 70 | return Err(Error::InvalidData( 71 | "stsc entry_count indicates more entries than could fit in the box", 72 | )); 73 | } 74 | let mut entries = Vec::with_capacity(entry_count as usize); 75 | for _ in 0..entry_count { 76 | let entry = StscEntry { 77 | first_chunk: reader.read_u32::()?, 78 | samples_per_chunk: reader.read_u32::()?, 79 | sample_description_index: reader.read_u32::()?, 80 | first_sample: 0, 81 | }; 82 | entries.push(entry); 83 | } 84 | 85 | let mut sample_id = 1; 86 | for i in 0..entry_count { 87 | let (first_chunk, samples_per_chunk) = { 88 | let entry = entries.get_mut(i as usize).unwrap(); 89 | entry.first_sample = sample_id; 90 | (entry.first_chunk, entry.samples_per_chunk) 91 | }; 92 | if i < entry_count - 1 { 93 | let next_entry = entries.get(i as usize + 1).unwrap(); 94 | sample_id = next_entry 95 | .first_chunk 96 | .checked_sub(first_chunk) 97 | .and_then(|n| n.checked_mul(samples_per_chunk)) 98 | .and_then(|n| n.checked_add(sample_id)) 99 | .ok_or(Error::InvalidData( 100 | "attempt to calculate stsc sample_id with overflow", 101 | ))?; 102 | } 103 | } 104 | 105 | skip_bytes_to(reader, start + size)?; 106 | 107 | Ok(StscBox { 108 | version, 109 | flags, 110 | entries, 111 | }) 112 | } 113 | } 114 | 115 | impl WriteBox<&mut W> for StscBox { 116 | fn write_box(&self, writer: &mut W) -> Result { 117 | let size = self.box_size(); 118 | BoxHeader::new(self.box_type(), size).write(writer)?; 119 | 120 | write_box_header_ext(writer, self.version, self.flags)?; 121 | 122 | writer.write_u32::(self.entries.len() as u32)?; 123 | for entry in self.entries.iter() { 124 | writer.write_u32::(entry.first_chunk)?; 125 | writer.write_u32::(entry.samples_per_chunk)?; 126 | writer.write_u32::(entry.sample_description_index)?; 127 | } 128 | 129 | Ok(size) 130 | } 131 | } 132 | 133 | #[cfg(test)] 134 | mod tests { 135 | use super::*; 136 | use crate::mp4box::BoxHeader; 137 | use std::io::Cursor; 138 | 139 | #[test] 140 | fn test_stsc() { 141 | let src_box = StscBox { 142 | version: 0, 143 | flags: 0, 144 | entries: vec![ 145 | StscEntry { 146 | first_chunk: 1, 147 | samples_per_chunk: 1, 148 | sample_description_index: 1, 149 | first_sample: 1, 150 | }, 151 | StscEntry { 152 | first_chunk: 19026, 153 | samples_per_chunk: 14, 154 | sample_description_index: 1, 155 | first_sample: 19026, 156 | }, 157 | ], 158 | }; 159 | let mut buf = Vec::new(); 160 | src_box.write_box(&mut buf).unwrap(); 161 | assert_eq!(buf.len(), src_box.box_size() as usize); 162 | 163 | let mut reader = Cursor::new(&buf); 164 | let header = BoxHeader::read(&mut reader).unwrap(); 165 | assert_eq!(header.name, BoxType::StscBox); 166 | assert_eq!(src_box.box_size(), header.size); 167 | 168 | let dst_box = StscBox::read_box(&mut reader, header.size).unwrap(); 169 | assert_eq!(src_box, dst_box); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/mp4box/stsd.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | 5 | use crate::mp4box::vp09::Vp09Box; 6 | use crate::mp4box::*; 7 | use crate::mp4box::{avc1::Avc1Box, hev1::Hev1Box, mp4a::Mp4aBox, tx3g::Tx3gBox}; 8 | 9 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 10 | pub struct StsdBox { 11 | pub version: u8, 12 | pub flags: u32, 13 | 14 | #[serde(skip_serializing_if = "Option::is_none")] 15 | pub avc1: Option, 16 | 17 | #[serde(skip_serializing_if = "Option::is_none")] 18 | pub hev1: Option, 19 | 20 | #[serde(skip_serializing_if = "Option::is_none")] 21 | pub vp09: Option, 22 | 23 | #[serde(skip_serializing_if = "Option::is_none")] 24 | pub mp4a: Option, 25 | 26 | #[serde(skip_serializing_if = "Option::is_none")] 27 | pub tx3g: Option, 28 | } 29 | 30 | impl StsdBox { 31 | pub fn get_type(&self) -> BoxType { 32 | BoxType::StsdBox 33 | } 34 | 35 | pub fn get_size(&self) -> u64 { 36 | let mut size = HEADER_SIZE + HEADER_EXT_SIZE + 4; 37 | if let Some(ref avc1) = self.avc1 { 38 | size += avc1.box_size(); 39 | } else if let Some(ref hev1) = self.hev1 { 40 | size += hev1.box_size(); 41 | } else if let Some(ref vp09) = self.vp09 { 42 | size += vp09.box_size(); 43 | } else if let Some(ref mp4a) = self.mp4a { 44 | size += mp4a.box_size(); 45 | } else if let Some(ref tx3g) = self.tx3g { 46 | size += tx3g.box_size(); 47 | } 48 | size 49 | } 50 | } 51 | 52 | impl Mp4Box for StsdBox { 53 | fn box_type(&self) -> BoxType { 54 | self.get_type() 55 | } 56 | 57 | fn box_size(&self) -> u64 { 58 | self.get_size() 59 | } 60 | 61 | fn to_json(&self) -> Result { 62 | Ok(serde_json::to_string(&self).unwrap()) 63 | } 64 | 65 | fn summary(&self) -> Result { 66 | let s = String::new(); 67 | Ok(s) 68 | } 69 | } 70 | 71 | impl ReadBox<&mut R> for StsdBox { 72 | fn read_box(reader: &mut R, size: u64) -> Result { 73 | let start = box_start(reader)?; 74 | 75 | let (version, flags) = read_box_header_ext(reader)?; 76 | 77 | reader.read_u32::()?; // XXX entry_count 78 | 79 | let mut avc1 = None; 80 | let mut hev1 = None; 81 | let mut vp09 = None; 82 | let mut mp4a = None; 83 | let mut tx3g = None; 84 | 85 | // Get box header. 86 | let header = BoxHeader::read(reader)?; 87 | let BoxHeader { name, size: s } = header; 88 | if s > size { 89 | return Err(Error::InvalidData( 90 | "stsd box contains a box with a larger size than it", 91 | )); 92 | } 93 | 94 | match name { 95 | BoxType::Avc1Box => { 96 | avc1 = Some(Avc1Box::read_box(reader, s)?); 97 | } 98 | BoxType::Hev1Box => { 99 | hev1 = Some(Hev1Box::read_box(reader, s)?); 100 | } 101 | BoxType::Vp09Box => { 102 | vp09 = Some(Vp09Box::read_box(reader, s)?); 103 | } 104 | BoxType::Mp4aBox => { 105 | mp4a = Some(Mp4aBox::read_box(reader, s)?); 106 | } 107 | BoxType::Tx3gBox => { 108 | tx3g = Some(Tx3gBox::read_box(reader, s)?); 109 | } 110 | _ => {} 111 | } 112 | 113 | skip_bytes_to(reader, start + size)?; 114 | 115 | Ok(StsdBox { 116 | version, 117 | flags, 118 | avc1, 119 | hev1, 120 | vp09, 121 | mp4a, 122 | tx3g, 123 | }) 124 | } 125 | } 126 | 127 | impl WriteBox<&mut W> for StsdBox { 128 | fn write_box(&self, writer: &mut W) -> Result { 129 | let size = self.box_size(); 130 | BoxHeader::new(self.box_type(), size).write(writer)?; 131 | 132 | write_box_header_ext(writer, self.version, self.flags)?; 133 | 134 | writer.write_u32::(1)?; // entry_count 135 | 136 | if let Some(ref avc1) = self.avc1 { 137 | avc1.write_box(writer)?; 138 | } else if let Some(ref hev1) = self.hev1 { 139 | hev1.write_box(writer)?; 140 | } else if let Some(ref vp09) = self.vp09 { 141 | vp09.write_box(writer)?; 142 | } else if let Some(ref mp4a) = self.mp4a { 143 | mp4a.write_box(writer)?; 144 | } else if let Some(ref tx3g) = self.tx3g { 145 | tx3g.write_box(writer)?; 146 | } 147 | 148 | Ok(size) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/mp4box/stss.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | use std::mem::size_of; 5 | 6 | use crate::mp4box::*; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 9 | pub struct StssBox { 10 | pub version: u8, 11 | pub flags: u32, 12 | 13 | #[serde(skip_serializing)] 14 | pub entries: Vec, 15 | } 16 | 17 | impl StssBox { 18 | pub fn get_type(&self) -> BoxType { 19 | BoxType::StssBox 20 | } 21 | 22 | pub fn get_size(&self) -> u64 { 23 | HEADER_SIZE + HEADER_EXT_SIZE + 4 + (4 * self.entries.len() as u64) 24 | } 25 | } 26 | 27 | impl Mp4Box for StssBox { 28 | fn box_type(&self) -> BoxType { 29 | self.get_type() 30 | } 31 | 32 | fn box_size(&self) -> u64 { 33 | self.get_size() 34 | } 35 | 36 | fn to_json(&self) -> Result { 37 | Ok(serde_json::to_string(&self).unwrap()) 38 | } 39 | 40 | fn summary(&self) -> Result { 41 | let s = format!("entries={}", self.entries.len()); 42 | Ok(s) 43 | } 44 | } 45 | 46 | impl ReadBox<&mut R> for StssBox { 47 | fn read_box(reader: &mut R, size: u64) -> Result { 48 | let start = box_start(reader)?; 49 | 50 | let (version, flags) = read_box_header_ext(reader)?; 51 | 52 | let header_size = HEADER_SIZE + HEADER_EXT_SIZE; 53 | let other_size = size_of::(); // entry_count 54 | let entry_size = size_of::(); // sample_number 55 | let entry_count = reader.read_u32::()?; 56 | if u64::from(entry_count) 57 | > size 58 | .saturating_sub(header_size) 59 | .saturating_sub(other_size as u64) 60 | / entry_size as u64 61 | { 62 | return Err(Error::InvalidData( 63 | "stss entry_count indicates more entries than could fit in the box", 64 | )); 65 | } 66 | let mut entries = Vec::with_capacity(entry_count as usize); 67 | for _i in 0..entry_count { 68 | let sample_number = reader.read_u32::()?; 69 | entries.push(sample_number); 70 | } 71 | 72 | skip_bytes_to(reader, start + size)?; 73 | 74 | Ok(StssBox { 75 | version, 76 | flags, 77 | entries, 78 | }) 79 | } 80 | } 81 | 82 | impl WriteBox<&mut W> for StssBox { 83 | fn write_box(&self, writer: &mut W) -> Result { 84 | let size = self.box_size(); 85 | BoxHeader::new(self.box_type(), size).write(writer)?; 86 | 87 | write_box_header_ext(writer, self.version, self.flags)?; 88 | 89 | writer.write_u32::(self.entries.len() as u32)?; 90 | for sample_number in self.entries.iter() { 91 | writer.write_u32::(*sample_number)?; 92 | } 93 | 94 | Ok(size) 95 | } 96 | } 97 | 98 | #[cfg(test)] 99 | mod tests { 100 | use super::*; 101 | use crate::mp4box::BoxHeader; 102 | use std::io::Cursor; 103 | 104 | #[test] 105 | fn test_stss() { 106 | let src_box = StssBox { 107 | version: 0, 108 | flags: 0, 109 | entries: vec![1, 61, 121, 181, 241, 301, 361, 421, 481], 110 | }; 111 | let mut buf = Vec::new(); 112 | src_box.write_box(&mut buf).unwrap(); 113 | assert_eq!(buf.len(), src_box.box_size() as usize); 114 | 115 | let mut reader = Cursor::new(&buf); 116 | let header = BoxHeader::read(&mut reader).unwrap(); 117 | assert_eq!(header.name, BoxType::StssBox); 118 | assert_eq!(src_box.box_size(), header.size); 119 | 120 | let dst_box = StssBox::read_box(&mut reader, header.size).unwrap(); 121 | assert_eq!(src_box, dst_box); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/mp4box/stsz.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | use std::mem::size_of; 5 | 6 | use crate::mp4box::*; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 9 | pub struct StszBox { 10 | pub version: u8, 11 | pub flags: u32, 12 | pub sample_size: u32, 13 | pub sample_count: u32, 14 | 15 | #[serde(skip_serializing)] 16 | pub sample_sizes: Vec, 17 | } 18 | 19 | impl StszBox { 20 | pub fn get_type(&self) -> BoxType { 21 | BoxType::StszBox 22 | } 23 | 24 | pub fn get_size(&self) -> u64 { 25 | HEADER_SIZE + HEADER_EXT_SIZE + 8 + (4 * self.sample_sizes.len() as u64) 26 | } 27 | } 28 | 29 | impl Mp4Box for StszBox { 30 | fn box_type(&self) -> BoxType { 31 | self.get_type() 32 | } 33 | 34 | fn box_size(&self) -> u64 { 35 | self.get_size() 36 | } 37 | 38 | fn to_json(&self) -> Result { 39 | Ok(serde_json::to_string(&self).unwrap()) 40 | } 41 | 42 | fn summary(&self) -> Result { 43 | let s = format!( 44 | "sample_size={} sample_count={} sample_sizes={}", 45 | self.sample_size, 46 | self.sample_count, 47 | self.sample_sizes.len() 48 | ); 49 | Ok(s) 50 | } 51 | } 52 | 53 | impl ReadBox<&mut R> for StszBox { 54 | fn read_box(reader: &mut R, size: u64) -> Result { 55 | let start = box_start(reader)?; 56 | 57 | let (version, flags) = read_box_header_ext(reader)?; 58 | 59 | let header_size = HEADER_SIZE + HEADER_EXT_SIZE; 60 | let other_size = size_of::() + size_of::(); // sample_size + sample_count 61 | let sample_size = reader.read_u32::()?; 62 | let stsz_item_size = if sample_size == 0 { 63 | size_of::() // entry_size 64 | } else { 65 | 0 66 | }; 67 | let sample_count = reader.read_u32::()?; 68 | let mut sample_sizes = Vec::new(); 69 | if sample_size == 0 { 70 | if u64::from(sample_count) 71 | > size 72 | .saturating_sub(header_size) 73 | .saturating_sub(other_size as u64) 74 | / stsz_item_size as u64 75 | { 76 | return Err(Error::InvalidData( 77 | "stsz sample_count indicates more values than could fit in the box", 78 | )); 79 | } 80 | sample_sizes.reserve(sample_count as usize); 81 | for _ in 0..sample_count { 82 | let sample_number = reader.read_u32::()?; 83 | sample_sizes.push(sample_number); 84 | } 85 | } 86 | 87 | skip_bytes_to(reader, start + size)?; 88 | 89 | Ok(StszBox { 90 | version, 91 | flags, 92 | sample_size, 93 | sample_count, 94 | sample_sizes, 95 | }) 96 | } 97 | } 98 | 99 | impl WriteBox<&mut W> for StszBox { 100 | fn write_box(&self, writer: &mut W) -> Result { 101 | let size = self.box_size(); 102 | BoxHeader::new(self.box_type(), size).write(writer)?; 103 | 104 | write_box_header_ext(writer, self.version, self.flags)?; 105 | 106 | writer.write_u32::(self.sample_size)?; 107 | writer.write_u32::(self.sample_count)?; 108 | if self.sample_size == 0 { 109 | if self.sample_count != self.sample_sizes.len() as u32 { 110 | return Err(Error::InvalidData("sample count out of sync")); 111 | } 112 | for sample_number in self.sample_sizes.iter() { 113 | writer.write_u32::(*sample_number)?; 114 | } 115 | } 116 | 117 | Ok(size) 118 | } 119 | } 120 | 121 | #[cfg(test)] 122 | mod tests { 123 | use super::*; 124 | use crate::mp4box::BoxHeader; 125 | use std::io::Cursor; 126 | 127 | #[test] 128 | fn test_stsz_same_size() { 129 | let src_box = StszBox { 130 | version: 0, 131 | flags: 0, 132 | sample_size: 1165, 133 | sample_count: 12, 134 | sample_sizes: vec![], 135 | }; 136 | let mut buf = Vec::new(); 137 | src_box.write_box(&mut buf).unwrap(); 138 | assert_eq!(buf.len(), src_box.box_size() as usize); 139 | 140 | let mut reader = Cursor::new(&buf); 141 | let header = BoxHeader::read(&mut reader).unwrap(); 142 | assert_eq!(header.name, BoxType::StszBox); 143 | assert_eq!(src_box.box_size(), header.size); 144 | 145 | let dst_box = StszBox::read_box(&mut reader, header.size).unwrap(); 146 | assert_eq!(src_box, dst_box); 147 | } 148 | 149 | #[test] 150 | fn test_stsz_many_sizes() { 151 | let src_box = StszBox { 152 | version: 0, 153 | flags: 0, 154 | sample_size: 0, 155 | sample_count: 9, 156 | sample_sizes: vec![1165, 11, 11, 8545, 10126, 10866, 9643, 9351, 7730], 157 | }; 158 | let mut buf = Vec::new(); 159 | src_box.write_box(&mut buf).unwrap(); 160 | assert_eq!(buf.len(), src_box.box_size() as usize); 161 | 162 | let mut reader = Cursor::new(&buf); 163 | let header = BoxHeader::read(&mut reader).unwrap(); 164 | assert_eq!(header.name, BoxType::StszBox); 165 | assert_eq!(src_box.box_size(), header.size); 166 | 167 | let dst_box = StszBox::read_box(&mut reader, header.size).unwrap(); 168 | assert_eq!(src_box, dst_box); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/mp4box/stts.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | use std::mem::size_of; 5 | 6 | use crate::mp4box::*; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 9 | pub struct SttsBox { 10 | pub version: u8, 11 | pub flags: u32, 12 | 13 | #[serde(skip_serializing)] 14 | pub entries: Vec, 15 | } 16 | 17 | impl SttsBox { 18 | pub fn get_type(&self) -> BoxType { 19 | BoxType::SttsBox 20 | } 21 | 22 | pub fn get_size(&self) -> u64 { 23 | HEADER_SIZE + HEADER_EXT_SIZE + 4 + (8 * self.entries.len() as u64) 24 | } 25 | } 26 | 27 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 28 | pub struct SttsEntry { 29 | pub sample_count: u32, 30 | pub sample_delta: u32, 31 | } 32 | 33 | impl Mp4Box for SttsBox { 34 | fn box_type(&self) -> BoxType { 35 | self.get_type() 36 | } 37 | 38 | fn box_size(&self) -> u64 { 39 | self.get_size() 40 | } 41 | 42 | fn to_json(&self) -> Result { 43 | Ok(serde_json::to_string(&self).unwrap()) 44 | } 45 | 46 | fn summary(&self) -> Result { 47 | let s = format!("entries={}", self.entries.len()); 48 | Ok(s) 49 | } 50 | } 51 | 52 | impl ReadBox<&mut R> for SttsBox { 53 | fn read_box(reader: &mut R, size: u64) -> Result { 54 | let start = box_start(reader)?; 55 | 56 | let (version, flags) = read_box_header_ext(reader)?; 57 | 58 | let header_size = HEADER_SIZE + HEADER_EXT_SIZE; 59 | let other_size = size_of::(); // entry_count 60 | let entry_size = size_of::() + size_of::(); // sample_count + sample_delta 61 | let entry_count = reader.read_u32::()?; 62 | if u64::from(entry_count) 63 | > size 64 | .saturating_sub(header_size) 65 | .saturating_sub(other_size as u64) 66 | / entry_size as u64 67 | { 68 | return Err(Error::InvalidData( 69 | "stts entry_count indicates more entries than could fit in the box", 70 | )); 71 | } 72 | let mut entries = Vec::with_capacity(entry_count as usize); 73 | for _i in 0..entry_count { 74 | let entry = SttsEntry { 75 | sample_count: reader.read_u32::()?, 76 | sample_delta: reader.read_u32::()?, 77 | }; 78 | entries.push(entry); 79 | } 80 | 81 | skip_bytes_to(reader, start + size)?; 82 | 83 | Ok(SttsBox { 84 | version, 85 | flags, 86 | entries, 87 | }) 88 | } 89 | } 90 | 91 | impl WriteBox<&mut W> for SttsBox { 92 | fn write_box(&self, writer: &mut W) -> Result { 93 | let size = self.box_size(); 94 | BoxHeader::new(self.box_type(), size).write(writer)?; 95 | 96 | write_box_header_ext(writer, self.version, self.flags)?; 97 | 98 | writer.write_u32::(self.entries.len() as u32)?; 99 | for entry in self.entries.iter() { 100 | writer.write_u32::(entry.sample_count)?; 101 | writer.write_u32::(entry.sample_delta)?; 102 | } 103 | 104 | Ok(size) 105 | } 106 | } 107 | 108 | #[cfg(test)] 109 | mod tests { 110 | use super::*; 111 | use crate::mp4box::BoxHeader; 112 | use std::io::Cursor; 113 | 114 | #[test] 115 | fn test_stts() { 116 | let src_box = SttsBox { 117 | version: 0, 118 | flags: 0, 119 | entries: vec![ 120 | SttsEntry { 121 | sample_count: 29726, 122 | sample_delta: 1024, 123 | }, 124 | SttsEntry { 125 | sample_count: 1, 126 | sample_delta: 512, 127 | }, 128 | ], 129 | }; 130 | let mut buf = Vec::new(); 131 | src_box.write_box(&mut buf).unwrap(); 132 | assert_eq!(buf.len(), src_box.box_size() as usize); 133 | 134 | let mut reader = Cursor::new(&buf); 135 | let header = BoxHeader::read(&mut reader).unwrap(); 136 | assert_eq!(header.name, BoxType::SttsBox); 137 | assert_eq!(src_box.box_size(), header.size); 138 | 139 | let dst_box = SttsBox::read_box(&mut reader, header.size).unwrap(); 140 | assert_eq!(src_box, dst_box); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/mp4box/tfdt.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | 5 | use crate::mp4box::*; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 8 | pub struct TfdtBox { 9 | pub version: u8, 10 | pub flags: u32, 11 | pub base_media_decode_time: u64, 12 | } 13 | 14 | impl TfdtBox { 15 | pub fn get_type(&self) -> BoxType { 16 | BoxType::TfdtBox 17 | } 18 | 19 | pub fn get_size(&self) -> u64 { 20 | let mut sum = HEADER_SIZE + HEADER_EXT_SIZE; 21 | if self.version == 1 { 22 | sum += 8; 23 | } else { 24 | sum += 4; 25 | } 26 | sum 27 | } 28 | } 29 | 30 | impl Mp4Box for TfdtBox { 31 | fn box_type(&self) -> BoxType { 32 | self.get_type() 33 | } 34 | 35 | fn box_size(&self) -> u64 { 36 | self.get_size() 37 | } 38 | 39 | fn to_json(&self) -> Result { 40 | Ok(serde_json::to_string(&self).unwrap()) 41 | } 42 | 43 | fn summary(&self) -> Result { 44 | let s = format!("base_media_decode_time={}", self.base_media_decode_time); 45 | Ok(s) 46 | } 47 | } 48 | 49 | impl ReadBox<&mut R> for TfdtBox { 50 | fn read_box(reader: &mut R, size: u64) -> Result { 51 | let start = box_start(reader)?; 52 | 53 | let (version, flags) = read_box_header_ext(reader)?; 54 | 55 | let base_media_decode_time = if version == 1 { 56 | reader.read_u64::()? 57 | } else if version == 0 { 58 | reader.read_u32::()? as u64 59 | } else { 60 | return Err(Error::InvalidData("version must be 0 or 1")); 61 | }; 62 | 63 | skip_bytes_to(reader, start + size)?; 64 | 65 | Ok(TfdtBox { 66 | version, 67 | flags, 68 | base_media_decode_time, 69 | }) 70 | } 71 | } 72 | 73 | impl WriteBox<&mut W> for TfdtBox { 74 | fn write_box(&self, writer: &mut W) -> Result { 75 | let size = self.box_size(); 76 | BoxHeader::new(self.box_type(), size).write(writer)?; 77 | 78 | write_box_header_ext(writer, self.version, self.flags)?; 79 | 80 | if self.version == 1 { 81 | writer.write_u64::(self.base_media_decode_time)?; 82 | } else if self.version == 0 { 83 | writer.write_u32::(self.base_media_decode_time as u32)?; 84 | } else { 85 | return Err(Error::InvalidData("version must be 0 or 1")); 86 | } 87 | 88 | Ok(size) 89 | } 90 | } 91 | 92 | #[cfg(test)] 93 | mod tests { 94 | use super::*; 95 | use crate::mp4box::BoxHeader; 96 | use std::io::Cursor; 97 | 98 | #[test] 99 | fn test_tfdt32() { 100 | let src_box = TfdtBox { 101 | version: 0, 102 | flags: 0, 103 | base_media_decode_time: 0, 104 | }; 105 | let mut buf = Vec::new(); 106 | src_box.write_box(&mut buf).unwrap(); 107 | assert_eq!(buf.len(), src_box.box_size() as usize); 108 | 109 | let mut reader = Cursor::new(&buf); 110 | let header = BoxHeader::read(&mut reader).unwrap(); 111 | assert_eq!(header.name, BoxType::TfdtBox); 112 | assert_eq!(src_box.box_size(), header.size); 113 | 114 | let dst_box = TfdtBox::read_box(&mut reader, header.size).unwrap(); 115 | assert_eq!(src_box, dst_box); 116 | } 117 | 118 | #[test] 119 | fn test_tfdt64() { 120 | let src_box = TfdtBox { 121 | version: 1, 122 | flags: 0, 123 | base_media_decode_time: 0, 124 | }; 125 | let mut buf = Vec::new(); 126 | src_box.write_box(&mut buf).unwrap(); 127 | assert_eq!(buf.len(), src_box.box_size() as usize); 128 | 129 | let mut reader = Cursor::new(&buf); 130 | let header = BoxHeader::read(&mut reader).unwrap(); 131 | assert_eq!(header.name, BoxType::TfdtBox); 132 | assert_eq!(src_box.box_size(), header.size); 133 | 134 | let dst_box = TfdtBox::read_box(&mut reader, header.size).unwrap(); 135 | assert_eq!(src_box, dst_box); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/mp4box/tfhd.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | 5 | use crate::mp4box::*; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)] 8 | pub struct TfhdBox { 9 | pub version: u8, 10 | pub flags: u32, 11 | pub track_id: u32, 12 | pub base_data_offset: Option, 13 | pub sample_description_index: Option, 14 | pub default_sample_duration: Option, 15 | pub default_sample_size: Option, 16 | pub default_sample_flags: Option, 17 | } 18 | 19 | impl TfhdBox { 20 | pub const FLAG_BASE_DATA_OFFSET: u32 = 0x01; 21 | pub const FLAG_SAMPLE_DESCRIPTION_INDEX: u32 = 0x02; 22 | pub const FLAG_DEFAULT_SAMPLE_DURATION: u32 = 0x08; 23 | pub const FLAG_DEFAULT_SAMPLE_SIZE: u32 = 0x10; 24 | pub const FLAG_DEFAULT_SAMPLE_FLAGS: u32 = 0x20; 25 | pub const FLAG_DURATION_IS_EMPTY: u32 = 0x10000; 26 | pub const FLAG_DEFAULT_BASE_IS_MOOF: u32 = 0x20000; 27 | 28 | pub fn get_type(&self) -> BoxType { 29 | BoxType::TfhdBox 30 | } 31 | 32 | pub fn get_size(&self) -> u64 { 33 | let mut sum = HEADER_SIZE + HEADER_EXT_SIZE + 4; 34 | if TfhdBox::FLAG_BASE_DATA_OFFSET & self.flags > 0 { 35 | sum += 8; 36 | } 37 | if TfhdBox::FLAG_SAMPLE_DESCRIPTION_INDEX & self.flags > 0 { 38 | sum += 4; 39 | } 40 | if TfhdBox::FLAG_DEFAULT_SAMPLE_DURATION & self.flags > 0 { 41 | sum += 4; 42 | } 43 | if TfhdBox::FLAG_DEFAULT_SAMPLE_SIZE & self.flags > 0 { 44 | sum += 4; 45 | } 46 | if TfhdBox::FLAG_DEFAULT_SAMPLE_FLAGS & self.flags > 0 { 47 | sum += 4; 48 | } 49 | sum 50 | } 51 | } 52 | 53 | impl Mp4Box for TfhdBox { 54 | fn box_type(&self) -> BoxType { 55 | self.get_type() 56 | } 57 | 58 | fn box_size(&self) -> u64 { 59 | self.get_size() 60 | } 61 | 62 | fn to_json(&self) -> Result { 63 | Ok(serde_json::to_string(&self).unwrap()) 64 | } 65 | 66 | fn summary(&self) -> Result { 67 | let s = format!("track_id={}", self.track_id); 68 | Ok(s) 69 | } 70 | } 71 | 72 | impl ReadBox<&mut R> for TfhdBox { 73 | fn read_box(reader: &mut R, size: u64) -> Result { 74 | let start = box_start(reader)?; 75 | 76 | let (version, flags) = read_box_header_ext(reader)?; 77 | let track_id = reader.read_u32::()?; 78 | let base_data_offset = if TfhdBox::FLAG_BASE_DATA_OFFSET & flags > 0 { 79 | Some(reader.read_u64::()?) 80 | } else { 81 | None 82 | }; 83 | let sample_description_index = if TfhdBox::FLAG_SAMPLE_DESCRIPTION_INDEX & flags > 0 { 84 | Some(reader.read_u32::()?) 85 | } else { 86 | None 87 | }; 88 | let default_sample_duration = if TfhdBox::FLAG_DEFAULT_SAMPLE_DURATION & flags > 0 { 89 | Some(reader.read_u32::()?) 90 | } else { 91 | None 92 | }; 93 | let default_sample_size = if TfhdBox::FLAG_DEFAULT_SAMPLE_SIZE & flags > 0 { 94 | Some(reader.read_u32::()?) 95 | } else { 96 | None 97 | }; 98 | let default_sample_flags = if TfhdBox::FLAG_DEFAULT_SAMPLE_FLAGS & flags > 0 { 99 | Some(reader.read_u32::()?) 100 | } else { 101 | None 102 | }; 103 | 104 | skip_bytes_to(reader, start + size)?; 105 | 106 | Ok(TfhdBox { 107 | version, 108 | flags, 109 | track_id, 110 | base_data_offset, 111 | sample_description_index, 112 | default_sample_duration, 113 | default_sample_size, 114 | default_sample_flags, 115 | }) 116 | } 117 | } 118 | 119 | impl WriteBox<&mut W> for TfhdBox { 120 | fn write_box(&self, writer: &mut W) -> Result { 121 | let size = self.box_size(); 122 | BoxHeader::new(self.box_type(), size).write(writer)?; 123 | 124 | write_box_header_ext(writer, self.version, self.flags)?; 125 | writer.write_u32::(self.track_id)?; 126 | if let Some(base_data_offset) = self.base_data_offset { 127 | writer.write_u64::(base_data_offset)?; 128 | } 129 | if let Some(sample_description_index) = self.sample_description_index { 130 | writer.write_u32::(sample_description_index)?; 131 | } 132 | if let Some(default_sample_duration) = self.default_sample_duration { 133 | writer.write_u32::(default_sample_duration)?; 134 | } 135 | if let Some(default_sample_size) = self.default_sample_size { 136 | writer.write_u32::(default_sample_size)?; 137 | } 138 | if let Some(default_sample_flags) = self.default_sample_flags { 139 | writer.write_u32::(default_sample_flags)?; 140 | } 141 | 142 | Ok(size) 143 | } 144 | } 145 | 146 | #[cfg(test)] 147 | mod tests { 148 | use super::*; 149 | use crate::mp4box::BoxHeader; 150 | use std::io::Cursor; 151 | 152 | #[test] 153 | fn test_tfhd() { 154 | let src_box = TfhdBox { 155 | version: 0, 156 | flags: 0, 157 | track_id: 1, 158 | base_data_offset: None, 159 | sample_description_index: None, 160 | default_sample_duration: None, 161 | default_sample_size: None, 162 | default_sample_flags: None, 163 | }; 164 | let mut buf = Vec::new(); 165 | src_box.write_box(&mut buf).unwrap(); 166 | assert_eq!(buf.len(), src_box.box_size() as usize); 167 | 168 | let mut reader = Cursor::new(&buf); 169 | let header = BoxHeader::read(&mut reader).unwrap(); 170 | assert_eq!(header.name, BoxType::TfhdBox); 171 | assert_eq!(src_box.box_size(), header.size); 172 | 173 | let dst_box = TfhdBox::read_box(&mut reader, header.size).unwrap(); 174 | assert_eq!(src_box, dst_box); 175 | } 176 | 177 | #[test] 178 | fn test_tfhd_with_flags() { 179 | let src_box = TfhdBox { 180 | version: 0, 181 | flags: TfhdBox::FLAG_SAMPLE_DESCRIPTION_INDEX 182 | | TfhdBox::FLAG_DEFAULT_SAMPLE_DURATION 183 | | TfhdBox::FLAG_DEFAULT_SAMPLE_FLAGS, 184 | track_id: 1, 185 | base_data_offset: None, 186 | sample_description_index: Some(1), 187 | default_sample_duration: Some(512), 188 | default_sample_size: None, 189 | default_sample_flags: Some(0x1010000), 190 | }; 191 | let mut buf = Vec::new(); 192 | src_box.write_box(&mut buf).unwrap(); 193 | assert_eq!(buf.len(), src_box.box_size() as usize); 194 | 195 | let mut reader = Cursor::new(&buf); 196 | let header = BoxHeader::read(&mut reader).unwrap(); 197 | assert_eq!(header.name, BoxType::TfhdBox); 198 | assert_eq!(src_box.box_size(), header.size); 199 | 200 | let dst_box = TfhdBox::read_box(&mut reader, header.size).unwrap(); 201 | assert_eq!(src_box, dst_box); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/mp4box/traf.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::io::{Read, Seek, Write}; 3 | 4 | use crate::mp4box::*; 5 | use crate::mp4box::{tfdt::TfdtBox, tfhd::TfhdBox, trun::TrunBox}; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 8 | pub struct TrafBox { 9 | pub tfhd: TfhdBox, 10 | pub tfdt: Option, 11 | pub trun: Option, 12 | } 13 | 14 | impl TrafBox { 15 | pub fn get_type(&self) -> BoxType { 16 | BoxType::TrafBox 17 | } 18 | 19 | pub fn get_size(&self) -> u64 { 20 | let mut size = HEADER_SIZE; 21 | size += self.tfhd.box_size(); 22 | if let Some(ref tfdt) = self.tfdt { 23 | size += tfdt.box_size(); 24 | } 25 | if let Some(ref trun) = self.trun { 26 | size += trun.box_size(); 27 | } 28 | size 29 | } 30 | } 31 | 32 | impl Mp4Box for TrafBox { 33 | fn box_type(&self) -> BoxType { 34 | self.get_type() 35 | } 36 | 37 | fn box_size(&self) -> u64 { 38 | self.get_size() 39 | } 40 | 41 | fn to_json(&self) -> Result { 42 | Ok(serde_json::to_string(&self).unwrap()) 43 | } 44 | 45 | fn summary(&self) -> Result { 46 | let s = String::new(); 47 | Ok(s) 48 | } 49 | } 50 | 51 | impl ReadBox<&mut R> for TrafBox { 52 | fn read_box(reader: &mut R, size: u64) -> Result { 53 | let start = box_start(reader)?; 54 | 55 | let mut tfhd = None; 56 | let mut tfdt = None; 57 | let mut trun = None; 58 | 59 | let mut current = reader.stream_position()?; 60 | let end = start + size; 61 | while current < end { 62 | // Get box header. 63 | let header = BoxHeader::read(reader)?; 64 | let BoxHeader { name, size: s } = header; 65 | if s > size { 66 | return Err(Error::InvalidData( 67 | "traf box contains a box with a larger size than it", 68 | )); 69 | } 70 | 71 | match name { 72 | BoxType::TfhdBox => { 73 | tfhd = Some(TfhdBox::read_box(reader, s)?); 74 | } 75 | BoxType::TfdtBox => { 76 | tfdt = Some(TfdtBox::read_box(reader, s)?); 77 | } 78 | BoxType::TrunBox => { 79 | trun = Some(TrunBox::read_box(reader, s)?); 80 | } 81 | _ => { 82 | // XXX warn!() 83 | skip_box(reader, s)?; 84 | } 85 | } 86 | 87 | current = reader.stream_position()?; 88 | } 89 | 90 | if tfhd.is_none() { 91 | return Err(Error::BoxNotFound(BoxType::TfhdBox)); 92 | } 93 | 94 | skip_bytes_to(reader, start + size)?; 95 | 96 | Ok(TrafBox { 97 | tfhd: tfhd.unwrap(), 98 | tfdt, 99 | trun, 100 | }) 101 | } 102 | } 103 | 104 | impl WriteBox<&mut W> for TrafBox { 105 | fn write_box(&self, writer: &mut W) -> Result { 106 | let size = self.box_size(); 107 | BoxHeader::new(self.box_type(), size).write(writer)?; 108 | 109 | self.tfhd.write_box(writer)?; 110 | if let Some(ref tfdt) = self.tfdt { 111 | tfdt.write_box(writer)?; 112 | } 113 | if let Some(ref trun) = self.trun { 114 | trun.write_box(writer)?; 115 | } 116 | 117 | Ok(size) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/mp4box/trak.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::io::{Read, Seek, Write}; 3 | 4 | use crate::meta::MetaBox; 5 | use crate::mp4box::*; 6 | use crate::mp4box::{edts::EdtsBox, mdia::MdiaBox, tkhd::TkhdBox}; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 9 | pub struct TrakBox { 10 | pub tkhd: TkhdBox, 11 | 12 | #[serde(skip_serializing_if = "Option::is_none")] 13 | pub edts: Option, 14 | 15 | #[serde(skip_serializing_if = "Option::is_none")] 16 | pub meta: Option, 17 | 18 | pub mdia: MdiaBox, 19 | } 20 | 21 | impl TrakBox { 22 | pub fn get_type(&self) -> BoxType { 23 | BoxType::TrakBox 24 | } 25 | 26 | pub fn get_size(&self) -> u64 { 27 | let mut size = HEADER_SIZE; 28 | size += self.tkhd.box_size(); 29 | if let Some(ref edts) = self.edts { 30 | size += edts.box_size(); 31 | } 32 | size += self.mdia.box_size(); 33 | size 34 | } 35 | } 36 | 37 | impl Mp4Box for TrakBox { 38 | fn box_type(&self) -> BoxType { 39 | self.get_type() 40 | } 41 | 42 | fn box_size(&self) -> u64 { 43 | self.get_size() 44 | } 45 | 46 | fn to_json(&self) -> Result { 47 | Ok(serde_json::to_string(&self).unwrap()) 48 | } 49 | 50 | fn summary(&self) -> Result { 51 | let s = String::new(); 52 | Ok(s) 53 | } 54 | } 55 | 56 | impl ReadBox<&mut R> for TrakBox { 57 | fn read_box(reader: &mut R, size: u64) -> Result { 58 | let start = box_start(reader)?; 59 | 60 | let mut tkhd = None; 61 | let mut edts = None; 62 | let mut meta = None; 63 | let mut mdia = None; 64 | 65 | let mut current = reader.stream_position()?; 66 | let end = start + size; 67 | while current < end { 68 | // Get box header. 69 | let header = BoxHeader::read(reader)?; 70 | let BoxHeader { name, size: s } = header; 71 | if s > size { 72 | return Err(Error::InvalidData( 73 | "trak box contains a box with a larger size than it", 74 | )); 75 | } 76 | 77 | match name { 78 | BoxType::TkhdBox => { 79 | tkhd = Some(TkhdBox::read_box(reader, s)?); 80 | } 81 | BoxType::EdtsBox => { 82 | edts = Some(EdtsBox::read_box(reader, s)?); 83 | } 84 | BoxType::MetaBox => { 85 | meta = Some(MetaBox::read_box(reader, s)?); 86 | } 87 | BoxType::MdiaBox => { 88 | mdia = Some(MdiaBox::read_box(reader, s)?); 89 | } 90 | _ => { 91 | // XXX warn!() 92 | skip_box(reader, s)?; 93 | } 94 | } 95 | 96 | current = reader.stream_position()?; 97 | } 98 | 99 | if tkhd.is_none() { 100 | return Err(Error::BoxNotFound(BoxType::TkhdBox)); 101 | } 102 | if mdia.is_none() { 103 | return Err(Error::BoxNotFound(BoxType::MdiaBox)); 104 | } 105 | 106 | skip_bytes_to(reader, start + size)?; 107 | 108 | Ok(TrakBox { 109 | tkhd: tkhd.unwrap(), 110 | edts, 111 | meta, 112 | mdia: mdia.unwrap(), 113 | }) 114 | } 115 | } 116 | 117 | impl WriteBox<&mut W> for TrakBox { 118 | fn write_box(&self, writer: &mut W) -> Result { 119 | let size = self.box_size(); 120 | BoxHeader::new(self.box_type(), size).write(writer)?; 121 | 122 | self.tkhd.write_box(writer)?; 123 | if let Some(ref edts) = self.edts { 124 | edts.write_box(writer)?; 125 | } 126 | self.mdia.write_box(writer)?; 127 | 128 | Ok(size) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/mp4box/trex.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | 5 | use crate::mp4box::*; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 8 | pub struct TrexBox { 9 | pub version: u8, 10 | pub flags: u32, 11 | pub track_id: u32, 12 | pub default_sample_description_index: u32, 13 | pub default_sample_duration: u32, 14 | pub default_sample_size: u32, 15 | pub default_sample_flags: u32, 16 | } 17 | 18 | impl TrexBox { 19 | pub fn get_type(&self) -> BoxType { 20 | BoxType::TrexBox 21 | } 22 | 23 | pub fn get_size(&self) -> u64 { 24 | HEADER_SIZE + HEADER_EXT_SIZE + 20 25 | } 26 | } 27 | 28 | impl Mp4Box for TrexBox { 29 | fn box_type(&self) -> BoxType { 30 | self.get_type() 31 | } 32 | 33 | fn box_size(&self) -> u64 { 34 | self.get_size() 35 | } 36 | 37 | fn to_json(&self) -> Result { 38 | Ok(serde_json::to_string(&self).unwrap()) 39 | } 40 | 41 | fn summary(&self) -> Result { 42 | let s = format!( 43 | "track_id={} default_sample_duration={}", 44 | self.track_id, self.default_sample_duration 45 | ); 46 | Ok(s) 47 | } 48 | } 49 | 50 | impl ReadBox<&mut R> for TrexBox { 51 | fn read_box(reader: &mut R, size: u64) -> Result { 52 | let start = box_start(reader)?; 53 | 54 | let (version, flags) = read_box_header_ext(reader)?; 55 | 56 | let track_id = reader.read_u32::()?; 57 | let default_sample_description_index = reader.read_u32::()?; 58 | let default_sample_duration = reader.read_u32::()?; 59 | let default_sample_size = reader.read_u32::()?; 60 | let default_sample_flags = reader.read_u32::()?; 61 | 62 | skip_bytes_to(reader, start + size)?; 63 | 64 | Ok(TrexBox { 65 | version, 66 | flags, 67 | track_id, 68 | default_sample_description_index, 69 | default_sample_duration, 70 | default_sample_size, 71 | default_sample_flags, 72 | }) 73 | } 74 | } 75 | 76 | impl WriteBox<&mut W> for TrexBox { 77 | fn write_box(&self, writer: &mut W) -> Result { 78 | let size = self.box_size(); 79 | BoxHeader::new(self.box_type(), size).write(writer)?; 80 | 81 | write_box_header_ext(writer, self.version, self.flags)?; 82 | 83 | writer.write_u32::(self.track_id)?; 84 | writer.write_u32::(self.default_sample_description_index)?; 85 | writer.write_u32::(self.default_sample_duration)?; 86 | writer.write_u32::(self.default_sample_size)?; 87 | writer.write_u32::(self.default_sample_flags)?; 88 | 89 | Ok(size) 90 | } 91 | } 92 | 93 | #[cfg(test)] 94 | mod tests { 95 | use super::*; 96 | use crate::mp4box::BoxHeader; 97 | use std::io::Cursor; 98 | 99 | #[test] 100 | fn test_trex() { 101 | let src_box = TrexBox { 102 | version: 0, 103 | flags: 0, 104 | track_id: 1, 105 | default_sample_description_index: 1, 106 | default_sample_duration: 1000, 107 | default_sample_size: 0, 108 | default_sample_flags: 65536, 109 | }; 110 | let mut buf = Vec::new(); 111 | src_box.write_box(&mut buf).unwrap(); 112 | assert_eq!(buf.len(), src_box.box_size() as usize); 113 | 114 | let mut reader = Cursor::new(&buf); 115 | let header = BoxHeader::read(&mut reader).unwrap(); 116 | assert_eq!(header.name, BoxType::TrexBox); 117 | assert_eq!(src_box.box_size(), header.size); 118 | 119 | let dst_box = TrexBox::read_box(&mut reader, header.size).unwrap(); 120 | assert_eq!(src_box, dst_box); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/mp4box/tx3g.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | 5 | use crate::mp4box::*; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Serialize)] 8 | pub struct Tx3gBox { 9 | pub data_reference_index: u16, 10 | pub display_flags: u32, 11 | pub horizontal_justification: i8, 12 | pub vertical_justification: i8, 13 | pub bg_color_rgba: RgbaColor, 14 | pub box_record: [i16; 4], 15 | pub style_record: [u8; 12], 16 | } 17 | 18 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 19 | pub struct RgbaColor { 20 | pub red: u8, 21 | pub green: u8, 22 | pub blue: u8, 23 | pub alpha: u8, 24 | } 25 | 26 | impl Default for Tx3gBox { 27 | fn default() -> Self { 28 | Tx3gBox { 29 | data_reference_index: 0, 30 | display_flags: 0, 31 | horizontal_justification: 1, 32 | vertical_justification: -1, 33 | bg_color_rgba: RgbaColor { 34 | red: 0, 35 | green: 0, 36 | blue: 0, 37 | alpha: 255, 38 | }, 39 | box_record: [0, 0, 0, 0], 40 | style_record: [0, 0, 0, 0, 0, 1, 0, 16, 255, 255, 255, 255], 41 | } 42 | } 43 | } 44 | 45 | impl Tx3gBox { 46 | pub fn get_type(&self) -> BoxType { 47 | BoxType::Tx3gBox 48 | } 49 | 50 | pub fn get_size(&self) -> u64 { 51 | HEADER_SIZE + 6 + 32 52 | } 53 | } 54 | 55 | impl Mp4Box for Tx3gBox { 56 | fn box_type(&self) -> BoxType { 57 | self.get_type() 58 | } 59 | 60 | fn box_size(&self) -> u64 { 61 | self.get_size() 62 | } 63 | 64 | fn to_json(&self) -> Result { 65 | Ok(serde_json::to_string(&self).unwrap()) 66 | } 67 | 68 | fn summary(&self) -> Result { 69 | let s = format!("data_reference_index={} horizontal_justification={} vertical_justification={} rgba={}{}{}{}", 70 | self.data_reference_index, self.horizontal_justification, 71 | self.vertical_justification, self.bg_color_rgba.red, 72 | self.bg_color_rgba.green, self.bg_color_rgba.blue, self.bg_color_rgba.alpha); 73 | Ok(s) 74 | } 75 | } 76 | 77 | impl ReadBox<&mut R> for Tx3gBox { 78 | fn read_box(reader: &mut R, size: u64) -> Result { 79 | let start = box_start(reader)?; 80 | 81 | reader.read_u32::()?; // reserved 82 | reader.read_u16::()?; // reserved 83 | let data_reference_index = reader.read_u16::()?; 84 | 85 | let display_flags = reader.read_u32::()?; 86 | let horizontal_justification = reader.read_i8()?; 87 | let vertical_justification = reader.read_i8()?; 88 | let bg_color_rgba = RgbaColor { 89 | red: reader.read_u8()?, 90 | green: reader.read_u8()?, 91 | blue: reader.read_u8()?, 92 | alpha: reader.read_u8()?, 93 | }; 94 | let box_record: [i16; 4] = [ 95 | reader.read_i16::()?, 96 | reader.read_i16::()?, 97 | reader.read_i16::()?, 98 | reader.read_i16::()?, 99 | ]; 100 | let style_record: [u8; 12] = [ 101 | reader.read_u8()?, 102 | reader.read_u8()?, 103 | reader.read_u8()?, 104 | reader.read_u8()?, 105 | reader.read_u8()?, 106 | reader.read_u8()?, 107 | reader.read_u8()?, 108 | reader.read_u8()?, 109 | reader.read_u8()?, 110 | reader.read_u8()?, 111 | reader.read_u8()?, 112 | reader.read_u8()?, 113 | ]; 114 | 115 | skip_bytes_to(reader, start + size)?; 116 | 117 | Ok(Tx3gBox { 118 | data_reference_index, 119 | display_flags, 120 | horizontal_justification, 121 | vertical_justification, 122 | bg_color_rgba, 123 | box_record, 124 | style_record, 125 | }) 126 | } 127 | } 128 | 129 | impl WriteBox<&mut W> for Tx3gBox { 130 | fn write_box(&self, writer: &mut W) -> Result { 131 | let size = self.box_size(); 132 | BoxHeader::new(self.box_type(), size).write(writer)?; 133 | 134 | writer.write_u32::(0)?; // reserved 135 | writer.write_u16::(0)?; // reserved 136 | writer.write_u16::(self.data_reference_index)?; 137 | writer.write_u32::(self.display_flags)?; 138 | writer.write_i8(self.horizontal_justification)?; 139 | writer.write_i8(self.vertical_justification)?; 140 | writer.write_u8(self.bg_color_rgba.red)?; 141 | writer.write_u8(self.bg_color_rgba.green)?; 142 | writer.write_u8(self.bg_color_rgba.blue)?; 143 | writer.write_u8(self.bg_color_rgba.alpha)?; 144 | for n in 0..4 { 145 | writer.write_i16::(self.box_record[n])?; 146 | } 147 | for n in 0..12 { 148 | writer.write_u8(self.style_record[n])?; 149 | } 150 | 151 | Ok(size) 152 | } 153 | } 154 | 155 | #[cfg(test)] 156 | mod tests { 157 | use super::*; 158 | use crate::mp4box::BoxHeader; 159 | use std::io::Cursor; 160 | 161 | #[test] 162 | fn test_tx3g() { 163 | let src_box = Tx3gBox { 164 | data_reference_index: 1, 165 | display_flags: 0, 166 | horizontal_justification: 1, 167 | vertical_justification: -1, 168 | bg_color_rgba: RgbaColor { 169 | red: 0, 170 | green: 0, 171 | blue: 0, 172 | alpha: 255, 173 | }, 174 | box_record: [0, 0, 0, 0], 175 | style_record: [0, 0, 0, 0, 0, 1, 0, 16, 255, 255, 255, 255], 176 | }; 177 | let mut buf = Vec::new(); 178 | src_box.write_box(&mut buf).unwrap(); 179 | assert_eq!(buf.len(), src_box.box_size() as usize); 180 | 181 | let mut reader = Cursor::new(&buf); 182 | let header = BoxHeader::read(&mut reader).unwrap(); 183 | assert_eq!(header.name, BoxType::Tx3gBox); 184 | assert_eq!(src_box.box_size(), header.size); 185 | 186 | let dst_box = Tx3gBox::read_box(&mut reader, header.size).unwrap(); 187 | assert_eq!(src_box, dst_box); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/mp4box/udta.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Read, Seek}; 2 | 3 | use serde::Serialize; 4 | 5 | use crate::mp4box::meta::MetaBox; 6 | use crate::mp4box::*; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 9 | pub struct UdtaBox { 10 | #[serde(skip_serializing_if = "Option::is_none")] 11 | pub meta: Option, 12 | } 13 | 14 | impl UdtaBox { 15 | pub fn get_type(&self) -> BoxType { 16 | BoxType::UdtaBox 17 | } 18 | 19 | pub fn get_size(&self) -> u64 { 20 | let mut size = HEADER_SIZE; 21 | if let Some(meta) = &self.meta { 22 | size += meta.box_size(); 23 | } 24 | size 25 | } 26 | } 27 | 28 | impl Mp4Box for UdtaBox { 29 | fn box_type(&self) -> BoxType { 30 | self.get_type() 31 | } 32 | 33 | fn box_size(&self) -> u64 { 34 | self.get_size() 35 | } 36 | 37 | fn to_json(&self) -> Result { 38 | Ok(serde_json::to_string(&self).unwrap()) 39 | } 40 | 41 | fn summary(&self) -> Result { 42 | Ok(String::new()) 43 | } 44 | } 45 | 46 | impl ReadBox<&mut R> for UdtaBox { 47 | fn read_box(reader: &mut R, size: u64) -> Result { 48 | let start = box_start(reader)?; 49 | 50 | let mut meta = None; 51 | 52 | let mut current = reader.stream_position()?; 53 | let end = start + size; 54 | while current < end { 55 | // Get box header. 56 | let header = BoxHeader::read(reader)?; 57 | let BoxHeader { name, size: s } = header; 58 | if s > size { 59 | return Err(Error::InvalidData( 60 | "udta box contains a box with a larger size than it", 61 | )); 62 | } 63 | 64 | match name { 65 | BoxType::MetaBox => { 66 | meta = Some(MetaBox::read_box(reader, s)?); 67 | } 68 | _ => { 69 | // XXX warn!() 70 | skip_box(reader, s)?; 71 | } 72 | } 73 | 74 | current = reader.stream_position()?; 75 | } 76 | 77 | skip_bytes_to(reader, start + size)?; 78 | 79 | Ok(UdtaBox { meta }) 80 | } 81 | } 82 | 83 | impl WriteBox<&mut W> for UdtaBox { 84 | fn write_box(&self, writer: &mut W) -> Result { 85 | let size = self.box_size(); 86 | BoxHeader::new(self.box_type(), size).write(writer)?; 87 | 88 | if let Some(meta) = &self.meta { 89 | meta.write_box(writer)?; 90 | } 91 | Ok(size) 92 | } 93 | } 94 | 95 | #[cfg(test)] 96 | mod tests { 97 | use super::*; 98 | use crate::mp4box::BoxHeader; 99 | use std::io::Cursor; 100 | 101 | #[test] 102 | fn test_udta_empty() { 103 | let src_box = UdtaBox { meta: None }; 104 | 105 | let mut buf = Vec::new(); 106 | src_box.write_box(&mut buf).unwrap(); 107 | assert_eq!(buf.len(), src_box.box_size() as usize); 108 | 109 | let mut reader = Cursor::new(&buf); 110 | let header = BoxHeader::read(&mut reader).unwrap(); 111 | assert_eq!(header.name, BoxType::UdtaBox); 112 | assert_eq!(header.size, src_box.box_size()); 113 | 114 | let dst_box = UdtaBox::read_box(&mut reader, header.size).unwrap(); 115 | assert_eq!(dst_box, src_box); 116 | } 117 | 118 | #[test] 119 | fn test_udta() { 120 | let src_box = UdtaBox { 121 | meta: Some(MetaBox::default()), 122 | }; 123 | 124 | let mut buf = Vec::new(); 125 | src_box.write_box(&mut buf).unwrap(); 126 | assert_eq!(buf.len(), src_box.box_size() as usize); 127 | 128 | let mut reader = Cursor::new(&buf); 129 | let header = BoxHeader::read(&mut reader).unwrap(); 130 | assert_eq!(header.name, BoxType::UdtaBox); 131 | assert_eq!(header.size, src_box.box_size()); 132 | 133 | let dst_box = UdtaBox::read_box(&mut reader, header.size).unwrap(); 134 | assert_eq!(dst_box, src_box); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/mp4box/vmhd.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; 2 | use serde::Serialize; 3 | use std::io::{Read, Seek, Write}; 4 | 5 | use crate::mp4box::*; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 8 | pub struct VmhdBox { 9 | pub version: u8, 10 | pub flags: u32, 11 | pub graphics_mode: u16, 12 | pub op_color: RgbColor, 13 | } 14 | 15 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 16 | pub struct RgbColor { 17 | pub red: u16, 18 | pub green: u16, 19 | pub blue: u16, 20 | } 21 | 22 | impl VmhdBox { 23 | pub fn get_type(&self) -> BoxType { 24 | BoxType::VmhdBox 25 | } 26 | 27 | pub fn get_size(&self) -> u64 { 28 | HEADER_SIZE + HEADER_EXT_SIZE + 8 29 | } 30 | } 31 | 32 | impl Mp4Box for VmhdBox { 33 | fn box_type(&self) -> BoxType { 34 | self.get_type() 35 | } 36 | 37 | fn box_size(&self) -> u64 { 38 | self.get_size() 39 | } 40 | 41 | fn to_json(&self) -> Result { 42 | Ok(serde_json::to_string(&self).unwrap()) 43 | } 44 | 45 | fn summary(&self) -> Result { 46 | let s = format!( 47 | "graphics_mode={} op_color={}{}{}", 48 | self.graphics_mode, self.op_color.red, self.op_color.green, self.op_color.blue 49 | ); 50 | Ok(s) 51 | } 52 | } 53 | 54 | impl ReadBox<&mut R> for VmhdBox { 55 | fn read_box(reader: &mut R, size: u64) -> Result { 56 | let start = box_start(reader)?; 57 | 58 | let (version, flags) = read_box_header_ext(reader)?; 59 | 60 | let graphics_mode = reader.read_u16::()?; 61 | let op_color = RgbColor { 62 | red: reader.read_u16::()?, 63 | green: reader.read_u16::()?, 64 | blue: reader.read_u16::()?, 65 | }; 66 | 67 | skip_bytes_to(reader, start + size)?; 68 | 69 | Ok(VmhdBox { 70 | version, 71 | flags, 72 | graphics_mode, 73 | op_color, 74 | }) 75 | } 76 | } 77 | 78 | impl WriteBox<&mut W> for VmhdBox { 79 | fn write_box(&self, writer: &mut W) -> Result { 80 | let size = self.box_size(); 81 | BoxHeader::new(self.box_type(), size).write(writer)?; 82 | 83 | write_box_header_ext(writer, self.version, self.flags)?; 84 | 85 | writer.write_u16::(self.graphics_mode)?; 86 | writer.write_u16::(self.op_color.red)?; 87 | writer.write_u16::(self.op_color.green)?; 88 | writer.write_u16::(self.op_color.blue)?; 89 | 90 | Ok(size) 91 | } 92 | } 93 | 94 | #[cfg(test)] 95 | mod tests { 96 | use super::*; 97 | use crate::mp4box::BoxHeader; 98 | use std::io::Cursor; 99 | 100 | #[test] 101 | fn test_vmhd() { 102 | let src_box = VmhdBox { 103 | version: 0, 104 | flags: 1, 105 | graphics_mode: 0, 106 | op_color: RgbColor { 107 | red: 0, 108 | green: 0, 109 | blue: 0, 110 | }, 111 | }; 112 | let mut buf = Vec::new(); 113 | src_box.write_box(&mut buf).unwrap(); 114 | assert_eq!(buf.len(), src_box.box_size() as usize); 115 | 116 | let mut reader = Cursor::new(&buf); 117 | let header = BoxHeader::read(&mut reader).unwrap(); 118 | assert_eq!(header.name, BoxType::VmhdBox); 119 | assert_eq!(src_box.box_size(), header.size); 120 | 121 | let dst_box = VmhdBox::read_box(&mut reader, header.size).unwrap(); 122 | assert_eq!(src_box, dst_box); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/mp4box/vp09.rs: -------------------------------------------------------------------------------- 1 | use crate::mp4box::vpcc::VpccBox; 2 | use crate::mp4box::*; 3 | use crate::Mp4Box; 4 | use serde::Serialize; 5 | 6 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 7 | pub struct Vp09Box { 8 | pub version: u8, 9 | pub flags: u32, 10 | pub start_code: u16, 11 | pub data_reference_index: u16, 12 | pub reserved0: [u8; 16], 13 | pub width: u16, 14 | pub height: u16, 15 | pub horizresolution: (u16, u16), 16 | pub vertresolution: (u16, u16), 17 | pub reserved1: [u8; 4], 18 | pub frame_count: u16, 19 | pub compressorname: [u8; 32], 20 | pub depth: u16, 21 | pub end_code: u16, 22 | pub vpcc: VpccBox, 23 | } 24 | 25 | impl Vp09Box { 26 | pub const DEFAULT_START_CODE: u16 = 0; 27 | pub const DEFAULT_END_CODE: u16 = 0xFFFF; 28 | pub const DEFAULT_DATA_REFERENCE_INDEX: u16 = 1; 29 | pub const DEFAULT_HORIZRESOLUTION: (u16, u16) = (0x48, 0x00); 30 | pub const DEFAULT_VERTRESOLUTION: (u16, u16) = (0x48, 0x00); 31 | pub const DEFAULT_FRAME_COUNT: u16 = 1; 32 | pub const DEFAULT_COMPRESSORNAME: [u8; 32] = [0; 32]; 33 | pub const DEFAULT_DEPTH: u16 = 24; 34 | 35 | pub fn new(config: &Vp9Config) -> Self { 36 | Vp09Box { 37 | version: 0, 38 | flags: 0, 39 | start_code: Vp09Box::DEFAULT_START_CODE, 40 | data_reference_index: Vp09Box::DEFAULT_DATA_REFERENCE_INDEX, 41 | reserved0: Default::default(), 42 | width: config.width, 43 | height: config.height, 44 | horizresolution: Vp09Box::DEFAULT_HORIZRESOLUTION, 45 | vertresolution: Vp09Box::DEFAULT_VERTRESOLUTION, 46 | reserved1: Default::default(), 47 | frame_count: Vp09Box::DEFAULT_FRAME_COUNT, 48 | compressorname: Vp09Box::DEFAULT_COMPRESSORNAME, 49 | depth: Vp09Box::DEFAULT_DEPTH, 50 | end_code: Vp09Box::DEFAULT_END_CODE, 51 | vpcc: VpccBox { 52 | version: VpccBox::DEFAULT_VERSION, 53 | flags: 0, 54 | profile: 0, 55 | level: 0x1F, 56 | bit_depth: VpccBox::DEFAULT_BIT_DEPTH, 57 | chroma_subsampling: 0, 58 | video_full_range_flag: false, 59 | color_primaries: 0, 60 | transfer_characteristics: 0, 61 | matrix_coefficients: 0, 62 | codec_initialization_data_size: 0, 63 | }, 64 | } 65 | } 66 | } 67 | 68 | impl Mp4Box for Vp09Box { 69 | fn box_type(&self) -> BoxType { 70 | BoxType::Vp09Box 71 | } 72 | 73 | fn box_size(&self) -> u64 { 74 | 0x6A 75 | } 76 | 77 | fn to_json(&self) -> Result { 78 | Ok(serde_json::to_string(&self).unwrap()) 79 | } 80 | 81 | fn summary(&self) -> Result { 82 | Ok(format!("{self:?}")) 83 | } 84 | } 85 | 86 | impl ReadBox<&mut R> for Vp09Box { 87 | fn read_box(reader: &mut R, size: u64) -> Result { 88 | let start = box_start(reader)?; 89 | let (version, flags) = read_box_header_ext(reader)?; 90 | 91 | let start_code: u16 = reader.read_u16::()?; 92 | let data_reference_index: u16 = reader.read_u16::()?; 93 | let reserved0: [u8; 16] = { 94 | let mut buf = [0u8; 16]; 95 | reader.read_exact(&mut buf)?; 96 | buf 97 | }; 98 | let width: u16 = reader.read_u16::()?; 99 | let height: u16 = reader.read_u16::()?; 100 | let horizresolution: (u16, u16) = ( 101 | reader.read_u16::()?, 102 | reader.read_u16::()?, 103 | ); 104 | let vertresolution: (u16, u16) = ( 105 | reader.read_u16::()?, 106 | reader.read_u16::()?, 107 | ); 108 | let reserved1: [u8; 4] = { 109 | let mut buf = [0u8; 4]; 110 | reader.read_exact(&mut buf)?; 111 | buf 112 | }; 113 | let frame_count: u16 = reader.read_u16::()?; 114 | let compressorname: [u8; 32] = { 115 | let mut buf = [0u8; 32]; 116 | reader.read_exact(&mut buf)?; 117 | buf 118 | }; 119 | let depth: u16 = reader.read_u16::()?; 120 | let end_code: u16 = reader.read_u16::()?; 121 | 122 | let vpcc = { 123 | let header = BoxHeader::read(reader)?; 124 | if header.size > size { 125 | return Err(Error::InvalidData( 126 | "vp09 box contains a box with a larger size than it", 127 | )); 128 | } 129 | VpccBox::read_box(reader, header.size)? 130 | }; 131 | 132 | skip_bytes_to(reader, start + size)?; 133 | 134 | Ok(Self { 135 | version, 136 | flags, 137 | start_code, 138 | data_reference_index, 139 | reserved0, 140 | width, 141 | height, 142 | horizresolution, 143 | vertresolution, 144 | reserved1, 145 | frame_count, 146 | compressorname, 147 | depth, 148 | end_code, 149 | vpcc, 150 | }) 151 | } 152 | } 153 | 154 | impl WriteBox<&mut W> for Vp09Box { 155 | fn write_box(&self, writer: &mut W) -> Result { 156 | let size = self.box_size(); 157 | BoxHeader::new(self.box_type(), size).write(writer)?; 158 | 159 | write_box_header_ext(writer, self.version, self.flags)?; 160 | 161 | writer.write_u16::(self.start_code)?; 162 | writer.write_u16::(self.data_reference_index)?; 163 | writer.write_all(&self.reserved0)?; 164 | writer.write_u16::(self.width)?; 165 | writer.write_u16::(self.height)?; 166 | writer.write_u16::(self.horizresolution.0)?; 167 | writer.write_u16::(self.horizresolution.1)?; 168 | writer.write_u16::(self.vertresolution.0)?; 169 | writer.write_u16::(self.vertresolution.1)?; 170 | writer.write_all(&self.reserved1)?; 171 | writer.write_u16::(self.frame_count)?; 172 | writer.write_all(&self.compressorname)?; 173 | writer.write_u16::(self.depth)?; 174 | writer.write_u16::(self.end_code)?; 175 | VpccBox::write_box(&self.vpcc, writer)?; 176 | 177 | Ok(size) 178 | } 179 | } 180 | 181 | #[cfg(test)] 182 | mod tests { 183 | use super::*; 184 | use crate::mp4box::BoxHeader; 185 | use std::io::Cursor; 186 | 187 | #[test] 188 | fn test_vpcc() { 189 | let src_box = Vp09Box::new(&Vp9Config { 190 | width: 1920, 191 | height: 1080, 192 | }); 193 | let mut buf = Vec::new(); 194 | src_box.write_box(&mut buf).unwrap(); 195 | assert_eq!(buf.len(), src_box.box_size() as usize); 196 | 197 | let mut reader = Cursor::new(&buf); 198 | let header = BoxHeader::read(&mut reader).unwrap(); 199 | assert_eq!(header.name, BoxType::Vp09Box); 200 | assert_eq!(src_box.box_size(), header.size); 201 | 202 | let dst_box = Vp09Box::read_box(&mut reader, header.size).unwrap(); 203 | assert_eq!(src_box, dst_box); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/mp4box/vpcc.rs: -------------------------------------------------------------------------------- 1 | use crate::mp4box::*; 2 | use crate::Mp4Box; 3 | use serde::Serialize; 4 | 5 | #[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)] 6 | pub struct VpccBox { 7 | pub version: u8, 8 | pub flags: u32, 9 | pub profile: u8, 10 | pub level: u8, 11 | pub bit_depth: u8, 12 | pub chroma_subsampling: u8, 13 | pub video_full_range_flag: bool, 14 | pub color_primaries: u8, 15 | pub transfer_characteristics: u8, 16 | pub matrix_coefficients: u8, 17 | pub codec_initialization_data_size: u16, 18 | } 19 | 20 | impl VpccBox { 21 | pub const DEFAULT_VERSION: u8 = 1; 22 | pub const DEFAULT_BIT_DEPTH: u8 = 8; 23 | } 24 | 25 | impl Mp4Box for VpccBox { 26 | fn box_type(&self) -> BoxType { 27 | BoxType::VpccBox 28 | } 29 | 30 | fn box_size(&self) -> u64 { 31 | HEADER_SIZE + HEADER_EXT_SIZE + 8 32 | } 33 | 34 | fn to_json(&self) -> Result { 35 | Ok(serde_json::to_string(&self).unwrap()) 36 | } 37 | 38 | fn summary(&self) -> Result { 39 | Ok(format!("{self:?}")) 40 | } 41 | } 42 | 43 | impl ReadBox<&mut R> for VpccBox { 44 | fn read_box(reader: &mut R, size: u64) -> Result { 45 | let start = box_start(reader)?; 46 | let (version, flags) = read_box_header_ext(reader)?; 47 | 48 | let profile: u8 = reader.read_u8()?; 49 | let level: u8 = reader.read_u8()?; 50 | let (bit_depth, chroma_subsampling, video_full_range_flag) = { 51 | let b = reader.read_u8()?; 52 | (b >> 4, b << 4 >> 5, b & 0x01 == 1) 53 | }; 54 | let transfer_characteristics: u8 = reader.read_u8()?; 55 | let matrix_coefficients: u8 = reader.read_u8()?; 56 | let codec_initialization_data_size: u16 = reader.read_u16::()?; 57 | 58 | skip_bytes_to(reader, start + size)?; 59 | 60 | Ok(Self { 61 | version, 62 | flags, 63 | profile, 64 | level, 65 | bit_depth, 66 | chroma_subsampling, 67 | video_full_range_flag, 68 | color_primaries: 0, 69 | transfer_characteristics, 70 | matrix_coefficients, 71 | codec_initialization_data_size, 72 | }) 73 | } 74 | } 75 | 76 | impl WriteBox<&mut W> for VpccBox { 77 | fn write_box(&self, writer: &mut W) -> Result { 78 | let size = self.box_size(); 79 | BoxHeader::new(self.box_type(), size).write(writer)?; 80 | 81 | write_box_header_ext(writer, self.version, self.flags)?; 82 | 83 | writer.write_u8(self.profile)?; 84 | writer.write_u8(self.level)?; 85 | writer.write_u8( 86 | (self.bit_depth << 4) 87 | | (self.chroma_subsampling << 1) 88 | | (self.video_full_range_flag as u8), 89 | )?; 90 | writer.write_u8(self.color_primaries)?; 91 | writer.write_u8(self.transfer_characteristics)?; 92 | writer.write_u8(self.matrix_coefficients)?; 93 | writer.write_u16::(self.codec_initialization_data_size)?; 94 | 95 | Ok(size) 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use super::*; 102 | use crate::mp4box::BoxHeader; 103 | use std::io::Cursor; 104 | 105 | #[test] 106 | fn test_vpcc() { 107 | let src_box = VpccBox { 108 | version: VpccBox::DEFAULT_VERSION, 109 | flags: 0, 110 | profile: 0, 111 | level: 0x1F, 112 | bit_depth: VpccBox::DEFAULT_BIT_DEPTH, 113 | chroma_subsampling: 0, 114 | video_full_range_flag: false, 115 | color_primaries: 0, 116 | transfer_characteristics: 0, 117 | matrix_coefficients: 0, 118 | codec_initialization_data_size: 0, 119 | }; 120 | let mut buf = Vec::new(); 121 | src_box.write_box(&mut buf).unwrap(); 122 | assert_eq!(buf.len(), src_box.box_size() as usize); 123 | 124 | let mut reader = Cursor::new(&buf); 125 | let header = BoxHeader::read(&mut reader).unwrap(); 126 | assert_eq!(header.name, BoxType::VpccBox); 127 | assert_eq!(src_box.box_size(), header.size); 128 | 129 | let dst_box = VpccBox::read_box(&mut reader, header.size).unwrap(); 130 | assert_eq!(src_box, dst_box); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/writer.rs: -------------------------------------------------------------------------------- 1 | use byteorder::{BigEndian, WriteBytesExt}; 2 | use std::io::{Seek, SeekFrom, Write}; 3 | 4 | use crate::mp4box::*; 5 | use crate::track::Mp4TrackWriter; 6 | use crate::*; 7 | 8 | #[derive(Debug, Clone, PartialEq, Eq)] 9 | pub struct Mp4Config { 10 | pub major_brand: FourCC, 11 | pub minor_version: u32, 12 | pub compatible_brands: Vec, 13 | pub timescale: u32, 14 | } 15 | 16 | #[derive(Debug)] 17 | pub struct Mp4Writer { 18 | writer: W, 19 | tracks: Vec, 20 | mdat_pos: u64, 21 | timescale: u32, 22 | duration: u64, 23 | } 24 | 25 | impl Mp4Writer { 26 | /// Consume self, returning the inner writer. 27 | /// 28 | /// This can be useful to recover the inner writer after completion in case 29 | /// it's owned by the [Mp4Writer] instance. 30 | /// 31 | /// # Examples 32 | /// 33 | /// ```rust 34 | /// use mp4::{Mp4Writer, Mp4Config}; 35 | /// use std::io::Cursor; 36 | /// 37 | /// # fn main() -> mp4::Result<()> { 38 | /// let config = Mp4Config { 39 | /// major_brand: str::parse("isom").unwrap(), 40 | /// minor_version: 512, 41 | /// compatible_brands: vec![ 42 | /// str::parse("isom").unwrap(), 43 | /// str::parse("iso2").unwrap(), 44 | /// str::parse("avc1").unwrap(), 45 | /// str::parse("mp41").unwrap(), 46 | /// ], 47 | /// timescale: 1000, 48 | /// }; 49 | /// 50 | /// let data = Cursor::new(Vec::::new()); 51 | /// let mut writer = mp4::Mp4Writer::write_start(data, &config)?; 52 | /// writer.write_end()?; 53 | /// 54 | /// let data: Vec = writer.into_writer().into_inner(); 55 | /// # Ok(()) } 56 | /// ``` 57 | pub fn into_writer(self) -> W { 58 | self.writer 59 | } 60 | } 61 | 62 | impl Mp4Writer { 63 | pub fn write_start(mut writer: W, config: &Mp4Config) -> Result { 64 | let ftyp = FtypBox { 65 | major_brand: config.major_brand, 66 | minor_version: config.minor_version, 67 | compatible_brands: config.compatible_brands.clone(), 68 | }; 69 | ftyp.write_box(&mut writer)?; 70 | 71 | // TODO largesize 72 | let mdat_pos = writer.stream_position()?; 73 | BoxHeader::new(BoxType::MdatBox, HEADER_SIZE).write(&mut writer)?; 74 | BoxHeader::new(BoxType::WideBox, HEADER_SIZE).write(&mut writer)?; 75 | 76 | let tracks = Vec::new(); 77 | let timescale = config.timescale; 78 | let duration = 0; 79 | Ok(Self { 80 | writer, 81 | tracks, 82 | mdat_pos, 83 | timescale, 84 | duration, 85 | }) 86 | } 87 | 88 | pub fn add_track(&mut self, config: &TrackConfig) -> Result<()> { 89 | let track_id = self.tracks.len() as u32 + 1; 90 | let track = Mp4TrackWriter::new(track_id, config)?; 91 | self.tracks.push(track); 92 | Ok(()) 93 | } 94 | 95 | fn update_durations(&mut self, track_dur: u64) { 96 | if track_dur > self.duration { 97 | self.duration = track_dur; 98 | } 99 | } 100 | 101 | pub fn write_sample(&mut self, track_id: u32, sample: &Mp4Sample) -> Result<()> { 102 | if track_id == 0 { 103 | return Err(Error::TrakNotFound(track_id)); 104 | } 105 | 106 | let track_dur = if let Some(ref mut track) = self.tracks.get_mut(track_id as usize - 1) { 107 | track.write_sample(&mut self.writer, sample, self.timescale)? 108 | } else { 109 | return Err(Error::TrakNotFound(track_id)); 110 | }; 111 | 112 | self.update_durations(track_dur); 113 | 114 | Ok(()) 115 | } 116 | 117 | fn update_mdat_size(&mut self) -> Result<()> { 118 | let mdat_end = self.writer.stream_position()?; 119 | let mdat_size = mdat_end - self.mdat_pos; 120 | if mdat_size > std::u32::MAX as u64 { 121 | self.writer.seek(SeekFrom::Start(self.mdat_pos))?; 122 | self.writer.write_u32::(1)?; 123 | self.writer.seek(SeekFrom::Start(self.mdat_pos + 8))?; 124 | self.writer.write_u64::(mdat_size)?; 125 | } else { 126 | self.writer.seek(SeekFrom::Start(self.mdat_pos))?; 127 | self.writer.write_u32::(mdat_size as u32)?; 128 | } 129 | self.writer.seek(SeekFrom::Start(mdat_end))?; 130 | Ok(()) 131 | } 132 | 133 | pub fn write_end(&mut self) -> Result<()> { 134 | let mut moov = MoovBox::default(); 135 | 136 | for track in self.tracks.iter_mut() { 137 | moov.traks.push(track.write_end(&mut self.writer)?); 138 | } 139 | self.update_mdat_size()?; 140 | 141 | moov.mvhd.timescale = self.timescale; 142 | moov.mvhd.duration = self.duration; 143 | if moov.mvhd.duration > (u32::MAX as u64) { 144 | moov.mvhd.version = 1 145 | } 146 | moov.write_box(&mut self.writer)?; 147 | Ok(()) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /tests/lib.rs: -------------------------------------------------------------------------------- 1 | use mp4::{ 2 | AudioObjectType, AvcProfile, ChannelConfig, MediaType, Metadata, Mp4Reader, SampleFreqIndex, 3 | TrackType, 4 | }; 5 | use std::fs::{self, File}; 6 | use std::io::BufReader; 7 | use std::time::Duration; 8 | 9 | #[test] 10 | fn test_read_mp4() { 11 | let mut mp4 = get_reader("tests/samples/minimal.mp4"); 12 | 13 | assert_eq!(2591, mp4.size()); 14 | 15 | // ftyp. 16 | assert_eq!(4, mp4.compatible_brands().len()); 17 | 18 | // Check compatible_brands. 19 | let brands = vec![ 20 | String::from("isom"), 21 | String::from("iso2"), 22 | String::from("avc1"), 23 | String::from("mp41"), 24 | ]; 25 | 26 | for b in brands { 27 | let t = mp4.compatible_brands().iter().any(|x| x.to_string() == b); 28 | assert!(t); 29 | } 30 | 31 | assert_eq!(mp4.duration(), Duration::from_millis(62)); 32 | assert_eq!(mp4.timescale(), 1000); 33 | assert_eq!(mp4.tracks().len(), 2); 34 | 35 | let sample_count = mp4.sample_count(1).unwrap(); 36 | assert_eq!(sample_count, 1); 37 | let sample_1_1 = mp4.read_sample(1, 1).unwrap().unwrap(); 38 | assert_eq!(sample_1_1.bytes.len(), 751); 39 | assert_eq!( 40 | sample_1_1, 41 | mp4::Mp4Sample { 42 | start_time: 0, 43 | duration: 512, 44 | rendering_offset: 0, 45 | is_sync: true, 46 | bytes: mp4::Bytes::from(vec![0x0u8; 751]), 47 | } 48 | ); 49 | let eos = mp4.read_sample(1, 2).unwrap(); 50 | assert!(eos.is_none()); 51 | 52 | let sample_count = mp4.sample_count(2).unwrap(); 53 | assert_eq!(sample_count, 3); 54 | let sample_2_1 = mp4.read_sample(2, 1).unwrap().unwrap(); 55 | assert_eq!(sample_2_1.bytes.len(), 179); 56 | assert_eq!( 57 | sample_2_1, 58 | mp4::Mp4Sample { 59 | start_time: 0, 60 | duration: 1024, 61 | rendering_offset: 0, 62 | is_sync: true, 63 | bytes: mp4::Bytes::from(vec![0x0u8; 179]), 64 | } 65 | ); 66 | 67 | let sample_2_2 = mp4.read_sample(2, 2).unwrap().unwrap(); 68 | assert_eq!( 69 | sample_2_2, 70 | mp4::Mp4Sample { 71 | start_time: 1024, 72 | duration: 1024, 73 | rendering_offset: 0, 74 | is_sync: true, 75 | bytes: mp4::Bytes::from(vec![0x0u8; 180]), 76 | } 77 | ); 78 | 79 | let sample_2_3 = mp4.read_sample(2, 3).unwrap().unwrap(); 80 | assert_eq!( 81 | sample_2_3, 82 | mp4::Mp4Sample { 83 | start_time: 2048, 84 | duration: 896, 85 | rendering_offset: 0, 86 | is_sync: true, 87 | bytes: mp4::Bytes::from(vec![0x0u8; 160]), 88 | } 89 | ); 90 | 91 | let eos = mp4.read_sample(2, 4).unwrap(); 92 | assert!(eos.is_none()); 93 | 94 | // track #1 95 | let track1 = mp4.tracks().get(&1).unwrap(); 96 | assert_eq!(track1.track_id(), 1); 97 | assert_eq!(track1.track_type().unwrap(), TrackType::Video); 98 | assert_eq!(track1.media_type().unwrap(), MediaType::H264); 99 | assert_eq!(track1.video_profile().unwrap(), AvcProfile::AvcHigh); 100 | assert_eq!(track1.width(), 320); 101 | assert_eq!(track1.height(), 240); 102 | assert_eq!(track1.bitrate(), 150200); 103 | assert_eq!(track1.frame_rate(), 25.00); 104 | 105 | // track #2 106 | let track2 = mp4.tracks().get(&2).unwrap(); 107 | assert_eq!(track2.track_type().unwrap(), TrackType::Audio); 108 | assert_eq!(track2.media_type().unwrap(), MediaType::AAC); 109 | assert_eq!( 110 | track2.audio_profile().unwrap(), 111 | AudioObjectType::AacLowComplexity 112 | ); 113 | assert_eq!( 114 | track2.sample_freq_index().unwrap(), 115 | SampleFreqIndex::Freq48000 116 | ); 117 | assert_eq!(track2.channel_config().unwrap(), ChannelConfig::Mono); 118 | assert_eq!(track2.bitrate(), 67695); 119 | } 120 | 121 | #[test] 122 | fn test_read_extended_audio_object_type() { 123 | // Extended audio object type and sample rate index of 15 124 | let mp4 = get_reader("tests/samples/extended_audio_object_type.mp4"); 125 | 126 | let track = mp4.tracks().get(&1).unwrap(); 127 | assert_eq!(track.track_type().unwrap(), TrackType::Audio); 128 | assert_eq!(track.media_type().unwrap(), MediaType::AAC); 129 | assert_eq!( 130 | track.audio_profile().unwrap(), 131 | AudioObjectType::AudioLosslessCoding 132 | ); 133 | assert_eq!( 134 | track 135 | .trak 136 | .mdia 137 | .minf 138 | .stbl 139 | .stsd 140 | .mp4a 141 | .as_ref() 142 | .unwrap() 143 | .esds 144 | .as_ref() 145 | .unwrap() 146 | .es_desc 147 | .dec_config 148 | .dec_specific 149 | .freq_index, 150 | 15 151 | ); 152 | assert_eq!(track.channel_config().unwrap(), ChannelConfig::Stereo); 153 | assert_eq!(track.bitrate(), 839250); 154 | } 155 | 156 | fn get_reader(path: &str) -> Mp4Reader> { 157 | let f = File::open(path).unwrap(); 158 | let f_size = f.metadata().unwrap().len(); 159 | let reader = BufReader::new(f); 160 | 161 | mp4::Mp4Reader::read_header(reader, f_size).unwrap() 162 | } 163 | 164 | #[test] 165 | fn test_read_metadata() { 166 | let want_poster = fs::read("tests/samples/big_buck_bunny.jpg").unwrap(); 167 | let want_summary = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue."; 168 | let mp4 = get_reader("tests/samples/big_buck_bunny_metadata.m4v"); 169 | let metadata = mp4.metadata(); 170 | assert_eq!(metadata.title(), Some("Big Buck Bunny".into())); 171 | assert_eq!(metadata.year(), Some(2008)); 172 | assert_eq!(metadata.summary(), Some(want_summary.into())); 173 | 174 | assert!(metadata.poster().is_some()); 175 | let poster = metadata.poster().unwrap(); 176 | assert_eq!(poster.len(), want_poster.len()); 177 | assert_eq!(poster, want_poster.as_slice()); 178 | } 179 | 180 | #[test] 181 | fn test_read_fragments() { 182 | let mp4 = get_reader("tests/samples/minimal_init.mp4"); 183 | 184 | assert_eq!(692, mp4.size()); 185 | assert_eq!(5, mp4.compatible_brands().len()); 186 | 187 | let sample_count = mp4.sample_count(1).unwrap(); 188 | assert_eq!(sample_count, 0); 189 | 190 | let f = File::open("tests/samples/minimal_fragment.m4s").unwrap(); 191 | let f_size = f.metadata().unwrap().len(); 192 | let frag_reader = BufReader::new(f); 193 | 194 | let mut mp4_fragment = mp4.read_fragment_header(frag_reader, f_size).unwrap(); 195 | let sample_count = mp4_fragment.sample_count(1).unwrap(); 196 | assert_eq!(sample_count, 1); 197 | let sample_1_1 = mp4_fragment.read_sample(1, 1).unwrap().unwrap(); 198 | assert_eq!(sample_1_1.bytes.len(), 751); 199 | assert_eq!( 200 | sample_1_1, 201 | mp4::Mp4Sample { 202 | start_time: 0, 203 | duration: 512, 204 | rendering_offset: 0, 205 | is_sync: true, 206 | bytes: mp4::Bytes::from(vec![0x0u8; 751]), 207 | } 208 | ); 209 | let eos = mp4_fragment.read_sample(1, 2); 210 | assert!(eos.is_err()); 211 | } 212 | -------------------------------------------------------------------------------- /tests/samples/big_buck_bunny.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/mp4-rust/35560e94f5e871a2b2d88bfe964013b39af131e8/tests/samples/big_buck_bunny.jpg -------------------------------------------------------------------------------- /tests/samples/big_buck_bunny_metadata.m4v: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/mp4-rust/35560e94f5e871a2b2d88bfe964013b39af131e8/tests/samples/big_buck_bunny_metadata.m4v -------------------------------------------------------------------------------- /tests/samples/extended_audio_object_type.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/mp4-rust/35560e94f5e871a2b2d88bfe964013b39af131e8/tests/samples/extended_audio_object_type.mp4 -------------------------------------------------------------------------------- /tests/samples/minimal.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/mp4-rust/35560e94f5e871a2b2d88bfe964013b39af131e8/tests/samples/minimal.mp4 -------------------------------------------------------------------------------- /tests/samples/minimal_fragment.m4s: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/mp4-rust/35560e94f5e871a2b2d88bfe964013b39af131e8/tests/samples/minimal_fragment.m4s -------------------------------------------------------------------------------- /tests/samples/minimal_init.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alfg/mp4-rust/35560e94f5e871a2b2d88bfe964013b39af131e8/tests/samples/minimal_init.mp4 --------------------------------------------------------------------------------