use crate::{Error, Reader};
use std::{collections::HashMap, sync::Arc};
pub type HandlerResult = Result<(), Error>;
pub type CallbackType = Arc<dyn Fn(ParsedBox) -> HandlerResult>;
#[derive(Clone, Default)]
pub struct Mp4Parser {
pub headers: HashMap<usize, BoxType>,
pub box_definitions: HashMap<usize, CallbackType>,
pub done: bool,
}
impl Mp4Parser {
pub fn _box(mut self, _type: &str, definition: CallbackType) -> Self {
let type_code = type_from_string(_type);
self.headers.insert(type_code, BoxType::BasicBox);
self.box_definitions.insert(type_code, definition);
self
}
pub fn full_box(mut self, _type: &str, definition: CallbackType) -> Self {
let type_code = type_from_string(_type);
self.headers.insert(type_code, BoxType::FullBox);
self.box_definitions.insert(type_code, definition);
self
}
pub fn stop(&mut self) {
self.done = true;
}
pub fn parse(
&mut self,
data: &[u8],
partial_okay: Option<bool>,
stop_on_partial: Option<bool>,
) -> HandlerResult {
let mut reader = Reader::new(data, false);
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: Option<bool>,
stop_on_partial: Option<bool>,
) -> HandlerResult {
let partial_okay = partial_okay.unwrap_or(false);
let stop_on_partial = stop_on_partial.unwrap_or(false);
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()
.map_err(|_| Error::new_read("box size (u32)."))? as u64;
let _type = reader
.read_u32()
.map_err(|_| Error::new_read("box type (u32)."))? as usize;
let name = type_to_string(_type)
.map_err(|_| Error::new_decode(format!("{_type} (u32) to string.")))?;
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()
.map_err(|_| Error::new_read("box size (u64)."))?;
has_64_bit_size = true;
}
_ => (),
}
let box_definition = self.box_definitions.get(&_type);
if let Some(box_definition) = box_definition {
let mut version = None;
let mut flags = None;
if *self.headers.get(&_type).unwrap() == BoxType::FullBox {
if stop_on_partial && reader.get_position() + 4 > reader.get_length() {
self.done = true;
return Ok(());
}
let version_and_flags = reader
.read_u32()
.map_err(|_| Error::new_read("box version and flags (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 payload_size = end - reader.get_position();
let payload = if payload_size > 0 {
reader.read_bytes_u8(payload_size as usize).map_err(|_| {
Error::new_read(format!("box payload ({payload_size} bytes)."))
})?
} else {
Vec::with_capacity(0)
};
let payload_reader = Reader::new(&payload, false);
let _box = ParsedBox {
name,
parser: self.clone(),
partial_okay,
version,
flags,
reader: payload_reader,
size: size as usize,
start: start + abs_start,
has_64_bit_size,
};
box_definition(_box)?;
} else {
let skip_length = (start + size - reader.get_position())
.min(reader.get_length() - reader.get_position());
reader
.skip(skip_length)
.map_err(|_| Error::new_read(format!("{skip_length} bytes.")))?;
}
Ok(())
}
}
pub fn children(mut _box: ParsedBox) -> HandlerResult {
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,
Some(_box.partial_okay),
None,
)?;
}
Ok(())
}
pub fn sample_description(mut _box: ParsedBox) -> HandlerResult {
let header_size = _box.header_size();
let count = _box
.reader
.read_u32()
.map_err(|_| Error::new_read("sample description count (u32)."))?;
for _ in 0..count {
_box.parser.parse_next(
_box.start + header_size,
&mut _box.reader,
Some(_box.partial_okay),
None,
)?;
if _box.parser.done {
break;
}
}
Ok(())
}
pub fn visual_sample_entry(mut _box: ParsedBox) -> HandlerResult {
let header_size = _box.header_size();
_box.reader
.skip(78)
.map_err(|_| Error::new_read("visual sample entry reserved 78 bytes."))?;
while _box.reader.has_more_data() && !_box.parser.done {
_box.parser.parse_next(
_box.start + header_size,
&mut _box.reader,
Some(_box.partial_okay),
None,
)?;
}
Ok(())
}
#[allow(clippy::arc_with_non_send_sync)]
pub fn alldata(callback: Arc<dyn Fn(Vec<u8>) -> HandlerResult>) -> CallbackType {
Arc::new(move |mut _box| {
let all = _box.reader.get_length() - _box.reader.get_position();
callback(
_box.reader
.read_bytes_u8(all as usize)
.map_err(|_| Error::new_read(format!("all data {all} bytes.")))?,
)
})
}
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, PartialEq)]
pub enum BoxType {
BasicBox,
FullBox,
}
#[derive(Clone, Default)]
pub struct ParsedBox {
pub name: String,
pub parser: Mp4Parser,
pub partial_okay: bool,
pub start: u64, pub size: usize,
pub version: Option<u32>,
pub flags: Option<u32>,
pub reader: Reader,
pub has_64_bit_size: bool,
}
impl ParsedBox {
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
}
}