diff --git a/mp4parse/src/lib.rs b/mp4parse/src/lib.rs index 1fb2ef4d..8f1b6222 100644 --- a/mp4parse/src/lib.rs +++ b/mp4parse/src/lib.rs @@ -1888,135 +1888,6 @@ impl DataBox { } } -#[cfg(test)] -mod media_data_box_tests { - use super::*; - - impl DataBox { - fn at_offset(file_offset: u64, data: std::vec::Vec) -> Self { - DataBox { - metadata: DataBoxMetadata::Mdat { file_offset }, - data: data.into(), - } - } - } - - #[test] - fn extent_with_length_before_mdat_returns_none() { - let mdat = DataBox::at_offset(100, vec![1; 5]); - let extent = Extent::WithLength { offset: 0, len: 2 }; - - assert!(mdat.get(&extent).is_none()); - } - - #[test] - fn extent_to_end_before_mdat_returns_none() { - let mdat = DataBox::at_offset(100, vec![1; 5]); - let extent = Extent::ToEnd { offset: 0 }; - - assert!(mdat.get(&extent).is_none()); - } - - #[test] - fn extent_with_length_crossing_front_mdat_boundary_returns_none() { - let mdat = DataBox::at_offset(100, vec![1; 5]); - let extent = Extent::WithLength { offset: 99, len: 3 }; - - assert!(mdat.get(&extent).is_none()); - } - - #[test] - fn extent_with_length_which_is_subset_of_mdat() { - let mdat = DataBox::at_offset(100, vec![1; 5]); - let extent = Extent::WithLength { - offset: 101, - len: 2, - }; - - assert_eq!(mdat.get(&extent), Some(&[1, 1][..])); - } - - #[test] - fn extent_to_end_which_is_subset_of_mdat() { - let mdat = DataBox::at_offset(100, vec![1; 5]); - let extent = Extent::ToEnd { offset: 101 }; - - assert_eq!(mdat.get(&extent), Some(&[1, 1, 1, 1][..])); - } - - #[test] - fn extent_with_length_which_is_all_of_mdat() { - let mdat = DataBox::at_offset(100, vec![1; 5]); - let extent = Extent::WithLength { - offset: 100, - len: 5, - }; - - assert_eq!(mdat.get(&extent), Some(mdat.data.as_slice())); - } - - #[test] - fn extent_to_end_which_is_all_of_mdat() { - let mdat = DataBox::at_offset(100, vec![1; 5]); - let extent = Extent::ToEnd { offset: 100 }; - - assert_eq!(mdat.get(&extent), Some(mdat.data.as_slice())); - } - - #[test] - fn extent_with_length_crossing_back_mdat_boundary_returns_none() { - let mdat = DataBox::at_offset(100, vec![1; 5]); - let extent = Extent::WithLength { - offset: 103, - len: 3, - }; - - assert!(mdat.get(&extent).is_none()); - } - - #[test] - fn extent_with_length_after_mdat_returns_none() { - let mdat = DataBox::at_offset(100, vec![1; 5]); - let extent = Extent::WithLength { - offset: 200, - len: 2, - }; - - assert!(mdat.get(&extent).is_none()); - } - - #[test] - fn extent_to_end_after_mdat_returns_none() { - let mdat = DataBox::at_offset(100, vec![1; 5]); - let extent = Extent::ToEnd { offset: 200 }; - - assert!(mdat.get(&extent).is_none()); - } - - #[test] - fn extent_with_length_which_overflows_usize() { - let mdat = DataBox::at_offset(std::u64::MAX - 1, vec![1; 5]); - let extent = Extent::WithLength { - offset: std::u64::MAX, - len: std::usize::MAX, - }; - - assert!(mdat.get(&extent).is_none()); - } - - // The end of the range would overflow `usize` if it were calculated, but - // because the range end is unbounded, we don't calculate it. - #[test] - fn extent_to_end_which_overflows_usize() { - let mdat = DataBox::at_offset(std::u64::MAX - 1, vec![1; 5]); - let extent = Extent::ToEnd { - offset: std::u64::MAX, - }; - - assert_eq!(mdat.get(&extent), Some(&[1, 1, 1, 1][..])); - } -} - #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] struct PropertyIndex(u16); #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] @@ -6289,3 +6160,132 @@ fn write_be_u32(des: &mut T, num: u32) -> Result<()> { des.write_u32::(num) .map_err(From::from) } + +#[cfg(test)] +mod media_data_box_tests { + use super::*; + + impl DataBox { + fn at_offset(file_offset: u64, data: std::vec::Vec) -> Self { + DataBox { + metadata: DataBoxMetadata::Mdat { file_offset }, + data: data.into(), + } + } + } + + #[test] + fn extent_with_length_before_mdat_returns_none() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { offset: 0, len: 2 }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_to_end_before_mdat_returns_none() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::ToEnd { offset: 0 }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_with_length_crossing_front_mdat_boundary_returns_none() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { offset: 99, len: 3 }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_with_length_which_is_subset_of_mdat() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { + offset: 101, + len: 2, + }; + + assert_eq!(mdat.get(&extent), Some(&[1, 1][..])); + } + + #[test] + fn extent_to_end_which_is_subset_of_mdat() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::ToEnd { offset: 101 }; + + assert_eq!(mdat.get(&extent), Some(&[1, 1, 1, 1][..])); + } + + #[test] + fn extent_with_length_which_is_all_of_mdat() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { + offset: 100, + len: 5, + }; + + assert_eq!(mdat.get(&extent), Some(mdat.data.as_slice())); + } + + #[test] + fn extent_to_end_which_is_all_of_mdat() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::ToEnd { offset: 100 }; + + assert_eq!(mdat.get(&extent), Some(mdat.data.as_slice())); + } + + #[test] + fn extent_with_length_crossing_back_mdat_boundary_returns_none() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { + offset: 103, + len: 3, + }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_with_length_after_mdat_returns_none() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { + offset: 200, + len: 2, + }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_to_end_after_mdat_returns_none() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::ToEnd { offset: 200 }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_with_length_which_overflows_usize() { + let mdat = DataBox::at_offset(std::u64::MAX - 1, vec![1; 5]); + let extent = Extent::WithLength { + offset: std::u64::MAX, + len: std::usize::MAX, + }; + + assert!(mdat.get(&extent).is_none()); + } + + // The end of the range would overflow `usize` if it were calculated, but + // because the range end is unbounded, we don't calculate it. + #[test] + fn extent_to_end_which_overflows_usize() { + let mdat = DataBox::at_offset(std::u64::MAX - 1, vec![1; 5]); + let extent = Extent::ToEnd { + offset: std::u64::MAX, + }; + + assert_eq!(mdat.get(&extent), Some(&[1, 1, 1, 1][..])); + } +} diff --git a/mp4parse/src/unstable.rs b/mp4parse/src/unstable.rs index 9b3e5b40..c185d5f1 100644 --- a/mp4parse/src/unstable.rs +++ b/mp4parse/src/unstable.rs @@ -465,7 +465,7 @@ impl<'a> SampleToChunkIterator<'a> { /// (n * s) / d is split into floor(n / d) * s + (n % d) * s / d. /// /// Return None on overflow or if the denominator is zero. -fn rational_scale(numerator: T, denominator: T, scale2: S) -> Option +pub fn rational_scale(numerator: T, denominator: T, scale2: S) -> Option where T: PrimInt + Zero, S: PrimInt, diff --git a/mp4parse_capi/src/lib.rs b/mp4parse_capi/src/lib.rs index 5b362f30..e143967d 100644 --- a/mp4parse_capi/src/lib.rs +++ b/mp4parse_capi/src/lib.rs @@ -35,6 +35,7 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. use byteorder::WriteBytesExt; +use mp4parse::unstable::rational_scale; use std::convert::TryFrom; use std::convert::TryInto; @@ -628,15 +629,21 @@ pub unsafe extern "C" fn mp4parse_get_track_info( let track = &context.tracks[track_index]; - if let (Some(timescale), Some(_)) = (track.timescale, context.timescale) { + if let (Some(timescale), Some(context_timescale)) = (track.timescale, context.timescale) { info.time_scale = timescale.0 as u32; let media_time: CheckedInteger = track .media_time .map_or(0.into(), |media_time| media_time.0.into()); - let empty_duration: CheckedInteger = track - .empty_duration - .map_or(0.into(), |empty_duration| empty_duration.0.into()); + // Empty duration is in the context's timescale, convert it and return it in the track's + // timescale + let empty_duration: CheckedInteger = + match track.empty_duration.map_or(Some(0), |empty_duration| { + rational_scale(empty_duration.0, context_timescale.0, timescale.0) + }) { + Some(time) => mp4parse::unstable::CheckedInteger(time), + None => return Mp4parseStatus::Invalid, + }; info.media_time = match media_time - empty_duration { Some(difference) => difference,