diff --git a/qtff/src/file.rs b/qtff/src/file.rs index e4c8e0a5..f7d863a3 100644 --- a/qtff/src/file.rs +++ b/qtff/src/file.rs @@ -8,11 +8,13 @@ use super::{ data::{AtomData, MovieData, ReadData}, }; +use crate::moof::MovieFragment; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; pub struct File { f: std::fs::File, movie_data: Option, + movie_fragments: Option>, } enum Data { @@ -34,6 +36,7 @@ impl File { Ok(File { f: std::fs::File::open(path)?, movie_data: None, + movie_fragments: None, }) } @@ -65,6 +68,40 @@ impl File { } } + fn get_moof_data(&mut self) -> Result> { + let mut fragments = vec![]; + loop { + let start_position = self.f.stream_position()?; + + let atom = match AtomReader::new(&mut self.f).find(|a| match a { + Ok(a) => a.typ == MovieFragment::TYPE, + Err(_) => true, + }) { + Some(Ok(a)) => a, + Some(Err(err)) => return Err(err.into()), + None => return Ok(fragments), + }; + let mut buf = Vec::new(); + atom.data(&mut self.f).read_to_end(&mut buf)?; + + fragments.push((start_position, MovieFragment::read(Cursor::new(buf.as_slice()))?)); + } + } + + pub fn get_movie_fragments(&mut self) -> Result> { + if self.movie_data.is_none() { + self.get_movie_data()?; + } + match &self.movie_fragments { + Some(v) => Ok(v.clone()), + None => { + let movie_fragments = self.get_moof_data()?; + self.movie_fragments = Some(movie_fragments.clone()); + Ok(movie_fragments) + } + } + } + pub fn trim_frames(&mut self, w: W, start_frame: u64, frame_count: u64) -> Result<()> { let movie_data = self.get_movie_data()?; @@ -484,6 +521,7 @@ impl<'a> Seek for &'a File { #[cfg(test)] mod tests { use super::*; + use crate::moof::{FragmentHeader, TrackFragmentHeader, TrackFragmentRunSampleData}; #[test] fn test_file_prores() { @@ -692,4 +730,58 @@ mod tests { let movie_data = f.get_movie_data().unwrap(); assert_eq!(movie_data.tracks.len(), 3); } + + #[test] + fn test_file_fragmented_mp4() { + let mut f = File::open("src/testdata/fragmented.mp4").unwrap(); + let movie_data = f.get_movie_data().unwrap(); + assert_eq!(movie_data.tracks.len(), 1); + let fragments = f.get_movie_fragments().unwrap(); + assert_eq!(fragments.len(), 2); + + let fragment = &fragments[0]; + assert_eq!(fragment.0, 834); + assert_eq!( + fragment.1.fragment_header, + FragmentHeader { + version: 0, + flags: 0, + sequence_number: 1, + } + ); + assert_eq!(fragment.1.track_fragments.len(), 1); + + let track_fragment = &fragment.1.track_fragments[0]; + assert_eq!( + track_fragment.track_fragment_header, + TrackFragmentHeader { + version: 0, + flags: 57, + track_id: 1, + base_data_offset: Some(834), + sample_description_index: None, + default_sample_duration: Some(3003), + default_sample_size: Some(1288964), + default_sample_flags: Some(16842752), + duration_is_empty: false, + } + ); + + assert_eq!(track_fragment.track_fragment_runs.len(), 1); + + let track_fragment_run = &track_fragment.track_fragment_runs[0]; + assert_eq!(track_fragment_run.sample_count, 64); + assert_eq!(track_fragment_run.data_offset, Some(376)); + assert_eq!(track_fragment_run.first_sample_flags, Some(33554432)); + assert_eq!(track_fragment_run.sample_data.len(), 64); + assert_eq!( + track_fragment_run.sample_data[0], + TrackFragmentRunSampleData { + sample_duration: None, + sample_size: Some(1288964,), + sample_flags: None, + sample_composition_time_offset: None, + } + ); + } } diff --git a/qtff/src/lib.rs b/qtff/src/lib.rs index 38024012..6315cc08 100644 --- a/qtff/src/lib.rs +++ b/qtff/src/lib.rs @@ -8,6 +8,7 @@ pub mod data; pub mod deserializer; pub mod error; pub mod file; +pub mod moof; pub mod serializer; pub use atom::*; diff --git a/qtff/src/moof.rs b/qtff/src/moof.rs new file mode 100644 index 00000000..29cf8802 --- /dev/null +++ b/qtff/src/moof.rs @@ -0,0 +1,164 @@ +use super::error::{Error, Result}; +use crate::{read_all, read_one, AtomData, FourCC, ReadData}; +use byteorder::{BigEndian, ReadBytesExt}; +use std::io::{Read, Seek}; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct FragmentHeader { + pub version: u8, + pub flags: u32, + pub sequence_number: u32, +} + +impl AtomData for FragmentHeader { + const TYPE: FourCC = FourCC::MFHD; +} + +impl ReadData for FragmentHeader { + fn read(mut reader: R) -> Result { + Ok(Self { + version: reader.read_u8()?, + flags: reader.read_u24::()?, + sequence_number: reader.read_u32::()?, + }) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TrackFragmentHeader { + pub version: u8, + pub flags: u32, + pub track_id: u32, + pub base_data_offset: Option, + pub sample_description_index: Option, + pub default_sample_duration: Option, + pub default_sample_size: Option, + pub default_sample_flags: Option, + pub duration_is_empty: bool, +} + +impl AtomData for TrackFragmentHeader { + const TYPE: FourCC = FourCC::TFHD; +} + +impl ReadData for TrackFragmentHeader { + fn read(mut reader: R) -> Result { + let version = reader.read_u8()?; + let flags = reader.read_u24::()?; + Ok(Self { + version, + flags, + track_id: reader.read_u32::()?, + base_data_offset: if (flags & 0x1) == 1 { Some(reader.read_u64::()?) } else { None }, + sample_description_index: if (flags & 0x2) != 0 { Some(reader.read_u32::()?) } else { None }, + default_sample_duration: if (flags & 0x8) != 0 { Some(reader.read_u32::()?) } else { None }, + default_sample_size: if (flags & 0x10) != 0 { Some(reader.read_u32::()?) } else { None }, + default_sample_flags: if (flags & 0x20) != 0 { Some(reader.read_u32::()?) } else { None }, + duration_is_empty: flags & 0x10000 != 0, + }) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TrackFragmentRunSampleData { + pub sample_duration: Option, + pub sample_size: Option, + pub sample_flags: Option, + pub sample_composition_time_offset: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TrackFragmentRun { + pub version: u8, + pub flags: u32, + pub sample_count: u32, + pub data_offset: Option, + pub first_sample_flags: Option, + pub sample_data: Vec, +} + +impl AtomData for TrackFragmentRun { + const TYPE: FourCC = FourCC::TRUN; +} + +impl ReadData for TrackFragmentRun { + fn read(mut reader: R) -> Result { + let version = reader.read_u8()?; + let flags = reader.read_u24::()?; + let sample_count = reader.read_u32::()?; + let data_offset = if flags & 0x1 != 0 { Some(reader.read_i32::()?) } else { None }; + let first_sample_flags = if flags & 0x4 != 0 { Some(reader.read_u32::()?) } else { None }; + let sample_duration_present = flags & 0x100 != 0; + let sample_size_present = flags & 0x200 != 0; + let sample_flags_present = flags & 0x400 != 0; + let sample_composition_time_offsets_present = flags & 0x800 != 0; + let mut sample_data = vec![]; + for _ in 0..sample_count { + let sample_duration = if sample_duration_present { + Some(reader.read_u32::()?) + } else { + None + }; + let sample_size = if sample_size_present { Some(reader.read_u32::()?) } else { None }; + let sample_flags = if sample_flags_present { Some(reader.read_u32::()?) } else { None }; + let sample_composition_time_offset = if sample_composition_time_offsets_present { + Some(reader.read_u32::()?) + } else { + None + }; + sample_data.push(TrackFragmentRunSampleData { + sample_duration, + sample_size, + sample_flags, + sample_composition_time_offset, + }); + } + + Ok(Self { + version, + flags, + sample_count, + data_offset, + first_sample_flags, + sample_data, + }) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct TrackFragment { + pub track_fragment_header: TrackFragmentHeader, // tfhd + pub track_fragment_runs: Vec, // trun +} + +impl AtomData for TrackFragment { + const TYPE: FourCC = FourCC::TRAF; +} + +impl ReadData for TrackFragment { + fn read(mut reader: R) -> Result { + Ok(Self { + track_fragment_header: read_one(&mut reader)?.ok_or(Error::MalformedFile("missing movie track fragment header"))?, + track_fragment_runs: read_all(&mut reader)?, + }) + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MovieFragment { + pub fragment_header: FragmentHeader, // mfhd + pub track_fragments: Vec, // traf +} + +impl AtomData for MovieFragment { + const TYPE: FourCC = FourCC::MOOF; +} + +impl ReadData for MovieFragment { + fn read(mut reader: R) -> Result { + Ok(Self { + fragment_header: read_one(&mut reader)?.ok_or(Error::MalformedFile("missing movie fragment header"))?, + track_fragments: read_all(&mut reader)?, + }) + } +} diff --git a/qtff/src/testdata/fragmented.mp4 b/qtff/src/testdata/fragmented.mp4 new file mode 100644 index 00000000..384c4b5a Binary files /dev/null and b/qtff/src/testdata/fragmented.mp4 differ diff --git a/xcoder/xcoder-sys/build.rs b/xcoder/xcoder-sys/build.rs index a2ddc6ab..4ce2e93b 100644 --- a/xcoder/xcoder-sys/build.rs +++ b/xcoder/xcoder-sys/build.rs @@ -68,6 +68,9 @@ fn main() { .whitelist_function("ni_.+") .whitelist_type("ni_.+") .whitelist_var("NI_.+") + // bindgen 0.60.0 broke layout tests: https://github.com/rust-lang/rust-bindgen/issues/2218 + // 0.60.1 claims to have fixed the issue, but does not. + .layout_tests(false) .clang_arg(format!("-I{}", source_path.to_str().unwrap())) .generate() .expect("unable to generate bindings");