mirror of
https://github.com/alfg/mp4-rust.git
synced 2024-05-20 01:08:06 +00:00
Compare commits
35 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 |
40
.github/workflows/rust.yml
vendored
40
.github/workflows/rust.yml
vendored
|
@ -13,8 +13,38 @@ 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
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "mp4"
|
||||
version = "0.10.0"
|
||||
version = "0.14.0"
|
||||
authors = ["Alf <alf.g.jr@gmail.com>"]
|
||||
edition = "2018"
|
||||
description = "MP4 reader and writer library in Rust."
|
||||
|
@ -10,6 +10,7 @@ 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"
|
||||
|
|
31
README.md
31
README.md
|
@ -42,7 +42,7 @@ fn main() -> Result<()> {
|
|||
println!("duration: {:?}", mp4.duration());
|
||||
|
||||
// Track info.
|
||||
for track in mp4.tracks().iter() {
|
||||
for track in mp4.tracks().values() {
|
||||
println!(
|
||||
"track: #{}({}) {} : {}",
|
||||
track.track_id(),
|
||||
|
@ -58,9 +58,12 @@ fn main() -> Result<()> {
|
|||
See [examples/](examples/) for more examples.
|
||||
|
||||
#### Install
|
||||
Add to your `Cargo.toml`:
|
||||
```
|
||||
cargo add mp4
|
||||
```
|
||||
or add to your `Cargo.toml`:
|
||||
```toml
|
||||
mp4 = "0.10.0"
|
||||
mp4 = "0.14.0"
|
||||
```
|
||||
|
||||
#### Documentation
|
||||
|
@ -76,6 +79,12 @@ mp4 = "0.10.0"
|
|||
cargo build
|
||||
```
|
||||
|
||||
#### Lint and Format
|
||||
```
|
||||
cargo clippy --fix
|
||||
cargo fmt --all
|
||||
```
|
||||
|
||||
#### Run Examples
|
||||
* `mp4info`
|
||||
```
|
||||
|
@ -97,6 +106,22 @@ With print statement output.
|
|||
cargo test -- --nocapture
|
||||
```
|
||||
|
||||
#### 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
|
||||
|
|
|
@ -1,22 +1,26 @@
|
|||
use criterion::BenchmarkId;
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use mp4;
|
||||
|
||||
use std::fs::File;
|
||||
|
||||
fn read_mp4(filename: &str) -> u64 {
|
||||
let f = File::open(filename).unwrap();
|
||||
let m = mp4::read_mp4(f).unwrap();
|
||||
let size = m.size();
|
||||
size
|
||||
|
||||
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));
|
||||
});
|
||||
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);
|
||||
criterion_main!(benches);
|
||||
|
|
|
@ -5,16 +5,9 @@ use std::io::{self, BufReader, BufWriter};
|
|||
use std::path::Path;
|
||||
|
||||
use mp4::{
|
||||
AacConfig,
|
||||
AvcConfig,
|
||||
HevcConfig,
|
||||
Vp9Config,
|
||||
TtxtConfig,
|
||||
MediaConfig,
|
||||
MediaType,
|
||||
Mp4Config,
|
||||
Result,
|
||||
TrackConfig};
|
||||
AacConfig, AvcConfig, HevcConfig, MediaConfig, MediaType, Mp4Config, Result, TrackConfig,
|
||||
TtxtConfig, Vp9Config,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
@ -41,7 +34,7 @@ fn copy<P: AsRef<Path>>(src_filename: &P, dst_filename: &P) -> Result<()> {
|
|||
let mut mp4_writer = mp4::Mp4Writer::write_start(
|
||||
writer,
|
||||
&Mp4Config {
|
||||
major_brand: mp4_reader.major_brand().clone(),
|
||||
major_brand: *mp4_reader.major_brand(),
|
||||
minor_version: mp4_reader.minor_version(),
|
||||
compatible_brands: mp4_reader.compatible_brands().to_vec(),
|
||||
timescale: mp4_reader.timescale(),
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::io::prelude::*;
|
|||
use std::io::{self, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
use mp4::{Result, Mp4Box};
|
||||
use mp4::{Mp4Box, Result};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
@ -25,7 +25,7 @@ fn dump<P: AsRef<Path>>(filename: &P) -> Result<()> {
|
|||
|
||||
// print out boxes
|
||||
for b in boxes.iter() {
|
||||
println!("[{}] size={} {}", b.name, b.size, b.summary);
|
||||
println!("[{}] size={} {}", b.name, b.size, b.summary);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -45,12 +45,11 @@ fn get_boxes(file: File) -> Result<Vec<Box>> {
|
|||
let mp4 = mp4::Mp4Reader::read_header(reader, size)?;
|
||||
|
||||
// collect known boxes
|
||||
let mut boxes = Vec::new();
|
||||
|
||||
// ftyp, moov, mvhd
|
||||
boxes.push(build_box(&mp4.ftyp));
|
||||
boxes.push(build_box(&mp4.moov));
|
||||
boxes.push(build_box(&mp4.moov.mvhd));
|
||||
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));
|
||||
|
@ -133,11 +132,11 @@ fn get_boxes(file: File) -> Result<Vec<Box>> {
|
|||
Ok(boxes)
|
||||
}
|
||||
|
||||
fn build_box<M: Mp4Box + std::fmt::Debug>(ref m: &M) -> Box {
|
||||
return Box{
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::io::prelude::*;
|
|||
use std::io::{self, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
use mp4::{Mp4Track, Result, TrackType, Error};
|
||||
use mp4::{Error, Mp4Track, Result, TrackType};
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
@ -32,13 +32,16 @@ fn info<P: AsRef<Path>>(filename: &P) -> Result<()> {
|
|||
let mut compatible_brands = String::new();
|
||||
for brand in mp4.compatible_brands().iter() {
|
||||
compatible_brands.push_str(&brand.to_string());
|
||||
compatible_brands.push_str(" ");
|
||||
compatible_brands.push(' ');
|
||||
}
|
||||
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!(
|
||||
" creation time: {}",
|
||||
creation_time(mp4.moov.mvhd.creation_time)
|
||||
);
|
||||
println!(" duration: {:?}", mp4.duration());
|
||||
println!(" fragments: {:?}", mp4.is_fragmented());
|
||||
println!(" timescale: {:?}\n", mp4.timescale());
|
||||
|
@ -46,16 +49,17 @@ fn info<P: AsRef<Path>>(filename: &P) -> Result<()> {
|
|||
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)?,
|
||||
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
|
||||
media_info.unwrap_or_else(|e| e.to_string())
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
|
@ -89,7 +93,6 @@ fn video_info(track: &Mp4Track) -> Result<String> {
|
|||
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(),
|
||||
|
@ -124,11 +127,7 @@ fn audio_info(track: &Mp4Track) -> Result<String> {
|
|||
|
||||
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()?,
|
||||
))
|
||||
Ok(format!("{} ({:?})", track.media_type()?, track.box_type()?,))
|
||||
} else {
|
||||
Err(Error::InvalidData("tx3g box not found"))
|
||||
}
|
||||
|
@ -141,4 +140,4 @@ fn creation_time(creation_time: u64) -> u64 {
|
|||
} else {
|
||||
creation_time
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::io::prelude::*;
|
|||
use std::io::{self, BufReader};
|
||||
use std::path::Path;
|
||||
|
||||
use mp4::{Result};
|
||||
use mp4::Result;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
|
@ -34,13 +34,14 @@ fn samples<P: AsRef<Path>>(filename: &P) -> Result<()> {
|
|||
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,
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,4 +21,4 @@ fn main() -> mp4::Result<()> {
|
|||
let data: Vec<u8> = writer.into_writer().into_inner();
|
||||
println!("{:?}", data);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use mp4;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
|
||||
|
@ -17,11 +16,12 @@ fn main() {
|
|||
println!("Major Brand: {}", mp4.major_brand());
|
||||
|
||||
for track in mp4.tracks().values() {
|
||||
println!("Track: #{}({}) {} {}",
|
||||
println!(
|
||||
"Track: #{}({}) {} {}",
|
||||
track.track_id(),
|
||||
track.language(),
|
||||
track.track_type().unwrap(),
|
||||
track.box_type().unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,4 +24,6 @@ pub enum Error {
|
|||
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),
|
||||
}
|
||||
|
|
21
src/lib.rs
21
src/lib.rs
|
@ -1,14 +1,14 @@
|
|||
//! `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};
|
||||
|
@ -49,9 +49,9 @@
|
|||
//! Ok(())
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! See [examples] for more examples.
|
||||
//!
|
||||
//!
|
||||
//! # Installation
|
||||
//!
|
||||
//! Add the following to your `Cargo.toml` file:
|
||||
|
@ -60,14 +60,13 @@
|
|||
//! [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/blob/master/src/examples
|
||||
//! [examples]: https://github.com/alfg/mp4-rust/tree/master/examples
|
||||
#![doc(html_root_url = "https://docs.rs/mp4/*")]
|
||||
|
||||
|
||||
use std::io::{BufReader};
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
|
||||
mod error;
|
||||
pub use error::Error;
|
||||
|
@ -94,4 +93,4 @@ pub fn read_mp4(f: File) -> Result<Mp4Reader<BufReader<File>>> {
|
|||
let reader = BufReader::new(f);
|
||||
let mp4 = reader::Mp4Reader::read_header(reader, size)?;
|
||||
Ok(mp4)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Avc1Box {
|
||||
pub data_reference_index: u16,
|
||||
pub width: u16,
|
||||
|
@ -60,11 +60,11 @@ impl Avc1Box {
|
|||
|
||||
impl Mp4Box for Avc1Box {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -72,8 +72,10 @@ impl Mp4Box for Avc1Box {
|
|||
}
|
||||
|
||||
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);
|
||||
let s = format!(
|
||||
"data_reference_index={} width={} height={} frame_count={}",
|
||||
self.data_reference_index, self.width, self.height, self.frame_count
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -99,25 +101,37 @@ impl<R: Read + Seek> ReadBox<&mut R> for Avc1Box {
|
|||
let depth = reader.read_u16::<BigEndian>()?;
|
||||
reader.read_i16::<BigEndian>()?; // pre-defined
|
||||
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
if name == BoxType::AvcCBox {
|
||||
let avcc = AvcCBox::read_box(reader, s)?;
|
||||
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)?;
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
Ok(Avc1Box {
|
||||
data_reference_index,
|
||||
width,
|
||||
height,
|
||||
horizresolution,
|
||||
vertresolution,
|
||||
frame_count,
|
||||
depth,
|
||||
avcc,
|
||||
})
|
||||
} else {
|
||||
Err(Error::InvalidData("avcc not found"))
|
||||
return Ok(Avc1Box {
|
||||
data_reference_index,
|
||||
width,
|
||||
height,
|
||||
horizresolution,
|
||||
vertresolution,
|
||||
frame_count,
|
||||
depth,
|
||||
avcc,
|
||||
});
|
||||
} else {
|
||||
skip_bytes_to(reader, current + s)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -151,7 +165,7 @@ impl<W: Write> WriteBox<&mut W> for Avc1Box {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct AvcCBox {
|
||||
pub configuration_version: u8,
|
||||
pub avc_profile_indication: u8,
|
||||
|
@ -197,8 +211,7 @@ impl Mp4Box for AvcCBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("avc_profile_indication={}",
|
||||
self.avc_profile_indication);
|
||||
let s = format!("avc_profile_indication={}", self.avc_profile_indication);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -261,7 +274,7 @@ impl<W: Write> WriteBox<&mut W> for AvcCBox {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct NalUnit {
|
||||
pub bytes: Vec<u8>,
|
||||
}
|
||||
|
@ -282,13 +295,13 @@ impl NalUnit {
|
|||
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(&mut bytes)?;
|
||||
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(&self.bytes)?;
|
||||
writer.write_all(&self.bytes)?;
|
||||
Ok(self.size() as u64)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct Co64Box {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -25,11 +26,11 @@ impl Co64Box {
|
|||
|
||||
impl Mp4Box for Co64Box {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -48,7 +49,20 @@ impl<R: Read + Seek> ReadBox<&mut R> for Co64Box {
|
|||
|
||||
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>()?;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct CttsBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -23,7 +24,7 @@ impl CttsBox {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct CttsEntry {
|
||||
pub sample_count: u32,
|
||||
pub sample_offset: i32,
|
||||
|
@ -31,11 +32,11 @@ pub struct CttsEntry {
|
|||
|
||||
impl Mp4Box for CttsBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -54,7 +55,21 @@ impl<R: Read + Seek> ReadBox<&mut R> for CttsBox {
|
|||
|
||||
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 {
|
||||
|
|
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);
|
||||
}
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct DinfBox {
|
||||
dref: DrefBox,
|
||||
}
|
||||
|
@ -20,11 +20,11 @@ impl DinfBox {
|
|||
|
||||
impl Mp4Box for DinfBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -32,7 +32,7 @@ impl Mp4Box for DinfBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("");
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -43,12 +43,17 @@ impl<R: Read + Seek> ReadBox<&mut R> for DinfBox {
|
|||
|
||||
let mut dref = None;
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
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 => {
|
||||
|
@ -60,7 +65,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for DinfBox {
|
|||
}
|
||||
}
|
||||
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if dref.is_none() {
|
||||
|
@ -84,7 +89,7 @@ impl<W: Write> WriteBox<&mut W> for DinfBox {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct DrefBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -119,11 +124,11 @@ impl DrefBox {
|
|||
|
||||
impl Mp4Box for DrefBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -131,7 +136,7 @@ impl Mp4Box for DrefBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("");
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -140,7 +145,7 @@ 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.seek(SeekFrom::Current(0))?;
|
||||
let mut current = reader.stream_position()?;
|
||||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
let end = start + size;
|
||||
|
@ -156,17 +161,22 @@ impl<R: Read + Seek> ReadBox<&mut R> for DrefBox {
|
|||
// 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)?);
|
||||
url = Some(UrlBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
@ -196,7 +206,7 @@ impl<W: Write> WriteBox<&mut W> for DrefBox {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct UrlBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -221,7 +231,7 @@ impl UrlBox {
|
|||
pub fn get_size(&self) -> u64 {
|
||||
let mut size = HEADER_SIZE + HEADER_EXT_SIZE;
|
||||
|
||||
if ! self.location.is_empty() {
|
||||
if !self.location.is_empty() {
|
||||
size += self.location.bytes().len() as u64 + 1;
|
||||
}
|
||||
|
||||
|
@ -231,11 +241,11 @@ impl UrlBox {
|
|||
|
||||
impl Mp4Box for UrlBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -254,22 +264,16 @@ impl<R: Read + Seek> ReadBox<&mut R> for UrlBox {
|
|||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let location = if size - HEADER_SIZE - HEADER_EXT_SIZE > 0 {
|
||||
let buf_size = size - HEADER_SIZE - HEADER_EXT_SIZE - 1;
|
||||
let mut buf = vec![0u8; buf_size as usize];
|
||||
reader.read_exact(&mut buf)?;
|
||||
match String::from_utf8(buf) {
|
||||
Ok(t) => {
|
||||
if t.len() != buf_size as usize {
|
||||
return Err(Error::InvalidData("string too small"))
|
||||
}
|
||||
t
|
||||
}
|
||||
_ => String::default(),
|
||||
}
|
||||
} else {
|
||||
String::default()
|
||||
};
|
||||
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)?;
|
||||
|
||||
|
@ -288,8 +292,8 @@ impl<W: Write> WriteBox<&mut W> for UrlBox {
|
|||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
if ! self.location.is_empty() {
|
||||
writer.write(self.location.as_bytes())?;
|
||||
if !self.location.is_empty() {
|
||||
writer.write_all(self.location.as_bytes())?;
|
||||
writer.write_u8(0)?;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::elst::ElstBox;
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct EdtsBox {
|
||||
pub elst: Option<ElstBox>,
|
||||
}
|
||||
|
@ -29,11 +29,11 @@ impl EdtsBox {
|
|||
|
||||
impl Mp4Box for EdtsBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -41,7 +41,7 @@ impl Mp4Box for EdtsBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("");
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -54,13 +54,15 @@ impl<R: Read + Seek> ReadBox<&mut R> for EdtsBox {
|
|||
|
||||
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",
|
||||
));
|
||||
}
|
||||
|
||||
match name {
|
||||
BoxType::ElstBox => {
|
||||
let elst = ElstBox::read_box(reader, s)?;
|
||||
edts.elst = Some(elst);
|
||||
}
|
||||
_ => {}
|
||||
if let BoxType::ElstBox = name {
|
||||
let elst = ElstBox::read_box(reader, s)?;
|
||||
edts.elst = Some(elst);
|
||||
}
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct ElstBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -13,7 +14,7 @@ pub struct ElstBox {
|
|||
pub entries: Vec<ElstEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct ElstEntry {
|
||||
pub segment_duration: u64,
|
||||
pub media_time: u64,
|
||||
|
@ -39,11 +40,11 @@ impl ElstBox {
|
|||
|
||||
impl Mp4Box for ElstBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -62,7 +63,29 @@ impl<R: Read + Seek> ReadBox<&mut R> for ElstBox {
|
|||
|
||||
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 {
|
||||
|
|
|
@ -6,7 +6,7 @@ use serde::Serialize;
|
|||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct EmsgBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -26,14 +26,14 @@ impl EmsgBox {
|
|||
4 + // id
|
||||
Self::time_size(version) +
|
||||
(scheme_id_uri.len() + 1) as u64 +
|
||||
(value.len() as u64 + 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")
|
||||
_ => panic!("version must be 0 or 1"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,8 +44,8 @@ impl Mp4Box for EmsgBox {
|
|||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
Self::size_without_message(self.version, &self.scheme_id_uri, &self.value) +
|
||||
self.message_data.len() as 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> {
|
||||
|
@ -64,10 +64,13 @@ impl<R: Read + Seek> ReadBox<&mut R> for EmsgBox {
|
|||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
|
||||
let (
|
||||
timescale, presentation_time, presentation_time_delta, event_duration,
|
||||
timescale,
|
||||
presentation_time,
|
||||
presentation_time_delta,
|
||||
event_duration,
|
||||
id,
|
||||
scheme_id_uri,
|
||||
value
|
||||
value,
|
||||
) = match version {
|
||||
0 => {
|
||||
let scheme_id_uri = read_null_terminated_utf8_string(reader)?;
|
||||
|
@ -79,7 +82,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for EmsgBox {
|
|||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
scheme_id_uri,
|
||||
value
|
||||
value,
|
||||
)
|
||||
}
|
||||
1 => (
|
||||
|
@ -89,9 +92,9 @@ impl<R: Read + Seek> ReadBox<&mut R> for EmsgBox {
|
|||
reader.read_u32::<BigEndian>()?,
|
||||
reader.read_u32::<BigEndian>()?,
|
||||
read_null_terminated_utf8_string(reader)?,
|
||||
read_null_terminated_utf8_string(reader)?
|
||||
read_null_terminated_utf8_string(reader)?,
|
||||
),
|
||||
_ => return Err(Error::InvalidData("version must be 0 or 1"))
|
||||
_ => return Err(Error::InvalidData("version must be 0 or 1")),
|
||||
};
|
||||
|
||||
let message_size = size - Self::size_without_message(version, &scheme_id_uri, &value);
|
||||
|
@ -140,7 +143,7 @@ impl<W: Write> WriteBox<&mut W> for EmsgBox {
|
|||
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"))
|
||||
_ => return Err(Error::InvalidData("version must be 0 or 1")),
|
||||
}
|
||||
|
||||
for &byte in &self.message_data {
|
||||
|
@ -156,7 +159,9 @@ fn read_null_terminated_utf8_string<R: Read + Seek>(reader: &mut R) -> Result<St
|
|||
loop {
|
||||
let byte = reader.read_u8()?;
|
||||
bytes.push(byte);
|
||||
if byte == 0 { break; }
|
||||
if byte == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Ok(str) = unsafe { CStr::from_bytes_with_nul_unchecked(&bytes) }.to_str() {
|
||||
Ok(str.to_string())
|
||||
|
@ -234,4 +239,4 @@ mod tests {
|
|||
let dst_box = EmsgBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct FtypBox {
|
||||
pub major_brand: FourCC,
|
||||
pub minor_version: u32,
|
||||
|
@ -23,11 +23,11 @@ impl FtypBox {
|
|||
|
||||
impl Mp4Box for FtypBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -39,8 +39,12 @@ impl Mp4Box for FtypBox {
|
|||
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("-"));
|
||||
let s = format!(
|
||||
"major_brand={} minor_version={} compatible_brands={}",
|
||||
self.major_brand,
|
||||
self.minor_version,
|
||||
compatible_brands.join("-")
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -49,12 +53,12 @@ impl<R: Read + Seek> ReadBox<&mut R> for FtypBox {
|
|||
fn read_box(reader: &mut R, size: u64) -> Result<Self> {
|
||||
let start = box_start(reader)?;
|
||||
|
||||
let major = reader.read_u32::<BigEndian>()?;
|
||||
let minor = reader.read_u32::<BigEndian>()?;
|
||||
if size % 4 != 0 {
|
||||
return Err(Error::InvalidData("invalid ftyp size"));
|
||||
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 {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct HdlrBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -24,11 +24,11 @@ impl HdlrBox {
|
|||
|
||||
impl Mp4Box for HdlrBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -36,7 +36,7 @@ impl Mp4Box for HdlrBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("handler_type={} name={}", self.handler_type.to_string(), self.name);
|
||||
let s = format!("handler_type={} name={}", self.handler_type, self.name);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -52,19 +52,16 @@ impl<R: Read + Seek> ReadBox<&mut R> for HdlrBox {
|
|||
|
||||
skip_bytes(reader, 12)?; // reserved
|
||||
|
||||
let buf_size = size - HEADER_SIZE - HEADER_EXT_SIZE - 20 - 1;
|
||||
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)?;
|
||||
|
||||
let handler_string = match String::from_utf8(buf) {
|
||||
Ok(t) => {
|
||||
if t.len() != buf_size as usize {
|
||||
return Err(Error::InvalidData("string too small"))
|
||||
}
|
||||
t
|
||||
}
|
||||
_ => String::from("null"),
|
||||
};
|
||||
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)?;
|
||||
|
||||
|
@ -92,7 +89,7 @@ impl<W: Write> WriteBox<&mut W> for HdlrBox {
|
|||
writer.write_u32::<BigEndian>(0)?;
|
||||
}
|
||||
|
||||
writer.write(self.name.as_bytes())?;
|
||||
writer.write_all(self.name.as_bytes())?;
|
||||
writer.write_u8(0)?;
|
||||
|
||||
Ok(size)
|
||||
|
@ -125,4 +122,52 @@ mod tests {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Hev1Box {
|
||||
pub data_reference_index: u16,
|
||||
pub width: u16,
|
||||
|
@ -60,11 +60,11 @@ impl Hev1Box {
|
|||
|
||||
impl Mp4Box for Hev1Box {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -72,8 +72,10 @@ impl Mp4Box for Hev1Box {
|
|||
}
|
||||
|
||||
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);
|
||||
let s = format!(
|
||||
"data_reference_index={} width={} height={} frame_count={}",
|
||||
self.data_reference_index, self.width, self.height, self.frame_count
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -101,6 +103,11 @@ impl<R: Read + Seek> ReadBox<&mut R> for Hev1Box {
|
|||
|
||||
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)?;
|
||||
|
||||
|
@ -151,15 +158,33 @@ impl<W: Write> WriteBox<&mut W> for Hev1Box {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -170,8 +195,13 @@ impl Mp4Box for HvcCBox {
|
|||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
let size = HEADER_SIZE + 1;
|
||||
size
|
||||
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> {
|
||||
|
@ -179,22 +209,108 @@ impl Mp4Box for HvcCBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("configuration_version={}",
|
||||
self.configuration_version);
|
||||
Ok(s)
|
||||
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 start = box_start(reader)?;
|
||||
|
||||
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;
|
||||
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -205,6 +321,40 @@ impl<W: Write> WriteBox<&mut W> for HvcCBox {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
@ -227,6 +377,7 @@ mod tests {
|
|||
depth: 24,
|
||||
hvcc: HvcCBox {
|
||||
configuration_version: 1,
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
let mut buf = Vec::new();
|
||||
|
|
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);
|
||||
}
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::char::{decode_utf16, REPLACEMENT_CHARACTER};
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct MdhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -50,11 +50,11 @@ impl Default for MdhdBox {
|
|||
|
||||
impl Mp4Box for MdhdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -62,8 +62,10 @@ impl Mp4Box for MdhdBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("creation_time={} timescale={} duration={} language={}",
|
||||
self.creation_time, self.timescale, self.duration, self.language);
|
||||
let s = format!(
|
||||
"creation_time={} timescale={} duration={} language={}",
|
||||
self.creation_time, self.timescale, self.duration, self.language
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -149,7 +151,7 @@ fn language_string(language: u16) -> String {
|
|||
.map(|r| r.unwrap_or(REPLACEMENT_CHARACTER))
|
||||
.collect::<String>();
|
||||
|
||||
return lang_str;
|
||||
lang_str
|
||||
}
|
||||
|
||||
fn language_code(language: &str) -> u16 {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use serde::{Serialize};
|
||||
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, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct MdiaBox {
|
||||
pub mdhd: MdhdBox,
|
||||
pub hdlr: HdlrBox,
|
||||
|
@ -23,11 +23,11 @@ impl MdiaBox {
|
|||
|
||||
impl Mp4Box for MdiaBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -35,7 +35,7 @@ impl Mp4Box for MdiaBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("");
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -48,12 +48,17 @@ impl<R: Read + Seek> ReadBox<&mut R> for MdiaBox {
|
|||
let mut hdlr = None;
|
||||
let mut minf = None;
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
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 => {
|
||||
|
@ -71,7 +76,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for MdiaBox {
|
|||
}
|
||||
}
|
||||
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if mdhd.is_none() {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
|
||||
pub struct MehdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -28,23 +28,13 @@ impl MehdBox {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for MehdBox {
|
||||
fn default() -> Self {
|
||||
MehdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
fragment_duration: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MehdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -105,7 +95,6 @@ mod tests {
|
|||
use crate::mp4box::BoxHeader;
|
||||
use std::io::Cursor;
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_mehd32() {
|
||||
let src_box = MehdBox {
|
||||
|
|
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);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct MfhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -27,17 +27,17 @@ impl MfhdBox {
|
|||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 4
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 4
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for MfhdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use serde::{Serialize};
|
||||
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, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct MinfBox {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub vmhd: Option<VmhdBox>,
|
||||
|
@ -37,11 +37,11 @@ impl MinfBox {
|
|||
|
||||
impl Mp4Box for MinfBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -49,7 +49,7 @@ impl Mp4Box for MinfBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("");
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -63,12 +63,17 @@ impl<R: Read + Seek> ReadBox<&mut R> for MinfBox {
|
|||
let mut dinf = None;
|
||||
let mut stbl = None;
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
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 => {
|
||||
|
@ -89,7 +94,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for MinfBox {
|
|||
}
|
||||
}
|
||||
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if dinf.is_none() {
|
||||
|
|
|
@ -1,18 +1,22 @@
|
|||
//! 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
|
||||
//!
|
||||
//! http://mp4ra.org/#/atoms
|
||||
//!
|
||||
//! Supported Atoms:
|
||||
//! ftyp
|
||||
//! moov
|
||||
//! mvhd
|
||||
//! udta
|
||||
//! meta
|
||||
//! ilst
|
||||
//! data
|
||||
//! trak
|
||||
//! tkhd
|
||||
//! mdia
|
||||
|
@ -46,10 +50,11 @@
|
|||
//! mfhd
|
||||
//! traf
|
||||
//! tfhd
|
||||
//! tfdt
|
||||
//! trun
|
||||
//! mdat
|
||||
//! free
|
||||
//!
|
||||
//!
|
||||
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use std::convert::TryInto;
|
||||
|
@ -60,24 +65,26 @@ 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 hev1;
|
||||
pub(crate) mod hdlr;
|
||||
pub(crate) mod hev1;
|
||||
pub(crate) mod ilst;
|
||||
pub(crate) mod mdhd;
|
||||
pub(crate) mod mdia;
|
||||
pub(crate) mod minf;
|
||||
pub(crate) mod moov;
|
||||
pub(crate) mod mvex;
|
||||
pub(crate) mod mehd;
|
||||
pub(crate) mod trex;
|
||||
pub(crate) mod emsg;
|
||||
pub(crate) mod moof;
|
||||
pub(crate) mod mp4a;
|
||||
pub(crate) mod mvhd;
|
||||
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;
|
||||
|
@ -86,20 +93,62 @@ pub(crate) mod stsd;
|
|||
pub(crate) mod stss;
|
||||
pub(crate) mod stsz;
|
||||
pub(crate) mod stts;
|
||||
pub(crate) mod tkhd;
|
||||
pub(crate) mod tfdt;
|
||||
pub(crate) mod tfhd;
|
||||
pub(crate) mod trak;
|
||||
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 ftyp::FtypBox;
|
||||
pub use moov::MoovBox;
|
||||
pub use moof::MoofBox;
|
||||
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;
|
||||
|
@ -107,7 +156,7 @@ pub const HEADER_EXT_SIZE: u64 = 4;
|
|||
|
||||
macro_rules! boxtype {
|
||||
($( $name:ident => $value:expr ),*) => {
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BoxType {
|
||||
$( $name, )*
|
||||
UnknownBox(u32),
|
||||
|
@ -122,9 +171,9 @@ macro_rules! boxtype {
|
|||
}
|
||||
}
|
||||
|
||||
impl Into<u32> for BoxType {
|
||||
fn into(self) -> u32 {
|
||||
match self {
|
||||
impl From<BoxType> for u32 {
|
||||
fn from(b: BoxType) -> u32 {
|
||||
match b {
|
||||
$( BoxType::$name => $value, )*
|
||||
BoxType::UnknownBox(t) => t,
|
||||
}
|
||||
|
@ -147,6 +196,7 @@ boxtype! {
|
|||
MoofBox => 0x6d6f6f66,
|
||||
TkhdBox => 0x746b6864,
|
||||
TfhdBox => 0x74666864,
|
||||
TfdtBox => 0x74666474,
|
||||
EdtsBox => 0x65647473,
|
||||
MdiaBox => 0x6d646961,
|
||||
ElstBox => 0x656c7374,
|
||||
|
@ -167,6 +217,7 @@ boxtype! {
|
|||
TrafBox => 0x74726166,
|
||||
TrunBox => 0x7472756E,
|
||||
UdtaBox => 0x75647461,
|
||||
MetaBox => 0x6d657461,
|
||||
DinfBox => 0x64696e66,
|
||||
DrefBox => 0x64726566,
|
||||
UrlBox => 0x75726C20,
|
||||
|
@ -179,7 +230,15 @@ boxtype! {
|
|||
EsdsBox => 0x65736473,
|
||||
Tx3gBox => 0x74783367,
|
||||
VpccBox => 0x76706343,
|
||||
Vp09Box => 0x76703039
|
||||
Vp09Box => 0x76703039,
|
||||
DataBox => 0x64617461,
|
||||
IlstBox => 0x696c7374,
|
||||
NameBox => 0xa96e616d,
|
||||
DayBox => 0xa9646179,
|
||||
CovrBox => 0x636f7672,
|
||||
DescBox => 0x64657363,
|
||||
WideBox => 0x77696465,
|
||||
WaveBox => 0x77617665
|
||||
}
|
||||
|
||||
pub trait Mp4Box: Sized {
|
||||
|
@ -212,7 +271,7 @@ impl BoxHeader {
|
|||
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(&mut buf)?;
|
||||
reader.read_exact(&mut buf)?;
|
||||
|
||||
// Get size.
|
||||
let s = buf[0..4].try_into().unwrap();
|
||||
|
@ -224,13 +283,20 @@ impl BoxHeader {
|
|||
|
||||
// Get largesize if size is 1
|
||||
if size == 1 {
|
||||
reader.read(&mut buf)?;
|
||||
let s = buf.try_into().unwrap();
|
||||
let largesize = u64::from_be_bytes(s);
|
||||
reader.read_exact(&mut buf)?;
|
||||
let largesize = u64::from_be_bytes(buf);
|
||||
|
||||
Ok(BoxHeader {
|
||||
name: BoxType::from(typ),
|
||||
size: largesize - HEADER_SIZE,
|
||||
|
||||
// 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 {
|
||||
|
@ -267,7 +333,7 @@ pub fn write_box_header_ext<W: Write>(w: &mut W, v: u8, f: u32) -> Result<u64> {
|
|||
}
|
||||
|
||||
pub fn box_start<R: Seek>(seeker: &mut R) -> Result<u64> {
|
||||
Ok(seeker.seek(SeekFrom::Current(0))? - HEADER_SIZE)
|
||||
Ok(seeker.stream_position()? - HEADER_SIZE)
|
||||
}
|
||||
|
||||
pub fn skip_bytes<S: Seek>(seeker: &mut S, size: u64) -> Result<()> {
|
||||
|
@ -297,48 +363,38 @@ mod value_u32 {
|
|||
use crate::types::FixedPointU16;
|
||||
use serde::{self, Serializer};
|
||||
|
||||
pub fn serialize<S>(
|
||||
fixed: &FixedPointU16,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error>
|
||||
pub fn serialize<S>(fixed: &FixedPointU16, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_u16(fixed.value())
|
||||
}
|
||||
{
|
||||
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>
|
||||
pub fn serialize<S>(fixed: &FixedPointI8, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_i8(fixed.value())
|
||||
}
|
||||
{
|
||||
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>
|
||||
pub fn serialize<S>(fixed: &FixedPointU8, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_u8(fixed.value())
|
||||
}
|
||||
{
|
||||
serializer.serialize_u8(fixed.value())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -351,4 +407,28 @@ mod tests {
|
|||
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, .. })));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use serde::{Serialize};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{mfhd::MfhdBox, traf::TrafBox};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct MoofBox {
|
||||
pub mfhd: MfhdBox,
|
||||
|
||||
|
@ -28,11 +28,11 @@ impl MoofBox {
|
|||
|
||||
impl Mp4Box for MoofBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -52,12 +52,17 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoofBox {
|
|||
let mut mfhd = None;
|
||||
let mut trafs = Vec::new();
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
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 => {
|
||||
|
@ -72,7 +77,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoofBox {
|
|||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if mfhd.is_none() {
|
||||
|
|
|
@ -1,18 +1,25 @@
|
|||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use serde::{Serialize};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::meta::MetaBox;
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{mvhd::MvhdBox, mvex::MvexBox, trak::TrakBox};
|
||||
use crate::mp4box::{mvex::MvexBox, mvhd::MvhdBox, trak::TrakBox, udta::UdtaBox};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[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 {
|
||||
|
@ -25,17 +32,23 @@ impl MoovBox {
|
|||
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 {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -53,20 +66,30 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
|
|||
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.seek(SeekFrom::Current(0))?;
|
||||
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)?);
|
||||
}
|
||||
|
@ -75,8 +98,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
|
|||
traks.push(trak);
|
||||
}
|
||||
BoxType::UdtaBox => {
|
||||
// XXX warn!()
|
||||
skip_box(reader, s)?;
|
||||
udta = Some(UdtaBox::read_box(reader, s)?);
|
||||
}
|
||||
_ => {
|
||||
// XXX warn!()
|
||||
|
@ -84,7 +106,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
|
|||
}
|
||||
}
|
||||
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if mvhd.is_none() {
|
||||
|
@ -95,6 +117,8 @@ impl<R: Read + Seek> ReadBox<&mut R> for MoovBox {
|
|||
|
||||
Ok(MoovBox {
|
||||
mvhd: mvhd.unwrap(),
|
||||
meta,
|
||||
udta,
|
||||
mvex,
|
||||
traks,
|
||||
})
|
||||
|
@ -110,6 +134,59 @@ impl<W: Write> WriteBox<&mut W> for MoovBox {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Mp4aBox {
|
||||
pub data_reference_index: u16,
|
||||
pub channelcount: u16,
|
||||
|
@ -65,8 +65,12 @@ impl Mp4Box for Mp4aBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("channel_count={} sample_size={} sample_rate={}",
|
||||
self.channelcount, self.samplesize, self.samplerate.value());
|
||||
let s = format!(
|
||||
"channel_count={} sample_size={} sample_rate={}",
|
||||
self.channelcount,
|
||||
self.samplesize,
|
||||
self.samplerate.value()
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -78,21 +82,48 @@ impl<R: Read + Seek> ReadBox<&mut R> for Mp4aBox {
|
|||
reader.read_u32::<BigEndian>()?; // reserved
|
||||
reader.read_u16::<BigEndian>()?; // reserved
|
||||
let data_reference_index = reader.read_u16::<BigEndian>()?;
|
||||
|
||||
reader.read_u64::<BigEndian>()?; // reserved
|
||||
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>()?);
|
||||
|
||||
let header = BoxHeader::read(reader)?;
|
||||
let BoxHeader { name, size: s } = header;
|
||||
|
||||
let mut esds = None;
|
||||
if name == BoxType::EsdsBox {
|
||||
esds = Some(EsdsBox::read_box(reader, s)?);
|
||||
if version == 1 {
|
||||
// Skip QTFF
|
||||
reader.read_u64::<BigEndian>()?;
|
||||
reader.read_u64::<BigEndian>()?;
|
||||
}
|
||||
skip_bytes_to(reader, start + size)?;
|
||||
|
||||
// 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,
|
||||
|
@ -127,7 +158,7 @@ impl<W: Write> WriteBox<&mut W> for Mp4aBox {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct EsdsBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -150,8 +181,11 @@ impl Mp4Box for 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
|
||||
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> {
|
||||
|
@ -171,7 +205,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for EsdsBox {
|
|||
|
||||
let mut es_desc = None;
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size;
|
||||
while current < end {
|
||||
let (desc_tag, desc_size) = read_desc(reader)?;
|
||||
|
@ -181,7 +215,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for EsdsBox {
|
|||
}
|
||||
_ => break,
|
||||
}
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if es_desc.is_none() {
|
||||
|
@ -268,7 +302,7 @@ fn write_desc<W: Write>(writer: &mut W, tag: u8, size: u32) -> Result<u64> {
|
|||
Ok(1 + nbytes as u64)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct ESDescriptor {
|
||||
pub es_id: u16,
|
||||
|
||||
|
@ -292,15 +326,18 @@ impl Descriptor for ESDescriptor {
|
|||
}
|
||||
|
||||
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()
|
||||
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.seek(SeekFrom::Current(0))?;
|
||||
let start = reader.stream_position()?;
|
||||
|
||||
let es_id = reader.read_u16::<BigEndian>()?;
|
||||
reader.read_u8()?; // XXX flags must be 0
|
||||
|
@ -308,7 +345,7 @@ impl<R: Read + Seek> ReadDesc<&mut R> for ESDescriptor {
|
|||
let mut dec_config = None;
|
||||
let mut sl_config = None;
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size as u64;
|
||||
while current < end {
|
||||
let (desc_tag, desc_size) = read_desc(reader)?;
|
||||
|
@ -323,13 +360,13 @@ impl<R: Read + Seek> ReadDesc<&mut R> for ESDescriptor {
|
|||
skip_bytes(reader, desc_size as u64)?;
|
||||
}
|
||||
}
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
Ok(ESDescriptor {
|
||||
es_id,
|
||||
dec_config: dec_config.unwrap_or(DecoderConfigDescriptor::default()),
|
||||
sl_config: sl_config.unwrap_or(SLConfigDescriptor::default()),
|
||||
dec_config: dec_config.unwrap_or_default(),
|
||||
sl_config: sl_config.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -349,7 +386,7 @@ impl<W: Write> WriteDesc<&mut W> for ESDescriptor {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct DecoderConfigDescriptor {
|
||||
pub object_type_indication: u8,
|
||||
pub stream_type: u8,
|
||||
|
@ -381,13 +418,15 @@ impl Descriptor for DecoderConfigDescriptor {
|
|||
}
|
||||
|
||||
fn desc_size() -> u32 {
|
||||
13 + 1 + size_of_length(DecoderSpecificDescriptor::desc_size()) + DecoderSpecificDescriptor::desc_size()
|
||||
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.seek(SeekFrom::Current(0))?;
|
||||
let start = reader.stream_position()?;
|
||||
|
||||
let object_type_indication = reader.read_u8()?;
|
||||
let byte_a = reader.read_u8()?;
|
||||
|
@ -399,7 +438,7 @@ impl<R: Read + Seek> ReadDesc<&mut R> for DecoderConfigDescriptor {
|
|||
|
||||
let mut dec_specific = None;
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
let mut current = reader.stream_position()?;
|
||||
let end = start + size as u64;
|
||||
while current < end {
|
||||
let (desc_tag, desc_size) = read_desc(reader)?;
|
||||
|
@ -411,7 +450,7 @@ impl<R: Read + Seek> ReadDesc<&mut R> for DecoderConfigDescriptor {
|
|||
skip_bytes(reader, desc_size as u64)?;
|
||||
}
|
||||
}
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
Ok(DecoderConfigDescriptor {
|
||||
|
@ -421,7 +460,7 @@ impl<R: Read + Seek> ReadDesc<&mut R> for DecoderConfigDescriptor {
|
|||
buffer_size_db,
|
||||
max_bitrate,
|
||||
avg_bitrate,
|
||||
dec_specific: dec_specific.unwrap_or(DecoderSpecificDescriptor::default()),
|
||||
dec_specific: dec_specific.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -443,7 +482,7 @@ impl<W: Write> WriteDesc<&mut W> for DecoderConfigDescriptor {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct DecoderSpecificDescriptor {
|
||||
pub profile: u8,
|
||||
pub freq_index: u8,
|
||||
|
@ -479,7 +518,12 @@ fn get_audio_object_type(byte_a: u8, byte_b: u8) -> u8 {
|
|||
profile
|
||||
}
|
||||
|
||||
fn get_chan_conf<R: Read + Seek>(reader: &mut R, byte_b: u8, freq_index: u8, extended_profile: bool) -> Result<u8> {
|
||||
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
|
||||
|
@ -530,7 +574,7 @@ impl<W: Write> WriteDesc<&mut W> for DecoderSpecificDescriptor {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct SLConfigDescriptor {}
|
||||
|
||||
impl SLConfigDescriptor {
|
||||
|
@ -560,9 +604,9 @@ impl<R: Read + Seek> ReadDesc<&mut R> for 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 - 1)?;
|
||||
write_desc(writer, Self::desc_tag(), size)?;
|
||||
|
||||
writer.write_u8(0)?; // pre-defined
|
||||
writer.write_u8(2)?; // pre-defined
|
||||
Ok(size)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use serde::{Serialize};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{mehd::MehdBox, trex::TrexBox};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct MvexBox {
|
||||
pub mehd: Option<MehdBox>,
|
||||
pub trex: TrexBox,
|
||||
|
@ -22,11 +22,11 @@ impl MvexBox {
|
|||
|
||||
impl Mp4Box for MvexBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -34,7 +34,7 @@ impl Mp4Box for MvexBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("");
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -46,12 +46,17 @@ impl<R: Read + Seek> ReadBox<&mut R> for MvexBox {
|
|||
let mut mehd = None;
|
||||
let mut trex = None;
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
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 => {
|
||||
|
@ -66,7 +71,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for MvexBox {
|
|||
}
|
||||
}
|
||||
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if trex.is_none() {
|
||||
|
@ -87,7 +92,7 @@ impl<W: Write> WriteBox<&mut W> for MvexBox {
|
|||
let size = self.box_size();
|
||||
BoxHeader::new(self.box_type(), size).write(writer)?;
|
||||
|
||||
if let Some(mehd) = &self.mehd{
|
||||
if let Some(mehd) = &self.mehd {
|
||||
mehd.write_box(writer)?;
|
||||
}
|
||||
self.trex.write_box(writer)?;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct MvhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -15,6 +15,12 @@ pub struct MvhdBox {
|
|||
|
||||
#[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 {
|
||||
|
@ -44,17 +50,20 @@ impl Default for MvhdBox {
|
|||
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 {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -62,8 +71,16 @@ impl Mp4Box for MvhdBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("creation_time={} timescale={} duration={} rate={}",
|
||||
self.creation_time, self.timescale, self.duration, self.rate.value());
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +110,28 @@ impl<R: Read + Seek> ReadBox<&mut R> for MvhdBox {
|
|||
};
|
||||
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 {
|
||||
|
@ -103,6 +142,9 @@ impl<R: Read + Seek> ReadBox<&mut R> for MvhdBox {
|
|||
timescale,
|
||||
duration,
|
||||
rate,
|
||||
volume,
|
||||
matrix,
|
||||
next_track_id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -129,8 +171,25 @@ impl<W: Write> WriteBox<&mut W> for MvhdBox {
|
|||
}
|
||||
writer.write_u32::<BigEndian>(self.rate.raw_value())?;
|
||||
|
||||
// XXX volume, ...
|
||||
write_zeros(writer, 76)?;
|
||||
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)
|
||||
}
|
||||
|
@ -152,6 +211,9 @@ mod tests {
|
|||
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();
|
||||
|
@ -176,6 +238,9 @@ mod tests {
|
|||
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();
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct SmhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -35,11 +35,11 @@ impl Default for SmhdBox {
|
|||
|
||||
impl Mp4Box for SmhdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
|
|
@ -1,19 +1,13 @@
|
|||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use serde::{Serialize};
|
||||
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,
|
||||
co64::Co64Box, ctts::CttsBox, stco::StcoBox, stsc::StscBox, stsd::StsdBox, stss::StssBox,
|
||||
stsz::StszBox, stts::SttsBox,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct StblBox {
|
||||
pub stsd: StsdBox,
|
||||
pub stts: SttsBox,
|
||||
|
@ -62,11 +56,11 @@ impl StblBox {
|
|||
|
||||
impl Mp4Box for StblBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -74,7 +68,7 @@ impl Mp4Box for StblBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("");
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -92,12 +86,17 @@ impl<R: Read + Seek> ReadBox<&mut R> for StblBox {
|
|||
let mut stco = None;
|
||||
let mut co64 = None;
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
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 => {
|
||||
|
@ -129,7 +128,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for StblBox {
|
|||
skip_box(reader, s)?;
|
||||
}
|
||||
}
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if stsd.is_none() {
|
||||
|
@ -153,12 +152,12 @@ impl<R: Read + Seek> ReadBox<&mut R> for StblBox {
|
|||
Ok(StblBox {
|
||||
stsd: stsd.unwrap(),
|
||||
stts: stts.unwrap(),
|
||||
ctts: ctts,
|
||||
stss: stss,
|
||||
ctts,
|
||||
stss,
|
||||
stsc: stsc.unwrap(),
|
||||
stsz: stsz.unwrap(),
|
||||
stco: stco,
|
||||
co64: co64,
|
||||
stco,
|
||||
co64,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct StcoBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -25,11 +26,11 @@ impl StcoBox {
|
|||
|
||||
impl Mp4Box for StcoBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -48,7 +49,20 @@ impl<R: Read + Seek> ReadBox<&mut R> for StcoBox {
|
|||
|
||||
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>()?;
|
||||
|
@ -81,6 +95,24 @@ impl<W: Write> WriteBox<&mut W> for StcoBox {
|
|||
}
|
||||
}
|
||||
|
||||
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::*;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct StscBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -23,7 +24,7 @@ impl StscBox {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct StscEntry {
|
||||
pub first_chunk: u32,
|
||||
pub samples_per_chunk: u32,
|
||||
|
@ -33,11 +34,11 @@ pub struct StscEntry {
|
|||
|
||||
impl Mp4Box for StscBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -56,7 +57,20 @@ impl<R: Read + Seek> ReadBox<&mut R> for StscBox {
|
|||
|
||||
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 {
|
||||
|
@ -71,13 +85,20 @@ impl<R: Read + Seek> ReadBox<&mut R> for StscBox {
|
|||
let mut sample_id = 1;
|
||||
for i in 0..entry_count {
|
||||
let (first_chunk, samples_per_chunk) = {
|
||||
let mut entry = entries.get_mut(i as usize).unwrap();
|
||||
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 - first_chunk) * samples_per_chunk;
|
||||
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",
|
||||
))?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::vp09::Vp09Box;
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{avc1::Avc1Box, hev1::Hev1Box, mp4a::Mp4aBox, tx3g::Tx3gBox};
|
||||
use crate::mp4box::vp09::Vp09Box;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct StsdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -16,7 +16,7 @@ pub struct StsdBox {
|
|||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub hev1: Option<Hev1Box>,
|
||||
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub vp09: Option<Vp09Box>,
|
||||
|
||||
|
@ -51,11 +51,11 @@ impl StsdBox {
|
|||
|
||||
impl Mp4Box for StsdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -63,7 +63,7 @@ impl Mp4Box for StsdBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("");
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -85,6 +85,11 @@ impl<R: Read + Seek> ReadBox<&mut R> for StsdBox {
|
|||
// 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 => {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct StssBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -25,11 +26,11 @@ impl StssBox {
|
|||
|
||||
impl Mp4Box for StssBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -48,7 +49,20 @@ impl<R: Read + Seek> ReadBox<&mut R> for StssBox {
|
|||
|
||||
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>()?;
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct StszBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -27,11 +28,11 @@ impl StszBox {
|
|||
|
||||
impl Mp4Box for StszBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -39,8 +40,12 @@ impl Mp4Box for StszBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("sample_size={} sample_count={} sample_sizes={}",
|
||||
self.sample_size, self.sample_count, self.sample_sizes.len());
|
||||
let s = format!(
|
||||
"sample_size={} sample_count={} sample_sizes={}",
|
||||
self.sample_size,
|
||||
self.sample_count,
|
||||
self.sample_sizes.len()
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -51,10 +56,28 @@ impl<R: Read + Seek> ReadBox<&mut R> for StszBox {
|
|||
|
||||
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::with_capacity(sample_count as usize);
|
||||
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);
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct SttsBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -23,7 +24,7 @@ impl SttsBox {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct SttsEntry {
|
||||
pub sample_count: u32,
|
||||
pub sample_delta: u32,
|
||||
|
@ -31,11 +32,11 @@ pub struct SttsEntry {
|
|||
|
||||
impl Mp4Box for SttsBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -54,7 +55,20 @@ impl<R: Read + Seek> ReadBox<&mut R> for SttsBox {
|
|||
|
||||
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 {
|
||||
|
|
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);
|
||||
}
|
||||
}
|
|
@ -1,45 +1,62 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)]
|
||||
pub struct TfhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub track_id: u32,
|
||||
pub base_data_offset: u64,
|
||||
}
|
||||
|
||||
impl Default for TfhdBox {
|
||||
fn default() -> Self {
|
||||
TfhdBox {
|
||||
version: 0,
|
||||
flags: 0,
|
||||
track_id: 0,
|
||||
base_data_offset: 0,
|
||||
}
|
||||
}
|
||||
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 {
|
||||
HEADER_SIZE + HEADER_EXT_SIZE + 4 + 8
|
||||
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 {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -58,7 +75,31 @@ impl<R: Read + Seek> ReadBox<&mut R> for TfhdBox {
|
|||
|
||||
let (version, flags) = read_box_header_ext(reader)?;
|
||||
let track_id = reader.read_u32::<BigEndian>()?;
|
||||
let base_data_offset = reader.read_u64::<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)?;
|
||||
|
||||
|
@ -67,6 +108,10 @@ impl<R: Read + Seek> ReadBox<&mut R> for TfhdBox {
|
|||
flags,
|
||||
track_id,
|
||||
base_data_offset,
|
||||
sample_description_index,
|
||||
default_sample_duration,
|
||||
default_sample_size,
|
||||
default_sample_flags,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +123,21 @@ impl<W: Write> WriteBox<&mut W> for TfhdBox {
|
|||
|
||||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
writer.write_u32::<BigEndian>(self.track_id)?;
|
||||
writer.write_u64::<BigEndian>(self.base_data_offset)?;
|
||||
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)
|
||||
}
|
||||
|
@ -96,7 +155,38 @@ mod tests {
|
|||
version: 0,
|
||||
flags: 0,
|
||||
track_id: 1,
|
||||
base_data_offset: 0,
|
||||
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();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
|
@ -10,7 +10,7 @@ pub enum TrackFlag {
|
|||
// TrackInPreview = 0x000004,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct TkhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -51,7 +51,7 @@ impl Default for TkhdBox {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Matrix {
|
||||
pub a: i32,
|
||||
pub b: i32,
|
||||
|
@ -64,6 +64,33 @@ pub struct Matrix {
|
|||
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
|
||||
|
@ -91,11 +118,11 @@ impl TkhdBox {
|
|||
|
||||
impl Mp4Box for TkhdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -103,9 +130,17 @@ impl Mp4Box for TkhdBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("creation_time={} track_id={} duration={} layer={} volume={} width={} height={}",
|
||||
self.creation_time, self.track_id, self.duration, self.layer,
|
||||
self.volume.value(), self.width.value(), self.height.value());
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +177,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for TkhdBox {
|
|||
|
||||
reader.read_u16::<BigEndian>()?; // reserved
|
||||
let matrix = Matrix {
|
||||
a: reader.read_i32::<byteorder::LittleEndian>()?,
|
||||
a: reader.read_i32::<BigEndian>()?,
|
||||
b: reader.read_i32::<BigEndian>()?,
|
||||
u: reader.read_i32::<BigEndian>()?,
|
||||
c: reader.read_i32::<BigEndian>()?,
|
||||
|
@ -205,7 +240,7 @@ impl<W: Write> WriteBox<&mut W> for TkhdBox {
|
|||
|
||||
writer.write_u16::<BigEndian>(0)?; // reserved
|
||||
|
||||
writer.write_i32::<byteorder::LittleEndian>(self.matrix.a)?;
|
||||
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)?;
|
||||
|
@ -240,17 +275,7 @@ mod tests {
|
|||
layer: 0,
|
||||
alternate_group: 0,
|
||||
volume: FixedPointU8::new(1),
|
||||
matrix: Matrix {
|
||||
a: 0x00010000,
|
||||
b: 0,
|
||||
u: 0,
|
||||
c: 0,
|
||||
d: 0x00010000,
|
||||
v: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 0x40000000,
|
||||
},
|
||||
matrix: Matrix::default(),
|
||||
width: FixedPointU16::new(512),
|
||||
height: FixedPointU16::new(288),
|
||||
};
|
||||
|
@ -279,17 +304,7 @@ mod tests {
|
|||
layer: 0,
|
||||
alternate_group: 0,
|
||||
volume: FixedPointU8::new(1),
|
||||
matrix: Matrix {
|
||||
a: 0x00010000,
|
||||
b: 0,
|
||||
u: 0,
|
||||
c: 0,
|
||||
d: 0x00010000,
|
||||
v: 0,
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 0x40000000,
|
||||
},
|
||||
matrix: Matrix::default(),
|
||||
width: FixedPointU16::new(512),
|
||||
height: FixedPointU16::new(288),
|
||||
};
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use serde::{Serialize};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::{tfhd::TfhdBox, trun::TrunBox};
|
||||
use crate::mp4box::{tfdt::TfdtBox, tfhd::TfhdBox, trun::TrunBox};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct TrafBox {
|
||||
pub tfhd: TfhdBox,
|
||||
pub tfdt: Option<TfdtBox>,
|
||||
pub trun: Option<TrunBox>,
|
||||
}
|
||||
|
||||
|
@ -18,6 +19,9 @@ impl 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();
|
||||
}
|
||||
|
@ -27,11 +31,11 @@ impl TrafBox {
|
|||
|
||||
impl Mp4Box for TrafBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -39,7 +43,7 @@ impl Mp4Box for TrafBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("");
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -49,19 +53,28 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrafBox {
|
|||
let start = box_start(reader)?;
|
||||
|
||||
let mut tfhd = None;
|
||||
let mut tfdt = None;
|
||||
let mut trun = None;
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
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)?);
|
||||
}
|
||||
|
@ -71,7 +84,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrafBox {
|
|||
}
|
||||
}
|
||||
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if tfhd.is_none() {
|
||||
|
@ -82,6 +95,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrafBox {
|
|||
|
||||
Ok(TrafBox {
|
||||
tfhd: tfhd.unwrap(),
|
||||
tfdt,
|
||||
trun,
|
||||
})
|
||||
}
|
||||
|
@ -93,6 +107,12 @@ impl<W: Write> WriteBox<&mut W> for TrafBox {
|
|||
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)
|
||||
}
|
||||
|
|
|
@ -1,16 +1,20 @@
|
|||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use serde::{Serialize};
|
||||
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, Default, Serialize)]
|
||||
#[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,
|
||||
}
|
||||
|
||||
|
@ -32,11 +36,11 @@ impl TrakBox {
|
|||
|
||||
impl Mp4Box for TrakBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -44,7 +48,7 @@ impl Mp4Box for TrakBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("");
|
||||
let s = String::new();
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -55,14 +59,20 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrakBox {
|
|||
|
||||
let mut tkhd = None;
|
||||
let mut edts = None;
|
||||
let mut meta = None;
|
||||
let mut mdia = None;
|
||||
|
||||
let mut current = reader.seek(SeekFrom::Current(0))?;
|
||||
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 => {
|
||||
|
@ -71,6 +81,9 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrakBox {
|
|||
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)?);
|
||||
}
|
||||
|
@ -80,7 +93,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrakBox {
|
|||
}
|
||||
}
|
||||
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if tkhd.is_none() {
|
||||
|
@ -95,6 +108,7 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrakBox {
|
|||
Ok(TrakBox {
|
||||
tkhd: tkhd.unwrap(),
|
||||
edts,
|
||||
meta,
|
||||
mdia: mdia.unwrap(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[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,
|
||||
pub default_sample_description_index: u32,
|
||||
pub default_sample_duration: u32,
|
||||
pub default_sample_size: u32,
|
||||
pub default_sample_flags: u32,
|
||||
}
|
||||
|
||||
impl TrexBox {
|
||||
|
@ -27,11 +27,11 @@ impl TrexBox {
|
|||
|
||||
impl Mp4Box for TrexBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -39,8 +39,10 @@ impl Mp4Box for TrexBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("track_id={} default_sample_duration={}",
|
||||
self.track_id, self.default_sample_duration);
|
||||
let s = format!(
|
||||
"track_id={} default_sample_duration={}",
|
||||
self.track_id, self.default_sample_duration
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
use std::mem::size_of;
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct TrunBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -60,11 +61,11 @@ impl TrunBox {
|
|||
|
||||
impl Mp4Box for TrunBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -72,8 +73,7 @@ impl Mp4Box for TrunBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
let s = format!("sample_size={}",
|
||||
self.sample_count);
|
||||
let s = format!("sample_size={}", self.sample_count);
|
||||
Ok(s)
|
||||
}
|
||||
}
|
||||
|
@ -84,6 +84,15 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrunBox {
|
|||
|
||||
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 {
|
||||
|
@ -98,10 +107,32 @@ impl<R: Read + Seek> ReadBox<&mut R> for TrunBox {
|
|||
None
|
||||
};
|
||||
|
||||
let mut sample_durations = Vec::with_capacity(sample_count as usize);
|
||||
let mut sample_sizes = Vec::with_capacity(sample_count as usize);
|
||||
let mut sample_flags = Vec::with_capacity(sample_count as usize);
|
||||
let mut sample_cts = Vec::with_capacity(sample_count as usize);
|
||||
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>()?;
|
||||
|
@ -148,7 +179,7 @@ impl<W: Write> WriteBox<&mut W> for TrunBox {
|
|||
write_box_header_ext(writer, self.version, self.flags)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.sample_count)?;
|
||||
if let Some(v) = self.data_offset{
|
||||
if let Some(v) = self.data_offset {
|
||||
writer.write_i32::<BigEndian>(v)?;
|
||||
}
|
||||
if let Some(v) = self.first_sample_flags {
|
||||
|
@ -212,7 +243,10 @@ mod tests {
|
|||
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,
|
||||
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],
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
|
||||
pub struct Tx3gBox {
|
||||
pub data_reference_index: u16,
|
||||
pub display_flags: u32,
|
||||
|
@ -15,12 +15,12 @@ pub struct Tx3gBox {
|
|||
pub style_record: [u8; 12],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct RgbaColor {
|
||||
pub red: u8,
|
||||
pub green: u8,
|
||||
pub blue: u8,
|
||||
pub alpha: u8
|
||||
pub alpha: u8,
|
||||
}
|
||||
|
||||
impl Default for Tx3gBox {
|
||||
|
@ -30,7 +30,7 @@ impl Default for Tx3gBox {
|
|||
display_flags: 0,
|
||||
horizontal_justification: 1,
|
||||
vertical_justification: -1,
|
||||
bg_color_rgba: RgbaColor{
|
||||
bg_color_rgba: RgbaColor {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0,
|
||||
|
@ -48,17 +48,17 @@ impl Tx3gBox {
|
|||
}
|
||||
|
||||
pub fn get_size(&self) -> u64 {
|
||||
HEADER_SIZE + 6 + 32
|
||||
HEADER_SIZE + 6 + 32
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Box for Tx3gBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -165,7 +165,7 @@ mod tests {
|
|||
display_flags: 0,
|
||||
horizontal_justification: 1,
|
||||
vertical_justification: -1,
|
||||
bg_color_rgba: RgbaColor{
|
||||
bg_color_rgba: RgbaColor {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0,
|
||||
|
|
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);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,10 @@
|
|||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use serde::Serialize;
|
||||
use std::io::{Read, Seek, Write};
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct VmhdBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -12,7 +12,7 @@ pub struct VmhdBox {
|
|||
pub op_color: RgbColor,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct RgbColor {
|
||||
pub red: u16,
|
||||
pub green: u16,
|
||||
|
@ -31,11 +31,11 @@ impl VmhdBox {
|
|||
|
||||
impl Mp4Box for VmhdBox {
|
||||
fn box_type(&self) -> BoxType {
|
||||
return self.get_type();
|
||||
self.get_type()
|
||||
}
|
||||
|
||||
fn box_size(&self) -> u64 {
|
||||
return self.get_size();
|
||||
self.get_size()
|
||||
}
|
||||
|
||||
fn to_json(&self) -> Result<String> {
|
||||
|
@ -43,11 +43,9 @@ impl Mp4Box for VmhdBox {
|
|||
}
|
||||
|
||||
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
|
||||
let s = format!(
|
||||
"graphics_mode={} op_color={}{}{}",
|
||||
self.graphics_mode, self.op_color.red, self.op_color.green, self.op_color.blue
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::Mp4Box;
|
||||
use crate::mp4box::*;
|
||||
use serde::{Serialize};
|
||||
use crate::mp4box::vpcc::VpccBox;
|
||||
use crate::mp4box::*;
|
||||
use crate::Mp4Box;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct Vp09Box {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -79,7 +79,7 @@ impl Mp4Box for Vp09Box {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
Ok(format!("{:?}", self))
|
||||
Ok(format!("{self:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,8 +97,14 @@ impl<R: Read + Seek> ReadBox<&mut R> for Vp09Box {
|
|||
};
|
||||
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 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)?;
|
||||
|
@ -115,6 +121,11 @@ impl<R: Read + Seek> ReadBox<&mut R> for Vp09Box {
|
|||
|
||||
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)?
|
||||
};
|
||||
|
||||
|
@ -175,7 +186,10 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn test_vpcc() {
|
||||
let src_box = Vp09Box::new(&Vp9Config{ width: 1920, height: 1080 });
|
||||
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);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use crate::Mp4Box;
|
||||
use crate::mp4box::*;
|
||||
use serde::{Serialize};
|
||||
use crate::Mp4Box;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default, Serialize)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize)]
|
||||
pub struct VpccBox {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
|
@ -36,7 +36,7 @@ impl Mp4Box for VpccBox {
|
|||
}
|
||||
|
||||
fn summary(&self) -> Result<String> {
|
||||
Ok(format!("{:?}", self))
|
||||
Ok(format!("{self:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,7 +82,11 @@ impl<W: Write> WriteBox<&mut W> for VpccBox {
|
|||
|
||||
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.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)?;
|
||||
|
@ -125,4 +129,4 @@ mod tests {
|
|||
let dst_box = VpccBox::read_box(&mut reader, header.size).unwrap();
|
||||
assert_eq!(src_box, dst_box);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
131
src/reader.rs
131
src/reader.rs
|
@ -1,9 +1,9 @@
|
|||
use std::collections::HashMap;
|
||||
use std::io::{Read, Seek, SeekFrom};
|
||||
use std::io::{Read, Seek};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::meta::MetaBox;
|
||||
use crate::*;
|
||||
use crate::mp4box::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Mp4Reader<R> {
|
||||
|
@ -19,11 +19,12 @@ pub struct Mp4Reader<R> {
|
|||
|
||||
impl<R: Read + Seek> Mp4Reader<R> {
|
||||
pub fn read_header(mut reader: R, size: u64) -> Result<Self> {
|
||||
let start = reader.seek(SeekFrom::Current(0))?;
|
||||
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;
|
||||
|
@ -31,6 +32,11 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
// 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 {
|
||||
|
@ -52,8 +58,10 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
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)?;
|
||||
|
@ -64,7 +72,7 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
skip_box(&mut reader, s)?;
|
||||
}
|
||||
}
|
||||
current = reader.seek(SeekFrom::Current(0))?;
|
||||
current = reader.stream_position()?;
|
||||
}
|
||||
|
||||
if ftyp.is_none() {
|
||||
|
@ -79,7 +87,8 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
if moov.traks.iter().any(|trak| trak.tkhd.track_id == 0) {
|
||||
return Err(Error::InvalidData("illegal track id 0"));
|
||||
}
|
||||
moov.traks.iter()
|
||||
moov.traks
|
||||
.iter()
|
||||
.map(|trak| (trak.tkhd.track_id, Mp4Track::from(trak)))
|
||||
.collect()
|
||||
} else {
|
||||
|
@ -87,7 +96,7 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
};
|
||||
|
||||
// Update tracks if any fragmented (moof) boxes are found.
|
||||
if moofs.len() > 0 {
|
||||
if !moofs.is_empty() {
|
||||
let mut default_sample_duration = 0;
|
||||
if let Some(ref moov) = moov {
|
||||
if let Some(ref mvex) = &moov.mvex {
|
||||
|
@ -95,11 +104,12 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
}
|
||||
}
|
||||
|
||||
for moof in moofs.iter() {
|
||||
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));
|
||||
|
@ -119,6 +129,92 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -144,7 +240,7 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
}
|
||||
|
||||
pub fn is_fragmented(&self) -> bool {
|
||||
self.moofs.len() != 0
|
||||
!self.moofs.is_empty()
|
||||
}
|
||||
|
||||
pub fn tracks(&self) -> &HashMap<u32, Mp4Track> {
|
||||
|
@ -166,4 +262,23 @@ impl<R: Read + Seek> Mp4Reader<R> {
|
|||
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,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
285
src/track.rs
285
src/track.rs
|
@ -4,27 +4,17 @@ use std::convert::TryFrom;
|
|||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::time::Duration;
|
||||
|
||||
use crate::mp4box::trak::TrakBox;
|
||||
use crate::mp4box::traf::TrafBox;
|
||||
use crate::mp4box::*;
|
||||
use crate::mp4box::trak::TrakBox;
|
||||
use crate::mp4box::trun::TrunBox;
|
||||
use crate::mp4box::{
|
||||
avc1::Avc1Box,
|
||||
hev1::Hev1Box,
|
||||
vp09::Vp09Box,
|
||||
ctts::CttsBox,
|
||||
ctts::CttsEntry,
|
||||
mp4a::Mp4aBox,
|
||||
smhd::SmhdBox,
|
||||
stco::StcoBox,
|
||||
stsc::StscEntry,
|
||||
stss::StssBox,
|
||||
stts::SttsEntry,
|
||||
tx3g::Tx3gBox,
|
||||
vmhd::VmhdBox,
|
||||
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)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct TrackConfig {
|
||||
pub track_type: TrackType,
|
||||
pub timescale: u32,
|
||||
|
@ -103,6 +93,7 @@ impl From<Vp9Config> for TrackConfig {
|
|||
pub struct Mp4Track {
|
||||
pub trak: TrakBox,
|
||||
pub trafs: Vec<TrafBox>,
|
||||
pub moof_offsets: Vec<u64>,
|
||||
|
||||
// Fragmented Tracks Defaults.
|
||||
pub default_sample_duration: u32,
|
||||
|
@ -111,7 +102,12 @@ pub struct Mp4Track {
|
|||
impl Mp4Track {
|
||||
pub(crate) fn from(trak: &TrakBox) -> Self {
|
||||
let trak = trak.clone();
|
||||
Self { trak, trafs: Vec::new(), default_sample_duration: 0, }
|
||||
Self {
|
||||
trak,
|
||||
trafs: Vec::new(),
|
||||
moof_offsets: Vec::new(),
|
||||
default_sample_duration: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn track_id(&self) -> u32 {
|
||||
|
@ -171,11 +167,11 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
pub fn frame_rate(&self) -> f64 {
|
||||
let dur_msec = self.duration().as_millis() as u64;
|
||||
if dur_msec > 0 {
|
||||
((self.sample_count() as u64 * 1000) / dur_msec) as f64
|
||||
} else {
|
||||
let dur = self.duration();
|
||||
if dur.is_zero() {
|
||||
0.0
|
||||
} else {
|
||||
self.sample_count() as f64 / dur.as_secs_f64()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,22 +222,24 @@ impl Mp4Track {
|
|||
}
|
||||
// mp4a.esds.es_desc.dec_config.avg_bitrate
|
||||
} else {
|
||||
let dur_sec = self.duration().as_secs();
|
||||
if dur_sec > 0 {
|
||||
let bitrate = self.total_sample_size() * 8 / dur_sec;
|
||||
bitrate as u32
|
||||
} 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.len() > 0 {
|
||||
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 += trun.sample_count;
|
||||
sample_count = sample_count
|
||||
.checked_add(trun.sample_count)
|
||||
.expect("attempt to sum trun sample_count with overflow");
|
||||
}
|
||||
}
|
||||
sample_count
|
||||
|
@ -264,7 +262,7 @@ impl Mp4Track {
|
|||
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(ref nal) => Ok(nal.bytes.as_ref()),
|
||||
Some(nal) => Ok(nal.bytes.as_ref()),
|
||||
None => Err(Error::EntryInStblNotFound(
|
||||
self.track_id(),
|
||||
BoxType::AvcCBox,
|
||||
|
@ -279,7 +277,7 @@ impl Mp4Track {
|
|||
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(ref nal) => Ok(nal.bytes.as_ref()),
|
||||
Some(nal) => Ok(nal.bytes.as_ref()),
|
||||
None => Err(Error::EntryInStblNotFound(
|
||||
self.track_id(),
|
||||
BoxType::AvcCBox,
|
||||
|
@ -344,28 +342,34 @@ impl Mp4Track {
|
|||
));
|
||||
}
|
||||
}
|
||||
return Err(Error::Box2NotFound(BoxType::StcoBox, BoxType::Co64Box));
|
||||
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 = 1;
|
||||
let mut sample_count: u32 = 1;
|
||||
for (i, entry) in ctts.entries.iter().enumerate() {
|
||||
if sample_id <= sample_count + entry.sample_count - 1 {
|
||||
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 += entry.sample_count;
|
||||
sample_count = next_sample_count;
|
||||
}
|
||||
|
||||
return Err(Error::EntryInStblNotFound(
|
||||
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)>{
|
||||
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() {
|
||||
|
@ -374,16 +378,24 @@ impl Mp4Track {
|
|||
if sample_count > (global_idx - offset) {
|
||||
return Some((traf_idx, (global_idx - offset) as _));
|
||||
}
|
||||
offset += sample_count;
|
||||
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.len() > 0 {
|
||||
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) {
|
||||
if let Some(size) = self.trafs[traf_idx]
|
||||
.trun
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.sample_sizes
|
||||
.get(sample_idx)
|
||||
{
|
||||
Ok(*size)
|
||||
} else {
|
||||
Err(Error::EntryInTrunNotFound(
|
||||
|
@ -393,10 +405,7 @@ impl Mp4Track {
|
|||
))
|
||||
}
|
||||
} else {
|
||||
Err(Error::BoxInTrafNotFound(
|
||||
self.track_id(),
|
||||
BoxType::TrafBox,
|
||||
))
|
||||
Err(Error::BoxInTrafNotFound(self.track_id(), BoxType::TrafBox))
|
||||
}
|
||||
} else {
|
||||
let stsz = &self.trak.mdia.minf.stbl.stsz;
|
||||
|
@ -406,11 +415,11 @@ impl Mp4Track {
|
|||
if let Some(size) = stsz.sample_sizes.get(sample_id as usize - 1) {
|
||||
Ok(*size)
|
||||
} else {
|
||||
return Err(Error::EntryInStblNotFound(
|
||||
Err(Error::EntryInStblNotFound(
|
||||
self.track_id(),
|
||||
BoxType::StszBox,
|
||||
sample_id,
|
||||
));
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -428,15 +437,36 @@ impl Mp4Track {
|
|||
}
|
||||
}
|
||||
|
||||
fn sample_offset(&self, sample_id: u32) -> Result<u64> {
|
||||
if self.trafs.len() > 0 {
|
||||
if let Some((traf_idx, _sample_idx)) = self.find_traf_idx_and_sample_idx(sample_id) {
|
||||
Ok(self.trafs[traf_idx].tfhd.base_data_offset as u64)
|
||||
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,
|
||||
))
|
||||
Err(Error::BoxInTrafNotFound(self.track_id(), BoxType::TrafBox))
|
||||
}
|
||||
} else {
|
||||
let stsc_index = self.stsc_index(sample_id)?;
|
||||
|
@ -448,7 +478,13 @@ impl Mp4Track {
|
|||
let first_sample = stsc_entry.first_sample;
|
||||
let samples_per_chunk = stsc_entry.samples_per_chunk;
|
||||
|
||||
let chunk_id = first_chunk + (sample_id - first_sample) / 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)?;
|
||||
|
||||
|
@ -464,36 +500,75 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
fn sample_time(&self, sample_id: u32) -> Result<(u64, u32)> {
|
||||
let stts = &self.trak.mdia.minf.stbl.stts;
|
||||
|
||||
let mut sample_count = 1;
|
||||
let mut elapsed = 0;
|
||||
|
||||
if self.trafs.len() > 0 {
|
||||
let start_time = ((sample_id - 1) * self.default_sample_duration) as u64;
|
||||
return Ok((start_time, self.default_sample_duration))
|
||||
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() {
|
||||
if sample_id <= sample_count + entry.sample_count - 1 {
|
||||
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 += entry.sample_count;
|
||||
sample_count = new_sample_count;
|
||||
elapsed += entry.sample_count as u64 * entry.sample_delta as u64;
|
||||
}
|
||||
|
||||
return Err(Error::EntryInStblNotFound(
|
||||
Err(Error::EntryInStblNotFound(
|
||||
self.track_id(),
|
||||
BoxType::SttsBox,
|
||||
sample_id,
|
||||
));
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn sample_rendering_offset(&self, sample_id: u32) -> i32 {
|
||||
if let Some(ref ctts) = self.trak.mdia.minf.stbl.ctts {
|
||||
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;
|
||||
|
@ -503,16 +578,13 @@ impl Mp4Track {
|
|||
}
|
||||
|
||||
fn is_sync_sample(&self, sample_id: u32) -> bool {
|
||||
if self.trafs.len() > 0 {
|
||||
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
|
||||
return sample_id == 1 || sample_id % sample_sizes_count == 0;
|
||||
}
|
||||
|
||||
if let Some(ref stss) = self.trak.mdia.minf.stbl.stss {
|
||||
match stss.entries.binary_search(&sample_id) {
|
||||
Ok(_) => true,
|
||||
Err(_) => false,
|
||||
}
|
||||
stss.entries.binary_search(&sample_id).is_ok()
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
@ -528,7 +600,11 @@ impl Mp4Track {
|
|||
Err(Error::EntryInStblNotFound(_, _, _)) => return Ok(None),
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let sample_size = self.sample_size(sample_id).unwrap();
|
||||
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))?;
|
||||
|
@ -571,8 +647,7 @@ impl Mp4TrackWriter {
|
|||
trak.mdia.mdhd.timescale = config.timescale;
|
||||
trak.mdia.mdhd.language = config.language.to_owned();
|
||||
trak.mdia.hdlr.handler_type = config.track_type.into();
|
||||
// XXX largesize
|
||||
trak.mdia.minf.stbl.stco = Some(StcoBox::default());
|
||||
trak.mdia.minf.stbl.co64 = Some(Co64Box::default());
|
||||
match config.media_conf {
|
||||
MediaConfig::AvcConfig(ref avc_config) => {
|
||||
trak.tkhd.set_width(avc_config.width);
|
||||
|
@ -611,7 +686,6 @@ impl Mp4TrackWriter {
|
|||
let tx3g = Tx3gBox::default();
|
||||
trak.mdia.minf.stbl.stsd.tx3g = Some(tx3g);
|
||||
}
|
||||
|
||||
}
|
||||
Ok(Mp4TrackWriter {
|
||||
trak,
|
||||
|
@ -633,27 +707,25 @@ impl Mp4TrackWriter {
|
|||
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);
|
||||
}
|
||||
} 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);
|
||||
}
|
||||
} else {
|
||||
self.trak.mdia.minf.stbl.stsz.sample_sizes.push(size);
|
||||
}
|
||||
self.trak.mdia.minf.stbl.stsz.sample_count += 1;
|
||||
}
|
||||
|
@ -735,8 +807,14 @@ impl Mp4TrackWriter {
|
|||
|
||||
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>(
|
||||
|
@ -762,14 +840,13 @@ impl Mp4TrackWriter {
|
|||
Ok(self.trak.tkhd.duration)
|
||||
}
|
||||
|
||||
// XXX largesize
|
||||
fn chunk_count(&self) -> u32 {
|
||||
let stco = self.trak.mdia.minf.stbl.stco.as_ref().unwrap();
|
||||
stco.entries.len() as 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(ref entry) = self.trak.mdia.minf.stbl.stsc.entries.last() {
|
||||
if let Some(entry) = self.trak.mdia.minf.stbl.stsc.entries.last() {
|
||||
if entry.samples_per_chunk == self.chunk_samples {
|
||||
return;
|
||||
}
|
||||
|
@ -785,15 +862,15 @@ impl Mp4TrackWriter {
|
|||
}
|
||||
|
||||
fn update_chunk_offsets(&mut self, offset: u64) {
|
||||
let stco = self.trak.mdia.minf.stbl.stco.as_mut().unwrap();
|
||||
stco.entries.push(offset as u32);
|
||||
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.seek(SeekFrom::Current(0))?;
|
||||
let chunk_offset = writer.stream_position()?;
|
||||
|
||||
writer.write_all(&self.chunk_buffer)?;
|
||||
|
||||
|
@ -831,6 +908,10 @@ impl Mp4TrackWriter {
|
|||
// 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())
|
||||
}
|
||||
|
|
278
src/types.rs
278
src/types.rs
|
@ -1,6 +1,7 @@
|
|||
use serde::Serialize;
|
||||
use std::borrow::Cow;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt;
|
||||
use serde::{Serialize};
|
||||
|
||||
use crate::mp4box::*;
|
||||
use crate::*;
|
||||
|
@ -8,7 +9,7 @@ use crate::*;
|
|||
pub use bytes::Bytes;
|
||||
pub use num_rational::Ratio;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
pub struct FixedPointU8(Ratio<u16>);
|
||||
|
||||
impl FixedPointU8 {
|
||||
|
@ -29,7 +30,7 @@ impl FixedPointU8 {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
pub struct FixedPointI8(Ratio<i16>);
|
||||
|
||||
impl FixedPointI8 {
|
||||
|
@ -50,7 +51,7 @@ impl FixedPointI8 {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
||||
pub struct FixedPointU16(Ratio<u32>);
|
||||
|
||||
impl FixedPointU16 {
|
||||
|
@ -73,19 +74,19 @@ impl FixedPointU16 {
|
|||
|
||||
impl fmt::Debug for BoxType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let fourcc: FourCC = From::from(self.clone());
|
||||
write!(f, "{}", fourcc)
|
||||
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.clone());
|
||||
write!(f, "{}", fourcc)
|
||||
let fourcc: FourCC = From::from(*self);
|
||||
write!(f, "{fourcc}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Clone, Copy, Serialize)]
|
||||
#[derive(Default, PartialEq, Eq, Clone, Copy, Serialize)]
|
||||
pub struct FourCC {
|
||||
pub value: [u8; 4],
|
||||
}
|
||||
|
@ -95,7 +96,9 @@ impl std::str::FromStr for FourCC {
|
|||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
if let [a, b, c, d] = s.as_bytes() {
|
||||
Ok(Self { value: [*a, *b, *c, *d] })
|
||||
Ok(Self {
|
||||
value: [*a, *b, *c, *d],
|
||||
})
|
||||
} else {
|
||||
Err(Error::InvalidData("expected exactly four bytes in string"))
|
||||
}
|
||||
|
@ -104,7 +107,9 @@ impl std::str::FromStr for FourCC {
|
|||
|
||||
impl From<u32> for FourCC {
|
||||
fn from(number: u32) -> Self {
|
||||
FourCC { value: number.to_be_bytes() }
|
||||
FourCC {
|
||||
value: number.to_be_bytes(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -137,7 +142,7 @@ 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, "{} / {:#010X}", string, code)
|
||||
write!(f, "{string} / {code:#010X}")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -160,7 +165,7 @@ 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)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TrackType {
|
||||
Video,
|
||||
Audio,
|
||||
|
@ -174,7 +179,7 @@ impl fmt::Display for TrackType {
|
|||
TrackType::Audio => DISPLAY_TYPE_AUDIO,
|
||||
TrackType::Subtitle => DISPLAY_TYPE_SUBTITLE,
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,9 +207,9 @@ impl TryFrom<&FourCC> for TrackType {
|
|||
}
|
||||
}
|
||||
|
||||
impl Into<FourCC> for TrackType {
|
||||
fn into(self) -> FourCC {
|
||||
match self {
|
||||
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(),
|
||||
|
@ -218,7 +223,7 @@ const MEDIA_TYPE_VP9: &str = "vp9";
|
|||
const MEDIA_TYPE_AAC: &str = "aac";
|
||||
const MEDIA_TYPE_TTXT: &str = "ttxt";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum MediaType {
|
||||
H264,
|
||||
H265,
|
||||
|
@ -230,7 +235,7 @@ pub enum MediaType {
|
|||
impl fmt::Display for MediaType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let s: &str = self.into();
|
||||
write!(f, "{}", s)
|
||||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -248,9 +253,9 @@ impl TryFrom<&str> for MediaType {
|
|||
}
|
||||
}
|
||||
|
||||
impl Into<&str> for MediaType {
|
||||
fn into(self) -> &'static str {
|
||||
match self {
|
||||
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,
|
||||
|
@ -260,9 +265,9 @@ impl Into<&str> for MediaType {
|
|||
}
|
||||
}
|
||||
|
||||
impl Into<&str> for &MediaType {
|
||||
fn into(self) -> &'static str {
|
||||
match self {
|
||||
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,
|
||||
|
@ -272,7 +277,7 @@ impl Into<&str> for &MediaType {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum AvcProfile {
|
||||
AvcConstrainedBaseline, // 66 with constraint set 1
|
||||
AvcBaseline, // 66,
|
||||
|
@ -286,7 +291,7 @@ 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;
|
||||
let constraint_set1_flag = (value.1 & 0x40) >> 7;
|
||||
match (profile, constraint_set1_flag) {
|
||||
(66, 1) => Ok(AvcProfile::AvcConstrainedBaseline),
|
||||
(66, 0) => Ok(AvcProfile::AvcBaseline),
|
||||
|
@ -307,54 +312,54 @@ impl fmt::Display for AvcProfile {
|
|||
AvcProfile::AvcExtended => "Extended",
|
||||
AvcProfile::AvcHigh => "High",
|
||||
};
|
||||
write!(f, "{}", profile)
|
||||
write!(f, "{profile}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
#[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
|
||||
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 {
|
||||
|
@ -454,11 +459,11 @@ impl fmt::Display for AudioObjectType {
|
|||
AudioObjectType::SpatialAudioObjectCodingDialogueEnhancement => "SAOC-DE",
|
||||
AudioObjectType::AudioSync => "Audio Sync",
|
||||
};
|
||||
write!(f, "{}", type_str)
|
||||
write!(f, "{type_str}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum SampleFreqIndex {
|
||||
Freq96000 = 0x0,
|
||||
Freq88200 = 0x1,
|
||||
|
@ -499,25 +504,25 @@ impl TryFrom<u8> for SampleFreqIndex {
|
|||
|
||||
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,
|
||||
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, Clone, Copy)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum ChannelConfig {
|
||||
Mono = 0x1,
|
||||
Stereo = 0x2,
|
||||
|
@ -555,11 +560,11 @@ impl fmt::Display for ChannelConfig {
|
|||
ChannelConfig::FiveOne => "five.one",
|
||||
ChannelConfig::SevenOne => "seven.one",
|
||||
};
|
||||
write!(f, "{}", s)
|
||||
write!(f, "{s}")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Default)]
|
||||
pub struct AvcConfig {
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
|
@ -567,19 +572,19 @@ pub struct AvcConfig {
|
|||
pub pic_param_set: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Default)]
|
||||
pub struct HevcConfig {
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Default)]
|
||||
pub struct Vp9Config {
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct AacConfig {
|
||||
pub bitrate: u32,
|
||||
pub profile: AudioObjectType,
|
||||
|
@ -598,10 +603,10 @@ impl Default for AacConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Default)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Default)]
|
||||
pub struct TtxtConfig {}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum MediaConfig {
|
||||
AvcConfig(AvcConfig),
|
||||
HevcConfig(HevcConfig),
|
||||
|
@ -651,3 +656,86 @@ pub fn creation_time(creation_time: u64) -> u64 {
|
|||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::mp4box::*;
|
|||
use crate::track::Mp4TrackWriter;
|
||||
use crate::*;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Mp4Config {
|
||||
pub major_brand: FourCC,
|
||||
pub minor_version: u32,
|
||||
|
@ -27,9 +27,9 @@ impl<W> Mp4Writer<W> {
|
|||
///
|
||||
/// 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;
|
||||
|
@ -62,15 +62,16 @@ impl<W> Mp4Writer<W> {
|
|||
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.clone(),
|
||||
minor_version: config.minor_version.clone(),
|
||||
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.seek(SeekFrom::Current(0))?;
|
||||
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;
|
||||
|
@ -114,13 +115,17 @@ impl<W: Write + Seek> Mp4Writer<W> {
|
|||
}
|
||||
|
||||
fn update_mdat_size(&mut self) -> Result<()> {
|
||||
let mdat_end = self.writer.seek(SeekFrom::Current(0))?;
|
||||
let mdat_end = self.writer.stream_position()?;
|
||||
let mdat_size = mdat_end - self.mdat_pos;
|
||||
if mdat_size > std::u32::MAX as u64 {
|
||||
return Err(Error::InvalidData("mdat size too large"));
|
||||
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(self.mdat_pos))?;
|
||||
self.writer.write_u32::<BigEndian>(mdat_size as u32)?;
|
||||
self.writer.seek(SeekFrom::Start(mdat_end))?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -135,6 +140,9 @@ impl<W: Write + Seek> Mp4Writer<W> {
|
|||
|
||||
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(())
|
||||
}
|
||||
|
|
81
tests/lib.rs
81
tests/lib.rs
|
@ -1,5 +1,8 @@
|
|||
use mp4::{AudioObjectType, AvcProfile, ChannelConfig, MediaType, Mp4Reader, SampleFreqIndex, TrackType};
|
||||
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;
|
||||
|
||||
|
@ -22,7 +25,7 @@ fn test_read_mp4() {
|
|||
|
||||
for b in brands {
|
||||
let t = mp4.compatible_brands().iter().any(|x| x.to_string() == b);
|
||||
assert_eq!(t, true);
|
||||
assert!(t);
|
||||
}
|
||||
|
||||
assert_eq!(mp4.duration(), Duration::from_millis(62));
|
||||
|
@ -96,8 +99,8 @@ fn test_read_mp4() {
|
|||
assert_eq!(track1.video_profile().unwrap(), AvcProfile::AvcHigh);
|
||||
assert_eq!(track1.width(), 320);
|
||||
assert_eq!(track1.height(), 240);
|
||||
assert_eq!(track1.bitrate(), 0); // XXX
|
||||
assert_eq!(track1.frame_rate(), 25.00); // XXX
|
||||
assert_eq!(track1.bitrate(), 150200);
|
||||
assert_eq!(track1.frame_rate(), 25.00);
|
||||
|
||||
// track #2
|
||||
let track2 = mp4.tracks().get(&2).unwrap();
|
||||
|
@ -128,7 +131,22 @@ fn test_read_extended_audio_object_type() {
|
|||
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,
|
||||
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);
|
||||
|
@ -141,4 +159,53 @@ fn get_reader(path: &str) -> Mp4Reader<BufReader<File>> {
|
|||
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/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