Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
add creation and modification fields to MediaContext
The mvhd creation and modification times are now extracted from the moov if
present.  While these aren't needed to play the MP4, they can be useful for
applications which allow sorting and/or searching by creation or modification
time.
  • Loading branch information
dicej committed Dec 24, 2024
commit 43ae32f3b32d2566296342311b769b537fbb06d1
55 changes: 36 additions & 19 deletions mp4parse/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,8 @@ impl FileTypeBox {
/// Movie header box 'mvhd'.
#[derive(Debug)]
struct MovieHeaderBox {
creation: u64,
modification: u64,
pub timescale: u32,
#[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340
duration: u64,
Expand Down Expand Up @@ -1503,9 +1505,14 @@ pub enum XmlBox {
BinaryXmlBox(TryVec<u8>),
}

#[derive(Debug)]
pub struct UtcSecondsSince1904(pub u64);

/// Internal data structures.
#[derive(Debug, Default)]
pub struct MediaContext {
pub creation: Option<UtcSecondsSince1904>,
pub modification: Option<UtcSecondsSince1904>,
pub timescale: Option<MediaTimeScale>,
/// Tracks found in the file.
pub tracks: TryVec<Track>,
Expand Down Expand Up @@ -4115,16 +4122,12 @@ pub fn read_mp4<T: Read>(f: &mut T) -> Result<MediaContext> {
context.ok_or(Error::MoovMissing)
}

/// Parse a Movie Header Box
/// See ISOBMFF (ISO 14496-12:2020) § 8.2.2
fn parse_mvhd<T: Read>(f: &mut BMFFBox<T>) -> Result<Option<MediaTimeScale>> {
let mvhd = read_mvhd(f)?;
debug!("{:?}", mvhd);
fn validate_timescale(mvhd: &MovieHeaderBox) -> Result<Option<MediaTimeScale>> {
if mvhd.timescale == 0 {
return Status::MvhdBadTimescale.into();
Err(Error::InvalidData(Status::MdhdBadTimescale))
} else {
Ok(Some(MediaTimeScale(u64::from(mvhd.timescale))))
}
let timescale = Some(MediaTimeScale(u64::from(mvhd.timescale)));
Ok(timescale)
}

/// Parse a Movie Box
Expand All @@ -4134,6 +4137,8 @@ fn parse_mvhd<T: Read>(f: &mut BMFFBox<T>) -> Result<Option<MediaTimeScale>> {
/// such as with tests/test_case_1185230.mp4.
fn read_moov<T: Read>(f: &mut BMFFBox<T>, context: Option<MediaContext>) -> Result<MediaContext> {
let MediaContext {
mut creation,
mut modification,
mut timescale,
mut tracks,
mut mvex,
Expand All @@ -4147,7 +4152,11 @@ fn read_moov<T: Read>(f: &mut BMFFBox<T>, context: Option<MediaContext>) -> Resu
while let Some(mut b) = iter.next_box()? {
match b.head.name {
BoxType::MovieHeaderBox => {
timescale = parse_mvhd(&mut b)?;
let mvhd = read_mvhd(&mut b)?;
debug!("{:?}", mvhd);
creation = Some(UtcSecondsSince1904(mvhd.creation));
modification = Some(UtcSecondsSince1904(mvhd.modification));
timescale = validate_timescale(&mvhd)?;
}
BoxType::TrackBox => {
let mut track = Track::new(tracks.len());
Expand Down Expand Up @@ -4178,6 +4187,8 @@ fn read_moov<T: Read>(f: &mut BMFFBox<T>, context: Option<MediaContext>) -> Resu
}

Ok(MediaContext {
creation,
modification,
timescale,
tracks,
mvex,
Expand Down Expand Up @@ -4490,28 +4501,32 @@ fn read_ftyp<T: Read>(src: &mut BMFFBox<T>) -> Result<FileTypeBox> {
/// Parse an mvhd box.
fn read_mvhd<T: Read>(src: &mut BMFFBox<T>) -> Result<MovieHeaderBox> {
let (version, _) = read_fullbox_extra(src)?;
let to_u64 = |n| {
if n == std::u32::MAX {
std::u64::MAX
} else {
u64::from(n)
}
};
let creation;
let modification;
match version {
// 64 bit creation and modification times.
1 => {
skip(src, 16)?;
creation = be_u64(src)?;
modification = be_u64(src)?;
}
// 32 bit creation and modification times.
0 => {
skip(src, 8)?;
creation = to_u64(be_u32(src)?);
modification = to_u64(be_u32(src)?);
}
_ => return Status::MvhdBadVersion.into(),
}
let timescale = be_u32(src)?;
let duration = match version {
1 => be_u64(src)?,
0 => {
let d = be_u32(src)?;
if d == u32::MAX {
u64::MAX
} else {
u64::from(d)
}
}
0 => to_u64(be_u32(src)?),
_ => unreachable!("Should have returned Status::MvhdBadVersion"),
};
// Skip remaining valid fields.
Expand All @@ -4520,6 +4535,8 @@ fn read_mvhd<T: Read>(src: &mut BMFFBox<T>) -> Result<MovieHeaderBox> {
// Padding could be added in some contents.
skip_box_remain(src)?;
Ok(MovieHeaderBox {
creation,
modification,
timescale,
duration,
})
Expand Down
2 changes: 1 addition & 1 deletion mp4parse/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ fn read_mvhd_invalid_timescale() {
let mut stream = iter.next_box().unwrap().unwrap();
assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
assert_eq!(stream.head.size, 120);
let r = super::parse_mvhd(&mut stream);
let r = super::validate_timescale(&super::read_mvhd(&mut stream).unwrap());
assert!(r.is_err());
}

Expand Down