Compare commits

..

6 Commits
mux ... main

Author SHA1 Message Date
Alex Bakon
189f4b824d
Upgrade some dependencies
Some checks failed
test / format-check (push) Has been cancelled
test / build-and-test (push) Has been cancelled
test / fuzz-afl (push) Has been cancelled
test / fuzz-libfuzzer (push) Has been cancelled
test / check-nightly (push) Has been cancelled
2025-03-11 13:45:48 -04:00
Alex Bakon
79703a59cd
Centralize dependency versions (#27) 2025-03-10 17:20:11 -04:00
Alex Bakon
339be16385
Bump MSRV to 1.66 (#28) 2025-03-10 17:19:57 -04:00
Jordan Rose
afa74cb65b Bump version to 0.5.3
0.5.2 was never published, but it would be more confusing to reuse the
number now.
2025-01-31 15:27:25 -08:00
Jordan Rose
7dd236d849 mp4san: Bump derive_builder dependency 2025-01-31 15:22:33 -08:00
Milan Stevanovic
28087b861d
Milan/sanitizing cumulative box sized files (#26) 2025-01-28 17:43:28 -08:00
59 changed files with 701 additions and 2726 deletions

View File

@ -1,14 +1,47 @@
[workspace]
members = ["cli", "common", "mp4san", "mp4san-derive", "mp4san-dump", "mp4san-test", "mp4san-test-gen", "webpsan", "webpsan-test"]
members = ["cli", "common", "mp4san", "mp4san-derive", "mp4san-test", "mp4san-test-gen", "webpsan", "webpsan-test"]
resolver = "2"
[workspace.package]
version = "0.5.2"
version = "0.5.3"
edition = "2021"
rust-version = "1.61.0"
rust-version = "1.66.0"
repository = "https://github.com/privacyresearchgroup/mp4san"
license = "MIT"
exclude = ["/.github", ".*", "/test-data/"]
[workspace.dependencies]
ac-ffmpeg = "0.18.1"
afl = "0.12.16"
anyhow = "1.0.68"
assert_matches = "1.5.0"
bindgen = "0.69.4"
bitflags = "2.4.0"
bitstream-io = "1.7.0"
bytes = "1.3.0"
cc = "1.0.79"
clap = "4.0.32"
criterion = "0.5.1"
derive-where = "1.1.0"
derive_builder = "0.20.2"
derive_more = "2.0.0"
downcast-rs = "2.0.1"
dyn-clonable = "0.9.0"
env_logger = "0.11.3"
ffmpeg-sys-next = { version = "7.0.0", default-features = false }
futures-util = { version = "0.3.28", default-features = false }
libflate = "2.1.0"
libwebp-sys = "0.9.4"
log = "0.4.17"
num-integer = { version = "0.1.45", default-features = false }
num-traits = { version = "0.2.16", default-features = false }
paste = "1.0.14"
pkg-config = "0.3.26"
proc-macro2 = "1.0"
quote = "1.0"
syn = "2.0"
thiserror = "2.0.12"
uuid = "1.3"

View File

@ -6,9 +6,9 @@ version = "0.1.0"
publish = false
[dependencies]
anyhow = "1.0.68"
clap = { version = "4.0.32", features = ["derive"] }
anyhow = { workspace = true }
clap = { workspace = true, features = ["derive"] }
mp4san = { path = "../mp4san" }
webpsan = { path = "../webpsan" }
env_logger = "0.11.3"
log = "0.4.17"
env_logger = { workspace = true }
log = { workspace = true }

View File

@ -6,7 +6,7 @@ use std::path::PathBuf;
use anyhow::Context;
use clap::{Parser as _, ValueEnum};
use mp4san::SanitizedMetadata;
use mp4san::{Config, SanitizedMetadata};
#[derive(clap::Parser)]
struct Args {
@ -20,6 +20,9 @@ struct Args {
#[clap(long, short = 'o')]
output: Option<PathBuf>,
#[clap(long, short = 'c')]
cumulative_mdat_box_size: Option<u32>,
/// Path to the file to test sanitization on.
file: PathBuf,
}
@ -51,23 +54,26 @@ fn main() -> Result<(), anyhow::Error> {
let mut infile = File::open(&args.file).context("Error opening file")?;
match format {
Format::Mp4 => match mp4san::sanitize(&mut infile).context("Error parsing mp4 file")? {
SanitizedMetadata { metadata: Some(metadata), data } => {
if let Some(output_path) = args.output {
let mut outfile = File::create(output_path).context("Error opening output file")?;
outfile.write(&metadata).context("Error writing output")?;
infile
.seek(io::SeekFrom::Start(data.offset))
.context("Error seeking input")?;
io::copy(&mut infile.take(data.len), &mut outfile).context("Error copying input to output")?;
Format::Mp4 => {
let config = Config { cumulative_mdat_box_size: args.cumulative_mdat_box_size, ..Default::default() };
match mp4san::sanitize_with_config(&mut infile, config).context("Error parsing mp4 file")? {
SanitizedMetadata { metadata: Some(metadata), data } => {
if let Some(output_path) = args.output {
let mut outfile = File::create(output_path).context("Error opening output file")?;
outfile.write(&metadata).context("Error writing output")?;
infile
.seek(io::SeekFrom::Start(data.offset))
.context("Error seeking input")?;
io::copy(&mut infile.take(data.len), &mut outfile).context("Error copying input to output")?;
}
}
SanitizedMetadata { metadata: None, .. } => {
if let Some(output_path) = args.output {
fs::copy(&args.file, output_path).context("Error writing output")?;
}
}
}
SanitizedMetadata { metadata: None, .. } => {
if let Some(output_path) = args.output {
fs::copy(&args.file, output_path).context("Error writing output")?;
}
}
},
}
Format::Webp => {
webpsan::sanitize(infile).context("Error parsing webp file")?;
if let Some(output_path) = args.output {

View File

@ -6,6 +6,6 @@ version = "0.1.0"
publish = false
[dependencies]
env_logger = "0.11.3"
libflate = "2.1.0"
log = "0.4.17"
env_logger = { workspace = true }
libflate = { workspace = true }
log = { workspace = true }

View File

@ -15,7 +15,7 @@ readme = "../README.md"
exclude.workspace = true
[dependencies]
bytes = "1.3.0"
derive_more = "0.99.17"
futures-util = { version = "0.3.28", default-features = false, features = ["io"] }
thiserror = "1.0.38"
bytes = { workspace = true }
derive_more = { workspace = true, features = ["deref", "deref_mut", "display"] }
futures-util = { workspace = true, features = ["io"] }
thiserror = { workspace = true }

View File

@ -40,12 +40,12 @@ pub struct Report<E: ReportableError> {
/// A [`Display`]-able indicating there was extra trailing input after parsing.
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "extra unparsed input")]
#[display("extra unparsed input")]
pub struct ExtraUnparsedInput;
/// A [`Display`]-able indicating an error occurred while parsing a certain type.
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "while parsing value of type `{}`", _0)]
#[display("while parsing value of type `{}`", _0)]
pub struct WhileParsingType(&'static str);
/// A convenience type alias for a [`Result`](std::result::Result) containing an error wrapped by a [`Report`].
@ -70,7 +70,7 @@ pub struct ReportStack {
/// A null error stack which ignores all data attached to it.
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "")]
#[display("")]
pub struct NullReportStack;
/// A trait for error types which can be used in a [`Report`].
@ -95,7 +95,7 @@ pub trait ReportableErrorStack: Display {
//
#[derive(derive_more::Display)]
#[display(fmt = "{message} at {location}")]
#[display("{message} at {location}")]
struct ReportEntry {
message: Box<dyn Display + Send + Sync + 'static>,
location: &'static Location<'static>,

View File

@ -18,8 +18,7 @@ exclude.workspace = true
proc-macro = true
[dependencies]
proc-macro2 = "1.0.66"
syn = "2.0.29"
quote = "1.0.33"
uuid = "1.4.1"
synstructure = "0.13.0"
proc-macro2 = { workspace = true }
syn = { workspace = true }
quote = { workspace = true }
uuid = { workspace = true }

View File

@ -1,67 +0,0 @@
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{Attribute, Expr, Lit, LitByteStr, Meta, MetaNameValue};
use uuid::Uuid;
pub(crate) fn extract_box_type(attrs: &[Attribute]) -> TokenStream {
let mut iter = attrs.iter().filter(|attr| attr.path().is_ident("box_type"));
let Some(attr) = iter.next() else {
// When emitting compiler errors, no semicolon should be placed after `compile_error!()`:
// doing so will generate extraneous errors (type mismatch errors, Rust parse errors, or the
// like) in addition to the error we intend to emit.
return quote! { std::compile_error!("missing `#[box_type]` attribute") };
};
if let Some(extra_attr) = iter.next() {
return quote_spanned! { extra_attr.span() =>
std::compile_error!("more than one `#[box_type]` attribute is not allowed")
};
}
let lit = match &attr.meta {
Meta::NameValue(MetaNameValue { value: Expr::Lit(expr_lit), .. }) => &expr_lit.lit,
_ => {
return quote_spanned! { attr.span() =>
std::compile_error!("`box_type` attribute must be of the form `#[box_type = ...]`")
}
}
};
match &lit {
Lit::Int(int_lit) => {
let int = match int_lit.base10_parse::<u128>() {
Ok(int) => int,
Err(error) => return error.into_compile_error(),
};
if let Ok(int) = u32::try_from(int) {
let bytes_lit = LitByteStr::new(&int.to_be_bytes(), int_lit.span());
return quote! { mp4san::parse::BoxType::FourCC(mp4san::parse::FourCC { value: *#bytes_lit }) };
} else {
let bytes_lit = LitByteStr::new(&int.to_be_bytes(), int_lit.span());
return quote! { mp4san::parse::BoxType::Uuid(mp4san::parse::BoxUuid { value: *#bytes_lit }) };
}
}
Lit::Str(string_lit) => {
if let Ok(uuid) = Uuid::parse_str(&string_lit.value()) {
let bytes_lit = LitByteStr::new(&uuid.as_u128().to_be_bytes(), string_lit.span());
return quote! { mp4san::parse::BoxType::Uuid(mp4san::parse::BoxUuid { value: *#bytes_lit }) };
} else if string_lit.value().len() == 4 {
let bytes_lit = LitByteStr::new(string_lit.value().as_bytes(), string_lit.span());
return quote! {
mp4san::parse::BoxType::FourCC(mp4san::parse::FourCC { value: *#bytes_lit })
};
}
}
Lit::ByteStr(bytes_lit) => {
if bytes_lit.value().len() == 4 {
return quote! {
mp4san::parse::BoxType::FourCC(mp4san::parse::FourCC { value: *#bytes_lit })
};
}
}
_ => {}
}
quote_spanned! { lit.span() => std::compile_error!(concat!(
r#"malformed `box_type` attribute input: try `"moov"`, `b"moov"`, or `0x6d6f6f76` for a"#,
r#" compact type, or `"a7b5465c-7eac-4caa-b744-bdc340127d37"` or"#,
r#" `0xa7b5465c_7eac_4caa_b744_bdc340127d37` for an extended type"#,
)) }
}

View File

@ -1,15 +1,212 @@
mod attr;
mod mp4_prim;
mod mp4_value;
mod parse_box;
mod parse_boxes;
mod parsed_box;
mod util;
use proc_macro::TokenStream;
use synstructure::decl_derive;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{parse_macro_input, Data, DeriveInput, Expr, Ident, Index, Lit};
use uuid::Uuid;
decl_derive!([ParseBox, attributes(box_type)] => parse_box::derive);
decl_derive!([ParsedBox, attributes(box_type)] => parsed_box::derive);
decl_derive!([ParseBoxes, attributes(box_type)] => parse_boxes::derive);
decl_derive!([Mp4Prim] => mp4_prim::derive);
decl_derive!([Mp4Value] => mp4_value::derive);
#[proc_macro_derive(ParseBox, attributes(box_type))]
pub fn derive_parse_box(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
if matches!(input.data, Data::Enum(_) | Data::Union(_)) {
// This one _does_ need a semicolon though.
return TokenStream::from(quote! {
std::compile_error!("this trait can only be derived for structs");
});
}
let box_type = extract_box_type(&input);
let read_fn = derive_read_fn(&input);
TokenStream::from(quote! {
#[automatically_derived]
impl #impl_generics mp4san::parse::ParseBox for #ident #ty_generics #where_clause {
fn box_type() -> mp4san::parse::BoxType {
#box_type
}
#read_fn
}
})
}
#[proc_macro_derive(ParsedBox, attributes(box_type))]
pub fn derive_parsed_box(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
if matches!(input.data, Data::Enum(_) | Data::Union(_)) {
// This one _does_ need a semicolon though.
return TokenStream::from(quote! {
std::compile_error!("this trait can only be derived for structs");
});
}
let size = sum_box_size(&input);
let write_fn = derive_write_fn(&input);
TokenStream::from(quote! {
#[automatically_derived]
impl #impl_generics mp4san::parse::ParsedBox for #ident #ty_generics #where_clause {
fn encoded_len(&self) -> u64 {
#size
}
#write_fn
}
})
}
fn derive_write_fn(input: &DeriveInput) -> TokenStream2 {
let write_fields = match &input.data {
Data::Struct(struct_data) => {
let place_expr = struct_data.fields.iter().enumerate().map(|(index, field)| {
if let Some(ident) = &field.ident {
quote_spanned! { field.span() => self.#ident }
} else {
let tuple_index = Index::from(index);
quote_spanned! { field.span() => self.#tuple_index }
}
});
quote! { #( mp4san::parse::Mp4Value::put_buf(&#place_expr, &mut *out); )* }
}
_ => unreachable!(),
};
quote! {
fn put_buf(&self, out: &mut dyn bytes::BufMut) {
#write_fields
}
}
}
fn derive_read_fn(input: &DeriveInput) -> TokenStream2 {
let ident = &input.ident;
match &input.data {
Data::Struct(struct_data) => {
let mut field_ty = Vec::new();
let mut field_ident = Vec::new();
let mut bind_ident = Vec::new();
for (index, field) in struct_data.fields.iter().enumerate() {
field_ty.push(field.ty.clone());
if let Some(ident) = &field.ident {
field_ident.push(quote_spanned! { field.span() => #ident });
bind_ident.push(ident.clone());
} else {
let tuple_index = Index::from(index);
field_ident.push(quote_spanned! { field.span() => #tuple_index });
bind_ident.push(Ident::new(&format!("field_{index}"), Span::mixed_site()));
}
}
quote! {
fn parse(buf: &mut bytes::BytesMut) -> std::result::Result<Self, mp4san::Report<mp4san::parse::ParseError>> {
#(
let #bind_ident: #field_ty =
mp4san::parse::error::ParseResultExt::while_parsing_field(
mp4san::parse::Mp4Value::parse(&mut *buf),
#ident::box_type(),
stringify!(#field_ty),
)?;
)*
if !buf.is_empty() {
return
mp4san::parse::error::ParseResultExt::while_parsing_box(
mp4san::error::ResultExt::attach_printable(
Err(mp4san::parse::ParseError::InvalidInput.into()),
"extra unparsed data",
),
#ident::box_type(),
);
}
std::result::Result::Ok(#ident { #( #field_ident: #bind_ident ),* })
}
}
}
_ => unreachable!(),
}
}
fn extract_box_type(input: &DeriveInput) -> TokenStream2 {
let mut iter = input.attrs.iter().filter(|attr| attr.path().is_ident("box_type"));
let Some(attr) = iter.next() else {
// When emitting compiler errors, no semicolon should be placed after `compile_error!()`:
// doing so will generate extraneous errors (type mismatch errors, Rust parse errors, or the
// like) in addition to the error we intend to emit.
return quote! { std::compile_error!("missing `#[box_type]` attribute") };
};
if let Some(extra_attr) = iter.next() {
return quote_spanned! { extra_attr.span() =>
std::compile_error!("more than one `#[box_type]` attribute is not allowed")
};
}
let lit = match attr.meta.require_name_value().map(|name_value| &name_value.value).ok() {
Some(Expr::Lit(lit)) => &lit.lit,
_ => {
return quote_spanned! { attr.span() =>
std::compile_error!("`box_type` attribute must be of the form `#[box_type = ...]`")
}
}
};
match &lit {
Lit::Int(int_lit) => {
let int = match int_lit.base10_parse::<u128>() {
Ok(int) => int,
Err(error) => return error.into_compile_error(),
};
if let Ok(int) = u32::try_from(int) {
return quote! { mp4san::parse::BoxType::FourCC(mp4san::parse::FourCC { value: #int.to_be_bytes() }) };
} else {
return quote! { mp4san::parse::BoxType::Uuid(mp4san::parse::BoxUuid { value: #int.to_be_bytes() }) };
}
}
Lit::Str(string_lit) => {
let string = string_lit.value();
if let Ok(uuid) = Uuid::parse_str(&string) {
let int = uuid.as_u128();
return quote! { mp4san::parse::BoxType::Uuid(mp4san::parse::BoxUuid { value: #int.to_be_bytes() }) };
} else if string.len() == 4 {
return quote! {
let type_string = #string_lit;
let type_ = std::convert::TryInto::try_into(type_string.as_bytes()).unwrap();
mp4san::parse::BoxType::FourCC(mp4san::parse::FourCC { value: type_ })
};
}
}
Lit::ByteStr(bytes_lit) => {
let bytes = bytes_lit.value();
if bytes.len() == 4 {
return quote! {
mp4san::parse::BoxType::FourCC(mp4san::parse::FourCC { value: *#bytes_lit })
};
}
}
_ => {}
}
quote_spanned! { lit.span() => std::compile_error!(concat!(
r#"malformed `box_type` attribute input: try `"moov"`, `b"moov"`, or `0x6d6f6f76` for a"#,
r#" compact type, or `"a7b5465c-7eac-4caa-b744-bdc340127d37"` or"#,
r#" `0xa7b5465c_7eac_4caa_b744_bdc340127d37` for an extended type"#,
)) }
}
fn sum_box_size(derive_input: &DeriveInput) -> TokenStream2 {
let sum_expr = match &derive_input.data {
Data::Struct(struct_data) => {
let sum_expr = struct_data.fields.iter().enumerate().map(|(index, field)| {
if let Some(ident) = &field.ident {
quote_spanned! { field.span() => mp4san::parse::Mp4Value::encoded_len(&self.#ident) }
} else {
let tuple_index = Index::from(index);
quote_spanned! { field.span() => mp4san::parse::Mp4Value::encoded_len(&self.#tuple_index) }
}
});
quote! { #(+ #sum_expr)* }
}
_ => unreachable!(),
};
quote! {
0 #sum_expr
}
}

View File

@ -1,59 +0,0 @@
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use synstructure::Structure;
use crate::util::StructureExt;
pub(crate) fn derive(input: Structure) -> TokenStream {
let parse = input.parse(|_variant, field, _idx| {
quote_spanned! {
field.span() => Mp4Prim::parse(&mut buf).while_parsing_type()
}
});
let mut variant_encoded_len = input.variants().iter().map(|variant| {
variant.bindings().iter().fold(quote!(0), |acc, binding| {
let ty = &binding.ast().ty;
quote! { #acc + <#ty>::ENCODED_LEN }
})
});
let first_variant_encoded_len = variant_encoded_len.next();
let put_buf = input.each(|binding| quote! { buf.put_mp4_value(#binding); });
let ident = &input.ast().ident;
input.gen_impl(quote! {
use std::prelude::v1::*;
use bytes::{Buf, BufMut, BytesMut};
use mp4san::Report;
use mp4san::error::__ResultExt;
use mp4san::parse::{Mp4Prim, Mp4ValueWriterExt, ParseError};
use mp4san::parse::error::__ParseResultExt;
#(if <#ident>::ENCODED_LEN != #variant_encoded_len {
panic!(concat!(
"error in #[derive(Mp4Prim)] for ",
stringify!(#ident),
": all variants must have equal encoded length",
));
})*
#[automatically_derived]
gen impl Mp4Prim for @Self {
const ENCODED_LEN: u64 = #first_variant_encoded_len;
fn parse<B: Buf + AsRef<[u8]>>(mut buf: B) -> Result<Self, Report<ParseError>> {
if (buf.remaining() as u64) < Self::ENCODED_LEN {
return Err(Report::from(ParseError::TruncatedBox)).while_parsing_type();
}
#parse
}
fn put_buf<B: BufMut>(&self, mut buf: B) {
match *self { #put_buf }
}
}
})
}

View File

@ -1,41 +0,0 @@
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use synstructure::Structure;
use crate::util::StructureExt;
pub(crate) fn derive(input: Structure) -> TokenStream {
let parse = input.parse(|_variant, field, _idx| {
quote_spanned! {
field.span() => Mp4Value::parse(&mut *buf).while_parsing_type()
}
});
let encoded_len = input.fold(quote!(0), |acc, binding| quote! { #acc + #binding.encoded_len() });
let put_buf = input.each(|binding| quote! { buf.put_mp4_value(#binding); });
input.gen_impl(quote! {
use std::prelude::v1::*;
use bytes::{BufMut, BytesMut};
use mp4san::Report;
use mp4san::error::__ResultExt;
use mp4san::parse::{Mp4Value, Mp4ValueWriterExt, ParseError};
use mp4san::parse::error::__ParseResultExt;
#[automatically_derived]
gen impl Mp4Value for @Self {
fn parse(buf: &mut BytesMut) -> Result<Self, Report<ParseError>> {
#parse
}
fn encoded_len(&self) -> u64 {
match *self { #encoded_len }
}
fn put_buf<B: BufMut>(&self, mut buf: B) {
match *self { #put_buf }
}
}
})
}

View File

@ -1,54 +0,0 @@
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use synstructure::Structure;
use crate::attr::extract_box_type;
use crate::util::StructureExt;
pub(crate) fn derive(input: Structure) -> TokenStream {
let box_type = extract_box_type(&input.ast().attrs);
let ident = &input.ast().ident;
let parse = input.parse(|variant, field, idx| {
let field_ident = &field.ident;
let check_buf_empty = (idx == variant.bindings().len() - 1).then(|| {
quote! {
if !buf.is_empty() {
return Err(Report::from(ParseError::InvalidInput))
.attach_printable(format!("{} bytes of extra unparsed data", buf.len()))
.while_parsing_box(<#ident>::NAME);
}
}
});
quote_spanned! {
field.span() => {
#[allow(clippy::let_and_return)]
let parsed = Mp4Value::parse(&mut *buf).while_parsing_field(<#ident>::NAME, stringify!(#field_ident));
if parsed.is_ok() {
#check_buf_empty
}
parsed
}
}
});
input.gen_impl(quote! {
use std::prelude::v1::*;
use bytes::BytesMut;
use mp4san::Report;
use mp4san::error::ResultExt;
use mp4san::parse::{BoxType, Mp4Value, ParseBox, ParseError, FourCC};
use mp4san::parse::error::ParseResultExt;
#[automatically_derived]
gen impl ParseBox for @Self {
const NAME: BoxType = #box_type;
fn parse(buf: &mut BytesMut) -> Result<Self, Report<ParseError>> {
#parse
}
}
})
}

View File

@ -1,255 +0,0 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote, quote_spanned};
use syn::spanned::Spanned;
use syn::token::Mut;
use syn::{parse2, Data, DataEnum, DataStruct, DeriveInput, Field, Fields};
use synstructure::{BindStyle, Structure, VariantAst, VariantInfo};
use crate::attr::extract_box_type;
pub(crate) fn derive(input: Structure) -> TokenStream {
if let Data::Union(_) = &input.ast().data {
return quote! {
std::compile_error!("this trait cannot be derived for unions");
};
};
let [variant] = input.variants() else {
return quote! {
std::compile_error!("this trait can only be derived for structs or single-variant enums");
};
};
let box_type = extract_box_type(&input.ast().attrs);
let ref_item @ DeriveInput { ident: ref_item_ident, generics: ref_item_generics, .. } =
&parse2(derive_ref_item(&input, false)).unwrap();
let ref_variant = Structure::new(ref_item).variants()[0].clone();
let ref_mut_item @ DeriveInput { ident: ref_mut_item_ident, generics: ref_mut_item_generics, .. } =
&parse2(derive_ref_item(&input, true)).unwrap();
let ref_mut_variant = Structure::new(ref_mut_item).variants()[0].clone();
let validate_fn_body = derive_parse_fn_body(variant, None, ParseFnKind::Validate);
let parse_fn_body = derive_parse_fn_body(variant, Some(&ref_mut_variant), ParseFnKind::Parse);
let parsed_fn_body = derive_parse_fn_body(variant, Some(&ref_variant), ParseFnKind::Parsed);
let into_iter_fn = derive_into_iter_fn(&input);
let parse_boxes_impl = input.gen_impl(quote! {
use std::vec;
use std::prelude::v1::*;
use mp4san::error::Report;
use mp4san::parse::{AnyMp4Box, Boxes, BoxType, Mp4Box, ParseBoxes, ParseError};
use mp4san::parse::derive::parse_boxes::{Field, Accumulator};
use mp4san::parse::error::__ParseResultExt;
type FieldType<T> = <T as Field>::Type;
type FieldAccumulator<T, U> = <T as Field>::Accumulator<U>;
type FieldAccumulatorUnwrapped<T, U> = <<T as Field>::Accumulator<U> as Accumulator<U>>::Unwrapped;
const NAME: BoxType = #box_type;
#[automatically_derived]
gen impl ParseBoxes for @Self {
type Ref<'a> = #ref_item_ident #ref_item_generics;
type RefMut<'a> = #ref_mut_item_ident #ref_mut_item_generics;
type IntoIter = vec::IntoIter<AnyMp4Box>;
fn validate<'boxes>(boxes: &'boxes mut [AnyMp4Box]) -> Result<(), Report<ParseError>> {
#validate_fn_body
}
fn parse<'boxes>(boxes: &'boxes mut [AnyMp4Box]) -> Result<Self::RefMut<'boxes>, Report<ParseError>>
where
Self: 'boxes,
{
#parse_fn_body
}
fn parsed<'boxes>(boxes: &'boxes [AnyMp4Box]) -> Self::Ref<'boxes>
where
Self: 'boxes,
{
let parsed_impl = |boxes: &'boxes [AnyMp4Box]| -> Result<Self::Ref<'boxes>, Report<ParseError>> {
#parsed_fn_body
};
parsed_impl(boxes).unwrap()
}
#into_iter_fn
}
});
quote! {
#parse_boxes_impl
#[derive(Clone, Debug)]
#[allow(clippy::type_complexity)]
#ref_item
#[derive(Debug)]
#[allow(clippy::type_complexity)]
#ref_mut_item
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum ParseFnKind {
Validate,
Parse,
Parsed,
}
fn derive_parse_fn_body(
variant: &VariantInfo<'_>,
return_variant: Option<&VariantInfo<'_>>,
parse_fn_kind: ParseFnKind,
) -> TokenStream {
let declare_field = variant.bindings().iter().map(|binding| {
let Field { ty, ident, .. } = binding.ast();
quote_spanned! { ident.span() => let mut #binding: FieldAccumulator<#ty, _> = Default::default(); }
});
let parse_match_arm = variant.bindings().iter().map(|binding| {
let Field { ty, .. } = binding.ast();
let parse_expr = match parse_fn_kind {
ParseFnKind::Validate => quote! { mp4box.parse_data_as()?.map(drop::<&mut FieldType<#ty>>) },
ParseFnKind::Parse => quote! { mp4box.parse_data_as()? },
ParseFnKind::Parsed => quote! { mp4box.data.parsed() },
};
quote! {
box_type if box_type == <FieldType<#ty>>::NAME && !Accumulator::is_full(&#binding) => {
if let Some(field) = #parse_expr {
Accumulator::push(&mut #binding, field);
}
},
}
});
let unwrap_field = variant.bindings().iter().map(|binding| {
let Field { ty, ident, .. } = binding.ast();
quote_spanned! {
ident.span() =>
let #binding = Accumulator::unwrap(#binding)
.ok_or_else(|| Report::from(ParseError::MissingRequiredBox(<FieldType<#ty>>::NAME)))
.while_parsing_child(NAME, <FieldType<#ty>>::NAME)?;
}
});
let return_value = match return_variant {
Some(return_variant) => return_variant.construct(|_field, idx| &variant.bindings()[idx]),
None => quote!(()),
};
quote! {
#(#declare_field)*
for mp4box in boxes {
match mp4box.box_type() {
#(#parse_match_arm)*
_ => (),
}
}
#(#unwrap_field)*
Ok(#return_value)
}
}
fn derive_into_iter_fn(input: &Structure) -> TokenStream {
let variant_len = input.variants().iter().map(|variant| variant.bindings().len());
let field_count = variant_len.sum::<usize>();
let push_fields = input
.clone()
.bind_with(|_| BindStyle::Move)
.fold(quote!(), |acc, binding| {
let push_fields = quote_spanned! {
binding.ast().span() =>
for mp4box in Field::into_iter(#binding) {
fields.push(AnyMp4Box::from(Mp4Box::with_parsed(Box::new(mp4box))?));
}
};
quote! { #acc #push_fields }
});
quote! {
fn try_into_iter(self) -> Result<Self::IntoIter, Report<ParseError>> {
let mut fields = Vec::with_capacity(#field_count);
match self { #push_fields }
Ok(IntoIterator::into_iter(fields))
}
}
}
fn derive_ref_item(input: &Structure, mutable: bool) -> TokenStream {
let DeriveInput { ident, vis, data, .. } = input.ast();
let ident = match mutable {
false => format_ident!("{ident}Ref", span = ident.span()),
true => format_ident!("{ident}RefMut", span = ident.span()),
};
let lt_a = (!input.variants().iter().all(|variant| variant.bindings().is_empty())).then(|| quote! { 'a });
let path = quote! { mp4san::parse::derive::parse_boxes:: };
let mutability = mutable.then(Mut::default);
let variants = input.variants().iter().fold(quote!(), |acc, variant| {
let VariantAst { ident, discriminant, .. } = variant.ast();
let discriminant = discriminant
.as_ref()
.map(|(eq, expr)| quote! { #eq #expr })
.unwrap_or_default();
let fields = variant.bindings().iter().fold(quote!(), |acc, binding| {
let Field { vis, ident, colon_token, ty, .. } = binding.ast();
let ty_as_field = quote_spanned! { ident.span() => #ty as #path Field };
let ty_ref = quote_spanned! { ident.span() => &'a #mutability <#ty_as_field>::Type };
let declare_field = quote_spanned! {
ident.span() => #vis #ident #colon_token
<<#ty_as_field>::Accumulator<#ty_ref> as #path Accumulator<#ty_ref>>::Unwrapped,
};
quote! { #acc #declare_field }
});
match &input.ast().data {
Data::Enum { .. } => match &variant.ast().fields {
Fields::Unnamed { .. } => quote! { #acc #ident #discriminant(#fields), },
Fields::Named { .. } => quote! { #acc #ident #discriminant { #fields }, },
Fields::Unit { .. } => quote! { #acc #ident #discriminant, },
},
Data::Union { .. } | Data::Struct { .. } => quote! { #acc #fields },
}
});
match data {
Data::Enum(DataEnum { enum_token, .. }) => quote! { #vis #enum_token #ident<#lt_a> { #variants } },
Data::Struct(DataStruct { struct_token, fields, .. }) => match fields {
Fields::Named(_) => quote! { #vis #struct_token #ident<#lt_a> { #variants } },
Fields::Unnamed(_) => quote! { #vis #struct_token #ident<#lt_a>(#variants); },
Fields::Unit => quote! { #vis #struct_token #ident<#lt_a>; },
},
Data::Union(_) => unreachable!(),
}
}
#[cfg(test)]
mod test {
use syn::parse2;
use super::*;
fn test_derive(input: TokenStream) -> TokenStream {
derive(Structure::new(&parse2(input).unwrap()))
}
#[test]
fn empty_struct() {
test_derive(quote! {
struct Empty {}
});
}
#[test]
fn unit_struct() {
test_derive(quote! {
struct Empty;
});
}
}

View File

@ -1,35 +0,0 @@
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use synstructure::Structure;
pub(crate) fn derive(input: Structure) -> TokenStream {
let size = input.fold(0u64, |acc, binding| quote! { #acc + Mp4Value::encoded_len(#binding) });
let write_fn = derive_write_fn(&input);
input.gen_impl(quote! {
use std::prelude::v1::*;
use mp4san::parse::{Mp4Value, ParsedBox};
#[automatically_derived]
gen impl ParsedBox for @Self {
fn encoded_len(&self) -> u64 {
match *self { #size }
}
#write_fn
}
})
}
fn derive_write_fn(input: &Structure) -> TokenStream {
let write_fields = input.each(|binding| {
quote_spanned! { binding.ast().span() => Mp4Value::put_buf(#binding, &mut *out); }
});
quote! {
fn put_buf(&self, out: &mut dyn bytes::BufMut) {
match *self { #write_fields }
}
}
}

View File

@ -1,52 +0,0 @@
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned, ToTokens};
use syn::spanned::Spanned;
use syn::Field;
use synstructure::{Structure, VariantInfo};
pub trait StructureExt {
fn construct<F: FnMut(&VariantInfo, usize) -> T, T: ToTokens>(&self, fun: F) -> TokenStream;
fn parse<F: FnMut(&VariantInfo, &Field, usize) -> T, T: ToTokens>(&self, fun: F) -> TokenStream;
}
impl StructureExt for Structure<'_> {
fn construct<F: FnMut(&VariantInfo, usize) -> T, T: ToTokens>(&self, mut fun: F) -> TokenStream {
let variants = self.variants().iter().enumerate();
variants.fold(quote!({}), |acc, (variant_idx, variant)| {
let parse_variant = fun(variant, variant_idx);
if variant_idx != self.variants().len() - 1 {
quote! { #acc let _ = { #parse_variant }; }
} else {
quote! { #acc #parse_variant }
}
})
}
fn parse<F: FnMut(&VariantInfo, &Field, usize) -> T, T: ToTokens>(&self, mut fun: F) -> TokenStream {
self.construct(|variant, _idx| {
let parse_expr = variant.construct(|field, idx| {
let parse_expr = fun(variant, field, idx);
match idx {
0 => quote_spanned! {
field.span() => {
let parse_result = { #parse_expr };
match parse_result {
Ok(parsed) => parsed,
Err(err) => break 'parse_variant Err::<Self, _>(err),
}
}
},
_ => quote_spanned! { field.span() => { #parse_expr }? },
}
});
let label = (!variant.bindings().is_empty()).then(|| quote! { 'parse_variant: });
quote_spanned! {
variant.ast().ident.span() => #label {
let parsed = { #parse_expr };
#[allow(clippy::needless_return)]
return Ok(parsed);
}
}
})
}
}

View File

@ -1,16 +0,0 @@
[package]
name = "mp4san-dump"
description = "Sample utilities built on top of mp4san to synchronously dump frames"
version.workspace = true
edition.workspace = true
publish = false
[dependencies]
mp4san = { path = "../mp4san" }
mediasan-common = { path = "../common" }
bytes = "1.3.0"
env_logger = "0.10.0"
futures-util = { version = "0.3.28", default-features = false, features = ["io"] }
log = "0.4.17"

View File

@ -1,218 +0,0 @@
//! WARNING: This is not a 100% correct implementation of a frame dumper for H.264 in MPEG-4.
//!
//! Many many things were skipped and/or hardcoded. Do not use this as a reference, only a starting
//! point.
use std::io;
use std::ops::Range;
use bytes::Buf as _;
use futures_util::{pin_mut, FutureExt as _};
use mediasan_common::{bail_attach, ensure_attach, report_attach, SeekSkipAdapter};
use mp4san::parse::{
ArrayEntry, BoxHeader, BoxType, FtypBox, MoovBox, Mp4Box, ParseBox, ParseError, ParsedBox, StscEntry, TrakBox,
};
use mp4san::{Error, COMPATIBLE_BRAND};
// Note: modified version of mp4san::sanitize_async_with_config; should eventually be folded back
// together with that.
pub fn parse(input: &[u8]) -> Result<Mp4Box<MoovBox>, Error> {
let mut reader = io::Cursor::new(input);
let mut mdat_seen = false;
let mut ftyp_seen = false;
let mut moov: Option<Mp4Box<MoovBox>> = None;
while reader.position() != reader.get_ref().len() as u64 {
let start_pos = reader.position();
let header = BoxHeader::parse(&mut reader).expect("valid header");
match header.box_type() {
name @ (BoxType::FREE | BoxType::SKIP) => {
skip_box(&mut reader, &header).expect("can skip in Cursor");
log::info!("{name} @ 0x{start_pos:08x}");
}
BoxType::FTYP => {
assert!(!ftyp_seen, "multiple ftyp boxes");
let mut read_ftyp = read_data_sync(&mut reader, header, 1024).expect("valid box");
let ftyp_data: &mut FtypBox = read_ftyp.data.parse().expect("valid ftyp");
let compatible_brand_count = ftyp_data.compatible_brands().len();
let FtypBox { major_brand, minor_version, .. } = ftyp_data;
log::info!("ftyp @ 0x{start_pos:08x}: {major_brand} version {minor_version}, {compatible_brand_count} compatible brands");
ensure_attach!(
ftyp_data.compatible_brands().any(|b| b == COMPATIBLE_BRAND),
ParseError::UnsupportedFormat(ftyp_data.major_brand)
);
ftyp_seen = true;
}
// NB: ISO 14496-12-2012 specifies a default ftyp, but we don't currently use it. The spec says that it
// contains a single compatible brand, "mp41", and notably not "isom" which is the ISO spec we follow for
// parsing now. This implies that there's additional stuff in "mp41" which is not in "isom". "mp41" is also
// very old at this point, so it'll require additional research/work to be able to parse/remux it.
_ if !ftyp_seen => {
bail_attach!(ParseError::InvalidBoxLayout, "ftyp is not the first significant box");
}
BoxType::MDAT => {
mdat_seen = true;
skip_box(&mut reader, &header).expect("can skip in Cursor");
log::info!("mdat @ 0x{start_pos:08x}");
}
BoxType::MOOV => {
let mut read_moov: Mp4Box<MoovBox> =
read_data_sync(&mut reader, header, 1024 * 1024).expect("can read box");
let children = read_moov.data.parse().expect("valid moov").parsed_mut();
let chunk_count = children.tracks.iter().map(|trak| trak.co().entry_count()).sum::<u32>();
let trak_count = children.tracks.len();
log::info!("moov @ 0x{start_pos:08x}: {trak_count} traks {chunk_count} chunks");
moov = Some(read_moov);
}
name => {
skip_box(&mut reader, &header).expect("can skip in Cursor");
log::info!("{name} @ 0x{start_pos:08x}");
}
}
}
if !ftyp_seen {
bail_attach!(ParseError::MissingRequiredBox(BoxType::FTYP));
}
if !mdat_seen {
bail_attach!(ParseError::MissingRequiredBox(BoxType::MDAT));
}
let Some(moov) = moov else {
bail_attach!(ParseError::MissingRequiredBox(BoxType::MOOV));
};
Ok(moov)
}
/// Skip a box's data assuming its header has already been read.
fn skip_box(reader: &mut impl io::Seek, header: &BoxHeader) -> Result<(), io::Error> {
match header.box_data_size().expect("valid header") {
Some(box_size) => reader.seek(io::SeekFrom::Current(box_size as i64))?,
None => reader.seek(io::SeekFrom::End(0))?,
};
Ok(())
}
/// Read a box's data.
fn read_data_sync<T, R>(reader: &mut R, header: BoxHeader, max_size: u64) -> Result<Mp4Box<T>, Error>
where
R: std::io::Read + std::io::Seek,
T: ParseBox + ParsedBox,
{
let async_reader = SeekSkipAdapter(futures_util::io::AllowStdIo::new(reader));
pin_mut!(async_reader);
Mp4Box::read_data(async_reader, header, max_size)
.now_or_never()
.expect("only awaits reader")
}
/// Iterates through every sample in the track in order, ignoring timecodes and edit lists.
pub fn for_each_sample(
track: &TrakBox,
full_data: &[u8],
mut process: impl FnMut(&[u8]) -> Result<(), Error>,
) -> Result<(), Error> {
let samples = track.parsed().media.parsed().info.parsed().samples;
let mut sample_to_chunk_walker = SampleToChunkWalker::new(samples.parsed().sample_to_chunk.entries());
let offset_for_chunk = |i| {
Ok::<usize, Error>(match samples.parsed().chunk_offsets {
mp4san::parse::StblCoRef::Stco(stco) => stco
.entries()
.nth(i as usize - 1)
.ok_or_else(|| report_attach!(ParseError::InvalidInput))?
.get()? as usize,
mp4san::parse::StblCoRef::Co64(co64) => co64
.entries()
.nth(i as usize - 1)
.ok_or_else(|| report_attach!(ParseError::InvalidInput))?
.get()? as usize,
})
};
let mut prev_chunk_index = 0;
let mut current_chunk_data: &[u8] = &[];
for (sample_size, sample_index) in samples.parsed().sample_sizes.sample_sizes().zip(0..) {
let sample_size = sample_size?;
let ChunkInfo { chunk_index, num_samples_in_chunk: _ } = sample_to_chunk_walker.chunk_info_for(sample_index);
if chunk_index != prev_chunk_index {
current_chunk_data = &full_data[offset_for_chunk(chunk_index)?..];
prev_chunk_index = chunk_index;
}
let current_sample = &current_chunk_data[..sample_size as usize];
current_chunk_data.advance(sample_size as usize);
process(current_sample)?;
}
Ok(())
}
#[derive(PartialEq, Eq)]
struct ChunkInfo {
pub chunk_index: u32,
pub num_samples_in_chunk: u32,
}
/// Wraps an iterator over stsc ("sample-to-chunk") entries to allow looking up chunks for given
/// sample indexes.
///
/// Samples must be accessed in increasing order, but skipping is allowed. This type expects sample
/// indexes to be zero-indexed.
struct SampleToChunkWalker<T: Iterator> {
entry_iter: std::iter::Peekable<T>,
chunks_in_entry: Range<u32>,
num_samples_in_chunk: u32,
samples_seen_so_far: usize,
}
impl<'a, T> SampleToChunkWalker<T>
where
T: Iterator<Item = ArrayEntry<'a, StscEntry>>,
{
fn new(iter: T) -> Self {
Self { entry_iter: iter.peekable(), chunks_in_entry: 0..0, num_samples_in_chunk: 0, samples_seen_so_far: 0 }
}
fn advance_one_chunk(&mut self) {
self.samples_seen_so_far += self.num_samples_in_chunk as usize;
_ = self.chunks_in_entry.next();
if self.chunks_in_entry.is_empty() {
let next_entry = self
.entry_iter
.next()
.expect("has more entries")
.get()
.expect("valid sample-to-chunk entry");
let upper_bound = self.entry_iter.peek().map_or(u32::MAX, |following_entry| {
following_entry.get().expect("valid sample-to-chunk entry").first_chunk
});
self.chunks_in_entry = next_entry.first_chunk..upper_bound;
self.num_samples_in_chunk = next_entry.samples_per_chunk;
}
}
fn chunk_info_for(&mut self, sample_index: usize) -> ChunkInfo {
assert!(
sample_index >= self.samples_seen_so_far,
"searching backwards is not implemented"
);
while sample_index - self.samples_seen_so_far >= self.num_samples_in_chunk as usize {
self.advance_one_chunk();
}
ChunkInfo { chunk_index: self.chunks_in_entry.start, num_samples_in_chunk: self.num_samples_in_chunk }
}
}

View File

@ -1,41 +0,0 @@
//! WARNING: This is not a 100% correct implementation of a frame dumper for H.264 in MPEG-4.
//!
//! Many many things were skipped and/or hardcoded. Do not use this as a reference, only a starting
//! point.
use std::io::Read as _;
use std::io::{self, Write};
use bytes::Buf;
use mp4san::parse::MoovBox;
const NAL_HEADER: &[u8] = &[0, 0, 0, 1];
pub fn main() {
env_logger::init();
let mut input = Vec::with_capacity(100 * 1024);
io::stdin().read_to_end(&mut input).expect("can read stdin");
let moov = mp4san_dump::parse(&input).expect("valid input");
let moov_children = moov.data.parsed::<MoovBox>().expect("parsed moov box already").parsed();
// FIXME: The first track isn't always the video track.
if let Some(track) = moov_children.tracks.get(0) {
mp4san_dump::for_each_sample(track, &input, |mut sample| {
// FIXME: not all NAL lengths use four bytes
let nal_length = sample.get_u32() as usize;
assert_eq!(
nal_length,
sample.len(),
"NAL split across samples, or multiple NALs in a sample"
);
std::io::stdout().write_all(NAL_HEADER).expect("can write to stdout");
std::io::stdout().write_all(sample).expect("can write to stdout");
Ok(())
})
.expect("valid parsed input");
}
}

View File

@ -6,9 +6,9 @@ version = "0.1.0"
publish = false
[dependencies]
anyhow = "1.0.68"
clap = { version = "4.0.32", features = ["derive"] }
env_logger = "0.11.3"
libflate = "2.1.0"
log = "0.4.17"
anyhow = { workspace = true }
clap = { workspace = true, features = ["derive"] }
env_logger = { workspace = true }
libflate = { workspace = true }
log = { workspace = true }
mp4san = { path = "../mp4san" }

View File

@ -11,12 +11,12 @@ ffmpeg = ["dep:ac-ffmpeg", "dep:bindgen", "dep:cc", "dep:ffmpeg-sys-next"]
gpac = ["dep:bindgen", "dep:cc", "dep:pkg-config"]
[dependencies]
ac-ffmpeg = { version = "0.18.1", optional = true }
ffmpeg-sys-next = { version = "7.0.0", default-features = false, features = ["avformat"], optional = true }
log = "0.4.17"
thiserror = "1.0.40"
ac-ffmpeg = { workspace = true, optional = true }
ffmpeg-sys-next = { workspace = true, default-features = false, features = ["avformat"], optional = true }
log = { workspace = true }
thiserror = { workspace = true }
[build-dependencies]
bindgen = { version = "0.69.4", optional = true }
cc = { version = "1.0.79", optional = true }
pkg-config = { version = "0.3.26", optional = true }
bindgen = { workspace = true, optional = true }
cc = { workspace = true, optional = true }
pkg-config = { workspace = true, optional = true }

View File

@ -117,139 +117,35 @@ pub fn example_mdat() -> Vec<u8> {
pub fn example_moov() -> Vec<u8> {
const EXAMPLE_MOOV: &[&[u8]] = &[
&[0, 0, 1, 133], // box size
b"moov", // box type
&[0, 0, 0, 56], // box size
b"moov", // box type
//
// trak box (inside moov box)
//
&[0, 0, 1, 17], // box size
&[0, 0, 0, 48], // box size
b"trak", // box type
//
// tkhd box (inside trak box)
//
&[0, 0, 0, 92], // box size
b"tkhd", // box type
&[0, 0, 0, 0], // box version & flags
b"\x64\x18\xa0\x50", // creation time
b"\x64\x18\xa0\x50", // modification time
&[0, 0, 0, 1], // track ID
&[0, 0, 0, 0], // reserved
&[0, 0, 0, 0], // duration
&[0; 8], // reserved
&[0, 0], // layer
&[0, 0], // alternate group
&[0, 0], // volume
&[0, 0], // reserved
&[0, 1, 0, 0], // matrix[0]
&[0, 0, 0, 0], // matrix[1]
&[0, 0, 0, 0], // matrix[2]
&[0, 0, 0, 0], // matrix[3]
&[0, 1, 0, 0], // matrix[4]
&[0, 0, 0, 0], // matrix[5]
&[0, 0, 0, 0], // matrix[6]
&[0, 0, 0, 0], // matrix[7]
&[64, 0, 0, 0], // matrix[8]
&[0, 0, 0, 0], // width
&[0, 0, 0, 0], // height
//
// mdia box (inside trak box)
//
&[0, 0, 0, 173], // box size
b"mdia", // box type
//
// mdhd box (inside mdia box)
//
&[0, 0, 0, 32], // box size
b"mdhd", // box type
&[0, 0, 0, 0], // box version & flags
b"\x64\x18\xa0\x50", // creation time
b"\x64\x18\xa0\x50", // modification time
&[0, 0, 2, 88], // time scale
&[0, 0, 0, 0], // duration
b"\x55\xc4", // padding & language
&[0, 0], // pre defined
//
// hdlr box (inside mdia box)
//
&[0, 0, 0, 33], // box size
b"hdlr", // box type
&[0, 0, 0, 0], // box version & flags
&[0, 0, 0, 0], // pre defined
b"vide", // handler type
&[0; 12], // reserved
b"\0", // name
&[0, 0, 0, 40], // box size
b"mdia", // box type
//
// minf box (inside mdia box)
//
&[0, 0, 0, 100], // box size
b"minf", // box type
&[0, 0, 0, 32], // box size
b"minf", // box type
//
// stbl box (inside minf box)
//
&[0, 0, 0, 92], // box size
&[0, 0, 0, 24], // box size
b"stbl", // box type
//
// stsd box (inside stbl box)
//
&[0, 0, 0, 16], // box size
b"stsd", // box type
&[0, 0, 0, 0], // box version & flags
&[0, 0, 0, 0], // entry count
//
// stts box (inside stbl box)
//
&[0, 0, 0, 16], // box size
b"stts", // box type
&[0, 0, 0, 0], // box version & flags
&[0, 0, 0, 0], // entry count
//
// stsc box (inside stbl box)
//
&[0, 0, 0, 16], // box size
b"stsc", // box type
&[0, 0, 0, 0], // box version & flags
&[0, 0, 0, 0], // entry count
//
// stco box (inside stbl box)
//
&[0, 0, 0, 16], // box size
b"stco", // box type
&[0, 0, 0, 0], // box version & flags
&[0, 0, 0, 0], // entry count
//
// stsz box (inside stbl box)
//
&[0, 0, 0, 20], // box size
b"stsz", // box type
&[0, 0, 0, 0], // box version & flags
&[0, 0, 0, 0], // sample size
&[0, 0, 0, 0], // entry count
//
//
// mvhd box (inside moov box)
//
&[0, 0, 0, 108], // box size
b"mvhd", // box type
&[0, 0, 0, 0], // box version & flags
b"\x64\x18\xa0\x50", // creation time
b"\x64\x18\xa0\x50", // modification time
&[0, 0, 2, 88], // time scale
&[0, 0, 0, 0], // duration
&[0, 1, 0, 0], // rate
&[1, 0], // volume
&[0, 0], // reserved
&[0; 8], // reserved
&[0, 1, 0, 0], // matrix[0]
&[0, 0, 0, 0], // matrix[1]
&[0, 0, 0, 0], // matrix[2]
&[0, 0, 0, 0], // matrix[3]
&[0, 1, 0, 0], // matrix[4]
&[0, 0, 0, 0], // matrix[5]
&[0, 0, 0, 0], // matrix[6]
&[0, 0, 0, 0], // matrix[7]
&[64, 0, 0, 0], // matrix[8]
&[0; 24], // pre defined
&[255, 255, 255, 255], // next track ID
];
EXAMPLE_MOOV.concat()
}

View File

@ -15,25 +15,20 @@ readme = "README.md"
exclude.workspace = true
[dependencies]
bytes = "1.3.0"
derive-where = "1.1.0"
derive_builder = "0.20.0"
derive_more = "0.99.17"
downcast-rs = "1.2.0"
dyn-clonable = "0.9.0"
# fixed 1.28.0 requires rustc 1.79.0 or later
fixed = { version = "<1.28.0", default-features = false }
futures-util = { version = "0.3.28", default-features = false, features = ["io"] }
half = { version = "1.8.2", default-features = false }
log = "0.4.17"
mediasan-common = { path = "../common", version = "=0.5.2" }
mp4san-derive = { path = "../mp4san-derive", version = "=0.5.2" }
nalgebra = { version = "0.32.3", default-features = false }
nonempty = "0.8.1"
paste = "1.0.14"
thiserror = "1.0.38"
bytes = { workspace = true }
derive-where = { workspace = true }
derive_builder = { workspace = true }
derive_more = { workspace = true, features = ["display", "from"] }
downcast-rs = { workspace = true }
dyn-clonable = { workspace = true }
futures-util = { workspace = true, features = ["io"] }
log = { workspace = true }
mediasan-common = { path = "../common", version = "=0.5.3" }
mp4san-derive = { path = "../mp4san-derive", version = "=0.5.3" }
paste = { workspace = true }
thiserror = { workspace = true }
[dev-dependencies]
assert_matches = "1.5.0"
assert_matches = { workspace = true }
mediasan-common-test = { path = "../common-test" }
mp4san-test = { path = "../mp4san-test" }

View File

@ -71,7 +71,7 @@ use mediasan_common::AsyncSkipExt;
use crate::error::Report;
use crate::parse::error::{MultipleBoxes, WhileParsingBox};
use crate::parse::{BoxHeader, BoxType, FourCC, FtypBox, MoovBox, Mp4Box, Mp4Value, ParseError, StblCoRefMut};
use crate::parse::{BoxHeader, BoxType, FourCC, FtypBox, MoovBox, Mp4Box, Mp4Value, ParseError, StblCoMut};
//
// public types
@ -90,6 +90,29 @@ pub struct Config {
/// The default is 1 GiB.
#[builder(default = "1024 * 1024 * 1024")]
pub max_metadata_size: u64,
/// The cumulative MDAT box size
///
/// The value is tightly associated with a specific
/// use case scenario in which the transcoder internally
/// generates a sequence of MDAT boxes, but delivers
/// them compounded as a single monolythic MDAT box.
///
/// In order to avoid constantly updating the single
/// MDAT box size (which may be impossible when the
/// payload is continually encrypted and written to
/// the file), the transcoder instead does the following:
/// a) writes the MDAT box size to be equal to the
/// fixed zero value
/// b) keeps accumulating the box size and passes
/// it as config argument to mp4sanitizer
///
/// IMPORTANT: given the special circumstances of
/// the use case scenario of transcoding on mobile
/// devices, the MDAT box size is expected to not
/// exceed the 32-bit max bytes limit. Hence the
/// cumulative_mdat_box_size is a 32-bit value
#[builder(default = None)]
pub cumulative_mdat_box_size: Option<u32>,
}
/// Sanitized metadata returned by the sanitizer.
@ -120,7 +143,7 @@ pub const COMPATIBLE_BRAND: FourCC = FourCC { value: *b"isom" };
//
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "box data too large: {} > {}", _0, _1)]
#[display("box data too large: {} > {}", _0, _1)]
struct BoxDataTooLarge(u64, u64);
const MAX_FTYP_SIZE: u64 = 1024;
@ -260,7 +283,7 @@ pub async fn sanitize_async_with_config<R: AsyncRead + AsyncSkip>(
while !reader.as_mut().fill_buf().await?.is_empty() {
let start_pos = reader.as_mut().stream_position().await?;
let header = BoxHeader::read(&mut reader)
let mut header = BoxHeader::read(&mut reader)
.await
.map_eof(|_| Error::Parse(report_attach!(ParseError::TruncatedBox, "while parsing box header")))?;
@ -306,6 +329,12 @@ pub async fn sanitize_async_with_config<R: AsyncRead + AsyncSkip>(
}
BoxType::MDAT => {
if let Ok(None) = header.box_data_size() {
if let Some(t) = config.cumulative_mdat_box_size {
header.overwrite_size(t);
}
}
let box_size = skip_box(reader.as_mut(), &header).await? + header.encoded_len();
log::info!("mdat @ 0x{start_pos:08x}: {box_size} bytes");
@ -323,12 +352,14 @@ pub async fn sanitize_async_with_config<R: AsyncRead + AsyncSkip>(
}
BoxType::MOOV => {
let mut read_moov =
Mp4Box::<MoovBox>::read_data(reader.as_mut(), header, config.max_metadata_size).await?;
let children = read_moov.data.parse()?.parsed_mut();
let mut read_moov = Mp4Box::read_data(reader.as_mut(), header, config.max_metadata_size).await?;
let chunk_count = children.tracks.iter().map(|trak| trak.co().entry_count()).sum::<u32>();
let trak_count = children.tracks.len();
let moov_data: &mut MoovBox = read_moov.data.parse()?;
let trak_chunk_counts = moov_data
.traks()
.map(|trak| Ok::<_, Report<_>>(trak?.co_mut()?.entry_count()));
let chunk_count = trak_chunk_counts.reduce(|a, b| Ok(a? + b?)).unwrap_or(Ok(0))?;
let trak_count = moov_data.traks().count();
log::info!("moov @ 0x{start_pos:08x}: {trak_count} traks {chunk_count} chunks");
moov = Some(read_moov);
@ -403,23 +434,25 @@ pub async fn sanitize_async_with_config<R: AsyncRead + AsyncSkip>(
log::info!("metadata: 0x{metadata_len:08x} bytes; displacing chunk offsets by 0x{mdat_displacement:08x}");
for trak in moov.data.parse()?.parsed_mut().tracks.iter_mut() {
match trak.co_mut() {
StblCoRefMut::Stco(stco) => {
for mut entry in &mut stco.entries_mut() {
let value = entry.get().unwrap_or_else(|_| unreachable!());
entry.set(checked_add_signed(value, mdat_displacement).ok_or_else(|| {
for trak in &mut moov.data.parse()?.traks() {
let co = trak?.co_mut()?;
if let StblCoMut::Stco(stco) = co {
for mut entry in &mut stco.entries_mut() {
let value = entry.get().unwrap_or_else(|_| unreachable!());
entry.set(
checked_add_signed(value, mdat_displacement).ok_or_else(|| {
report_attach!(ParseError::InvalidInput, "chunk offset not within mdat")
})?);
}
})?,
);
}
StblCoRefMut::Co64(co64) => {
for mut entry in &mut co64.entries_mut() {
let value = entry.get().unwrap_or_else(|_| unreachable!());
entry.set(checked_add_signed(value, mdat_displacement.into()).ok_or_else(|| {
} else if let StblCoMut::Co64(co64) = co {
for mut entry in &mut co64.entries_mut() {
let value = entry.get().unwrap_or_else(|_| unreachable!());
entry.set(
checked_add_signed(value, mdat_displacement.into()).ok_or_else(|| {
report_attach!(ParseError::InvalidInput, "chunk offset not within mdat")
})?);
}
})?,
);
}
}
}
@ -561,13 +594,13 @@ mod test {
let mut test = test_mp4().mdat_data(vec![]).clone();
let test_data_len = test.mdat_data_len(u64::MAX - 16).build().data.len() as u64;
let test = test.mdat_data_len(u64::MAX - test_data_len + 1).build();
dbg!(sanitize(test).unwrap_err());
sanitize(test).unwrap_err();
}
#[test]
fn box_size_overflow() {
let test = test_mp4().mdat_data_len(u64::MAX - 16).build();
dbg!(sanitize(test).unwrap_err());
sanitize(test).unwrap_err();
}
#[test]
@ -580,7 +613,7 @@ mod test {
let test = test_mp4()
.ftyp(test_ftyp().compatible_brands(compatible_brands).clone())
.build();
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::InvalidInput);
});
}
@ -602,7 +635,7 @@ mod test {
.build();
let test = test_spec.build();
test.sanitize_ok();
assert_matches!(dbg!(sanitize_with_config(test, config).unwrap_err()), Error::Parse(err) => {
assert_matches!(sanitize_with_config(test, config).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::InvalidInput);
});
}
@ -615,7 +648,7 @@ mod test {
#[test]
fn no_ftyp() {
let test = test_mp4().boxes(&[MOOV, MDAT][..]).build();
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::InvalidBoxLayout);
});
}
@ -623,7 +656,7 @@ mod test {
#[test]
fn multiple_ftyp() {
let test = test_mp4().boxes(&[FTYP, FTYP, MOOV, MDAT][..]).build();
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::InvalidBoxLayout);
});
}
@ -637,7 +670,7 @@ mod test {
#[test]
fn ftyp_not_first_significant_box() {
let test = test_mp4().boxes(&[MOOV, FTYP, MDAT][..]).build();
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::InvalidBoxLayout);
});
}
@ -645,7 +678,7 @@ mod test {
#[test]
fn no_moov() {
let test = test_mp4().boxes(&[FTYP, MDAT][..]).build();
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::MissingRequiredBox(MOOV));
});
}
@ -653,7 +686,7 @@ mod test {
#[test]
fn no_mdat() {
let test = test_mp4().boxes(&[FTYP, MOOV][..]).build();
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::MissingRequiredBox(MDAT));
});
}
@ -693,7 +726,7 @@ mod test {
#[test]
fn uuid() {
let test = test_mp4().boxes(&[FTYP, MOOV, TEST_UUID, MDAT][..]).build();
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::UnsupportedBox(TEST_UUID));
});
}
@ -703,7 +736,7 @@ mod test {
let test = test_mp4()
.ftyp(test_ftyp().major_brand(MP41).add_compatible_brand(MP41).clone())
.build();
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::UnsupportedFormat(MP41));
});
}
@ -723,7 +756,7 @@ mod test {
let test = test_mp4()
.ftyp(test_ftyp().major_brand(ISOM).compatible_brands(vec![]).clone())
.build();
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::UnsupportedFormat(ISOM));
});
}
@ -731,7 +764,7 @@ mod test {
#[test]
fn no_trak() {
let test = test_mp4().moov(test_moov().trak(false).clone()).build();
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::MissingRequiredBox(TRAK));
});
}
@ -742,7 +775,7 @@ mod test {
.boxes(&[FTYP, MDAT, MOOV][..])
.moov(test_moov().mdia(false).clone())
.build();
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::MissingRequiredBox(MDIA));
});
}
@ -753,7 +786,7 @@ mod test {
.boxes(&[FTYP, MDAT, MOOV][..])
.moov(test_moov().minf(false).clone())
.build();
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::MissingRequiredBox(MINF));
});
}
@ -764,7 +797,7 @@ mod test {
.boxes(&[FTYP, MDAT, MOOV][..])
.moov(test_moov().stbl(false).clone())
.build();
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::MissingRequiredBox(STBL));
});
}
@ -775,7 +808,7 @@ mod test {
.boxes(&[FTYP, MDAT, MOOV][..])
.moov(test_moov().stco(false).clone())
.build();
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::MissingRequiredBox(STCO | CO64));
});
}
@ -795,8 +828,26 @@ mod test {
.boxes(&[FTYP, MDAT, MOOV][..])
.moov(test_moov().co64(true).clone())
.build();
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::InvalidBoxLayout);
});
}
#[test]
fn cumulative_mdat_box_size() {
let test_spec = test_mp4().mdat_data_until_eof().build_spec().unwrap();
let test_1 = test_spec.build();
let mdat_box_length = test_1.mdat.len as u32;
let config_bad = Config::builder().build();
assert_matches!(sanitize_with_config(test_1, config_bad).unwrap_err(), Error::Parse(err) => {
assert_matches!(err.into_inner(), ParseError::MissingRequiredBox(_));
});
let test_2 = test_spec.build();
let config_good = Config::builder()
.cumulative_mdat_box_size(Some(mdat_box_length))
.build();
test_2.sanitize_ok_with_config(config_good);
}
}

View File

@ -1,30 +1,17 @@
//! Unstable API for parsing individual MP4 box types.
#[macro_use]
mod macros;
mod array;
mod co64;
pub mod derive;
pub mod error;
mod ftyp;
mod hdlr;
mod header;
mod integers;
mod mdhd;
mod mdia;
mod minf;
mod moov;
mod mp4box;
mod mvhd;
mod stbl;
mod stco;
mod string;
mod stsc;
mod stsd;
mod stsz;
mod stts;
mod tkhd;
mod trak;
mod value;
@ -32,36 +19,22 @@ pub use array::{ArrayEntry, ArrayEntryMut, BoundedArray, UnboundedArray};
pub use co64::Co64Box;
pub use error::ParseError;
pub use ftyp::FtypBox;
pub use hdlr::HdlrBox;
pub use header::{box_type, fourcc, BoxFlags, BoxHeader, BoxSize, BoxType, BoxUuid, ConstFullBoxHeader, FullBoxHeader};
pub use integers::{
ConstI16, ConstI32, ConstI64, ConstI8, ConstU16, ConstU32, ConstU64, ConstU8, Mp4Prim, Mp4Transform,
};
pub use mdhd::MdhdBox;
pub use mdia::{MdiaBox, MdiaChildren, MdiaChildrenRef, MdiaChildrenRefMut};
pub use minf::{MinfBox, MinfChildren, MinfChildrenRef, MinfChildrenRefMut};
pub use moov::{MoovBox, MoovChildren, MoovChildrenRef, MoovChildrenRefMut};
pub use mp4box::{AnyMp4Box, BoundedBoxes, BoxData, Boxes, Mp4Box, ParseBox, ParseBoxes, ParsedBox};
pub use mvhd::MvhdBox;
pub use stbl::{StblBox, StblChildren, StblChildrenRef, StblChildrenRefMut, StblCo, StblCoRef, StblCoRefMut};
pub use header::{box_type, fourcc, BoxHeader, BoxSize, BoxType, BoxUuid, ConstFullBoxHeader, FullBoxHeader};
pub use integers::Mp4Prim;
pub use mdia::MdiaBox;
pub use minf::MinfBox;
pub use moov::MoovBox;
pub use mp4box::{AnyMp4Box, BoxData, Boxes, BoxesValidator, Mp4Box, ParseBox, ParsedBox};
pub use stbl::{StblBox, StblCoMut};
pub use stco::StcoBox;
pub use string::Mp4String;
pub use stsc::{StscBox, StscEntry};
pub use stsd::StsdBox;
pub use stsz::StszBox;
pub use stts::{SttsBox, SttsEntry};
pub use tkhd::TkhdBox;
pub use trak::{TrakBox, TrakChildren, TrakChildrenRef, TrakChildrenRefMut};
pub use trak::TrakBox;
pub use value::{Mp4Value, Mp4ValueReaderExt, Mp4ValueWriterExt};
pub use mediasan_common::parse::FourCC;
pub use mp4san_derive::{Mp4Prim, Mp4Value, ParseBox, ParseBoxes, ParsedBox};
pub use mp4san_derive::{ParseBox, ParsedBox};
#[cfg(test)]
mod test {
use std::fmt::Debug;
use assert_matches::assert_matches;
use bytes::BytesMut;
use super::*;
@ -93,38 +66,12 @@ mod test {
pub unbounded_array: UnboundedArray<u8>,
}
#[derive(Clone, Debug, ParseBox, ParsedBox)]
#[box_type = "vers"]
pub enum VersionedBox {
V0 {
_parsed_header: ConstFullBoxHeader,
foo: u32,
},
V1(ConstFullBoxHeader<1, 101>, u64),
V2(ConstFullBoxHeader<2, 102>),
}
#[derive(Clone, Debug, ParseBox, ParsedBox)]
#[box_type = "vers"]
pub enum StrictVersionedBox {
V0(ConstFullBoxHeader),
V1 { parsed_header: ConstFullBoxHeader<1> },
}
fn parse_box<T: ParseBox>(bytes: impl AsRef<[u8]>) -> T {
T::parse(&mut BytesMut::from(bytes.as_ref())).unwrap()
}
fn parse_box_err<T: ParseBox + Debug>(bytes: impl AsRef<[u8]>) -> ParseError {
T::parse(&mut BytesMut::from(bytes.as_ref())).unwrap_err().into_inner()
}
#[test]
fn test_size_simple() {
let not_a_real = NotARealBox { bar_ax: u64::MAX, foo_by: u32::MAX };
assert_eq!(
not_a_real.encoded_len(),
(<u64 as Mp4Prim>::ENCODED_LEN + <u32 as Mp4Prim>::ENCODED_LEN) as u64
(<u64 as Mp4Prim>::encoded_len() + <u32 as Mp4Prim>::encoded_len()) as u64
);
}
@ -135,13 +82,16 @@ mod test {
#[test]
fn test_type_bytes() {
assert_eq!(NotARealBox::NAME, BoxType::FourCC(FourCC { value: *b"\xffX0\x00" }));
assert_eq!(
NotARealBox::box_type(),
BoxType::FourCC(FourCC { value: *b"\xffX0\x00" })
);
}
#[test]
fn test_type_compact_int_decimal() {
assert_eq!(
AnotherFakeBox::NAME,
AnotherFakeBox::box_type(),
BoxType::FourCC(FourCC { value: 4283969538u32.to_be_bytes() })
);
}
@ -149,53 +99,12 @@ mod test {
#[test]
fn test_type_extended() {
let expected = BoxType::Uuid(BoxUuid { value: 0xc12fdd3f_1e93_464c_baee_7c4480628f58u128.to_be_bytes() });
assert_eq!(FakeUuidTypeBox::NAME, expected);
assert_eq!(FakeUuidTypeBox::box_type(), expected);
}
#[test]
fn test_type_compact_str() {
assert_eq!(Fifth::NAME, BoxType::FourCC(FourCC { value: *b"xa04" }));
}
#[test]
fn versioned() {
assert_matches!(
parse_box([0, 0, 0, 0, 0, 0, 0, 32]),
VersionedBox::V0 { _parsed_header: ConstFullBoxHeader, foo: 32 }
);
assert_matches!(
parse_box([1, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, 64]),
VersionedBox::V1(ConstFullBoxHeader, 64)
);
assert_matches!(parse_box([2, 0, 0, 102]), VersionedBox::V2(ConstFullBoxHeader));
assert_matches!(parse_box_err::<VersionedBox>([2, 0, 0, 0]), ParseError::InvalidInput);
assert_matches!(
parse_box_err::<VersionedBox>([3, 0, 0, 103, 104]),
ParseError::InvalidInput
);
}
#[test]
fn versioned_truncated() {
assert_matches!(parse_box_err::<VersionedBox>([]), ParseError::TruncatedBox);
assert_matches!(parse_box_err::<VersionedBox>([0, 0, 0, 0]), ParseError::TruncatedBox);
assert_matches!(
parse_box_err::<VersionedBox>([1, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0]),
ParseError::TruncatedBox
);
}
#[test]
fn versioned_extra() {
assert_matches!(parse_box_err::<VersionedBox>([0; 9]), ParseError::InvalidInput);
assert_matches!(
parse_box_err::<VersionedBox>([1, 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
ParseError::InvalidInput
);
assert_matches!(
parse_box_err::<VersionedBox>([2, 0, 0, 102, 1]),
ParseError::InvalidInput
);
assert_eq!(Fifth::box_type(), BoxType::FourCC(FourCC { value: *b"xa04" }));
}
#[test]

View File

@ -12,15 +12,15 @@ use crate::error::Result;
use super::{Mp4Prim, Mp4Value, Mp4ValueWriterExt, ParseError};
#[derive(PartialEq, Eq)]
#[derive_where(Clone, Debug, Default; C)]
#[derive(Default, PartialEq, Eq)]
#[derive_where(Clone, Debug; C)]
pub struct BoundedArray<C, T> {
entry_count: C,
array: UnboundedArray<T>,
}
#[derive(PartialEq, Eq)]
#[derive_where(Clone, Debug, Default)]
#[derive(Default, PartialEq, Eq)]
#[derive_where(Clone, Debug)]
pub struct UnboundedArray<T> {
entries: BytesMut,
_t: PhantomData<T>,
@ -59,7 +59,7 @@ impl<C: Clone, T: Mp4Prim> BoundedArray<C, T> {
impl<C: Mp4Prim + Into<u32> + Clone, T: Mp4Prim> Mp4Value for BoundedArray<C, T> {
fn parse(buf: &mut BytesMut) -> Result<Self, ParseError> {
let entry_count = C::parse(&mut *buf).while_parsing_type()?;
let entries_len = (T::ENCODED_LEN as u32)
let entries_len = (T::encoded_len() as u32)
.checked_mul(entry_count.clone().into())
.ok_or_else(|| report_attach!(ParseError::InvalidInput, "overflow", WhileParsingType::new::<Self>()))?;
ensure_attach!(
@ -73,7 +73,7 @@ impl<C: Mp4Prim + Into<u32> + Clone, T: Mp4Prim> Mp4Value for BoundedArray<C, T>
}
fn encoded_len(&self) -> u64 {
C::ENCODED_LEN + self.array.encoded_len()
C::encoded_len() + self.array.encoded_len()
}
fn put_buf<B: BufMut>(&self, mut buf: B) {
@ -96,18 +96,18 @@ impl<C: From<u32>, T: Mp4Prim> FromIterator<T> for BoundedArray<C, T> {
impl<T: Mp4Prim> UnboundedArray<T> {
pub fn entries(&self) -> impl Iterator<Item = ArrayEntry<'_, T>> + ExactSizeIterator + '_ {
self.entries
.chunks_exact(T::ENCODED_LEN as usize)
.chunks_exact(T::encoded_len() as usize)
.map(|data| ArrayEntry { data, _t: PhantomData })
}
pub fn entries_mut(&mut self) -> impl Iterator<Item = ArrayEntryMut<'_, T>> + ExactSizeIterator + '_ {
self.entries
.chunks_exact_mut(T::ENCODED_LEN as usize)
.chunks_exact_mut(T::encoded_len() as usize)
.map(|data| ArrayEntryMut { data, _t: PhantomData })
}
pub fn entry_count(&self) -> usize {
self.entries.len() / T::ENCODED_LEN as usize
self.entries.len() / T::encoded_len() as usize
}
}

View File

@ -1,6 +1,6 @@
#![allow(missing_docs)]
use super::{ArrayEntry, ArrayEntryMut, BoundedArray, ConstFullBoxHeader, ParseBox, ParsedBox};
use super::{ArrayEntryMut, BoundedArray, ConstFullBoxHeader, ParseBox, ParsedBox};
#[derive(Clone, Debug, Default, ParseBox, ParsedBox)]
#[box_type = "co64"]
@ -10,10 +10,6 @@ pub struct Co64Box {
}
impl Co64Box {
pub fn entries(&self) -> impl Iterator<Item = ArrayEntry<'_, u64>> + ExactSizeIterator + '_ {
self.entries.entries()
}
pub fn entries_mut(&mut self) -> impl Iterator<Item = ArrayEntryMut<'_, u64>> + ExactSizeIterator + '_ {
self.entries.entries_mut()
}

View File

@ -1,3 +0,0 @@
//! Unstable API used by the derive macros' generated code.
pub mod parse_boxes;

View File

@ -1,185 +0,0 @@
//! Unstable API used by the `#[derive(ParseBoxes)]` macro's generated code.
use std::iter;
use std::option;
use std::vec;
use derive_where::derive_where;
use nonempty::NonEmpty;
use crate::parse::ParseBox;
/// A type which can be used as a field in a `#[derive(ParseBoxes)]`.
pub trait Field {
/// The type of box that this field contains.
///
/// For example, for `Vec<T>`, this would be `T`.
type Type: ParseBox;
/// A type which can accumulate values of type `T` to construct a container like `Self`.
///
/// For example, for `Vec<U>`, this could be `Vec<T>`. For a plain `U`, this could be `Option<U>`.
type Accumulator<T>: Accumulator<T>;
/// The type returned by `<Self as Field>::into_iter`.
type IntoIter: Iterator<Item = Self::Type>;
/// Creates an [`Iterator`] of boxes contained in this field.
fn into_iter(self) -> Self::IntoIter;
}
/// A type which can accumulate values and unwrap into another type containing those values.
pub trait Accumulator<T> {
/// The type that this accumulator [unwraps](Self::unwrap) to.
type Unwrapped;
/// Appends an element to the accumulator.
fn push(&mut self, field: T);
/// Returns `true` if no more elements can be accumulated.
fn is_full(&self) -> bool;
/// Constructs a container from the values accumulated, if possible.
fn unwrap(self) -> Option<Self::Unwrapped>;
}
#[doc(hidden)]
#[derive(Clone)]
#[derive_where(Default)]
/// An [`Accumulator`] to build a [`T`].
pub struct PlainFieldAccumulator<T> {
value: Option<T>,
}
#[doc(hidden)]
#[derive(Clone)]
#[derive_where(Default)]
/// An [`Accumulator`] of `T`s to build a [`NonEmpty<T>`].
pub struct NonEmptyAccumulator<T> {
head: Option<T>,
tail: Vec<T>,
}
//
// Field impls
//
impl<T: ParseBox> Field for T {
type Type = Self;
type Accumulator<U> = PlainFieldAccumulator<U>;
type IntoIter = iter::Once<T>;
fn into_iter(self) -> Self::IntoIter {
iter::once(self)
}
}
impl<T: ParseBox> Field for Option<T> {
type Type = T;
type Accumulator<U> = Option<U>;
type IntoIter = option::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
IntoIterator::into_iter(self)
}
}
impl<T: ParseBox> Field for Vec<T> {
type Type = T;
type Accumulator<U> = Vec<U>;
type IntoIter = vec::IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
IntoIterator::into_iter(self)
}
}
impl<T: ParseBox> Field for NonEmpty<T> {
type Type = T;
type Accumulator<U> = NonEmptyAccumulator<U>;
type IntoIter = <NonEmpty<T> as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
IntoIterator::into_iter(self)
}
}
//
// Accumulator impls
//
impl<T> Accumulator<T> for Option<T> {
type Unwrapped = Option<T>;
fn push(&mut self, field: T) {
*self = Some(field);
}
fn is_full(&self) -> bool {
self.is_some()
}
fn unwrap(self) -> Option<Self::Unwrapped> {
Some(self)
}
}
impl<T> Accumulator<T> for Vec<T> {
type Unwrapped = Self;
fn push(&mut self, field: T) {
self.push(field);
}
fn is_full(&self) -> bool {
false
}
fn unwrap(self) -> Option<Self::Unwrapped> {
Some(self)
}
}
//
// PlainFieldAccumulator impls
//
impl<T> Accumulator<T> for PlainFieldAccumulator<T> {
type Unwrapped = T;
fn push(&mut self, field: T) {
self.value = Some(field);
}
fn is_full(&self) -> bool {
self.value.is_some()
}
fn unwrap(self) -> Option<Self::Unwrapped> {
self.value
}
}
//
// NonEmptyAccumulator impls
//
impl<T> Accumulator<T> for NonEmptyAccumulator<T> {
type Unwrapped = NonEmpty<T>;
fn push(&mut self, field: T) {
if self.head.is_none() {
self.head = Some(field);
} else {
self.tail.push(field);
}
}
fn is_full(&self) -> bool {
false
}
fn unwrap(self) -> Option<Self::Unwrapped> {
self.head.map(|head| NonEmpty { head, tail: self.tail })
}
}

View File

@ -51,12 +51,10 @@ pub enum ParseError {
#[doc(hidden)]
/// Used by the derive macros' generated code.
pub trait __ParseResultExt: ResultExt + Sized {
#[track_caller]
fn while_parsing_box(self, box_type: BoxType) -> Self {
self.attach_printable(WhileParsingBox(box_type))
}
#[track_caller]
fn while_parsing_field<T>(self, box_type: BoxType, field_name: T) -> Self
where
T: Display + Debug + Send + Sync + 'static,
@ -64,12 +62,10 @@ pub trait __ParseResultExt: ResultExt + Sized {
self.attach_printable(WhileParsingField(box_type, field_name))
}
#[track_caller]
fn while_parsing_child(self, box_type: BoxType, child_box_type: BoxType) -> Self {
self.attach_printable(WhileParsingChild(box_type, child_box_type))
}
#[track_caller]
fn where_eq<T, U>(self, lhs: T, rhs: U) -> Self
where
T: Display + Debug + Send + Sync + 'static,
@ -81,23 +77,23 @@ pub trait __ParseResultExt: ResultExt + Sized {
pub(crate) use self::__ParseResultExt as ParseResultExt;
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "multiple `{}` boxes", _0)]
#[display("multiple `{}` boxes", _0)]
pub(crate) struct MultipleBoxes(pub(crate) BoxType);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "while parsing `{}` box", _0)]
#[display("while parsing `{}` box", _0)]
pub(crate) struct WhileParsingBox(pub(crate) BoxType);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "while parsing `{}` box field `{}`", _0, _1)]
#[display("while parsing `{}` box field `{}`", _0, _1)]
pub(crate) struct WhileParsingField<T>(pub(crate) BoxType, pub(crate) T);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "while parsing `{}` box child `{}`", _0, _1)]
#[display("while parsing `{}` box child `{}`", _0, _1)]
pub(crate) struct WhileParsingChild(pub(crate) BoxType, pub(crate) BoxType);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "where `{} = {}`", _0, _1)]
#[display("where `{} = {}`", _0, _1)]
pub(crate) struct WhereEq<T, U>(pub(crate) T, pub(crate) U);
impl ReportableError for ParseError {

View File

@ -1,34 +0,0 @@
#![allow(missing_docs)]
use super::{ConstFullBoxHeader, ConstU32, FourCC, Mp4String, ParseBox, ParsedBox};
#[derive(Clone, Debug, ParseBox, ParsedBox)]
#[box_type = "hdlr"]
pub struct HdlrBox {
_parsed_header: ConstFullBoxHeader,
_pre_defined: ConstU32,
handler_type: FourCC,
_reserved: [ConstU32; 3],
name: Mp4String,
}
impl HdlrBox {
define_fourcc_lower!(VIDE, SOUN, HINT, AUXV, NULL);
}
#[cfg(test)]
mod test {
use super::*;
impl HdlrBox {
pub(crate) fn dummy() -> Self {
Self {
_parsed_header: Default::default(),
_pre_defined: Default::default(),
handler_type: Self::NULL,
_reserved: Default::default(),
name: Default::default(),
}
}
}
}

View File

@ -49,15 +49,11 @@ pub struct BoxUuid {
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct FullBoxHeader {
pub version: u8,
pub flags: BoxFlags,
pub flags: u32,
}
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Default, From, PartialEq, Eq)]
pub struct BoxFlags(pub u32);
#[allow(missing_docs)]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[derive(Clone, Copy, Debug, Default)]
pub struct ConstFullBoxHeader<const VERSION: u8 = 0, const FLAGS: u32 = 0>;
#[allow(missing_docs)]
@ -135,6 +131,11 @@ impl BoxHeader {
Ok(Self { box_type: name, box_size: size })
}
pub fn overwrite_size(&mut self, actual_box_size: u32) {
assert_eq!(self.box_size, BoxSize::UntilEof);
self.box_size = BoxSize::Size(actual_box_size);
}
pub const fn encoded_len(&self) -> u64 {
let mut size = FourCC::size() + size_of::<u32>() as u64;
if let BoxSize::Ext(_) = self.box_size {
@ -276,7 +277,7 @@ impl fmt::Display for BoxUuid {
#[allow(missing_docs)]
impl FullBoxHeader {
pub const fn default() -> Self {
Self { version: 0, flags: BoxFlags(0) }
Self { version: 0, flags: 0 }
}
pub fn ensure_eq(&self, other: &Self) -> Result<(), ParseError> {
@ -288,62 +289,48 @@ impl FullBoxHeader {
ensure_attach!(
self.flags == other.flags,
ParseError::InvalidInput,
format!(
"box flags 0b{:024b} do not match 0b{:024b}",
self.flags.0, other.flags.0
),
format!("box flags 0b{:024b} do not match 0b{:024b}", self.flags, other.flags),
);
Ok(())
}
}
impl Mp4Prim for FullBoxHeader {
const ENCODED_LEN: u64 = u8::ENCODED_LEN + BoxFlags::ENCODED_LEN;
fn parse<B: Buf + AsRef<[u8]>>(mut buf: B) -> Result<Self, ParseError> {
ensure_attach!(buf.remaining() >= Self::ENCODED_LEN as usize, ParseError::TruncatedBox);
fn parse<B: Buf>(mut buf: B) -> Result<Self, ParseError> {
let version = u8::parse(&mut buf)?;
let flags = BoxFlags::parse(&mut buf)?;
let flags = <[u8; 3]>::parse(buf)?;
let flags = u32::from_be_bytes([0, flags[0], flags[1], flags[2]]);
Ok(Self { version, flags })
}
fn encoded_len() -> u64 {
4
}
fn put_buf<B: BufMut>(&self, mut out: B) {
out.put_u8(self.version);
self.flags.put_buf(out);
}
}
impl Mp4Prim for BoxFlags {
const ENCODED_LEN: u64 = 3;
fn parse<B: Buf + AsRef<[u8]>>(buf: B) -> Result<Self, ParseError> {
ensure_attach!(buf.remaining() >= Self::ENCODED_LEN as usize, ParseError::TruncatedBox);
let value = <[u8; 3]>::parse(buf)?;
let value = u32::from_be_bytes([0, value[0], value[1], value[2]]);
Ok(Self(value))
}
fn put_buf<B: BufMut>(&self, mut out: B) {
out.put_uint(self.0.into(), 3);
out.put_uint(self.flags.into(), 3);
}
}
impl<const VERSION: u8, const FLAGS: u32> From<ConstFullBoxHeader<VERSION, FLAGS>> for FullBoxHeader {
fn from(_: ConstFullBoxHeader<VERSION, FLAGS>) -> Self {
Self { version: VERSION, flags: BoxFlags(FLAGS) }
Self { version: VERSION, flags: FLAGS }
}
}
impl<const VERSION: u8, const FLAGS: u32> Mp4Prim for ConstFullBoxHeader<VERSION, FLAGS> {
const ENCODED_LEN: u64 = FullBoxHeader::ENCODED_LEN;
fn parse<B: Buf + AsRef<[u8]>>(mut buf: B) -> Result<Self, ParseError> {
FullBoxHeader::parse(buf.as_ref())?.ensure_eq(&Self.into())?;
buf.advance(Self::ENCODED_LEN as usize);
fn parse<B: Buf>(buf: B) -> Result<Self, ParseError> {
FullBoxHeader::parse(buf)?.ensure_eq(&Self.into())?;
Ok(Self)
}
fn put_buf<B: BufMut>(&self, out: B) {
FullBoxHeader::from(*self).put_buf(out)
fn encoded_len() -> u64 {
FullBoxHeader::encoded_len()
}
fn put_buf<B: BufMut>(&self, mut out: B) {
out.put_u8(VERSION);
out.put_uint(FLAGS.into(), 3);
}
}

View File

@ -1,157 +1,66 @@
#![allow(missing_docs)]
use std::fmt;
use std::mem::size_of;
use std::num::{NonZeroI16, NonZeroI32, NonZeroI64, NonZeroI8, NonZeroU16, NonZeroU32, NonZeroU64, NonZeroU8};
use bytes::{Buf, BufMut};
use fixed::traits::Fixed;
use fixed::types::{I16F16, I2F30};
use fixed::{FixedI16, FixedI32, FixedI64, FixedI8, FixedU16, FixedU32, FixedU64, FixedU8};
use bytes::Buf;
use bytes::BufMut;
use mediasan_common::error::WhileParsingType;
use mediasan_common::ResultExt;
use nalgebra::{Matrix3, Matrix3x2, Vector3};
use crate::error::Result;
use super::error::WhereEq;
use super::{FourCC, Mp4ValueWriterExt, ParseError};
//
// types
//
pub trait Mp4Prim: Sized {
const ENCODED_LEN: u64;
fn parse<B: Buf + AsRef<[u8]>>(buf: B) -> Result<Self, ParseError>;
fn parse<B: Buf>(buf: B) -> Result<Self, ParseError>;
fn encoded_len() -> u64;
fn put_buf<B: BufMut>(&self, buf: B);
}
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct ConstU8<const N: u8 = 0>;
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct ConstU16<const N: u16 = 0>;
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct ConstU32<const N: u32 = 0>;
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct ConstU64<const N: u64 = 0>;
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct ConstI8<const N: i8 = 0>;
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct ConstI16<const N: i16 = 0>;
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct ConstI32<const N: i32 = 0>;
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct ConstI64<const N: i64 = 0>;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Mp4Transform {
pub transform: Matrix3x2<I16F16>,
pub normalizer: Vector3<I2F30>,
}
trait Mp4Fixed {}
//
// Mp4Prim impls
//
macro_rules! mp4_int {
($($ty:ty, $nonzero_ty:ty => ($get_fun:ident, $put_fun:ident)),+ $(,)?) => {
($($ty:ty => ($get_fun:ident, $put_fun:ident)),+ $(,)?) => {
$(impl Mp4Prim for $ty {
const ENCODED_LEN: u64 = size_of::<Self>() as u64;
fn parse<B: Buf>(mut buf: B) -> Result<Self, ParseError> {
ensure_attach!(
buf.remaining() >= Self::ENCODED_LEN as usize,
ParseError::TruncatedBox,
WhileParsingType::new::<$ty>(),
);
if buf.remaining() < Self::encoded_len() as usize {
bail_attach!(ParseError::TruncatedBox, WhileParsingType::new::<$ty>());
}
Ok(buf.$get_fun())
}
fn encoded_len() -> u64 {
size_of::<Self>() as u64
}
fn put_buf<B: BufMut>(&self, mut buf: B) {
buf.$put_fun(*self)
}
})+
$(impl Mp4Prim for $nonzero_ty {
const ENCODED_LEN: u64 = size_of::<Self>() as u64;
fn parse<B: Buf>(mut buf: B) -> Result<Self, ParseError> {
ensure_attach!(
buf.remaining() >= Self::ENCODED_LEN as usize,
ParseError::TruncatedBox,
WhileParsingType::new::<$ty>(),
);
let Some(value) = <$nonzero_ty>::new(buf.$get_fun()) else {
bail_attach!(
ParseError::InvalidInput,
WhileParsingType::new::<$nonzero_ty>(),
);
};
Ok(value)
}
fn put_buf<B: BufMut>(&self, mut buf: B) {
buf.$put_fun(self.get())
}
})+
};
}
mp4_int! {
u8, NonZeroU8 => (get_u8, put_u8),
u16, NonZeroU16 => (get_u16, put_u16),
u32, NonZeroU32 => (get_u32, put_u32),
u64, NonZeroU64 => (get_u64, put_u64),
i8, NonZeroI8 => (get_i8, put_i8),
i16, NonZeroI16 => (get_i16, put_i16),
i32, NonZeroI32 => (get_i32, put_i32),
i64, NonZeroI64 => (get_i64, put_i64),
u8 => (get_u8, put_u8),
u16 => (get_u16, put_u16),
u32 => (get_u32, put_u32),
u64 => (get_u64, put_u64),
i8 => (get_i8, put_i8),
i16 => (get_i16, put_i16),
i32 => (get_i32, put_i32),
i64 => (get_i64, put_i64),
}
impl<T: Fixed + Mp4Fixed> Mp4Prim for T
where
T::Bits: Mp4Prim,
{
const ENCODED_LEN: u64 = <T::Bits>::ENCODED_LEN;
fn parse<B: Buf + AsRef<[u8]>>(buf: B) -> Result<Self, ParseError> {
let bits = <T::Bits>::parse(buf)?;
Ok(T::from_bits(bits))
}
fn put_buf<B: BufMut>(&self, buf: B) {
self.to_bits().put_buf(buf)
}
}
impl<T: Mp4Prim, const N: usize> Mp4Prim for [T; N]
where
[T; N]: Default,
{
const ENCODED_LEN: u64 = T::ENCODED_LEN * N as u64;
fn parse<B: Buf + AsRef<[u8]>>(mut buf: B) -> Result<Self, ParseError> {
impl<T: Mp4Prim, const N: usize> Mp4Prim for [T; N] {
fn parse<B: Buf>(mut buf: B) -> Result<Self, ParseError> {
ensure_attach!(
buf.remaining() >= Self::ENCODED_LEN as usize,
buf.remaining() >= Self::encoded_len() as usize,
ParseError::TruncatedBox,
WhileParsingType::new::<Self>(),
);
let mut parsed: [T; N] = Default::default();
for value in &mut parsed {
*value = T::parse(&mut buf)?;
}
Ok(parsed)
Ok([(); N].map(|()| T::parse(&mut buf).unwrap_or_else(|_| unreachable!())))
}
fn encoded_len() -> u64 {
size_of::<Self>() as u64
}
fn put_buf<B: BufMut>(&self, mut buf: B) {
@ -162,124 +71,15 @@ where
}
impl Mp4Prim for FourCC {
fn parse<B: Buf + AsRef<[u8]>>(buf: B) -> Result<Self, ParseError> {
fn parse<B: Buf>(buf: B) -> Result<Self, ParseError> {
Mp4Prim::parse(buf).map(|value| Self { value }).while_parsing_type()
}
const ENCODED_LEN: u64 = Self::size();
fn encoded_len() -> u64 {
Self::size()
}
fn put_buf<B: BufMut>(&self, mut buf: B) {
buf.put_mp4_value(&self.value);
}
}
//
// Const* impls
//
macro_rules! mp4_const_int {
($($ident:ident => $ty:ty),+ $(,)?) => {
$(
impl<const N: $ty> Mp4Prim for $ident<N> {
const ENCODED_LEN: u64 = <$ty>::ENCODED_LEN;
fn parse<B: Buf + AsRef<[u8]>>(mut buf: B) -> Result<Self, ParseError> {
ensure_attach!(
<$ty>::parse(buf.as_ref())? == N,
ParseError::InvalidInput,
WhereEq(stringify!(N), N)
);
buf.advance(Self::ENCODED_LEN as usize);
Ok(Self)
}
fn put_buf<B: BufMut>(&self, buf: B) {
N.put_buf(buf)
}
}
impl<const N: $ty> fmt::Debug for $ident<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct(&format!(concat!(stringify!($ident), "<{}>"), N))
.finish()
}
}
)+
};
}
mp4_const_int! {
ConstU8 => u8,
ConstU16 => u16,
ConstU32 => u32,
ConstU64 => u64,
ConstI8 => i8,
ConstI16 => i16,
ConstI32 => i32,
ConstI64 => i64,
}
//
// Mp4Transform impls
//
impl Mp4Transform {
pub const UNITY: Self = Self {
transform: Matrix3x2::new(
I16F16::ONE,
I16F16::ZERO,
I16F16::ZERO,
I16F16::ZERO,
I16F16::ONE,
I16F16::ZERO,
),
normalizer: Vector3::new(I2F30::ZERO, I2F30::ZERO, I2F30::ONE),
};
}
impl Default for Mp4Transform {
fn default() -> Self {
Self::UNITY
}
}
impl Mp4Prim for Mp4Transform {
const ENCODED_LEN: u64 = <[i32; 9]>::ENCODED_LEN;
fn parse<B: Buf + AsRef<[u8]>>(mut buf: B) -> Result<Self, ParseError> {
let mut raw = Matrix3::default();
for value in &mut raw {
*value = Mp4Prim::parse(&mut buf)?;
}
Ok(Self {
transform: raw.fixed_columns::<2>(0).map(I16F16::from_bits),
normalizer: raw.column(2).map(I2F30::from_bits),
})
}
fn put_buf<B: BufMut>(&self, mut buf: B) {
let raw = Matrix3::from_columns(&[
self.transform.column(0).map(I16F16::to_bits),
self.transform.column(1).map(I16F16::to_bits),
self.normalizer.map(I2F30::to_bits),
]);
for row in raw.row_iter() {
for value in &row {
buf.put_mp4_value(value);
}
}
}
}
//
// Mp4Fixed impls
//
impl<Frac> Mp4Fixed for FixedI8<Frac> {}
impl<Frac> Mp4Fixed for FixedI16<Frac> {}
impl<Frac> Mp4Fixed for FixedI32<Frac> {}
impl<Frac> Mp4Fixed for FixedI64<Frac> {}
impl<Frac> Mp4Fixed for FixedU8<Frac> {}
impl<Frac> Mp4Fixed for FixedU16<Frac> {}
impl<Frac> Mp4Fixed for FixedU32<Frac> {}
impl<Frac> Mp4Fixed for FixedU64<Frac> {}

View File

@ -1,10 +0,0 @@
macro_rules! define_fourcc_lower {
($($name:ident),+ $(,)?) => {
paste::paste! {
$(
#[doc = concat!("The `", stringify!([<$name:lower>]), "` FourCC code.")]
pub const $name: $crate::parse::FourCC = $crate::parse::FourCC::from_str(stringify!([<$name:lower>]));
)+
}
};
}

View File

@ -1,44 +0,0 @@
#![allow(missing_docs)]
use super::{ConstFullBoxHeader, ConstU16, ParseBox, ParsedBox};
#[derive(Clone, Debug, ParseBox, ParsedBox)]
#[box_type = "mdhd"]
pub enum MdhdBox {
V1 {
_parsed_header: ConstFullBoxHeader<1>,
creation_time: u64,
modification_time: u64,
timescale: u32,
duration: u64,
languages: [u8; 2],
_pre_defined: ConstU16,
},
V0 {
_parsed_header: ConstFullBoxHeader,
creation_time: u32,
modification_time: u32,
timescale: u32,
duration: u32,
languages: [u8; 2],
_pre_defined: ConstU16,
},
}
impl MdhdBox {
pub const DURATION_UNDETERMINED_V0: u32 = u32::MAX;
pub const DURATION_UNDETERMINED_V1: u64 = u64::MAX;
#[cfg(test)]
pub(crate) fn dummy() -> Self {
Self::V1 {
_parsed_header: ConstFullBoxHeader,
creation_time: 0,
modification_time: 0,
timescale: 0,
duration: Self::DURATION_UNDETERMINED_V1,
languages: [0; 2],
_pre_defined: Default::default(),
}
}
}

View File

@ -1,44 +1,26 @@
#![allow(missing_docs)]
use derive_more::{Deref, DerefMut};
use crate::error::Result;
use super::error::ParseResultExt;
use super::mp4box::Boxes;
use super::{HdlrBox, MdhdBox, MinfBox, ParseBox, ParseBoxes, ParseError, ParsedBox};
use super::{BoxType, MinfBox, ParseBox, ParseError, ParsedBox};
#[derive(Clone, Debug, Deref, DerefMut, ParseBox, ParsedBox)]
#[derive(Clone, Debug, ParseBox, ParsedBox)]
#[box_type = "mdia"]
pub struct MdiaBox {
pub children: Boxes<MdiaChildren>,
pub children: Boxes,
}
#[non_exhaustive]
#[derive(Clone, Debug, ParseBoxes)]
#[box_type = "mdia"]
pub struct MdiaChildren {
pub header: MdhdBox,
pub handler: HdlrBox,
pub info: MinfBox,
}
const NAME: BoxType = BoxType::MDIA;
impl MdiaBox {
pub fn with_children(children: MdiaChildren) -> Result<Self, ParseError> {
Ok(Self { children: Boxes::new(children, [])? })
}
}
#[cfg(test)]
mod test {
use super::*;
impl MdiaBox {
pub(crate) fn dummy() -> Self {
Self::new(MdhdBox::dummy(), HdlrBox::dummy(), MinfBox::dummy()).unwrap()
}
pub(crate) fn new(header: MdhdBox, handler: HdlrBox, info: MinfBox) -> Result<Self, ParseError> {
Self::with_children(MdiaChildren { header, handler, info })
}
#[cfg(test)]
pub(crate) fn with_children<C: Into<Boxes>>(children: C) -> Self {
Self { children: children.into() }
}
pub fn minf_mut(&mut self) -> Result<&mut MinfBox, ParseError> {
self.children.get_one_mut().while_parsing_child(NAME, BoxType::MINF)
}
}

View File

@ -1,43 +1,26 @@
#![allow(missing_docs)]
use derive_more::{Deref, DerefMut};
use crate::error::Result;
use super::error::ParseResultExt;
use super::mp4box::Boxes;
use super::{ParseBox, ParseBoxes, ParseError, ParsedBox, StblBox};
use super::{BoxType, ParseBox, ParseError, ParsedBox, StblBox};
#[derive(Clone, Debug, Deref, DerefMut, ParseBox, ParsedBox)]
#[derive(Clone, Debug, ParseBox, ParsedBox)]
#[box_type = "minf"]
pub struct MinfBox {
pub children: Boxes<MinfChildren>,
children: Boxes,
}
#[non_exhaustive]
#[derive(Clone, Debug, ParseBoxes)]
#[box_type = "minf"]
pub struct MinfChildren {
pub samples: StblBox,
}
const NAME: BoxType = BoxType::MINF;
impl MinfBox {
pub fn with_children(children: MinfChildren) -> Result<Self, ParseError> {
Ok(Self { children: Boxes::new(children, [])? })
}
}
#[cfg(test)]
mod test {
use super::*;
impl MinfBox {
pub(crate) fn dummy() -> Self {
Self::new(StblBox::dummy()).unwrap()
}
#[cfg(test)]
pub(crate) fn new(samples: StblBox) -> Result<Self, ParseError> {
Self::with_children(MinfChildren { samples })
}
#[cfg(test)]
pub(crate) fn with_children<C: Into<Boxes>>(children: C) -> Self {
Self { children: children.into() }
}
pub fn stbl_mut(&mut self) -> Result<&mut StblBox, ParseError> {
self.children.get_one_mut().while_parsing_child(NAME, BoxType::STBL)
}
}

View File

@ -1,57 +1,68 @@
#![allow(missing_docs)]
use derive_more::{Deref, DerefMut};
use nonempty::NonEmpty;
use crate::error::Result;
use super::{Boxes, MvhdBox, ParseBox, ParseBoxes, ParseError, ParsedBox, TrakBox};
use super::error::{ParseResultExt, WhileParsingField};
use super::{BoxType, Boxes, BoxesValidator, ParseBox, ParseError, ParsedBox, TrakBox};
#[derive(Clone, Debug, Deref, DerefMut, ParseBox, ParsedBox)]
#[derive(Clone, Debug, ParseBox, ParsedBox)]
#[box_type = "moov"]
pub struct MoovBox {
pub children: Boxes<MoovChildren>,
children: Boxes<MoovChildrenValidator>,
}
#[non_exhaustive]
#[derive(Clone, Debug, ParseBoxes)]
#[box_type = "moov"]
pub struct MoovChildren {
pub header: MvhdBox,
pub tracks: NonEmpty<TrakBox>,
}
pub(crate) struct MoovChildrenValidator;
const NAME: BoxType = BoxType::MOOV;
impl MoovBox {
pub fn with_children(children: MoovChildren) -> Result<Self, ParseError> {
Ok(Self { children: Boxes::new(children, [])? })
#[cfg(test)]
pub(crate) fn with_children<C: Into<Boxes<MoovChildrenValidator>>>(children: C) -> Self {
Self { children: children.into() }
}
pub fn traks(&mut self) -> impl Iterator<Item = Result<&mut TrakBox, ParseError>> + '_ {
self.children
.get_mut()
.map(|result| result.while_parsing_child(NAME, BoxType::TRAK))
}
}
impl BoxesValidator for MoovChildrenValidator {
fn validate<V>(children: &Boxes<V>) -> Result<(), ParseError> {
ensure_attach!(
children.box_types().any(|box_type| box_type == BoxType::TRAK),
ParseError::MissingRequiredBox(BoxType::TRAK),
WhileParsingField(NAME, "children"),
);
Ok(())
}
}
#[cfg(test)]
mod test {
use crate::parse::{BoxType, Mp4Value};
use crate::util::test::test_mvhd;
use bytes::BytesMut;
use crate::parse::Mp4Box;
use super::*;
impl MoovBox {
pub(crate) fn dummy() -> Self {
Self::new(MvhdBox::dummy(), NonEmpty::new(TrakBox::dummy())).unwrap()
}
pub(crate) fn new(header: MvhdBox, tracks: NonEmpty<TrakBox>) -> Result<Self, ParseError> {
Self::with_children(MoovChildren { header, tracks })
}
fn test_trak() -> Mp4Box<TrakBox> {
Mp4Box::with_data(TrakBox::with_children(vec![]).into()).unwrap()
}
#[test]
fn roundtrip() {
MoovBox::parse(&mut MoovBox::dummy().to_bytes()).unwrap();
let mut data = BytesMut::new();
MoovBox::with_children(vec![test_trak().into()]).put_buf(&mut data);
MoovBox::parse(&mut data).unwrap();
}
#[test]
fn no_traks() {
let err = MoovBox::parse(&mut test_mvhd().to_bytes()).unwrap_err();
let mut data = BytesMut::new();
MoovBox::with_children(vec![]).put_buf(&mut data);
let err = MoovBox::parse(&mut data).unwrap_err();
assert!(
matches!(err.get_ref(), ParseError::MissingRequiredBox(BoxType::TRAK)),
"{err}",

View File

@ -1,28 +1,27 @@
#![allow(missing_docs)]
use std::fmt::Debug;
use std::iter;
use std::marker::PhantomData;
use std::mem::take;
use std::pin::Pin;
use std::result::Result as StdResult;
use bytes::{Buf, BufMut, BytesMut};
use derive_more::{Deref, DerefMut, From};
use derive_more::From;
use derive_where::derive_where;
use downcast_rs::{impl_downcast, Downcast};
use dyn_clonable::clonable;
use futures_util::io::BufReader;
use futures_util::{AsyncRead, AsyncReadExt};
use mediasan_common::error::WhileParsingType;
use mediasan_common::{AsyncSkipExt, ResultExt};
use crate::error::{Report, Result};
use crate::parse::error::ParseResultExt;
use crate::error::Result;
use crate::util::IoResultExt;
use crate::{AsyncSkip, BoxDataTooLarge, Error};
use super::error::{MultipleBoxes, WhileParsingBox};
use super::{BoxHeader, BoxType, Mp4Prim, Mp4Value, Mp4ValueWriterExt, ParseError};
use super::{BoxHeader, BoxType, Mp4Value, ParseError};
#[derive(Debug)]
#[derive_where(Clone; BoxData<T>)]
@ -41,9 +40,9 @@ pub enum BoxData<T: ?Sized> {
}
pub trait ParseBox: Sized {
const NAME: BoxType;
fn parse(buf: &mut BytesMut) -> Result<Self, ParseError>;
fn box_type() -> BoxType;
}
#[clonable]
@ -57,67 +56,30 @@ pub trait ParsedBox: Clone + Debug + Downcast {
#[derive_where(Clone, Debug, Default)]
pub struct Boxes<V = ()> {
boxes: Vec<AnyMp4Box>,
_typed: PhantomData<V>,
_validator: PhantomData<V>,
}
#[derive(Deref, DerefMut)]
#[derive_where(Clone, Debug, Default; C)]
pub struct BoundedBoxes<C, V = ()> {
#[deref]
#[deref_mut]
boxes: Boxes<V>,
_count: PhantomData<C>,
}
pub trait ParseBoxes: Sized {
type Ref<'a>
where
Self: 'a;
type RefMut<'a>
where
Self: 'a;
type IntoIter: Iterator<Item = AnyMp4Box>;
fn validate(boxes: &mut [AnyMp4Box]) -> Result<(), ParseError> {
Self::parse(boxes).map(drop)
pub trait BoxesValidator {
fn validate<V>(_boxes: &Boxes<V>) -> Result<(), ParseError> {
Ok(())
}
fn parse<'a>(boxes: &'a mut [AnyMp4Box]) -> Result<Self::RefMut<'a>, ParseError>
where
Self: 'a;
fn parsed<'a>(boxes: &'a [AnyMp4Box]) -> Self::Ref<'a>
where
Self: 'a;
fn try_into_iter(self) -> Result<Self::IntoIter, ParseError>;
}
//
// Mp4Box impls
//
impl<T: ParsedBox + ?Sized> Mp4Box<T> {
pub fn with_data(data: BoxData<T>) -> Result<Self, ParseError>
where
T: ParseBox,
{
let parsed_header = BoxHeader::with_data_size(T::NAME, data.encoded_len())?;
let parsed_header = BoxHeader::with_data_size(T::box_type(), data.encoded_len())?;
Ok(Self { parsed_header, data })
}
pub fn with_parsed(data: Box<T>) -> Result<Self, ParseError>
where
T: ParseBox,
{
Self::with_data(BoxData::from(data))
}
/// Read and parse a box's data assuming its header has already been read.
///
/// It is highly recommended that `R` be a buffered reader.
pub async fn read_data<R>(mut reader: Pin<&mut R>, header: BoxHeader, max_size: u64) -> StdResult<Self, Error>
pub(crate) async fn read_data<R>(
mut reader: Pin<&mut BufReader<R>>,
header: BoxHeader,
max_size: u64,
) -> StdResult<Self, Error>
where
R: AsyncRead + AsyncSkip,
T: ParseBox,
@ -155,12 +117,8 @@ impl<T: ParsedBox + ?Sized> Mp4Box<T> {
}
}
pub fn box_type(&self) -> BoxType {
self.parsed_header.box_type()
}
pub fn parse_data_as<U: ParseBox + ParsedBox + Into<Box<T>>>(&mut self) -> Result<Option<&mut U>, ParseError> {
if self.parsed_header.box_type() != U::NAME {
if self.parsed_header.box_type() != U::box_type() {
return Ok(None);
}
self.data.parse_as()
@ -169,8 +127,8 @@ impl<T: ParsedBox + ?Sized> Mp4Box<T> {
impl<T: ParsedBox + ?Sized> Mp4Value for Mp4Box<T> {
fn parse(mut buf: &mut BytesMut) -> Result<Self, ParseError> {
let parsed_header = BoxHeader::parse(&mut buf).attach_printable(WhileParsingType::new::<T>())?;
let data = BoxData::get_from_bytes_mut(buf, &parsed_header).attach_printable(WhileParsingType::new::<T>())?;
let parsed_header = BoxHeader::parse(&mut buf).attach_printable(WhileParsingType::new::<Self>())?;
let data = BoxData::get_from_bytes_mut(buf, &parsed_header).attach_printable(WhileParsingType::new::<Self>())?;
Ok(Self { parsed_header, data })
}
@ -203,7 +161,7 @@ impl<T: ParsedBox> From<Mp4Box<T>> for AnyMp4Box {
impl<T: ParsedBox + ?Sized> BoxData<T> {
pub fn get_from_bytes_mut(buf: &mut BytesMut, header: &BoxHeader) -> Result<Self, ParseError> {
match header.box_data_size().while_parsing_box(header.box_type())? {
match header.box_data_size()? {
None => Ok(Self::Bytes(take(buf))),
Some(box_data_size) => match box_data_size.try_into() {
Ok(box_data_size) => {
@ -228,12 +186,12 @@ impl<T: ParsedBox + ?Sized> BoxData<T> {
T: ParseBox + Sized,
{
if let BoxData::Bytes(data) = self {
let parsed = T::parse(data).while_parsing_box(T::NAME)?;
let parsed = T::parse(data).while_parsing_type()?;
ensure_attach!(
data.is_empty(),
ParseError::InvalidInput,
"extra unparsed data",
WhileParsingBox(T::NAME),
WhileParsingBox(T::box_type()),
);
*self = Self::Parsed(Box::new(parsed));
}
@ -243,21 +201,14 @@ impl<T: ParsedBox + ?Sized> BoxData<T> {
}
}
pub fn parsed<U: ParsedBox>(&self) -> Option<&U> {
let BoxData::Parsed(parsed) = self else { return None };
// This is subtle because of the generic T; we have to make sure Downcast::as_any_mut is called on T and
// not on Box<T>, or the subsequent Any::downcast_ref::<U>() will fail.
<T>::as_any(parsed).downcast_ref()
}
fn parse_as<U: ParseBox + ParsedBox + Into<Box<T>>>(&mut self) -> Result<Option<&mut U>, ParseError> {
if let BoxData::Bytes(data) = self {
let parsed = U::parse(data).while_parsing_box(U::NAME)?;
let parsed = U::parse(data).while_parsing_type()?;
ensure_attach!(
data.is_empty(),
ParseError::InvalidInput,
"extra unparsed data",
WhileParsingBox(U::NAME),
WhileParsingBox(U::box_type()),
);
*self = Self::Parsed(parsed.into());
}
@ -317,21 +268,6 @@ impl<'a, T: ParsedBox + 'a> From<T> for Box<dyn ParsedBox + 'a> {
// Boxes impls
//
impl<V: ParseBoxes> Boxes<V> {
pub fn new(typed: V, untyped: impl IntoIterator<Item = AnyMp4Box>) -> Result<Self, ParseError> {
let boxes = typed.try_into_iter()?.chain(untyped).collect();
Ok(Self { boxes, _typed: PhantomData })
}
pub fn parsed(&self) -> V::Ref<'_> {
V::parsed(&self.boxes)
}
pub fn parsed_mut(&mut self) -> V::RefMut<'_> {
V::parse(&mut self.boxes).unwrap_or_else(|_| unreachable!())
}
}
impl<V> Boxes<V> {
pub fn box_types(&self) -> impl Iterator<Item = BoxType> + ExactSizeIterator + '_ {
self.boxes.iter().map(|mp4box| mp4box.parsed_header.box_type())
@ -345,40 +281,24 @@ impl<V> Boxes<V> {
pub fn get_one_mut<T: ParseBox + ParsedBox>(&mut self) -> Result<&mut T, ParseError> {
ensure_attach!(
self.box_types().filter(|box_type| *box_type == T::NAME).count() <= 1,
self.box_types().filter(|box_type| *box_type == T::box_type()).count() <= 1,
ParseError::InvalidBoxLayout,
MultipleBoxes(T::NAME),
MultipleBoxes(T::box_type()),
);
self.get_mut()
.next()
.ok_or_else(|| ParseError::MissingRequiredBox(T::NAME))?
}
pub fn is_empty(&self) -> bool {
self.boxes.is_empty()
}
pub fn len(&self) -> usize {
self.boxes.len()
}
pub fn iter(&self) -> impl Iterator<Item = &AnyMp4Box> + '_ {
self.boxes.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut AnyMp4Box> + '_ {
self.boxes.iter_mut()
.ok_or_else(|| ParseError::MissingRequiredBox(T::box_type()))?
}
}
impl<V: ParseBoxes> Mp4Value for Boxes<V> {
impl<V: BoxesValidator> Mp4Value for Boxes<V> {
fn parse(buf: &mut BytesMut) -> Result<Self, ParseError> {
let mut boxes = Vec::new();
while buf.has_remaining() {
boxes.push(Mp4Box::parse(buf)?);
}
V::validate(&mut boxes)?;
let boxes = Self { boxes, _typed: PhantomData };
let boxes = Self { boxes, _validator: PhantomData };
V::validate(&boxes)?;
Ok(boxes)
}
@ -393,88 +313,14 @@ impl<V: ParseBoxes> Mp4Value for Boxes<V> {
}
}
impl<V: ParseBoxes> TryFrom<Vec<AnyMp4Box>> for Boxes<V> {
type Error = Report<ParseError>;
fn try_from(mut boxes: Vec<AnyMp4Box>) -> Result<Self, ParseError> {
V::validate(&mut boxes)?;
Ok(Self { boxes, _typed: PhantomData })
}
}
impl<'a, V> IntoIterator for &'a Boxes<V> {
type Item = &'a AnyMp4Box;
// TODO when stabilizing the API, replace this with a wrapper.
type IntoIter = std::slice::Iter<'a, AnyMp4Box>;
fn into_iter(self) -> Self::IntoIter {
self.boxes.iter()
}
}
impl<'a, V> IntoIterator for &'a mut Boxes<V> {
type Item = &'a mut AnyMp4Box;
// TODO when stabilizing the API, replace this with a wrapper.
type IntoIter = std::slice::IterMut<'a, AnyMp4Box>;
fn into_iter(self) -> Self::IntoIter {
self.boxes.iter_mut()
impl<V> From<Vec<AnyMp4Box>> for Boxes<V> {
fn from(boxes: Vec<AnyMp4Box>) -> Self {
Self { boxes, _validator: PhantomData }
}
}
//
// BoundedBoxes impls
// BoxesValidator impls
//
impl<C, V: ParseBoxes> From<Boxes<V>> for BoundedBoxes<C, V> {
fn from(boxes: Boxes<V>) -> Self {
Self { boxes, _count: PhantomData }
}
}
impl<C: Mp4Prim + Into<u32> + TryFrom<u32>, V: ParseBoxes> Mp4Value for BoundedBoxes<C, V> {
fn parse(buf: &mut BytesMut) -> Result<Self, ParseError> {
let count = C::parse(&mut *buf)?;
let boxes = Boxes::parse(&mut *buf)?;
ensure_attach!(boxes.len() as u32 == count.into(), ParseError::InvalidInput);
Ok(Self { boxes, _count: PhantomData })
}
fn encoded_len(&self) -> u64 {
C::ENCODED_LEN + self.boxes.encoded_len()
}
fn put_buf<B: BufMut>(&self, mut buf: B) {
buf.put_mp4_value(&C::try_from(self.boxes.len() as u32).unwrap_or_else(|_| panic!()));
self.boxes.put_buf(buf);
}
}
//
// ParseBoxes impls
//
impl ParseBoxes for () {
type Ref<'a> = ();
type RefMut<'a> = ();
type IntoIter = iter::Empty<AnyMp4Box>;
fn parse<'a>(_boxes: &'a mut [AnyMp4Box]) -> Result<Self, ParseError>
where
Self: 'a,
{
Ok(())
}
fn parsed<'a>(_boxes: &'a [AnyMp4Box]) -> Self::Ref<'a>
where
Self: 'a,
{
}
fn try_into_iter(self) -> Result<Self::IntoIter, ParseError> {
Ok(iter::empty())
}
}
impl BoxesValidator for () {}

View File

@ -1,75 +0,0 @@
#![allow(missing_docs)]
use std::num::NonZeroU32;
use fixed::types::{I16F16, I8F8};
use super::{ConstFullBoxHeader, ConstU16, ConstU32, Mp4Transform, ParseBox, ParsedBox};
#[derive(Clone, Debug, ParseBox, ParsedBox)]
#[box_type = "mvhd"]
pub enum MvhdBox {
V1 {
_parsed_header: ConstFullBoxHeader<1>,
creation_time: u64,
modification_time: u64,
timescale: u32,
duration: u64,
rate: I16F16,
volume: I8F8,
_reserved: ConstU16,
_reserved_2: [ConstU32; 2],
matrix: Mp4Transform,
_pre_defined: [ConstU32; 6],
next_track_id: NonZeroU32,
},
V0 {
_parsed_header: ConstFullBoxHeader,
creation_time: u32,
modification_time: u32,
timescale: u32,
duration: u32,
rate: I16F16,
volume: I8F8,
_reserved: ConstU16,
_reserved_2: [ConstU32; 2],
matrix: Mp4Transform,
_pre_defined: [ConstU32; 6],
next_track_id: NonZeroU32,
},
}
impl MvhdBox {
pub const DURATION_UNDETERMINED_V0: u32 = u32::MAX;
pub const DURATION_UNDETERMINED_V1: u64 = u64::MAX;
pub const NEXT_TRACK_ID_UNDETERMINED: NonZeroU32 = {
let Some(value) = NonZeroU32::new(u32::MAX) else {
unreachable!()
};
value
};
}
#[cfg(test)]
mod test {
use super::*;
impl MvhdBox {
pub(crate) fn dummy() -> Self {
Self::V1 {
_parsed_header: Default::default(),
creation_time: 0,
modification_time: 0,
timescale: 0,
duration: Self::DURATION_UNDETERMINED_V1,
rate: 1i16.into(),
volume: 1.into(),
_reserved: Default::default(),
_reserved_2: Default::default(),
matrix: Default::default(),
_pre_defined: Default::default(),
next_track_id: Self::NEXT_TRACK_ID_UNDETERMINED,
}
}
}
}

View File

@ -1,180 +1,51 @@
#![allow(missing_docs)]
use std::vec;
use derive_more::{Deref, DerefMut};
use crate::error::Result;
use crate::parse::error::WhileParsingBox;
use super::{
AnyMp4Box, Boxes, Co64Box, ParseBox, ParseBoxes, ParseError, ParsedBox, StcoBox, StscBox, StsdBox, StszBox, SttsBox,
};
use super::error::{ParseResultExt, WhileParsingChild};
use super::{BoxType, Boxes, Co64Box, ParseBox, ParseError, ParsedBox, StcoBox};
#[derive(Clone, Debug, Deref, DerefMut, ParseBox, ParsedBox)]
#[derive(Clone, Debug, ParseBox, ParsedBox)]
#[box_type = "stbl"]
pub struct StblBox {
pub children: Boxes<StblChildren>,
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub struct StblChildren {
pub sample_descriptions: StsdBox,
pub time_to_sample: SttsBox,
pub sample_to_chunk: StscBox,
pub sample_sizes: StszBox,
pub chunk_offsets: StblCo,
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug)]
pub struct StblChildrenRef<'a> {
pub sample_descriptions: &'a StsdBox,
pub time_to_sample: &'a SttsBox,
pub sample_to_chunk: &'a StscBox,
pub sample_sizes: &'a StszBox,
pub chunk_offsets: StblCoRef<'a>,
}
#[non_exhaustive]
#[derive(Debug)]
pub struct StblChildrenRefMut<'a> {
pub sample_descriptions: &'a mut StsdBox,
pub time_to_sample: &'a mut SttsBox,
pub sample_to_chunk: &'a mut StscBox,
pub sample_sizes: &'a mut StszBox,
pub chunk_offsets: StblCoRefMut<'a>,
}
#[derive(Clone, Debug)]
pub enum StblCo {
Stco(StcoBox),
Co64(Co64Box),
}
#[derive(Clone, Copy, Debug)]
pub enum StblCoRef<'a> {
Stco(&'a StcoBox),
Co64(&'a Co64Box),
children: Boxes,
}
#[derive(Debug)]
pub enum StblCoRefMut<'a> {
pub enum StblCoMut<'a> {
Stco(&'a mut StcoBox),
Co64(&'a mut Co64Box),
}
#[non_exhaustive]
#[derive(Clone, Debug, ParseBoxes)]
#[box_type = "stbl"]
struct DerivedStblChildren {
sample_descriptions: StsdBox,
time_to_sample: SttsBox,
sample_to_chunk: StscBox,
sample_sizes: StszBox,
chunk_offsets_32: Option<StcoBox>,
chunk_offsets_64: Option<Co64Box>,
}
const NAME: BoxType = BoxType::STBL;
const STCO: BoxType = BoxType::STCO;
const CO64: BoxType = BoxType::CO64;
impl StblBox {
pub fn with_children(children: StblChildren) -> Result<Self, ParseError> {
Ok(Self { children: Boxes::new(children, [])? })
}
}
//
// StblCo impls
//
impl ParseBoxes for StblChildren {
type Ref<'a> = StblChildrenRef<'a>;
type RefMut<'a> = StblChildrenRefMut<'a>;
type IntoIter = vec::IntoIter<AnyMp4Box>;
fn parse<'a>(boxes: &'a mut [AnyMp4Box]) -> Result<Self::RefMut<'a>, ParseError>
where
Self: 'a,
{
let DerivedStblChildrenRefMut {
sample_descriptions,
time_to_sample,
sample_to_chunk,
sample_sizes,
chunk_offsets_32,
chunk_offsets_64,
} = DerivedStblChildren::parse(boxes)?;
let chunk_offsets = match (chunk_offsets_32, chunk_offsets_64) {
(Some(chunk_offsets_32), None) => StblCoRefMut::Stco(chunk_offsets_32),
(None, Some(chunk_offsets_64)) => StblCoRefMut::Co64(chunk_offsets_64),
(Some(_), Some(_)) => bail_attach!(
ParseError::InvalidBoxLayout,
"more than one stco and co64 present",
WhileParsingBox(StblBox::NAME),
),
(None, None) => bail_attach!(
ParseError::MissingRequiredBox(StcoBox::NAME),
WhileParsingBox(StblBox::NAME),
),
};
Ok(Self::RefMut { sample_descriptions, time_to_sample, sample_to_chunk, sample_sizes, chunk_offsets })
#[cfg(test)]
pub(crate) fn with_children<C: Into<Boxes>>(children: C) -> Self {
Self { children: children.into() }
}
fn parsed<'a>(boxes: &'a [AnyMp4Box]) -> Self::Ref<'a>
where
Self: 'a,
{
let DerivedStblChildrenRef {
sample_descriptions,
time_to_sample,
sample_to_chunk,
sample_sizes,
chunk_offsets_32,
chunk_offsets_64,
} = DerivedStblChildren::parsed(boxes);
let chunk_offsets_32 = chunk_offsets_32.map(StblCoRef::Stco);
let chunk_offsets_64 = chunk_offsets_64.map(StblCoRef::Co64);
let chunk_offsets = chunk_offsets_32.or(chunk_offsets_64).unwrap();
Self::Ref { sample_descriptions, time_to_sample, sample_to_chunk, sample_sizes, chunk_offsets }
}
fn try_into_iter(self) -> Result<Self::IntoIter, ParseError> {
let Self { sample_descriptions, time_to_sample, sample_to_chunk, sample_sizes, chunk_offsets } = self;
let (chunk_offsets_32, chunk_offsets_64) = match chunk_offsets {
StblCo::Stco(chunk_offsets_32) => (Some(chunk_offsets_32), None),
StblCo::Co64(chunk_offsets_64) => (None, Some(chunk_offsets_64)),
};
DerivedStblChildren {
sample_descriptions,
time_to_sample,
sample_to_chunk,
sample_sizes,
chunk_offsets_32,
chunk_offsets_64,
}
.try_into_iter()
}
}
//
// StblCo impls
//
impl Default for StblCo {
fn default() -> Self {
Self::Stco(Default::default())
}
}
//
// StblCoRef impls
//
impl StblCoRef<'_> {
pub fn entry_count(&self) -> u32 {
match self {
Self::Stco(stco) => stco.entry_count(),
Self::Co64(co64) => co64.entry_count(),
pub fn co_mut(&mut self) -> Result<StblCoMut<'_>, ParseError> {
let have_stco = self.children.box_types().any(|box_type| box_type == STCO);
let have_co64 = self.children.box_types().any(|box_type| box_type == CO64);
ensure_attach!(
!(have_stco && have_co64),
ParseError::InvalidBoxLayout,
"more than one stco and co64 present",
WhileParsingChild(NAME, STCO),
);
if have_stco {
self.children
.get_one_mut()
.while_parsing_child(NAME, STCO)
.map(StblCoMut::Stco)
} else {
self.children
.get_one_mut()
.while_parsing_child(NAME, CO64)
.map(StblCoMut::Co64)
}
}
}
@ -183,46 +54,11 @@ impl StblCoRef<'_> {
// StblCoMut impls
//
impl StblCoRefMut<'_> {
impl StblCoMut<'_> {
pub fn entry_count(&self) -> u32 {
match self {
Self::Stco(stco) => stco.entry_count(),
Self::Co64(co64) => co64.entry_count(),
}
}
}
#[cfg(test)]
mod test {
use super::*;
impl StblBox {
pub(crate) fn dummy() -> Self {
Self::new(
Default::default(),
Default::default(),
Default::default(),
Default::default(),
Default::default(),
)
.unwrap()
}
#[cfg(test)]
pub(crate) fn new(
sample_descriptions: StsdBox,
time_to_sample: SttsBox,
sample_to_chunk: StscBox,
sample_sizes: StszBox,
chunk_offsets: StblCo,
) -> Result<Self, ParseError> {
Self::with_children(StblChildren {
sample_descriptions,
time_to_sample,
sample_to_chunk,
sample_sizes,
chunk_offsets,
})
StblCoMut::Stco(stco) => stco.entry_count(),
StblCoMut::Co64(co64) => co64.entry_count(),
}
}
}

View File

@ -1,6 +1,6 @@
#![allow(missing_docs)]
use super::{ArrayEntry, ArrayEntryMut, BoundedArray, ConstFullBoxHeader, ParseBox, ParsedBox};
use super::{ArrayEntryMut, BoundedArray, ConstFullBoxHeader, ParseBox, ParsedBox};
#[derive(Clone, Debug, Default, ParseBox, ParsedBox)]
#[box_type = "stco"]
@ -10,10 +10,6 @@ pub struct StcoBox {
}
impl StcoBox {
pub fn entries(&self) -> impl Iterator<Item = ArrayEntry<'_, u32>> + ExactSizeIterator + '_ {
self.entries.entries()
}
pub fn entries_mut(&mut self) -> impl Iterator<Item = ArrayEntryMut<'_, u32>> + ExactSizeIterator + '_ {
self.entries.entries_mut()
}

View File

@ -1,54 +0,0 @@
#![allow(missing_docs)]
use std::ops::{Deref, DerefMut};
use std::str;
use bytes::{BufMut, BytesMut};
use crate::error::Result;
use super::{Mp4Value, ParseError};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Mp4String {
data: BytesMut,
}
impl Deref for Mp4String {
type Target = str;
fn deref(&self) -> &Self::Target {
str::from_utf8(&self.data).unwrap_or_else(|_| unreachable!())
}
}
impl DerefMut for Mp4String {
fn deref_mut(&mut self) -> &mut Self::Target {
str::from_utf8_mut(&mut self.data).unwrap_or_else(|_| unreachable!())
}
}
impl Mp4Value for Mp4String {
fn parse(buf: &mut BytesMut) -> Result<Self, ParseError> {
let mut data = buf.split();
ensure_attach!(
data.last() == Some(&0),
ParseError::InvalidInput,
"string not null-terminated"
);
let _ = data.split_off(data.len() - 1);
if let Err(err) = str::from_utf8(&data) {
bail_attach!(ParseError::InvalidInput, err);
}
Ok(Self { data })
}
fn encoded_len(&self) -> u64 {
self.data.len() as u64 + 1
}
fn put_buf<B: BufMut>(&self, mut buf: B) {
buf.put(&self.data[..]);
buf.put_u8(0);
}
}

View File

@ -1,59 +0,0 @@
#![allow(missing_docs)]
use super::{ArrayEntry, ArrayEntryMut, BoundedArray, ConstFullBoxHeader, Mp4Prim, ParseBox, ParsedBox};
#[derive(Clone, Debug, Default, ParseBox, ParsedBox)]
#[box_type = "stsc"]
pub struct StscBox {
_parsed_header: ConstFullBoxHeader,
entries: BoundedArray<u32, StscEntry>,
}
#[derive(Clone, Copy, Debug, Mp4Prim)]
pub struct StscEntry {
pub first_chunk: u32,
pub samples_per_chunk: u32,
pub samples_description_index: u32,
}
#[derive(Mp4Prim)]
pub enum Test {
A([u8; 4]),
B(u32),
}
impl StscBox {
pub fn entries(&self) -> impl ExactSizeIterator<Item = ArrayEntry<'_, StscEntry>> + '_ {
self.entries.entries()
}
pub fn entries_mut(&mut self) -> impl ExactSizeIterator<Item = ArrayEntryMut<'_, StscEntry>> + '_ {
self.entries.entries_mut()
}
pub fn entry_count(&self) -> u32 {
self.entries.entry_count()
}
}
impl FromIterator<StscEntry> for StscBox {
fn from_iter<I: IntoIterator<Item = StscEntry>>(entries: I) -> Self {
Self { _parsed_header: Default::default(), entries: entries.into_iter().collect() }
}
}
#[cfg(test)]
mod test {
use bytes::BytesMut;
use crate::parse::{ParseBox, ParsedBox};
use super::StscBox;
#[test]
fn roundtrip() {
let mut buf = BytesMut::new();
StscBox::default().put_buf(&mut buf);
StscBox::parse(&mut buf).unwrap();
}
}

View File

@ -1,14 +0,0 @@
#![allow(missing_docs)]
use derive_more::{Deref, DerefMut};
use super::{BoundedBoxes, ConstFullBoxHeader, ParseBox, ParsedBox};
#[derive(Clone, Debug, Default, Deref, DerefMut, ParseBox, ParsedBox)]
#[box_type = "stsd"]
pub struct StsdBox {
pub _parsed_header: ConstFullBoxHeader,
#[deref]
#[deref_mut]
pub children: BoundedBoxes<u32>,
}

View File

@ -1,50 +0,0 @@
#![allow(missing_docs)]
use std::num::NonZeroU32;
use super::{BoundedArray, ConstFullBoxHeader, ConstU32, Mp4Value, ParseBox, ParseError, ParsedBox};
use crate::error::Result;
#[derive(Clone, Debug, Default, ParseBox, ParsedBox)]
#[box_type = "stsz"]
pub struct StszBox {
_parsed_header: ConstFullBoxHeader,
entries: StszEntries,
}
#[derive(Clone, Debug, Mp4Value)]
enum StszEntries {
VariableSize {
_not_fixed_size: ConstU32,
entries: BoundedArray<u32, u32>,
},
FixedSize {
size: NonZeroU32,
number_of_samples: u32,
},
}
impl Default for StszEntries {
fn default() -> Self {
Self::VariableSize { _not_fixed_size: Default::default(), entries: Default::default() }
}
}
impl StszBox {
pub fn sample_sizes(&self) -> impl ExactSizeIterator<Item = Result<u32, ParseError>> + '_ {
let (mut variable_iter, fixed_size, count) = match &self.entries {
StszEntries::FixedSize { size, number_of_samples } => (None, u32::from(*size), *number_of_samples),
StszEntries::VariableSize { _not_fixed_size, entries } => {
let variable_iter = entries.entries().map(|entry| entry.get());
(Some(variable_iter), 0, entries.entry_count())
}
};
// Handrolled "Either" here:
(0..count).map(move |_| {
variable_iter
.as_mut()
.map_or(Ok(fixed_size), |iter| iter.next().expect("matches count"))
})
}
}

View File

@ -1,48 +0,0 @@
#![allow(missing_docs)]
use super::{ArrayEntryMut, BoundedArray, ConstFullBoxHeader, Mp4Prim, ParseBox, ParsedBox};
#[derive(Clone, Debug, Default, ParseBox, ParsedBox)]
#[box_type = "stts"]
pub struct SttsBox {
_parsed_header: ConstFullBoxHeader,
entries: BoundedArray<u32, SttsEntry>,
}
#[derive(Clone, Copy, Debug, Mp4Prim)]
pub struct SttsEntry {
pub sample_count: u32,
pub sample_delta: u32,
}
impl SttsBox {
pub fn entries_mut(&mut self) -> impl ExactSizeIterator<Item = ArrayEntryMut<'_, SttsEntry>> + '_ {
self.entries.entries_mut()
}
pub fn entry_count(&self) -> u32 {
self.entries.entry_count()
}
}
impl FromIterator<SttsEntry> for SttsBox {
fn from_iter<I: IntoIterator<Item = SttsEntry>>(entries: I) -> Self {
Self { _parsed_header: Default::default(), entries: entries.into_iter().collect() }
}
}
#[cfg(test)]
mod test {
use bytes::BytesMut;
use crate::parse::{ParseBox, ParsedBox};
use super::SttsBox;
#[test]
fn roundtrip() {
let mut buf = BytesMut::new();
SttsBox::default().put_buf(&mut buf);
SttsBox::parse(&mut buf).unwrap();
}
}

View File

@ -1,76 +0,0 @@
#![allow(missing_docs)]
use fixed::types::I8F8;
use super::{BoxFlags, ConstU16, ConstU32, ConstU8, Mp4Transform, ParseBox, ParsedBox};
#[derive(Clone, Debug, ParseBox, ParsedBox)]
#[box_type = "tkhd"]
pub enum TkhdBox {
V1 {
_version: ConstU8<1>,
flags: BoxFlags,
creation_time: u64,
modification_time: u64,
track_id: u32,
_reserved: ConstU32,
duration: u64,
_reserved_2: [ConstU32; 2],
layer: i16,
alternate_group: i16,
volume: I8F8,
_reserved_3: ConstU16,
matrix: Mp4Transform,
width: u32,
height: u32,
},
V0 {
_version: ConstU8,
flags: BoxFlags,
creation_time: u32,
modification_time: u32,
track_id: u32,
_reserved: ConstU32,
duration: u32,
_reserved_2: [ConstU32; 2],
layer: i16,
alternate_group: i16,
volume: I8F8,
_reserved_3: ConstU16,
matrix: Mp4Transform,
width: u32,
height: u32,
},
}
impl TkhdBox {
pub const DURATION_UNDETERMINED_V0: u32 = u32::MAX;
pub const DURATION_UNDETERMINED_V1: u64 = u64::MAX;
}
#[cfg(test)]
mod test {
use super::*;
impl TkhdBox {
pub(crate) fn dummy() -> Self {
Self::V1 {
_version: Default::default(),
flags: Default::default(),
creation_time: 0,
modification_time: 0,
track_id: 0,
_reserved: Default::default(),
duration: Self::DURATION_UNDETERMINED_V1,
_reserved_2: Default::default(),
layer: 0,
alternate_group: 0,
volume: 1.into(),
_reserved_3: Default::default(),
matrix: Default::default(),
width: 0,
height: 0,
}
}
}
}

View File

@ -1,56 +1,30 @@
#![allow(missing_docs)]
use derive_more::{Deref, DerefMut};
use super::mp4box::Boxes;
use super::{MdiaBox, ParseBox, ParseBoxes, ParseError, ParsedBox, StblCoRef, StblCoRefMut, TkhdBox};
use crate::error::Result;
#[derive(Clone, Debug, Deref, DerefMut, ParseBox, ParsedBox)]
use super::error::ParseResultExt;
use super::mp4box::Boxes;
use super::{BoxType, MdiaBox, ParseBox, ParseError, ParsedBox, StblCoMut};
#[derive(Clone, Debug, ParseBox, ParsedBox)]
#[box_type = "trak"]
pub struct TrakBox {
pub children: Boxes<TrakChildren>,
children: Boxes,
}
#[non_exhaustive]
#[derive(Clone, Debug, ParseBoxes)]
#[box_type = "trak"]
pub struct TrakChildren {
pub header: TkhdBox,
pub media: MdiaBox,
}
const NAME: BoxType = BoxType::TRAK;
impl TrakBox {
pub fn with_children(children: TrakChildren) -> Result<Self, ParseError> {
Ok(Self { children: Boxes::new(children, [])? })
#[cfg(test)]
pub(crate) fn with_children<C: Into<Boxes>>(children: C) -> Self {
Self { children: children.into() }
}
pub fn co(&self) -> StblCoRef<'_> {
let media = self.parsed().media;
let info = media.parsed().info;
let samples = info.parsed().samples;
samples.parsed().chunk_offsets
pub fn co_mut(&mut self) -> Result<StblCoMut<'_>, ParseError> {
self.mdia_mut()?.minf_mut()?.stbl_mut()?.co_mut()
}
pub fn co_mut(&mut self) -> StblCoRefMut<'_> {
let media = self.parsed_mut().media;
let info = media.parsed_mut().info;
let samples = info.parsed_mut().samples;
samples.parsed_mut().chunk_offsets
}
}
#[cfg(test)]
mod test {
use super::*;
impl TrakBox {
pub(crate) fn dummy() -> Self {
Self::new(TkhdBox::dummy(), MdiaBox::dummy()).unwrap()
}
pub(crate) fn new(header: TkhdBox, media: MdiaBox) -> Result<Self, ParseError> {
Self::with_children(TrakChildren { header, media })
}
pub fn mdia_mut(&mut self) -> Result<&mut MdiaBox, ParseError> {
self.children.get_one_mut().while_parsing_child(NAME, BoxType::MDIA)
}
}

View File

@ -10,16 +10,6 @@ pub trait Mp4Value: Sized {
fn parse(buf: &mut BytesMut) -> Result<Self, ParseError>;
fn encoded_len(&self) -> u64;
fn put_buf<B: BufMut>(&self, buf: B);
fn to_bytes(&self) -> BytesMut {
let mut buf = BytesMut::with_capacity(self.encoded_len() as usize);
self.put_buf(&mut buf);
buf
}
fn to_vec(&self) -> Vec<u8> {
self.to_bytes().to_vec()
}
}
pub trait Mp4ValueReaderExt {
@ -37,7 +27,7 @@ impl<T: Mp4Prim> Mp4Value for T {
Self::parse(buf)
}
fn encoded_len(&self) -> u64 {
Self::ENCODED_LEN
Self::encoded_len()
}
fn put_buf<B: BufMut>(&self, buf: B) {
self.put_buf(buf)

View File

@ -7,7 +7,7 @@ use std::iter;
use bytes::{BufMut, BytesMut};
use crate::parse::box_type::{DINF, DREF, HDLR, MDAT, MDHD, MECO, META, METT, MVHD, STSC, STSD, STSZ, STTS, TKHD, URL};
use crate::parse::{fourcc, AnyMp4Box, BoxFlags, BoxHeader, BoxType, BoxUuid, FourCC, FullBoxHeader, Mp4Box, Mp4Value};
use crate::parse::{fourcc, AnyMp4Box, BoxHeader, BoxType, BoxUuid, FourCC, FullBoxHeader, Mp4Box, Mp4Value};
use crate::{InputSpan, SanitizedMetadata};
pub const TEST_UUID: BoxType = BoxType::Uuid(BoxUuid { value: *b"thisisatestuuid!" });
@ -140,7 +140,7 @@ pub fn write_test_dinf_data<B: BufMut>(mut out: B) {
FullBoxHeader::default().put_buf(&mut out);
out.put_u32(1); // entry count
BoxHeader::with_u32_data_size(URL, 4).put_buf(&mut out); // url header
FullBoxHeader { version: 0, flags: BoxFlags(1) }.put_buf(&mut out);
FullBoxHeader { version: 0, flags: 1 }.put_buf(&mut out);
}
pub fn write_test_mdat(out: &mut Vec<u8>, data: &[u8]) -> InputSpan {

View File

@ -1,10 +1,6 @@
use bytes::BytesMut;
use derive_builder::Builder;
use crate::parse::{
fourcc, AnyMp4Box, BoxData, Boxes, Co64Box, MdiaBox, MinfBox, MoovBox, Mp4Box, Mp4Value, ParseBox, ParsedBox,
StblBox, StcoBox, TrakBox,
};
use crate::parse::{fourcc, Co64Box, MdiaBox, MinfBox, MoovBox, Mp4Box, StblBox, StcoBox, TrakBox};
use super::{test_dinf, test_hdlr, test_mdhd, test_mvhd, test_stsc, test_stsd, test_stsz, test_stts, test_tkhd};
@ -51,37 +47,23 @@ impl TestMoovBuilder {
let mut minf = vec![test_dinf()];
if spec.stbl {
let stbl: Mp4Box<StblBox> = container(stbl);
minf.push(stbl.into());
minf.push(Mp4Box::with_data(StblBox::with_children(stbl).into()).unwrap().into());
}
let mut mdia = vec![test_mdhd(), test_hdlr(fourcc::META)];
if spec.minf {
let minf: Mp4Box<MinfBox> = container(minf);
mdia.push(minf.into());
mdia.push(Mp4Box::with_data(MinfBox::with_children(minf).into()).unwrap().into());
}
let mut trak = vec![test_tkhd(1)];
if spec.mdia {
let mdia: Mp4Box<MdiaBox> = container(mdia);
trak.push(mdia.into());
trak.push(Mp4Box::with_data(MdiaBox::with_children(mdia).into()).unwrap().into());
}
let mut moov = vec![test_mvhd()];
if spec.trak {
let trak: Mp4Box<TrakBox> = container(trak);
moov.push(trak.into());
moov.push(Mp4Box::with_data(TrakBox::with_children(trak).into()).unwrap().into());
}
container(moov)
Mp4Box::with_data(MoovBox::with_children(moov).into()).unwrap()
}
}
fn container<T: ParseBox + ParsedBox, I: IntoIterator>(boxes: I) -> Mp4Box<T>
where
AnyMp4Box: From<I::Item>,
{
let mut data = BytesMut::new();
let boxes = boxes.into_iter().map(AnyMp4Box::from).collect::<Vec<_>>();
Boxes::<()>::try_from(boxes).unwrap().put_buf(&mut data);
Mp4Box::with_data(BoxData::Bytes(data.into())).unwrap()
}

@ -1 +1 @@
Subproject commit b9f4d3f3273819ce50170357098a56113eb088b2
Subproject commit b2121e38ec04025fdb62306b4863566fa993c8cb

View File

@ -10,6 +10,6 @@ default = []
libwebp = ["dep:libwebp-sys"]
[dependencies]
libwebp-sys = { version = "0.9.4", optional = true }
log = "0.4.17"
thiserror = "1.0.40"
libwebp-sys = { workspace = true, optional = true }
log = { workspace = true }
thiserror = { workspace = true }

View File

@ -19,21 +19,21 @@ default = ["error-detail"]
error-detail = []
[dependencies]
assert_matches = "1.5.0"
bitflags = "2.4.0"
bitstream-io = "1.7.0"
bytes = "1.3.0"
derive_builder = "0.20.0"
derive_more = "0.99.17"
log = "0.4.17"
mediasan-common = { path = "../common", version = "=0.5.2" }
num-integer = { version = "0.1.45", default-features = false }
num-traits = { version = "0.2.16", default-features = false }
thiserror = "1.0.38"
assert_matches = { workspace = true }
bitflags = { workspace = true }
bitstream-io = { workspace = true }
bytes = { workspace = true }
derive_builder = { workspace = true }
derive_more = { workspace = true, features = ["display"] }
log = { workspace = true }
mediasan-common = { path = "../common", version = "=0.5.3" }
num-integer = { workspace = true }
num-traits = { workspace = true }
thiserror = { workspace = true }
[dev-dependencies]
assert_matches = "1.5.0"
criterion = { version = "0.5.1", features = ["async_futures"] }
assert_matches = { workspace = true }
criterion = { workspace = true, features = ["async_futures"] }
mediasan-common-test = { path = "../common-test" }
webpsan-test = { path = "../webpsan-test" }

View File

@ -72,7 +72,7 @@ trait ReadSkip: Read + Skip {}
type DynChunkReader<'a> = ChunkReader<dyn ReadSkip + 'a>;
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "frame dimensions `{_0}`x`{_1}` do not match canvas dimensions `{_2}`x`{_3}`")]
#[display("frame dimensions `{_0}`x`{_1}` do not match canvas dimensions `{_2}`x`{_3}`")]
struct FrameDimensionsMismatch(NonZeroU16, NonZeroU16, NonZeroU32, NonZeroU32);
//

View File

@ -26,7 +26,7 @@ pub struct CanonicalHuffmanTree<E: Endianness, S: Clone> {
}
#[derive(Display)]
#[display(fmt = "invalid lz77 prefix code `{_0}`")]
#[display("invalid lz77 prefix code `{_0}`")]
struct InvalidLz77PrefixCode(u16);
pub const LZ77_MAX_LEN: u16 = (LZ77_MAX_SYMBOL - 2) >> 1;

View File

@ -58,19 +58,19 @@ pub(crate) trait ParseResultExt: ResultExt + Sized {
}
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "multiple `{}` chunks", _0)]
#[display("multiple `{}` chunks", _0)]
pub(crate) struct MultipleChunks(pub(crate) FourCC);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "expected `{}` chunk", _0)]
#[display("expected `{}` chunk", _0)]
pub(crate) struct ExpectedChunk(pub(crate) FourCC);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "while parsing `{}` chunk", _0)]
#[display("while parsing `{}` chunk", _0)]
pub(crate) struct WhileParsingChunk(pub(crate) FourCC);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "while parsing `{}` chunk field `{}`", _0, _1)]
#[display("while parsing `{}` chunk field `{}`", _0, _1)]
pub(crate) struct WhileParsingField<T>(pub(crate) FourCC, pub(crate) T);
impl ReportableError for ParseError {

View File

@ -26,26 +26,26 @@ pub struct LosslessImage {
#[derive(Clone, Display, PartialEq, Eq)]
enum Transform {
#[display(fmt = "predictor transform: block size {block_size}")]
#[display("predictor transform: block size {block_size}")]
Predictor { block_size: u16, _image: EntropyCodedImage },
#[display(fmt = "color transform: block size {block_size}")]
#[display("color transform: block size {block_size}")]
Color { block_size: u16, _image: EntropyCodedImage },
#[display(fmt = "subtract green transform")]
#[display("subtract green transform")]
SubtractGreen,
#[display(fmt = "color indexing transform: {} colors", "image.width")]
#[display("color indexing transform: {} colors", "image.width")]
ColorIndexing { image: EntropyCodedImage },
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Display, PartialEq, Eq, PartialOrd, Ord)]
enum TransformType {
#[display(fmt = "predictor")]
#[display("predictor")]
Predictor = 0b00,
#[display(fmt = "color")]
#[display("color")]
Color = 0b01,
#[display(fmt = "subtract green")]
#[display("subtract green")]
SubtractGreen = 0b10,
#[display(fmt = "color indexing")]
#[display("color indexing")]
ColorIndexing = 0b11,
}
@ -59,14 +59,14 @@ struct EntropyCodedImage {
struct SpatiallyCodedImage;
#[derive(Clone, Copy, Debug, Display, PartialEq, Eq)]
#[display(fmt = "distance {dist} length {len}")]
#[display("distance {dist} length {len}")]
struct BackReference {
dist: NonZeroU32,
len: NonZeroU32,
}
#[derive(Clone, Copy, Debug, Default, Display, PartialEq, Eq)]
#[display(fmt = "({alpha}, {red}, {green}, {blue})")]
#[display("({alpha}, {red}, {green}, {blue})")]
struct Color {
alpha: u8,
red: u8,
@ -81,9 +81,9 @@ struct ColorCache {
#[derive(Clone, Display, PartialEq, Eq)]
enum MetaPrefixCodes {
#[display(fmt = "single meta prefix code")]
#[display("single meta prefix code")]
Single,
#[display(fmt = "multiple meta prefix codes: max code group {max_code_group}, block size {block_size}")]
#[display("multiple meta prefix codes: max code group {max_code_group}, block size {block_size}")]
Multiple {
block_size: u16,
max_code_group: u16,
@ -124,39 +124,39 @@ struct DistancePrefixCode {
}
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "out-of-bounds color cache index `{_0}` >= `{_1}`")]
#[display("out-of-bounds color cache index `{_0}` >= `{_1}`")]
struct ColorCacheIndexOutOfBounds(u16, u16);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "invalid back-reference distance `{_0}` at pixel `{_1}`")]
#[display("invalid back-reference distance `{_0}` at pixel `{_1}`")]
struct InvalidBackRefDistance(NonZeroU32, u32);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "invalid back-reference length `{_0}` at pixel `{_1}` with image length `{_2}`")]
#[display("invalid back-reference length `{_0}` at pixel `{_1}` with image length `{_2}`")]
struct InvalidBackRefLength(NonZeroU32, u32, u32);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "invalid code length repetition `{_0}` at `{_1}` with max symbols `{_2}`")]
#[display("invalid code length repetition `{_0}` at `{_1}` with max symbols `{_2}`")]
struct InvalidCodeLengthRepetition(u8, usize, u16);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "invalid color cache size `{_0}`")]
#[display("invalid color cache size `{_0}`")]
struct InvalidColorCacheSize(u8);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "invalid duplicate {_0} transform")]
#[display("invalid duplicate {_0} transform")]
struct InvalidDuplicateTransform(TransformType);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "invalid predictor `{_0}`")]
#[display("invalid predictor `{_0}`")]
struct InvalidPredictor(u8);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "invalid symbol count `{_0}` >= `{_1}`")]
#[display("invalid symbol count `{_0}` >= `{_1}`")]
struct InvalidSymbolCount(u16, u16);
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "while parsing {_0} transform")]
#[display("while parsing {_0} transform")]
struct WhileParsingTransform(TransformType);
//

View File

@ -31,7 +31,7 @@ pub struct Vp8lChunk {
//
#[derive(Clone, Copy, Debug, Display)]
#[display(fmt = "invalid VP8L signature `0x{_0:x}` != `0x{}`", Vp8lChunk::SIGNATURE)]
#[display("invalid VP8L signature `0x{_0:x}` != `0x{}`", Vp8lChunk::SIGNATURE)]
struct InvalidSignature(u8);
//