Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
479e0bf36f | ||
|
|
a9bfc4198d | ||
|
|
3deef86070 | ||
|
|
4c49f3578b | ||
|
|
132a8425e2 | ||
|
|
46ab331325 | ||
|
|
a92f41c94f | ||
|
|
7dc24e0e85 | ||
|
|
74a3177ff9 | ||
|
|
c190e85bb3 | ||
|
|
4315016042 | ||
|
|
66159c7be3 | ||
|
|
0834aa9223 | ||
|
|
3de0df01f2 | ||
|
|
f6244d321b | ||
|
|
70de41379e | ||
|
|
8f45aef9e2 | ||
|
|
9f5962a56f | ||
|
|
83fd823e22 | ||
|
|
5be8d3aead |
@ -1,5 +1,5 @@
|
||||
[workspace]
|
||||
members = ["cli", "common", "mp4san", "mp4san-derive", "mp4san-test", "mp4san-test-gen", "webpsan", "webpsan-test"]
|
||||
members = ["cli", "common", "mp4san", "mp4san-derive", "mp4san-dump", "mp4san-test", "mp4san-test-gen", "webpsan", "webpsan-test"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
|
||||
@ -18,7 +18,8 @@ exclude.workspace = true
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
syn = "2.0"
|
||||
quote = "1.0"
|
||||
uuid = "1.3"
|
||||
proc-macro2 = "1.0.66"
|
||||
syn = "2.0.29"
|
||||
quote = "1.0.33"
|
||||
uuid = "1.4.1"
|
||||
synstructure = "0.13.0"
|
||||
|
||||
67
mp4san-derive/src/attr.rs
Normal file
67
mp4san-derive/src/attr.rs
Normal file
@ -0,0 +1,67 @@
|
||||
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"#,
|
||||
)) }
|
||||
}
|
||||
@ -1,212 +1,15 @@
|
||||
use proc_macro::TokenStream;
|
||||
mod attr;
|
||||
mod mp4_prim;
|
||||
mod mp4_value;
|
||||
mod parse_box;
|
||||
mod parse_boxes;
|
||||
mod parsed_box;
|
||||
mod util;
|
||||
|
||||
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;
|
||||
use synstructure::decl_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
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
59
mp4san-derive/src/mp4_prim.rs
Normal file
59
mp4san-derive/src/mp4_prim.rs
Normal file
@ -0,0 +1,59 @@
|
||||
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 }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
41
mp4san-derive/src/mp4_value.rs
Normal file
41
mp4san-derive/src/mp4_value.rs
Normal file
@ -0,0 +1,41 @@
|
||||
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 }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
54
mp4san-derive/src/parse_box.rs
Normal file
54
mp4san-derive/src/parse_box.rs
Normal file
@ -0,0 +1,54 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
255
mp4san-derive/src/parse_boxes.rs
Normal file
255
mp4san-derive/src/parse_boxes.rs
Normal file
@ -0,0 +1,255 @@
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
35
mp4san-derive/src/parsed_box.rs
Normal file
35
mp4san-derive/src/parsed_box.rs
Normal file
@ -0,0 +1,35 @@
|
||||
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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
52
mp4san-derive/src/util.rs
Normal file
52
mp4san-derive/src/util.rs
Normal file
@ -0,0 +1,52 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
16
mp4san-dump/Cargo.toml
Normal file
16
mp4san-dump/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[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"
|
||||
218
mp4san-dump/src/lib.rs
Normal file
218
mp4san-dump/src/lib.rs
Normal file
@ -0,0 +1,218 @@
|
||||
//! 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 = ¤t_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 }
|
||||
}
|
||||
}
|
||||
41
mp4san-dump/src/main.rs
Normal file
41
mp4san-dump/src/main.rs
Normal file
@ -0,0 +1,41 @@
|
||||
//! 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");
|
||||
}
|
||||
}
|
||||
@ -117,35 +117,139 @@ pub fn example_mdat() -> Vec<u8> {
|
||||
|
||||
pub fn example_moov() -> Vec<u8> {
|
||||
const EXAMPLE_MOOV: &[&[u8]] = &[
|
||||
&[0, 0, 0, 56], // box size
|
||||
b"moov", // box type
|
||||
&[0, 0, 1, 133], // box size
|
||||
b"moov", // box type
|
||||
//
|
||||
// trak box (inside moov box)
|
||||
//
|
||||
&[0, 0, 0, 48], // box size
|
||||
&[0, 0, 1, 17], // 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, 40], // box size
|
||||
b"mdia", // box type
|
||||
&[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
|
||||
//
|
||||
// minf box (inside mdia box)
|
||||
//
|
||||
&[0, 0, 0, 32], // box size
|
||||
b"minf", // box type
|
||||
&[0, 0, 0, 100], // box size
|
||||
b"minf", // box type
|
||||
//
|
||||
// stbl box (inside minf box)
|
||||
//
|
||||
&[0, 0, 0, 24], // box size
|
||||
&[0, 0, 0, 92], // 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()
|
||||
}
|
||||
|
||||
@ -21,10 +21,15 @@ 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"
|
||||
|
||||
|
||||
@ -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, StblCoMut};
|
||||
use crate::parse::{BoxHeader, BoxType, FourCC, FtypBox, MoovBox, Mp4Box, Mp4Value, ParseError, StblCoRefMut};
|
||||
|
||||
//
|
||||
// public types
|
||||
@ -323,14 +323,12 @@ pub async fn sanitize_async_with_config<R: AsyncRead + AsyncSkip>(
|
||||
}
|
||||
|
||||
BoxType::MOOV => {
|
||||
let mut read_moov = Mp4Box::read_data(reader.as_mut(), header, config.max_metadata_size).await?;
|
||||
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 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();
|
||||
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);
|
||||
@ -405,25 +403,23 @@ 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 &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(|| {
|
||||
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(|| {
|
||||
report_attach!(ParseError::InvalidInput, "chunk offset not within mdat")
|
||||
})?,
|
||||
);
|
||||
})?);
|
||||
}
|
||||
}
|
||||
} 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(|| {
|
||||
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(|| {
|
||||
report_attach!(ParseError::InvalidInput, "chunk offset not within mdat")
|
||||
})?,
|
||||
);
|
||||
})?);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -565,13 +561,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();
|
||||
sanitize(test).unwrap_err();
|
||||
dbg!(sanitize(test).unwrap_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn box_size_overflow() {
|
||||
let test = test_mp4().mdat_data_len(u64::MAX - 16).build();
|
||||
sanitize(test).unwrap_err();
|
||||
dbg!(sanitize(test).unwrap_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -584,7 +580,7 @@ mod test {
|
||||
let test = test_mp4()
|
||||
.ftyp(test_ftyp().compatible_brands(compatible_brands).clone())
|
||||
.build();
|
||||
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
|
||||
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
|
||||
assert_matches!(err.into_inner(), ParseError::InvalidInput);
|
||||
});
|
||||
}
|
||||
@ -606,7 +602,7 @@ mod test {
|
||||
.build();
|
||||
let test = test_spec.build();
|
||||
test.sanitize_ok();
|
||||
assert_matches!(sanitize_with_config(test, config).unwrap_err(), Error::Parse(err) => {
|
||||
assert_matches!(dbg!(sanitize_with_config(test, config).unwrap_err()), Error::Parse(err) => {
|
||||
assert_matches!(err.into_inner(), ParseError::InvalidInput);
|
||||
});
|
||||
}
|
||||
@ -619,7 +615,7 @@ mod test {
|
||||
#[test]
|
||||
fn no_ftyp() {
|
||||
let test = test_mp4().boxes(&[MOOV, MDAT][..]).build();
|
||||
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
|
||||
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
|
||||
assert_matches!(err.into_inner(), ParseError::InvalidBoxLayout);
|
||||
});
|
||||
}
|
||||
@ -627,7 +623,7 @@ mod test {
|
||||
#[test]
|
||||
fn multiple_ftyp() {
|
||||
let test = test_mp4().boxes(&[FTYP, FTYP, MOOV, MDAT][..]).build();
|
||||
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
|
||||
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
|
||||
assert_matches!(err.into_inner(), ParseError::InvalidBoxLayout);
|
||||
});
|
||||
}
|
||||
@ -641,7 +637,7 @@ mod test {
|
||||
#[test]
|
||||
fn ftyp_not_first_significant_box() {
|
||||
let test = test_mp4().boxes(&[MOOV, FTYP, MDAT][..]).build();
|
||||
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
|
||||
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
|
||||
assert_matches!(err.into_inner(), ParseError::InvalidBoxLayout);
|
||||
});
|
||||
}
|
||||
@ -649,7 +645,7 @@ mod test {
|
||||
#[test]
|
||||
fn no_moov() {
|
||||
let test = test_mp4().boxes(&[FTYP, MDAT][..]).build();
|
||||
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
|
||||
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
|
||||
assert_matches!(err.into_inner(), ParseError::MissingRequiredBox(MOOV));
|
||||
});
|
||||
}
|
||||
@ -657,7 +653,7 @@ mod test {
|
||||
#[test]
|
||||
fn no_mdat() {
|
||||
let test = test_mp4().boxes(&[FTYP, MOOV][..]).build();
|
||||
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
|
||||
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
|
||||
assert_matches!(err.into_inner(), ParseError::MissingRequiredBox(MDAT));
|
||||
});
|
||||
}
|
||||
@ -697,7 +693,7 @@ mod test {
|
||||
#[test]
|
||||
fn uuid() {
|
||||
let test = test_mp4().boxes(&[FTYP, MOOV, TEST_UUID, MDAT][..]).build();
|
||||
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
|
||||
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
|
||||
assert_matches!(err.into_inner(), ParseError::UnsupportedBox(TEST_UUID));
|
||||
});
|
||||
}
|
||||
@ -707,7 +703,7 @@ mod test {
|
||||
let test = test_mp4()
|
||||
.ftyp(test_ftyp().major_brand(MP41).add_compatible_brand(MP41).clone())
|
||||
.build();
|
||||
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
|
||||
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
|
||||
assert_matches!(err.into_inner(), ParseError::UnsupportedFormat(MP41));
|
||||
});
|
||||
}
|
||||
@ -727,7 +723,7 @@ mod test {
|
||||
let test = test_mp4()
|
||||
.ftyp(test_ftyp().major_brand(ISOM).compatible_brands(vec![]).clone())
|
||||
.build();
|
||||
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
|
||||
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
|
||||
assert_matches!(err.into_inner(), ParseError::UnsupportedFormat(ISOM));
|
||||
});
|
||||
}
|
||||
@ -735,7 +731,7 @@ mod test {
|
||||
#[test]
|
||||
fn no_trak() {
|
||||
let test = test_mp4().moov(test_moov().trak(false).clone()).build();
|
||||
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
|
||||
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
|
||||
assert_matches!(err.into_inner(), ParseError::MissingRequiredBox(TRAK));
|
||||
});
|
||||
}
|
||||
@ -746,7 +742,7 @@ mod test {
|
||||
.boxes(&[FTYP, MDAT, MOOV][..])
|
||||
.moov(test_moov().mdia(false).clone())
|
||||
.build();
|
||||
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
|
||||
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
|
||||
assert_matches!(err.into_inner(), ParseError::MissingRequiredBox(MDIA));
|
||||
});
|
||||
}
|
||||
@ -757,7 +753,7 @@ mod test {
|
||||
.boxes(&[FTYP, MDAT, MOOV][..])
|
||||
.moov(test_moov().minf(false).clone())
|
||||
.build();
|
||||
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
|
||||
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
|
||||
assert_matches!(err.into_inner(), ParseError::MissingRequiredBox(MINF));
|
||||
});
|
||||
}
|
||||
@ -768,7 +764,7 @@ mod test {
|
||||
.boxes(&[FTYP, MDAT, MOOV][..])
|
||||
.moov(test_moov().stbl(false).clone())
|
||||
.build();
|
||||
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
|
||||
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
|
||||
assert_matches!(err.into_inner(), ParseError::MissingRequiredBox(STBL));
|
||||
});
|
||||
}
|
||||
@ -779,7 +775,7 @@ mod test {
|
||||
.boxes(&[FTYP, MDAT, MOOV][..])
|
||||
.moov(test_moov().stco(false).clone())
|
||||
.build();
|
||||
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
|
||||
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
|
||||
assert_matches!(err.into_inner(), ParseError::MissingRequiredBox(STCO | CO64));
|
||||
});
|
||||
}
|
||||
@ -799,7 +795,7 @@ mod test {
|
||||
.boxes(&[FTYP, MDAT, MOOV][..])
|
||||
.moov(test_moov().co64(true).clone())
|
||||
.build();
|
||||
assert_matches!(sanitize(test).unwrap_err(), Error::Parse(err) => {
|
||||
assert_matches!(dbg!(sanitize(test).unwrap_err()), Error::Parse(err) => {
|
||||
assert_matches!(err.into_inner(), ParseError::InvalidBoxLayout);
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,17 +1,30 @@
|
||||
//! 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;
|
||||
|
||||
@ -19,22 +32,36 @@ pub use array::{ArrayEntry, ArrayEntryMut, BoundedArray, UnboundedArray};
|
||||
pub use co64::Co64Box;
|
||||
pub use error::ParseError;
|
||||
pub use ftyp::FtypBox;
|
||||
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 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 stco::StcoBox;
|
||||
pub use trak::TrakBox;
|
||||
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 value::{Mp4Value, Mp4ValueReaderExt, Mp4ValueWriterExt};
|
||||
|
||||
pub use mediasan_common::parse::FourCC;
|
||||
pub use mp4san_derive::{ParseBox, ParsedBox};
|
||||
pub use mp4san_derive::{Mp4Prim, Mp4Value, ParseBox, ParseBoxes, ParsedBox};
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::fmt::Debug;
|
||||
|
||||
use assert_matches::assert_matches;
|
||||
use bytes::BytesMut;
|
||||
|
||||
use super::*;
|
||||
@ -66,12 +93,38 @@ 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
|
||||
);
|
||||
}
|
||||
|
||||
@ -82,16 +135,13 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_type_bytes() {
|
||||
assert_eq!(
|
||||
NotARealBox::box_type(),
|
||||
BoxType::FourCC(FourCC { value: *b"\xffX0\x00" })
|
||||
);
|
||||
assert_eq!(NotARealBox::NAME, BoxType::FourCC(FourCC { value: *b"\xffX0\x00" }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_compact_int_decimal() {
|
||||
assert_eq!(
|
||||
AnotherFakeBox::box_type(),
|
||||
AnotherFakeBox::NAME,
|
||||
BoxType::FourCC(FourCC { value: 4283969538u32.to_be_bytes() })
|
||||
);
|
||||
}
|
||||
@ -99,12 +149,53 @@ mod test {
|
||||
#[test]
|
||||
fn test_type_extended() {
|
||||
let expected = BoxType::Uuid(BoxUuid { value: 0xc12fdd3f_1e93_464c_baee_7c4480628f58u128.to_be_bytes() });
|
||||
assert_eq!(FakeUuidTypeBox::box_type(), expected);
|
||||
assert_eq!(FakeUuidTypeBox::NAME, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_type_compact_str() {
|
||||
assert_eq!(Fifth::box_type(), BoxType::FourCC(FourCC { value: *b"xa04" }));
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -12,15 +12,15 @@ use crate::error::Result;
|
||||
|
||||
use super::{Mp4Prim, Mp4Value, Mp4ValueWriterExt, ParseError};
|
||||
|
||||
#[derive(Default, PartialEq, Eq)]
|
||||
#[derive_where(Clone, Debug; C)]
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive_where(Clone, Debug, Default; C)]
|
||||
pub struct BoundedArray<C, T> {
|
||||
entry_count: C,
|
||||
array: UnboundedArray<T>,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Eq)]
|
||||
#[derive_where(Clone, Debug)]
|
||||
#[derive(PartialEq, Eq)]
|
||||
#[derive_where(Clone, Debug, Default)]
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use super::{ArrayEntryMut, BoundedArray, ConstFullBoxHeader, ParseBox, ParsedBox};
|
||||
use super::{ArrayEntry, ArrayEntryMut, BoundedArray, ConstFullBoxHeader, ParseBox, ParsedBox};
|
||||
|
||||
#[derive(Clone, Debug, Default, ParseBox, ParsedBox)]
|
||||
#[box_type = "co64"]
|
||||
@ -10,6 +10,10 @@ 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()
|
||||
}
|
||||
|
||||
3
mp4san/src/parse/derive.rs
Normal file
3
mp4san/src/parse/derive.rs
Normal file
@ -0,0 +1,3 @@
|
||||
//! Unstable API used by the derive macros' generated code.
|
||||
|
||||
pub mod parse_boxes;
|
||||
185
mp4san/src/parse/derive/parse_boxes.rs
Normal file
185
mp4san/src/parse/derive/parse_boxes.rs
Normal file
@ -0,0 +1,185 @@
|
||||
//! 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 })
|
||||
}
|
||||
}
|
||||
@ -51,10 +51,12 @@ 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,
|
||||
@ -62,10 +64,12 @@ 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,
|
||||
|
||||
34
mp4san/src/parse/hdlr.rs
Normal file
34
mp4san/src/parse/hdlr.rs
Normal file
@ -0,0 +1,34 @@
|
||||
#![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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -49,11 +49,15 @@ pub struct BoxUuid {
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct FullBoxHeader {
|
||||
pub version: u8,
|
||||
pub flags: u32,
|
||||
pub flags: BoxFlags,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
#[derive(Clone, Copy, Debug, Default, From, PartialEq, Eq)]
|
||||
pub struct BoxFlags(pub u32);
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
pub struct ConstFullBoxHeader<const VERSION: u8 = 0, const FLAGS: u32 = 0>;
|
||||
|
||||
#[allow(missing_docs)]
|
||||
@ -272,7 +276,7 @@ impl fmt::Display for BoxUuid {
|
||||
#[allow(missing_docs)]
|
||||
impl FullBoxHeader {
|
||||
pub const fn default() -> Self {
|
||||
Self { version: 0, flags: 0 }
|
||||
Self { version: 0, flags: BoxFlags(0) }
|
||||
}
|
||||
|
||||
pub fn ensure_eq(&self, other: &Self) -> Result<(), ParseError> {
|
||||
@ -284,48 +288,62 @@ impl FullBoxHeader {
|
||||
ensure_attach!(
|
||||
self.flags == other.flags,
|
||||
ParseError::InvalidInput,
|
||||
format!("box flags 0b{:024b} do not match 0b{:024b}", self.flags, other.flags),
|
||||
format!(
|
||||
"box flags 0b{:024b} do not match 0b{:024b}",
|
||||
self.flags.0, other.flags.0
|
||||
),
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Mp4Prim for FullBoxHeader {
|
||||
fn parse<B: Buf>(mut buf: B) -> Result<Self, ParseError> {
|
||||
let version = u8::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 })
|
||||
}
|
||||
const ENCODED_LEN: u64 = u8::ENCODED_LEN + BoxFlags::ENCODED_LEN;
|
||||
|
||||
fn encoded_len() -> u64 {
|
||||
4
|
||||
fn parse<B: Buf + AsRef<[u8]>>(mut buf: B) -> Result<Self, ParseError> {
|
||||
ensure_attach!(buf.remaining() >= Self::ENCODED_LEN as usize, ParseError::TruncatedBox);
|
||||
let version = u8::parse(&mut buf)?;
|
||||
let flags = BoxFlags::parse(&mut buf)?;
|
||||
Ok(Self { version, flags })
|
||||
}
|
||||
|
||||
fn put_buf<B: BufMut>(&self, mut out: B) {
|
||||
out.put_u8(self.version);
|
||||
out.put_uint(self.flags.into(), 3);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
impl<const VERSION: u8, const FLAGS: u32> From<ConstFullBoxHeader<VERSION, FLAGS>> for FullBoxHeader {
|
||||
fn from(_: ConstFullBoxHeader<VERSION, FLAGS>) -> Self {
|
||||
Self { version: VERSION, flags: FLAGS }
|
||||
Self { version: VERSION, flags: BoxFlags(FLAGS) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<const VERSION: u8, const FLAGS: u32> Mp4Prim for ConstFullBoxHeader<VERSION, FLAGS> {
|
||||
fn parse<B: Buf>(buf: B) -> Result<Self, ParseError> {
|
||||
FullBoxHeader::parse(buf)?.ensure_eq(&Self.into())?;
|
||||
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);
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
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);
|
||||
fn put_buf<B: BufMut>(&self, out: B) {
|
||||
FullBoxHeader::from(*self).put_buf(out)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,66 +1,157 @@
|
||||
#![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;
|
||||
use bytes::BufMut;
|
||||
use bytes::{Buf, BufMut};
|
||||
use fixed::traits::Fixed;
|
||||
use fixed::types::{I16F16, I2F30};
|
||||
use fixed::{FixedI16, FixedI32, FixedI64, FixedI8, FixedU16, FixedU32, FixedU64, FixedU8};
|
||||
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 {
|
||||
fn parse<B: Buf>(buf: B) -> Result<Self, ParseError>;
|
||||
fn encoded_len() -> u64;
|
||||
const ENCODED_LEN: u64;
|
||||
|
||||
fn parse<B: Buf + AsRef<[u8]>>(buf: B) -> Result<Self, ParseError>;
|
||||
fn put_buf<B: BufMut>(&self, buf: B);
|
||||
}
|
||||
|
||||
macro_rules! mp4_int {
|
||||
($($ty:ty => ($get_fun:ident, $put_fun:ident)),+ $(,)?) => {
|
||||
$(impl Mp4Prim for $ty {
|
||||
fn parse<B: Buf>(mut buf: B) -> Result<Self, ParseError> {
|
||||
if buf.remaining() < Self::encoded_len() as usize {
|
||||
bail_attach!(ParseError::TruncatedBox, WhileParsingType::new::<$ty>());
|
||||
}
|
||||
Ok(buf.$get_fun())
|
||||
}
|
||||
#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct ConstU8<const N: u8 = 0>;
|
||||
|
||||
fn encoded_len() -> u64 {
|
||||
size_of::<Self>() as u64
|
||||
#[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)),+ $(,)?) => {
|
||||
$(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>(),
|
||||
);
|
||||
Ok(buf.$get_fun())
|
||||
}
|
||||
|
||||
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 => (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),
|
||||
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),
|
||||
}
|
||||
|
||||
impl<T: Mp4Prim, const N: usize> Mp4Prim for [T; N] {
|
||||
fn parse<B: Buf>(mut buf: B) -> Result<Self, ParseError> {
|
||||
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> {
|
||||
ensure_attach!(
|
||||
buf.remaining() >= Self::encoded_len() as usize,
|
||||
buf.remaining() >= Self::ENCODED_LEN as usize,
|
||||
ParseError::TruncatedBox,
|
||||
WhileParsingType::new::<Self>(),
|
||||
);
|
||||
Ok([(); N].map(|()| T::parse(&mut buf).unwrap_or_else(|_| unreachable!())))
|
||||
}
|
||||
|
||||
fn encoded_len() -> u64 {
|
||||
size_of::<Self>() as u64
|
||||
let mut parsed: [T; N] = Default::default();
|
||||
for value in &mut parsed {
|
||||
*value = T::parse(&mut buf)?;
|
||||
}
|
||||
Ok(parsed)
|
||||
}
|
||||
|
||||
fn put_buf<B: BufMut>(&self, mut buf: B) {
|
||||
@ -71,15 +162,124 @@ impl<T: Mp4Prim, const N: usize> Mp4Prim for [T; N] {
|
||||
}
|
||||
|
||||
impl Mp4Prim for FourCC {
|
||||
fn parse<B: Buf>(buf: B) -> Result<Self, ParseError> {
|
||||
fn parse<B: Buf + AsRef<[u8]>>(buf: B) -> Result<Self, ParseError> {
|
||||
Mp4Prim::parse(buf).map(|value| Self { value }).while_parsing_type()
|
||||
}
|
||||
|
||||
fn encoded_len() -> u64 {
|
||||
Self::size()
|
||||
}
|
||||
const 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> {}
|
||||
|
||||
10
mp4san/src/parse/macros.rs
Normal file
10
mp4san/src/parse/macros.rs
Normal file
@ -0,0 +1,10 @@
|
||||
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>]));
|
||||
)+
|
||||
}
|
||||
};
|
||||
}
|
||||
44
mp4san/src/parse/mdhd.rs
Normal file
44
mp4san/src/parse/mdhd.rs
Normal file
@ -0,0 +1,44 @@
|
||||
#![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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,26 +1,44 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
||||
use crate::error::Result;
|
||||
|
||||
use super::error::ParseResultExt;
|
||||
use super::mp4box::Boxes;
|
||||
use super::{BoxType, MinfBox, ParseBox, ParseError, ParsedBox};
|
||||
use super::{HdlrBox, MdhdBox, MinfBox, ParseBox, ParseBoxes, ParseError, ParsedBox};
|
||||
|
||||
#[derive(Clone, Debug, ParseBox, ParsedBox)]
|
||||
#[derive(Clone, Debug, Deref, DerefMut, ParseBox, ParsedBox)]
|
||||
#[box_type = "mdia"]
|
||||
pub struct MdiaBox {
|
||||
pub children: Boxes,
|
||||
pub children: Boxes<MdiaChildren>,
|
||||
}
|
||||
|
||||
const NAME: BoxType = BoxType::MDIA;
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Debug, ParseBoxes)]
|
||||
#[box_type = "mdia"]
|
||||
pub struct MdiaChildren {
|
||||
pub header: MdhdBox,
|
||||
pub handler: HdlrBox,
|
||||
pub info: MinfBox,
|
||||
}
|
||||
|
||||
impl MdiaBox {
|
||||
#[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)
|
||||
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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,26 +1,43 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
||||
use crate::error::Result;
|
||||
|
||||
use super::error::ParseResultExt;
|
||||
use super::mp4box::Boxes;
|
||||
use super::{BoxType, ParseBox, ParseError, ParsedBox, StblBox};
|
||||
use super::{ParseBox, ParseBoxes, ParseError, ParsedBox, StblBox};
|
||||
|
||||
#[derive(Clone, Debug, ParseBox, ParsedBox)]
|
||||
#[derive(Clone, Debug, Deref, DerefMut, ParseBox, ParsedBox)]
|
||||
#[box_type = "minf"]
|
||||
pub struct MinfBox {
|
||||
children: Boxes,
|
||||
pub children: Boxes<MinfChildren>,
|
||||
}
|
||||
|
||||
const NAME: BoxType = BoxType::MINF;
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Debug, ParseBoxes)]
|
||||
#[box_type = "minf"]
|
||||
pub struct MinfChildren {
|
||||
pub samples: StblBox,
|
||||
}
|
||||
|
||||
impl MinfBox {
|
||||
#[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)
|
||||
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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,68 +1,57 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use nonempty::NonEmpty;
|
||||
|
||||
use crate::error::Result;
|
||||
|
||||
use super::error::{ParseResultExt, WhileParsingField};
|
||||
use super::{BoxType, Boxes, BoxesValidator, ParseBox, ParseError, ParsedBox, TrakBox};
|
||||
use super::{Boxes, MvhdBox, ParseBox, ParseBoxes, ParseError, ParsedBox, TrakBox};
|
||||
|
||||
#[derive(Clone, Debug, ParseBox, ParsedBox)]
|
||||
#[derive(Clone, Debug, Deref, DerefMut, ParseBox, ParsedBox)]
|
||||
#[box_type = "moov"]
|
||||
pub struct MoovBox {
|
||||
children: Boxes<MoovChildrenValidator>,
|
||||
pub children: Boxes<MoovChildren>,
|
||||
}
|
||||
|
||||
pub(crate) struct MoovChildrenValidator;
|
||||
|
||||
const NAME: BoxType = BoxType::MOOV;
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Debug, ParseBoxes)]
|
||||
#[box_type = "moov"]
|
||||
pub struct MoovChildren {
|
||||
pub header: MvhdBox,
|
||||
pub tracks: NonEmpty<TrakBox>,
|
||||
}
|
||||
|
||||
impl MoovBox {
|
||||
#[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(())
|
||||
pub fn with_children(children: MoovChildren) -> Result<Self, ParseError> {
|
||||
Ok(Self { children: Boxes::new(children, [])? })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use bytes::BytesMut;
|
||||
|
||||
use crate::parse::Mp4Box;
|
||||
use crate::parse::{BoxType, Mp4Value};
|
||||
use crate::util::test::test_mvhd;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn test_trak() -> Mp4Box<TrakBox> {
|
||||
Mp4Box::with_data(TrakBox::with_children(vec![]).into()).unwrap()
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip() {
|
||||
let mut data = BytesMut::new();
|
||||
MoovBox::with_children(vec![test_trak().into()]).put_buf(&mut data);
|
||||
MoovBox::parse(&mut data).unwrap();
|
||||
MoovBox::parse(&mut MoovBox::dummy().to_bytes()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_traks() {
|
||||
let mut data = BytesMut::new();
|
||||
MoovBox::with_children(vec![]).put_buf(&mut data);
|
||||
let err = MoovBox::parse(&mut data).unwrap_err();
|
||||
let err = MoovBox::parse(&mut test_mvhd().to_bytes()).unwrap_err();
|
||||
assert!(
|
||||
matches!(err.get_ref(), ParseError::MissingRequiredBox(BoxType::TRAK)),
|
||||
"{err}",
|
||||
|
||||
@ -1,27 +1,28 @@
|
||||
#![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::From;
|
||||
use derive_more::{Deref, DerefMut, 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::Result;
|
||||
use crate::error::{Report, Result};
|
||||
use crate::parse::error::ParseResultExt;
|
||||
use crate::util::IoResultExt;
|
||||
use crate::{AsyncSkip, BoxDataTooLarge, Error};
|
||||
|
||||
use super::error::{MultipleBoxes, WhileParsingBox};
|
||||
use super::{BoxHeader, BoxType, Mp4Value, ParseError};
|
||||
use super::{BoxHeader, BoxType, Mp4Prim, Mp4Value, Mp4ValueWriterExt, ParseError};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive_where(Clone; BoxData<T>)]
|
||||
@ -40,9 +41,9 @@ pub enum BoxData<T: ?Sized> {
|
||||
}
|
||||
|
||||
pub trait ParseBox: Sized {
|
||||
fn parse(buf: &mut BytesMut) -> Result<Self, ParseError>;
|
||||
const NAME: BoxType;
|
||||
|
||||
fn box_type() -> BoxType;
|
||||
fn parse(buf: &mut BytesMut) -> Result<Self, ParseError>;
|
||||
}
|
||||
|
||||
#[clonable]
|
||||
@ -56,30 +57,67 @@ pub trait ParsedBox: Clone + Debug + Downcast {
|
||||
#[derive_where(Clone, Debug, Default)]
|
||||
pub struct Boxes<V = ()> {
|
||||
boxes: Vec<AnyMp4Box>,
|
||||
_validator: PhantomData<V>,
|
||||
_typed: PhantomData<V>,
|
||||
}
|
||||
|
||||
pub trait BoxesValidator {
|
||||
fn validate<V>(_boxes: &Boxes<V>) -> Result<(), ParseError> {
|
||||
Ok(())
|
||||
}
|
||||
#[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)
|
||||
}
|
||||
|
||||
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::box_type(), data.encoded_len())?;
|
||||
let parsed_header = BoxHeader::with_data_size(T::NAME, 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.
|
||||
pub(crate) async fn read_data<R>(
|
||||
mut reader: Pin<&mut BufReader<R>>,
|
||||
header: BoxHeader,
|
||||
max_size: u64,
|
||||
) -> StdResult<Self, Error>
|
||||
///
|
||||
/// 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>
|
||||
where
|
||||
R: AsyncRead + AsyncSkip,
|
||||
T: ParseBox,
|
||||
@ -117,8 +155,12 @@ 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::box_type() {
|
||||
if self.parsed_header.box_type() != U::NAME {
|
||||
return Ok(None);
|
||||
}
|
||||
self.data.parse_as()
|
||||
@ -127,8 +169,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::<Self>())?;
|
||||
let data = BoxData::get_from_bytes_mut(buf, &parsed_header).attach_printable(WhileParsingType::new::<Self>())?;
|
||||
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>())?;
|
||||
Ok(Self { parsed_header, data })
|
||||
}
|
||||
|
||||
@ -161,7 +203,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()? {
|
||||
match header.box_data_size().while_parsing_box(header.box_type())? {
|
||||
None => Ok(Self::Bytes(take(buf))),
|
||||
Some(box_data_size) => match box_data_size.try_into() {
|
||||
Ok(box_data_size) => {
|
||||
@ -186,12 +228,12 @@ impl<T: ParsedBox + ?Sized> BoxData<T> {
|
||||
T: ParseBox + Sized,
|
||||
{
|
||||
if let BoxData::Bytes(data) = self {
|
||||
let parsed = T::parse(data).while_parsing_type()?;
|
||||
let parsed = T::parse(data).while_parsing_box(T::NAME)?;
|
||||
ensure_attach!(
|
||||
data.is_empty(),
|
||||
ParseError::InvalidInput,
|
||||
"extra unparsed data",
|
||||
WhileParsingBox(T::box_type()),
|
||||
WhileParsingBox(T::NAME),
|
||||
);
|
||||
*self = Self::Parsed(Box::new(parsed));
|
||||
}
|
||||
@ -201,14 +243,21 @@ 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_type()?;
|
||||
let parsed = U::parse(data).while_parsing_box(U::NAME)?;
|
||||
ensure_attach!(
|
||||
data.is_empty(),
|
||||
ParseError::InvalidInput,
|
||||
"extra unparsed data",
|
||||
WhileParsingBox(U::box_type()),
|
||||
WhileParsingBox(U::NAME),
|
||||
);
|
||||
*self = Self::Parsed(parsed.into());
|
||||
}
|
||||
@ -268,6 +317,21 @@ 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())
|
||||
@ -281,24 +345,40 @@ 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::box_type()).count() <= 1,
|
||||
self.box_types().filter(|box_type| *box_type == T::NAME).count() <= 1,
|
||||
ParseError::InvalidBoxLayout,
|
||||
MultipleBoxes(T::box_type()),
|
||||
MultipleBoxes(T::NAME),
|
||||
);
|
||||
self.get_mut()
|
||||
.next()
|
||||
.ok_or_else(|| ParseError::MissingRequiredBox(T::box_type()))?
|
||||
.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()
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: BoxesValidator> Mp4Value for Boxes<V> {
|
||||
impl<V: ParseBoxes> 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)?);
|
||||
}
|
||||
let boxes = Self { boxes, _validator: PhantomData };
|
||||
V::validate(&boxes)?;
|
||||
V::validate(&mut boxes)?;
|
||||
let boxes = Self { boxes, _typed: PhantomData };
|
||||
Ok(boxes)
|
||||
}
|
||||
|
||||
@ -313,14 +393,88 @@ impl<V: BoxesValidator> Mp4Value for Boxes<V> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> From<Vec<AnyMp4Box>> for Boxes<V> {
|
||||
fn from(boxes: Vec<AnyMp4Box>) -> Self {
|
||||
Self { boxes, _validator: PhantomData }
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// BoxesValidator impls
|
||||
// BoundedBoxes impls
|
||||
//
|
||||
|
||||
impl BoxesValidator for () {}
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
75
mp4san/src/parse/mvhd.rs
Normal file
75
mp4san/src/parse/mvhd.rs
Normal file
@ -0,0 +1,75 @@
|
||||
#![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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,51 +1,180 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use std::vec;
|
||||
|
||||
use derive_more::{Deref, DerefMut};
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::parse::error::WhileParsingBox;
|
||||
|
||||
use super::error::{ParseResultExt, WhileParsingChild};
|
||||
use super::{BoxType, Boxes, Co64Box, ParseBox, ParseError, ParsedBox, StcoBox};
|
||||
use super::{
|
||||
AnyMp4Box, Boxes, Co64Box, ParseBox, ParseBoxes, ParseError, ParsedBox, StcoBox, StscBox, StsdBox, StszBox, SttsBox,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, ParseBox, ParsedBox)]
|
||||
#[derive(Clone, Debug, Deref, DerefMut, ParseBox, ParsedBox)]
|
||||
#[box_type = "stbl"]
|
||||
pub struct StblBox {
|
||||
children: Boxes,
|
||||
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),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StblCoMut<'a> {
|
||||
pub enum StblCoRefMut<'a> {
|
||||
Stco(&'a mut StcoBox),
|
||||
Co64(&'a mut Co64Box),
|
||||
}
|
||||
|
||||
const NAME: BoxType = BoxType::STBL;
|
||||
const STCO: BoxType = BoxType::STCO;
|
||||
const CO64: BoxType = BoxType::CO64;
|
||||
#[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>,
|
||||
}
|
||||
|
||||
impl StblBox {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn with_children<C: Into<Boxes>>(children: C) -> Self {
|
||||
Self { children: children.into() }
|
||||
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 })
|
||||
}
|
||||
|
||||
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)
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -54,11 +183,46 @@ impl StblBox {
|
||||
// StblCoMut impls
|
||||
//
|
||||
|
||||
impl StblCoMut<'_> {
|
||||
impl StblCoRefMut<'_> {
|
||||
pub fn entry_count(&self) -> u32 {
|
||||
match self {
|
||||
StblCoMut::Stco(stco) => stco.entry_count(),
|
||||
StblCoMut::Co64(co64) => co64.entry_count(),
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
#![allow(missing_docs)]
|
||||
|
||||
use super::{ArrayEntryMut, BoundedArray, ConstFullBoxHeader, ParseBox, ParsedBox};
|
||||
use super::{ArrayEntry, ArrayEntryMut, BoundedArray, ConstFullBoxHeader, ParseBox, ParsedBox};
|
||||
|
||||
#[derive(Clone, Debug, Default, ParseBox, ParsedBox)]
|
||||
#[box_type = "stco"]
|
||||
@ -10,6 +10,10 @@ 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()
|
||||
}
|
||||
|
||||
54
mp4san/src/parse/string.rs
Normal file
54
mp4san/src/parse/string.rs
Normal file
@ -0,0 +1,54 @@
|
||||
#![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);
|
||||
}
|
||||
}
|
||||
59
mp4san/src/parse/stsc.rs
Normal file
59
mp4san/src/parse/stsc.rs
Normal file
@ -0,0 +1,59 @@
|
||||
#![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();
|
||||
}
|
||||
}
|
||||
14
mp4san/src/parse/stsd.rs
Normal file
14
mp4san/src/parse/stsd.rs
Normal file
@ -0,0 +1,14 @@
|
||||
#![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>,
|
||||
}
|
||||
50
mp4san/src/parse/stsz.rs
Normal file
50
mp4san/src/parse/stsz.rs
Normal file
@ -0,0 +1,50 @@
|
||||
#![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"))
|
||||
})
|
||||
}
|
||||
}
|
||||
48
mp4san/src/parse/stts.rs
Normal file
48
mp4san/src/parse/stts.rs
Normal file
@ -0,0 +1,48 @@
|
||||
#![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();
|
||||
}
|
||||
}
|
||||
76
mp4san/src/parse/tkhd.rs
Normal file
76
mp4san/src/parse/tkhd.rs
Normal file
@ -0,0 +1,76 @@
|
||||
#![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,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,30 +1,56 @@
|
||||
#![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;
|
||||
|
||||
use super::error::ParseResultExt;
|
||||
use super::mp4box::Boxes;
|
||||
use super::{BoxType, MdiaBox, ParseBox, ParseError, ParsedBox, StblCoMut};
|
||||
|
||||
#[derive(Clone, Debug, ParseBox, ParsedBox)]
|
||||
#[derive(Clone, Debug, Deref, DerefMut, ParseBox, ParsedBox)]
|
||||
#[box_type = "trak"]
|
||||
pub struct TrakBox {
|
||||
children: Boxes,
|
||||
pub children: Boxes<TrakChildren>,
|
||||
}
|
||||
|
||||
const NAME: BoxType = BoxType::TRAK;
|
||||
#[non_exhaustive]
|
||||
#[derive(Clone, Debug, ParseBoxes)]
|
||||
#[box_type = "trak"]
|
||||
pub struct TrakChildren {
|
||||
pub header: TkhdBox,
|
||||
pub media: MdiaBox,
|
||||
}
|
||||
|
||||
impl TrakBox {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn with_children<C: Into<Boxes>>(children: C) -> Self {
|
||||
Self { children: children.into() }
|
||||
pub fn with_children(children: TrakChildren) -> Result<Self, ParseError> {
|
||||
Ok(Self { children: Boxes::new(children, [])? })
|
||||
}
|
||||
|
||||
pub fn co_mut(&mut self) -> Result<StblCoMut<'_>, ParseError> {
|
||||
self.mdia_mut()?.minf_mut()?.stbl_mut()?.co_mut()
|
||||
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 mdia_mut(&mut self) -> Result<&mut MdiaBox, ParseError> {
|
||||
self.children.get_one_mut().while_parsing_child(NAME, BoxType::MDIA)
|
||||
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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,16 @@ 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 {
|
||||
@ -27,7 +37,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)
|
||||
|
||||
@ -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, BoxHeader, BoxType, BoxUuid, FourCC, FullBoxHeader, Mp4Box, Mp4Value};
|
||||
use crate::parse::{fourcc, AnyMp4Box, BoxFlags, 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: 1 }.put_buf(&mut out);
|
||||
FullBoxHeader { version: 0, flags: BoxFlags(1) }.put_buf(&mut out);
|
||||
}
|
||||
|
||||
pub fn write_test_mdat(out: &mut Vec<u8>, data: &[u8]) -> InputSpan {
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
use bytes::BytesMut;
|
||||
use derive_builder::Builder;
|
||||
|
||||
use crate::parse::{fourcc, Co64Box, MdiaBox, MinfBox, MoovBox, Mp4Box, StblBox, StcoBox, TrakBox};
|
||||
use crate::parse::{
|
||||
fourcc, AnyMp4Box, BoxData, Boxes, Co64Box, MdiaBox, MinfBox, MoovBox, Mp4Box, Mp4Value, ParseBox, ParsedBox,
|
||||
StblBox, StcoBox, TrakBox,
|
||||
};
|
||||
|
||||
use super::{test_dinf, test_hdlr, test_mdhd, test_mvhd, test_stsc, test_stsd, test_stsz, test_stts, test_tkhd};
|
||||
|
||||
@ -47,23 +51,37 @@ impl TestMoovBuilder {
|
||||
|
||||
let mut minf = vec![test_dinf()];
|
||||
if spec.stbl {
|
||||
minf.push(Mp4Box::with_data(StblBox::with_children(stbl).into()).unwrap().into());
|
||||
let stbl: Mp4Box<StblBox> = container(stbl);
|
||||
minf.push(stbl.into());
|
||||
}
|
||||
|
||||
let mut mdia = vec![test_mdhd(), test_hdlr(fourcc::META)];
|
||||
if spec.minf {
|
||||
mdia.push(Mp4Box::with_data(MinfBox::with_children(minf).into()).unwrap().into());
|
||||
let minf: Mp4Box<MinfBox> = container(minf);
|
||||
mdia.push(minf.into());
|
||||
}
|
||||
|
||||
let mut trak = vec![test_tkhd(1)];
|
||||
if spec.mdia {
|
||||
trak.push(Mp4Box::with_data(MdiaBox::with_children(mdia).into()).unwrap().into());
|
||||
let mdia: Mp4Box<MdiaBox> = container(mdia);
|
||||
trak.push(mdia.into());
|
||||
}
|
||||
|
||||
let mut moov = vec![test_mvhd()];
|
||||
if spec.trak {
|
||||
moov.push(Mp4Box::with_data(TrakBox::with_children(trak).into()).unwrap().into());
|
||||
let trak: Mp4Box<TrakBox> = container(trak);
|
||||
moov.push(trak.into());
|
||||
}
|
||||
Mp4Box::with_data(MoovBox::with_children(moov).into()).unwrap()
|
||||
container(moov)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user