use crate::{Error, Reader};
use std::{collections::HashMap, rc::Rc};
type CallbackResult = Result<(), Error>;
#[derive(Default)]
pub struct Mp4Parser {
#[allow(clippy::type_complexity)]
box_definitions: HashMap<usize, (BoxType, Rc<dyn Fn(ParsedBox) -> CallbackResult>)>,
done: bool,
}
impl Mp4Parser {
pub fn new() -> Self {
Self::default()
}
pub fn base_box(
mut self,
type_: &str,
definition: impl Fn(ParsedBox) -> CallbackResult + 'static,
) -> Self {
let type_code = type_from_string(type_);
self.box_definitions
.insert(type_code, (BoxType::BasicBox, Rc::new(definition)));
self
}
pub fn full_box(
mut self,
type_: &str,
definition: impl Fn(ParsedBox) -> CallbackResult + 'static,
) -> Self {
let type_code = type_from_string(type_);
self.box_definitions
.insert(type_code, (BoxType::FullBox, Rc::new(definition)));
self
}
pub fn stop(&mut self) {
self.done = true;
}
pub fn parse(
&mut self,
data: &[u8],
partial_okay: bool,
stop_on_partial: bool,
) -> CallbackResult {
let mut reader = Reader::new_big_endian(data);
self.done = false;
while reader.has_more_data() && !self.done {
self.parse_next(0, &mut reader, partial_okay, stop_on_partial)?;
}
Ok(())
}
fn parse_next(
&mut self,
abs_start: u64,
reader: &mut Reader,
partial_okay: bool,
stop_on_partial: bool,
) -> CallbackResult {
let start = reader.get_position();
if stop_on_partial && start + 8 > reader.get_length() {
self.done = true;
return Ok(());
}
let mut size = reader.read_u32()? as u64;
let type_ = reader.read_u32()? as usize;
let name = type_to_string(type_)?;
let mut has_64_bit_size = false;
match size {
0 => size = reader.get_length() - start,
1 => {
if stop_on_partial && reader.get_position() + 8 > reader.get_length() {
self.done = true;
return Ok(());
}
size = reader.read_u64()?;
has_64_bit_size = true;
}
_ => (),
}
let box_definition = self.box_definitions.get(&type_).cloned();
if let Some((header_type, box_definition)) = box_definition {
let mut version = None;
let mut flags = None;
if let BoxType::FullBox = header_type {
if stop_on_partial && reader.get_position() + 4 > reader.get_length() {
self.done = true;
return Ok(());
}
let version_and_flags = reader.read_u32()?;
version = Some(version_and_flags >> 24);
flags = Some(version_and_flags & 0xFFFFFF);
}
let mut end = start + size;
if partial_okay && end > reader.get_length() {
end = reader.get_length();
}
if stop_on_partial && end > reader.get_length() {
self.done = true;
return Ok(());
}
let header_end = reader.get_position();
let payload_size = end - header_end;
let payload = if payload_size > 0 {
reader.read_bytes_u8(payload_size as usize)?
} else {
Vec::with_capacity(0)
};
let payload_reader = Reader::new_big_endian(&payload);
let box_ = ParsedBox {
name,
parser: self,
partial_okay,
stop_on_partial,
version,
flags,
reader: payload_reader,
size: size as usize,
start: start + abs_start,
has_64_bit_size,
header: reader.as_bytes()[start as usize..header_end as usize].to_vec(),
};
box_definition(box_)?;
} else {
let skip_length = (start + size - reader.get_position())
.min(reader.get_length() - reader.get_position());
reader.skip(skip_length)?;
}
Ok(())
}
}
pub fn children(mut box_: ParsedBox) -> CallbackResult {
let header_size = box_.header_size();
while box_.reader.has_more_data() && !box_.parser.done {
box_.parser.parse_next(
box_.start + header_size,
&mut box_.reader,
box_.partial_okay,
box_.stop_on_partial,
)?;
}
Ok(())
}
pub fn sample_description(mut box_: ParsedBox) -> CallbackResult {
let header_size = box_.header_size();
let count = box_.reader.read_u32()?;
for _ in 0..count {
box_.parser.parse_next(
box_.start + header_size,
&mut box_.reader,
box_.partial_okay,
box_.stop_on_partial,
)?;
if box_.parser.done {
break;
}
}
Ok(())
}
pub fn visual_sample_entry(mut box_: ParsedBox) -> CallbackResult {
let header_size = box_.header_size();
box_.reader.skip(78)?;
while box_.reader.has_more_data() && !box_.parser.done {
box_.parser.parse_next(
box_.start + header_size,
&mut box_.reader,
box_.partial_okay,
box_.stop_on_partial,
)?;
}
Ok(())
}
pub fn audio_sample_entry(mut box_: ParsedBox) -> CallbackResult {
let header_size = box_.header_size();
box_.reader.skip(8)?;
let version = box_.reader.read_u16()?;
box_.reader.skip(6)?;
if version == 2 {
box_.reader.skip(48)?;
} else {
box_.reader.skip(12)?;
}
if version == 1 {
box_.reader.skip(16)?;
}
while box_.reader.has_more_data() && !box_.parser.done {
box_.parser.parse_next(
box_.start + header_size,
&mut box_.reader,
box_.partial_okay,
box_.stop_on_partial,
)?;
}
Ok(())
}
pub fn alldata(
callback: impl Fn(Vec<u8>) -> CallbackResult + 'static,
) -> impl Fn(ParsedBox) -> CallbackResult + 'static {
move |mut box_| {
let all = box_.reader.get_length() - box_.reader.get_position();
callback(box_.reader.read_bytes_u8(all as usize)?)
}
}
pub fn type_from_string(name: &str) -> usize {
assert!(name.len() == 4, "MP4 box names must be 4 characters long");
let mut code = 0;
for chr in name.chars() {
code = (code << 8) | chr as usize;
}
code
}
pub fn type_to_string(type_: usize) -> Result<String, std::string::FromUtf8Error> {
String::from_utf8(vec![
((type_ >> 24) & 0xff) as u8,
((type_ >> 16) & 0xff) as u8,
((type_ >> 8) & 0xff) as u8,
(type_ & 0xff) as u8,
])
}
#[derive(Clone)]
pub enum BoxType {
BasicBox,
FullBox,
}
pub struct ParsedBox<'a> {
pub name: String,
pub parser: &'a mut Mp4Parser,
pub partial_okay: bool,
pub stop_on_partial: bool,
pub start: u64, pub size: usize,
pub version: Option<u32>,
pub flags: Option<u32>,
pub reader: Reader,
pub has_64_bit_size: bool,
pub header: Vec<u8>,
}
impl<'a> ParsedBox<'a> {
pub fn header_size(&self) -> u64 {
let basic_header_size = 8;
let _64_bit_field_size = if self.has_64_bit_size { 8 } else { 0 };
let version_and_flags_size = if self.flags.is_some() { 4 } else { 0 };
basic_header_size + _64_bit_field_size + version_and_flags_size
}
pub fn full_data(&self) -> Vec<u8> {
let mut data = Vec::with_capacity(self.header.len() + self.reader.as_bytes().len());
data.extend_from_slice(&self.header);
data.extend_from_slice(self.reader.as_bytes());
data
}
}