use nom::{
bytes::complete::take,
number::complete::{be_u16, be_u32, be_u64},
sequence::tuple,
};
use super::{find_box, travel_while, BoxHolder, FullBoxHeader, ParseBody, ParseBox};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TkhdBox {
header: FullBoxHeader,
creation_time: u32,
modification_time: u32,
track_id: u32,
duration: u32,
layer: u16,
alt_group: u16,
volume: u16,
pub width: u32,
pub height: u32,
}
impl ParseBody<TkhdBox> for TkhdBox {
fn parse_body(body: &[u8], header: FullBoxHeader) -> nom::IResult<&[u8], TkhdBox> {
let (
remain,
(
creation_time,
modification_time,
track_id,
_,
duration,
_,
layer,
alt_group,
volume,
_,
_,
width,
_,
height,
_,
),
) = tuple((
be_u32,
be_u32,
be_u32,
be_u32,
be_u32,
be_u64,
be_u16,
be_u16,
be_u16,
be_u16,
take(36usize),
be_u16,
be_u16,
be_u16,
be_u16,
))(body)?;
Ok((
remain,
TkhdBox {
header,
creation_time,
modification_time,
track_id,
duration,
layer,
alt_group,
volume,
width: width as u32,
height: height as u32,
},
))
}
}
pub fn parse_video_tkhd_in_moov(input: &[u8]) -> crate::Result<Option<TkhdBox>> {
let Some(bbox) = find_video_track(input)? else {
return Ok(None);
};
let (_, Some(bbox)) = find_box(bbox.body_data(), "tkhd")? else {
return Ok(None);
};
let (_, tkhd) = TkhdBox::parse_box(bbox.data).map_err(|_| "parse tkhd failed")?;
Ok(Some(tkhd))
}
fn find_video_track(input: &[u8]) -> crate::Result<Option<BoxHolder<'_>>> {
let (_, bbox) = travel_while(input, |b| {
if b.box_type() != "trak" {
true
} else {
let found = find_box(b.body_data(), "mdia/hdlr");
let Ok(bbox) = found else {
return true;
};
let Some(hdlr) = bbox.1 else {
return true;
};
if hdlr.body_data().len() < 12 {
return true;
}
let subtype = &hdlr.body_data()[8..12]; if subtype == b"vide" {
false
} else {
true
}
}
})
.map_err(|e| format!("find vide trak failed: {e:?}"))?;
Ok(bbox)
}
#[cfg(test)]
mod tests {
use crate::{bbox::travel_while, testkit::read_sample};
use super::*;
use test_case::test_case;
#[test_case("meta.mov", 720, 1280)]
#[test_case("meta.mp4", 1920, 1080)]
fn tkhd_box(path: &str, width: u32, height: u32) {
let _ = tracing_subscriber::fmt().with_test_writer().try_init();
let buf = read_sample(path).unwrap();
let (_, bbox) = travel_while(&buf, |b| b.box_type() != "moov").unwrap();
let bbox = bbox.unwrap();
let tkhd = parse_video_tkhd_in_moov(bbox.body_data()).unwrap().unwrap();
assert_eq!(tkhd.width, width);
assert_eq!(tkhd.height, height);
}
#[test_case("crash_moov-trak")]
fn tkhd_crash(path: &str) {
let _ = tracing_subscriber::fmt().with_test_writer().try_init();
let buf = read_sample(path).unwrap();
let (_, bbox) = travel_while(&buf, |b| b.box_type() != "moov").unwrap();
let bbox = bbox.unwrap();
let _ = parse_video_tkhd_in_moov(bbox.body_data());
}
}