use crate::boxes::{BoxHeader, BoxRef, FourCC, NodeKind};
use crate::known_boxes::KnownBox;
use crate::util::ReadExt;
use std::io::{Read, Seek, SeekFrom};
#[derive(Debug)]
pub enum ParseError {
Io(std::io::Error),
InvalidSize,
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ParseError::Io(e) => write!(f, "io: {}", e),
ParseError::InvalidSize => write!(f, "invalid box size"),
}
}
}
impl std::error::Error for ParseError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ParseError::Io(e) => Some(e),
ParseError::InvalidSize => None,
}
}
}
impl From<std::io::Error> for ParseError {
fn from(e: std::io::Error) -> Self {
ParseError::Io(e)
}
}
pub type Result<T> = std::result::Result<T, ParseError>;
pub fn read_box_header<R: Read + Seek>(r: &mut R) -> Result<BoxHeader> {
let start = r.stream_position()?;
let size32 = r.read_u32_be()?;
let mut typ = [0u8; 4];
r.read_exact(&mut typ)?;
let mut size = size32 as u64;
if size32 == 1 {
size = r.read_u64_be()?;
}
let mut uuid = None;
if &typ == b"uuid" {
let mut u = [0u8; 16];
r.read_exact(&mut u)?;
uuid = Some(u);
}
let header_size = match (size32 == 1, &typ == b"uuid") {
(true, true) => 8 + 8 + 16,
(true, false) => 8 + 8,
(false, true) => 8 + 16,
(false, false) => 8,
} as u64;
if size != 0 && size < header_size {
return Err(ParseError::InvalidSize);
}
Ok(BoxHeader {
size,
typ: FourCC(typ),
uuid,
header_size,
start,
})
}
pub fn parse_boxes<R: Read + Seek>(r: &mut R, start: u64, end: u64) -> Result<Vec<BoxRef>> {
r.seek(SeekFrom::Start(start))?;
parse_children(r, end)
}
#[derive(Debug, Clone, serde::Serialize)]
pub struct ParseIssue {
pub offset: u64,
pub message: String,
}
impl std::fmt::Display for ParseIssue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:#x}: {}", self.offset, self.message)
}
}
pub fn parse_boxes_tolerant<R: Read + Seek>(
r: &mut R,
start: u64,
end: u64,
) -> Result<(Vec<BoxRef>, Vec<ParseIssue>)> {
r.seek(SeekFrom::Start(start))?;
let mut issues = Vec::new();
let boxes = match parse_children_impl(r, end, &mut Some(&mut issues)) {
Ok(boxes) => boxes,
Err(e) => {
issues.push(ParseIssue {
offset: r.stream_position().unwrap_or(start),
message: format!("parse aborted: {}", e),
});
Vec::new()
}
};
Ok((boxes, issues))
}
pub fn parse_children<R: Read + Seek>(r: &mut R, parent_end: u64) -> Result<Vec<BoxRef>> {
parse_children_impl(r, parent_end, &mut None)
}
fn parse_children_impl<R: Read + Seek>(
r: &mut R,
parent_end: u64,
issues: &mut Option<&mut Vec<ParseIssue>>,
) -> Result<Vec<BoxRef>> {
let mut kids = Vec::new();
while r.stream_position()? + 8 <= parent_end {
let header_start = r.stream_position()?;
let h = match read_box_header(r) {
Ok(h) => h,
Err(e) => {
let Some(issues) = issues else {
return Err(e);
};
issues.push(ParseIssue {
offset: header_start,
message: format!(
"unreadable box header ({}); skipping remaining {} bytes of container",
e,
parent_end - header_start
),
});
break;
}
};
if let Some(issues) = issues
&& h.size != 0
&& h.start + h.size > parent_end
{
issues.push(ParseIssue {
offset: h.start,
message: format!(
"box '{}' declares size {} which overruns its container by {} bytes; clamped",
h.typ,
h.size,
h.start + h.size - parent_end
),
});
}
let box_end = box_end(&h, parent_end);
let kind = match classify_box(r, &h, box_end, issues) {
Ok(kind) => kind,
Err(e) => {
let Some(issues) = issues else {
return Err(e);
};
issues.push(ParseIssue {
offset: h.start,
message: format!("failed to parse contents of '{}': {}", h.typ, e),
});
let data_offset = h.start + h.header_size;
NodeKind::Leaf {
data_offset,
data_len: box_end.saturating_sub(data_offset),
}
}
};
r.seek(SeekFrom::Start(box_end))?;
kids.push(BoxRef { hdr: h, kind });
}
Ok(kids)
}
fn box_end(h: &BoxHeader, parent_end: u64) -> u64 {
if h.size == 0 {
parent_end
} else {
(h.start + h.size).min(parent_end)
}
}
fn read_version_flags<R: Read>(r: &mut R) -> Result<(u8, u32)> {
let version = r.read_u8()?;
let mut f = [0u8; 3];
r.read_exact(&mut f)?;
let flags = ((f[0] as u32) << 16) | ((f[1] as u32) << 8) | (f[2] as u32);
Ok((version, flags))
}
fn classify_box<R: Read + Seek>(
r: &mut R,
h: &BoxHeader,
box_end: u64,
issues: &mut Option<&mut Vec<ParseIssue>>,
) -> Result<NodeKind> {
let kb = KnownBox::from(h.typ);
let content_start = h.start + h.header_size;
if kb == KnownBox::Stsd {
return parse_stsd(r, h, box_end, issues);
}
if kb.is_full_container() {
if kb == KnownBox::Meta && meta_is_quicktime_style(r, content_start, box_end)? {
r.seek(SeekFrom::Start(content_start))?;
return Ok(NodeKind::Container(parse_children_impl(
r, box_end, issues,
)?));
}
r.seek(SeekFrom::Start(content_start))?;
let (version, flags) = read_version_flags(r)?;
let data_offset = r.stream_position()?;
let data_len = box_end.saturating_sub(data_offset);
let children = parse_children_impl(r, box_end, issues)?;
return Ok(NodeKind::FullContainer {
version,
flags,
data_offset,
data_len,
children,
});
}
if kb.is_container() {
r.seek(SeekFrom::Start(content_start))?;
return Ok(NodeKind::Container(parse_children_impl(
r, box_end, issues,
)?));
}
if kb.is_full_box() {
r.seek(SeekFrom::Start(content_start))?;
let (version, flags) = read_version_flags(r)?;
let data_offset = r.stream_position()?;
let data_len = box_end.saturating_sub(data_offset);
return Ok(NodeKind::FullBox {
version,
flags,
data_offset,
data_len,
});
}
let data_offset = content_start;
let data_len = box_end.saturating_sub(data_offset);
if &h.typ.0 == b"uuid" {
Ok(NodeKind::Unknown {
data_offset,
data_len,
})
} else {
Ok(NodeKind::Leaf {
data_offset,
data_len,
})
}
}
fn meta_is_quicktime_style<R: Read + Seek>(
r: &mut R,
content_start: u64,
box_end: u64,
) -> Result<bool> {
if content_start + 8 > box_end {
return Ok(false);
}
r.seek(SeekFrom::Start(content_start))?;
let size = r.read_u32_be()? as u64;
let mut typ = [0u8; 4];
r.read_exact(&mut typ)?;
Ok(size >= 8 && content_start + size <= box_end && fourcc_is_printable(&FourCC(typ)))
}
fn fourcc_is_printable(cc: &FourCC) -> bool {
cc.0.iter().all(|&c| (0x20..=0x7e).contains(&c))
}
fn parse_stsd<R: Read + Seek>(
r: &mut R,
h: &BoxHeader,
box_end: u64,
issues: &mut Option<&mut Vec<ParseIssue>>,
) -> Result<NodeKind> {
let content_start = h.start + h.header_size;
r.seek(SeekFrom::Start(content_start))?;
let (version, flags) = read_version_flags(r)?;
let data_offset = r.stream_position()?;
let data_len = box_end.saturating_sub(data_offset);
let _entry_count = r.read_u32_be()?;
let mut children = Vec::new();
while r.stream_position()? + 8 <= box_end {
let entry_start = r.stream_position()?;
let eh = match read_box_header(r) {
Ok(eh) => eh,
Err(e) => {
let Some(issues) = issues else {
return Err(e);
};
issues.push(ParseIssue {
offset: entry_start,
message: format!("unreadable stsd sample entry header ({})", e),
});
break;
}
};
let entry_end = self::box_end(&eh, box_end);
let kind = parse_sample_entry(r, &eh, entry_end)?;
r.seek(SeekFrom::Start(entry_end))?;
children.push(BoxRef { hdr: eh, kind });
}
Ok(NodeKind::FullContainer {
version,
flags,
data_offset,
data_len,
children,
})
}
fn sample_entry_fixed_len<R: Read + Seek>(
r: &mut R,
typ: FourCC,
content_start: u64,
entry_end: u64,
) -> Result<Option<u64>> {
const VISUAL: u64 = 8 + 70;
const AUDIO_V0: u64 = 8 + 20;
const AUDIO_V1: u64 = AUDIO_V0 + 16;
const AUDIO_V2: u64 = 8 + 56;
match KnownBox::from(typ) {
KnownBox::Avc1
| KnownBox::Avc2
| KnownBox::Avc3
| KnownBox::Avc4
| KnownBox::Hev1
| KnownBox::Hvc1
| KnownBox::Vvc1
| KnownBox::Mp4v
| KnownBox::Vp08
| KnownBox::Vp09
| KnownBox::Av01
| KnownBox::Dvh1
| KnownBox::Dvhe
| KnownBox::Dav1
| KnownBox::Encv => Ok(Some(VISUAL)),
KnownBox::Mp4a
| KnownBox::Ac3
| KnownBox::Ec3
| KnownBox::Opus
| KnownBox::Samr
| KnownBox::Sawb
| KnownBox::Alac
| KnownBox::Flac
| KnownBox::Enca
| KnownBox::Ipcm
| KnownBox::Fpcm => {
if content_start + 10 > entry_end {
return Ok(None);
}
r.seek(SeekFrom::Start(content_start + 8))?;
let qt_version = r.read_u16_be()?;
Ok(Some(match qt_version {
1 => AUDIO_V1,
2 => AUDIO_V2,
_ => AUDIO_V0,
}))
}
_ => Ok(None),
}
}
fn parse_sample_entry<R: Read + Seek>(
r: &mut R,
h: &BoxHeader,
entry_end: u64,
) -> Result<NodeKind> {
let content_start = h.start + h.header_size;
let leaf = NodeKind::Leaf {
data_offset: content_start,
data_len: entry_end.saturating_sub(content_start),
};
let Some(fixed) = sample_entry_fixed_len(r, h.typ, content_start, entry_end)? else {
return Ok(leaf);
};
let child_start = content_start + fixed;
if child_start + 8 > entry_end {
return Ok(leaf);
}
r.seek(SeekFrom::Start(child_start))?;
match parse_children(r, entry_end) {
Ok(kids) if !kids.is_empty() && kids.iter().all(|k| fourcc_is_printable(&k.hdr.typ)) => {
Ok(NodeKind::Container(kids))
}
_ => Ok(leaf),
}
}