mirror of
https://github.com/alfg/mp4-rust.git
synced 2024-06-02 21:49:24 +00:00
Compare commits
107 commits
Author | SHA1 | Date | |
---|---|---|---|
35560e94f5 | |||
19e4eaa3c8 | |||
b4fca8a199 | |||
df92ac893c | |||
85b8209d5e | |||
b90e43dd9c | |||
18f1718b70 | |||
d6c38642de | |||
c104047215 | |||
aff3bf6c45 | |||
55875d72de | |||
a3508fe1c9 | |||
7cfdffbd71 | |||
024c26ade7 | |||
c8cc6f25a8 | |||
9f10bd335e | |||
2a374c57f2 | |||
9c0f6534bd | |||
16c1b5d67f | |||
df6e0b5ea0 | |||
3095051512 | |||
c26bdcab59 | |||
0cd2abb60a | |||
ace2799c75 | |||
5d648f1a72 | |||
00385bad0b | |||
cc9bf192fa | |||
21afff228c | |||
87933a23da | |||
80aabdf549 | |||
3ecfd0cc9d | |||
33959766d4 | |||
7218ada431 | |||
084381bde5 | |||
93dbb56464 | |||
02b87d3ce7 | |||
b48066fec4 | |||
b749eaa064 | |||
12ba304023 | |||
49f9ed0432 | |||
e7f5f71ae3 | |||
7be2ebe49d | |||
b4e43c21c2 | |||
2849d4d791 | |||
60b6d56704 | |||
8fe009e248 | |||
94f9e88b2d | |||
56cc260a5c | |||
751a2221af | |||
28dd9e0a27 | |||
335aa39f7c | |||
b59758c7df | |||
0d2d2547d1 | |||
75599fe561 | |||
eedccec776 | |||
4d2b5acf9e | |||
8fd133eccf | |||
d73e80107d | |||
2a33b990e6 | |||
00b50636b6 | |||
9e8f27be2a | |||
9c43838793 | |||
b63522186e | |||
4bf4a98414 | |||
8af11652f1 | |||
098de52658 | |||
95ce57363a | |||
fba770a00e | |||
6ec013b7b9 | |||
f8f767dc07 | |||
a1f7902c6c | |||
dd2eae6eed | |||
0f373b03db | |||
0330a8eda7 | |||
9822589734 | |||
d0e169b679 | |||
76607394df | |||
6bc1c9b217 | |||
e879500c1a | |||
421d9e7606 | |||
05e20124e0 | |||
fa53d6b138 | |||
8def228352 | |||
e4c7cf4520 | |||
3b5a728a30 | |||
042629a88c | |||
b4b4dbd49f | |||
65b3408625 | |||
be92897be6 | |||
6d1da78160 | |||
682f8a93c1 | |||
b016ac3b47 | |||
71f0f78ae6 | |||
4df1097948 | |||
d51a193272 | |||
c4ff8627b0 | |||
0ac9986c7f | |||
6cd4f72d28 | |||
4f417f885d | |||
0abb242037 | |||
b5367032a7 | |||
3104a2d95b | |||
ba69f3812b | |||
0df82aec5f | |||
b755db3fa0 | |||
4df9fa1bd4 | |||
8f56200dd0 |
42
.github/workflows/rust.yml
vendored
42
.github/workflows/rust.yml
vendored
|
@ -11,12 +11,40 @@ env:
|
|||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build
|
||||
run: cargo build --verbose
|
||||
- name: Run tests
|
||||
run: cargo test --verbose
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: stable
|
||||
override: true
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Setup rust smart caching
|
||||
uses: Swatinem/rust-cache@v1.3.0
|
||||
|
||||
- name: Cargo fmt
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: fmt
|
||||
args: --all -- --check
|
||||
|
||||
- name: Cargo clippy
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: clippy
|
||||
args: --no-deps -- -D warnings
|
||||
|
||||
- name: Cargo build
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
|
||||
- name: Cargo test
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
/Cargo.lock
|
||||
/target
|
||||
**/*.rs.bk
|
||||
*.exe
|
||||
|
@ -5,4 +6,4 @@
|
|||
*.mp4
|
||||
.idea/
|
||||
.vscode/
|
||||
!tests/samples/*.mp4
|
||||
!tests/samples/*.mp4
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
language: rust
|
||||
rust:
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
jobs:
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
fast_finish: true
|
16
Cargo.lock
generated
16
Cargo.lock
generated
|
@ -1,16 +0,0 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "mp4"
|
||||
version = "0.4.3"
|
||||
dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5"
|
33
Cargo.toml
33
Cargo.toml
|
@ -1,21 +1,28 @@
|
|||
[package]
|
||||
name = "mp4"
|
||||
version = "0.4.3"
|
||||
version = "0.14.0"
|
||||
authors = ["Alf <alf.g.jr@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
description = """
|
||||
MP4 Reader in Rust
|
||||
ISO/IEC 14496-12 - ISO Base Media File Format (QuickTime, MPEG-4, etc)
|
||||
"""
|
||||
|
||||
documentation = "https://docs.rs/mp4rs"
|
||||
homepage = "https://github.com/alfg/mp4rs"
|
||||
repository = "https://github.com/alfg/mp4rs"
|
||||
|
||||
keywords = ["mp4", "isobmff"]
|
||||
|
||||
description = "MP4 reader and writer library in Rust."
|
||||
documentation = "https://docs.rs/mp4"
|
||||
readme = "README.md"
|
||||
homepage = "https://github.com/alfg/mp4-rust"
|
||||
repository = "https://github.com/alfg/mp4-rust"
|
||||
keywords = ["mp4", "iso-mp4", "isobmff", "video", "multimedia"]
|
||||
license = "MIT"
|
||||
include = ["src", "benches", "Cargo.toml", "README", "LICENSE"]
|
||||
|
||||
[dependencies]
|
||||
thiserror = "^1.0"
|
||||
byteorder = "1"
|
||||
bytes = "1.1.0"
|
||||
num-rational = { version = "0.4.0", features = ["serde"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
|
||||
[[bench]]
|
||||
name = "bench_main"
|
||||
harness = false
|
||||
|
|
118
README.md
118
README.md
|
@ -1,30 +1,70 @@
|
|||
# mp4rs
|
||||
> MP4 Reader in Rust
|
||||
# mp4
|
||||
> MP4 Reader and Writer in Rust 🦀
|
||||
|
||||
ISO/IEC 14496-12 - ISO Base Media File Format (QuickTime, MPEG-4, etc)
|
||||
`mp4` is a Rust library to read and write ISO-MP4 files. This package contains MPEG-4 specifications defined in parts:
|
||||
* [ISO/IEC 14496-12](https://en.wikipedia.org/wiki/ISO/IEC_base_media_file_format) - ISO Base Media File Format (QuickTime, MPEG-4, etc)
|
||||
* [ISO/IEC 14496-14](https://en.wikipedia.org/wiki/MPEG-4_Part_14) - MP4 file format
|
||||
* ISO/IEC 14496-17 - Streaming text format
|
||||
|
||||
https://crates.io/crates/mp4
|
||||
|
||||
[![Crates.io](https://img.shields.io/crates/v/mp4)](https://crates.io/crates/mp4)
|
||||
[![Crates.io](https://img.shields.io/crates/d/mp4)](https://crates.io/crates/mp4)
|
||||
[![Build Status](https://travis-ci.org/alfg/mp4rs.svg?branch=master)](https://travis-ci.org/alfg/mp4rs)
|
||||
![Rust](https://github.com/alfg/mp4rs/workflows/Rust/badge.svg)
|
||||
[![Docs](https://img.shields.io/badge/docs-online-5023dd.svg?style=flat-square)](https://docs.rs/mp4)
|
||||
[![Rust](https://github.com/alfg/mp4-rust/workflows/Rust/badge.svg)](https://github.com/alfg/mp4-rust/actions)
|
||||
|
||||
#### Example
|
||||
```rust
|
||||
use mp4;
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader};
|
||||
use mp4::{Result};
|
||||
|
||||
fn main() {
|
||||
let f = File::open("example.mp4").unwrap();
|
||||
fn main() -> Result<()> {
|
||||
let f = File::open("tests/samples/minimal.mp4").unwrap();
|
||||
let size = f.metadata()?.len();
|
||||
let reader = BufReader::new(f);
|
||||
|
||||
let bmff = mp4::read_mp4(f).unwrap();
|
||||
let mp4 = mp4::Mp4Reader::read_header(reader, size)?;
|
||||
|
||||
println!("file size: {}", bmff.size);
|
||||
println!("brands: {:?} {:?}\n",
|
||||
bmff.ftyp.major_brand, bmff.ftyp.compatible_brands
|
||||
);
|
||||
// Print boxes.
|
||||
println!("major brand: {}", mp4.ftyp.major_brand);
|
||||
println!("timescale: {}", mp4.moov.mvhd.timescale);
|
||||
|
||||
// Use available methods.
|
||||
println!("size: {}", mp4.size());
|
||||
|
||||
let mut compatible_brands = String::new();
|
||||
for brand in mp4.compatible_brands().iter() {
|
||||
compatible_brands.push_str(&brand.to_string());
|
||||
compatible_brands.push_str(",");
|
||||
}
|
||||
println!("compatible brands: {}", compatible_brands);
|
||||
println!("duration: {:?}", mp4.duration());
|
||||
|
||||
// Track info.
|
||||
for track in mp4.tracks().values() {
|
||||
println!(
|
||||
"track: #{}({}) {} : {}",
|
||||
track.track_id(),
|
||||
track.language(),
|
||||
track.track_type()?,
|
||||
track.box_type()?,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
See [examples/](examples/) for a full example.
|
||||
See [examples/](examples/) for more examples.
|
||||
|
||||
#### Install
|
||||
```
|
||||
cargo add mp4
|
||||
```
|
||||
or add to your `Cargo.toml`:
|
||||
```toml
|
||||
mp4 = "0.14.0"
|
||||
```
|
||||
|
||||
#### Documentation
|
||||
* https://docs.rs/mp4/
|
||||
|
@ -39,12 +79,23 @@ See [examples/](examples/) for a full example.
|
|||
cargo build
|
||||
```
|
||||
|
||||
#### Lint and Format
|
||||
```
|
||||
cargo clippy --fix
|
||||
cargo fmt --all
|
||||
```
|
||||
|
||||
#### Run Examples
|
||||
* `mp4info`
|
||||
```
|
||||
cargo run --example mp4info <movie.mp4>
|
||||
```
|
||||
|
||||
* `mp4dump`
|
||||
```
|
||||
cargo run --example mp4dump <movie.mp4>
|
||||
```
|
||||
|
||||
#### Run Tests
|
||||
```
|
||||
cargo test
|
||||
|
@ -55,11 +106,46 @@ With print statement output.
|
|||
cargo test -- --nocapture
|
||||
```
|
||||
|
||||
## Resources
|
||||
Thanks to the following resources used when learning Rust:
|
||||
#### Run Cargo fmt
|
||||
Run fmt to catch formatting errors.
|
||||
|
||||
```
|
||||
rustup component add rustfmt
|
||||
cargo fmt --all -- --check
|
||||
```
|
||||
|
||||
#### Run Clippy
|
||||
Run Clippy tests to catch common lints and mistakes.
|
||||
|
||||
```
|
||||
rustup component add clippy
|
||||
cargo clippy --no-deps -- -D warnings
|
||||
```
|
||||
|
||||
#### Run Benchmark Tests
|
||||
```
|
||||
cargo bench
|
||||
```
|
||||
|
||||
View HTML report at `target/criterion/report/index.html`
|
||||
|
||||
#### Generate Docs
|
||||
```
|
||||
cargo docs
|
||||
```
|
||||
|
||||
View at `target/doc/mp4/index.html`
|
||||
|
||||
## Web Assembly
|
||||
See the [mp4-inspector](https://github.com/alfg/mp4-inspector) project as a reference for using this library in Javascript via Web Assembly.
|
||||
|
||||
## Related Projects
|
||||
* https://github.com/mozilla/mp4parse-rust
|
||||
* https://github.com/pcwalton/rust-media
|
||||
* https://github.com/alfg/mp4
|
||||
|
||||
## License
|
||||
MIT
|
||||
|
||||
[docs]: https://docs.rs/mp4
|
||||
[docs-badge]: https://img.shields.io/badge/docs-online-5023dd.svg?style=flat-square
|
||||
|
|
26
benches/bench_main.rs
Normal file
26
benches/bench_main.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use criterion::BenchmarkId;
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use std::fs::File;
|
||||
|
||||
fn read_mp4(filename: &str) -> u64 {
|
||||
let f = File::open(filename).unwrap();
|
||||
let m = mp4::read_mp4(f).unwrap();
|
||||
|
||||
m.size()
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
let filename = "tests/samples/minimal.mp4";
|
||||
|
||||
c.bench_with_input(
|
||||
BenchmarkId::new("input_example", filename),
|
||||
&filename,
|
||||
|b, &s| {
|
||||
b.iter(|| read_mp4(s));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
93
examples/mp4copy.rs
Normal file
93
examples/mp4copy.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, BufReader, BufWriter};
|
||||
use std::path::Path;
|
||||
|
||||
use mp4::{
|
||||
AacConfig, AvcConfig, HevcConfig, MediaConfig, MediaType, Mp4Config, Result, TrackConfig,
|
||||
TtxtConfig, Vp9Config,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() < 3 {
|
||||
println!("Usage: mp4copy <source file> <target file>");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if let Err(err) = copy(&args[1], &args[2]) {
|
||||
let _ = writeln!(io::stderr(), "{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn copy<P: AsRef<Path>>(src_filename: &P, dst_filename: &P) -> Result<()> {
|
||||
let src_file = File::open(src_filename)?;
|
||||
let size = src_file.metadata()?.len();
|
||||
let reader = BufReader::new(src_file);
|
||||
|
||||
let dst_file = File::create(dst_filename)?;
|
||||
let writer = BufWriter::new(dst_file);
|
||||
|
||||
let mut mp4_reader = mp4::Mp4Reader::read_header(reader, size)?;
|
||||
let mut mp4_writer = mp4::Mp4Writer::write_start(
|
||||
writer,
|
||||
&Mp4Config {
|
||||
major_brand: *mp4_reader.major_brand(),
|
||||
minor_version: mp4_reader.minor_version(),
|
||||
compatible_brands: mp4_reader.compatible_brands().to_vec(),
|
||||
timescale: mp4_reader.timescale(),
|
||||
},
|
||||
)?;
|
||||
|
||||
// TODO interleaving
|
||||
for track in mp4_reader.tracks().values() {
|
||||
let media_conf = match track.media_type()? {
|
||||
MediaType::H264 => MediaConfig::AvcConfig(AvcConfig {
|
||||
width: track.width(),
|
||||
height: track.height(),
|
||||
seq_param_set: track.sequence_parameter_set()?.to_vec(),
|
||||
pic_param_set: track.picture_parameter_set()?.to_vec(),
|
||||
}),
|
||||
MediaType::H265 => MediaConfig::HevcConfig(HevcConfig {
|
||||
width: track.width(),
|
||||
height: track.height(),
|
||||
}),
|
||||
MediaType::VP9 => MediaConfig::Vp9Config(Vp9Config {
|
||||
width: track.width(),
|
||||
height: track.height(),
|
||||
}),
|
||||
MediaType::AAC => MediaConfig::AacConfig(AacConfig {
|
||||
bitrate: track.bitrate(),
|
||||
profile: track.audio_profile()?,
|
||||
freq_index: track.sample_freq_index()?,
|
||||
chan_conf: track.channel_config()?,
|
||||
}),
|
||||
MediaType::TTXT => MediaConfig::TtxtConfig(TtxtConfig {}),
|
||||
};
|
||||
|
||||
let track_conf = TrackConfig {
|
||||
track_type: track.track_type()?,
|
||||
timescale: track.timescale(),
|
||||
language: track.language().to_string(),
|
||||
media_conf,
|
||||
};
|
||||
|
||||
mp4_writer.add_track(&track_conf)?;
|
||||
}
|
||||
|
||||
for track_id in mp4_reader.tracks().keys().copied().collect::<Vec<u32>>() {
|
||||
let sample_count = mp4_reader.sample_count(track_id)?;
|
||||
for sample_idx in 0..sample_count {
|
||||
let sample_id = sample_idx + 1;
|
||||
let sample = mp4_reader.read_sample(track_id, sample_id)?.unwrap();
|
||||
mp4_writer.write_sample(track_id, &sample)?;
|
||||
// println!("copy {}:({})", sample_id, sample);
|
||||
}
|
||||
}
|
||||
|
||||
mp4_writer.write_end()?;
|
||||
|
||||
Ok(())
|
||||
}
|
142
examples/mp4dump.rs
Normal file
142
examples/mp4dump.rs
Normal file
|
@ -0,0 +1,142 @@
|
|||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
use mp4::{Mp4Box, Result};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() < 2 {
|
||||
println!("Usage: mp4dump <filename>");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if let Err(err) = dump(&args[1]) {
|
||||
let _ = writeln!(io::stderr(), "{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn dump<P: AsRef<Path>>(filename: &P) -> Result<()> {
|
||||
let f = File::open(filename)?;
|
||||
let boxes = get_boxes(f)?;
|
||||
|
||||
// print out boxes
|
||||
for b in boxes.iter() {
|
||||
println!("[{}] size={} {}", b.name, b.size, b.summary);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub struct Box {
|
||||
name: String,
|
||||
size: u64,
|
||||
summary: String,
|
||||
indent: u32,
|
||||
}
|
||||
|
||||
fn get_boxes(file: File) -> Result<Vec<Box>> {
|
||||
let size = file.metadata()?.len();
|
||||
let reader = BufReader::new(file);
|
||||
let mp4 = mp4::Mp4Reader::read_header(reader, size)?;
|
||||
|
||||
// collect known boxes
|
||||
let mut boxes = vec![
|
||||
build_box(&mp4.ftyp),
|
||||
build_box(&mp4.moov),
|
||||
build_box(&mp4.moov.mvhd),
|
||||
];
|
||||
|
||||
if let Some(ref mvex) = &mp4.moov.mvex {
|
||||
boxes.push(build_box(mvex));
|
||||
if let Some(mehd) = &mvex.mehd {
|
||||
boxes.push(build_box(mehd));
|
||||
}
|
||||
boxes.push(build_box(&mvex.trex));
|
||||
}
|
||||
|
||||
// trak.
|
||||
for track in mp4.tracks().values() {
|
||||
boxes.push(build_box(&track.trak));
|
||||
boxes.push(build_box(&track.trak.tkhd));
|
||||
if let Some(ref edts) = track.trak.edts {
|
||||
boxes.push(build_box(edts));
|
||||
if let Some(ref elst) = edts.elst {
|
||||
boxes.push(build_box(elst));
|
||||
}
|
||||
}
|
||||
|
||||
// trak.mdia
|
||||
let mdia = &track.trak.mdia;
|
||||
boxes.push(build_box(mdia));
|
||||
boxes.push(build_box(&mdia.mdhd));
|
||||
boxes.push(build_box(&mdia.hdlr));
|
||||
boxes.push(build_box(&track.trak.mdia.minf));
|
||||
|
||||
// trak.mdia.minf
|
||||
let minf = &track.trak.mdia.minf;
|
||||
if let Some(ref vmhd) = &minf.vmhd {
|
||||
boxes.push(build_box(vmhd));
|
||||
}
|
||||
if let Some(ref smhd) = &minf.smhd {
|
||||
boxes.push(build_box(smhd));
|
||||
}
|
||||
|
||||
// trak.mdia.minf.stbl
|
||||
let stbl = &track.trak.mdia.minf.stbl;
|
||||
boxes.push(build_box(stbl));
|
||||
boxes.push(build_box(&stbl.stsd));
|
||||
if let Some(ref avc1) = &stbl.stsd.avc1 {
|
||||
boxes.push(build_box(avc1));
|
||||
}
|
||||
if let Some(ref hev1) = &stbl.stsd.hev1 {
|
||||
boxes.push(build_box(hev1));
|
||||
}
|
||||
if let Some(ref mp4a) = &stbl.stsd.mp4a {
|
||||
boxes.push(build_box(mp4a));
|
||||
}
|
||||
boxes.push(build_box(&stbl.stts));
|
||||
if let Some(ref ctts) = &stbl.ctts {
|
||||
boxes.push(build_box(ctts));
|
||||
}
|
||||
if let Some(ref stss) = &stbl.stss {
|
||||
boxes.push(build_box(stss));
|
||||
}
|
||||
boxes.push(build_box(&stbl.stsc));
|
||||
boxes.push(build_box(&stbl.stsz));
|
||||
if let Some(ref stco) = &stbl.stco {
|
||||
boxes.push(build_box(stco));
|
||||
}
|
||||
if let Some(ref co64) = &stbl.co64 {
|
||||
boxes.push(build_box(co64));
|
||||
}
|
||||
}
|
||||
|
||||
// If fragmented, add moof boxes.
|
||||
for moof in mp4.moofs.iter() {
|
||||
boxes.push(build_box(moof));
|
||||
boxes.push(build_box(&moof.mfhd));
|
||||
for traf in moof.trafs.iter() {
|
||||
boxes.push(build_box(traf));
|
||||
boxes.push(build_box(&traf.tfhd));
|
||||
if let Some(ref trun) = &traf.trun {
|
||||
boxes.push(build_box(trun));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(boxes)
|
||||
}
|
||||
|
||||
fn build_box<M: Mp4Box + std::fmt::Debug>(m: &M) -> Box {
|
||||
Box {
|
||||
name: m.box_type().to_string(),
|
||||
size: m.box_size(),
|
||||
summary: m.summary().unwrap(),
|
||||
indent: 0,
|
||||
}
|
||||
}
|
|
@ -1,114 +1,139 @@
|
|||
use mp4;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
use mp4::{Error, Mp4Track, Result, TrackType};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
match args.len() {
|
||||
2 => {
|
||||
let filename = &args[1];
|
||||
let f = File::open(filename).unwrap();
|
||||
if args.len() < 2 {
|
||||
println!("Usage: mp4info <filename>");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let bmff = mp4::read_mp4(f).unwrap();
|
||||
let moov = bmff.moov.unwrap();
|
||||
|
||||
// Print results.
|
||||
println!("File:");
|
||||
println!(" file size: {}", bmff.size);
|
||||
println!(
|
||||
" brands: {:?} {:?}\n",
|
||||
bmff.ftyp.major_brand, bmff.ftyp.compatible_brands
|
||||
);
|
||||
|
||||
println!("Movie:");
|
||||
println!(" version: {:?}", moov.mvhd.version);
|
||||
println!(
|
||||
" creation time: {}",
|
||||
creation_time(moov.mvhd.creation_time)
|
||||
);
|
||||
println!(" duration: {:?}", moov.mvhd.duration);
|
||||
println!(" timescale: {:?}\n", moov.mvhd.timescale);
|
||||
|
||||
println!("Found {} Tracks", moov.traks.len());
|
||||
for trak in moov.traks.iter() {
|
||||
let tkhd = trak.tkhd.as_ref().unwrap();
|
||||
println!("Track: {:?}", tkhd.track_id);
|
||||
println!(" flags: {:?}", tkhd.flags);
|
||||
println!(" id: {:?}", tkhd.track_id);
|
||||
println!(" duration: {:?}", tkhd.duration);
|
||||
if tkhd.width != 0 && tkhd.height != 0 {
|
||||
println!(" width: {:?}", tkhd.width);
|
||||
println!(" height: {:?}", tkhd.height);
|
||||
}
|
||||
if let Some(ref mdia) = trak.mdia {
|
||||
let hdlr = mdia.hdlr.as_ref().unwrap();
|
||||
let mdhd = mdia.mdhd.as_ref().unwrap();
|
||||
let stts = mdia
|
||||
.minf
|
||||
.as_ref()
|
||||
.map(|m| m.stbl.as_ref().map(|s| s.stts.as_ref()).flatten())
|
||||
.flatten();
|
||||
|
||||
println!(
|
||||
" type: {:?}",
|
||||
get_handler_type(hdlr.handler_type.value.as_ref())
|
||||
);
|
||||
println!(" language: {:?}", mdhd.language_string);
|
||||
|
||||
println!(" media:");
|
||||
if let Some(ref s) = stts {
|
||||
println!(" sample count: {:?}", s.sample_counts[0]);
|
||||
}
|
||||
println!(" timescale: {:?}", mdhd.timescale);
|
||||
println!(
|
||||
" duration: {:?} (media timescale units)",
|
||||
mdhd.duration
|
||||
);
|
||||
println!(
|
||||
" duration: {:?} (ms)",
|
||||
get_duration_ms(mdhd.duration, mdhd.timescale)
|
||||
);
|
||||
if get_handler_type(hdlr.handler_type.value.as_ref()) == mp4::TrackType::Video {
|
||||
if let Some(ref s) = stts {
|
||||
println!(
|
||||
" frame rate: (computed): {:?}",
|
||||
get_framerate(&s.sample_counts, mdhd.duration, mdhd.timescale)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
println!("Usage: mp4info <filename>");
|
||||
}
|
||||
if let Err(err) = info(&args[1]) {
|
||||
let _ = writeln!(io::stderr(), "{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_handler_type(handler: &str) -> mp4::TrackType {
|
||||
let mut typ: mp4::TrackType = mp4::TrackType::Unknown;
|
||||
match handler {
|
||||
"vide" => typ = mp4::TrackType::Video,
|
||||
"soun" => typ = mp4::TrackType::Audio,
|
||||
"meta" => typ = mp4::TrackType::Unknown,
|
||||
_ => (),
|
||||
fn info<P: AsRef<Path>>(filename: &P) -> Result<()> {
|
||||
let f = File::open(filename)?;
|
||||
let size = f.metadata()?.len();
|
||||
let reader = BufReader::new(f);
|
||||
|
||||
let mp4 = mp4::Mp4Reader::read_header(reader, size)?;
|
||||
|
||||
println!("File:");
|
||||
println!(" file size: {}", mp4.size());
|
||||
println!(" major_brand: {}", mp4.major_brand());
|
||||
let mut compatible_brands = String::new();
|
||||
for brand in mp4.compatible_brands().iter() {
|
||||
compatible_brands.push_str(&brand.to_string());
|
||||
compatible_brands.push(' ');
|
||||
}
|
||||
return typ;
|
||||
println!(" compatible_brands: {}\n", compatible_brands);
|
||||
|
||||
println!("Movie:");
|
||||
println!(" version: {}", mp4.moov.mvhd.version);
|
||||
println!(
|
||||
" creation time: {}",
|
||||
creation_time(mp4.moov.mvhd.creation_time)
|
||||
);
|
||||
println!(" duration: {:?}", mp4.duration());
|
||||
println!(" fragments: {:?}", mp4.is_fragmented());
|
||||
println!(" timescale: {:?}\n", mp4.timescale());
|
||||
|
||||
println!("Found {} Tracks", mp4.tracks().len());
|
||||
for track in mp4.tracks().values() {
|
||||
let media_info = match track.track_type()? {
|
||||
TrackType::Video => video_info(track),
|
||||
TrackType::Audio => audio_info(track),
|
||||
TrackType::Subtitle => subtitle_info(track),
|
||||
};
|
||||
|
||||
println!(
|
||||
" Track: #{}({}) {}: {}",
|
||||
track.track_id(),
|
||||
track.language(),
|
||||
track.track_type()?,
|
||||
media_info.unwrap_or_else(|e| e.to_string())
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_duration_ms(duration: u32, timescale: u32) -> String {
|
||||
let ms = (duration as f64 / timescale as f64) * 1000.0;
|
||||
return format!("{:.2}", ms.floor());
|
||||
fn video_info(track: &Mp4Track) -> Result<String> {
|
||||
if track.trak.mdia.minf.stbl.stsd.avc1.is_some() {
|
||||
Ok(format!(
|
||||
"{} ({}) ({:?}), {}x{}, {} kb/s, {:.2} fps",
|
||||
track.media_type()?,
|
||||
track.video_profile()?,
|
||||
track.box_type()?,
|
||||
track.width(),
|
||||
track.height(),
|
||||
track.bitrate() / 1000,
|
||||
track.frame_rate()
|
||||
))
|
||||
} else {
|
||||
Ok(format!(
|
||||
"{} ({:?}), {}x{}, {} kb/s, {:.2} fps",
|
||||
track.media_type()?,
|
||||
track.box_type()?,
|
||||
track.width(),
|
||||
track.height(),
|
||||
track.bitrate() / 1000,
|
||||
track.frame_rate()
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_framerate(sample_counts: &Vec<u32>, duration: u32, timescale: u32) -> String {
|
||||
let sc = (sample_counts[0] as f64) * 1000.0;
|
||||
let ms = (duration as f64 / timescale as f64) * 1000.0;
|
||||
return format!("{:.2}", sc / ms.floor());
|
||||
fn audio_info(track: &Mp4Track) -> Result<String> {
|
||||
if let Some(ref mp4a) = track.trak.mdia.minf.stbl.stsd.mp4a {
|
||||
if mp4a.esds.is_some() {
|
||||
let profile = match track.audio_profile() {
|
||||
Ok(val) => val.to_string(),
|
||||
_ => "-".to_string(),
|
||||
};
|
||||
|
||||
let channel_config = match track.channel_config() {
|
||||
Ok(val) => val.to_string(),
|
||||
_ => "-".to_string(),
|
||||
};
|
||||
|
||||
Ok(format!(
|
||||
"{} ({}) ({:?}), {} Hz, {}, {} kb/s",
|
||||
track.media_type()?,
|
||||
profile,
|
||||
track.box_type()?,
|
||||
track.sample_freq_index()?.freq(),
|
||||
channel_config,
|
||||
track.bitrate() / 1000
|
||||
))
|
||||
} else {
|
||||
Ok(format!(
|
||||
"{} ({:?}), {} kb/s",
|
||||
track.media_type()?,
|
||||
track.box_type()?,
|
||||
track.bitrate() / 1000
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(Error::InvalidData("mp4a box not found"))
|
||||
}
|
||||
}
|
||||
|
||||
fn creation_time(creation_time: u32) -> u32 {
|
||||
fn subtitle_info(track: &Mp4Track) -> Result<String> {
|
||||
if track.trak.mdia.minf.stbl.stsd.tx3g.is_some() {
|
||||
Ok(format!("{} ({:?})", track.media_type()?, track.box_type()?,))
|
||||
} else {
|
||||
Err(Error::InvalidData("tx3g box not found"))
|
||||
}
|
||||
}
|
||||
|
||||
fn creation_time(creation_time: u64) -> u64 {
|
||||
// convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01)
|
||||
if creation_time >= 2082844800 {
|
||||
creation_time - 2082844800
|
||||
|
|
50
examples/mp4sample.rs
Normal file
50
examples/mp4sample.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
use std::io::{self, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
use mp4::Result;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() < 2 {
|
||||
println!("Usage: mp4sample <filename>");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if let Err(err) = samples(&args[1]) {
|
||||
let _ = writeln!(io::stderr(), "{}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn samples<P: AsRef<Path>>(filename: &P) -> Result<()> {
|
||||
let f = File::open(filename)?;
|
||||
let size = f.metadata()?.len();
|
||||
let reader = BufReader::new(f);
|
||||
|
||||
let mut mp4 = mp4::Mp4Reader::read_header(reader, size)?;
|
||||
|
||||
for track_id in mp4.tracks().keys().copied().collect::<Vec<u32>>() {
|
||||
let sample_count = mp4.sample_count(track_id).unwrap();
|
||||
|
||||
for sample_idx in 0..sample_count {
|
||||
let sample_id = sample_idx + 1;
|
||||
let sample = mp4.read_sample(track_id, sample_id);
|
||||
|
||||
if let Some(ref samp) = sample.unwrap() {
|
||||
println!(
|
||||
"[{}] start_time={} duration={} rendering_offset={} size={} is_sync={}",
|
||||
sample_id,
|
||||
samp.start_time,
|
||||
samp.duration,
|
||||
samp.rendering_offset,
|
||||
samp.bytes.len(),
|
||||
samp.is_sync,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
24
examples/mp4writer.rs
Normal file
24
examples/mp4writer.rs
Normal file
|
@ -0,0 +1,24 @@
|
|||
use mp4::{Mp4Config, Mp4Writer};
|
||||
use std::io::Cursor;
|
||||
|
||||
fn main() -> mp4::Result<()> {
|
||||
let config = Mp4Config {
|
||||
major_brand: str::parse("isom").unwrap(),
|
||||
minor_version: 512,
|
||||
compatible_brands: vec![
|
||||
str::parse("isom").unwrap(),
|
||||
str::parse("iso2").unwrap(),
|
||||
str::parse("avc1").unwrap(),
|
||||
str::parse("mp41").unwrap(),
|
||||
],
|
||||
timescale: 1000,
|
||||
};
|
||||
|
||||
let data = Cursor::new(Vec::<u8>::new());
|
||||
let mut writer = Mp4Writer::write_start(data, &config)?;
|
||||
writer.write_end()?;
|
||||
|
||||
let data: Vec<u8> = writer.into_writer().into_inner();
|
||||
println!("{:?}", data);
|
||||
Ok(())
|
||||
}
|
2
examples/mpeg_aac_decoder/.gitignore
vendored
Normal file
2
examples/mpeg_aac_decoder/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/Cargo.lock
|
||||
/target
|
9
examples/mpeg_aac_decoder/Cargo.toml
Normal file
9
examples/mpeg_aac_decoder/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "mpeg_aac_decoder"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
mp4 = "0.8.1"
|
||||
fdk-aac = "0.4.0"
|
||||
rodio = { version = "0.13.0", default-features = false }
|
BIN
examples/mpeg_aac_decoder/audio_aac.m4a
Normal file
BIN
examples/mpeg_aac_decoder/audio_aac.m4a
Normal file
Binary file not shown.
237
examples/mpeg_aac_decoder/src/main.rs
Normal file
237
examples/mpeg_aac_decoder/src/main.rs
Normal file
|
@ -0,0 +1,237 @@
|
|||
use fdk_aac::dec::{Decoder, DecoderError, Transport};
|
||||
use rodio::{OutputStream, Sink, Source};
|
||||
use std::fs::File;
|
||||
use std::io::{BufReader, Read, Seek};
|
||||
use std::ops::Range;
|
||||
use std::time::Duration;
|
||||
|
||||
fn main() {
|
||||
let path = "audio_aac.m4a";
|
||||
let file = File::open(path).expect("Error opening file");
|
||||
|
||||
let metadata = file.metadata().expect("Error getting file metadata");
|
||||
let size = metadata.len();
|
||||
let buf = BufReader::new(file);
|
||||
|
||||
let decoder = MpegAacDecoder::new(buf, size).expect("Error creating decoder");
|
||||
|
||||
let output_stream = OutputStream::try_default();
|
||||
let (_stream, handle) = output_stream.expect("Error creating output stream");
|
||||
let sink = Sink::try_new(&handle).expect("Error creating sink");
|
||||
|
||||
sink.append(decoder);
|
||||
sink.play();
|
||||
sink.set_volume(0.5);
|
||||
sink.sleep_until_end();
|
||||
}
|
||||
|
||||
pub struct MpegAacDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
mp4_reader: mp4::Mp4Reader<R>,
|
||||
decoder: Decoder,
|
||||
current_pcm_index: usize,
|
||||
current_pcm: Vec<i16>,
|
||||
track_id: u32,
|
||||
position: u32,
|
||||
}
|
||||
|
||||
impl<R> MpegAacDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
pub fn new(reader: R, size: u64) -> Result<MpegAacDecoder<R>, &'static str> {
|
||||
let decoder = Decoder::new(Transport::Adts);
|
||||
let mp4 = mp4::Mp4Reader::read_header(reader, size).or(Err("Error reading MPEG header"))?;
|
||||
let mut track_id: Option<u32> = None;
|
||||
{
|
||||
for track in mp4.tracks().iter() {
|
||||
let media_type = track.media_type().or(Err("Error getting media type"))?;
|
||||
match media_type {
|
||||
mp4::MediaType::AAC => {
|
||||
track_id = Some(track.track_id());
|
||||
break;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
match track_id {
|
||||
Some(track_id) => {
|
||||
return Ok(MpegAacDecoder {
|
||||
mp4_reader: mp4,
|
||||
decoder: decoder,
|
||||
current_pcm_index: 0,
|
||||
current_pcm: Vec::new(),
|
||||
track_id: track_id,
|
||||
position: 1,
|
||||
});
|
||||
}
|
||||
None => {
|
||||
return Err("No aac track found");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Iterator for MpegAacDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
type Item = i16;
|
||||
fn next(&mut self) -> Option<i16> {
|
||||
if self.current_pcm_index == self.current_pcm.len() {
|
||||
let mut pcm = vec![0; 8192];
|
||||
let result = match self.decoder.decode_frame(&mut self.current_pcm) {
|
||||
Err(DecoderError::NOT_ENOUGH_BITS) => {
|
||||
let sample_result = self.mp4_reader.read_sample(self.track_id, self.position);
|
||||
let sample = sample_result.expect("Error reading sample")?;
|
||||
let tracks = self.mp4_reader.tracks();
|
||||
let track = tracks.get(self.track_id as usize - 1).expect("No track ID");
|
||||
let adts_header = construct_adts_header(track, &sample).expect("ADTS bytes");
|
||||
let adts_bytes = mp4::Bytes::copy_from_slice(&adts_header);
|
||||
let bytes = [adts_bytes, sample.bytes].concat();
|
||||
self.position += 1;
|
||||
let _bytes_read = match self.decoder.fill(&bytes) {
|
||||
Ok(bytes_read) => bytes_read,
|
||||
Err(_) => return None,
|
||||
};
|
||||
self.decoder.decode_frame(&mut pcm)
|
||||
}
|
||||
val => val,
|
||||
};
|
||||
if let Err(err) = result {
|
||||
println!("DecoderError: {}", err);
|
||||
return None;
|
||||
}
|
||||
let decoded_fram_size = self.decoder.decoded_frame_size();
|
||||
if decoded_fram_size < pcm.len() {
|
||||
let _ = pcm.split_off(decoded_fram_size);
|
||||
}
|
||||
self.current_pcm = pcm;
|
||||
self.current_pcm_index = 0;
|
||||
}
|
||||
let value = self.current_pcm[self.current_pcm_index];
|
||||
self.current_pcm_index += 1;
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Source for MpegAacDecoder<R>
|
||||
where
|
||||
R: Read + Seek,
|
||||
{
|
||||
fn current_frame_len(&self) -> Option<usize> {
|
||||
let frame_size: usize = self.decoder.decoded_frame_size();
|
||||
Some(frame_size)
|
||||
}
|
||||
fn channels(&self) -> u16 {
|
||||
let num_channels: i32 = self.decoder.stream_info().numChannels;
|
||||
num_channels as _
|
||||
}
|
||||
fn sample_rate(&self) -> u32 {
|
||||
let sample_rate: i32 = self.decoder.stream_info().sampleRate;
|
||||
sample_rate as _
|
||||
}
|
||||
fn total_duration(&self) -> Option<Duration> {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
fn get_bits(byte: u16, range: Range<u16>) -> u16 {
|
||||
let shaved_left = byte << range.start - 1;
|
||||
let moved_back = shaved_left >> range.start - 1;
|
||||
let shave_right = moved_back >> 16 - range.end;
|
||||
return shave_right;
|
||||
}
|
||||
|
||||
fn get_bits_u8(byte: u8, range: Range<u8>) -> u8 {
|
||||
let shaved_left = byte << range.start - 1;
|
||||
let moved_back = shaved_left >> range.start - 1;
|
||||
let shave_right = moved_back >> 8 - range.end;
|
||||
return shave_right;
|
||||
}
|
||||
|
||||
pub fn construct_adts_header(track: &mp4::Mp4Track, sample: &mp4::Mp4Sample) -> Option<Vec<u8>> {
|
||||
// B: Only support 0 (MPEG-4)
|
||||
// D: Only support 1 (without CRC)
|
||||
// byte7 and byte9 not included without CRC
|
||||
let adts_header_length = 7;
|
||||
|
||||
// AAAA_AAAA
|
||||
let byte0 = 0b1111_1111;
|
||||
|
||||
// AAAA_BCCD
|
||||
let byte1 = 0b1111_0001;
|
||||
|
||||
// EEFF_FFGH
|
||||
let mut byte2 = 0b0000_0000;
|
||||
let object_type = match track.audio_profile() {
|
||||
Ok(mp4::AudioObjectType::AacMain) => 1,
|
||||
Ok(mp4::AudioObjectType::AacLowComplexity) => 2,
|
||||
Ok(mp4::AudioObjectType::AacScalableSampleRate) => 3,
|
||||
Ok(mp4::AudioObjectType::AacLongTermPrediction) => 4,
|
||||
Err(_) => return None,
|
||||
};
|
||||
let adts_object_type = object_type - 1;
|
||||
byte2 = (byte2 << 2) | adts_object_type; // EE
|
||||
|
||||
let sample_freq_index = match track.sample_freq_index() {
|
||||
Ok(mp4::SampleFreqIndex::Freq96000) => 0,
|
||||
Ok(mp4::SampleFreqIndex::Freq88200) => 1,
|
||||
Ok(mp4::SampleFreqIndex::Freq64000) => 2,
|
||||
Ok(mp4::SampleFreqIndex::Freq48000) => 3,
|
||||
Ok(mp4::SampleFreqIndex::Freq44100) => 4,
|
||||
Ok(mp4::SampleFreqIndex::Freq32000) => 5,
|
||||
Ok(mp4::SampleFreqIndex::Freq24000) => 6,
|
||||
Ok(mp4::SampleFreqIndex::Freq22050) => 7,
|
||||
Ok(mp4::SampleFreqIndex::Freq16000) => 8,
|
||||
Ok(mp4::SampleFreqIndex::Freq12000) => 9,
|
||||
Ok(mp4::SampleFreqIndex::Freq11025) => 10,
|
||||
Ok(mp4::SampleFreqIndex::Freq8000) => 11,
|
||||
Ok(mp4::SampleFreqIndex::Freq7350) => 12,
|
||||
// 13-14 = reserved
|
||||
// 15 = explicit frequency (forbidden in adts)
|
||||
Err(_) => return None,
|
||||
};
|
||||
byte2 = (byte2 << 4) | sample_freq_index; // FFFF
|
||||
byte2 = (byte2 << 1) | 0b1; // G
|
||||
|
||||
let channel_config = match track.channel_config() {
|
||||
// 0 = for when channel config is sent via an inband PCE
|
||||
Ok(mp4::ChannelConfig::Mono) => 1,
|
||||
Ok(mp4::ChannelConfig::Stereo) => 2,
|
||||
Ok(mp4::ChannelConfig::Three) => 3,
|
||||
Ok(mp4::ChannelConfig::Four) => 4,
|
||||
Ok(mp4::ChannelConfig::Five) => 5,
|
||||
Ok(mp4::ChannelConfig::FiveOne) => 6,
|
||||
Ok(mp4::ChannelConfig::SevenOne) => 7,
|
||||
// 8-15 = reserved
|
||||
Err(_) => return None,
|
||||
};
|
||||
byte2 = (byte2 << 1) | get_bits_u8(channel_config, 6..6); // H
|
||||
|
||||
// HHIJ_KLMM
|
||||
let mut byte3 = 0b0000_0000;
|
||||
byte3 = (byte3 << 2) | get_bits_u8(channel_config, 7..8); // HH
|
||||
byte3 = (byte3 << 4) | 0b1111; // IJKL
|
||||
|
||||
let frame_length = adts_header_length + sample.bytes.len() as u16;
|
||||
byte3 = (byte3 << 2) | get_bits(frame_length, 3..5) as u8; // MM
|
||||
|
||||
// MMMM_MMMM
|
||||
let byte4 = get_bits(frame_length, 6..13) as u8;
|
||||
|
||||
// MMMO_OOOO
|
||||
let mut byte5 = 0b0000_0000;
|
||||
byte5 = (byte5 << 3) | get_bits(frame_length, 14..16) as u8;
|
||||
byte5 = (byte5 << 5) | 0b11111; // OOOOO
|
||||
|
||||
// OOOO_OOPP
|
||||
let mut byte6 = 0b0000_0000;
|
||||
byte6 = (byte6 << 6) | 0b111111; // OOOOOO
|
||||
byte6 = (byte6 << 2) | 0b00; // PP
|
||||
|
||||
return Some(vec![byte0, byte1, byte2, byte3, byte4, byte5, byte6]);
|
||||
}
|
27
examples/simple.rs
Normal file
27
examples/simple.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
use std::env;
|
||||
use std::fs::File;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
||||
if args.len() < 2 {
|
||||
println!("Usage: simple <filename>");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let filename = &args[1];
|
||||
let f = File::open(filename).unwrap();
|
||||
let mp4 = mp4::read_mp4(f).unwrap();
|
||||
|
||||
println!("Major Brand: {}", mp4.major_brand());
|
||||
|
||||
for track in mp4.tracks().values() {
|
||||
println!(
|
||||
"Track: #{}({}) {} {}",
|
||||
track.track_id(),
|
||||
track.language(),
|
||||
track.track_type().unwrap(),
|
||||
track.box_type().unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
767
src/atoms.rs
767
src/atoms.rs
|
@ -1,767 +0,0 @@
|
|||
use std::fmt;
|
||||
use std::io::{BufReader, SeekFrom, Seek, Read};
|
||||
use byteorder::{BigEndian, ReadBytesExt};
|
||||
use std::char::{decode_utf16, REPLACEMENT_CHARACTER};
|
||||
use crate::{Error, read_box_header, BoxHeader, HEADER_SIZE};
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
macro_rules! boxtype {
|
||||
($( $name:ident => $value:expr ),*) => {
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum BoxType {
|
||||
$( $name, )*
|
||||
UnknownBox(u32),
|
||||
}
|
||||
|
||||
impl From<u32> for BoxType {
|
||||
fn from(t: u32) -> BoxType {
|
||||
match t {
|
||||
$( $value => BoxType::$name, )*
|
||||
_ => BoxType::UnknownBox(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<u32> for BoxType {
|
||||
fn into(self) -> u32 {
|
||||
match self {
|
||||
$( BoxType::$name => $value, )*
|
||||
BoxType::UnknownBox(t) => t,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! read_box_header_ext {
|
||||
($r:ident, $v:ident, $ f:ident) => {
|
||||
let $v = $r.read_u8().unwrap();
|
||||
let flags_a = $r.read_u8().unwrap();
|
||||
let flags_b = $r.read_u8().unwrap();
|
||||
let flags_c = $r.read_u8().unwrap();
|
||||
let $f = u32::from(flags_a) << 16 | u32::from(flags_b) << 8 | u32::from(flags_c);
|
||||
}
|
||||
}
|
||||
|
||||
boxtype!{
|
||||
FtypBox => 0x66747970,
|
||||
MvhdBox => 0x6d766864,
|
||||
FreeBox => 0x66726565,
|
||||
MdatBox => 0x6d646174,
|
||||
MoovBox => 0x6d6f6f76,
|
||||
MoofBox => 0x6d6f6f66,
|
||||
TkhdBox => 0x746b6864,
|
||||
EdtsBox => 0x65647473,
|
||||
MdiaBox => 0x6d646961,
|
||||
ElstBox => 0x656c7374,
|
||||
MdhdBox => 0x6d646864,
|
||||
HdlrBox => 0x68646c72,
|
||||
MinfBox => 0x6d696e66,
|
||||
VmhdBox => 0x766d6864,
|
||||
StblBox => 0x7374626c,
|
||||
StsdBox => 0x73747364,
|
||||
SttsBox => 0x73747473,
|
||||
TrakBox => 0x7472616b,
|
||||
UdtaBox => 0x75647461,
|
||||
DinfBox => 0x64696e66,
|
||||
SmhdBox => 0x736d6864,
|
||||
Avc1Box => 0x61766331,
|
||||
Mp4aBox => 0x6d703461
|
||||
}
|
||||
|
||||
impl fmt::Debug for BoxType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let fourcc: FourCC = From::from(self.clone());
|
||||
write!(f, "{}", fourcc)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone)]
|
||||
pub struct FourCC {
|
||||
pub value: String
|
||||
}
|
||||
|
||||
impl From<u32> for FourCC {
|
||||
fn from(number: u32) -> FourCC {
|
||||
let mut box_chars = Vec::new();
|
||||
for x in 0..4 {
|
||||
let c = (number >> (x * 8) & 0x0000_00FF) as u8;
|
||||
box_chars.push(c);
|
||||
}
|
||||
box_chars.reverse();
|
||||
|
||||
let box_string = match String::from_utf8(box_chars) {
|
||||
Ok(t) => t,
|
||||
_ => String::from("null"), // error to retrieve fourcc
|
||||
};
|
||||
|
||||
FourCC {
|
||||
value: box_string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BoxType> for FourCC {
|
||||
fn from(t: BoxType) -> FourCC {
|
||||
let box_num: u32 = Into::into(t);
|
||||
From::from(box_num)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FourCC {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FourCC {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ReadBox<T>: Sized {
|
||||
fn read_box(_: T, size: u32) -> Result<Self>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct FtypBox {
|
||||
pub major_brand: FourCC,
|
||||
pub minor_version: u32,
|
||||
pub compatible_brands: Vec<FourCC>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MoovBox {
|
||||
pub mvhd: MvhdBox,
|
||||
pub traks: Vec<TrakBox>,
|
||||
}
|
||||
|
||||
impl MoovBox {
|
||||
pub(crate) fn new() -> MoovBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MvhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub creation_time: u32,
|
||||
pub modification_time: u32,
|
||||
pub timescale: u32,
|
||||
pub duration: u32,
|
||||
pub rate: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TrakBox {
|
||||
pub tkhd: Option<TkhdBox>,
|
||||
pub edts: Option<EdtsBox>,
|
||||
pub mdia: Option<MdiaBox>,
|
||||
}
|
||||
|
||||
impl TrakBox {
|
||||
pub(crate) fn new() -> TrakBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TkhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub creation_time: u32,
|
||||
pub modification_time: u32,
|
||||
pub track_id: u32,
|
||||
pub duration: u64,
|
||||
pub layer: u16,
|
||||
pub alternate_group: u16,
|
||||
pub volume: u16,
|
||||
pub matrix: Matrix,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Matrix {
|
||||
pub a: i32,
|
||||
pub b: i32,
|
||||
pub u: i32,
|
||||
pub c: i32,
|
||||
pub d: i32,
|
||||
pub v: i32,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub w: i32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct EdtsBox {
|
||||
pub elst: Option<ElstBox>,
|
||||
}
|
||||
|
||||
impl EdtsBox {
|
||||
pub(crate) fn new() -> EdtsBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ElstBox {
|
||||
pub version: u32,
|
||||
pub entry_count: u32,
|
||||
pub entries: Vec<ElstEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ElstEntry {
|
||||
pub segment_duration: u32,
|
||||
pub media_time: u32,
|
||||
pub media_rate: u16,
|
||||
pub media_rate_fraction: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MdiaBox {
|
||||
pub mdhd: Option<MdhdBox>,
|
||||
pub hdlr: Option<HdlrBox>,
|
||||
pub minf: Option<MinfBox>,
|
||||
}
|
||||
|
||||
impl MdiaBox {
|
||||
pub(crate) fn new() -> MdiaBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MdhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub creation_time: u32,
|
||||
pub modification_time: u32,
|
||||
pub timescale: u32,
|
||||
pub duration: u32,
|
||||
pub language: u16,
|
||||
pub language_string: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct HdlrBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub handler_type: FourCC,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MinfBox {
|
||||
pub vmhd: Option<VmhdBox>,
|
||||
pub stbl: Option<StblBox>,
|
||||
}
|
||||
|
||||
impl MinfBox {
|
||||
pub(crate) fn new() -> MinfBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct VmhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub graphics_mode: u16,
|
||||
pub op_color: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct StblBox {
|
||||
pub stts: Option<SttsBox>,
|
||||
pub stsd: Option<StsdBox>,
|
||||
}
|
||||
|
||||
impl StblBox {
|
||||
pub(crate) fn new() -> StblBox {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SttsBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub entry_count: u32,
|
||||
pub sample_counts: Vec<u32>,
|
||||
pub sample_deltas: Vec<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct StsdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for FtypBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let major = reader.read_u32::<BigEndian>().unwrap();
|
||||
let minor = reader.read_u32::<BigEndian>().unwrap();
|
||||
if size % 4 != 0 {
|
||||
return Err(Error::InvalidData("invalid ftyp size"));
|
||||
}
|
||||
let brand_count = (size - 16) / 4; // header + major + minor
|
||||
|
||||
let mut brands = Vec::new();
|
||||
for _ in 0..brand_count {
|
||||
let b = reader.read_u32::<BigEndian>().unwrap();
|
||||
brands.push(From::from(b));
|
||||
}
|
||||
|
||||
Ok(FtypBox {
|
||||
major_brand: From::from(major),
|
||||
minor_version: minor,
|
||||
compatible_brands: brands,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MoovBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let mut moov = MoovBox::new();
|
||||
|
||||
let mut start = 0u64;
|
||||
while start < size as u64 {
|
||||
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start).unwrap();
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::MvhdBox => {
|
||||
moov.mvhd = MvhdBox::read_box(reader, s as u32).unwrap();
|
||||
}
|
||||
BoxType::TrakBox => {
|
||||
let trak = TrakBox::read_box(reader, s as u32).unwrap();
|
||||
moov.traks.push(trak);
|
||||
}
|
||||
BoxType::UdtaBox => {
|
||||
start = (s as u32 - HEADER_SIZE) as u64;
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
Ok(moov)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MvhdBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
|
||||
read_box_header_ext!(reader, version, flags);
|
||||
|
||||
let creation_time = reader.read_u32::<BigEndian>().unwrap();
|
||||
let modification_time = reader.read_u32::<BigEndian>().unwrap();
|
||||
let timescale = reader.read_u32::<BigEndian>().unwrap();
|
||||
let duration = reader.read_u32::<BigEndian>().unwrap();
|
||||
let rate = reader.read_u32::<BigEndian>().unwrap();
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(MvhdBox{
|
||||
version,
|
||||
flags,
|
||||
creation_time,
|
||||
modification_time,
|
||||
timescale,
|
||||
duration,
|
||||
rate,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for TrakBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
let mut trak = TrakBox::new();
|
||||
|
||||
let start = 0u64;
|
||||
while start < size as u64 {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start).unwrap();
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::TkhdBox => {
|
||||
let tkhd = TkhdBox::read_box(reader, s as u32).unwrap();
|
||||
trak.tkhd = Some(tkhd);
|
||||
}
|
||||
BoxType::EdtsBox => {
|
||||
let edts = EdtsBox::read_box(reader, s as u32).unwrap();
|
||||
trak.edts = Some(edts);
|
||||
}
|
||||
BoxType::MdiaBox => {
|
||||
let mdia = MdiaBox::read_box(reader, s as u32).unwrap();
|
||||
trak.mdia = Some(mdia);
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(trak)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for TkhdBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
|
||||
read_box_header_ext!(reader, version, flags);
|
||||
|
||||
let creation_time = reader.read_u32::<BigEndian>().unwrap();
|
||||
let modification_time = reader.read_u32::<BigEndian>().unwrap();
|
||||
let track_id = reader.read_u32::<BigEndian>().unwrap();
|
||||
let duration = reader.read_u64::<BigEndian>().unwrap();
|
||||
reader.read_u64::<BigEndian>().unwrap(); // skip.
|
||||
let layer = reader.read_u16::<BigEndian>().unwrap();
|
||||
let alternate_group = reader.read_u16::<BigEndian>().unwrap();
|
||||
let volume = reader.read_u16::<BigEndian>().unwrap() >> 8;
|
||||
|
||||
reader.read_u8().unwrap(); // skip.
|
||||
let matrix = Matrix{
|
||||
a: reader.read_i32::<byteorder::LittleEndian>().unwrap(),
|
||||
b: reader.read_i32::<BigEndian>().unwrap(),
|
||||
u: reader.read_i32::<BigEndian>().unwrap(),
|
||||
c: reader.read_i32::<BigEndian>().unwrap(),
|
||||
d: reader.read_i32::<BigEndian>().unwrap(),
|
||||
v: reader.read_i32::<BigEndian>().unwrap(),
|
||||
x: reader.read_i32::<BigEndian>().unwrap(),
|
||||
y: reader.read_i32::<BigEndian>().unwrap(),
|
||||
w: reader.read_i32::<BigEndian>().unwrap(),
|
||||
};
|
||||
|
||||
let width = reader.read_u32::<BigEndian>().unwrap() >> 8;
|
||||
let height = reader.read_u32::<BigEndian>().unwrap() >> 8;
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(TkhdBox {
|
||||
version,
|
||||
flags,
|
||||
creation_time,
|
||||
modification_time,
|
||||
track_id,
|
||||
duration,
|
||||
layer,
|
||||
alternate_group,
|
||||
volume,
|
||||
matrix,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for EdtsBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
let mut edts = EdtsBox::new();
|
||||
|
||||
let start = 0u64;
|
||||
while start < size as u64 {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start).unwrap();
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::ElstBox => {
|
||||
let elst = ElstBox::read_box(reader, s as u32).unwrap();
|
||||
edts.elst = Some(elst);
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(edts)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for ElstBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
|
||||
let version = reader.read_u32::<BigEndian>().unwrap();
|
||||
let entry_count = reader.read_u32::<BigEndian>().unwrap();
|
||||
|
||||
let mut entries = Vec::new();
|
||||
|
||||
for _i in 0..entry_count {
|
||||
let entry = ElstEntry{
|
||||
segment_duration: reader.read_u32::<BigEndian>().unwrap(),
|
||||
media_time: reader.read_u32::<BigEndian>().unwrap(),
|
||||
media_rate: reader.read_u16::<BigEndian>().unwrap(),
|
||||
media_rate_fraction: reader.read_u16::<BigEndian>().unwrap(),
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(ElstBox {
|
||||
version,
|
||||
entry_count,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MdiaBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
let mut mdia = MdiaBox::new();
|
||||
|
||||
let start = 0u64;
|
||||
while start < size as u64 {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start).unwrap();
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::MdhdBox => {
|
||||
let mdhd = MdhdBox::read_box(reader, s as u32).unwrap();
|
||||
mdia.mdhd = Some(mdhd);
|
||||
}
|
||||
BoxType::HdlrBox => {
|
||||
let hdlr = HdlrBox::read_box(reader, s as u32).unwrap();
|
||||
mdia.hdlr = Some(hdlr);
|
||||
}
|
||||
BoxType::MinfBox => {
|
||||
let minf = MinfBox::read_box(reader, s as u32).unwrap();
|
||||
mdia.minf = Some(minf);
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(mdia)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MdhdBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
|
||||
read_box_header_ext!(reader, version, flags);
|
||||
|
||||
let creation_time = reader.read_u32::<BigEndian>().unwrap();
|
||||
let modification_time = reader.read_u32::<BigEndian>().unwrap();
|
||||
let timescale = reader.read_u32::<BigEndian>().unwrap();
|
||||
let duration = reader.read_u32::<BigEndian>().unwrap();
|
||||
let language = reader.read_u16::<BigEndian>().unwrap();
|
||||
let language_string = get_language_string(language);
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(MdhdBox {
|
||||
version,
|
||||
flags,
|
||||
creation_time,
|
||||
modification_time,
|
||||
timescale,
|
||||
duration,
|
||||
language,
|
||||
language_string,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_language_string(language: u16) -> String {
|
||||
let mut lang: [u16; 3] = [0; 3];
|
||||
|
||||
lang[0] = ((language >> 10) & 0x1F) + 0x60;
|
||||
lang[1] = ((language >> 5) & 0x1F) + 0x60;
|
||||
lang[2] = ((language) & 0x1F) + 0x60;
|
||||
|
||||
// Decode utf-16 encoded bytes into a string.
|
||||
let lang_str = decode_utf16(lang.iter().cloned())
|
||||
.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
|
||||
.collect::<String>();
|
||||
|
||||
return lang_str;
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for HdlrBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
|
||||
read_box_header_ext!(reader, version, flags);
|
||||
|
||||
reader.read_u32::<BigEndian>().unwrap(); // skip.
|
||||
let handler = reader.read_u32::<BigEndian>().unwrap();
|
||||
|
||||
let n = reader.seek(SeekFrom::Current(12)).unwrap(); // 12 bytes reserved.
|
||||
let buf_size = (size as u64 - (n - current)) - HEADER_SIZE as u64;
|
||||
let mut buf = vec![0u8; buf_size as usize];
|
||||
reader.read_exact(&mut buf).unwrap();
|
||||
|
||||
let handler_string = match String::from_utf8(buf) {
|
||||
Ok(t) => t,
|
||||
_ => String::from("null"),
|
||||
};
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(HdlrBox {
|
||||
version,
|
||||
flags,
|
||||
handler_type: From::from(handler),
|
||||
name: handler_string,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for MinfBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
let mut minf = MinfBox::new();
|
||||
|
||||
let mut start = 0u64;
|
||||
while start < size as u64 {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start).unwrap();
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::VmhdBox => {
|
||||
let vmhd = VmhdBox::read_box(reader, s as u32).unwrap();
|
||||
minf.vmhd = Some(vmhd);
|
||||
}
|
||||
BoxType::SmhdBox => {
|
||||
start = (s as u32 - HEADER_SIZE) as u64;
|
||||
}
|
||||
BoxType::DinfBox => {
|
||||
start = (s as u32 - HEADER_SIZE) as u64;
|
||||
}
|
||||
BoxType::StblBox => {
|
||||
let stbl = StblBox::read_box(reader, s as u32).unwrap();
|
||||
minf.stbl = Some(stbl);
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(minf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for VmhdBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
|
||||
read_box_header_ext!(reader, version, flags);
|
||||
|
||||
let graphics_mode = reader.read_u16::<BigEndian>().unwrap();
|
||||
let op_color = reader.read_u16::<BigEndian>().unwrap();
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(VmhdBox {
|
||||
version,
|
||||
flags,
|
||||
graphics_mode,
|
||||
op_color,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for StblBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let mut stbl = StblBox::new();
|
||||
|
||||
let start = 0u64;
|
||||
while start < size as u64 {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start).unwrap();
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::StsdBox => {
|
||||
let stsd = StsdBox::read_box(reader, s as u32).unwrap();
|
||||
stbl.stsd = Some(stsd);
|
||||
}
|
||||
BoxType::SttsBox => {
|
||||
let stts = SttsBox::read_box(reader, s as u32).unwrap();
|
||||
stbl.stts = Some(stts);
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
Ok(stbl)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for SttsBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
|
||||
read_box_header_ext!(reader, version, flags);
|
||||
|
||||
let entry_count = reader.read_u32::<BigEndian>().unwrap();
|
||||
let mut sample_counts = Vec::new();
|
||||
let mut sample_deltas = Vec::new();
|
||||
|
||||
for _i in 0..entry_count {
|
||||
let sc = reader.read_u32::<BigEndian>().unwrap();
|
||||
let sd = reader.read_u32::<BigEndian>().unwrap();
|
||||
sample_counts.push(sc);
|
||||
sample_deltas.push(sd);
|
||||
}
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(SttsBox {
|
||||
version,
|
||||
flags,
|
||||
entry_count,
|
||||
sample_counts,
|
||||
sample_deltas,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut BufReader<R>> for StsdBox {
|
||||
fn read_box(reader: &mut BufReader<R>, size: u32) -> Result<Self> {
|
||||
let current = reader.seek(SeekFrom::Current(0)).unwrap(); // Current cursor position.
|
||||
|
||||
read_box_header_ext!(reader, version, flags);
|
||||
|
||||
reader.read_u32::<BigEndian>().unwrap(); // skip.
|
||||
|
||||
let mut start = 0u64;
|
||||
while start < size as u64 {
|
||||
// Get box header.
|
||||
let header = read_box_header(reader, start).unwrap();
|
||||
let BoxHeader{ name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::Avc1Box => {
|
||||
start = (s as u32 - HEADER_SIZE) as u64;
|
||||
}
|
||||
BoxType::Mp4aBox => {
|
||||
start = (s as u32 - HEADER_SIZE) as u64;
|
||||
}
|
||||
_ => break
|
||||
}
|
||||
}
|
||||
skip(reader, current, size);
|
||||
|
||||
Ok(StsdBox {
|
||||
version,
|
||||
flags,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn skip<R: Read + Seek>(reader: &mut BufReader<R>, current: u64, size: u32) {
|
||||
let after = reader.seek(SeekFrom::Current(0)).unwrap();
|
||||
let remaining_bytes = (size as u64 - (after - current)) as i64;
|
||||
reader.seek(SeekFrom::Current(remaining_bytes - HEADER_SIZE as i64)).unwrap();
|
||||
}
|
29
src/error.rs
Normal file
29
src/error.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
use thiserror::Error;
|
||||
|
||||
use crate::mp4box::BoxType;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error {
|
||||
#[error("{0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
#[error("{0}")]
|
||||
InvalidData(&'static str),
|
||||
#[error("{0} not found")]
|
||||
BoxNotFound(BoxType),
|
||||
#[error("{0} and {1} not found")]
|
||||
Box2NotFound(BoxType, BoxType),
|
||||
#[error("trak[{0}] not found")]
|
||||
TrakNotFound(u32),
|
||||
#[error("trak[{0}].{1} not found")]
|
||||
BoxInTrakNotFound(u32, BoxType),
|
||||
#[error("traf[{0}].{1} not found")]
|
||||
BoxInTrafNotFound(u32, BoxType),
|
||||
#[error("trak[{0}].stbl.{1} not found")]
|
||||
BoxInStblNotFound(u32, BoxType),
|
||||
#[error("trak[{0}].stbl.{1}.entry[{2}] not found")]
|
||||
EntryInStblNotFound(u32, BoxType, u32),
|
||||
#[error("traf[{0}].trun.{1}.entry[{2}] not found")]
|
||||
EntryInTrunNotFound(u32, BoxType, u32),
|
||||
#[error("{0} version {1} is not supported")]
|
||||
UnsupportedBoxVersion(BoxType, u8),
|
||||
}
|
218
src/lib.rs
218
src/lib.rs
|
@ -1,134 +1,96 @@
|
|||
extern crate byteorder;
|
||||
//! `mp4` is a Rust library to read and write ISO-MP4 files.
|
||||
//!
|
||||
//! This package contains MPEG-4 specifications defined in parts:
|
||||
//! * ISO/IEC 14496-12 - ISO Base Media File Format (QuickTime, MPEG-4, etc)
|
||||
//! * ISO/IEC 14496-14 - MP4 file format
|
||||
//! * ISO/IEC 14496-17 - Streaming text format
|
||||
//!
|
||||
//! See: [mp4box] for supported MP4 atoms.
|
||||
//!
|
||||
//! ### Example
|
||||
//!
|
||||
//! ```
|
||||
//! use std::fs::File;
|
||||
//! use std::io::{BufReader};
|
||||
//! use mp4::{Result};
|
||||
//!
|
||||
//! fn main() -> Result<()> {
|
||||
//! let f = File::open("tests/samples/minimal.mp4").unwrap();
|
||||
//! let size = f.metadata()?.len();
|
||||
//! let reader = BufReader::new(f);
|
||||
//!
|
||||
//! let mp4 = mp4::Mp4Reader::read_header(reader, size)?;
|
||||
//!
|
||||
//! // Print boxes.
|
||||
//! println!("major brand: {}", mp4.ftyp.major_brand);
|
||||
//! println!("timescale: {}", mp4.moov.mvhd.timescale);
|
||||
//!
|
||||
//! // Use available methods.
|
||||
//! println!("size: {}", mp4.size());
|
||||
//!
|
||||
//! let mut compatible_brands = String::new();
|
||||
//! for brand in mp4.compatible_brands().iter() {
|
||||
//! compatible_brands.push_str(&brand.to_string());
|
||||
//! compatible_brands.push_str(",");
|
||||
//! }
|
||||
//! println!("compatible brands: {}", compatible_brands);
|
||||
//! println!("duration: {:?}", mp4.duration());
|
||||
//!
|
||||
//! // Track info.
|
||||
//! for track in mp4.tracks().values() {
|
||||
//! println!(
|
||||
//! "track: #{}({}) {} : {}",
|
||||
//! track.track_id(),
|
||||
//! track.language(),
|
||||
//! track.track_type()?,
|
||||
//! track.box_type()?,
|
||||
//! );
|
||||
//! }
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! See [examples] for more examples.
|
||||
//!
|
||||
//! # Installation
|
||||
//!
|
||||
//! Add the following to your `Cargo.toml` file:
|
||||
//!
|
||||
//! ```toml
|
||||
//! [dependencies]
|
||||
//! mp4 = "0.7.0"
|
||||
//! ```
|
||||
//!
|
||||
//! [mp4box]: https://github.com/alfg/mp4-rust/blob/master/src/mp4box/mod.rs
|
||||
//! [examples]: https://github.com/alfg/mp4-rust/tree/master/examples
|
||||
#![doc(html_root_url = "https://docs.rs/mp4/*")]
|
||||
|
||||
use std::io::prelude::*;
|
||||
use std::io::{BufReader, Read, SeekFrom};
|
||||
use std::fs::File;
|
||||
use std::convert::TryInto;
|
||||
use std::io::BufReader;
|
||||
|
||||
mod atoms;
|
||||
use crate::atoms::*;
|
||||
mod error;
|
||||
pub use error::Error;
|
||||
|
||||
const HEADER_SIZE: u32 = 8;
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidData(&'static str),
|
||||
mod types;
|
||||
pub use types::*;
|
||||
|
||||
mod mp4box;
|
||||
pub use mp4box::*;
|
||||
|
||||
mod track;
|
||||
pub use track::{Mp4Track, TrackConfig};
|
||||
|
||||
mod reader;
|
||||
pub use reader::Mp4Reader;
|
||||
|
||||
mod writer;
|
||||
pub use writer::{Mp4Config, Mp4Writer};
|
||||
|
||||
pub fn read_mp4(f: File) -> Result<Mp4Reader<BufReader<File>>> {
|
||||
let size = f.metadata()?.len();
|
||||
let reader = BufReader::new(f);
|
||||
let mp4 = reader::Mp4Reader::read_header(reader, size)?;
|
||||
Ok(mp4)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum TrackType {
|
||||
Audio,
|
||||
Video,
|
||||
Metadata,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct BMFF {
|
||||
pub ftyp: FtypBox,
|
||||
pub moov: Option<MoovBox>,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
impl BMFF {
|
||||
fn new() -> BMFF {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct BoxHeader {
|
||||
name: BoxType,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
pub fn read_mp4(f: File) -> Result<BMFF> {
|
||||
|
||||
// Open file and read boxes.
|
||||
let bmff = read_boxes(f).unwrap();
|
||||
|
||||
Ok(bmff)
|
||||
}
|
||||
|
||||
fn read_boxes(f: File) -> Result<BMFF> {
|
||||
let filesize = f.metadata().unwrap().len();
|
||||
let mut reader = BufReader::new(f);
|
||||
let mut bmff = BMFF::new();
|
||||
bmff.size = filesize;
|
||||
|
||||
let mut start = 0u64;
|
||||
while start < filesize {
|
||||
|
||||
// Get box header.
|
||||
let header = read_box_header(&mut reader, start).unwrap();
|
||||
let BoxHeader{ name, size } = header;
|
||||
|
||||
// Match and parse the atom boxes.
|
||||
match name {
|
||||
BoxType::FtypBox => {
|
||||
let ftyp = FtypBox::read_box(&mut reader, size as u32).unwrap();
|
||||
bmff.ftyp = ftyp;
|
||||
}
|
||||
BoxType::FreeBox => {
|
||||
start = 0;
|
||||
}
|
||||
BoxType::MdatBox => {
|
||||
start = (size as u32 - HEADER_SIZE) as u64;
|
||||
}
|
||||
BoxType::MoovBox => {
|
||||
let moov = MoovBox::read_box(&mut reader, size as u32).unwrap();
|
||||
bmff.moov = Some(moov);
|
||||
}
|
||||
BoxType::MoofBox => {
|
||||
start = (size as u32 - HEADER_SIZE) as u64;
|
||||
}
|
||||
_ => {
|
||||
// Skip over unsupported boxes, but stop if the size is zero,
|
||||
// meaning the last box has been reached.
|
||||
if size == 0 {
|
||||
break;
|
||||
} else {
|
||||
start = (size as u32 - HEADER_SIZE) as u64;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Ok(bmff)
|
||||
}
|
||||
|
||||
// TODO: if size is 0, then this box is the last one in the file
|
||||
fn read_box_header<R: Read + Seek>(reader: &mut BufReader<R>, start: u64) -> Result<BoxHeader> {
|
||||
// Seek to offset.
|
||||
let _r = reader.seek(SeekFrom::Current(start as i64));
|
||||
|
||||
// Create and read to buf.
|
||||
let mut buf = [0u8;8]; // 8 bytes for box header.
|
||||
reader.read(&mut buf).unwrap();
|
||||
|
||||
// Get size.
|
||||
let s = buf[0..4].try_into().unwrap();
|
||||
let size = u32::from_be_bytes(s);
|
||||
|
||||
// Get box type string.
|
||||
let t = buf[4..8].try_into().unwrap();
|
||||
let typ = u32::from_be_bytes(t);
|
||||
|
||||
// Get largesize if size is 1
|
||||
if size == 1 {
|
||||
reader.read(&mut buf).unwrap();
|
||||
let s = buf.try_into().unwrap();
|
||||
let largesize = u64::from_be_bytes(s);
|
||||
|
||||
Ok(BoxHeader {
|
||||
name: BoxType::from(typ),
|
||||
size: largesize,
|
||||
})
|
||||
} else {
|
||||
Ok(BoxHeader {
|
||||
name: BoxType::from(typ),
|
||||
size: size as u64,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
354
src/mp4box/avc1.rs
Normal file
354
src/mp4box/avc1.rs
Normal file
|
@ -0,0 +1,354 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Avc1Box {
|
||||
pub data_reference_index: u16,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
|
||||
#[serde(with = "value_u32")]
|
||||
pub horizresolution: FixedPointU16,
|
||||
|
||||
#[serde(with = "value_u32")]
|
||||
pub vertresolution: FixedPointU16,
|
||||
pub frame_count: u16,
|
||||
pub depth: u16,
|
||||
pub avcc: AvcCBox,
|
||||
}
|
||||
|
||||
impl Default for Avc1Box {
|
||||
fn default() -> Self {
|
||||
Avc1Box {
|
||||
data_reference_index: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
horizresolution: FixedPointU16::new(0x48),
|
||||
vertresolution: FixedPointU16::new(0x48),
|
||||
frame_count: 1,
|
||||
depth: 0x0018,
|
||||
avcc: AvcCBox::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Avc1Box {
|
||||
pub fn new(config: &AvcConfig) -> Self {
|
||||
Avc1Box {
|
||||
data_reference_index: 1,
|
||||
width: config.width,
|
||||
height: config.height,
|
||||
horizresolution: FixedPointU16::new(0x48),
|
||||
vertresolution: FixedPointU16::new(0x48),
|
||||
frame_count: 1,
|
||||
depth: 0x0018,
|
||||
avcc: AvcCBox::new(&config.seq_param_set, &config.pic_param_set),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::Avc1Box
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + 8 + 70 + self.avcc.box_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for Avc1Box {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!(
|
||||
"data_reference_index={} width={} height={} frame_count={}",
|
||||
self.data_reference_index, self.width, self.height, self.frame_count
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Avc1Box {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // reserved
|
||||
reader.read_u16::<BigEndian>()?; // reserved
|
||||
let data_reference_index = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // pre-defined, reserved
|
||||
reader.read_u64::<BigEndian>()?; // pre-defined
|
||||
reader.read_u32::<BigEndian>()?; // pre-defined
|
||||
let width = reader.read_u16::<BigEndian>()?;
|
||||
let height = reader.read_u16::<BigEndian>()?;
|
||||
let horizresolution = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
|
||||
let vertresolution = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
|
||||
reader.read_u32::<BigEndian>()?; // reserved
|
||||
let frame_count = reader.read_u16::<BigEndian>()?;
|
||||
skip_bytes(reader, 32)?; // compressorname
|
||||
let depth = reader.read_u16::<BigEndian>()?;
|
||||
reader.read_i16::<BigEndian>()?; // pre-defined
|
||||
|
||||
let end = start + size;
|
||||
loop {
|
||||
let current = reader.stream_position()?;
|
||||
if current >= end {
|
||||
return Err(Error::InvalidData("avcc not found"));
|
||||
}
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"avc1 box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
if name == BoxType::AvcCBox {
|
||||
let avcc = AvcCBox::read_box(reader, s)?;
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
return Ok(Avc1Box {
|
||||
data_reference_index,
|
||||
width,
|
||||
height,
|
||||
horizresolution,
|
||||
vertresolution,
|
||||
frame_count,
|
||||
depth,
|
||||
avcc,
|
||||
});
|
||||
} else {
|
||||
skip_bytes_to(reader, current + s)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for Avc1Box {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.data_reference_index)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(0)?; // pre-defined, reserved
|
||||
writer.write_u64::<BigEndian>(0)?; // pre-defined
|
||||
writer.write_u32::<BigEndian>(0)?; // pre-defined
|
||||
writer.write_u16::<BigEndian>(self.width)?;
|
||||
writer.write_u16::<BigEndian>(self.height)?;
|
||||
writer.write_u32::<BigEndian>(self.horizresolution.raw_value())?;
|
||||
writer.write_u32::<BigEndian>(self.vertresolution.raw_value())?;
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.frame_count)?;
|
||||
// skip compressorname
|
||||
write_zeros(writer, 32)?;
|
||||
writer.write_u16::<BigEndian>(self.depth)?;
|
||||
writer.write_i16::<BigEndian>(-1)?; // pre-defined
|
||||
|
||||
self.avcc.write_box(writer)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct AvcCBox {
|
||||
pub configuration_version: u8,
|
||||
pub avc_profile_indication: u8,
|
||||
pub profile_compatibility: u8,
|
||||
pub avc_level_indication: u8,
|
||||
pub length_size_minus_one: u8,
|
||||
pub sequence_parameter_sets: Vec<NalUnit>,
|
||||
pub picture_parameter_sets: Vec<NalUnit>,
|
||||
}
|
||||
|
||||
impl AvcCBox {
|
||||
pub fn new(sps: &[u8], pps: &[u8]) -> Self {
|
||||
Self {
|
||||
configuration_version: 1,
|
||||
avc_profile_indication: sps[1],
|
||||
profile_compatibility: sps[2],
|
||||
avc_level_indication: sps[3],
|
||||
length_size_minus_one: 0xff, // length_size = 4
|
||||
sequence_parameter_sets: vec![NalUnit::from(sps)],
|
||||
picture_parameter_sets: vec![NalUnit::from(pps)],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for AvcCBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::AvcCBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + 7;
|
||||
for sps in self.sequence_parameter_sets.iter() {
|
||||
size += sps.size() as u64;
|
||||
}
|
||||
for pps in self.picture_parameter_sets.iter() {
|
||||
size += pps.size() as u64;
|
||||
}
|
||||
size
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("avc_profile_indication={}", self.avc_profile_indication);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for AvcCBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let configuration_version = reader.read_u8()?;
|
||||
let avc_profile_indication = reader.read_u8()?;
|
||||
let profile_compatibility = reader.read_u8()?;
|
||||
let avc_level_indication = reader.read_u8()?;
|
||||
let length_size_minus_one = reader.read_u8()? & 0x3;
|
||||
let num_of_spss = reader.read_u8()? & 0x1F;
|
||||
let mut sequence_parameter_sets = Vec::with_capacity(num_of_spss as usize);
|
||||
for _ in 0..num_of_spss {
|
||||
let nal_unit = NalUnit::read(reader)?;
|
||||
sequence_parameter_sets.push(nal_unit);
|
||||
}
|
||||
let num_of_ppss = reader.read_u8()?;
|
||||
let mut picture_parameter_sets = Vec::with_capacity(num_of_ppss as usize);
|
||||
for _ in 0..num_of_ppss {
|
||||
let nal_unit = NalUnit::read(reader)?;
|
||||
picture_parameter_sets.push(nal_unit);
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(AvcCBox {
|
||||
configuration_version,
|
||||
avc_profile_indication,
|
||||
profile_compatibility,
|
||||
avc_level_indication,
|
||||
length_size_minus_one,
|
||||
sequence_parameter_sets,
|
||||
picture_parameter_sets,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for AvcCBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
writer.write_u8(self.configuration_version)?;
|
||||
writer.write_u8(self.avc_profile_indication)?;
|
||||
writer.write_u8(self.profile_compatibility)?;
|
||||
writer.write_u8(self.avc_level_indication)?;
|
||||
writer.write_u8(self.length_size_minus_one | 0xFC)?;
|
||||
writer.write_u8(self.sequence_parameter_sets.len() as u8 | 0xE0)?;
|
||||
for sps in self.sequence_parameter_sets.iter() {
|
||||
sps.write(writer)?;
|
||||
}
|
||||
writer.write_u8(self.picture_parameter_sets.len() as u8)?;
|
||||
for pps in self.picture_parameter_sets.iter() {
|
||||
pps.write(writer)?;
|
||||
}
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct NalUnit {
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<&[u8]> for NalUnit {
|
||||
fn from(bytes: &[u8]) -> Self {
|
||||
Self {
|
||||
bytes: bytes.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NalUnit {
|
||||
fn size(&self) -> usize {
|
||||
2 + self.bytes.len()
|
||||
}
|
||||
|
||||
fn read<R: Read + Seek>(reader: &mut R) -> Result<Self> {
|
||||
let length = reader.read_u16::<BigEndian>()? as usize;
|
||||
let mut bytes = vec![0u8; length];
|
||||
reader.read_exact(&mut bytes)?;
|
||||
Ok(NalUnit { bytes })
|
||||
}
|
||||
|
||||
fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
|
||||
writer.write_u16::<BigEndian>(self.bytes.len() as u16)?;
|
||||
writer.write_all(&self.bytes)?;
|
||||
Ok(self.size() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_avc1() {
|
||||
let src_box = Avc1Box {
|
||||
data_reference_index: 1,
|
||||
width: 320,
|
||||
height: 240,
|
||||
horizresolution: FixedPointU16::new(0x48),
|
||||
vertresolution: FixedPointU16::new(0x48),
|
||||
frame_count: 1,
|
||||
depth: 24,
|
||||
avcc: AvcCBox {
|
||||
configuration_version: 1,
|
||||
avc_profile_indication: 100,
|
||||
profile_compatibility: 0,
|
||||
avc_level_indication: 13,
|
||||
length_size_minus_one: 3,
|
||||
sequence_parameter_sets: vec![NalUnit {
|
||||
bytes: vec![
|
||||
0x67, 0x64, 0x00, 0x0D, 0xAC, 0xD9, 0x41, 0x41, 0xFA, 0x10, 0x00, 0x00,
|
||||
0x03, 0x00, 0x10, 0x00, 0x00, 0x03, 0x03, 0x20, 0xF1, 0x42, 0x99, 0x60,
|
||||
],
|
||||
}],
|
||||
picture_parameter_sets: vec![NalUnit {
|
||||
bytes: vec![0x68, 0xEB, 0xE3, 0xCB, 0x22, 0xC0],
|
||||
}],
|
||||
},
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::Avc1Box);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = Avc1Box::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
123
src/mp4box/co64.rs
Normal file
123
src/mp4box/co64.rs
Normal file
|
@ -0,0 +1,123 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct Co64Box {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
||||
#[serde(skip_serializing)]
|
||||
pub entries: Vec<u64>,
|
||||
}
|
||||
|
||||
impl Co64Box {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::Co64Box
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 4 + (8 * self.entries.len() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for Co64Box {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("entries_count={}", self.entries.len());
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Co64Box {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let header_size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
let other_size = size_of::<u32>(); // entry_count
|
||||
let entry_size = size_of::<u64>(); // chunk_offset
|
||||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
if u64::from(entry_count)
|
||||
> size
|
||||
.saturating_sub(header_size)
|
||||
.saturating_sub(other_size as u64)
|
||||
/ entry_size as u64
|
||||
{
|
||||
return Err(Error::InvalidData(
|
||||
"co64 entry_count indicates more entries than could fit in the box",
|
||||
));
|
||||
}
|
||||
let mut entries = Vec::with_capacity(entry_count as usize);
|
||||
for _i in 0..entry_count {
|
||||
let chunk_offset = reader.read_u64::<BigEndian>()?;
|
||||
entries.push(chunk_offset);
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(Co64Box {
|
||||
version,
|
||||
flags,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for Co64Box {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.entries.len() as u32)?;
|
||||
for chunk_offset in self.entries.iter() {
|
||||
writer.write_u64::<BigEndian>(*chunk_offset)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_co64() {
|
||||
let src_box = Co64Box {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
entries: vec![267, 1970, 2535, 2803, 11843, 22223, 33584],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::Co64Box);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = Co64Box::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
143
src/mp4box/ctts.rs
Normal file
143
src/mp4box/ctts.rs
Normal file
|
@ -0,0 +1,143 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct CttsBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
||||
#[serde(skip_serializing)]
|
||||
pub entries: Vec<CttsEntry>,
|
||||
}
|
||||
|
||||
impl CttsBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::CttsBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 4 + (8 * self.entries.len() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct CttsEntry {
|
||||
pub sample_count: u32,
|
||||
pub sample_offset: i32,
|
||||
}
|
||||
|
||||
impl Mp4Box for CttsBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("entries_count={}", self.entries.len());
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for CttsBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let header_size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
let entry_size = size_of::<u32>() + size_of::<i32>(); // sample_count + sample_offset
|
||||
// (sample_offset might be a u32, but the size is the same.)
|
||||
let other_size = size_of::<i32>(); // entry_count
|
||||
if u64::from(entry_count)
|
||||
> size
|
||||
.saturating_sub(header_size)
|
||||
.saturating_sub(other_size as u64)
|
||||
/ entry_size as u64
|
||||
{
|
||||
return Err(Error::InvalidData(
|
||||
"ctts entry_count indicates more entries than could fit in the box",
|
||||
));
|
||||
}
|
||||
let mut entries = Vec::with_capacity(entry_count as usize);
|
||||
for _ in 0..entry_count {
|
||||
let entry = CttsEntry {
|
||||
sample_count: reader.read_u32::<BigEndian>()?,
|
||||
sample_offset: reader.read_i32::<BigEndian>()?,
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(CttsBox {
|
||||
version,
|
||||
flags,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for CttsBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.entries.len() as u32)?;
|
||||
for entry in self.entries.iter() {
|
||||
writer.write_u32::<BigEndian>(entry.sample_count)?;
|
||||
writer.write_i32::<BigEndian>(entry.sample_offset)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_ctts() {
|
||||
let src_box = CttsBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
entries: vec![
|
||||
CttsEntry {
|
||||
sample_count: 1,
|
||||
sample_offset: 200,
|
||||
},
|
||||
CttsEntry {
|
||||
sample_count: 2,
|
||||
sample_offset: -100,
|
||||
},
|
||||
],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::CttsBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = CttsBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
118
src/mp4box/data.rs
Normal file
118
src/mp4box/data.rs
Normal file
|
@ -0,0 +1,118 @@
|
|||
use std::{
|
||||
convert::TryFrom,
|
||||
io::{Read, Seek},
|
||||
};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct DataBox {
|
||||
pub data: Vec<u8>,
|
||||
pub data_type: DataType,
|
||||
}
|
||||
|
||||
impl DataBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::DataBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
size += 4; // data_type
|
||||
size += 4; // reserved
|
||||
size += self.data.len() as u64;
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for DataBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("type={:?} len={}", self.data_type, self.data.len());
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for DataBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let data_type = DataType::try_from(reader.read_u32::<BigEndian>()?)?;
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // reserved = 0
|
||||
|
||||
let current = reader.stream_position()?;
|
||||
let mut data = vec![0u8; (start + size - current) as usize];
|
||||
reader.read_exact(&mut data)?;
|
||||
|
||||
Ok(DataBox { data, data_type })
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for DataBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.data_type.clone() as u32)?;
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved = 0
|
||||
writer.write_all(&self.data)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_data() {
|
||||
let src_box = DataBox {
|
||||
data_type: DataType::Text,
|
||||
data: b"test_data".to_vec(),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::DataBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = DataBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_data_empty() {
|
||||
let src_box = DataBox::default();
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::DataBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = DataBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
302
src/mp4box/dinf.rs
Normal file
302
src/mp4box/dinf.rs
Normal file
|
@ -0,0 +1,302 @@
|
|||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct DinfBox {
|
||||
dref: DrefBox,
|
||||
}
|
||||
|
||||
impl DinfBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::DinfBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + self.dref.box_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for DinfBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for DinfBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut dref = None;
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"dinf box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
match name {
|
||||
BoxType::DrefBox => {
|
||||
dref = Some(DrefBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if dref.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::DrefBox));
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(DinfBox {
|
||||
dref: dref.unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for DinfBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
self.dref.write_box(writer)?;
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct DrefBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub url: Option<UrlBox>,
|
||||
}
|
||||
|
||||
impl Default for DrefBox {
|
||||
fn default() -> Self {
|
||||
DrefBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
url: Some(UrlBox::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DrefBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::DrefBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE + 4;
|
||||
if let Some(ref url) = self.url {
|
||||
size += url.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for DrefBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for DrefBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
let end = start + size;
|
||||
|
||||
let mut url = None;
|
||||
|
||||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
for _i in 0..entry_count {
|
||||
if current >= end {
|
||||
break;
|
||||
}
|
||||
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"dinf box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
match name {
|
||||
BoxType::UrlBox => {
|
||||
url = Some(UrlBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(DrefBox {
|
||||
version,
|
||||
flags,
|
||||
url,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for DrefBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(1)?;
|
||||
|
||||
if let Some(ref url) = self.url {
|
||||
url.write_box(writer)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct UrlBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub location: String,
|
||||
}
|
||||
|
||||
impl Default for UrlBox {
|
||||
fn default() -> Self {
|
||||
UrlBox {
|
||||
version: 0,
|
||||
flags: 1,
|
||||
location: String::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UrlBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::UrlBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
|
||||
if !self.location.is_empty() {
|
||||
size += self.location.bytes().len() as u64 + 1;
|
||||
}
|
||||
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for UrlBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("location={}", self.location);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for UrlBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let buf_size = size
|
||||
.checked_sub(HEADER_SIZE + HEADER_EXT_SIZE)
|
||||
.ok_or(Error::InvalidData("url size too small"))?;
|
||||
|
||||
let mut buf = vec![0u8; buf_size as usize];
|
||||
reader.read_exact(&mut buf)?;
|
||||
if let Some(end) = buf.iter().position(|&b| b == b'\0') {
|
||||
buf.truncate(end);
|
||||
}
|
||||
let location = String::from_utf8(buf).unwrap_or_default();
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(UrlBox {
|
||||
version,
|
||||
flags,
|
||||
location,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for UrlBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
if !self.location.is_empty() {
|
||||
writer.write_all(self.location.as_bytes())?;
|
||||
writer.write_u8(0)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
85
src/mp4box/edts.rs
Normal file
85
src/mp4box/edts.rs
Normal file
|
@ -0,0 +1,85 @@
|
|||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::elst::ElstBox;
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct EdtsBox {
|
||||
pub elst: Option<ElstBox>,
|
||||
}
|
||||
|
||||
impl EdtsBox {
|
||||
pub(crate) fn new() -> EdtsBox {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::EdtsBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(ref elst) = self.elst {
|
||||
size += elst.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for EdtsBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for EdtsBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut edts = EdtsBox::new();
|
||||
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"edts box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
if let BoxType::ElstBox = name {
|
||||
let elst = ElstBox::read_box(reader, s)?;
|
||||
edts.elst = Some(elst);
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(edts)
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for EdtsBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
if let Some(ref elst) = self.elst {
|
||||
elst.write_box(writer)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
201
src/mp4box/elst.rs
Normal file
201
src/mp4box/elst.rs
Normal file
|
@ -0,0 +1,201 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct ElstBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
||||
#[serde(skip_serializing)]
|
||||
pub entries: Vec<ElstEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct ElstEntry {
|
||||
pub segment_duration: u64,
|
||||
pub media_time: u64,
|
||||
pub media_rate: u16,
|
||||
pub media_rate_fraction: u16,
|
||||
}
|
||||
|
||||
impl ElstBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::ElstBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE + 4;
|
||||
if self.version == 1 {
|
||||
size += self.entries.len() as u64 * 20;
|
||||
} else if self.version == 0 {
|
||||
size += self.entries.len() as u64 * 12;
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for ElstBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("elst_entries={}", self.entries.len());
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for ElstBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let header_size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
let other_size = size_of::<i32>(); // entry_count
|
||||
let entry_size = {
|
||||
let mut entry_size = 0;
|
||||
entry_size += if version == 1 {
|
||||
size_of::<u64>() + size_of::<i64>() // segment_duration + media_time
|
||||
} else {
|
||||
size_of::<u32>() + size_of::<i32>() // segment_duration + media_time
|
||||
};
|
||||
entry_size += size_of::<i16>() + size_of::<i16>(); // media_rate_integer + media_rate_fraction
|
||||
entry_size
|
||||
};
|
||||
if u64::from(entry_count)
|
||||
> size
|
||||
.saturating_sub(header_size)
|
||||
.saturating_sub(other_size as u64)
|
||||
/ entry_size as u64
|
||||
{
|
||||
return Err(Error::InvalidData(
|
||||
"elst entry_count indicates more entries than could fit in the box",
|
||||
));
|
||||
}
|
||||
let mut entries = Vec::with_capacity(entry_count as usize);
|
||||
for _ in 0..entry_count {
|
||||
let (segment_duration, media_time) = if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
};
|
||||
|
||||
let entry = ElstEntry {
|
||||
segment_duration,
|
||||
media_time,
|
||||
media_rate: reader.read_u16::<BigEndian>()?,
|
||||
media_rate_fraction: reader.read_u16::<BigEndian>()?,
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(ElstBox {
|
||||
version,
|
||||
flags,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for ElstBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.entries.len() as u32)?;
|
||||
for entry in self.entries.iter() {
|
||||
if self.version == 1 {
|
||||
writer.write_u64::<BigEndian>(entry.segment_duration)?;
|
||||
writer.write_u64::<BigEndian>(entry.media_time)?;
|
||||
} else {
|
||||
writer.write_u32::<BigEndian>(entry.segment_duration as u32)?;
|
||||
writer.write_u32::<BigEndian>(entry.media_time as u32)?;
|
||||
}
|
||||
writer.write_u16::<BigEndian>(entry.media_rate)?;
|
||||
writer.write_u16::<BigEndian>(entry.media_rate_fraction)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_elst32() {
|
||||
let src_box = ElstBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
entries: vec![ElstEntry {
|
||||
segment_duration: 634634,
|
||||
media_time: 0,
|
||||
media_rate: 1,
|
||||
media_rate_fraction: 0,
|
||||
}],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::ElstBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = ElstBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_elst64() {
|
||||
let src_box = ElstBox {
|
||||
version: 1,
|
||||
flags: 0,
|
||||
entries: vec![ElstEntry {
|
||||
segment_duration: 634634,
|
||||
media_time: 0,
|
||||
media_rate: 1,
|
||||
media_rate_fraction: 0,
|
||||
}],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::ElstBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = ElstBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
242
src/mp4box/emsg.rs
Normal file
242
src/mp4box/emsg.rs
Normal file
|
@ -0,0 +1,242 @@
|
|||
use std::ffi::CStr;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct EmsgBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub timescale: u32,
|
||||
pub presentation_time: Option<u64>,
|
||||
pub presentation_time_delta: Option<u32>,
|
||||
pub event_duration: u32,
|
||||
pub id: u32,
|
||||
pub scheme_id_uri: String,
|
||||
pub value: String,
|
||||
pub message_data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl EmsgBox {
|
||||
fn size_without_message(version: u8, scheme_id_uri: &str, value: &str) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE +
|
||||
4 + // id
|
||||
Self::time_size(version) +
|
||||
(scheme_id_uri.len() + 1) as u64 +
|
||||
(value.len() as u64 + 1)
|
||||
}
|
||||
|
||||
fn time_size(version: u8) -> u64 {
|
||||
match version {
|
||||
0 => 12,
|
||||
1 => 16,
|
||||
_ => panic!("version must be 0 or 1"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for EmsgBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::EmsgBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
Self::size_without_message(self.version, &self.scheme_id_uri, &self.value)
|
||||
+ self.message_data.len() as u64
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("id={} value={}", self.id, self.value);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for EmsgBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let (
|
||||
timescale,
|
||||
presentation_time,
|
||||
presentation_time_delta,
|
||||
event_duration,
|
||||
id,
|
||||
scheme_id_uri,
|
||||
value,
|
||||
) = match version {
|
||||
0 => {
|
||||
let scheme_id_uri = read_null_terminated_utf8_string(reader)?;
|
||||
let value = read_null_terminated_utf8_string(reader)?;
|
||||
(
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
None,
|
||||
Some(reader.read_u32::<BigEndian>()?),
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
scheme_id_uri,
|
||||
value,
|
||||
)
|
||||
}
|
||||
1 => (
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
Some(reader.read_u64::<BigEndian>()?),
|
||||
None,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
read_null_terminated_utf8_string(reader)?,
|
||||
read_null_terminated_utf8_string(reader)?,
|
||||
),
|
||||
_ => return Err(Error::InvalidData("version must be 0 or 1")),
|
||||
};
|
||||
|
||||
let message_size = size - Self::size_without_message(version, &scheme_id_uri, &value);
|
||||
let mut message_data = Vec::with_capacity(message_size as usize);
|
||||
for _ in 0..message_size {
|
||||
message_data.push(reader.read_u8()?);
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(EmsgBox {
|
||||
version,
|
||||
flags,
|
||||
timescale,
|
||||
presentation_time,
|
||||
presentation_time_delta,
|
||||
event_duration,
|
||||
id,
|
||||
scheme_id_uri,
|
||||
value,
|
||||
message_data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for EmsgBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
match self.version {
|
||||
0 => {
|
||||
write_null_terminated_str(writer, &self.scheme_id_uri)?;
|
||||
write_null_terminated_str(writer, &self.value)?;
|
||||
writer.write_u32::<BigEndian>(self.timescale)?;
|
||||
writer.write_u32::<BigEndian>(self.presentation_time_delta.unwrap())?;
|
||||
writer.write_u32::<BigEndian>(self.event_duration)?;
|
||||
writer.write_u32::<BigEndian>(self.id)?;
|
||||
}
|
||||
1 => {
|
||||
writer.write_u32::<BigEndian>(self.timescale)?;
|
||||
writer.write_u64::<BigEndian>(self.presentation_time.unwrap())?;
|
||||
writer.write_u32::<BigEndian>(self.event_duration)?;
|
||||
writer.write_u32::<BigEndian>(self.id)?;
|
||||
write_null_terminated_str(writer, &self.scheme_id_uri)?;
|
||||
write_null_terminated_str(writer, &self.value)?;
|
||||
}
|
||||
_ => return Err(Error::InvalidData("version must be 0 or 1")),
|
||||
}
|
||||
|
||||
for &byte in &self.message_data {
|
||||
writer.write_u8(byte)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_null_terminated_utf8_string<R: Read + Seek>(reader: &mut R) -> Result<String> {
|
||||
let mut bytes = Vec::new();
|
||||
loop {
|
||||
let byte = reader.read_u8()?;
|
||||
bytes.push(byte);
|
||||
if byte == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Ok(str) = unsafe { CStr::from_bytes_with_nul_unchecked(&bytes) }.to_str() {
|
||||
Ok(str.to_string())
|
||||
} else {
|
||||
Err(Error::InvalidData("invalid utf8"))
|
||||
}
|
||||
}
|
||||
|
||||
fn write_null_terminated_str<W: Write>(writer: &mut W, string: &str) -> Result<()> {
|
||||
for byte in string.bytes() {
|
||||
writer.write_u8(byte)?;
|
||||
}
|
||||
writer.write_u8(0)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Cursor;
|
||||
|
||||
use crate::mp4box::BoxHeader;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_emsg_version0() {
|
||||
let src_box = EmsgBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
timescale: 48000,
|
||||
presentation_time: None,
|
||||
presentation_time_delta: Some(100),
|
||||
event_duration: 200,
|
||||
id: 8,
|
||||
scheme_id_uri: String::from("foo"),
|
||||
value: String::from("foo"),
|
||||
message_data: vec![1, 2, 3],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::EmsgBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = EmsgBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emsg_version1() {
|
||||
let src_box = EmsgBox {
|
||||
version: 1,
|
||||
flags: 0,
|
||||
timescale: 48000,
|
||||
presentation_time: Some(50000),
|
||||
presentation_time_delta: None,
|
||||
event_duration: 200,
|
||||
id: 8,
|
||||
scheme_id_uri: String::from("foo"),
|
||||
value: String::from("foo"),
|
||||
message_data: vec![3, 2, 1],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::EmsgBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = EmsgBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
123
src/mp4box/ftyp.rs
Normal file
123
src/mp4box/ftyp.rs
Normal file
|
@ -0,0 +1,123 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct FtypBox {
|
||||
pub major_brand: FourCC,
|
||||
pub minor_version: u32,
|
||||
pub compatible_brands: Vec<FourCC>,
|
||||
}
|
||||
|
||||
impl FtypBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::FtypBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + 8 + (4 * self.compatible_brands.len() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for FtypBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let mut compatible_brands = Vec::new();
|
||||
for brand in self.compatible_brands.iter() {
|
||||
compatible_brands.push(brand.to_string());
|
||||
}
|
||||
let s = format!(
|
||||
"major_brand={} minor_version={} compatible_brands={}",
|
||||
self.major_brand,
|
||||
self.minor_version,
|
||||
compatible_brands.join("-")
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for FtypBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
if size < 16 || size % 4 != 0 {
|
||||
return Err(Error::InvalidData("ftyp size too small or not aligned"));
|
||||
}
|
||||
let brand_count = (size - 16) / 4; // header + major + minor
|
||||
let major = reader.read_u32::<BigEndian>()?;
|
||||
let minor = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
let mut brands = Vec::new();
|
||||
for _ in 0..brand_count {
|
||||
let b = reader.read_u32::<BigEndian>()?;
|
||||
brands.push(From::from(b));
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(FtypBox {
|
||||
major_brand: From::from(major),
|
||||
minor_version: minor,
|
||||
compatible_brands: brands,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for FtypBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
writer.write_u32::<BigEndian>((&self.major_brand).into())?;
|
||||
writer.write_u32::<BigEndian>(self.minor_version)?;
|
||||
for b in self.compatible_brands.iter() {
|
||||
writer.write_u32::<BigEndian>(b.into())?;
|
||||
}
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_ftyp() {
|
||||
let src_box = FtypBox {
|
||||
major_brand: str::parse("isom").unwrap(),
|
||||
minor_version: 0,
|
||||
compatible_brands: vec![
|
||||
str::parse("isom").unwrap(),
|
||||
str::parse("iso2").unwrap(),
|
||||
str::parse("avc1").unwrap(),
|
||||
str::parse("mp41").unwrap(),
|
||||
],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::FtypBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = FtypBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
173
src/mp4box/hdlr.rs
Normal file
173
src/mp4box/hdlr.rs
Normal file
|
@ -0,0 +1,173 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct HdlrBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub handler_type: FourCC,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl HdlrBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::HdlrBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 20 + self.name.len() as u64 + 1
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for HdlrBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("handler_type={} name={}", self.handler_type, self.name);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for HdlrBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // pre-defined
|
||||
let handler = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
skip_bytes(reader, 12)?; // reserved
|
||||
|
||||
let buf_size = size
|
||||
.checked_sub(HEADER_SIZE + HEADER_EXT_SIZE + 20)
|
||||
.ok_or(Error::InvalidData("hdlr size too small"))?;
|
||||
|
||||
let mut buf = vec![0u8; buf_size as usize];
|
||||
reader.read_exact(&mut buf)?;
|
||||
if let Some(end) = buf.iter().position(|&b| b == b'\0') {
|
||||
buf.truncate(end);
|
||||
}
|
||||
let handler_string = String::from_utf8(buf).unwrap_or_default();
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(HdlrBox {
|
||||
version,
|
||||
flags,
|
||||
handler_type: From::from(handler),
|
||||
name: handler_string,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for HdlrBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(0)?; // pre-defined
|
||||
writer.write_u32::<BigEndian>((&self.handler_type).into())?;
|
||||
|
||||
// 12 bytes reserved
|
||||
for _ in 0..3 {
|
||||
writer.write_u32::<BigEndian>(0)?;
|
||||
}
|
||||
|
||||
writer.write_all(self.name.as_bytes())?;
|
||||
writer.write_u8(0)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_hdlr() {
|
||||
let src_box = HdlrBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
handler_type: str::parse::<FourCC>("vide").unwrap(),
|
||||
name: String::from("VideoHandler"),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::HdlrBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = HdlrBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hdlr_empty() {
|
||||
let src_box = HdlrBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
handler_type: str::parse::<FourCC>("vide").unwrap(),
|
||||
name: String::new(),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::HdlrBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = HdlrBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hdlr_extra() {
|
||||
let real_src_box = HdlrBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
handler_type: str::parse::<FourCC>("vide").unwrap(),
|
||||
name: String::from("Good"),
|
||||
};
|
||||
let src_box = HdlrBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
handler_type: str::parse::<FourCC>("vide").unwrap(),
|
||||
name: String::from_utf8(b"Good\0Bad".to_vec()).unwrap(),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::HdlrBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = HdlrBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(real_src_box, dst_box);
|
||||
}
|
||||
}
|
395
src/mp4box/hev1.rs
Normal file
395
src/mp4box/hev1.rs
Normal file
|
@ -0,0 +1,395 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Hev1Box {
|
||||
pub data_reference_index: u16,
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
|
||||
#[serde(with = "value_u32")]
|
||||
pub horizresolution: FixedPointU16,
|
||||
|
||||
#[serde(with = "value_u32")]
|
||||
pub vertresolution: FixedPointU16,
|
||||
pub frame_count: u16,
|
||||
pub depth: u16,
|
||||
pub hvcc: HvcCBox,
|
||||
}
|
||||
|
||||
impl Default for Hev1Box {
|
||||
fn default() -> Self {
|
||||
Hev1Box {
|
||||
data_reference_index: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
horizresolution: FixedPointU16::new(0x48),
|
||||
vertresolution: FixedPointU16::new(0x48),
|
||||
frame_count: 1,
|
||||
depth: 0x0018,
|
||||
hvcc: HvcCBox::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hev1Box {
|
||||
pub fn new(config: &HevcConfig) -> Self {
|
||||
Hev1Box {
|
||||
data_reference_index: 1,
|
||||
width: config.width,
|
||||
height: config.height,
|
||||
horizresolution: FixedPointU16::new(0x48),
|
||||
vertresolution: FixedPointU16::new(0x48),
|
||||
frame_count: 1,
|
||||
depth: 0x0018,
|
||||
hvcc: HvcCBox::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::Hev1Box
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + 8 + 70 + self.hvcc.box_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for Hev1Box {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!(
|
||||
"data_reference_index={} width={} height={} frame_count={}",
|
||||
self.data_reference_index, self.width, self.height, self.frame_count
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Hev1Box {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // reserved
|
||||
reader.read_u16::<BigEndian>()?; // reserved
|
||||
let data_reference_index = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // pre-defined, reserved
|
||||
reader.read_u64::<BigEndian>()?; // pre-defined
|
||||
reader.read_u32::<BigEndian>()?; // pre-defined
|
||||
let width = reader.read_u16::<BigEndian>()?;
|
||||
let height = reader.read_u16::<BigEndian>()?;
|
||||
let horizresolution = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
|
||||
let vertresolution = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
|
||||
reader.read_u32::<BigEndian>()?; // reserved
|
||||
let frame_count = reader.read_u16::<BigEndian>()?;
|
||||
skip_bytes(reader, 32)?; // compressorname
|
||||
let depth = reader.read_u16::<BigEndian>()?;
|
||||
reader.read_i16::<BigEndian>()?; // pre-defined
|
||||
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"hev1 box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
if name == BoxType::HvcCBox {
|
||||
let hvcc = HvcCBox::read_box(reader, s)?;
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(Hev1Box {
|
||||
data_reference_index,
|
||||
width,
|
||||
height,
|
||||
horizresolution,
|
||||
vertresolution,
|
||||
frame_count,
|
||||
depth,
|
||||
hvcc,
|
||||
})
|
||||
} else {
|
||||
Err(Error::InvalidData("hvcc not found"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for Hev1Box {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.data_reference_index)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(0)?; // pre-defined, reserved
|
||||
writer.write_u64::<BigEndian>(0)?; // pre-defined
|
||||
writer.write_u32::<BigEndian>(0)?; // pre-defined
|
||||
writer.write_u16::<BigEndian>(self.width)?;
|
||||
writer.write_u16::<BigEndian>(self.height)?;
|
||||
writer.write_u32::<BigEndian>(self.horizresolution.raw_value())?;
|
||||
writer.write_u32::<BigEndian>(self.vertresolution.raw_value())?;
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.frame_count)?;
|
||||
// skip compressorname
|
||||
write_zeros(writer, 32)?;
|
||||
writer.write_u16::<BigEndian>(self.depth)?;
|
||||
writer.write_i16::<BigEndian>(-1)?; // pre-defined
|
||||
|
||||
self.hvcc.write_box(writer)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct HvcCBox {
|
||||
pub configuration_version: u8,
|
||||
pub general_profile_space: u8,
|
||||
pub general_tier_flag: bool,
|
||||
pub general_profile_idc: u8,
|
||||
pub general_profile_compatibility_flags: u32,
|
||||
pub general_constraint_indicator_flag: u64,
|
||||
pub general_level_idc: u8,
|
||||
pub min_spatial_segmentation_idc: u16,
|
||||
pub parallelism_type: u8,
|
||||
pub chroma_format_idc: u8,
|
||||
pub bit_depth_luma_minus8: u8,
|
||||
pub bit_depth_chroma_minus8: u8,
|
||||
pub avg_frame_rate: u16,
|
||||
pub constant_frame_rate: u8,
|
||||
pub num_temporal_layers: u8,
|
||||
pub temporal_id_nested: bool,
|
||||
pub length_size_minus_one: u8,
|
||||
pub arrays: Vec<HvcCArray>,
|
||||
}
|
||||
|
||||
impl HvcCBox {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
configuration_version: 1,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for HvcCBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::HvcCBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
HEADER_SIZE
|
||||
+ 23
|
||||
+ self
|
||||
.arrays
|
||||
.iter()
|
||||
.map(|a| 3 + a.nalus.iter().map(|x| 2 + x.data.len() as u64).sum::<u64>())
|
||||
.sum::<u64>()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
Ok(format!("configuration_version={} general_profile_space={} general_tier_flag={} general_profile_idc={} general_profile_compatibility_flags={} general_constraint_indicator_flag={} general_level_idc={} min_spatial_segmentation_idc={} parallelism_type={} chroma_format_idc={} bit_depth_luma_minus8={} bit_depth_chroma_minus8={} avg_frame_rate={} constant_frame_rate={} num_temporal_layers={} temporal_id_nested={} length_size_minus_one={}",
|
||||
self.configuration_version,
|
||||
self.general_profile_space,
|
||||
self.general_tier_flag,
|
||||
self.general_profile_idc,
|
||||
self.general_profile_compatibility_flags,
|
||||
self.general_constraint_indicator_flag,
|
||||
self.general_level_idc,
|
||||
self.min_spatial_segmentation_idc,
|
||||
self.parallelism_type,
|
||||
self.chroma_format_idc,
|
||||
self.bit_depth_luma_minus8,
|
||||
self.bit_depth_chroma_minus8,
|
||||
self.avg_frame_rate,
|
||||
self.constant_frame_rate,
|
||||
self.num_temporal_layers,
|
||||
self.temporal_id_nested,
|
||||
self.length_size_minus_one
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct HvcCArrayNalu {
|
||||
pub size: u16,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct HvcCArray {
|
||||
pub completeness: bool,
|
||||
pub nal_unit_type: u8,
|
||||
pub nalus: Vec<HvcCArrayNalu>,
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for HvcCBox {
|
||||
fn read_box(reader: &mut R, _size: u64) -> Result<Self> {
|
||||
let configuration_version = reader.read_u8()?;
|
||||
let params = reader.read_u8()?;
|
||||
let general_profile_space = params & 0b11000000 >> 6;
|
||||
let general_tier_flag = (params & 0b00100000 >> 5) > 0;
|
||||
let general_profile_idc = params & 0b00011111;
|
||||
|
||||
let general_profile_compatibility_flags = reader.read_u32::<BigEndian>()?;
|
||||
let general_constraint_indicator_flag = reader.read_u48::<BigEndian>()?;
|
||||
let general_level_idc = reader.read_u8()?;
|
||||
let min_spatial_segmentation_idc = reader.read_u16::<BigEndian>()? & 0x0FFF;
|
||||
let parallelism_type = reader.read_u8()? & 0b11;
|
||||
let chroma_format_idc = reader.read_u8()? & 0b11;
|
||||
let bit_depth_luma_minus8 = reader.read_u8()? & 0b111;
|
||||
let bit_depth_chroma_minus8 = reader.read_u8()? & 0b111;
|
||||
let avg_frame_rate = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
let params = reader.read_u8()?;
|
||||
let constant_frame_rate = params & 0b11000000 >> 6;
|
||||
let num_temporal_layers = params & 0b00111000 >> 3;
|
||||
let temporal_id_nested = (params & 0b00000100 >> 2) > 0;
|
||||
let length_size_minus_one = params & 0b000011;
|
||||
|
||||
let num_of_arrays = reader.read_u8()?;
|
||||
|
||||
let mut arrays = Vec::with_capacity(num_of_arrays as _);
|
||||
for _ in 0..num_of_arrays {
|
||||
let params = reader.read_u8()?;
|
||||
let num_nalus = reader.read_u16::<BigEndian>()?;
|
||||
let mut nalus = Vec::with_capacity(num_nalus as usize);
|
||||
|
||||
for _ in 0..num_nalus {
|
||||
let size = reader.read_u16::<BigEndian>()?;
|
||||
let mut data = vec![0; size as usize];
|
||||
|
||||
reader.read_exact(&mut data)?;
|
||||
|
||||
nalus.push(HvcCArrayNalu { size, data })
|
||||
}
|
||||
|
||||
arrays.push(HvcCArray {
|
||||
completeness: (params & 0b10000000) > 0,
|
||||
nal_unit_type: params & 0b111111,
|
||||
nalus,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(HvcCBox {
|
||||
configuration_version,
|
||||
general_profile_space,
|
||||
general_tier_flag,
|
||||
general_profile_idc,
|
||||
general_profile_compatibility_flags,
|
||||
general_constraint_indicator_flag,
|
||||
general_level_idc,
|
||||
min_spatial_segmentation_idc,
|
||||
parallelism_type,
|
||||
chroma_format_idc,
|
||||
bit_depth_luma_minus8,
|
||||
bit_depth_chroma_minus8,
|
||||
avg_frame_rate,
|
||||
constant_frame_rate,
|
||||
num_temporal_layers,
|
||||
temporal_id_nested,
|
||||
length_size_minus_one,
|
||||
arrays,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for HvcCBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
writer.write_u8(self.configuration_version)?;
|
||||
let general_profile_space = (self.general_profile_space & 0b11) << 6;
|
||||
let general_tier_flag = u8::from(self.general_tier_flag) << 5;
|
||||
let general_profile_idc = self.general_profile_idc & 0b11111;
|
||||
|
||||
writer.write_u8(general_profile_space | general_tier_flag | general_profile_idc)?;
|
||||
writer.write_u32::<BigEndian>(self.general_profile_compatibility_flags)?;
|
||||
writer.write_u48::<BigEndian>(self.general_constraint_indicator_flag)?;
|
||||
writer.write_u8(self.general_level_idc)?;
|
||||
|
||||
writer.write_u16::<BigEndian>(self.min_spatial_segmentation_idc & 0x0FFF)?;
|
||||
writer.write_u8(self.parallelism_type & 0b11)?;
|
||||
writer.write_u8(self.chroma_format_idc & 0b11)?;
|
||||
writer.write_u8(self.bit_depth_luma_minus8 & 0b111)?;
|
||||
writer.write_u8(self.bit_depth_chroma_minus8 & 0b111)?;
|
||||
writer.write_u16::<BigEndian>(self.avg_frame_rate)?;
|
||||
|
||||
let constant_frame_rate = (self.constant_frame_rate & 0b11) << 6;
|
||||
let num_temporal_layers = (self.num_temporal_layers & 0b111) << 3;
|
||||
let temporal_id_nested = u8::from(self.temporal_id_nested) << 2;
|
||||
let length_size_minus_one = self.length_size_minus_one & 0b11;
|
||||
writer.write_u8(
|
||||
constant_frame_rate | num_temporal_layers | temporal_id_nested | length_size_minus_one,
|
||||
)?;
|
||||
writer.write_u8(self.arrays.len() as u8)?;
|
||||
for arr in &self.arrays {
|
||||
writer.write_u8((arr.nal_unit_type & 0b111111) | u8::from(arr.completeness) << 7)?;
|
||||
writer.write_u16::<BigEndian>(arr.nalus.len() as _)?;
|
||||
|
||||
for nalu in &arr.nalus {
|
||||
writer.write_u16::<BigEndian>(nalu.size)?;
|
||||
writer.write_all(&nalu.data)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_hev1() {
|
||||
let src_box = Hev1Box {
|
||||
data_reference_index: 1,
|
||||
width: 320,
|
||||
height: 240,
|
||||
horizresolution: FixedPointU16::new(0x48),
|
||||
vertresolution: FixedPointU16::new(0x48),
|
||||
frame_count: 1,
|
||||
depth: 24,
|
||||
hvcc: HvcCBox {
|
||||
configuration_version: 1,
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::Hev1Box);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = Hev1Box::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
253
src/mp4box/ilst.rs
Normal file
253
src/mp4box/ilst.rs
Normal file
|
@ -0,0 +1,253 @@
|
|||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::io::{Read, Seek};
|
||||
|
||||
use byteorder::ByteOrder;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::mp4box::data::DataBox;
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct IlstBox {
|
||||
pub items: HashMap<MetadataKey, IlstItemBox>,
|
||||
}
|
||||
|
||||
impl IlstBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::IlstBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
for item in self.items.values() {
|
||||
size += item.get_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for IlstBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("item_count={}", self.items.len());
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for IlstBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut items = HashMap::new();
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"ilst box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
match name {
|
||||
BoxType::NameBox => {
|
||||
items.insert(MetadataKey::Title, IlstItemBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::DayBox => {
|
||||
items.insert(MetadataKey::Year, IlstItemBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::CovrBox => {
|
||||
items.insert(MetadataKey::Poster, IlstItemBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::DescBox => {
|
||||
items.insert(MetadataKey::Summary, IlstItemBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(IlstBox { items })
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for IlstBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
for (key, value) in &self.items {
|
||||
let name = match key {
|
||||
MetadataKey::Title => BoxType::NameBox,
|
||||
MetadataKey::Year => BoxType::DayBox,
|
||||
MetadataKey::Poster => BoxType::CovrBox,
|
||||
MetadataKey::Summary => BoxType::DescBox,
|
||||
};
|
||||
BoxHeader::new(name, value.get_size()).write(writer)?;
|
||||
value.data.write_box(writer)?;
|
||||
}
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct IlstItemBox {
|
||||
pub data: DataBox,
|
||||
}
|
||||
|
||||
impl IlstItemBox {
|
||||
fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + self.data.box_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for IlstItemBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut data = None;
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"ilst item box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
match name {
|
||||
BoxType::DataBox => {
|
||||
data = Some(DataBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if data.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::DataBox));
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(IlstItemBox {
|
||||
data: data.unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Metadata<'a> for IlstBox {
|
||||
fn title(&self) -> Option<Cow<str>> {
|
||||
self.items.get(&MetadataKey::Title).map(item_to_str)
|
||||
}
|
||||
|
||||
fn year(&self) -> Option<u32> {
|
||||
self.items.get(&MetadataKey::Year).and_then(item_to_u32)
|
||||
}
|
||||
|
||||
fn poster(&self) -> Option<&[u8]> {
|
||||
self.items.get(&MetadataKey::Poster).map(item_to_bytes)
|
||||
}
|
||||
|
||||
fn summary(&self) -> Option<Cow<str>> {
|
||||
self.items.get(&MetadataKey::Summary).map(item_to_str)
|
||||
}
|
||||
}
|
||||
|
||||
fn item_to_bytes(item: &IlstItemBox) -> &[u8] {
|
||||
&item.data.data
|
||||
}
|
||||
|
||||
fn item_to_str(item: &IlstItemBox) -> Cow<str> {
|
||||
String::from_utf8_lossy(&item.data.data)
|
||||
}
|
||||
|
||||
fn item_to_u32(item: &IlstItemBox) -> Option<u32> {
|
||||
match item.data.data_type {
|
||||
DataType::Binary if item.data.data.len() == 4 => Some(BigEndian::read_u32(&item.data.data)),
|
||||
DataType::Text => String::from_utf8_lossy(&item.data.data).parse::<u32>().ok(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_ilst() {
|
||||
let src_year = IlstItemBox {
|
||||
data: DataBox {
|
||||
data_type: DataType::Text,
|
||||
data: b"test_year".to_vec(),
|
||||
},
|
||||
};
|
||||
let src_box = IlstBox {
|
||||
items: [
|
||||
(MetadataKey::Title, IlstItemBox::default()),
|
||||
(MetadataKey::Year, src_year),
|
||||
(MetadataKey::Poster, IlstItemBox::default()),
|
||||
(MetadataKey::Summary, IlstItemBox::default()),
|
||||
]
|
||||
.into(),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::IlstBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = IlstBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ilst_empty() {
|
||||
let src_box = IlstBox::default();
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::IlstBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = IlstBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
231
src/mp4box/mdhd.rs
Normal file
231
src/mp4box/mdhd.rs
Normal file
|
@ -0,0 +1,231 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::char::{decode_utf16, REPLACEMENT_CHARACTER};
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct MdhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub creation_time: u64,
|
||||
pub modification_time: u64,
|
||||
pub timescale: u32,
|
||||
pub duration: u64,
|
||||
pub language: String,
|
||||
}
|
||||
|
||||
impl MdhdBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::MdhdBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
|
||||
if self.version == 1 {
|
||||
size += 28;
|
||||
} else if self.version == 0 {
|
||||
size += 16;
|
||||
}
|
||||
size += 4;
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MdhdBox {
|
||||
fn default() -> Self {
|
||||
MdhdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
creation_time: 0,
|
||||
modification_time: 0,
|
||||
timescale: 1000,
|
||||
duration: 0,
|
||||
language: String::from("und"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MdhdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!(
|
||||
"creation_time={} timescale={} duration={} language={}",
|
||||
self.creation_time, self.timescale, self.duration, self.language
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MdhdBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let (creation_time, modification_time, timescale, duration) = if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else if version == 0 {
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
} else {
|
||||
return Err(Error::InvalidData("version must be 0 or 1"));
|
||||
};
|
||||
let language_code = reader.read_u16::<BigEndian>()?;
|
||||
let language = language_string(language_code);
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(MdhdBox {
|
||||
version,
|
||||
flags,
|
||||
creation_time,
|
||||
modification_time,
|
||||
timescale,
|
||||
duration,
|
||||
language,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for MdhdBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
if self.version == 1 {
|
||||
writer.write_u64::<BigEndian>(self.creation_time)?;
|
||||
writer.write_u64::<BigEndian>(self.modification_time)?;
|
||||
writer.write_u32::<BigEndian>(self.timescale)?;
|
||||
writer.write_u64::<BigEndian>(self.duration)?;
|
||||
} else if self.version == 0 {
|
||||
writer.write_u32::<BigEndian>(self.creation_time as u32)?;
|
||||
writer.write_u32::<BigEndian>(self.modification_time as u32)?;
|
||||
writer.write_u32::<BigEndian>(self.timescale)?;
|
||||
writer.write_u32::<BigEndian>(self.duration as u32)?;
|
||||
} else {
|
||||
return Err(Error::InvalidData("version must be 0 or 1"));
|
||||
}
|
||||
|
||||
let language_code = language_code(&self.language);
|
||||
writer.write_u16::<BigEndian>(language_code)?;
|
||||
writer.write_u16::<BigEndian>(0)?; // pre-defined
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
fn language_string(language: u16) -> String {
|
||||
let mut lang: [u16; 3] = [0; 3];
|
||||
|
||||
lang[0] = ((language >> 10) & 0x1F) + 0x60;
|
||||
lang[1] = ((language >> 5) & 0x1F) + 0x60;
|
||||
lang[2] = ((language) & 0x1F) + 0x60;
|
||||
|
||||
// Decode utf-16 encoded bytes into a string.
|
||||
let lang_str = decode_utf16(lang.iter().cloned())
|
||||
.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
|
||||
.collect::<String>();
|
||||
|
||||
lang_str
|
||||
}
|
||||
|
||||
fn language_code(language: &str) -> u16 {
|
||||
let mut lang = language.encode_utf16();
|
||||
let mut code = (lang.next().unwrap_or(0) & 0x1F) << 10;
|
||||
code += (lang.next().unwrap_or(0) & 0x1F) << 5;
|
||||
code += lang.next().unwrap_or(0) & 0x1F;
|
||||
code
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
fn test_language_code(lang: &str) {
|
||||
let code = language_code(lang);
|
||||
let lang2 = language_string(code);
|
||||
assert_eq!(lang, lang2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_language_codes() {
|
||||
test_language_code("und");
|
||||
test_language_code("eng");
|
||||
test_language_code("kor");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mdhd32() {
|
||||
let src_box = MdhdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
creation_time: 100,
|
||||
modification_time: 200,
|
||||
timescale: 48000,
|
||||
duration: 30439936,
|
||||
language: String::from("und"),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MdhdBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = MdhdBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mdhd64() {
|
||||
let src_box = MdhdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
creation_time: 100,
|
||||
modification_time: 200,
|
||||
timescale: 48000,
|
||||
duration: 30439936,
|
||||
language: String::from("eng"),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MdhdBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = MdhdBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
113
src/mp4box/mdia.rs
Normal file
113
src/mp4box/mdia.rs
Normal file
|
@ -0,0 +1,113 @@
|
|||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{hdlr::HdlrBox, mdhd::MdhdBox, minf::MinfBox};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct MdiaBox {
|
||||
pub mdhd: MdhdBox,
|
||||
pub hdlr: HdlrBox,
|
||||
pub minf: MinfBox,
|
||||
}
|
||||
|
||||
impl MdiaBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::MdiaBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + self.mdhd.box_size() + self.hdlr.box_size() + self.minf.box_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MdiaBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MdiaBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut mdhd = None;
|
||||
let mut hdlr = None;
|
||||
let mut minf = None;
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"mdia box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
match name {
|
||||
BoxType::MdhdBox => {
|
||||
mdhd = Some(MdhdBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::HdlrBox => {
|
||||
hdlr = Some(HdlrBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::MinfBox => {
|
||||
minf = Some(MinfBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if mdhd.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::MdhdBox));
|
||||
}
|
||||
if hdlr.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::HdlrBox));
|
||||
}
|
||||
if minf.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::MinfBox));
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(MdiaBox {
|
||||
mdhd: mdhd.unwrap(),
|
||||
hdlr: hdlr.unwrap(),
|
||||
minf: minf.unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for MdiaBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
self.mdhd.write_box(writer)?;
|
||||
self.hdlr.write_box(writer)?;
|
||||
self.minf.write_box(writer)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
137
src/mp4box/mehd.rs
Normal file
137
src/mp4box/mehd.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
|
||||
pub struct MehdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub fragment_duration: u64,
|
||||
}
|
||||
|
||||
impl MehdBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::MehdBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
|
||||
if self.version == 1 {
|
||||
size += 8;
|
||||
} else if self.version == 0 {
|
||||
size += 4;
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MehdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("fragment_duration={}", self.fragment_duration);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MehdBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let fragment_duration = if version == 1 {
|
||||
reader.read_u64::<BigEndian>()?
|
||||
} else if version == 0 {
|
||||
reader.read_u32::<BigEndian>()? as u64
|
||||
} else {
|
||||
return Err(Error::InvalidData("version must be 0 or 1"));
|
||||
};
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(MehdBox {
|
||||
version,
|
||||
flags,
|
||||
fragment_duration,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for MehdBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
if self.version == 1 {
|
||||
writer.write_u64::<BigEndian>(self.fragment_duration)?;
|
||||
} else if self.version == 0 {
|
||||
writer.write_u32::<BigEndian>(self.fragment_duration as u32)?;
|
||||
} else {
|
||||
return Err(Error::InvalidData("version must be 0 or 1"));
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_mehd32() {
|
||||
let src_box = MehdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
fragment_duration: 32,
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MehdBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = MehdBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mehd64() {
|
||||
let src_box = MehdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
fragment_duration: 30439936,
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MehdBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = MehdBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
312
src/mp4box/meta.rs
Normal file
312
src/mp4box/meta.rs
Normal file
|
@ -0,0 +1,312 @@
|
|||
use std::io::{Read, Seek};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::mp4box::hdlr::HdlrBox;
|
||||
use crate::mp4box::ilst::IlstBox;
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
#[serde(tag = "hdlr")]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum MetaBox {
|
||||
Mdir {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
ilst: Option<IlstBox>,
|
||||
},
|
||||
|
||||
#[serde(skip)]
|
||||
Unknown {
|
||||
#[serde(skip)]
|
||||
hdlr: HdlrBox,
|
||||
|
||||
#[serde(skip)]
|
||||
data: Vec<(BoxType, Vec<u8>)>,
|
||||
},
|
||||
}
|
||||
|
||||
const MDIR: FourCC = FourCC { value: *b"mdir" };
|
||||
|
||||
impl MetaBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::MetaBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
match self {
|
||||
Self::Mdir { ilst } => {
|
||||
size += HdlrBox::default().box_size();
|
||||
if let Some(ilst) = ilst {
|
||||
size += ilst.box_size();
|
||||
}
|
||||
}
|
||||
Self::Unknown { hdlr, data } => {
|
||||
size += hdlr.box_size()
|
||||
+ data
|
||||
.iter()
|
||||
.map(|(_, data)| data.len() as u64 + HEADER_SIZE)
|
||||
.sum::<u64>()
|
||||
}
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MetaBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = match self {
|
||||
Self::Mdir { .. } => "hdlr=ilst".to_string(),
|
||||
Self::Unknown { hdlr, data } => {
|
||||
format!("hdlr={} data_len={}", hdlr.handler_type, data.len())
|
||||
}
|
||||
};
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MetaBox {
|
||||
fn default() -> Self {
|
||||
Self::Unknown {
|
||||
hdlr: Default::default(),
|
||||
data: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MetaBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let extended_header = reader.read_u32::<BigEndian>()?;
|
||||
if extended_header != 0 {
|
||||
// ISO mp4 requires this header (version & flags) to be 0. Some
|
||||
// files skip the extended header and directly start the hdlr box.
|
||||
let possible_hdlr = BoxType::from(reader.read_u32::<BigEndian>()?);
|
||||
if possible_hdlr == BoxType::HdlrBox {
|
||||
// This file skipped the extended header! Go back to start.
|
||||
reader.seek(SeekFrom::Current(-8))?;
|
||||
} else {
|
||||
// Looks like we actually have a bad version number or flags.
|
||||
let v = (extended_header >> 24) as u8;
|
||||
return Err(Error::UnsupportedBoxVersion(BoxType::MetaBox, v));
|
||||
}
|
||||
}
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size;
|
||||
|
||||
let content_start = current;
|
||||
|
||||
// find the hdlr box
|
||||
let mut hdlr = None;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::HdlrBox => {
|
||||
hdlr = Some(HdlrBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
let Some(hdlr) = hdlr else {
|
||||
return Err(Error::BoxNotFound(BoxType::HdlrBox));
|
||||
};
|
||||
|
||||
// rewind and handle the other boxes
|
||||
reader.seek(SeekFrom::Start(content_start))?;
|
||||
current = reader.stream_position()?;
|
||||
|
||||
let mut ilst = None;
|
||||
|
||||
match hdlr.handler_type {
|
||||
MDIR => {
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::IlstBox => {
|
||||
ilst = Some(IlstBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
Ok(MetaBox::Mdir { ilst })
|
||||
}
|
||||
_ => {
|
||||
let mut data = Vec::new();
|
||||
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
|
||||
match name {
|
||||
BoxType::HdlrBox => {
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
_ => {
|
||||
let mut box_data = vec![0; (s - HEADER_SIZE) as usize];
|
||||
reader.read_exact(&mut box_data)?;
|
||||
|
||||
data.push((name, box_data));
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
Ok(MetaBox::Unknown { hdlr, data })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for MetaBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, 0, 0)?;
|
||||
|
||||
let hdlr = match self {
|
||||
Self::Mdir { .. } => HdlrBox {
|
||||
handler_type: MDIR,
|
||||
..Default::default()
|
||||
},
|
||||
Self::Unknown { hdlr, .. } => hdlr.clone(),
|
||||
};
|
||||
hdlr.write_box(writer)?;
|
||||
|
||||
match self {
|
||||
Self::Mdir { ilst } => {
|
||||
if let Some(ilst) = ilst {
|
||||
ilst.write_box(writer)?;
|
||||
}
|
||||
}
|
||||
Self::Unknown { data, .. } => {
|
||||
for (box_type, data) in data {
|
||||
BoxHeader::new(*box_type, data.len() as u64 + HEADER_SIZE).write(writer)?;
|
||||
writer.write_all(data)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_meta_mdir_empty() {
|
||||
let src_box = MetaBox::Mdir { ilst: None };
|
||||
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MetaBox);
|
||||
assert_eq!(header.size, src_box.box_size());
|
||||
|
||||
let dst_box = MetaBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(dst_box, src_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_meta_mdir() {
|
||||
let src_box = MetaBox::Mdir {
|
||||
ilst: Some(IlstBox::default()),
|
||||
};
|
||||
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MetaBox);
|
||||
assert_eq!(header.size, src_box.box_size());
|
||||
|
||||
let dst_box = MetaBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(dst_box, src_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_meta_hdrl_non_first() {
|
||||
let data = b"\x00\x00\x00\x7fmeta\x00\x00\x00\x00\x00\x00\x00Qilst\x00\x00\x00I\xa9too\x00\x00\x00Adata\x00\x00\x00\x01\x00\x00\x00\x00TMPGEnc Video Mastering Works 7 Version 7.0.15.17\x00\x00\x00\"hdlr\x00\x00\x00\x00\x00\x00\x00\x00mdirappl\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
|
||||
let mut reader = Cursor::new(data);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MetaBox);
|
||||
|
||||
let meta_box = MetaBox::read_box(&mut reader, header.size).unwrap();
|
||||
|
||||
// this contains \xa9too box in the ilst
|
||||
// it designates the tool that created the file, but is not yet supported by this crate
|
||||
assert_eq!(
|
||||
meta_box,
|
||||
MetaBox::Mdir {
|
||||
ilst: Some(IlstBox::default())
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_meta_unknown() {
|
||||
let src_hdlr = HdlrBox {
|
||||
handler_type: FourCC::from(*b"test"),
|
||||
..Default::default()
|
||||
};
|
||||
let src_data = (BoxType::UnknownBox(0x42494241), b"123".to_vec());
|
||||
let src_box = MetaBox::Unknown {
|
||||
hdlr: src_hdlr,
|
||||
data: vec![src_data],
|
||||
};
|
||||
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MetaBox);
|
||||
assert_eq!(header.size, src_box.box_size());
|
||||
|
||||
let dst_box = MetaBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(dst_box, src_box);
|
||||
}
|
||||
}
|
107
src/mp4box/mfhd.rs
Normal file
107
src/mp4box/mfhd.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct MfhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub sequence_number: u32,
|
||||
}
|
||||
|
||||
impl Default for MfhdBox {
|
||||
fn default() -> Self {
|
||||
MfhdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
sequence_number: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MfhdBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::MfhdBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 4
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MfhdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("sequence_number={}", self.sequence_number);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MfhdBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
let sequence_number = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(MfhdBox {
|
||||
version,
|
||||
flags,
|
||||
sequence_number,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for MfhdBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
writer.write_u32::<BigEndian>(self.sequence_number)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_mfhd() {
|
||||
let src_box = MfhdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
sequence_number: 1,
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MfhdBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = MfhdBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
134
src/mp4box/minf.rs
Normal file
134
src/mp4box/minf.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{dinf::DinfBox, smhd::SmhdBox, stbl::StblBox, vmhd::VmhdBox};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct MinfBox {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub vmhd: Option<VmhdBox>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub smhd: Option<SmhdBox>,
|
||||
|
||||
pub dinf: DinfBox,
|
||||
pub stbl: StblBox,
|
||||
}
|
||||
|
||||
impl MinfBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::MinfBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(ref vmhd) = self.vmhd {
|
||||
size += vmhd.box_size();
|
||||
}
|
||||
if let Some(ref smhd) = self.smhd {
|
||||
size += smhd.box_size();
|
||||
}
|
||||
size += self.dinf.box_size();
|
||||
size += self.stbl.box_size();
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MinfBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MinfBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut vmhd = None;
|
||||
let mut smhd = None;
|
||||
let mut dinf = None;
|
||||
let mut stbl = None;
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"minf box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
match name {
|
||||
BoxType::VmhdBox => {
|
||||
vmhd = Some(VmhdBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::SmhdBox => {
|
||||
smhd = Some(SmhdBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::DinfBox => {
|
||||
dinf = Some(DinfBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::StblBox => {
|
||||
stbl = Some(StblBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if dinf.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::DinfBox));
|
||||
}
|
||||
if stbl.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::StblBox));
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(MinfBox {
|
||||
vmhd,
|
||||
smhd,
|
||||
dinf: dinf.unwrap(),
|
||||
stbl: stbl.unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for MinfBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
if let Some(ref vmhd) = self.vmhd {
|
||||
vmhd.write_box(writer)?;
|
||||
}
|
||||
if let Some(ref smhd) = self.smhd {
|
||||
smhd.write_box(writer)?;
|
||||
}
|
||||
self.dinf.write_box(writer)?;
|
||||
self.stbl.write_box(writer)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
434
src/mp4box/mod.rs
Normal file
434
src/mp4box/mod.rs
Normal file
|
@ -0,0 +1,434 @@
|
|||
//! All ISO-MP4 boxes (atoms) and operations.
|
||||
//!
|
||||
//! * [ISO/IEC 14496-12](https://en.wikipedia.org/wiki/MPEG-4_Part_14) - ISO Base Media File Format (QuickTime, MPEG-4, etc)
|
||||
//! * [ISO/IEC 14496-14](https://en.wikipedia.org/wiki/MPEG-4_Part_14) - MP4 file format
|
||||
//! * ISO/IEC 14496-17 - Streaming text format
|
||||
//! * [ISO 23009-1](https://www.iso.org/standard/79329.html) -Dynamic adaptive streaming over HTTP (DASH)
|
||||
//!
|
||||
//! http://developer.apple.com/documentation/QuickTime/QTFF/index.html
|
||||
//! http://www.adobe.com/devnet/video/articles/mp4_movie_atom.html
|
||||
//! http://mp4ra.org/#/atoms
|
||||
//!
|
||||
//! Supported Atoms:
|
||||
//! ftyp
|
||||
//! moov
|
||||
//! mvhd
|
||||
//! udta
|
||||
//! meta
|
||||
//! ilst
|
||||
//! data
|
||||
//! trak
|
||||
//! tkhd
|
||||
//! mdia
|
||||
//! mdhd
|
||||
//! hdlr
|
||||
//! minf
|
||||
//! stbl
|
||||
//! stsd
|
||||
//! avc1
|
||||
//! hev1
|
||||
//! mp4a
|
||||
//! tx3g
|
||||
//! stts
|
||||
//! stsc
|
||||
//! stsz
|
||||
//! stss
|
||||
//! stco
|
||||
//! co64
|
||||
//! ctts
|
||||
//! dinf
|
||||
//! dref
|
||||
//! smhd
|
||||
//! vmhd
|
||||
//! edts
|
||||
//! elst
|
||||
//! mvex
|
||||
//! mehd
|
||||
//! trex
|
||||
//! emsg
|
||||
//! moof
|
||||
//! mfhd
|
||||
//! traf
|
||||
//! tfhd
|
||||
//! tfdt
|
||||
//! trun
|
||||
//! mdat
|
||||
//! free
|
||||
//!
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::convert::TryInto;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub(crate) mod avc1;
|
||||
pub(crate) mod co64;
|
||||
pub(crate) mod ctts;
|
||||
pub(crate) mod data;
|
||||
pub(crate) mod dinf;
|
||||
pub(crate) mod edts;
|
||||
pub(crate) mod elst;
|
||||
pub(crate) mod emsg;
|
||||
pub(crate) mod ftyp;
|
||||
pub(crate) mod hdlr;
|
||||
pub(crate) mod hev1;
|
||||
pub(crate) mod ilst;
|
||||
pub(crate) mod mdhd;
|
||||
pub(crate) mod mdia;
|
||||
pub(crate) mod mehd;
|
||||
pub(crate) mod meta;
|
||||
pub(crate) mod mfhd;
|
||||
pub(crate) mod minf;
|
||||
pub(crate) mod moof;
|
||||
pub(crate) mod moov;
|
||||
pub(crate) mod mp4a;
|
||||
pub(crate) mod mvex;
|
||||
pub(crate) mod mvhd;
|
||||
pub(crate) mod smhd;
|
||||
pub(crate) mod stbl;
|
||||
pub(crate) mod stco;
|
||||
pub(crate) mod stsc;
|
||||
pub(crate) mod stsd;
|
||||
pub(crate) mod stss;
|
||||
pub(crate) mod stsz;
|
||||
pub(crate) mod stts;
|
||||
pub(crate) mod tfdt;
|
||||
pub(crate) mod tfhd;
|
||||
pub(crate) mod tkhd;
|
||||
pub(crate) mod traf;
|
||||
pub(crate) mod trak;
|
||||
pub(crate) mod trex;
|
||||
pub(crate) mod trun;
|
||||
pub(crate) mod tx3g;
|
||||
pub(crate) mod udta;
|
||||
pub(crate) mod vmhd;
|
||||
pub(crate) mod vp09;
|
||||
pub(crate) mod vpcc;
|
||||
|
||||
pub use avc1::Avc1Box;
|
||||
pub use co64::Co64Box;
|
||||
pub use ctts::CttsBox;
|
||||
pub use data::DataBox;
|
||||
pub use dinf::DinfBox;
|
||||
pub use edts::EdtsBox;
|
||||
pub use elst::ElstBox;
|
||||
pub use emsg::EmsgBox;
|
||||
pub use ftyp::FtypBox;
|
||||
pub use hdlr::HdlrBox;
|
||||
pub use hev1::Hev1Box;
|
||||
pub use ilst::IlstBox;
|
||||
pub use mdhd::MdhdBox;
|
||||
pub use mdia::MdiaBox;
|
||||
pub use mehd::MehdBox;
|
||||
pub use meta::MetaBox;
|
||||
pub use mfhd::MfhdBox;
|
||||
pub use minf::MinfBox;
|
||||
pub use moof::MoofBox;
|
||||
pub use moov::MoovBox;
|
||||
pub use mp4a::Mp4aBox;
|
||||
pub use mvex::MvexBox;
|
||||
pub use mvhd::MvhdBox;
|
||||
pub use smhd::SmhdBox;
|
||||
pub use stbl::StblBox;
|
||||
pub use stco::StcoBox;
|
||||
pub use stsc::StscBox;
|
||||
pub use stsd::StsdBox;
|
||||
pub use stss::StssBox;
|
||||
pub use stsz::StszBox;
|
||||
pub use stts::SttsBox;
|
||||
pub use tfdt::TfdtBox;
|
||||
pub use tfhd::TfhdBox;
|
||||
pub use tkhd::TkhdBox;
|
||||
pub use traf::TrafBox;
|
||||
pub use trak::TrakBox;
|
||||
pub use trex::TrexBox;
|
||||
pub use trun::TrunBox;
|
||||
pub use tx3g::Tx3gBox;
|
||||
pub use udta::UdtaBox;
|
||||
pub use vmhd::VmhdBox;
|
||||
pub use vp09::Vp09Box;
|
||||
pub use vpcc::VpccBox;
|
||||
|
||||
pub const HEADER_SIZE: u64 = 8;
|
||||
// const HEADER_LARGE_SIZE: u64 = 16;
|
||||
pub const HEADER_EXT_SIZE: u64 = 4;
|
||||
|
||||
macro_rules! boxtype {
|
||||
($( $name:ident => $value:expr ),*) => {
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BoxType {
|
||||
$( $name, )*
|
||||
UnknownBox(u32),
|
||||
}
|
||||
|
||||
impl From<u32> for BoxType {
|
||||
fn from(t: u32) -> BoxType {
|
||||
match t {
|
||||
$( $value => BoxType::$name, )*
|
||||
_ => BoxType::UnknownBox(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BoxType> for u32 {
|
||||
fn from(b: BoxType) -> u32 {
|
||||
match b {
|
||||
$( BoxType::$name => $value, )*
|
||||
BoxType::UnknownBox(t) => t,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boxtype! {
|
||||
FtypBox => 0x66747970,
|
||||
MvhdBox => 0x6d766864,
|
||||
MfhdBox => 0x6d666864,
|
||||
FreeBox => 0x66726565,
|
||||
MdatBox => 0x6d646174,
|
||||
MoovBox => 0x6d6f6f76,
|
||||
MvexBox => 0x6d766578,
|
||||
MehdBox => 0x6d656864,
|
||||
TrexBox => 0x74726578,
|
||||
EmsgBox => 0x656d7367,
|
||||
MoofBox => 0x6d6f6f66,
|
||||
TkhdBox => 0x746b6864,
|
||||
TfhdBox => 0x74666864,
|
||||
TfdtBox => 0x74666474,
|
||||
EdtsBox => 0x65647473,
|
||||
MdiaBox => 0x6d646961,
|
||||
ElstBox => 0x656c7374,
|
||||
MdhdBox => 0x6d646864,
|
||||
HdlrBox => 0x68646c72,
|
||||
MinfBox => 0x6d696e66,
|
||||
VmhdBox => 0x766d6864,
|
||||
StblBox => 0x7374626c,
|
||||
StsdBox => 0x73747364,
|
||||
SttsBox => 0x73747473,
|
||||
CttsBox => 0x63747473,
|
||||
StssBox => 0x73747373,
|
||||
StscBox => 0x73747363,
|
||||
StszBox => 0x7374737A,
|
||||
StcoBox => 0x7374636F,
|
||||
Co64Box => 0x636F3634,
|
||||
TrakBox => 0x7472616b,
|
||||
TrafBox => 0x74726166,
|
||||
TrunBox => 0x7472756E,
|
||||
UdtaBox => 0x75647461,
|
||||
MetaBox => 0x6d657461,
|
||||
DinfBox => 0x64696e66,
|
||||
DrefBox => 0x64726566,
|
||||
UrlBox => 0x75726C20,
|
||||
SmhdBox => 0x736d6864,
|
||||
Avc1Box => 0x61766331,
|
||||
AvcCBox => 0x61766343,
|
||||
Hev1Box => 0x68657631,
|
||||
HvcCBox => 0x68766343,
|
||||
Mp4aBox => 0x6d703461,
|
||||
EsdsBox => 0x65736473,
|
||||
Tx3gBox => 0x74783367,
|
||||
VpccBox => 0x76706343,
|
||||
Vp09Box => 0x76703039,
|
||||
DataBox => 0x64617461,
|
||||
IlstBox => 0x696c7374,
|
||||
NameBox => 0xa96e616d,
|
||||
DayBox => 0xa9646179,
|
||||
CovrBox => 0x636f7672,
|
||||
DescBox => 0x64657363,
|
||||
WideBox => 0x77696465,
|
||||
WaveBox => 0x77617665
|
||||
}
|
||||
|
||||
pub trait Mp4Box: Sized {
|
||||
fn box_type(&self) -> BoxType;
|
||||
fn box_size(&self) -> u64;
|
||||
fn to_json(&self) -> Result<String>;
|
||||
fn summary(&self) -> Result<String>;
|
||||
}
|
||||
|
||||
pub trait ReadBox<T>: Sized {
|
||||
fn read_box(_: T, size: u64) -> Result<Self>;
|
||||
}
|
||||
|
||||
pub trait WriteBox<T>: Sized {
|
||||
fn write_box(&self, _: T) -> Result<u64>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BoxHeader {
|
||||
pub name: BoxType,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
impl BoxHeader {
|
||||
pub fn new(name: BoxType, size: u64) -> Self {
|
||||
Self { name, size }
|
||||
}
|
||||
|
||||
// TODO: if size is 0, then this box is the last one in the file
|
||||
pub fn read<R: Read>(reader: &mut R) -> Result<Self> {
|
||||
// Create and read to buf.
|
||||
let mut buf = [0u8; 8]; // 8 bytes for box header.
|
||||
reader.read_exact(&mut buf)?;
|
||||
|
||||
// Get size.
|
||||
let s = buf[0..4].try_into().unwrap();
|
||||
let size = u32::from_be_bytes(s);
|
||||
|
||||
// Get box type string.
|
||||
let t = buf[4..8].try_into().unwrap();
|
||||
let typ = u32::from_be_bytes(t);
|
||||
|
||||
// Get largesize if size is 1
|
||||
if size == 1 {
|
||||
reader.read_exact(&mut buf)?;
|
||||
let largesize = u64::from_be_bytes(buf);
|
||||
|
||||
Ok(BoxHeader {
|
||||
name: BoxType::from(typ),
|
||||
|
||||
// Subtract the length of the serialized largesize, as callers assume `size - HEADER_SIZE` is the length
|
||||
// of the box data. Disallow `largesize < 16`, or else a largesize of 8 will result in a BoxHeader::size
|
||||
// of 0, incorrectly indicating that the box data extends to the end of the stream.
|
||||
size: match largesize {
|
||||
0 => 0,
|
||||
1..=15 => return Err(Error::InvalidData("64-bit box size too small")),
|
||||
16..=u64::MAX => largesize - 8,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
Ok(BoxHeader {
|
||||
name: BoxType::from(typ),
|
||||
size: size as u64,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write<W: Write>(&self, writer: &mut W) -> Result<u64> {
|
||||
if self.size > u32::MAX as u64 {
|
||||
writer.write_u32::<BigEndian>(1)?;
|
||||
writer.write_u32::<BigEndian>(self.name.into())?;
|
||||
writer.write_u64::<BigEndian>(self.size)?;
|
||||
Ok(16)
|
||||
} else {
|
||||
writer.write_u32::<BigEndian>(self.size as u32)?;
|
||||
writer.write_u32::<BigEndian>(self.name.into())?;
|
||||
Ok(8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_box_header_ext<R: Read>(reader: &mut R) -> Result<(u8, u32)> {
|
||||
let version = reader.read_u8()?;
|
||||
let flags = reader.read_u24::<BigEndian>()?;
|
||||
Ok((version, flags))
|
||||
}
|
||||
|
||||
pub fn write_box_header_ext<W: Write>(w: &mut W, v: u8, f: u32) -> Result<u64> {
|
||||
w.write_u8(v)?;
|
||||
w.write_u24::<BigEndian>(f)?;
|
||||
Ok(4)
|
||||
}
|
||||
|
||||
pub fn box_start<R: Seek>(seeker: &mut R) -> Result<u64> {
|
||||
Ok(seeker.stream_position()? - HEADER_SIZE)
|
||||
}
|
||||
|
||||
pub fn skip_bytes<S: Seek>(seeker: &mut S, size: u64) -> Result<()> {
|
||||
seeker.seek(SeekFrom::Current(size as i64))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn skip_bytes_to<S: Seek>(seeker: &mut S, pos: u64) -> Result<()> {
|
||||
seeker.seek(SeekFrom::Start(pos))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn skip_box<S: Seek>(seeker: &mut S, size: u64) -> Result<()> {
|
||||
let start = box_start(seeker)?;
|
||||
skip_bytes_to(seeker, start + size)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_zeros<W: Write>(writer: &mut W, size: u64) -> Result<()> {
|
||||
for _ in 0..size {
|
||||
writer.write_u8(0)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
mod value_u32 {
|
||||
use crate::types::FixedPointU16;
|
||||
use serde::{self, Serializer};
|
||||
|
||||
pub fn serialize<S>(fixed: &FixedPointU16, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_u16(fixed.value())
|
||||
}
|
||||
}
|
||||
|
||||
mod value_i16 {
|
||||
use crate::types::FixedPointI8;
|
||||
use serde::{self, Serializer};
|
||||
|
||||
pub fn serialize<S>(fixed: &FixedPointI8, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_i8(fixed.value())
|
||||
}
|
||||
}
|
||||
|
||||
mod value_u8 {
|
||||
use crate::types::FixedPointU8;
|
||||
use serde::{self, Serializer};
|
||||
|
||||
pub fn serialize<S>(fixed: &FixedPointU8, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_u8(fixed.value())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_fourcc() {
|
||||
let ftyp_fcc = 0x66747970;
|
||||
let ftyp_value = FourCC::from(ftyp_fcc);
|
||||
assert_eq!(&ftyp_value.value[..], b"ftyp");
|
||||
let ftyp_fcc2: u32 = ftyp_value.into();
|
||||
assert_eq!(ftyp_fcc, ftyp_fcc2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_largesize_too_small() {
|
||||
let error = BoxHeader::read(&mut &[0, 0, 0, 1, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 7][..]);
|
||||
assert!(matches!(error, Err(Error::InvalidData(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_largesize() {
|
||||
let error = BoxHeader::read(&mut &[0, 0, 0, 1, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 8][..]);
|
||||
assert!(matches!(error, Err(Error::InvalidData(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nonzero_largesize_too_small() {
|
||||
let error = BoxHeader::read(&mut &[0, 0, 0, 1, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 15][..]);
|
||||
assert!(matches!(error, Err(Error::InvalidData(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_largesize() {
|
||||
let header = BoxHeader::read(&mut &[0, 0, 0, 1, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 16][..]);
|
||||
assert!(matches!(header, Ok(BoxHeader { size: 8, .. })));
|
||||
}
|
||||
}
|
107
src/mp4box/moof.rs
Normal file
107
src/mp4box/moof.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{mfhd::MfhdBox, traf::TrafBox};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct MoofBox {
|
||||
pub mfhd: MfhdBox,
|
||||
|
||||
#[serde(rename = "traf")]
|
||||
pub trafs: Vec<TrafBox>,
|
||||
}
|
||||
|
||||
impl MoofBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::MoofBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + self.mfhd.box_size();
|
||||
for traf in self.trafs.iter() {
|
||||
size += traf.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MoofBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("trafs={}", self.trafs.len());
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MoofBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut mfhd = None;
|
||||
let mut trafs = Vec::new();
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"moof box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
match name {
|
||||
BoxType::MfhdBox => {
|
||||
mfhd = Some(MfhdBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::TrafBox => {
|
||||
let traf = TrafBox::read_box(reader, s)?;
|
||||
trafs.push(traf);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if mfhd.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::MfhdBox));
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(MoofBox {
|
||||
mfhd: mfhd.unwrap(),
|
||||
trafs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for MoofBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
self.mfhd.write_box(writer)?;
|
||||
for traf in self.trafs.iter() {
|
||||
traf.write_box(writer)?;
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
}
|
192
src/mp4box/moov.rs
Normal file
192
src/mp4box/moov.rs
Normal file
|
@ -0,0 +1,192 @@
|
|||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::meta::MetaBox;
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{mvex::MvexBox, mvhd::MvhdBox, trak::TrakBox, udta::UdtaBox};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct MoovBox {
|
||||
pub mvhd: MvhdBox,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<MetaBox>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mvex: Option<MvexBox>,
|
||||
|
||||
#[serde(rename = "trak")]
|
||||
pub traks: Vec<TrakBox>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub udta: Option<UdtaBox>,
|
||||
}
|
||||
|
||||
impl MoovBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::MoovBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + self.mvhd.box_size();
|
||||
for trak in self.traks.iter() {
|
||||
size += trak.box_size();
|
||||
}
|
||||
if let Some(meta) = &self.meta {
|
||||
size += meta.box_size();
|
||||
}
|
||||
if let Some(udta) = &self.udta {
|
||||
size += udta.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MoovBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("traks={}", self.traks.len());
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut mvhd = None;
|
||||
let mut meta = None;
|
||||
let mut udta = None;
|
||||
let mut mvex = None;
|
||||
let mut traks = Vec::new();
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"moov box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
match name {
|
||||
BoxType::MvhdBox => {
|
||||
mvhd = Some(MvhdBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::MetaBox => {
|
||||
meta = Some(MetaBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::MvexBox => {
|
||||
mvex = Some(MvexBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::TrakBox => {
|
||||
let trak = TrakBox::read_box(reader, s)?;
|
||||
traks.push(trak);
|
||||
}
|
||||
BoxType::UdtaBox => {
|
||||
udta = Some(UdtaBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if mvhd.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::MvhdBox));
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(MoovBox {
|
||||
mvhd: mvhd.unwrap(),
|
||||
meta,
|
||||
udta,
|
||||
mvex,
|
||||
traks,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for MoovBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
self.mvhd.write_box(writer)?;
|
||||
for trak in self.traks.iter() {
|
||||
trak.write_box(writer)?;
|
||||
}
|
||||
if let Some(meta) = &self.meta {
|
||||
meta.write_box(writer)?;
|
||||
}
|
||||
if let Some(udta) = &self.udta {
|
||||
udta.write_box(writer)?;
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_moov() {
|
||||
let src_box = MoovBox {
|
||||
mvhd: MvhdBox::default(),
|
||||
mvex: None, // XXX mvex is not written currently
|
||||
traks: vec![],
|
||||
meta: Some(MetaBox::default()),
|
||||
udta: Some(UdtaBox::default()),
|
||||
};
|
||||
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MoovBox);
|
||||
assert_eq!(header.size, src_box.box_size());
|
||||
|
||||
let dst_box = MoovBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(dst_box, src_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_moov_empty() {
|
||||
let src_box = MoovBox::default();
|
||||
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MoovBox);
|
||||
assert_eq!(header.size, src_box.box_size());
|
||||
|
||||
let dst_box = MoovBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(dst_box, src_box);
|
||||
}
|
||||
}
|
683
src/mp4box/mp4a.rs
Normal file
683
src/mp4box/mp4a.rs
Normal file
|
@ -0,0 +1,683 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Mp4aBox {
|
||||
pub data_reference_index: u16,
|
||||
pub channelcount: u16,
|
||||
pub samplesize: u16,
|
||||
|
||||
#[serde(with = "value_u32")]
|
||||
pub samplerate: FixedPointU16,
|
||||
pub esds: Option<EsdsBox>,
|
||||
}
|
||||
|
||||
impl Default for Mp4aBox {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
data_reference_index: 0,
|
||||
channelcount: 2,
|
||||
samplesize: 16,
|
||||
samplerate: FixedPointU16::new(48000),
|
||||
esds: Some(EsdsBox::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4aBox {
|
||||
pub fn new(config: &AacConfig) -> Self {
|
||||
Self {
|
||||
data_reference_index: 1,
|
||||
channelcount: config.chan_conf as u16,
|
||||
samplesize: 16,
|
||||
samplerate: FixedPointU16::new(config.freq_index.freq() as u16),
|
||||
esds: Some(EsdsBox::new(config)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::Mp4aBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + 8 + 20;
|
||||
if let Some(ref esds) = self.esds {
|
||||
size += esds.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for Mp4aBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!(
|
||||
"channel_count={} sample_size={} sample_rate={}",
|
||||
self.channelcount,
|
||||
self.samplesize,
|
||||
self.samplerate.value()
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Mp4aBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // reserved
|
||||
reader.read_u16::<BigEndian>()?; // reserved
|
||||
let data_reference_index = reader.read_u16::<BigEndian>()?;
|
||||
let version = reader.read_u16::<BigEndian>()?;
|
||||
reader.read_u16::<BigEndian>()?; // reserved
|
||||
reader.read_u32::<BigEndian>()?; // reserved
|
||||
let channelcount = reader.read_u16::<BigEndian>()?;
|
||||
let samplesize = reader.read_u16::<BigEndian>()?;
|
||||
reader.read_u32::<BigEndian>()?; // pre-defined, reserved
|
||||
let samplerate = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
|
||||
|
||||
if version == 1 {
|
||||
// Skip QTFF
|
||||
reader.read_u64::<BigEndian>()?;
|
||||
reader.read_u64::<BigEndian>()?;
|
||||
}
|
||||
|
||||
// Find esds in mp4a or wave
|
||||
let mut esds = None;
|
||||
let end = start + size;
|
||||
loop {
|
||||
let current = reader.stream_position()?;
|
||||
if current >= end {
|
||||
break;
|
||||
}
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"mp4a box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
if name == BoxType::EsdsBox {
|
||||
esds = Some(EsdsBox::read_box(reader, s)?);
|
||||
break;
|
||||
} else if name == BoxType::WaveBox {
|
||||
// Typically contains frma, mp4a, esds, and a terminator atom
|
||||
} else {
|
||||
// Skip boxes
|
||||
let skip_to = current + s;
|
||||
skip_bytes_to(reader, skip_to)?;
|
||||
}
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, end)?;
|
||||
|
||||
Ok(Mp4aBox {
|
||||
data_reference_index,
|
||||
channelcount,
|
||||
samplesize,
|
||||
samplerate,
|
||||
esds,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for Mp4aBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.data_reference_index)?;
|
||||
|
||||
writer.write_u64::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.channelcount)?;
|
||||
writer.write_u16::<BigEndian>(self.samplesize)?;
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u32::<BigEndian>(self.samplerate.raw_value())?;
|
||||
|
||||
if let Some(ref esds) = self.esds {
|
||||
esds.write_box(writer)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct EsdsBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub es_desc: ESDescriptor,
|
||||
}
|
||||
|
||||
impl EsdsBox {
|
||||
pub fn new(config: &AacConfig) -> Self {
|
||||
Self {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
es_desc: ESDescriptor::new(config),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for EsdsBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::EsdsBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
HEADER_SIZE
|
||||
+ HEADER_EXT_SIZE
|
||||
+ 1
|
||||
+ size_of_length(ESDescriptor::desc_size()) as u64
|
||||
+ ESDescriptor::desc_size() as u64
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
Ok(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for EsdsBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let mut es_desc = None;
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
let (desc_tag, desc_size) = read_desc(reader)?;
|
||||
match desc_tag {
|
||||
0x03 => {
|
||||
es_desc = Some(ESDescriptor::read_desc(reader, desc_size)?);
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if es_desc.is_none() {
|
||||
return Err(Error::InvalidData("ESDescriptor not found"));
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(EsdsBox {
|
||||
version,
|
||||
flags,
|
||||
es_desc: es_desc.unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for EsdsBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
self.es_desc.write_desc(writer)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
trait Descriptor: Sized {
|
||||
fn desc_tag() -> u8;
|
||||
fn desc_size() -> u32;
|
||||
}
|
||||
|
||||
trait ReadDesc<T>: Sized {
|
||||
fn read_desc(_: T, size: u32) -> Result<Self>;
|
||||
}
|
||||
|
||||
trait WriteDesc<T>: Sized {
|
||||
fn write_desc(&self, _: T) -> Result<u32>;
|
||||
}
|
||||
|
||||
fn read_desc<R: Read>(reader: &mut R) -> Result<(u8, u32)> {
|
||||
let tag = reader.read_u8()?;
|
||||
|
||||
let mut size: u32 = 0;
|
||||
for _ in 0..4 {
|
||||
let b = reader.read_u8()?;
|
||||
size = (size << 7) | (b & 0x7F) as u32;
|
||||
if b & 0x80 == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok((tag, size))
|
||||
}
|
||||
|
||||
fn size_of_length(size: u32) -> u32 {
|
||||
match size {
|
||||
0x0..=0x7F => 1,
|
||||
0x80..=0x3FFF => 2,
|
||||
0x4000..=0x1FFFFF => 3,
|
||||
_ => 4,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_desc<W: Write>(writer: &mut W, tag: u8, size: u32) -> Result<u64> {
|
||||
writer.write_u8(tag)?;
|
||||
|
||||
if size as u64 > std::u32::MAX as u64 {
|
||||
return Err(Error::InvalidData("invalid descriptor length range"));
|
||||
}
|
||||
|
||||
let nbytes = size_of_length(size);
|
||||
|
||||
for i in 0..nbytes {
|
||||
let mut b = (size >> ((nbytes - i - 1) * 7)) as u8 & 0x7F;
|
||||
if i < nbytes - 1 {
|
||||
b |= 0x80;
|
||||
}
|
||||
writer.write_u8(b)?;
|
||||
}
|
||||
|
||||
Ok(1 + nbytes as u64)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct ESDescriptor {
|
||||
pub es_id: u16,
|
||||
|
||||
pub dec_config: DecoderConfigDescriptor,
|
||||
pub sl_config: SLConfigDescriptor,
|
||||
}
|
||||
|
||||
impl ESDescriptor {
|
||||
pub fn new(config: &AacConfig) -> Self {
|
||||
Self {
|
||||
es_id: 1,
|
||||
dec_config: DecoderConfigDescriptor::new(config),
|
||||
sl_config: SLConfigDescriptor::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Descriptor for ESDescriptor {
|
||||
fn desc_tag() -> u8 {
|
||||
0x03
|
||||
}
|
||||
|
||||
fn desc_size() -> u32 {
|
||||
3 + 1
|
||||
+ size_of_length(DecoderConfigDescriptor::desc_size())
|
||||
+ DecoderConfigDescriptor::desc_size()
|
||||
+ 1
|
||||
+ size_of_length(SLConfigDescriptor::desc_size())
|
||||
+ SLConfigDescriptor::desc_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadDesc<&mut R> for ESDescriptor {
|
||||
fn read_desc(reader: &mut R, size: u32) -> Result<Self> {
|
||||
let start = reader.stream_position()?;
|
||||
|
||||
let es_id = reader.read_u16::<BigEndian>()?;
|
||||
reader.read_u8()?; // XXX flags must be 0
|
||||
|
||||
let mut dec_config = None;
|
||||
let mut sl_config = None;
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size as u64;
|
||||
while current < end {
|
||||
let (desc_tag, desc_size) = read_desc(reader)?;
|
||||
match desc_tag {
|
||||
0x04 => {
|
||||
dec_config = Some(DecoderConfigDescriptor::read_desc(reader, desc_size)?);
|
||||
}
|
||||
0x06 => {
|
||||
sl_config = Some(SLConfigDescriptor::read_desc(reader, desc_size)?);
|
||||
}
|
||||
_ => {
|
||||
skip_bytes(reader, desc_size as u64)?;
|
||||
}
|
||||
}
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
Ok(ESDescriptor {
|
||||
es_id,
|
||||
dec_config: dec_config.unwrap_or_default(),
|
||||
sl_config: sl_config.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteDesc<&mut W> for ESDescriptor {
|
||||
fn write_desc(&self, writer: &mut W) -> Result<u32> {
|
||||
let size = Self::desc_size();
|
||||
write_desc(writer, Self::desc_tag(), size)?;
|
||||
|
||||
writer.write_u16::<BigEndian>(self.es_id)?;
|
||||
writer.write_u8(0)?;
|
||||
|
||||
self.dec_config.write_desc(writer)?;
|
||||
self.sl_config.write_desc(writer)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct DecoderConfigDescriptor {
|
||||
pub object_type_indication: u8,
|
||||
pub stream_type: u8,
|
||||
pub up_stream: u8,
|
||||
pub buffer_size_db: u32,
|
||||
pub max_bitrate: u32,
|
||||
pub avg_bitrate: u32,
|
||||
|
||||
pub dec_specific: DecoderSpecificDescriptor,
|
||||
}
|
||||
|
||||
impl DecoderConfigDescriptor {
|
||||
pub fn new(config: &AacConfig) -> Self {
|
||||
Self {
|
||||
object_type_indication: 0x40, // XXX AAC
|
||||
stream_type: 0x05, // XXX Audio
|
||||
up_stream: 0,
|
||||
buffer_size_db: 0,
|
||||
max_bitrate: config.bitrate, // XXX
|
||||
avg_bitrate: config.bitrate,
|
||||
dec_specific: DecoderSpecificDescriptor::new(config),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Descriptor for DecoderConfigDescriptor {
|
||||
fn desc_tag() -> u8 {
|
||||
0x04
|
||||
}
|
||||
|
||||
fn desc_size() -> u32 {
|
||||
13 + 1
|
||||
+ size_of_length(DecoderSpecificDescriptor::desc_size())
|
||||
+ DecoderSpecificDescriptor::desc_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadDesc<&mut R> for DecoderConfigDescriptor {
|
||||
fn read_desc(reader: &mut R, size: u32) -> Result<Self> {
|
||||
let start = reader.stream_position()?;
|
||||
|
||||
let object_type_indication = reader.read_u8()?;
|
||||
let byte_a = reader.read_u8()?;
|
||||
let stream_type = (byte_a & 0xFC) >> 2;
|
||||
let up_stream = byte_a & 0x02;
|
||||
let buffer_size_db = reader.read_u24::<BigEndian>()?;
|
||||
let max_bitrate = reader.read_u32::<BigEndian>()?;
|
||||
let avg_bitrate = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
let mut dec_specific = None;
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size as u64;
|
||||
while current < end {
|
||||
let (desc_tag, desc_size) = read_desc(reader)?;
|
||||
match desc_tag {
|
||||
0x05 => {
|
||||
dec_specific = Some(DecoderSpecificDescriptor::read_desc(reader, desc_size)?);
|
||||
}
|
||||
_ => {
|
||||
skip_bytes(reader, desc_size as u64)?;
|
||||
}
|
||||
}
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
Ok(DecoderConfigDescriptor {
|
||||
object_type_indication,
|
||||
stream_type,
|
||||
up_stream,
|
||||
buffer_size_db,
|
||||
max_bitrate,
|
||||
avg_bitrate,
|
||||
dec_specific: dec_specific.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteDesc<&mut W> for DecoderConfigDescriptor {
|
||||
fn write_desc(&self, writer: &mut W) -> Result<u32> {
|
||||
let size = Self::desc_size();
|
||||
write_desc(writer, Self::desc_tag(), size)?;
|
||||
|
||||
writer.write_u8(self.object_type_indication)?;
|
||||
writer.write_u8((self.stream_type << 2) + (self.up_stream & 0x02) + 1)?; // 1 reserved
|
||||
writer.write_u24::<BigEndian>(self.buffer_size_db)?;
|
||||
writer.write_u32::<BigEndian>(self.max_bitrate)?;
|
||||
writer.write_u32::<BigEndian>(self.avg_bitrate)?;
|
||||
|
||||
self.dec_specific.write_desc(writer)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct DecoderSpecificDescriptor {
|
||||
pub profile: u8,
|
||||
pub freq_index: u8,
|
||||
pub chan_conf: u8,
|
||||
}
|
||||
|
||||
impl DecoderSpecificDescriptor {
|
||||
pub fn new(config: &AacConfig) -> Self {
|
||||
Self {
|
||||
profile: config.profile as u8,
|
||||
freq_index: config.freq_index as u8,
|
||||
chan_conf: config.chan_conf as u8,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Descriptor for DecoderSpecificDescriptor {
|
||||
fn desc_tag() -> u8 {
|
||||
0x05
|
||||
}
|
||||
|
||||
fn desc_size() -> u32 {
|
||||
2
|
||||
}
|
||||
}
|
||||
|
||||
fn get_audio_object_type(byte_a: u8, byte_b: u8) -> u8 {
|
||||
let mut profile = byte_a >> 3;
|
||||
if profile == 31 {
|
||||
profile = 32 + ((byte_a & 7) | (byte_b >> 5));
|
||||
}
|
||||
|
||||
profile
|
||||
}
|
||||
|
||||
fn get_chan_conf<R: Read + Seek>(
|
||||
reader: &mut R,
|
||||
byte_b: u8,
|
||||
freq_index: u8,
|
||||
extended_profile: bool,
|
||||
) -> Result<u8> {
|
||||
let chan_conf;
|
||||
if freq_index == 15 {
|
||||
// Skip the 24 bit sample rate
|
||||
let sample_rate = reader.read_u24::<BigEndian>()?;
|
||||
chan_conf = ((sample_rate >> 4) & 0x0F) as u8;
|
||||
} else if extended_profile {
|
||||
let byte_c = reader.read_u8()?;
|
||||
chan_conf = (byte_b & 1) | (byte_c & 0xE0);
|
||||
} else {
|
||||
chan_conf = (byte_b >> 3) & 0x0F;
|
||||
}
|
||||
|
||||
Ok(chan_conf)
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadDesc<&mut R> for DecoderSpecificDescriptor {
|
||||
fn read_desc(reader: &mut R, _size: u32) -> Result<Self> {
|
||||
let byte_a = reader.read_u8()?;
|
||||
let byte_b = reader.read_u8()?;
|
||||
let profile = get_audio_object_type(byte_a, byte_b);
|
||||
let freq_index;
|
||||
let chan_conf;
|
||||
if profile > 31 {
|
||||
freq_index = (byte_b >> 1) & 0x0F;
|
||||
chan_conf = get_chan_conf(reader, byte_b, freq_index, true)?;
|
||||
} else {
|
||||
freq_index = ((byte_a & 0x07) << 1) + (byte_b >> 7);
|
||||
chan_conf = get_chan_conf(reader, byte_b, freq_index, false)?;
|
||||
}
|
||||
|
||||
Ok(DecoderSpecificDescriptor {
|
||||
profile,
|
||||
freq_index,
|
||||
chan_conf,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteDesc<&mut W> for DecoderSpecificDescriptor {
|
||||
fn write_desc(&self, writer: &mut W) -> Result<u32> {
|
||||
let size = Self::desc_size();
|
||||
write_desc(writer, Self::desc_tag(), size)?;
|
||||
|
||||
writer.write_u8((self.profile << 3) + (self.freq_index >> 1))?;
|
||||
writer.write_u8((self.freq_index << 7) + (self.chan_conf << 3))?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct SLConfigDescriptor {}
|
||||
|
||||
impl SLConfigDescriptor {
|
||||
pub fn new() -> Self {
|
||||
SLConfigDescriptor {}
|
||||
}
|
||||
}
|
||||
|
||||
impl Descriptor for SLConfigDescriptor {
|
||||
fn desc_tag() -> u8 {
|
||||
0x06
|
||||
}
|
||||
|
||||
fn desc_size() -> u32 {
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadDesc<&mut R> for SLConfigDescriptor {
|
||||
fn read_desc(reader: &mut R, _size: u32) -> Result<Self> {
|
||||
reader.read_u8()?; // pre-defined
|
||||
|
||||
Ok(SLConfigDescriptor {})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteDesc<&mut W> for SLConfigDescriptor {
|
||||
fn write_desc(&self, writer: &mut W) -> Result<u32> {
|
||||
let size = Self::desc_size();
|
||||
write_desc(writer, Self::desc_tag(), size)?;
|
||||
|
||||
writer.write_u8(2)?; // pre-defined
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_mp4a() {
|
||||
let src_box = Mp4aBox {
|
||||
data_reference_index: 1,
|
||||
channelcount: 2,
|
||||
samplesize: 16,
|
||||
samplerate: FixedPointU16::new(48000),
|
||||
esds: Some(EsdsBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
es_desc: ESDescriptor {
|
||||
es_id: 2,
|
||||
dec_config: DecoderConfigDescriptor {
|
||||
object_type_indication: 0x40,
|
||||
stream_type: 0x05,
|
||||
up_stream: 0,
|
||||
buffer_size_db: 0,
|
||||
max_bitrate: 67695,
|
||||
avg_bitrate: 67695,
|
||||
dec_specific: DecoderSpecificDescriptor {
|
||||
profile: 2,
|
||||
freq_index: 3,
|
||||
chan_conf: 1,
|
||||
},
|
||||
},
|
||||
sl_config: SLConfigDescriptor::default(),
|
||||
},
|
||||
}),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::Mp4aBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = Mp4aBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mp4a_no_esds() {
|
||||
let src_box = Mp4aBox {
|
||||
data_reference_index: 1,
|
||||
channelcount: 2,
|
||||
samplesize: 16,
|
||||
samplerate: FixedPointU16::new(48000),
|
||||
esds: None,
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::Mp4aBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = Mp4aBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
102
src/mp4box/mvex.rs
Normal file
102
src/mp4box/mvex.rs
Normal file
|
@ -0,0 +1,102 @@
|
|||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{mehd::MehdBox, trex::TrexBox};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct MvexBox {
|
||||
pub mehd: Option<MehdBox>,
|
||||
pub trex: TrexBox,
|
||||
}
|
||||
|
||||
impl MvexBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::MdiaBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + self.mehd.as_ref().map(|x| x.box_size()).unwrap_or(0) + self.trex.box_size()
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MvexBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MvexBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut mehd = None;
|
||||
let mut trex = None;
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"mvex box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
match name {
|
||||
BoxType::MehdBox => {
|
||||
mehd = Some(MehdBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::TrexBox => {
|
||||
trex = Some(TrexBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if trex.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::TrexBox));
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(MvexBox {
|
||||
mehd,
|
||||
trex: trex.unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for MvexBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
if let Some(mehd) = &self.mehd {
|
||||
mehd.write_box(writer)?;
|
||||
}
|
||||
self.trex.write_box(writer)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
257
src/mp4box/mvhd.rs
Normal file
257
src/mp4box/mvhd.rs
Normal file
|
@ -0,0 +1,257 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct MvhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub creation_time: u64,
|
||||
pub modification_time: u64,
|
||||
pub timescale: u32,
|
||||
pub duration: u64,
|
||||
|
||||
#[serde(with = "value_u32")]
|
||||
pub rate: FixedPointU16,
|
||||
#[serde(with = "value_u8")]
|
||||
pub volume: FixedPointU8,
|
||||
|
||||
pub matrix: tkhd::Matrix,
|
||||
|
||||
pub next_track_id: u32,
|
||||
}
|
||||
|
||||
impl MvhdBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::MvhdBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
if self.version == 1 {
|
||||
size += 28;
|
||||
} else if self.version == 0 {
|
||||
size += 16;
|
||||
}
|
||||
size += 80;
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MvhdBox {
|
||||
fn default() -> Self {
|
||||
MvhdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
creation_time: 0,
|
||||
modification_time: 0,
|
||||
timescale: 1000,
|
||||
duration: 0,
|
||||
rate: FixedPointU16::new(1),
|
||||
matrix: tkhd::Matrix::default(),
|
||||
volume: FixedPointU8::new(1),
|
||||
next_track_id: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MvhdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!(
|
||||
"creation_time={} timescale={} duration={} rate={} volume={}, matrix={}, next_track_id={}",
|
||||
self.creation_time,
|
||||
self.timescale,
|
||||
self.duration,
|
||||
self.rate.value(),
|
||||
self.volume.value(),
|
||||
self.matrix,
|
||||
self.next_track_id
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for MvhdBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let (creation_time, modification_time, timescale, duration) = if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else if version == 0 {
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
} else {
|
||||
return Err(Error::InvalidData("version must be 0 or 1"));
|
||||
};
|
||||
let rate = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
|
||||
|
||||
let volume = FixedPointU8::new_raw(reader.read_u16::<BigEndian>()?);
|
||||
|
||||
reader.read_u16::<BigEndian>()?; // reserved = 0
|
||||
|
||||
reader.read_u64::<BigEndian>()?; // reserved = 0
|
||||
|
||||
let matrix = tkhd::Matrix {
|
||||
a: reader.read_i32::<BigEndian>()?,
|
||||
b: reader.read_i32::<BigEndian>()?,
|
||||
u: reader.read_i32::<BigEndian>()?,
|
||||
c: reader.read_i32::<BigEndian>()?,
|
||||
d: reader.read_i32::<BigEndian>()?,
|
||||
v: reader.read_i32::<BigEndian>()?,
|
||||
x: reader.read_i32::<BigEndian>()?,
|
||||
y: reader.read_i32::<BigEndian>()?,
|
||||
w: reader.read_i32::<BigEndian>()?,
|
||||
};
|
||||
|
||||
skip_bytes(reader, 24)?; // pre_defined = 0
|
||||
|
||||
let next_track_id = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(MvhdBox {
|
||||
version,
|
||||
flags,
|
||||
creation_time,
|
||||
modification_time,
|
||||
timescale,
|
||||
duration,
|
||||
rate,
|
||||
volume,
|
||||
matrix,
|
||||
next_track_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for MvhdBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
if self.version == 1 {
|
||||
writer.write_u64::<BigEndian>(self.creation_time)?;
|
||||
writer.write_u64::<BigEndian>(self.modification_time)?;
|
||||
writer.write_u32::<BigEndian>(self.timescale)?;
|
||||
writer.write_u64::<BigEndian>(self.duration)?;
|
||||
} else if self.version == 0 {
|
||||
writer.write_u32::<BigEndian>(self.creation_time as u32)?;
|
||||
writer.write_u32::<BigEndian>(self.modification_time as u32)?;
|
||||
writer.write_u32::<BigEndian>(self.timescale)?;
|
||||
writer.write_u32::<BigEndian>(self.duration as u32)?;
|
||||
} else {
|
||||
return Err(Error::InvalidData("version must be 0 or 1"));
|
||||
}
|
||||
writer.write_u32::<BigEndian>(self.rate.raw_value())?;
|
||||
|
||||
writer.write_u16::<BigEndian>(self.volume.raw_value())?;
|
||||
|
||||
writer.write_u16::<BigEndian>(0)?; // reserved = 0
|
||||
|
||||
writer.write_u64::<BigEndian>(0)?; // reserved = 0
|
||||
|
||||
writer.write_i32::<BigEndian>(self.matrix.a)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.b)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.u)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.c)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.d)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.v)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.x)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.y)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.w)?;
|
||||
|
||||
write_zeros(writer, 24)?; // pre_defined = 0
|
||||
|
||||
writer.write_u32::<BigEndian>(self.next_track_id)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_mvhd32() {
|
||||
let src_box = MvhdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
creation_time: 100,
|
||||
modification_time: 200,
|
||||
timescale: 1000,
|
||||
duration: 634634,
|
||||
rate: FixedPointU16::new(1),
|
||||
volume: FixedPointU8::new(1),
|
||||
matrix: tkhd::Matrix::default(),
|
||||
next_track_id: 1,
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MvhdBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = MvhdBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mvhd64() {
|
||||
let src_box = MvhdBox {
|
||||
version: 1,
|
||||
flags: 0,
|
||||
creation_time: 100,
|
||||
modification_time: 200,
|
||||
timescale: 1000,
|
||||
duration: 634634,
|
||||
rate: FixedPointU16::new(1),
|
||||
volume: FixedPointU8::new(1),
|
||||
matrix: tkhd::Matrix::default(),
|
||||
next_track_id: 1,
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::MvhdBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = MvhdBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
112
src/mp4box/smhd.rs
Normal file
112
src/mp4box/smhd.rs
Normal file
|
@ -0,0 +1,112 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct SmhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
||||
#[serde(with = "value_i16")]
|
||||
pub balance: FixedPointI8,
|
||||
}
|
||||
|
||||
impl SmhdBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::SmhdBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 4
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SmhdBox {
|
||||
fn default() -> Self {
|
||||
SmhdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
balance: FixedPointI8::new_raw(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for SmhdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("balance={}", self.balance.value());
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for SmhdBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let balance = FixedPointI8::new_raw(reader.read_i16::<BigEndian>()?);
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(SmhdBox {
|
||||
version,
|
||||
flags,
|
||||
balance,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for SmhdBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_i16::<BigEndian>(self.balance.raw_value())?;
|
||||
writer.write_u16::<BigEndian>(0)?; // reserved
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_smhd() {
|
||||
let src_box = SmhdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
balance: FixedPointI8::new_raw(-1),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::SmhdBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = SmhdBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
189
src/mp4box/stbl.rs
Normal file
189
src/mp4box/stbl.rs
Normal file
|
@ -0,0 +1,189 @@
|
|||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{
|
||||
co64::Co64Box, ctts::CttsBox, stco::StcoBox, stsc::StscBox, stsd::StsdBox, stss::StssBox,
|
||||
stsz::StszBox, stts::SttsBox,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct StblBox {
|
||||
pub stsd: StsdBox,
|
||||
pub stts: SttsBox,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ctts: Option<CttsBox>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stss: Option<StssBox>,
|
||||
pub stsc: StscBox,
|
||||
pub stsz: StszBox,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub stco: Option<StcoBox>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub co64: Option<Co64Box>,
|
||||
}
|
||||
|
||||
impl StblBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::StblBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
size += self.stsd.box_size();
|
||||
size += self.stts.box_size();
|
||||
if let Some(ref ctts) = self.ctts {
|
||||
size += ctts.box_size();
|
||||
}
|
||||
if let Some(ref stss) = self.stss {
|
||||
size += stss.box_size();
|
||||
}
|
||||
size += self.stsc.box_size();
|
||||
size += self.stsz.box_size();
|
||||
if let Some(ref stco) = self.stco {
|
||||
size += stco.box_size();
|
||||
}
|
||||
if let Some(ref co64) = self.co64 {
|
||||
size += co64.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for StblBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for StblBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut stsd = None;
|
||||
let mut stts = None;
|
||||
let mut ctts = None;
|
||||
let mut stss = None;
|
||||
let mut stsc = None;
|
||||
let mut stsz = None;
|
||||
let mut stco = None;
|
||||
let mut co64 = None;
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"stbl box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
match name {
|
||||
BoxType::StsdBox => {
|
||||
stsd = Some(StsdBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::SttsBox => {
|
||||
stts = Some(SttsBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::CttsBox => {
|
||||
ctts = Some(CttsBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::StssBox => {
|
||||
stss = Some(StssBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::StscBox => {
|
||||
stsc = Some(StscBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::StszBox => {
|
||||
stsz = Some(StszBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::StcoBox => {
|
||||
stco = Some(StcoBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::Co64Box => {
|
||||
co64 = Some(Co64Box::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if stsd.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::StsdBox));
|
||||
}
|
||||
if stts.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::SttsBox));
|
||||
}
|
||||
if stsc.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::StscBox));
|
||||
}
|
||||
if stsz.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::StszBox));
|
||||
}
|
||||
if stco.is_none() && co64.is_none() {
|
||||
return Err(Error::Box2NotFound(BoxType::StcoBox, BoxType::Co64Box));
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(StblBox {
|
||||
stsd: stsd.unwrap(),
|
||||
stts: stts.unwrap(),
|
||||
ctts,
|
||||
stss,
|
||||
stsc: stsc.unwrap(),
|
||||
stsz: stsz.unwrap(),
|
||||
stco,
|
||||
co64,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for StblBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
self.stsd.write_box(writer)?;
|
||||
self.stts.write_box(writer)?;
|
||||
if let Some(ref ctts) = self.ctts {
|
||||
ctts.write_box(writer)?;
|
||||
}
|
||||
if let Some(ref stss) = self.stss {
|
||||
stss.write_box(writer)?;
|
||||
}
|
||||
self.stsc.write_box(writer)?;
|
||||
self.stsz.write_box(writer)?;
|
||||
if let Some(ref stco) = self.stco {
|
||||
stco.write_box(writer)?;
|
||||
}
|
||||
if let Some(ref co64) = self.co64 {
|
||||
co64.write_box(writer)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
141
src/mp4box/stco.rs
Normal file
141
src/mp4box/stco.rs
Normal file
|
@ -0,0 +1,141 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct StcoBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
||||
#[serde(skip_serializing)]
|
||||
pub entries: Vec<u32>,
|
||||
}
|
||||
|
||||
impl StcoBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::StcoBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 4 + (4 * self.entries.len() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for StcoBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("entries={}", self.entries.len());
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for StcoBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let header_size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
let other_size = size_of::<u32>(); // entry_count
|
||||
let entry_size = size_of::<u32>(); // chunk_offset
|
||||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
if u64::from(entry_count)
|
||||
> size
|
||||
.saturating_sub(header_size)
|
||||
.saturating_sub(other_size as u64)
|
||||
/ entry_size as u64
|
||||
{
|
||||
return Err(Error::InvalidData(
|
||||
"stco entry_count indicates more entries than could fit in the box",
|
||||
));
|
||||
}
|
||||
let mut entries = Vec::with_capacity(entry_count as usize);
|
||||
for _i in 0..entry_count {
|
||||
let chunk_offset = reader.read_u32::<BigEndian>()?;
|
||||
entries.push(chunk_offset);
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(StcoBox {
|
||||
version,
|
||||
flags,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for StcoBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.entries.len() as u32)?;
|
||||
for chunk_offset in self.entries.iter() {
|
||||
writer.write_u32::<BigEndian>(*chunk_offset)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<&co64::Co64Box> for StcoBox {
|
||||
type Error = std::num::TryFromIntError;
|
||||
|
||||
fn try_from(co64: &co64::Co64Box) -> std::result::Result<Self, Self::Error> {
|
||||
let entries = co64
|
||||
.entries
|
||||
.iter()
|
||||
.copied()
|
||||
.map(u32::try_from)
|
||||
.collect::<std::result::Result<Vec<_>, _>>()?;
|
||||
Ok(Self {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_stco() {
|
||||
let src_box = StcoBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
entries: vec![267, 1970, 2535, 2803, 11843, 22223, 33584],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::StcoBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = StcoBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
171
src/mp4box/stsc.rs
Normal file
171
src/mp4box/stsc.rs
Normal file
|
@ -0,0 +1,171 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct StscBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
||||
#[serde(skip_serializing)]
|
||||
pub entries: Vec<StscEntry>,
|
||||
}
|
||||
|
||||
impl StscBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::StscBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 4 + (12 * self.entries.len() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct StscEntry {
|
||||
pub first_chunk: u32,
|
||||
pub samples_per_chunk: u32,
|
||||
pub sample_description_index: u32,
|
||||
pub first_sample: u32,
|
||||
}
|
||||
|
||||
impl Mp4Box for StscBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("entries={}", self.entries.len());
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for StscBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let header_size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
let other_size = size_of::<u32>(); // entry_count
|
||||
let entry_size = size_of::<u32>() + size_of::<u32>() + size_of::<u32>(); // first_chunk + samples_per_chunk + sample_description_index
|
||||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
if u64::from(entry_count)
|
||||
> size
|
||||
.saturating_sub(header_size)
|
||||
.saturating_sub(other_size as u64)
|
||||
/ entry_size as u64
|
||||
{
|
||||
return Err(Error::InvalidData(
|
||||
"stsc entry_count indicates more entries than could fit in the box",
|
||||
));
|
||||
}
|
||||
let mut entries = Vec::with_capacity(entry_count as usize);
|
||||
for _ in 0..entry_count {
|
||||
let entry = StscEntry {
|
||||
first_chunk: reader.read_u32::<BigEndian>()?,
|
||||
samples_per_chunk: reader.read_u32::<BigEndian>()?,
|
||||
sample_description_index: reader.read_u32::<BigEndian>()?,
|
||||
first_sample: 0,
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
let mut sample_id = 1;
|
||||
for i in 0..entry_count {
|
||||
let (first_chunk, samples_per_chunk) = {
|
||||
let entry = entries.get_mut(i as usize).unwrap();
|
||||
entry.first_sample = sample_id;
|
||||
(entry.first_chunk, entry.samples_per_chunk)
|
||||
};
|
||||
if i < entry_count - 1 {
|
||||
let next_entry = entries.get(i as usize + 1).unwrap();
|
||||
sample_id = next_entry
|
||||
.first_chunk
|
||||
.checked_sub(first_chunk)
|
||||
.and_then(|n| n.checked_mul(samples_per_chunk))
|
||||
.and_then(|n| n.checked_add(sample_id))
|
||||
.ok_or(Error::InvalidData(
|
||||
"attempt to calculate stsc sample_id with overflow",
|
||||
))?;
|
||||
}
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(StscBox {
|
||||
version,
|
||||
flags,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for StscBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.entries.len() as u32)?;
|
||||
for entry in self.entries.iter() {
|
||||
writer.write_u32::<BigEndian>(entry.first_chunk)?;
|
||||
writer.write_u32::<BigEndian>(entry.samples_per_chunk)?;
|
||||
writer.write_u32::<BigEndian>(entry.sample_description_index)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_stsc() {
|
||||
let src_box = StscBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
entries: vec![
|
||||
StscEntry {
|
||||
first_chunk: 1,
|
||||
samples_per_chunk: 1,
|
||||
sample_description_index: 1,
|
||||
first_sample: 1,
|
||||
},
|
||||
StscEntry {
|
||||
first_chunk: 19026,
|
||||
samples_per_chunk: 14,
|
||||
sample_description_index: 1,
|
||||
first_sample: 19026,
|
||||
},
|
||||
],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::StscBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = StscBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
150
src/mp4box/stsd.rs
Normal file
150
src/mp4box/stsd.rs
Normal file
|
@ -0,0 +1,150 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::vp09::Vp09Box;
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{avc1::Avc1Box, hev1::Hev1Box, mp4a::Mp4aBox, tx3g::Tx3gBox};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct StsdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub avc1: Option<Avc1Box>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hev1: Option<Hev1Box>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub vp09: Option<Vp09Box>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mp4a: Option<Mp4aBox>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tx3g: Option<Tx3gBox>,
|
||||
}
|
||||
|
||||
impl StsdBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::StsdBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE + 4;
|
||||
if let Some(ref avc1) = self.avc1 {
|
||||
size += avc1.box_size();
|
||||
} else if let Some(ref hev1) = self.hev1 {
|
||||
size += hev1.box_size();
|
||||
} else if let Some(ref vp09) = self.vp09 {
|
||||
size += vp09.box_size();
|
||||
} else if let Some(ref mp4a) = self.mp4a {
|
||||
size += mp4a.box_size();
|
||||
} else if let Some(ref tx3g) = self.tx3g {
|
||||
size += tx3g.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for StsdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // XXX entry_count
|
||||
|
||||
let mut avc1 = None;
|
||||
let mut hev1 = None;
|
||||
let mut vp09 = None;
|
||||
let mut mp4a = None;
|
||||
let mut tx3g = None;
|
||||
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"stsd box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
match name {
|
||||
BoxType::Avc1Box => {
|
||||
avc1 = Some(Avc1Box::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::Hev1Box => {
|
||||
hev1 = Some(Hev1Box::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::Vp09Box => {
|
||||
vp09 = Some(Vp09Box::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::Mp4aBox => {
|
||||
mp4a = Some(Mp4aBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::Tx3gBox => {
|
||||
tx3g = Some(Tx3gBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(StsdBox {
|
||||
version,
|
||||
flags,
|
||||
avc1,
|
||||
hev1,
|
||||
vp09,
|
||||
mp4a,
|
||||
tx3g,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for StsdBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(1)?; // entry_count
|
||||
|
||||
if let Some(ref avc1) = self.avc1 {
|
||||
avc1.write_box(writer)?;
|
||||
} else if let Some(ref hev1) = self.hev1 {
|
||||
hev1.write_box(writer)?;
|
||||
} else if let Some(ref vp09) = self.vp09 {
|
||||
vp09.write_box(writer)?;
|
||||
} else if let Some(ref mp4a) = self.mp4a {
|
||||
mp4a.write_box(writer)?;
|
||||
} else if let Some(ref tx3g) = self.tx3g {
|
||||
tx3g.write_box(writer)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
123
src/mp4box/stss.rs
Normal file
123
src/mp4box/stss.rs
Normal file
|
@ -0,0 +1,123 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct StssBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
||||
#[serde(skip_serializing)]
|
||||
pub entries: Vec<u32>,
|
||||
}
|
||||
|
||||
impl StssBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::StssBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 4 + (4 * self.entries.len() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for StssBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("entries={}", self.entries.len());
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for StssBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let header_size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
let other_size = size_of::<u32>(); // entry_count
|
||||
let entry_size = size_of::<u32>(); // sample_number
|
||||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
if u64::from(entry_count)
|
||||
> size
|
||||
.saturating_sub(header_size)
|
||||
.saturating_sub(other_size as u64)
|
||||
/ entry_size as u64
|
||||
{
|
||||
return Err(Error::InvalidData(
|
||||
"stss entry_count indicates more entries than could fit in the box",
|
||||
));
|
||||
}
|
||||
let mut entries = Vec::with_capacity(entry_count as usize);
|
||||
for _i in 0..entry_count {
|
||||
let sample_number = reader.read_u32::<BigEndian>()?;
|
||||
entries.push(sample_number);
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(StssBox {
|
||||
version,
|
||||
flags,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for StssBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.entries.len() as u32)?;
|
||||
for sample_number in self.entries.iter() {
|
||||
writer.write_u32::<BigEndian>(*sample_number)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_stss() {
|
||||
let src_box = StssBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
entries: vec![1, 61, 121, 181, 241, 301, 361, 421, 481],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::StssBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = StssBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
170
src/mp4box/stsz.rs
Normal file
170
src/mp4box/stsz.rs
Normal file
|
@ -0,0 +1,170 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct StszBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub sample_size: u32,
|
||||
pub sample_count: u32,
|
||||
|
||||
#[serde(skip_serializing)]
|
||||
pub sample_sizes: Vec<u32>,
|
||||
}
|
||||
|
||||
impl StszBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::StszBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 8 + (4 * self.sample_sizes.len() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for StszBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!(
|
||||
"sample_size={} sample_count={} sample_sizes={}",
|
||||
self.sample_size,
|
||||
self.sample_count,
|
||||
self.sample_sizes.len()
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for StszBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let header_size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
let other_size = size_of::<u32>() + size_of::<u32>(); // sample_size + sample_count
|
||||
let sample_size = reader.read_u32::<BigEndian>()?;
|
||||
let stsz_item_size = if sample_size == 0 {
|
||||
size_of::<u32>() // entry_size
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let sample_count = reader.read_u32::<BigEndian>()?;
|
||||
let mut sample_sizes = Vec::new();
|
||||
if sample_size == 0 {
|
||||
if u64::from(sample_count)
|
||||
> size
|
||||
.saturating_sub(header_size)
|
||||
.saturating_sub(other_size as u64)
|
||||
/ stsz_item_size as u64
|
||||
{
|
||||
return Err(Error::InvalidData(
|
||||
"stsz sample_count indicates more values than could fit in the box",
|
||||
));
|
||||
}
|
||||
sample_sizes.reserve(sample_count as usize);
|
||||
for _ in 0..sample_count {
|
||||
let sample_number = reader.read_u32::<BigEndian>()?;
|
||||
sample_sizes.push(sample_number);
|
||||
}
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(StszBox {
|
||||
version,
|
||||
flags,
|
||||
sample_size,
|
||||
sample_count,
|
||||
sample_sizes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for StszBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.sample_size)?;
|
||||
writer.write_u32::<BigEndian>(self.sample_count)?;
|
||||
if self.sample_size == 0 {
|
||||
if self.sample_count != self.sample_sizes.len() as u32 {
|
||||
return Err(Error::InvalidData("sample count out of sync"));
|
||||
}
|
||||
for sample_number in self.sample_sizes.iter() {
|
||||
writer.write_u32::<BigEndian>(*sample_number)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_stsz_same_size() {
|
||||
let src_box = StszBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
sample_size: 1165,
|
||||
sample_count: 12,
|
||||
sample_sizes: vec![],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::StszBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = StszBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stsz_many_sizes() {
|
||||
let src_box = StszBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
sample_size: 0,
|
||||
sample_count: 9,
|
||||
sample_sizes: vec![1165, 11, 11, 8545, 10126, 10866, 9643, 9351, 7730],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::StszBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = StszBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
142
src/mp4box/stts.rs
Normal file
142
src/mp4box/stts.rs
Normal file
|
@ -0,0 +1,142 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct SttsBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
||||
#[serde(skip_serializing)]
|
||||
pub entries: Vec<SttsEntry>,
|
||||
}
|
||||
|
||||
impl SttsBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::SttsBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 4 + (8 * self.entries.len() as u64)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct SttsEntry {
|
||||
pub sample_count: u32,
|
||||
pub sample_delta: u32,
|
||||
}
|
||||
|
||||
impl Mp4Box for SttsBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("entries={}", self.entries.len());
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for SttsBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let header_size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
let other_size = size_of::<u32>(); // entry_count
|
||||
let entry_size = size_of::<u32>() + size_of::<u32>(); // sample_count + sample_delta
|
||||
let entry_count = reader.read_u32::<BigEndian>()?;
|
||||
if u64::from(entry_count)
|
||||
> size
|
||||
.saturating_sub(header_size)
|
||||
.saturating_sub(other_size as u64)
|
||||
/ entry_size as u64
|
||||
{
|
||||
return Err(Error::InvalidData(
|
||||
"stts entry_count indicates more entries than could fit in the box",
|
||||
));
|
||||
}
|
||||
let mut entries = Vec::with_capacity(entry_count as usize);
|
||||
for _i in 0..entry_count {
|
||||
let entry = SttsEntry {
|
||||
sample_count: reader.read_u32::<BigEndian>()?,
|
||||
sample_delta: reader.read_u32::<BigEndian>()?,
|
||||
};
|
||||
entries.push(entry);
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(SttsBox {
|
||||
version,
|
||||
flags,
|
||||
entries,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for SttsBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.entries.len() as u32)?;
|
||||
for entry in self.entries.iter() {
|
||||
writer.write_u32::<BigEndian>(entry.sample_count)?;
|
||||
writer.write_u32::<BigEndian>(entry.sample_delta)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_stts() {
|
||||
let src_box = SttsBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
entries: vec![
|
||||
SttsEntry {
|
||||
sample_count: 29726,
|
||||
sample_delta: 1024,
|
||||
},
|
||||
SttsEntry {
|
||||
sample_count: 1,
|
||||
sample_delta: 512,
|
||||
},
|
||||
],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::SttsBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = SttsBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
137
src/mp4box/tfdt.rs
Normal file
137
src/mp4box/tfdt.rs
Normal file
|
@ -0,0 +1,137 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct TfdtBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub base_media_decode_time: u64,
|
||||
}
|
||||
|
||||
impl TfdtBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::TfdtBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut sum = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
if self.version == 1 {
|
||||
sum += 8;
|
||||
} else {
|
||||
sum += 4;
|
||||
}
|
||||
sum
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for TfdtBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("base_media_decode_time={}", self.base_media_decode_time);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for TfdtBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let base_media_decode_time = if version == 1 {
|
||||
reader.read_u64::<BigEndian>()?
|
||||
} else if version == 0 {
|
||||
reader.read_u32::<BigEndian>()? as u64
|
||||
} else {
|
||||
return Err(Error::InvalidData("version must be 0 or 1"));
|
||||
};
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(TfdtBox {
|
||||
version,
|
||||
flags,
|
||||
base_media_decode_time,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for TfdtBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
if self.version == 1 {
|
||||
writer.write_u64::<BigEndian>(self.base_media_decode_time)?;
|
||||
} else if self.version == 0 {
|
||||
writer.write_u32::<BigEndian>(self.base_media_decode_time as u32)?;
|
||||
} else {
|
||||
return Err(Error::InvalidData("version must be 0 or 1"));
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_tfdt32() {
|
||||
let src_box = TfdtBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
base_media_decode_time: 0,
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::TfdtBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = TfdtBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tfdt64() {
|
||||
let src_box = TfdtBox {
|
||||
version: 1,
|
||||
flags: 0,
|
||||
base_media_decode_time: 0,
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::TfdtBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = TfdtBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
203
src/mp4box/tfhd.rs
Normal file
203
src/mp4box/tfhd.rs
Normal file
|
@ -0,0 +1,203 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
|
||||
pub struct TfhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub track_id: u32,
|
||||
pub base_data_offset: Option<u64>,
|
||||
pub sample_description_index: Option<u32>,
|
||||
pub default_sample_duration: Option<u32>,
|
||||
pub default_sample_size: Option<u32>,
|
||||
pub default_sample_flags: Option<u32>,
|
||||
}
|
||||
|
||||
impl TfhdBox {
|
||||
pub const FLAG_BASE_DATA_OFFSET: u32 = 0x01;
|
||||
pub const FLAG_SAMPLE_DESCRIPTION_INDEX: u32 = 0x02;
|
||||
pub const FLAG_DEFAULT_SAMPLE_DURATION: u32 = 0x08;
|
||||
pub const FLAG_DEFAULT_SAMPLE_SIZE: u32 = 0x10;
|
||||
pub const FLAG_DEFAULT_SAMPLE_FLAGS: u32 = 0x20;
|
||||
pub const FLAG_DURATION_IS_EMPTY: u32 = 0x10000;
|
||||
pub const FLAG_DEFAULT_BASE_IS_MOOF: u32 = 0x20000;
|
||||
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::TfhdBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut sum = HEADER_SIZE + HEADER_EXT_SIZE + 4;
|
||||
if TfhdBox::FLAG_BASE_DATA_OFFSET & self.flags > 0 {
|
||||
sum += 8;
|
||||
}
|
||||
if TfhdBox::FLAG_SAMPLE_DESCRIPTION_INDEX & self.flags > 0 {
|
||||
sum += 4;
|
||||
}
|
||||
if TfhdBox::FLAG_DEFAULT_SAMPLE_DURATION & self.flags > 0 {
|
||||
sum += 4;
|
||||
}
|
||||
if TfhdBox::FLAG_DEFAULT_SAMPLE_SIZE & self.flags > 0 {
|
||||
sum += 4;
|
||||
}
|
||||
if TfhdBox::FLAG_DEFAULT_SAMPLE_FLAGS & self.flags > 0 {
|
||||
sum += 4;
|
||||
}
|
||||
sum
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for TfhdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("track_id={}", self.track_id);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for TfhdBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
let track_id = reader.read_u32::<BigEndian>()?;
|
||||
let base_data_offset = if TfhdBox::FLAG_BASE_DATA_OFFSET & flags > 0 {
|
||||
Some(reader.read_u64::<BigEndian>()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let sample_description_index = if TfhdBox::FLAG_SAMPLE_DESCRIPTION_INDEX & flags > 0 {
|
||||
Some(reader.read_u32::<BigEndian>()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let default_sample_duration = if TfhdBox::FLAG_DEFAULT_SAMPLE_DURATION & flags > 0 {
|
||||
Some(reader.read_u32::<BigEndian>()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let default_sample_size = if TfhdBox::FLAG_DEFAULT_SAMPLE_SIZE & flags > 0 {
|
||||
Some(reader.read_u32::<BigEndian>()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let default_sample_flags = if TfhdBox::FLAG_DEFAULT_SAMPLE_FLAGS & flags > 0 {
|
||||
Some(reader.read_u32::<BigEndian>()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(TfhdBox {
|
||||
version,
|
||||
flags,
|
||||
track_id,
|
||||
base_data_offset,
|
||||
sample_description_index,
|
||||
default_sample_duration,
|
||||
default_sample_size,
|
||||
default_sample_flags,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for TfhdBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
writer.write_u32::<BigEndian>(self.track_id)?;
|
||||
if let Some(base_data_offset) = self.base_data_offset {
|
||||
writer.write_u64::<BigEndian>(base_data_offset)?;
|
||||
}
|
||||
if let Some(sample_description_index) = self.sample_description_index {
|
||||
writer.write_u32::<BigEndian>(sample_description_index)?;
|
||||
}
|
||||
if let Some(default_sample_duration) = self.default_sample_duration {
|
||||
writer.write_u32::<BigEndian>(default_sample_duration)?;
|
||||
}
|
||||
if let Some(default_sample_size) = self.default_sample_size {
|
||||
writer.write_u32::<BigEndian>(default_sample_size)?;
|
||||
}
|
||||
if let Some(default_sample_flags) = self.default_sample_flags {
|
||||
writer.write_u32::<BigEndian>(default_sample_flags)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_tfhd() {
|
||||
let src_box = TfhdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
track_id: 1,
|
||||
base_data_offset: None,
|
||||
sample_description_index: None,
|
||||
default_sample_duration: None,
|
||||
default_sample_size: None,
|
||||
default_sample_flags: None,
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::TfhdBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = TfhdBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tfhd_with_flags() {
|
||||
let src_box = TfhdBox {
|
||||
version: 0,
|
||||
flags: TfhdBox::FLAG_SAMPLE_DESCRIPTION_INDEX
|
||||
| TfhdBox::FLAG_DEFAULT_SAMPLE_DURATION
|
||||
| TfhdBox::FLAG_DEFAULT_SAMPLE_FLAGS,
|
||||
track_id: 1,
|
||||
base_data_offset: None,
|
||||
sample_description_index: Some(1),
|
||||
default_sample_duration: Some(512),
|
||||
default_sample_size: None,
|
||||
default_sample_flags: Some(0x1010000),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::TfhdBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = TfhdBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
323
src/mp4box/tkhd.rs
Normal file
323
src/mp4box/tkhd.rs
Normal file
|
@ -0,0 +1,323 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
pub enum TrackFlag {
|
||||
TrackEnabled = 0x000001,
|
||||
// TrackInMovie = 0x000002,
|
||||
// TrackInPreview = 0x000004,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct TkhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub creation_time: u64,
|
||||
pub modification_time: u64,
|
||||
pub track_id: u32,
|
||||
pub duration: u64,
|
||||
pub layer: u16,
|
||||
pub alternate_group: u16,
|
||||
|
||||
#[serde(with = "value_u8")]
|
||||
pub volume: FixedPointU8,
|
||||
pub matrix: Matrix,
|
||||
|
||||
#[serde(with = "value_u32")]
|
||||
pub width: FixedPointU16,
|
||||
|
||||
#[serde(with = "value_u32")]
|
||||
pub height: FixedPointU16,
|
||||
}
|
||||
|
||||
impl Default for TkhdBox {
|
||||
fn default() -> Self {
|
||||
TkhdBox {
|
||||
version: 0,
|
||||
flags: TrackFlag::TrackEnabled as u32,
|
||||
creation_time: 0,
|
||||
modification_time: 0,
|
||||
track_id: 0,
|
||||
duration: 0,
|
||||
layer: 0,
|
||||
alternate_group: 0,
|
||||
volume: FixedPointU8::new(1),
|
||||
matrix: Matrix::default(),
|
||||
width: FixedPointU16::new(0),
|
||||
height: FixedPointU16::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Matrix {
|
||||
pub a: i32,
|
||||
pub b: i32,
|
||||
pub u: i32,
|
||||
pub c: i32,
|
||||
pub d: i32,
|
||||
pub v: i32,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub w: i32,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Matrix {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{:#x} {:#x} {:#x} {:#x} {:#x} {:#x} {:#x} {:#x} {:#x}",
|
||||
self.a, self.b, self.u, self.c, self.d, self.v, self.x, self.y, self.w
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Matrix {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// unity matrix according to ISO/IEC 14496-12:2005(E)
|
||||
a: 0x00010000,
|
||||
b: 0,
|
||||
u: 0,
|
||||
c: 0,
|
||||
d: 0x00010000,
|
||||
v: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 0x40000000,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TkhdBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::TkhdBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
if self.version == 1 {
|
||||
size += 32;
|
||||
} else if self.version == 0 {
|
||||
size += 20;
|
||||
}
|
||||
size += 60;
|
||||
size
|
||||
}
|
||||
|
||||
pub fn set_width(&mut self, width: u16) {
|
||||
self.width = FixedPointU16::new(width);
|
||||
}
|
||||
|
||||
pub fn set_height(&mut self, height: u16) {
|
||||
self.height = FixedPointU16::new(height);
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for TkhdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!(
|
||||
"creation_time={} track_id={} duration={} layer={} volume={} matrix={} width={} height={}",
|
||||
self.creation_time,
|
||||
self.track_id,
|
||||
self.duration,
|
||||
self.layer,
|
||||
self.volume.value(),
|
||||
self.matrix,
|
||||
self.width.value(),
|
||||
self.height.value()
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for TkhdBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let (creation_time, modification_time, track_id, _, duration) = if version == 1 {
|
||||
(
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u64::<BigEndian>()?,
|
||||
)
|
||||
} else if version == 0 {
|
||||
(
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()? as u64,
|
||||
)
|
||||
} else {
|
||||
return Err(Error::InvalidData("version must be 0 or 1"));
|
||||
};
|
||||
reader.read_u64::<BigEndian>()?; // reserved
|
||||
let layer = reader.read_u16::<BigEndian>()?;
|
||||
let alternate_group = reader.read_u16::<BigEndian>()?;
|
||||
let volume = FixedPointU8::new_raw(reader.read_u16::<BigEndian>()?);
|
||||
|
||||
reader.read_u16::<BigEndian>()?; // reserved
|
||||
let matrix = Matrix {
|
||||
a: reader.read_i32::<BigEndian>()?,
|
||||
b: reader.read_i32::<BigEndian>()?,
|
||||
u: reader.read_i32::<BigEndian>()?,
|
||||
c: reader.read_i32::<BigEndian>()?,
|
||||
d: reader.read_i32::<BigEndian>()?,
|
||||
v: reader.read_i32::<BigEndian>()?,
|
||||
x: reader.read_i32::<BigEndian>()?,
|
||||
y: reader.read_i32::<BigEndian>()?,
|
||||
w: reader.read_i32::<BigEndian>()?,
|
||||
};
|
||||
|
||||
let width = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
|
||||
let height = FixedPointU16::new_raw(reader.read_u32::<BigEndian>()?);
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(TkhdBox {
|
||||
version,
|
||||
flags,
|
||||
creation_time,
|
||||
modification_time,
|
||||
track_id,
|
||||
duration,
|
||||
layer,
|
||||
alternate_group,
|
||||
volume,
|
||||
matrix,
|
||||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for TkhdBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
if self.version == 1 {
|
||||
writer.write_u64::<BigEndian>(self.creation_time)?;
|
||||
writer.write_u64::<BigEndian>(self.modification_time)?;
|
||||
writer.write_u32::<BigEndian>(self.track_id)?;
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u64::<BigEndian>(self.duration)?;
|
||||
} else if self.version == 0 {
|
||||
writer.write_u32::<BigEndian>(self.creation_time as u32)?;
|
||||
writer.write_u32::<BigEndian>(self.modification_time as u32)?;
|
||||
writer.write_u32::<BigEndian>(self.track_id)?;
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u32::<BigEndian>(self.duration as u32)?;
|
||||
} else {
|
||||
return Err(Error::InvalidData("version must be 0 or 1"));
|
||||
}
|
||||
|
||||
writer.write_u64::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.layer)?;
|
||||
writer.write_u16::<BigEndian>(self.alternate_group)?;
|
||||
writer.write_u16::<BigEndian>(self.volume.raw_value())?;
|
||||
|
||||
writer.write_u16::<BigEndian>(0)?; // reserved
|
||||
|
||||
writer.write_i32::<BigEndian>(self.matrix.a)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.b)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.u)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.c)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.d)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.v)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.x)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.y)?;
|
||||
writer.write_i32::<BigEndian>(self.matrix.w)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.width.raw_value())?;
|
||||
writer.write_u32::<BigEndian>(self.height.raw_value())?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_tkhd32() {
|
||||
let src_box = TkhdBox {
|
||||
version: 0,
|
||||
flags: TrackFlag::TrackEnabled as u32,
|
||||
creation_time: 100,
|
||||
modification_time: 200,
|
||||
track_id: 1,
|
||||
duration: 634634,
|
||||
layer: 0,
|
||||
alternate_group: 0,
|
||||
volume: FixedPointU8::new(1),
|
||||
matrix: Matrix::default(),
|
||||
width: FixedPointU16::new(512),
|
||||
height: FixedPointU16::new(288),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::TkhdBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = TkhdBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tkhd64() {
|
||||
let src_box = TkhdBox {
|
||||
version: 1,
|
||||
flags: TrackFlag::TrackEnabled as u32,
|
||||
creation_time: 100,
|
||||
modification_time: 200,
|
||||
track_id: 1,
|
||||
duration: 634634,
|
||||
layer: 0,
|
||||
alternate_group: 0,
|
||||
volume: FixedPointU8::new(1),
|
||||
matrix: Matrix::default(),
|
||||
width: FixedPointU16::new(512),
|
||||
height: FixedPointU16::new(288),
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::TkhdBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = TkhdBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
119
src/mp4box/traf.rs
Normal file
119
src/mp4box/traf.rs
Normal file
|
@ -0,0 +1,119 @@
|
|||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{tfdt::TfdtBox, tfhd::TfhdBox, trun::TrunBox};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct TrafBox {
|
||||
pub tfhd: TfhdBox,
|
||||
pub tfdt: Option<TfdtBox>,
|
||||
pub trun: Option<TrunBox>,
|
||||
}
|
||||
|
||||
impl TrafBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::TrafBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
size += self.tfhd.box_size();
|
||||
if let Some(ref tfdt) = self.tfdt {
|
||||
size += tfdt.box_size();
|
||||
}
|
||||
if let Some(ref trun) = self.trun {
|
||||
size += trun.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for TrafBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for TrafBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut tfhd = None;
|
||||
let mut tfdt = None;
|
||||
let mut trun = None;
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"traf box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
match name {
|
||||
BoxType::TfhdBox => {
|
||||
tfhd = Some(TfhdBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::TfdtBox => {
|
||||
tfdt = Some(TfdtBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::TrunBox => {
|
||||
trun = Some(TrunBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if tfhd.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::TfhdBox));
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(TrafBox {
|
||||
tfhd: tfhd.unwrap(),
|
||||
tfdt,
|
||||
trun,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for TrafBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
self.tfhd.write_box(writer)?;
|
||||
if let Some(ref tfdt) = self.tfdt {
|
||||
tfdt.write_box(writer)?;
|
||||
}
|
||||
if let Some(ref trun) = self.trun {
|
||||
trun.write_box(writer)?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
130
src/mp4box/trak.rs
Normal file
130
src/mp4box/trak.rs
Normal file
|
@ -0,0 +1,130 @@
|
|||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::meta::MetaBox;
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{edts::EdtsBox, mdia::MdiaBox, tkhd::TkhdBox};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct TrakBox {
|
||||
pub tkhd: TkhdBox,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub edts: Option<EdtsBox>,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<MetaBox>,
|
||||
|
||||
pub mdia: MdiaBox,
|
||||
}
|
||||
|
||||
impl TrakBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::TrakBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
size += self.tkhd.box_size();
|
||||
if let Some(ref edts) = self.edts {
|
||||
size += edts.box_size();
|
||||
}
|
||||
size += self.mdia.box_size();
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for TrakBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for TrakBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut tkhd = None;
|
||||
let mut edts = None;
|
||||
let mut meta = None;
|
||||
let mut mdia = None;
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"trak box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
match name {
|
||||
BoxType::TkhdBox => {
|
||||
tkhd = Some(TkhdBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::EdtsBox => {
|
||||
edts = Some(EdtsBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::MetaBox => {
|
||||
meta = Some(MetaBox::read_box(reader, s)?);
|
||||
}
|
||||
BoxType::MdiaBox => {
|
||||
mdia = Some(MdiaBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if tkhd.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::TkhdBox));
|
||||
}
|
||||
if mdia.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::MdiaBox));
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(TrakBox {
|
||||
tkhd: tkhd.unwrap(),
|
||||
edts,
|
||||
meta,
|
||||
mdia: mdia.unwrap(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for TrakBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
self.tkhd.write_box(writer)?;
|
||||
if let Some(ref edts) = self.edts {
|
||||
edts.write_box(writer)?;
|
||||
}
|
||||
self.mdia.write_box(writer)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
122
src/mp4box/trex.rs
Normal file
122
src/mp4box/trex.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct TrexBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub track_id: u32,
|
||||
pub default_sample_description_index: u32,
|
||||
pub default_sample_duration: u32,
|
||||
pub default_sample_size: u32,
|
||||
pub default_sample_flags: u32,
|
||||
}
|
||||
|
||||
impl TrexBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::TrexBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 20
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for TrexBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!(
|
||||
"track_id={} default_sample_duration={}",
|
||||
self.track_id, self.default_sample_duration
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for TrexBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let track_id = reader.read_u32::<BigEndian>()?;
|
||||
let default_sample_description_index = reader.read_u32::<BigEndian>()?;
|
||||
let default_sample_duration = reader.read_u32::<BigEndian>()?;
|
||||
let default_sample_size = reader.read_u32::<BigEndian>()?;
|
||||
let default_sample_flags = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(TrexBox {
|
||||
version,
|
||||
flags,
|
||||
track_id,
|
||||
default_sample_description_index,
|
||||
default_sample_duration,
|
||||
default_sample_size,
|
||||
default_sample_flags,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for TrexBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.track_id)?;
|
||||
writer.write_u32::<BigEndian>(self.default_sample_description_index)?;
|
||||
writer.write_u32::<BigEndian>(self.default_sample_duration)?;
|
||||
writer.write_u32::<BigEndian>(self.default_sample_size)?;
|
||||
writer.write_u32::<BigEndian>(self.default_sample_flags)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_trex() {
|
||||
let src_box = TrexBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
track_id: 1,
|
||||
default_sample_description_index: 1,
|
||||
default_sample_duration: 1000,
|
||||
default_sample_size: 0,
|
||||
default_sample_flags: 65536,
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::TrexBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = TrexBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
270
src/mp4box/trun.rs
Normal file
270
src/mp4box/trun.rs
Normal file
|
@ -0,0 +1,270 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct TrunBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub sample_count: u32,
|
||||
pub data_offset: Option<i32>,
|
||||
pub first_sample_flags: Option<u32>,
|
||||
|
||||
#[serde(skip_serializing)]
|
||||
pub sample_durations: Vec<u32>,
|
||||
#[serde(skip_serializing)]
|
||||
pub sample_sizes: Vec<u32>,
|
||||
#[serde(skip_serializing)]
|
||||
pub sample_flags: Vec<u32>,
|
||||
#[serde(skip_serializing)]
|
||||
pub sample_cts: Vec<u32>,
|
||||
}
|
||||
|
||||
impl TrunBox {
|
||||
pub const FLAG_DATA_OFFSET: u32 = 0x01;
|
||||
pub const FLAG_FIRST_SAMPLE_FLAGS: u32 = 0x04;
|
||||
pub const FLAG_SAMPLE_DURATION: u32 = 0x100;
|
||||
pub const FLAG_SAMPLE_SIZE: u32 = 0x200;
|
||||
pub const FLAG_SAMPLE_FLAGS: u32 = 0x400;
|
||||
pub const FLAG_SAMPLE_CTS: u32 = 0x800;
|
||||
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::TrunBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut sum = HEADER_SIZE + HEADER_EXT_SIZE + 4;
|
||||
if TrunBox::FLAG_DATA_OFFSET & self.flags > 0 {
|
||||
sum += 4;
|
||||
}
|
||||
if TrunBox::FLAG_FIRST_SAMPLE_FLAGS & self.flags > 0 {
|
||||
sum += 4;
|
||||
}
|
||||
if TrunBox::FLAG_SAMPLE_DURATION & self.flags > 0 {
|
||||
sum += 4 * self.sample_count as u64;
|
||||
}
|
||||
if TrunBox::FLAG_SAMPLE_SIZE & self.flags > 0 {
|
||||
sum += 4 * self.sample_count as u64;
|
||||
}
|
||||
if TrunBox::FLAG_SAMPLE_FLAGS & self.flags > 0 {
|
||||
sum += 4 * self.sample_count as u64;
|
||||
}
|
||||
if TrunBox::FLAG_SAMPLE_CTS & self.flags > 0 {
|
||||
sum += 4 * self.sample_count as u64;
|
||||
}
|
||||
sum
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for TrunBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("sample_size={}", self.sample_count);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for TrunBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let header_size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
let other_size = size_of::<u32>() // sample_count
|
||||
+ if TrunBox::FLAG_DATA_OFFSET & flags > 0 { size_of::<i32>() } else { 0 } // data_offset
|
||||
+ if TrunBox::FLAG_FIRST_SAMPLE_FLAGS & flags > 0 { size_of::<u32>() } else { 0 }; // first_sample_flags
|
||||
let sample_size = if TrunBox::FLAG_SAMPLE_DURATION & flags > 0 { size_of::<u32>() } else { 0 } // sample_duration
|
||||
+ if TrunBox::FLAG_SAMPLE_SIZE & flags > 0 { size_of::<u32>() } else { 0 } // sample_size
|
||||
+ if TrunBox::FLAG_SAMPLE_FLAGS & flags > 0 { size_of::<u32>() } else { 0 } // sample_flags
|
||||
+ if TrunBox::FLAG_SAMPLE_CTS & flags > 0 { size_of::<u32>() } else { 0 }; // sample_composition_time_offset
|
||||
|
||||
let sample_count = reader.read_u32::<BigEndian>()?;
|
||||
|
||||
let data_offset = if TrunBox::FLAG_DATA_OFFSET & flags > 0 {
|
||||
Some(reader.read_i32::<BigEndian>()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let first_sample_flags = if TrunBox::FLAG_FIRST_SAMPLE_FLAGS & flags > 0 {
|
||||
Some(reader.read_u32::<BigEndian>()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut sample_durations = Vec::new();
|
||||
let mut sample_sizes = Vec::new();
|
||||
let mut sample_flags = Vec::new();
|
||||
let mut sample_cts = Vec::new();
|
||||
if u64::from(sample_count) * sample_size as u64
|
||||
> size
|
||||
.saturating_sub(header_size)
|
||||
.saturating_sub(other_size as u64)
|
||||
{
|
||||
return Err(Error::InvalidData(
|
||||
"trun sample_count indicates more values than could fit in the box",
|
||||
));
|
||||
}
|
||||
if TrunBox::FLAG_SAMPLE_DURATION & flags > 0 {
|
||||
sample_durations.reserve(sample_count as usize);
|
||||
}
|
||||
if TrunBox::FLAG_SAMPLE_SIZE & flags > 0 {
|
||||
sample_sizes.reserve(sample_count as usize);
|
||||
}
|
||||
if TrunBox::FLAG_SAMPLE_FLAGS & flags > 0 {
|
||||
sample_flags.reserve(sample_count as usize);
|
||||
}
|
||||
if TrunBox::FLAG_SAMPLE_CTS & flags > 0 {
|
||||
sample_cts.reserve(sample_count as usize);
|
||||
}
|
||||
|
||||
for _ in 0..sample_count {
|
||||
if TrunBox::FLAG_SAMPLE_DURATION & flags > 0 {
|
||||
let duration = reader.read_u32::<BigEndian>()?;
|
||||
sample_durations.push(duration);
|
||||
}
|
||||
|
||||
if TrunBox::FLAG_SAMPLE_SIZE & flags > 0 {
|
||||
let sample_size = reader.read_u32::<BigEndian>()?;
|
||||
sample_sizes.push(sample_size);
|
||||
}
|
||||
|
||||
if TrunBox::FLAG_SAMPLE_FLAGS & flags > 0 {
|
||||
let sample_flag = reader.read_u32::<BigEndian>()?;
|
||||
sample_flags.push(sample_flag);
|
||||
}
|
||||
|
||||
if TrunBox::FLAG_SAMPLE_CTS & flags > 0 {
|
||||
let cts = reader.read_u32::<BigEndian>()?;
|
||||
sample_cts.push(cts);
|
||||
}
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(TrunBox {
|
||||
version,
|
||||
flags,
|
||||
sample_count,
|
||||
data_offset,
|
||||
first_sample_flags,
|
||||
sample_durations,
|
||||
sample_sizes,
|
||||
sample_flags,
|
||||
sample_cts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for TrunBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.sample_count)?;
|
||||
if let Some(v) = self.data_offset {
|
||||
writer.write_i32::<BigEndian>(v)?;
|
||||
}
|
||||
if let Some(v) = self.first_sample_flags {
|
||||
writer.write_u32::<BigEndian>(v)?;
|
||||
}
|
||||
if self.sample_count != self.sample_sizes.len() as u32 {
|
||||
return Err(Error::InvalidData("sample count out of sync"));
|
||||
}
|
||||
for i in 0..self.sample_count as usize {
|
||||
if TrunBox::FLAG_SAMPLE_DURATION & self.flags > 0 {
|
||||
writer.write_u32::<BigEndian>(self.sample_durations[i])?;
|
||||
}
|
||||
if TrunBox::FLAG_SAMPLE_SIZE & self.flags > 0 {
|
||||
writer.write_u32::<BigEndian>(self.sample_sizes[i])?;
|
||||
}
|
||||
if TrunBox::FLAG_SAMPLE_FLAGS & self.flags > 0 {
|
||||
writer.write_u32::<BigEndian>(self.sample_flags[i])?;
|
||||
}
|
||||
if TrunBox::FLAG_SAMPLE_CTS & self.flags > 0 {
|
||||
writer.write_u32::<BigEndian>(self.sample_cts[i])?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_trun_same_size() {
|
||||
let src_box = TrunBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
data_offset: None,
|
||||
sample_count: 0,
|
||||
sample_sizes: vec![],
|
||||
sample_flags: vec![],
|
||||
first_sample_flags: None,
|
||||
sample_durations: vec![],
|
||||
sample_cts: vec![],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::TrunBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = TrunBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_trun_many_sizes() {
|
||||
let src_box = TrunBox {
|
||||
version: 0,
|
||||
flags: TrunBox::FLAG_SAMPLE_DURATION
|
||||
| TrunBox::FLAG_SAMPLE_SIZE
|
||||
| TrunBox::FLAG_SAMPLE_FLAGS
|
||||
| TrunBox::FLAG_SAMPLE_CTS,
|
||||
data_offset: None,
|
||||
sample_count: 9,
|
||||
sample_sizes: vec![1165, 11, 11, 8545, 10126, 10866, 9643, 9351, 7730],
|
||||
sample_flags: vec![1165, 11, 11, 8545, 10126, 10866, 9643, 9351, 7730],
|
||||
first_sample_flags: None,
|
||||
sample_durations: vec![1165, 11, 11, 8545, 10126, 10866, 9643, 9351, 7730],
|
||||
sample_cts: vec![1165, 11, 11, 8545, 10126, 10866, 9643, 9351, 7730],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::TrunBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = TrunBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
189
src/mp4box/tx3g.rs
Normal file
189
src/mp4box/tx3g.rs
Normal file
|
@ -0,0 +1,189 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Tx3gBox {
|
||||
pub data_reference_index: u16,
|
||||
pub display_flags: u32,
|
||||
pub horizontal_justification: i8,
|
||||
pub vertical_justification: i8,
|
||||
pub bg_color_rgba: RgbaColor,
|
||||
pub box_record: [i16; 4],
|
||||
pub style_record: [u8; 12],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct RgbaColor {
|
||||
pub red: u8,
|
||||
pub green: u8,
|
||||
pub blue: u8,
|
||||
pub alpha: u8,
|
||||
}
|
||||
|
||||
impl Default for Tx3gBox {
|
||||
fn default() -> Self {
|
||||
Tx3gBox {
|
||||
data_reference_index: 0,
|
||||
display_flags: 0,
|
||||
horizontal_justification: 1,
|
||||
vertical_justification: -1,
|
||||
bg_color_rgba: RgbaColor {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0,
|
||||
alpha: 255,
|
||||
},
|
||||
box_record: [0, 0, 0, 0],
|
||||
style_record: [0, 0, 0, 0, 0, 1, 0, 16, 255, 255, 255, 255],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Tx3gBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::Tx3gBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + 6 + 32
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for Tx3gBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("data_reference_index={} horizontal_justification={} vertical_justification={} rgba={}{}{}{}",
|
||||
self.data_reference_index, self.horizontal_justification,
|
||||
self.vertical_justification, self.bg_color_rgba.red,
|
||||
self.bg_color_rgba.green, self.bg_color_rgba.blue, self.bg_color_rgba.alpha);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Tx3gBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
reader.read_u32::<BigEndian>()?; // reserved
|
||||
reader.read_u16::<BigEndian>()?; // reserved
|
||||
let data_reference_index = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
let display_flags = reader.read_u32::<BigEndian>()?;
|
||||
let horizontal_justification = reader.read_i8()?;
|
||||
let vertical_justification = reader.read_i8()?;
|
||||
let bg_color_rgba = RgbaColor {
|
||||
red: reader.read_u8()?,
|
||||
green: reader.read_u8()?,
|
||||
blue: reader.read_u8()?,
|
||||
alpha: reader.read_u8()?,
|
||||
};
|
||||
let box_record: [i16; 4] = [
|
||||
reader.read_i16::<BigEndian>()?,
|
||||
reader.read_i16::<BigEndian>()?,
|
||||
reader.read_i16::<BigEndian>()?,
|
||||
reader.read_i16::<BigEndian>()?,
|
||||
];
|
||||
let style_record: [u8; 12] = [
|
||||
reader.read_u8()?,
|
||||
reader.read_u8()?,
|
||||
reader.read_u8()?,
|
||||
reader.read_u8()?,
|
||||
reader.read_u8()?,
|
||||
reader.read_u8()?,
|
||||
reader.read_u8()?,
|
||||
reader.read_u8()?,
|
||||
reader.read_u8()?,
|
||||
reader.read_u8()?,
|
||||
reader.read_u8()?,
|
||||
reader.read_u8()?,
|
||||
];
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(Tx3gBox {
|
||||
data_reference_index,
|
||||
display_flags,
|
||||
horizontal_justification,
|
||||
vertical_justification,
|
||||
bg_color_rgba,
|
||||
box_record,
|
||||
style_record,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for Tx3gBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(0)?; // reserved
|
||||
writer.write_u16::<BigEndian>(self.data_reference_index)?;
|
||||
writer.write_u32::<BigEndian>(self.display_flags)?;
|
||||
writer.write_i8(self.horizontal_justification)?;
|
||||
writer.write_i8(self.vertical_justification)?;
|
||||
writer.write_u8(self.bg_color_rgba.red)?;
|
||||
writer.write_u8(self.bg_color_rgba.green)?;
|
||||
writer.write_u8(self.bg_color_rgba.blue)?;
|
||||
writer.write_u8(self.bg_color_rgba.alpha)?;
|
||||
for n in 0..4 {
|
||||
writer.write_i16::<BigEndian>(self.box_record[n])?;
|
||||
}
|
||||
for n in 0..12 {
|
||||
writer.write_u8(self.style_record[n])?;
|
||||
}
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_tx3g() {
|
||||
let src_box = Tx3gBox {
|
||||
data_reference_index: 1,
|
||||
display_flags: 0,
|
||||
horizontal_justification: 1,
|
||||
vertical_justification: -1,
|
||||
bg_color_rgba: RgbaColor {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0,
|
||||
alpha: 255,
|
||||
},
|
||||
box_record: [0, 0, 0, 0],
|
||||
style_record: [0, 0, 0, 0, 0, 1, 0, 16, 255, 255, 255, 255],
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::Tx3gBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = Tx3gBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
136
src/mp4box/udta.rs
Normal file
136
src/mp4box/udta.rs
Normal file
|
@ -0,0 +1,136 @@
|
|||
use std::io::{Read, Seek};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::mp4box::meta::MetaBox;
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct UdtaBox {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub meta: Option<MetaBox>,
|
||||
}
|
||||
|
||||
impl UdtaBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::UdtaBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE;
|
||||
if let Some(meta) = &self.meta {
|
||||
size += meta.box_size();
|
||||
}
|
||||
size
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for UdtaBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
Ok(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for UdtaBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let mut meta = None;
|
||||
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"udta box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
match name {
|
||||
BoxType::MetaBox => {
|
||||
meta = Some(MetaBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(UdtaBox { meta })
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for UdtaBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
if let Some(meta) = &self.meta {
|
||||
meta.write_box(writer)?;
|
||||
}
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_udta_empty() {
|
||||
let src_box = UdtaBox { meta: None };
|
||||
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::UdtaBox);
|
||||
assert_eq!(header.size, src_box.box_size());
|
||||
|
||||
let dst_box = UdtaBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(dst_box, src_box);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_udta() {
|
||||
let src_box = UdtaBox {
|
||||
meta: Some(MetaBox::default()),
|
||||
};
|
||||
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::UdtaBox);
|
||||
assert_eq!(header.size, src_box.box_size());
|
||||
|
||||
let dst_box = UdtaBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(dst_box, src_box);
|
||||
}
|
||||
}
|
124
src/mp4box/vmhd.rs
Normal file
124
src/mp4box/vmhd.rs
Normal file
|
@ -0,0 +1,124 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct VmhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub graphics_mode: u16,
|
||||
pub op_color: RgbColor,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct RgbColor {
|
||||
pub red: u16,
|
||||
pub green: u16,
|
||||
pub blue: u16,
|
||||
}
|
||||
|
||||
impl VmhdBox {
|
||||
pub fn get_type(&self) -> BoxType {
|
||||
BoxType::VmhdBox
|
||||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 8
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for VmhdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!(
|
||||
"graphics_mode={} op_color={}{}{}",
|
||||
self.graphics_mode, self.op_color.red, self.op_color.green, self.op_color.blue
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for VmhdBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let graphics_mode = reader.read_u16::<BigEndian>()?;
|
||||
let op_color = RgbColor {
|
||||
red: reader.read_u16::<BigEndian>()?,
|
||||
green: reader.read_u16::<BigEndian>()?,
|
||||
blue: reader.read_u16::<BigEndian>()?,
|
||||
};
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(VmhdBox {
|
||||
version,
|
||||
flags,
|
||||
graphics_mode,
|
||||
op_color,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for VmhdBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u16::<BigEndian>(self.graphics_mode)?;
|
||||
writer.write_u16::<BigEndian>(self.op_color.red)?;
|
||||
writer.write_u16::<BigEndian>(self.op_color.green)?;
|
||||
writer.write_u16::<BigEndian>(self.op_color.blue)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_vmhd() {
|
||||
let src_box = VmhdBox {
|
||||
version: 0,
|
||||
flags: 1,
|
||||
graphics_mode: 0,
|
||||
op_color: RgbColor {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0,
|
||||
},
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::VmhdBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = VmhdBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
205
src/mp4box/vp09.rs
Normal file
205
src/mp4box/vp09.rs
Normal file
|
@ -0,0 +1,205 @@
|
|||
use crate::mp4box::vpcc::VpccBox;
|
||||
use crate::mp4box::*;
|
||||
use crate::Mp4Box;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct Vp09Box {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub start_code: u16,
|
||||
pub data_reference_index: u16,
|
||||
pub reserved0: [u8; 16],
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
pub horizresolution: (u16, u16),
|
||||
pub vertresolution: (u16, u16),
|
||||
pub reserved1: [u8; 4],
|
||||
pub frame_count: u16,
|
||||
pub compressorname: [u8; 32],
|
||||
pub depth: u16,
|
||||
pub end_code: u16,
|
||||
pub vpcc: VpccBox,
|
||||
}
|
||||
|
||||
impl Vp09Box {
|
||||
pub const DEFAULT_START_CODE: u16 = 0;
|
||||
pub const DEFAULT_END_CODE: u16 = 0xFFFF;
|
||||
pub const DEFAULT_DATA_REFERENCE_INDEX: u16 = 1;
|
||||
pub const DEFAULT_HORIZRESOLUTION: (u16, u16) = (0x48, 0x00);
|
||||
pub const DEFAULT_VERTRESOLUTION: (u16, u16) = (0x48, 0x00);
|
||||
pub const DEFAULT_FRAME_COUNT: u16 = 1;
|
||||
pub const DEFAULT_COMPRESSORNAME: [u8; 32] = [0; 32];
|
||||
pub const DEFAULT_DEPTH: u16 = 24;
|
||||
|
||||
pub fn new(config: &Vp9Config) -> Self {
|
||||
Vp09Box {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
start_code: Vp09Box::DEFAULT_START_CODE,
|
||||
data_reference_index: Vp09Box::DEFAULT_DATA_REFERENCE_INDEX,
|
||||
reserved0: Default::default(),
|
||||
width: config.width,
|
||||
height: config.height,
|
||||
horizresolution: Vp09Box::DEFAULT_HORIZRESOLUTION,
|
||||
vertresolution: Vp09Box::DEFAULT_VERTRESOLUTION,
|
||||
reserved1: Default::default(),
|
||||
frame_count: Vp09Box::DEFAULT_FRAME_COUNT,
|
||||
compressorname: Vp09Box::DEFAULT_COMPRESSORNAME,
|
||||
depth: Vp09Box::DEFAULT_DEPTH,
|
||||
end_code: Vp09Box::DEFAULT_END_CODE,
|
||||
vpcc: VpccBox {
|
||||
version: VpccBox::DEFAULT_VERSION,
|
||||
flags: 0,
|
||||
profile: 0,
|
||||
level: 0x1F,
|
||||
bit_depth: VpccBox::DEFAULT_BIT_DEPTH,
|
||||
chroma_subsampling: 0,
|
||||
video_full_range_flag: false,
|
||||
color_primaries: 0,
|
||||
transfer_characteristics: 0,
|
||||
matrix_coefficients: 0,
|
||||
codec_initialization_data_size: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for Vp09Box {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::Vp09Box
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
0x6A
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
Ok(format!("{self:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for Vp09Box {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let start_code: u16 = reader.read_u16::<BigEndian>()?;
|
||||
let data_reference_index: u16 = reader.read_u16::<BigEndian>()?;
|
||||
let reserved0: [u8; 16] = {
|
||||
let mut buf = [0u8; 16];
|
||||
reader.read_exact(&mut buf)?;
|
||||
buf
|
||||
};
|
||||
let width: u16 = reader.read_u16::<BigEndian>()?;
|
||||
let height: u16 = reader.read_u16::<BigEndian>()?;
|
||||
let horizresolution: (u16, u16) = (
|
||||
reader.read_u16::<BigEndian>()?,
|
||||
reader.read_u16::<BigEndian>()?,
|
||||
);
|
||||
let vertresolution: (u16, u16) = (
|
||||
reader.read_u16::<BigEndian>()?,
|
||||
reader.read_u16::<BigEndian>()?,
|
||||
);
|
||||
let reserved1: [u8; 4] = {
|
||||
let mut buf = [0u8; 4];
|
||||
reader.read_exact(&mut buf)?;
|
||||
buf
|
||||
};
|
||||
let frame_count: u16 = reader.read_u16::<BigEndian>()?;
|
||||
let compressorname: [u8; 32] = {
|
||||
let mut buf = [0u8; 32];
|
||||
reader.read_exact(&mut buf)?;
|
||||
buf
|
||||
};
|
||||
let depth: u16 = reader.read_u16::<BigEndian>()?;
|
||||
let end_code: u16 = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
let vpcc = {
|
||||
let header = BoxHeader::read(reader)?;
|
||||
if header.size > size {
|
||||
return Err(Error::InvalidData(
|
||||
"vp09 box contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
VpccBox::read_box(reader, header.size)?
|
||||
};
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(Self {
|
||||
version,
|
||||
flags,
|
||||
start_code,
|
||||
data_reference_index,
|
||||
reserved0,
|
||||
width,
|
||||
height,
|
||||
horizresolution,
|
||||
vertresolution,
|
||||
reserved1,
|
||||
frame_count,
|
||||
compressorname,
|
||||
depth,
|
||||
end_code,
|
||||
vpcc,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for Vp09Box {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u16::<BigEndian>(self.start_code)?;
|
||||
writer.write_u16::<BigEndian>(self.data_reference_index)?;
|
||||
writer.write_all(&self.reserved0)?;
|
||||
writer.write_u16::<BigEndian>(self.width)?;
|
||||
writer.write_u16::<BigEndian>(self.height)?;
|
||||
writer.write_u16::<BigEndian>(self.horizresolution.0)?;
|
||||
writer.write_u16::<BigEndian>(self.horizresolution.1)?;
|
||||
writer.write_u16::<BigEndian>(self.vertresolution.0)?;
|
||||
writer.write_u16::<BigEndian>(self.vertresolution.1)?;
|
||||
writer.write_all(&self.reserved1)?;
|
||||
writer.write_u16::<BigEndian>(self.frame_count)?;
|
||||
writer.write_all(&self.compressorname)?;
|
||||
writer.write_u16::<BigEndian>(self.depth)?;
|
||||
writer.write_u16::<BigEndian>(self.end_code)?;
|
||||
VpccBox::write_box(&self.vpcc, writer)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_vpcc() {
|
||||
let src_box = Vp09Box::new(&Vp9Config {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
});
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::Vp09Box);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = Vp09Box::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
132
src/mp4box/vpcc.rs
Normal file
132
src/mp4box/vpcc.rs
Normal file
|
@ -0,0 +1,132 @@
|
|||
use crate::mp4box::*;
|
||||
use crate::Mp4Box;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct VpccBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub profile: u8,
|
||||
pub level: u8,
|
||||
pub bit_depth: u8,
|
||||
pub chroma_subsampling: u8,
|
||||
pub video_full_range_flag: bool,
|
||||
pub color_primaries: u8,
|
||||
pub transfer_characteristics: u8,
|
||||
pub matrix_coefficients: u8,
|
||||
pub codec_initialization_data_size: u16,
|
||||
}
|
||||
|
||||
impl VpccBox {
|
||||
pub const DEFAULT_VERSION: u8 = 1;
|
||||
pub const DEFAULT_BIT_DEPTH: u8 = 8;
|
||||
}
|
||||
|
||||
impl Mp4Box for VpccBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
BoxType::VpccBox
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 8
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
Ok(serde_json::to_string(&self).unwrap())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
Ok(format!("{self:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> ReadBox<&mut R> for VpccBox {
|
||||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let profile: u8 = reader.read_u8()?;
|
||||
let level: u8 = reader.read_u8()?;
|
||||
let (bit_depth, chroma_subsampling, video_full_range_flag) = {
|
||||
let b = reader.read_u8()?;
|
||||
(b >> 4, b << 4 >> 5, b & 0x01 == 1)
|
||||
};
|
||||
let transfer_characteristics: u8 = reader.read_u8()?;
|
||||
let matrix_coefficients: u8 = reader.read_u8()?;
|
||||
let codec_initialization_data_size: u16 = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(Self {
|
||||
version,
|
||||
flags,
|
||||
profile,
|
||||
level,
|
||||
bit_depth,
|
||||
chroma_subsampling,
|
||||
video_full_range_flag,
|
||||
color_primaries: 0,
|
||||
transfer_characteristics,
|
||||
matrix_coefficients,
|
||||
codec_initialization_data_size,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> WriteBox<&mut W> for VpccBox {
|
||||
fn write_box(&self, writer: &mut W) -> Result<u64> {
|
||||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u8(self.profile)?;
|
||||
writer.write_u8(self.level)?;
|
||||
writer.write_u8(
|
||||
(self.bit_depth << 4)
|
||||
| (self.chroma_subsampling << 1)
|
||||
| (self.video_full_range_flag as u8),
|
||||
)?;
|
||||
writer.write_u8(self.color_primaries)?;
|
||||
writer.write_u8(self.transfer_characteristics)?;
|
||||
writer.write_u8(self.matrix_coefficients)?;
|
||||
writer.write_u16::<BigEndian>(self.codec_initialization_data_size)?;
|
||||
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[test]
|
||||
fn test_vpcc() {
|
||||
let src_box = VpccBox {
|
||||
version: VpccBox::DEFAULT_VERSION,
|
||||
flags: 0,
|
||||
profile: 0,
|
||||
level: 0x1F,
|
||||
bit_depth: VpccBox::DEFAULT_BIT_DEPTH,
|
||||
chroma_subsampling: 0,
|
||||
video_full_range_flag: false,
|
||||
color_primaries: 0,
|
||||
transfer_characteristics: 0,
|
||||
matrix_coefficients: 0,
|
||||
codec_initialization_data_size: 0,
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
src_box.write_box(&mut buf).unwrap();
|
||||
assert_eq!(buf.len(), src_box.box_size() as usize);
|
||||
|
||||
let mut reader = Cursor::new(&buf);
|
||||
let header = BoxHeader::read(&mut reader).unwrap();
|
||||
assert_eq!(header.name, BoxType::VpccBox);
|
||||
assert_eq!(src_box.box_size(), header.size);
|
||||
|
||||
let dst_box = VpccBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
284
src/reader.rs
Normal file
284
src/reader.rs
Normal file
|
@ -0,0 +1,284 @@
|
|||
use std::collections::HashMap;
|
||||
use std::io::{Read, Seek};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::meta::MetaBox;
|
||||
use crate::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mp4Reader<R> {
|
||||
reader: R,
|
||||
pub ftyp: FtypBox,
|
||||
pub moov: MoovBox,
|
||||
pub moofs: Vec<MoofBox>,
|
||||
pub emsgs: Vec<EmsgBox>,
|
||||
|
||||
tracks: HashMap<u32, Mp4Track>,
|
||||
size: u64,
|
||||
}
|
||||
|
||||
impl<R: Read + Seek> Mp4Reader<R> {
|
||||
pub fn read_header(mut reader: R, size: u64) -> Result<Self> {
|
||||
let start = reader.stream_position()?;
|
||||
|
||||
let mut ftyp = None;
|
||||
let mut moov = None;
|
||||
let mut moofs = Vec::new();
|
||||
let mut moof_offsets = Vec::new();
|
||||
let mut emsgs = Vec::new();
|
||||
|
||||
let mut current = start;
|
||||
while current < size {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(&mut reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"file contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
// Break if size zero BoxHeader, which can result in dead-loop.
|
||||
if s == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
// Match and parse the atom boxes.
|
||||
match name {
|
||||
BoxType::FtypBox => {
|
||||
ftyp = Some(FtypBox::read_box(&mut reader, s)?);
|
||||
}
|
||||
BoxType::FreeBox => {
|
||||
skip_box(&mut reader, s)?;
|
||||
}
|
||||
BoxType::MdatBox => {
|
||||
skip_box(&mut reader, s)?;
|
||||
}
|
||||
BoxType::MoovBox => {
|
||||
moov = Some(MoovBox::read_box(&mut reader, s)?);
|
||||
}
|
||||
BoxType::MoofBox => {
|
||||
let moof_offset = reader.stream_position()? - 8;
|
||||
let moof = MoofBox::read_box(&mut reader, s)?;
|
||||
moofs.push(moof);
|
||||
moof_offsets.push(moof_offset);
|
||||
}
|
||||
BoxType::EmsgBox => {
|
||||
let emsg = EmsgBox::read_box(&mut reader, s)?;
|
||||
emsgs.push(emsg);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(&mut reader, s)?;
|
||||
}
|
||||
}
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if ftyp.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::FtypBox));
|
||||
}
|
||||
if moov.is_none() {
|
||||
return Err(Error::BoxNotFound(BoxType::MoovBox));
|
||||
}
|
||||
|
||||
let size = current - start;
|
||||
let mut tracks = if let Some(ref moov) = moov {
|
||||
if moov.traks.iter().any(|trak| trak.tkhd.track_id == 0) {
|
||||
return Err(Error::InvalidData("illegal track id 0"));
|
||||
}
|
||||
moov.traks
|
||||
.iter()
|
||||
.map(|trak| (trak.tkhd.track_id, Mp4Track::from(trak)))
|
||||
.collect()
|
||||
} else {
|
||||
HashMap::new()
|
||||
};
|
||||
|
||||
// Update tracks if any fragmented (moof) boxes are found.
|
||||
if !moofs.is_empty() {
|
||||
let mut default_sample_duration = 0;
|
||||
if let Some(ref moov) = moov {
|
||||
if let Some(ref mvex) = &moov.mvex {
|
||||
default_sample_duration = mvex.trex.default_sample_duration
|
||||
}
|
||||
}
|
||||
|
||||
for (moof, moof_offset) in moofs.iter().zip(moof_offsets) {
|
||||
for traf in moof.trafs.iter() {
|
||||
let track_id = traf.tfhd.track_id;
|
||||
if let Some(track) = tracks.get_mut(&track_id) {
|
||||
track.default_sample_duration = default_sample_duration;
|
||||
track.moof_offsets.push(moof_offset);
|
||||
track.trafs.push(traf.clone())
|
||||
} else {
|
||||
return Err(Error::TrakNotFound(track_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Mp4Reader {
|
||||
reader,
|
||||
ftyp: ftyp.unwrap(),
|
||||
moov: moov.unwrap(),
|
||||
moofs,
|
||||
emsgs,
|
||||
size,
|
||||
tracks,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn read_fragment_header<FR: Read + Seek>(
|
||||
&self,
|
||||
mut reader: FR,
|
||||
size: u64,
|
||||
) -> Result<Mp4Reader<FR>> {
|
||||
let start = reader.stream_position()?;
|
||||
|
||||
let mut moofs = Vec::new();
|
||||
let mut moof_offsets = Vec::new();
|
||||
|
||||
let mut current = start;
|
||||
while current < size {
|
||||
// Get box header.
|
||||
let header = BoxHeader::read(&mut reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if s > size {
|
||||
return Err(Error::InvalidData(
|
||||
"file contains a box with a larger size than it",
|
||||
));
|
||||
}
|
||||
|
||||
// Break if size zero BoxHeader, which can result in dead-loop.
|
||||
if s == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
// Match and parse the atom boxes.
|
||||
match name {
|
||||
BoxType::MdatBox => {
|
||||
skip_box(&mut reader, s)?;
|
||||
}
|
||||
BoxType::MoofBox => {
|
||||
let moof_offset = reader.stream_position()? - 8;
|
||||
let moof = MoofBox::read_box(&mut reader, s)?;
|
||||
moofs.push(moof);
|
||||
moof_offsets.push(moof_offset);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
skip_box(&mut reader, s)?;
|
||||
}
|
||||
}
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if moofs.is_empty() {
|
||||
return Err(Error::BoxNotFound(BoxType::MoofBox));
|
||||
}
|
||||
|
||||
let size = current - start;
|
||||
let mut tracks: HashMap<u32, Mp4Track> = self
|
||||
.moov
|
||||
.traks
|
||||
.iter()
|
||||
.map(|trak| (trak.tkhd.track_id, Mp4Track::from(trak)))
|
||||
.collect();
|
||||
|
||||
let mut default_sample_duration = 0;
|
||||
if let Some(ref mvex) = &self.moov.mvex {
|
||||
default_sample_duration = mvex.trex.default_sample_duration
|
||||
}
|
||||
|
||||
for (moof, moof_offset) in moofs.iter().zip(moof_offsets) {
|
||||
for traf in moof.trafs.iter() {
|
||||
let track_id = traf.tfhd.track_id;
|
||||
if let Some(track) = tracks.get_mut(&track_id) {
|
||||
track.default_sample_duration = default_sample_duration;
|
||||
track.moof_offsets.push(moof_offset);
|
||||
track.trafs.push(traf.clone())
|
||||
} else {
|
||||
return Err(Error::TrakNotFound(track_id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Mp4Reader {
|
||||
reader,
|
||||
ftyp: self.ftyp.clone(),
|
||||
moov: self.moov.clone(),
|
||||
moofs,
|
||||
emsgs: Vec::new(),
|
||||
tracks,
|
||||
size,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn size(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
|
||||
pub fn major_brand(&self) -> &FourCC {
|
||||
&self.ftyp.major_brand
|
||||
}
|
||||
|
||||
pub fn minor_version(&self) -> u32 {
|
||||
self.ftyp.minor_version
|
||||
}
|
||||
|
||||
pub fn compatible_brands(&self) -> &[FourCC] {
|
||||
&self.ftyp.compatible_brands
|
||||
}
|
||||
|
||||
pub fn duration(&self) -> Duration {
|
||||
Duration::from_millis(self.moov.mvhd.duration * 1000 / self.moov.mvhd.timescale as u64)
|
||||
}
|
||||
|
||||
pub fn timescale(&self) -> u32 {
|
||||
self.moov.mvhd.timescale
|
||||
}
|
||||
|
||||
pub fn is_fragmented(&self) -> bool {
|
||||
!self.moofs.is_empty()
|
||||
}
|
||||
|
||||
pub fn tracks(&self) -> &HashMap<u32, Mp4Track> {
|
||||
&self.tracks
|
||||
}
|
||||
|
||||
pub fn sample_count(&self, track_id: u32) -> Result<u32> {
|
||||
if let Some(track) = self.tracks.get(&track_id) {
|
||||
Ok(track.sample_count())
|
||||
} else {
|
||||
Err(Error::TrakNotFound(track_id))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_sample(&mut self, track_id: u32, sample_id: u32) -> Result<Option<Mp4Sample>> {
|
||||
if let Some(track) = self.tracks.get(&track_id) {
|
||||
track.read_sample(&mut self.reader, sample_id)
|
||||
} else {
|
||||
Err(Error::TrakNotFound(track_id))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_offset(&mut self, track_id: u32, sample_id: u32) -> Result<u64> {
|
||||
if let Some(track) = self.tracks.get(&track_id) {
|
||||
track.sample_offset(sample_id)
|
||||
} else {
|
||||
Err(Error::TrakNotFound(track_id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> Mp4Reader<R> {
|
||||
pub fn metadata(&self) -> impl Metadata<'_> {
|
||||
self.moov.udta.as_ref().and_then(|udta| {
|
||||
udta.meta.as_ref().and_then(|meta| match meta {
|
||||
MetaBox::Mdir { ilst } => ilst.as_ref(),
|
||||
_ => None,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
918
src/track.rs
Normal file
918
src/track.rs
Normal file
|
@ -0,0 +1,918 @@
|
|||
use bytes::BytesMut;
|
||||
use std::cmp;
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::mp4box::traf::TrafBox;
|
||||
use crate::mp4box::trak::TrakBox;
|
||||
use crate::mp4box::trun::TrunBox;
|
||||
use crate::mp4box::{
|
||||
avc1::Avc1Box, co64::Co64Box, ctts::CttsBox, ctts::CttsEntry, hev1::Hev1Box, mp4a::Mp4aBox,
|
||||
smhd::SmhdBox, stco::StcoBox, stsc::StscEntry, stss::StssBox, stts::SttsEntry, tx3g::Tx3gBox,
|
||||
vmhd::VmhdBox, vp09::Vp09Box,
|
||||
};
|
||||
use crate::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TrackConfig {
|
||||
pub track_type: TrackType,
|
||||
pub timescale: u32,
|
||||
pub language: String,
|
||||
pub media_conf: MediaConfig,
|
||||
}
|
||||
|
||||
impl From<MediaConfig> for TrackConfig {
|
||||
fn from(media_conf: MediaConfig) -> Self {
|
||||
match media_conf {
|
||||
MediaConfig::AvcConfig(avc_conf) => Self::from(avc_conf),
|
||||
MediaConfig::HevcConfig(hevc_conf) => Self::from(hevc_conf),
|
||||
MediaConfig::AacConfig(aac_conf) => Self::from(aac_conf),
|
||||
MediaConfig::TtxtConfig(ttxt_conf) => Self::from(ttxt_conf),
|
||||
MediaConfig::Vp9Config(vp9_config) => Self::from(vp9_config),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AvcConfig> for TrackConfig {
|
||||
fn from(avc_conf: AvcConfig) -> Self {
|
||||
Self {
|
||||
track_type: TrackType::Video,
|
||||
timescale: 1000, // XXX
|
||||
language: String::from("und"), // XXX
|
||||
media_conf: MediaConfig::AvcConfig(avc_conf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HevcConfig> for TrackConfig {
|
||||
fn from(hevc_conf: HevcConfig) -> Self {
|
||||
Self {
|
||||
track_type: TrackType::Video,
|
||||
timescale: 1000, // XXX
|
||||
language: String::from("und"), // XXX
|
||||
media_conf: MediaConfig::HevcConfig(hevc_conf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AacConfig> for TrackConfig {
|
||||
fn from(aac_conf: AacConfig) -> Self {
|
||||
Self {
|
||||
track_type: TrackType::Audio,
|
||||
timescale: 1000, // XXX
|
||||
language: String::from("und"), // XXX
|
||||
media_conf: MediaConfig::AacConfig(aac_conf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TtxtConfig> for TrackConfig {
|
||||
fn from(txtt_conf: TtxtConfig) -> Self {
|
||||
Self {
|
||||
track_type: TrackType::Subtitle,
|
||||
timescale: 1000, // XXX
|
||||
language: String::from("und"), // XXX
|
||||
media_conf: MediaConfig::TtxtConfig(txtt_conf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vp9Config> for TrackConfig {
|
||||
fn from(vp9_conf: Vp9Config) -> Self {
|
||||
Self {
|
||||
track_type: TrackType::Video,
|
||||
timescale: 1000, // XXX
|
||||
language: String::from("und"), // XXX
|
||||
media_conf: MediaConfig::Vp9Config(vp9_conf),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mp4Track {
|
||||
pub trak: TrakBox,
|
||||
pub trafs: Vec<TrafBox>,
|
||||
pub moof_offsets: Vec<u64>,
|
||||
|
||||
// Fragmented Tracks Defaults.
|
||||
pub default_sample_duration: u32,
|
||||
}
|
||||
|
||||
impl Mp4Track {
|
||||
pub(crate) fn from(trak: &TrakBox) -> Self {
|
||||
let trak = trak.clone();
|
||||
Self {
|
||||
trak,
|
||||
trafs: Vec::new(),
|
||||
moof_offsets: Vec::new(),
|
||||
default_sample_duration: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn track_id(&self) -> u32 {
|
||||
self.trak.tkhd.track_id
|
||||
}
|
||||
|
||||
pub fn track_type(&self) -> Result<TrackType> {
|
||||
TrackType::try_from(&self.trak.mdia.hdlr.handler_type)
|
||||
}
|
||||
|
||||
pub fn media_type(&self) -> Result<MediaType> {
|
||||
if self.trak.mdia.minf.stbl.stsd.avc1.is_some() {
|
||||
Ok(MediaType::H264)
|
||||
} else if self.trak.mdia.minf.stbl.stsd.hev1.is_some() {
|
||||
Ok(MediaType::H265)
|
||||
} else if self.trak.mdia.minf.stbl.stsd.vp09.is_some() {
|
||||
Ok(MediaType::VP9)
|
||||
} else if self.trak.mdia.minf.stbl.stsd.mp4a.is_some() {
|
||||
Ok(MediaType::AAC)
|
||||
} else if self.trak.mdia.minf.stbl.stsd.tx3g.is_some() {
|
||||
Ok(MediaType::TTXT)
|
||||
} else {
|
||||
Err(Error::InvalidData("unsupported media type"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn box_type(&self) -> Result<FourCC> {
|
||||
if self.trak.mdia.minf.stbl.stsd.avc1.is_some() {
|
||||
Ok(FourCC::from(BoxType::Avc1Box))
|
||||
} else if self.trak.mdia.minf.stbl.stsd.hev1.is_some() {
|
||||
Ok(FourCC::from(BoxType::Hev1Box))
|
||||
} else if self.trak.mdia.minf.stbl.stsd.vp09.is_some() {
|
||||
Ok(FourCC::from(BoxType::Vp09Box))
|
||||
} else if self.trak.mdia.minf.stbl.stsd.mp4a.is_some() {
|
||||
Ok(FourCC::from(BoxType::Mp4aBox))
|
||||
} else if self.trak.mdia.minf.stbl.stsd.tx3g.is_some() {
|
||||
Ok(FourCC::from(BoxType::Tx3gBox))
|
||||
} else {
|
||||
Err(Error::InvalidData("unsupported sample entry box"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn width(&self) -> u16 {
|
||||
if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 {
|
||||
avc1.width
|
||||
} else {
|
||||
self.trak.tkhd.width.value()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn height(&self) -> u16 {
|
||||
if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 {
|
||||
avc1.height
|
||||
} else {
|
||||
self.trak.tkhd.height.value()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn frame_rate(&self) -> f64 {
|
||||
let dur = self.duration();
|
||||
if dur.is_zero() {
|
||||
0.0
|
||||
} else {
|
||||
self.sample_count() as f64 / dur.as_secs_f64()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_freq_index(&self) -> Result<SampleFreqIndex> {
|
||||
if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a {
|
||||
if let Some(ref esds) = mp4a.esds {
|
||||
SampleFreqIndex::try_from(esds.es_desc.dec_config.dec_specific.freq_index)
|
||||
} else {
|
||||
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::EsdsBox))
|
||||
}
|
||||
} else {
|
||||
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn channel_config(&self) -> Result<ChannelConfig> {
|
||||
if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a {
|
||||
if let Some(ref esds) = mp4a.esds {
|
||||
ChannelConfig::try_from(esds.es_desc.dec_config.dec_specific.chan_conf)
|
||||
} else {
|
||||
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::EsdsBox))
|
||||
}
|
||||
} else {
|
||||
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn language(&self) -> &str {
|
||||
&self.trak.mdia.mdhd.language
|
||||
}
|
||||
|
||||
pub fn timescale(&self) -> u32 {
|
||||
self.trak.mdia.mdhd.timescale
|
||||
}
|
||||
|
||||
pub fn duration(&self) -> Duration {
|
||||
Duration::from_micros(
|
||||
self.trak.mdia.mdhd.duration * 1_000_000 / self.trak.mdia.mdhd.timescale as u64,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn bitrate(&self) -> u32 {
|
||||
if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a {
|
||||
if let Some(ref esds) = mp4a.esds {
|
||||
esds.es_desc.dec_config.avg_bitrate
|
||||
} else {
|
||||
0
|
||||
}
|
||||
// mp4a.esds.es_desc.dec_config.avg_bitrate
|
||||
} else {
|
||||
let dur = self.duration();
|
||||
if dur.is_zero() {
|
||||
0
|
||||
} else {
|
||||
let bitrate = self.total_sample_size() as f64 * 8.0 / dur.as_secs_f64();
|
||||
bitrate as u32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_count(&self) -> u32 {
|
||||
if !self.trafs.is_empty() {
|
||||
let mut sample_count = 0u32;
|
||||
for traf in self.trafs.iter() {
|
||||
if let Some(ref trun) = traf.trun {
|
||||
sample_count = sample_count
|
||||
.checked_add(trun.sample_count)
|
||||
.expect("attempt to sum trun sample_count with overflow");
|
||||
}
|
||||
}
|
||||
sample_count
|
||||
} else {
|
||||
self.trak.mdia.minf.stbl.stsz.sample_count
|
||||
}
|
||||
}
|
||||
|
||||
pub fn video_profile(&self) -> Result<AvcProfile> {
|
||||
if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 {
|
||||
AvcProfile::try_from((
|
||||
avc1.avcc.avc_profile_indication,
|
||||
avc1.avcc.profile_compatibility,
|
||||
))
|
||||
} else {
|
||||
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Avc1Box))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sequence_parameter_set(&self) -> Result<&[u8]> {
|
||||
if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 {
|
||||
match avc1.avcc.sequence_parameter_sets.get(0) {
|
||||
Some(nal) => Ok(nal.bytes.as_ref()),
|
||||
None => Err(Error::EntryInStblNotFound(
|
||||
self.track_id(),
|
||||
BoxType::AvcCBox,
|
||||
0,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Avc1Box))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn picture_parameter_set(&self) -> Result<&[u8]> {
|
||||
if let Some(ref avc1) = self.trak.mdia.minf.stbl.stsd.avc1 {
|
||||
match avc1.avcc.picture_parameter_sets.get(0) {
|
||||
Some(nal) => Ok(nal.bytes.as_ref()),
|
||||
None => Err(Error::EntryInStblNotFound(
|
||||
self.track_id(),
|
||||
BoxType::AvcCBox,
|
||||
0,
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Avc1Box))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn audio_profile(&self) -> Result<AudioObjectType> {
|
||||
if let Some(ref mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a {
|
||||
if let Some(ref esds) = mp4a.esds {
|
||||
AudioObjectType::try_from(esds.es_desc.dec_config.dec_specific.profile)
|
||||
} else {
|
||||
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::EsdsBox))
|
||||
}
|
||||
} else {
|
||||
Err(Error::BoxInStblNotFound(self.track_id(), BoxType::Mp4aBox))
|
||||
}
|
||||
}
|
||||
|
||||
fn stsc_index(&self, sample_id: u32) -> Result<usize> {
|
||||
if self.trak.mdia.minf.stbl.stsc.entries.is_empty() {
|
||||
return Err(Error::InvalidData("no stsc entries"));
|
||||
}
|
||||
for (i, entry) in self.trak.mdia.minf.stbl.stsc.entries.iter().enumerate() {
|
||||
if sample_id < entry.first_sample {
|
||||
return if i == 0 {
|
||||
Err(Error::InvalidData("sample not found"))
|
||||
} else {
|
||||
Ok(i - 1)
|
||||
};
|
||||
}
|
||||
}
|
||||
Ok(self.trak.mdia.minf.stbl.stsc.entries.len() - 1)
|
||||
}
|
||||
|
||||
fn chunk_offset(&self, chunk_id: u32) -> Result<u64> {
|
||||
if self.trak.mdia.minf.stbl.stco.is_none() && self.trak.mdia.minf.stbl.co64.is_none() {
|
||||
return Err(Error::InvalidData("must have either stco or co64 boxes"));
|
||||
}
|
||||
if let Some(ref stco) = self.trak.mdia.minf.stbl.stco {
|
||||
if let Some(offset) = stco.entries.get(chunk_id as usize - 1) {
|
||||
return Ok(*offset as u64);
|
||||
} else {
|
||||
return Err(Error::EntryInStblNotFound(
|
||||
self.track_id(),
|
||||
BoxType::StcoBox,
|
||||
chunk_id,
|
||||
));
|
||||
}
|
||||
} else if let Some(ref co64) = self.trak.mdia.minf.stbl.co64 {
|
||||
if let Some(offset) = co64.entries.get(chunk_id as usize - 1) {
|
||||
return Ok(*offset);
|
||||
} else {
|
||||
return Err(Error::EntryInStblNotFound(
|
||||
self.track_id(),
|
||||
BoxType::Co64Box,
|
||||
chunk_id,
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(Error::Box2NotFound(BoxType::StcoBox, BoxType::Co64Box))
|
||||
}
|
||||
|
||||
fn ctts_index(&self, sample_id: u32) -> Result<(usize, u32)> {
|
||||
let ctts = self.trak.mdia.minf.stbl.ctts.as_ref().unwrap();
|
||||
let mut sample_count: u32 = 1;
|
||||
for (i, entry) in ctts.entries.iter().enumerate() {
|
||||
let next_sample_count =
|
||||
sample_count
|
||||
.checked_add(entry.sample_count)
|
||||
.ok_or(Error::InvalidData(
|
||||
"attempt to sum ctts entries sample_count with overflow",
|
||||
))?;
|
||||
if sample_id < next_sample_count {
|
||||
return Ok((i, sample_count));
|
||||
}
|
||||
sample_count = next_sample_count;
|
||||
}
|
||||
|
||||
Err(Error::EntryInStblNotFound(
|
||||
self.track_id(),
|
||||
BoxType::CttsBox,
|
||||
sample_id,
|
||||
))
|
||||
}
|
||||
|
||||
/// return `(traf_idx, sample_idx_in_trun)`
|
||||
fn find_traf_idx_and_sample_idx(&self, sample_id: u32) -> Option<(usize, usize)> {
|
||||
let global_idx = sample_id - 1;
|
||||
let mut offset = 0;
|
||||
for traf_idx in 0..self.trafs.len() {
|
||||
if let Some(trun) = &self.trafs[traf_idx].trun {
|
||||
let sample_count = trun.sample_count;
|
||||
if sample_count > (global_idx - offset) {
|
||||
return Some((traf_idx, (global_idx - offset) as _));
|
||||
}
|
||||
offset = offset
|
||||
.checked_add(sample_count)
|
||||
.expect("attempt to sum trun sample_count with overflow");
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn sample_size(&self, sample_id: u32) -> Result<u32> {
|
||||
if !self.trafs.is_empty() {
|
||||
if let Some((traf_idx, sample_idx)) = self.find_traf_idx_and_sample_idx(sample_id) {
|
||||
if let Some(size) = self.trafs[traf_idx]
|
||||
.trun
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.sample_sizes
|
||||
.get(sample_idx)
|
||||
{
|
||||
Ok(*size)
|
||||
} else {
|
||||
Err(Error::EntryInTrunNotFound(
|
||||
self.track_id(),
|
||||
BoxType::TrunBox,
|
||||
sample_id,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
Err(Error::BoxInTrafNotFound(self.track_id(), BoxType::TrafBox))
|
||||
}
|
||||
} else {
|
||||
let stsz = &self.trak.mdia.minf.stbl.stsz;
|
||||
if stsz.sample_size > 0 {
|
||||
return Ok(stsz.sample_size);
|
||||
}
|
||||
if let Some(size) = stsz.sample_sizes.get(sample_id as usize - 1) {
|
||||
Ok(*size)
|
||||
} else {
|
||||
Err(Error::EntryInStblNotFound(
|
||||
self.track_id(),
|
||||
BoxType::StszBox,
|
||||
sample_id,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn total_sample_size(&self) -> u64 {
|
||||
let stsz = &self.trak.mdia.minf.stbl.stsz;
|
||||
if stsz.sample_size > 0 {
|
||||
stsz.sample_size as u64 * self.sample_count() as u64
|
||||
} else {
|
||||
let mut total_size = 0;
|
||||
for size in stsz.sample_sizes.iter() {
|
||||
total_size += *size as u64;
|
||||
}
|
||||
total_size
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sample_offset(&self, sample_id: u32) -> Result<u64> {
|
||||
if !self.trafs.is_empty() {
|
||||
if let Some((traf_idx, sample_idx)) = self.find_traf_idx_and_sample_idx(sample_id) {
|
||||
let mut sample_offset = self.trafs[traf_idx]
|
||||
.tfhd
|
||||
.base_data_offset
|
||||
.unwrap_or(self.moof_offsets[traf_idx]);
|
||||
|
||||
if let Some(data_offset) = self.trafs[traf_idx]
|
||||
.trun
|
||||
.as_ref()
|
||||
.and_then(|trun| trun.data_offset)
|
||||
{
|
||||
sample_offset = sample_offset.checked_add_signed(data_offset as i64).ok_or(
|
||||
Error::InvalidData("attempt to calculate trun sample offset with overflow"),
|
||||
)?;
|
||||
}
|
||||
|
||||
let first_sample_in_trun = sample_id - sample_idx as u32;
|
||||
for i in first_sample_in_trun..sample_id {
|
||||
sample_offset = sample_offset
|
||||
.checked_add(self.sample_size(i)? as u64)
|
||||
.ok_or(Error::InvalidData(
|
||||
"attempt to calculate trun entry sample offset with overflow",
|
||||
))?;
|
||||
}
|
||||
|
||||
Ok(sample_offset)
|
||||
} else {
|
||||
Err(Error::BoxInTrafNotFound(self.track_id(), BoxType::TrafBox))
|
||||
}
|
||||
} else {
|
||||
let stsc_index = self.stsc_index(sample_id)?;
|
||||
|
||||
let stsc = &self.trak.mdia.minf.stbl.stsc;
|
||||
let stsc_entry = stsc.entries.get(stsc_index).unwrap();
|
||||
|
||||
let first_chunk = stsc_entry.first_chunk;
|
||||
let first_sample = stsc_entry.first_sample;
|
||||
let samples_per_chunk = stsc_entry.samples_per_chunk;
|
||||
|
||||
let chunk_id = sample_id
|
||||
.checked_sub(first_sample)
|
||||
.map(|n| n / samples_per_chunk)
|
||||
.and_then(|n| n.checked_add(first_chunk))
|
||||
.ok_or(Error::InvalidData(
|
||||
"attempt to calculate stsc chunk_id with overflow",
|
||||
))?;
|
||||
|
||||
let chunk_offset = self.chunk_offset(chunk_id)?;
|
||||
|
||||
let first_sample_in_chunk = sample_id - (sample_id - first_sample) % samples_per_chunk;
|
||||
|
||||
let mut sample_offset = 0;
|
||||
for i in first_sample_in_chunk..sample_id {
|
||||
sample_offset += self.sample_size(i)?;
|
||||
}
|
||||
|
||||
Ok(chunk_offset + sample_offset as u64)
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_time(&self, sample_id: u32) -> Result<(u64, u32)> {
|
||||
if !self.trafs.is_empty() {
|
||||
let mut base_start_time = 0;
|
||||
let mut default_sample_duration = self.default_sample_duration;
|
||||
if let Some((traf_idx, sample_idx)) = self.find_traf_idx_and_sample_idx(sample_id) {
|
||||
let traf = &self.trafs[traf_idx];
|
||||
if let Some(tfdt) = &traf.tfdt {
|
||||
base_start_time = tfdt.base_media_decode_time;
|
||||
}
|
||||
if let Some(duration) = traf.tfhd.default_sample_duration {
|
||||
default_sample_duration = duration;
|
||||
}
|
||||
if let Some(trun) = &traf.trun {
|
||||
if TrunBox::FLAG_SAMPLE_DURATION & trun.flags != 0 {
|
||||
let mut start_offset = 0u64;
|
||||
for duration in &trun.sample_durations[..sample_idx] {
|
||||
start_offset = start_offset.checked_add(*duration as u64).ok_or(
|
||||
Error::InvalidData("attempt to sum sample durations with overflow"),
|
||||
)?;
|
||||
}
|
||||
let duration = trun.sample_durations[sample_idx];
|
||||
return Ok((base_start_time + start_offset, duration));
|
||||
}
|
||||
}
|
||||
}
|
||||
let start_offset = ((sample_id - 1) * default_sample_duration) as u64;
|
||||
Ok((base_start_time + start_offset, default_sample_duration))
|
||||
} else {
|
||||
let stts = &self.trak.mdia.minf.stbl.stts;
|
||||
|
||||
let mut sample_count: u32 = 1;
|
||||
let mut elapsed = 0;
|
||||
|
||||
for entry in stts.entries.iter() {
|
||||
let new_sample_count =
|
||||
sample_count
|
||||
.checked_add(entry.sample_count)
|
||||
.ok_or(Error::InvalidData(
|
||||
"attempt to sum stts entries sample_count with overflow",
|
||||
))?;
|
||||
if sample_id < new_sample_count {
|
||||
let start_time =
|
||||
(sample_id - sample_count) as u64 * entry.sample_delta as u64 + elapsed;
|
||||
return Ok((start_time, entry.sample_delta));
|
||||
}
|
||||
|
||||
sample_count = new_sample_count;
|
||||
elapsed += entry.sample_count as u64 * entry.sample_delta as u64;
|
||||
}
|
||||
|
||||
Err(Error::EntryInStblNotFound(
|
||||
self.track_id(),
|
||||
BoxType::SttsBox,
|
||||
sample_id,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_rendering_offset(&self, sample_id: u32) -> i32 {
|
||||
if !self.trafs.is_empty() {
|
||||
if let Some((traf_idx, sample_idx)) = self.find_traf_idx_and_sample_idx(sample_id) {
|
||||
if let Some(cts) = self.trafs[traf_idx]
|
||||
.trun
|
||||
.as_ref()
|
||||
.and_then(|trun| trun.sample_cts.get(sample_idx))
|
||||
{
|
||||
return *cts as i32;
|
||||
}
|
||||
}
|
||||
} else if let Some(ref ctts) = self.trak.mdia.minf.stbl.ctts {
|
||||
if let Ok((ctts_index, _)) = self.ctts_index(sample_id) {
|
||||
let ctts_entry = ctts.entries.get(ctts_index).unwrap();
|
||||
return ctts_entry.sample_offset;
|
||||
}
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
fn is_sync_sample(&self, sample_id: u32) -> bool {
|
||||
if !self.trafs.is_empty() {
|
||||
let sample_sizes_count = self.sample_count() / self.trafs.len() as u32;
|
||||
return sample_id == 1 || sample_id % sample_sizes_count == 0;
|
||||
}
|
||||
|
||||
if let Some(ref stss) = self.trak.mdia.minf.stbl.stss {
|
||||
stss.entries.binary_search(&sample_id).is_ok()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn read_sample<R: Read + Seek>(
|
||||
&self,
|
||||
reader: &mut R,
|
||||
sample_id: u32,
|
||||
) -> Result<Option<Mp4Sample>> {
|
||||
let sample_offset = match self.sample_offset(sample_id) {
|
||||
Ok(offset) => offset,
|
||||
Err(Error::EntryInStblNotFound(_, _, _)) => return Ok(None),
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let sample_size = match self.sample_size(sample_id) {
|
||||
Ok(size) => size,
|
||||
Err(Error::EntryInStblNotFound(_, _, _)) => return Ok(None),
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
let mut buffer = vec![0x0u8; sample_size as usize];
|
||||
reader.seek(SeekFrom::Start(sample_offset))?;
|
||||
reader.read_exact(&mut buffer)?;
|
||||
|
||||
let (start_time, duration) = self.sample_time(sample_id).unwrap(); // XXX
|
||||
let rendering_offset = self.sample_rendering_offset(sample_id);
|
||||
let is_sync = self.is_sync_sample(sample_id);
|
||||
|
||||
Ok(Some(Mp4Sample {
|
||||
start_time,
|
||||
duration,
|
||||
rendering_offset,
|
||||
is_sync,
|
||||
bytes: Bytes::from(buffer),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// TODO creation_time, modification_time
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct Mp4TrackWriter {
|
||||
trak: TrakBox,
|
||||
|
||||
sample_id: u32,
|
||||
fixed_sample_size: u32,
|
||||
is_fixed_sample_size: bool,
|
||||
chunk_samples: u32,
|
||||
chunk_duration: u32,
|
||||
chunk_buffer: BytesMut,
|
||||
|
||||
samples_per_chunk: u32,
|
||||
duration_per_chunk: u32,
|
||||
}
|
||||
|
||||
impl Mp4TrackWriter {
|
||||
pub(crate) fn new(track_id: u32, config: &TrackConfig) -> Result<Self> {
|
||||
let mut trak = TrakBox::default();
|
||||
trak.tkhd.track_id = track_id;
|
||||
trak.mdia.mdhd.timescale = config.timescale;
|
||||
trak.mdia.mdhd.language = config.language.to_owned();
|
||||
trak.mdia.hdlr.handler_type = config.track_type.into();
|
||||
trak.mdia.minf.stbl.co64 = Some(Co64Box::default());
|
||||
match config.media_conf {
|
||||
MediaConfig::AvcConfig(ref avc_config) => {
|
||||
trak.tkhd.set_width(avc_config.width);
|
||||
trak.tkhd.set_height(avc_config.height);
|
||||
|
||||
let vmhd = VmhdBox::default();
|
||||
trak.mdia.minf.vmhd = Some(vmhd);
|
||||
|
||||
let avc1 = Avc1Box::new(avc_config);
|
||||
trak.mdia.minf.stbl.stsd.avc1 = Some(avc1);
|
||||
}
|
||||
MediaConfig::HevcConfig(ref hevc_config) => {
|
||||
trak.tkhd.set_width(hevc_config.width);
|
||||
trak.tkhd.set_height(hevc_config.height);
|
||||
|
||||
let vmhd = VmhdBox::default();
|
||||
trak.mdia.minf.vmhd = Some(vmhd);
|
||||
|
||||
let hev1 = Hev1Box::new(hevc_config);
|
||||
trak.mdia.minf.stbl.stsd.hev1 = Some(hev1);
|
||||
}
|
||||
MediaConfig::Vp9Config(ref config) => {
|
||||
trak.tkhd.set_width(config.width);
|
||||
trak.tkhd.set_height(config.height);
|
||||
|
||||
trak.mdia.minf.stbl.stsd.vp09 = Some(Vp09Box::new(config));
|
||||
}
|
||||
MediaConfig::AacConfig(ref aac_config) => {
|
||||
let smhd = SmhdBox::default();
|
||||
trak.mdia.minf.smhd = Some(smhd);
|
||||
|
||||
let mp4a = Mp4aBox::new(aac_config);
|
||||
trak.mdia.minf.stbl.stsd.mp4a = Some(mp4a);
|
||||
}
|
||||
MediaConfig::TtxtConfig(ref _ttxt_config) => {
|
||||
let tx3g = Tx3gBox::default();
|
||||
trak.mdia.minf.stbl.stsd.tx3g = Some(tx3g);
|
||||
}
|
||||
}
|
||||
Ok(Mp4TrackWriter {
|
||||
trak,
|
||||
chunk_buffer: BytesMut::new(),
|
||||
sample_id: 1,
|
||||
duration_per_chunk: config.timescale, // 1 second
|
||||
..Self::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn update_sample_sizes(&mut self, size: u32) {
|
||||
if self.trak.mdia.minf.stbl.stsz.sample_count == 0 {
|
||||
if size == 0 {
|
||||
self.trak.mdia.minf.stbl.stsz.sample_size = 0;
|
||||
self.is_fixed_sample_size = false;
|
||||
self.trak.mdia.minf.stbl.stsz.sample_sizes.push(0);
|
||||
} else {
|
||||
self.trak.mdia.minf.stbl.stsz.sample_size = size;
|
||||
self.fixed_sample_size = size;
|
||||
self.is_fixed_sample_size = true;
|
||||
}
|
||||
} else if self.is_fixed_sample_size {
|
||||
if self.fixed_sample_size != size {
|
||||
self.is_fixed_sample_size = false;
|
||||
if self.trak.mdia.minf.stbl.stsz.sample_size > 0 {
|
||||
self.trak.mdia.minf.stbl.stsz.sample_size = 0;
|
||||
for _ in 0..self.trak.mdia.minf.stbl.stsz.sample_count {
|
||||
self.trak
|
||||
.mdia
|
||||
.minf
|
||||
.stbl
|
||||
.stsz
|
||||
.sample_sizes
|
||||
.push(self.fixed_sample_size);
|
||||
}
|
||||
}
|
||||
self.trak.mdia.minf.stbl.stsz.sample_sizes.push(size);
|
||||
}
|
||||
} else {
|
||||
self.trak.mdia.minf.stbl.stsz.sample_sizes.push(size);
|
||||
}
|
||||
self.trak.mdia.minf.stbl.stsz.sample_count += 1;
|
||||
}
|
||||
|
||||
fn update_sample_times(&mut self, dur: u32) {
|
||||
if let Some(ref mut entry) = self.trak.mdia.minf.stbl.stts.entries.last_mut() {
|
||||
if entry.sample_delta == dur {
|
||||
entry.sample_count += 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let entry = SttsEntry {
|
||||
sample_count: 1,
|
||||
sample_delta: dur,
|
||||
};
|
||||
self.trak.mdia.minf.stbl.stts.entries.push(entry);
|
||||
}
|
||||
|
||||
fn update_rendering_offsets(&mut self, offset: i32) {
|
||||
let ctts = if let Some(ref mut ctts) = self.trak.mdia.minf.stbl.ctts {
|
||||
ctts
|
||||
} else {
|
||||
if offset == 0 {
|
||||
return;
|
||||
}
|
||||
let mut ctts = CttsBox::default();
|
||||
if self.sample_id > 1 {
|
||||
let entry = CttsEntry {
|
||||
sample_count: self.sample_id - 1,
|
||||
sample_offset: 0,
|
||||
};
|
||||
ctts.entries.push(entry);
|
||||
}
|
||||
self.trak.mdia.minf.stbl.ctts = Some(ctts);
|
||||
self.trak.mdia.minf.stbl.ctts.as_mut().unwrap()
|
||||
};
|
||||
|
||||
if let Some(ref mut entry) = ctts.entries.last_mut() {
|
||||
if entry.sample_offset == offset {
|
||||
entry.sample_count += 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let entry = CttsEntry {
|
||||
sample_count: 1,
|
||||
sample_offset: offset,
|
||||
};
|
||||
ctts.entries.push(entry);
|
||||
}
|
||||
|
||||
fn update_sync_samples(&mut self, is_sync: bool) {
|
||||
if let Some(ref mut stss) = self.trak.mdia.minf.stbl.stss {
|
||||
if !is_sync {
|
||||
return;
|
||||
}
|
||||
|
||||
stss.entries.push(self.sample_id);
|
||||
} else {
|
||||
if !is_sync {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the stts box if not found and push the entry.
|
||||
let mut stss = StssBox::default();
|
||||
stss.entries.push(self.sample_id);
|
||||
self.trak.mdia.minf.stbl.stss = Some(stss);
|
||||
};
|
||||
}
|
||||
|
||||
fn is_chunk_full(&self) -> bool {
|
||||
if self.samples_per_chunk > 0 {
|
||||
self.chunk_samples >= self.samples_per_chunk
|
||||
} else {
|
||||
self.chunk_duration >= self.duration_per_chunk
|
||||
}
|
||||
}
|
||||
|
||||
fn update_durations(&mut self, dur: u32, movie_timescale: u32) {
|
||||
self.trak.mdia.mdhd.duration += dur as u64;
|
||||
if self.trak.mdia.mdhd.duration > (u32::MAX as u64) {
|
||||
self.trak.mdia.mdhd.version = 1
|
||||
}
|
||||
self.trak.tkhd.duration +=
|
||||
dur as u64 * movie_timescale as u64 / self.trak.mdia.mdhd.timescale as u64;
|
||||
if self.trak.tkhd.duration > (u32::MAX as u64) {
|
||||
self.trak.tkhd.version = 1
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn write_sample<W: Write + Seek>(
|
||||
&mut self,
|
||||
writer: &mut W,
|
||||
sample: &Mp4Sample,
|
||||
movie_timescale: u32,
|
||||
) -> Result<u64> {
|
||||
self.chunk_buffer.extend_from_slice(&sample.bytes);
|
||||
self.chunk_samples += 1;
|
||||
self.chunk_duration += sample.duration;
|
||||
self.update_sample_sizes(sample.bytes.len() as u32);
|
||||
self.update_sample_times(sample.duration);
|
||||
self.update_rendering_offsets(sample.rendering_offset);
|
||||
self.update_sync_samples(sample.is_sync);
|
||||
if self.is_chunk_full() {
|
||||
self.write_chunk(writer)?;
|
||||
}
|
||||
self.update_durations(sample.duration, movie_timescale);
|
||||
|
||||
self.sample_id += 1;
|
||||
|
||||
Ok(self.trak.tkhd.duration)
|
||||
}
|
||||
|
||||
fn chunk_count(&self) -> u32 {
|
||||
let co64 = self.trak.mdia.minf.stbl.co64.as_ref().unwrap();
|
||||
co64.entries.len() as u32
|
||||
}
|
||||
|
||||
fn update_sample_to_chunk(&mut self, chunk_id: u32) {
|
||||
if let Some(entry) = self.trak.mdia.minf.stbl.stsc.entries.last() {
|
||||
if entry.samples_per_chunk == self.chunk_samples {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let entry = StscEntry {
|
||||
first_chunk: chunk_id,
|
||||
samples_per_chunk: self.chunk_samples,
|
||||
sample_description_index: 1,
|
||||
first_sample: self.sample_id - self.chunk_samples + 1,
|
||||
};
|
||||
self.trak.mdia.minf.stbl.stsc.entries.push(entry);
|
||||
}
|
||||
|
||||
fn update_chunk_offsets(&mut self, offset: u64) {
|
||||
let co64 = self.trak.mdia.minf.stbl.co64.as_mut().unwrap();
|
||||
co64.entries.push(offset);
|
||||
}
|
||||
|
||||
fn write_chunk<W: Write + Seek>(&mut self, writer: &mut W) -> Result<()> {
|
||||
if self.chunk_buffer.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let chunk_offset = writer.stream_position()?;
|
||||
|
||||
writer.write_all(&self.chunk_buffer)?;
|
||||
|
||||
self.update_sample_to_chunk(self.chunk_count() + 1);
|
||||
self.update_chunk_offsets(chunk_offset);
|
||||
|
||||
self.chunk_buffer.clear();
|
||||
self.chunk_samples = 0;
|
||||
self.chunk_duration = 0;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn max_sample_size(&self) -> u32 {
|
||||
if self.trak.mdia.minf.stbl.stsz.sample_size > 0 {
|
||||
self.trak.mdia.minf.stbl.stsz.sample_size
|
||||
} else {
|
||||
let mut max_size = 0;
|
||||
for sample_size in self.trak.mdia.minf.stbl.stsz.sample_sizes.iter() {
|
||||
max_size = cmp::max(max_size, *sample_size);
|
||||
}
|
||||
max_size
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn write_end<W: Write + Seek>(&mut self, writer: &mut W) -> Result<TrakBox> {
|
||||
self.write_chunk(writer)?;
|
||||
|
||||
let max_sample_size = self.max_sample_size();
|
||||
if let Some(ref mut mp4a) = self.trak.mdia.minf.stbl.stsd.mp4a {
|
||||
if let Some(ref mut esds) = mp4a.esds {
|
||||
esds.es_desc.dec_config.buffer_size_db = max_sample_size;
|
||||
}
|
||||
// TODO
|
||||
// mp4a.esds.es_desc.dec_config.max_bitrate
|
||||
// mp4a.esds.es_desc.dec_config.avg_bitrate
|
||||
}
|
||||
if let Ok(stco) = StcoBox::try_from(self.trak.mdia.minf.stbl.co64.as_ref().unwrap()) {
|
||||
self.trak.mdia.minf.stbl.stco = Some(stco);
|
||||
self.trak.mdia.minf.stbl.co64 = None;
|
||||
}
|
||||
|
||||
Ok(self.trak.clone())
|
||||
}
|
||||
}
|
741
src/types.rs
Normal file
741
src/types.rs
Normal file
|
@ -0,0 +1,741 @@
|
|||
use serde::Serialize;
|
||||
use std::borrow::Cow;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
|
||||
use crate::mp4box::*;
|
||||
use crate::*;
|
||||
|
||||
pub use bytes::Bytes;
|
||||
pub use num_rational::Ratio;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
pub struct FixedPointU8(Ratio<u16>);
|
||||
|
||||
impl FixedPointU8 {
|
||||
pub fn new(val: u8) -> Self {
|
||||
Self(Ratio::new_raw(val as u16 * 0x100, 0x100))
|
||||
}
|
||||
|
||||
pub fn new_raw(val: u16) -> Self {
|
||||
Self(Ratio::new_raw(val, 0x100))
|
||||
}
|
||||
|
||||
pub fn value(&self) -> u8 {
|
||||
self.0.to_integer() as u8
|
||||
}
|
||||
|
||||
pub fn raw_value(&self) -> u16 {
|
||||
*self.0.numer()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
pub struct FixedPointI8(Ratio<i16>);
|
||||
|
||||
impl FixedPointI8 {
|
||||
pub fn new(val: i8) -> Self {
|
||||
Self(Ratio::new_raw(val as i16 * 0x100, 0x100))
|
||||
}
|
||||
|
||||
pub fn new_raw(val: i16) -> Self {
|
||||
Self(Ratio::new_raw(val, 0x100))
|
||||
}
|
||||
|
||||
pub fn value(&self) -> i8 {
|
||||
self.0.to_integer() as i8
|
||||
}
|
||||
|
||||
pub fn raw_value(&self) -> i16 {
|
||||
*self.0.numer()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
pub struct FixedPointU16(Ratio<u32>);
|
||||
|
||||
impl FixedPointU16 {
|
||||
pub fn new(val: u16) -> Self {
|
||||
Self(Ratio::new_raw(val as u32 * 0x10000, 0x10000))
|
||||
}
|
||||
|
||||
pub fn new_raw(val: u32) -> Self {
|
||||
Self(Ratio::new_raw(val, 0x10000))
|
||||
}
|
||||
|
||||
pub fn value(&self) -> u16 {
|
||||
self.0.to_integer() as u16
|
||||
}
|
||||
|
||||
pub fn raw_value(&self) -> u32 {
|
||||
*self.0.numer()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for BoxType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let fourcc: FourCC = From::from(*self);
|
||||
write!(f, "{fourcc}")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BoxType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let fourcc: FourCC = From::from(*self);
|
||||
write!(f, "{fourcc}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Serialize)]
|
||||
pub struct FourCC {
|
||||
pub value: [u8; 4],
|
||||
}
|
||||
|
||||
impl std::str::FromStr for FourCC {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
if let [a, b, c, d] = s.as_bytes() {
|
||||
Ok(Self {
|
||||
value: [*a, *b, *c, *d],
|
||||
})
|
||||
} else {
|
||||
Err(Error::InvalidData("expected exactly four bytes in string"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for FourCC {
|
||||
fn from(number: u32) -> Self {
|
||||
FourCC {
|
||||
value: number.to_be_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FourCC> for u32 {
|
||||
fn from(fourcc: FourCC) -> u32 {
|
||||
(&fourcc).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&FourCC> for u32 {
|
||||
fn from(fourcc: &FourCC) -> u32 {
|
||||
u32::from_be_bytes(fourcc.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 4]> for FourCC {
|
||||
fn from(value: [u8; 4]) -> FourCC {
|
||||
FourCC { value }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BoxType> for FourCC {
|
||||
fn from(t: BoxType) -> FourCC {
|
||||
let box_num: u32 = Into::into(t);
|
||||
From::from(box_num)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for FourCC {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let code: u32 = self.into();
|
||||
let string = String::from_utf8_lossy(&self.value[..]);
|
||||
write!(f, "{string} / {code:#010X}")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FourCC {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", String::from_utf8_lossy(&self.value[..]))
|
||||
}
|
||||
}
|
||||
|
||||
const DISPLAY_TYPE_VIDEO: &str = "Video";
|
||||
const DISPLAY_TYPE_AUDIO: &str = "Audio";
|
||||
const DISPLAY_TYPE_SUBTITLE: &str = "Subtitle";
|
||||
|
||||
const HANDLER_TYPE_VIDEO: &str = "vide";
|
||||
const HANDLER_TYPE_VIDEO_FOURCC: [u8; 4] = [b'v', b'i', b'd', b'e'];
|
||||
|
||||
const HANDLER_TYPE_AUDIO: &str = "soun";
|
||||
const HANDLER_TYPE_AUDIO_FOURCC: [u8; 4] = [b's', b'o', b'u', b'n'];
|
||||
|
||||
const HANDLER_TYPE_SUBTITLE: &str = "sbtl";
|
||||
const HANDLER_TYPE_SUBTITLE_FOURCC: [u8; 4] = [b's', b'b', b't', b'l'];
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TrackType {
|
||||
Video,
|
||||
Audio,
|
||||
Subtitle,
|
||||
}
|
||||
|
||||
impl fmt::Display for TrackType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s = match self {
|
||||
TrackType::Video => DISPLAY_TYPE_VIDEO,
|
||||
TrackType::Audio => DISPLAY_TYPE_AUDIO,
|
||||
TrackType::Subtitle => DISPLAY_TYPE_SUBTITLE,
|
||||
};
|
||||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for TrackType {
|
||||
type Error = Error;
|
||||
fn try_from(handler: &str) -> Result<TrackType> {
|
||||
match handler {
|
||||
HANDLER_TYPE_VIDEO => Ok(TrackType::Video),
|
||||
HANDLER_TYPE_AUDIO => Ok(TrackType::Audio),
|
||||
HANDLER_TYPE_SUBTITLE => Ok(TrackType::Subtitle),
|
||||
_ => Err(Error::InvalidData("unsupported handler type")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&FourCC> for TrackType {
|
||||
type Error = Error;
|
||||
fn try_from(fourcc: &FourCC) -> Result<TrackType> {
|
||||
match fourcc.value {
|
||||
HANDLER_TYPE_VIDEO_FOURCC => Ok(TrackType::Video),
|
||||
HANDLER_TYPE_AUDIO_FOURCC => Ok(TrackType::Audio),
|
||||
HANDLER_TYPE_SUBTITLE_FOURCC => Ok(TrackType::Subtitle),
|
||||
_ => Err(Error::InvalidData("unsupported handler type")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TrackType> for FourCC {
|
||||
fn from(t: TrackType) -> FourCC {
|
||||
match t {
|
||||
TrackType::Video => HANDLER_TYPE_VIDEO_FOURCC.into(),
|
||||
TrackType::Audio => HANDLER_TYPE_AUDIO_FOURCC.into(),
|
||||
TrackType::Subtitle => HANDLER_TYPE_SUBTITLE_FOURCC.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MEDIA_TYPE_H264: &str = "h264";
|
||||
const MEDIA_TYPE_H265: &str = "h265";
|
||||
const MEDIA_TYPE_VP9: &str = "vp9";
|
||||
const MEDIA_TYPE_AAC: &str = "aac";
|
||||
const MEDIA_TYPE_TTXT: &str = "ttxt";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MediaType {
|
||||
H264,
|
||||
H265,
|
||||
VP9,
|
||||
AAC,
|
||||
TTXT,
|
||||
}
|
||||
|
||||
impl fmt::Display for MediaType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s: &str = self.into();
|
||||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for MediaType {
|
||||
type Error = Error;
|
||||
fn try_from(media: &str) -> Result<MediaType> {
|
||||
match media {
|
||||
MEDIA_TYPE_H264 => Ok(MediaType::H264),
|
||||
MEDIA_TYPE_H265 => Ok(MediaType::H265),
|
||||
MEDIA_TYPE_VP9 => Ok(MediaType::VP9),
|
||||
MEDIA_TYPE_AAC => Ok(MediaType::AAC),
|
||||
MEDIA_TYPE_TTXT => Ok(MediaType::TTXT),
|
||||
_ => Err(Error::InvalidData("unsupported media type")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MediaType> for &str {
|
||||
fn from(t: MediaType) -> &'static str {
|
||||
match t {
|
||||
MediaType::H264 => MEDIA_TYPE_H264,
|
||||
MediaType::H265 => MEDIA_TYPE_H265,
|
||||
MediaType::VP9 => MEDIA_TYPE_VP9,
|
||||
MediaType::AAC => MEDIA_TYPE_AAC,
|
||||
MediaType::TTXT => MEDIA_TYPE_TTXT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&MediaType> for &str {
|
||||
fn from(t: &MediaType) -> &'static str {
|
||||
match t {
|
||||
MediaType::H264 => MEDIA_TYPE_H264,
|
||||
MediaType::H265 => MEDIA_TYPE_H265,
|
||||
MediaType::VP9 => MEDIA_TYPE_VP9,
|
||||
MediaType::AAC => MEDIA_TYPE_AAC,
|
||||
MediaType::TTXT => MEDIA_TYPE_TTXT,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum AvcProfile {
|
||||
AvcConstrainedBaseline, // 66 with constraint set 1
|
||||
AvcBaseline, // 66,
|
||||
AvcMain, // 77,
|
||||
AvcExtended, // 88,
|
||||
AvcHigh, // 100
|
||||
// TODO Progressive High Profile, Constrained High Profile, ...
|
||||
}
|
||||
|
||||
impl TryFrom<(u8, u8)> for AvcProfile {
|
||||
type Error = Error;
|
||||
fn try_from(value: (u8, u8)) -> Result<AvcProfile> {
|
||||
let profile = value.0;
|
||||
let constraint_set1_flag = (value.1 & 0x40) >> 7;
|
||||
match (profile, constraint_set1_flag) {
|
||||
(66, 1) => Ok(AvcProfile::AvcConstrainedBaseline),
|
||||
(66, 0) => Ok(AvcProfile::AvcBaseline),
|
||||
(77, _) => Ok(AvcProfile::AvcMain),
|
||||
(88, _) => Ok(AvcProfile::AvcExtended),
|
||||
(100, _) => Ok(AvcProfile::AvcHigh),
|
||||
_ => Err(Error::InvalidData("unsupported avc profile")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AvcProfile {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let profile = match self {
|
||||
AvcProfile::AvcConstrainedBaseline => "Constrained Baseline",
|
||||
AvcProfile::AvcBaseline => "Baseline",
|
||||
AvcProfile::AvcMain => "Main",
|
||||
AvcProfile::AvcExtended => "Extended",
|
||||
AvcProfile::AvcHigh => "High",
|
||||
};
|
||||
write!(f, "{profile}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum AudioObjectType {
|
||||
AacMain = 1, // AAC Main Profile
|
||||
AacLowComplexity = 2, // AAC Low Complexity
|
||||
AacScalableSampleRate = 3, // AAC Scalable Sample Rate
|
||||
AacLongTermPrediction = 4, // AAC Long Term Predictor
|
||||
SpectralBandReplication = 5, // Spectral band Replication
|
||||
AACScalable = 6, // AAC Scalable
|
||||
TwinVQ = 7, // Twin VQ
|
||||
CodeExcitedLinearPrediction = 8, // CELP
|
||||
HarmonicVectorExcitationCoding = 9, // HVXC
|
||||
TextToSpeechtInterface = 12, // TTSI
|
||||
MainSynthetic = 13, // Main Synthetic
|
||||
WavetableSynthesis = 14, // Wavetable Synthesis
|
||||
GeneralMIDI = 15, // General MIDI
|
||||
AlgorithmicSynthesis = 16, // Algorithmic Synthesis
|
||||
ErrorResilientAacLowComplexity = 17, // ER AAC LC
|
||||
ErrorResilientAacLongTermPrediction = 19, // ER AAC LTP
|
||||
ErrorResilientAacScalable = 20, // ER AAC Scalable
|
||||
ErrorResilientAacTwinVQ = 21, // ER AAC TwinVQ
|
||||
ErrorResilientAacBitSlicedArithmeticCoding = 22, // ER Bit Sliced Arithmetic Coding
|
||||
ErrorResilientAacLowDelay = 23, // ER AAC Low Delay
|
||||
ErrorResilientCodeExcitedLinearPrediction = 24, // ER CELP
|
||||
ErrorResilientHarmonicVectorExcitationCoding = 25, // ER HVXC
|
||||
ErrorResilientHarmonicIndividualLinesNoise = 26, // ER HILN
|
||||
ErrorResilientParametric = 27, // ER Parametric
|
||||
SinuSoidalCoding = 28, // SSC
|
||||
ParametricStereo = 29, // PS
|
||||
MpegSurround = 30, // MPEG Surround
|
||||
MpegLayer1 = 32, // MPEG Layer 1
|
||||
MpegLayer2 = 33, // MPEG Layer 2
|
||||
MpegLayer3 = 34, // MPEG Layer 3
|
||||
DirectStreamTransfer = 35, // DST Direct Stream Transfer
|
||||
AudioLosslessCoding = 36, // ALS Audio Lossless Coding
|
||||
ScalableLosslessCoding = 37, // SLC Scalable Lossless Coding
|
||||
ScalableLosslessCodingNoneCore = 38, // SLC non-core
|
||||
ErrorResilientAacEnhancedLowDelay = 39, // ER AAC ELD
|
||||
SymbolicMusicRepresentationSimple = 40, // SMR Simple
|
||||
SymbolicMusicRepresentationMain = 41, // SMR Main
|
||||
UnifiedSpeechAudioCoding = 42, // USAC
|
||||
SpatialAudioObjectCoding = 43, // SAOC
|
||||
LowDelayMpegSurround = 44, // LD MPEG Surround
|
||||
SpatialAudioObjectCodingDialogueEnhancement = 45, // SAOC-DE
|
||||
AudioSync = 46, // Audio Sync
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for AudioObjectType {
|
||||
type Error = Error;
|
||||
fn try_from(value: u8) -> Result<AudioObjectType> {
|
||||
match value {
|
||||
1 => Ok(AudioObjectType::AacMain),
|
||||
2 => Ok(AudioObjectType::AacLowComplexity),
|
||||
3 => Ok(AudioObjectType::AacScalableSampleRate),
|
||||
4 => Ok(AudioObjectType::AacLongTermPrediction),
|
||||
5 => Ok(AudioObjectType::SpectralBandReplication),
|
||||
6 => Ok(AudioObjectType::AACScalable),
|
||||
7 => Ok(AudioObjectType::TwinVQ),
|
||||
8 => Ok(AudioObjectType::CodeExcitedLinearPrediction),
|
||||
9 => Ok(AudioObjectType::HarmonicVectorExcitationCoding),
|
||||
12 => Ok(AudioObjectType::TextToSpeechtInterface),
|
||||
13 => Ok(AudioObjectType::MainSynthetic),
|
||||
14 => Ok(AudioObjectType::WavetableSynthesis),
|
||||
15 => Ok(AudioObjectType::GeneralMIDI),
|
||||
16 => Ok(AudioObjectType::AlgorithmicSynthesis),
|
||||
17 => Ok(AudioObjectType::ErrorResilientAacLowComplexity),
|
||||
19 => Ok(AudioObjectType::ErrorResilientAacLongTermPrediction),
|
||||
20 => Ok(AudioObjectType::ErrorResilientAacScalable),
|
||||
21 => Ok(AudioObjectType::ErrorResilientAacTwinVQ),
|
||||
22 => Ok(AudioObjectType::ErrorResilientAacBitSlicedArithmeticCoding),
|
||||
23 => Ok(AudioObjectType::ErrorResilientAacLowDelay),
|
||||
24 => Ok(AudioObjectType::ErrorResilientCodeExcitedLinearPrediction),
|
||||
25 => Ok(AudioObjectType::ErrorResilientHarmonicVectorExcitationCoding),
|
||||
26 => Ok(AudioObjectType::ErrorResilientHarmonicIndividualLinesNoise),
|
||||
27 => Ok(AudioObjectType::ErrorResilientParametric),
|
||||
28 => Ok(AudioObjectType::SinuSoidalCoding),
|
||||
29 => Ok(AudioObjectType::ParametricStereo),
|
||||
30 => Ok(AudioObjectType::MpegSurround),
|
||||
32 => Ok(AudioObjectType::MpegLayer1),
|
||||
33 => Ok(AudioObjectType::MpegLayer2),
|
||||
34 => Ok(AudioObjectType::MpegLayer3),
|
||||
35 => Ok(AudioObjectType::DirectStreamTransfer),
|
||||
36 => Ok(AudioObjectType::AudioLosslessCoding),
|
||||
37 => Ok(AudioObjectType::ScalableLosslessCoding),
|
||||
38 => Ok(AudioObjectType::ScalableLosslessCodingNoneCore),
|
||||
39 => Ok(AudioObjectType::ErrorResilientAacEnhancedLowDelay),
|
||||
40 => Ok(AudioObjectType::SymbolicMusicRepresentationSimple),
|
||||
41 => Ok(AudioObjectType::SymbolicMusicRepresentationMain),
|
||||
42 => Ok(AudioObjectType::UnifiedSpeechAudioCoding),
|
||||
43 => Ok(AudioObjectType::SpatialAudioObjectCoding),
|
||||
44 => Ok(AudioObjectType::LowDelayMpegSurround),
|
||||
45 => Ok(AudioObjectType::SpatialAudioObjectCodingDialogueEnhancement),
|
||||
46 => Ok(AudioObjectType::AudioSync),
|
||||
_ => Err(Error::InvalidData("invalid audio object type")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AudioObjectType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let type_str = match self {
|
||||
AudioObjectType::AacMain => "AAC Main",
|
||||
AudioObjectType::AacLowComplexity => "LC",
|
||||
AudioObjectType::AacScalableSampleRate => "SSR",
|
||||
AudioObjectType::AacLongTermPrediction => "LTP",
|
||||
AudioObjectType::SpectralBandReplication => "SBR",
|
||||
AudioObjectType::AACScalable => "Scalable",
|
||||
AudioObjectType::TwinVQ => "TwinVQ",
|
||||
AudioObjectType::CodeExcitedLinearPrediction => "CELP",
|
||||
AudioObjectType::HarmonicVectorExcitationCoding => "HVXC",
|
||||
AudioObjectType::TextToSpeechtInterface => "TTSI",
|
||||
AudioObjectType::MainSynthetic => "Main Synthetic",
|
||||
AudioObjectType::WavetableSynthesis => "Wavetable Synthesis",
|
||||
AudioObjectType::GeneralMIDI => "General MIDI",
|
||||
AudioObjectType::AlgorithmicSynthesis => "Algorithmic Synthesis",
|
||||
AudioObjectType::ErrorResilientAacLowComplexity => "ER AAC LC",
|
||||
AudioObjectType::ErrorResilientAacLongTermPrediction => "ER AAC LTP",
|
||||
AudioObjectType::ErrorResilientAacScalable => "ER AAC scalable",
|
||||
AudioObjectType::ErrorResilientAacTwinVQ => "ER AAC TwinVQ",
|
||||
AudioObjectType::ErrorResilientAacBitSlicedArithmeticCoding => "ER AAC BSAC",
|
||||
AudioObjectType::ErrorResilientAacLowDelay => "ER AAC LD",
|
||||
AudioObjectType::ErrorResilientCodeExcitedLinearPrediction => "ER CELP",
|
||||
AudioObjectType::ErrorResilientHarmonicVectorExcitationCoding => "ER HVXC",
|
||||
AudioObjectType::ErrorResilientHarmonicIndividualLinesNoise => "ER HILN",
|
||||
AudioObjectType::ErrorResilientParametric => "ER Parametric",
|
||||
AudioObjectType::SinuSoidalCoding => "SSC",
|
||||
AudioObjectType::ParametricStereo => "Parametric Stereo",
|
||||
AudioObjectType::MpegSurround => "MPEG surround",
|
||||
AudioObjectType::MpegLayer1 => "MPEG Layer 1",
|
||||
AudioObjectType::MpegLayer2 => "MPEG Layer 2",
|
||||
AudioObjectType::MpegLayer3 => "MPEG Layer 3",
|
||||
AudioObjectType::DirectStreamTransfer => "DST",
|
||||
AudioObjectType::AudioLosslessCoding => "ALS",
|
||||
AudioObjectType::ScalableLosslessCoding => "SLS",
|
||||
AudioObjectType::ScalableLosslessCodingNoneCore => "SLS Non-core",
|
||||
AudioObjectType::ErrorResilientAacEnhancedLowDelay => "ER AAC ELD",
|
||||
AudioObjectType::SymbolicMusicRepresentationSimple => "SMR Simple",
|
||||
AudioObjectType::SymbolicMusicRepresentationMain => "SMR Main",
|
||||
AudioObjectType::UnifiedSpeechAudioCoding => "USAC",
|
||||
AudioObjectType::SpatialAudioObjectCoding => "SAOC",
|
||||
AudioObjectType::LowDelayMpegSurround => "LD MPEG Surround",
|
||||
AudioObjectType::SpatialAudioObjectCodingDialogueEnhancement => "SAOC-DE",
|
||||
AudioObjectType::AudioSync => "Audio Sync",
|
||||
};
|
||||
write!(f, "{type_str}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum SampleFreqIndex {
|
||||
Freq96000 = 0x0,
|
||||
Freq88200 = 0x1,
|
||||
Freq64000 = 0x2,
|
||||
Freq48000 = 0x3,
|
||||
Freq44100 = 0x4,
|
||||
Freq32000 = 0x5,
|
||||
Freq24000 = 0x6,
|
||||
Freq22050 = 0x7,
|
||||
Freq16000 = 0x8,
|
||||
Freq12000 = 0x9,
|
||||
Freq11025 = 0xa,
|
||||
Freq8000 = 0xb,
|
||||
Freq7350 = 0xc,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for SampleFreqIndex {
|
||||
type Error = Error;
|
||||
fn try_from(value: u8) -> Result<SampleFreqIndex> {
|
||||
match value {
|
||||
0x0 => Ok(SampleFreqIndex::Freq96000),
|
||||
0x1 => Ok(SampleFreqIndex::Freq88200),
|
||||
0x2 => Ok(SampleFreqIndex::Freq64000),
|
||||
0x3 => Ok(SampleFreqIndex::Freq48000),
|
||||
0x4 => Ok(SampleFreqIndex::Freq44100),
|
||||
0x5 => Ok(SampleFreqIndex::Freq32000),
|
||||
0x6 => Ok(SampleFreqIndex::Freq24000),
|
||||
0x7 => Ok(SampleFreqIndex::Freq22050),
|
||||
0x8 => Ok(SampleFreqIndex::Freq16000),
|
||||
0x9 => Ok(SampleFreqIndex::Freq12000),
|
||||
0xa => Ok(SampleFreqIndex::Freq11025),
|
||||
0xb => Ok(SampleFreqIndex::Freq8000),
|
||||
0xc => Ok(SampleFreqIndex::Freq7350),
|
||||
_ => Err(Error::InvalidData("invalid sampling frequency index")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SampleFreqIndex {
|
||||
pub fn freq(&self) -> u32 {
|
||||
match *self {
|
||||
SampleFreqIndex::Freq96000 => 96000,
|
||||
SampleFreqIndex::Freq88200 => 88200,
|
||||
SampleFreqIndex::Freq64000 => 64000,
|
||||
SampleFreqIndex::Freq48000 => 48000,
|
||||
SampleFreqIndex::Freq44100 => 44100,
|
||||
SampleFreqIndex::Freq32000 => 32000,
|
||||
SampleFreqIndex::Freq24000 => 24000,
|
||||
SampleFreqIndex::Freq22050 => 22050,
|
||||
SampleFreqIndex::Freq16000 => 16000,
|
||||
SampleFreqIndex::Freq12000 => 12000,
|
||||
SampleFreqIndex::Freq11025 => 11025,
|
||||
SampleFreqIndex::Freq8000 => 8000,
|
||||
SampleFreqIndex::Freq7350 => 7350,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum ChannelConfig {
|
||||
Mono = 0x1,
|
||||
Stereo = 0x2,
|
||||
Three = 0x3,
|
||||
Four = 0x4,
|
||||
Five = 0x5,
|
||||
FiveOne = 0x6,
|
||||
SevenOne = 0x7,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for ChannelConfig {
|
||||
type Error = Error;
|
||||
fn try_from(value: u8) -> Result<ChannelConfig> {
|
||||
match value {
|
||||
0x1 => Ok(ChannelConfig::Mono),
|
||||
0x2 => Ok(ChannelConfig::Stereo),
|
||||
0x3 => Ok(ChannelConfig::Three),
|
||||
0x4 => Ok(ChannelConfig::Four),
|
||||
0x5 => Ok(ChannelConfig::Five),
|
||||
0x6 => Ok(ChannelConfig::FiveOne),
|
||||
0x7 => Ok(ChannelConfig::SevenOne),
|
||||
_ => Err(Error::InvalidData("invalid channel configuration")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ChannelConfig {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s = match self {
|
||||
ChannelConfig::Mono => "mono",
|
||||
ChannelConfig::Stereo => "stereo",
|
||||
ChannelConfig::Three => "three",
|
||||
ChannelConfig::Four => "four",
|
||||
ChannelConfig::Five => "five",
|
||||
ChannelConfig::FiveOne => "five.one",
|
||||
ChannelConfig::SevenOne => "seven.one",
|
||||
};
|
||||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Default)]
|
||||
pub struct AvcConfig {
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
pub seq_param_set: Vec<u8>,
|
||||
pub pic_param_set: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Default)]
|
||||
pub struct HevcConfig {
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Default)]
|
||||
pub struct Vp9Config {
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct AacConfig {
|
||||
pub bitrate: u32,
|
||||
pub profile: AudioObjectType,
|
||||
pub freq_index: SampleFreqIndex,
|
||||
pub chan_conf: ChannelConfig,
|
||||
}
|
||||
|
||||
impl Default for AacConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
bitrate: 0,
|
||||
profile: AudioObjectType::AacLowComplexity,
|
||||
freq_index: SampleFreqIndex::Freq48000,
|
||||
chan_conf: ChannelConfig::Stereo,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Default)]
|
||||
pub struct TtxtConfig {}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum MediaConfig {
|
||||
AvcConfig(AvcConfig),
|
||||
HevcConfig(HevcConfig),
|
||||
Vp9Config(Vp9Config),
|
||||
AacConfig(AacConfig),
|
||||
TtxtConfig(TtxtConfig),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mp4Sample {
|
||||
pub start_time: u64,
|
||||
pub duration: u32,
|
||||
pub rendering_offset: i32,
|
||||
pub is_sync: bool,
|
||||
pub bytes: Bytes,
|
||||
}
|
||||
|
||||
impl PartialEq for Mp4Sample {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.start_time == other.start_time
|
||||
&& self.duration == other.duration
|
||||
&& self.rendering_offset == other.rendering_offset
|
||||
&& self.is_sync == other.is_sync
|
||||
&& self.bytes.len() == other.bytes.len() // XXX for easy check
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Mp4Sample {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"start_time {}, duration {}, rendering_offset {}, is_sync {}, length {}",
|
||||
self.start_time,
|
||||
self.duration,
|
||||
self.rendering_offset,
|
||||
self.is_sync,
|
||||
self.bytes.len()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn creation_time(creation_time: u64) -> u64 {
|
||||
// convert from MP4 epoch (1904-01-01) to Unix epoch (1970-01-01)
|
||||
if creation_time >= 2082844800 {
|
||||
creation_time - 2082844800
|
||||
} else {
|
||||
creation_time
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub enum DataType {
|
||||
Binary = 0x000000,
|
||||
Text = 0x000001,
|
||||
Image = 0x00000D,
|
||||
TempoCpil = 0x000015,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
impl std::default::Default for DataType {
|
||||
fn default() -> Self {
|
||||
DataType::Binary
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for DataType {
|
||||
type Error = Error;
|
||||
fn try_from(value: u32) -> Result<DataType> {
|
||||
match value {
|
||||
0x000000 => Ok(DataType::Binary),
|
||||
0x000001 => Ok(DataType::Text),
|
||||
0x00000D => Ok(DataType::Image),
|
||||
0x000015 => Ok(DataType::TempoCpil),
|
||||
_ => Err(Error::InvalidData("invalid data type")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
|
||||
pub enum MetadataKey {
|
||||
Title,
|
||||
Year,
|
||||
Poster,
|
||||
Summary,
|
||||
}
|
||||
|
||||
pub trait Metadata<'a> {
|
||||
/// The video's title
|
||||
fn title(&self) -> Option<Cow<str>>;
|
||||
/// The video's release year
|
||||
fn year(&self) -> Option<u32>;
|
||||
/// The video's poster (cover art)
|
||||
fn poster(&self) -> Option<&[u8]>;
|
||||
/// The video's summary
|
||||
fn summary(&self) -> Option<Cow<str>>;
|
||||
}
|
||||
|
||||
impl<'a, T: Metadata<'a>> Metadata<'a> for &'a T {
|
||||
fn title(&self) -> Option<Cow<str>> {
|
||||
(**self).title()
|
||||
}
|
||||
|
||||
fn year(&self) -> Option<u32> {
|
||||
(**self).year()
|
||||
}
|
||||
|
||||
fn poster(&self) -> Option<&[u8]> {
|
||||
(**self).poster()
|
||||
}
|
||||
|
||||
fn summary(&self) -> Option<Cow<str>> {
|
||||
(**self).summary()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: Metadata<'a>> Metadata<'a> for Option<T> {
|
||||
fn title(&self) -> Option<Cow<str>> {
|
||||
self.as_ref().and_then(|t| t.title())
|
||||
}
|
||||
|
||||
fn year(&self) -> Option<u32> {
|
||||
self.as_ref().and_then(|t| t.year())
|
||||
}
|
||||
|
||||
fn poster(&self) -> Option<&[u8]> {
|
||||
self.as_ref().and_then(|t| t.poster())
|
||||
}
|
||||
|
||||
fn summary(&self) -> Option<Cow<str>> {
|
||||
self.as_ref().and_then(|t| t.summary())
|
||||
}
|
||||
}
|
149
src/writer.rs
Normal file
149
src/writer.rs
Normal file
|
@ -0,0 +1,149 @@
|
|||
use byteorder::{BigEndian, WriteBytesExt};
|
||||
use std::io::{Seek, SeekFrom, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
use crate::track::Mp4TrackWriter;
|
||||
use crate::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Mp4Config {
|
||||
pub major_brand: FourCC,
|
||||
pub minor_version: u32,
|
||||
pub compatible_brands: Vec<FourCC>,
|
||||
pub timescale: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mp4Writer<W> {
|
||||
writer: W,
|
||||
tracks: Vec<Mp4TrackWriter>,
|
||||
mdat_pos: u64,
|
||||
timescale: u32,
|
||||
duration: u64,
|
||||
}
|
||||
|
||||
impl<W> Mp4Writer<W> {
|
||||
/// Consume self, returning the inner writer.
|
||||
///
|
||||
/// This can be useful to recover the inner writer after completion in case
|
||||
/// it's owned by the [Mp4Writer] instance.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// use mp4::{Mp4Writer, Mp4Config};
|
||||
/// use std::io::Cursor;
|
||||
///
|
||||
/// # fn main() -> mp4::Result<()> {
|
||||
/// let config = Mp4Config {
|
||||
/// major_brand: str::parse("isom").unwrap(),
|
||||
/// minor_version: 512,
|
||||
/// compatible_brands: vec![
|
||||
/// str::parse("isom").unwrap(),
|
||||
/// str::parse("iso2").unwrap(),
|
||||
/// str::parse("avc1").unwrap(),
|
||||
/// str::parse("mp41").unwrap(),
|
||||
/// ],
|
||||
/// timescale: 1000,
|
||||
/// };
|
||||
///
|
||||
/// let data = Cursor::new(Vec::<u8>::new());
|
||||
/// let mut writer = mp4::Mp4Writer::write_start(data, &config)?;
|
||||
/// writer.write_end()?;
|
||||
///
|
||||
/// let data: Vec<u8> = writer.into_writer().into_inner();
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub fn into_writer(self) -> W {
|
||||
self.writer
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write + Seek> Mp4Writer<W> {
|
||||
pub fn write_start(mut writer: W, config: &Mp4Config) -> Result<Self> {
|
||||
let ftyp = FtypBox {
|
||||
major_brand: config.major_brand,
|
||||
minor_version: config.minor_version,
|
||||
compatible_brands: config.compatible_brands.clone(),
|
||||
};
|
||||
ftyp.write_box(&mut writer)?;
|
||||
|
||||
// TODO largesize
|
||||
let mdat_pos = writer.stream_position()?;
|
||||
BoxHeader::new(BoxType::MdatBox, HEADER_SIZE).write(&mut writer)?;
|
||||
BoxHeader::new(BoxType::WideBox, HEADER_SIZE).write(&mut writer)?;
|
||||
|
||||
let tracks = Vec::new();
|
||||
let timescale = config.timescale;
|
||||
let duration = 0;
|
||||
Ok(Self {
|
||||
writer,
|
||||
tracks,
|
||||
mdat_pos,
|
||||
timescale,
|
||||
duration,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_track(&mut self, config: &TrackConfig) -> Result<()> {
|
||||
let track_id = self.tracks.len() as u32 + 1;
|
||||
let track = Mp4TrackWriter::new(track_id, config)?;
|
||||
self.tracks.push(track);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_durations(&mut self, track_dur: u64) {
|
||||
if track_dur > self.duration {
|
||||
self.duration = track_dur;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_sample(&mut self, track_id: u32, sample: &Mp4Sample) -> Result<()> {
|
||||
if track_id == 0 {
|
||||
return Err(Error::TrakNotFound(track_id));
|
||||
}
|
||||
|
||||
let track_dur = if let Some(ref mut track) = self.tracks.get_mut(track_id as usize - 1) {
|
||||
track.write_sample(&mut self.writer, sample, self.timescale)?
|
||||
} else {
|
||||
return Err(Error::TrakNotFound(track_id));
|
||||
};
|
||||
|
||||
self.update_durations(track_dur);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn update_mdat_size(&mut self) -> Result<()> {
|
||||
let mdat_end = self.writer.stream_position()?;
|
||||
let mdat_size = mdat_end - self.mdat_pos;
|
||||
if mdat_size > std::u32::MAX as u64 {
|
||||
self.writer.seek(SeekFrom::Start(self.mdat_pos))?;
|
||||
self.writer.write_u32::<BigEndian>(1)?;
|
||||
self.writer.seek(SeekFrom::Start(self.mdat_pos + 8))?;
|
||||
self.writer.write_u64::<BigEndian>(mdat_size)?;
|
||||
} else {
|
||||
self.writer.seek(SeekFrom::Start(self.mdat_pos))?;
|
||||
self.writer.write_u32::<BigEndian>(mdat_size as u32)?;
|
||||
}
|
||||
self.writer.seek(SeekFrom::Start(mdat_end))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_end(&mut self) -> Result<()> {
|
||||
let mut moov = MoovBox::default();
|
||||
|
||||
for track in self.tracks.iter_mut() {
|
||||
moov.traks.push(track.write_end(&mut self.writer)?);
|
||||
}
|
||||
self.update_mdat_size()?;
|
||||
|
||||
moov.mvhd.timescale = self.timescale;
|
||||
moov.mvhd.duration = self.duration;
|
||||
if moov.mvhd.duration > (u32::MAX as u64) {
|
||||
moov.mvhd.version = 1
|
||||
}
|
||||
moov.write_box(&mut self.writer)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
213
tests/lib.rs
213
tests/lib.rs
|
@ -1,38 +1,211 @@
|
|||
use mp4;
|
||||
use std::fs::File;
|
||||
|
||||
use mp4::{
|
||||
AudioObjectType, AvcProfile, ChannelConfig, MediaType, Metadata, Mp4Reader, SampleFreqIndex,
|
||||
TrackType,
|
||||
};
|
||||
use std::fs::{self, File};
|
||||
use std::io::BufReader;
|
||||
use std::time::Duration;
|
||||
|
||||
#[test]
|
||||
fn test_read_mp4() {
|
||||
let filename = "tests/samples/minimal.mp4";
|
||||
let f = File::open(filename).unwrap();
|
||||
let bmff = mp4::read_mp4(f).unwrap();
|
||||
let mut mp4 = get_reader("tests/samples/minimal.mp4");
|
||||
|
||||
assert_eq!(2591, bmff.size);
|
||||
assert_eq!(2591, mp4.size());
|
||||
|
||||
// ftyp.
|
||||
println!("{:?}", bmff.ftyp.compatible_brands);
|
||||
assert_eq!(4, bmff.ftyp.compatible_brands.len());
|
||||
assert_eq!(4, mp4.compatible_brands().len());
|
||||
|
||||
// Check compatible_brands.
|
||||
let brands = vec![
|
||||
String::from("isom"),
|
||||
String::from("iso2"),
|
||||
String::from("avc1"),
|
||||
String::from("mp41")
|
||||
String::from("mp41"),
|
||||
];
|
||||
|
||||
for b in brands {
|
||||
let t = bmff.ftyp.compatible_brands.iter().any(|x| x.to_string() == b);
|
||||
assert_eq!(t, true);
|
||||
let t = mp4.compatible_brands().iter().any(|x| x.to_string() == b);
|
||||
assert!(t);
|
||||
}
|
||||
|
||||
// moov.
|
||||
let moov = bmff.moov.unwrap();
|
||||
assert_eq!(moov.mvhd.version, 0);
|
||||
assert_eq!(moov.mvhd.creation_time, 0);
|
||||
assert_eq!(moov.mvhd.duration, 62);
|
||||
assert_eq!(moov.mvhd.timescale, 1000);
|
||||
assert_eq!(moov.traks.len(), 2);
|
||||
assert_eq!(mp4.duration(), Duration::from_millis(62));
|
||||
assert_eq!(mp4.timescale(), 1000);
|
||||
assert_eq!(mp4.tracks().len(), 2);
|
||||
|
||||
}
|
||||
let sample_count = mp4.sample_count(1).unwrap();
|
||||
assert_eq!(sample_count, 1);
|
||||
let sample_1_1 = mp4.read_sample(1, 1).unwrap().unwrap();
|
||||
assert_eq!(sample_1_1.bytes.len(), 751);
|
||||
assert_eq!(
|
||||
sample_1_1,
|
||||
mp4::Mp4Sample {
|
||||
start_time: 0,
|
||||
duration: 512,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 751]),
|
||||
}
|
||||
);
|
||||
let eos = mp4.read_sample(1, 2).unwrap();
|
||||
assert!(eos.is_none());
|
||||
|
||||
let sample_count = mp4.sample_count(2).unwrap();
|
||||
assert_eq!(sample_count, 3);
|
||||
let sample_2_1 = mp4.read_sample(2, 1).unwrap().unwrap();
|
||||
assert_eq!(sample_2_1.bytes.len(), 179);
|
||||
assert_eq!(
|
||||
sample_2_1,
|
||||
mp4::Mp4Sample {
|
||||
start_time: 0,
|
||||
duration: 1024,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 179]),
|
||||
}
|
||||
);
|
||||
|
||||
let sample_2_2 = mp4.read_sample(2, 2).unwrap().unwrap();
|
||||
assert_eq!(
|
||||
sample_2_2,
|
||||
mp4::Mp4Sample {
|
||||
start_time: 1024,
|
||||
duration: 1024,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 180]),
|
||||
}
|
||||
);
|
||||
|
||||
let sample_2_3 = mp4.read_sample(2, 3).unwrap().unwrap();
|
||||
assert_eq!(
|
||||
sample_2_3,
|
||||
mp4::Mp4Sample {
|
||||
start_time: 2048,
|
||||
duration: 896,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 160]),
|
||||
}
|
||||
);
|
||||
|
||||
let eos = mp4.read_sample(2, 4).unwrap();
|
||||
assert!(eos.is_none());
|
||||
|
||||
// track #1
|
||||
let track1 = mp4.tracks().get(&1).unwrap();
|
||||
assert_eq!(track1.track_id(), 1);
|
||||
assert_eq!(track1.track_type().unwrap(), TrackType::Video);
|
||||
assert_eq!(track1.media_type().unwrap(), MediaType::H264);
|
||||
assert_eq!(track1.video_profile().unwrap(), AvcProfile::AvcHigh);
|
||||
assert_eq!(track1.width(), 320);
|
||||
assert_eq!(track1.height(), 240);
|
||||
assert_eq!(track1.bitrate(), 150200);
|
||||
assert_eq!(track1.frame_rate(), 25.00);
|
||||
|
||||
// track #2
|
||||
let track2 = mp4.tracks().get(&2).unwrap();
|
||||
assert_eq!(track2.track_type().unwrap(), TrackType::Audio);
|
||||
assert_eq!(track2.media_type().unwrap(), MediaType::AAC);
|
||||
assert_eq!(
|
||||
track2.audio_profile().unwrap(),
|
||||
AudioObjectType::AacLowComplexity
|
||||
);
|
||||
assert_eq!(
|
||||
track2.sample_freq_index().unwrap(),
|
||||
SampleFreqIndex::Freq48000
|
||||
);
|
||||
assert_eq!(track2.channel_config().unwrap(), ChannelConfig::Mono);
|
||||
assert_eq!(track2.bitrate(), 67695);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_extended_audio_object_type() {
|
||||
// Extended audio object type and sample rate index of 15
|
||||
let mp4 = get_reader("tests/samples/extended_audio_object_type.mp4");
|
||||
|
||||
let track = mp4.tracks().get(&1).unwrap();
|
||||
assert_eq!(track.track_type().unwrap(), TrackType::Audio);
|
||||
assert_eq!(track.media_type().unwrap(), MediaType::AAC);
|
||||
assert_eq!(
|
||||
track.audio_profile().unwrap(),
|
||||
AudioObjectType::AudioLosslessCoding
|
||||
);
|
||||
assert_eq!(
|
||||
track
|
||||
.trak
|
||||
.mdia
|
||||
.minf
|
||||
.stbl
|
||||
.stsd
|
||||
.mp4a
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.esds
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.es_desc
|
||||
.dec_config
|
||||
.dec_specific
|
||||
.freq_index,
|
||||
15
|
||||
);
|
||||
assert_eq!(track.channel_config().unwrap(), ChannelConfig::Stereo);
|
||||
assert_eq!(track.bitrate(), 839250);
|
||||
}
|
||||
|
||||
fn get_reader(path: &str) -> Mp4Reader<BufReader<File>> {
|
||||
let f = File::open(path).unwrap();
|
||||
let f_size = f.metadata().unwrap().len();
|
||||
let reader = BufReader::new(f);
|
||||
|
||||
mp4::Mp4Reader::read_header(reader, f_size).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_metadata() {
|
||||
let want_poster = fs::read("tests/samples/big_buck_bunny.jpg").unwrap();
|
||||
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.";
|
||||
let mp4 = get_reader("tests/samples/big_buck_bunny_metadata.m4v");
|
||||
let metadata = mp4.metadata();
|
||||
assert_eq!(metadata.title(), Some("Big Buck Bunny".into()));
|
||||
assert_eq!(metadata.year(), Some(2008));
|
||||
assert_eq!(metadata.summary(), Some(want_summary.into()));
|
||||
|
||||
assert!(metadata.poster().is_some());
|
||||
let poster = metadata.poster().unwrap();
|
||||
assert_eq!(poster.len(), want_poster.len());
|
||||
assert_eq!(poster, want_poster.as_slice());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_fragments() {
|
||||
let mp4 = get_reader("tests/samples/minimal_init.mp4");
|
||||
|
||||
assert_eq!(692, mp4.size());
|
||||
assert_eq!(5, mp4.compatible_brands().len());
|
||||
|
||||
let sample_count = mp4.sample_count(1).unwrap();
|
||||
assert_eq!(sample_count, 0);
|
||||
|
||||
let f = File::open("tests/samples/minimal_fragment.m4s").unwrap();
|
||||
let f_size = f.metadata().unwrap().len();
|
||||
let frag_reader = BufReader::new(f);
|
||||
|
||||
let mut mp4_fragment = mp4.read_fragment_header(frag_reader, f_size).unwrap();
|
||||
let sample_count = mp4_fragment.sample_count(1).unwrap();
|
||||
assert_eq!(sample_count, 1);
|
||||
let sample_1_1 = mp4_fragment.read_sample(1, 1).unwrap().unwrap();
|
||||
assert_eq!(sample_1_1.bytes.len(), 751);
|
||||
assert_eq!(
|
||||
sample_1_1,
|
||||
mp4::Mp4Sample {
|
||||
start_time: 0,
|
||||
duration: 512,
|
||||
rendering_offset: 0,
|
||||
is_sync: true,
|
||||
bytes: mp4::Bytes::from(vec![0x0u8; 751]),
|
||||
}
|
||||
);
|
||||
let eos = mp4_fragment.read_sample(1, 2);
|
||||
assert!(eos.is_err());
|
||||
}
|
||||
|
|
BIN
tests/samples/big_buck_bunny.jpg
Normal file
BIN
tests/samples/big_buck_bunny.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 132 KiB |
BIN
tests/samples/big_buck_bunny_metadata.m4v
Normal file
BIN
tests/samples/big_buck_bunny_metadata.m4v
Normal file
Binary file not shown.
BIN
tests/samples/extended_audio_object_type.mp4
Normal file
BIN
tests/samples/extended_audio_object_type.mp4
Normal file
Binary file not shown.
BIN
tests/samples/minimal_fragment.m4s
Normal file
BIN
tests/samples/minimal_fragment.m4s
Normal file
Binary file not shown.
BIN
tests/samples/minimal_init.mp4
Normal file
BIN
tests/samples/minimal_init.mp4
Normal file
Binary file not shown.
Loading…
Reference in a new issue