diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index b946deb..4f9de92 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -13,6 +13,9 @@ jobs: steps: - uses: actions/checkout@v3 + with: + submodules: true + lfs: true # Install Just to run CI scripts - uses: extractions/setup-just@v2 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..4e5ee9d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "FileFormatConformance"] + path = FileFormatConformance + url = https://github.com/MPEGGroup/FileFormatConformance.git diff --git a/FileFormatConformance b/FileFormatConformance new file mode 160000 index 0000000..767a5bc --- /dev/null +++ b/FileFormatConformance @@ -0,0 +1 @@ +Subproject commit 767a5bcb9fa4841d1b3bb0ca112a6e7d4c6bf067 diff --git a/src/any.rs b/src/any.rs index 295c137..45c8510 100644 --- a/src/any.rs +++ b/src/any.rs @@ -261,6 +261,8 @@ any! { Moov, Mvhd, Udta, + Cprt, + Kind, Skip, // Trak, // boxed to avoid large size differences between variants Tkhd, @@ -304,6 +306,7 @@ any! { Stts, Stsc, Stsz, + Stz2, Stss, Stco, Co64, @@ -316,7 +319,9 @@ any! { Saiz, Dinf, Dref, + Nmhd, Smhd, + Sthd, Vmhd, Edts, Elst, diff --git a/src/atom.rs b/src/atom.rs index 7c1b4b2..87632b6 100644 --- a/src/atom.rs +++ b/src/atom.rs @@ -210,6 +210,56 @@ macro_rules! nested { }) } + fn encode_body(&self, buf: &mut B) -> Result<()> { + $( self.[<$required:lower>].encode(buf)?; )* + $( self.[<$optional:lower>].encode(buf)?; )* + $( self.[<$multiple:lower>].iter().map(|x| x.encode(buf)).collect::>()?; )* + + Ok(()) + } + } + }; + (required: [$($required:ident),*$(,)?], optional: [$($optional:ident),*$(,)?], multiple: [$($multiple:ident),*$(,)?], post_parse: $some_fn:ident, ) => { + paste::paste! { + fn decode_body(buf: &mut B) -> Result { + $( let mut [<$required:lower>] = None;)* + $( let mut [<$optional:lower>] = None;)* + $( let mut [<$multiple:lower>] = Vec::new();)* + + while let Some(atom) = Any::decode_maybe(buf)? { + match atom { + $(Any::$required(atom) => { + if [<$required:lower>].is_some() { + return Err(Error::DuplicateBox($required::KIND)); + } + [<$required:lower>] = Some(atom); + },)* + $(Any::$optional(atom) => { + if [<$optional:lower>].is_some() { + return Err(Error::DuplicateBox($optional::KIND)); + } + [<$optional:lower>] = Some(atom); + },)* + $(Any::$multiple(atom) => { + [<$multiple:lower>].push(atom.into()); + },)* + Any::Skip(atom) => tracing::debug!(size = atom.zeroed.size, "skipping skip box"), + Any::Free(atom) => tracing::debug!(size = atom.zeroed.size, "skipping free box"), + unknown => Self::decode_unknown(&unknown)?, + } + } + + let result = Self { + $([<$required:lower>]: [<$required:lower>].ok_or(Error::MissingBox($required::KIND))? ,)* + $([<$optional:lower>],)* + $([<$multiple:lower>],)* + }; + + result.[<$some_fn>]()?; + + Ok(result) + } + fn encode_body(&self, buf: &mut B) -> Result<()> { $( self.[<$required:lower>].encode(buf)?; )* $( self.[<$optional:lower>].encode(buf)?; )* diff --git a/src/iso639.rs b/src/iso639.rs new file mode 100644 index 0000000..9b6e199 --- /dev/null +++ b/src/iso639.rs @@ -0,0 +1,36 @@ +pub fn language_string(language: u16) -> String { + let mut lang: [u16; 3] = [0; 3]; + + lang[0] = ((language >> 10) & 0x1F) + 0x60; + lang[1] = ((language >> 5) & 0x1F) + 0x60; + lang[2] = ((language) & 0x1F) + 0x60; + + // Decode utf-16 encoded bytes into a string. + String::from_utf16_lossy(&lang) +} + +pub fn language_code(language: &str) -> u16 { + let mut lang = language.encode_utf16(); + let mut code = (lang.next().unwrap_or(0) & 0x1F) << 10; + code += (lang.next().unwrap_or(0) & 0x1F) << 5; + code += lang.next().unwrap_or(0) & 0x1F; + code +} + +#[cfg(test)] +mod tests { + use super::*; + + fn test_language_code(lang: &str) { + let code = language_code(lang); + let lang2 = language_string(code); + assert_eq!(lang, lang2); + } + + #[test] + fn test_language_codes() { + test_language_code("und"); + test_language_code("eng"); + test_language_code("kor"); + } +} diff --git a/src/lib.rs b/src/lib.rs index 61d6dcf..9d51eea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -148,6 +148,7 @@ mod free; mod ftyp; mod header; mod io; +mod iso639; mod mdat; mod meta; mod mfra; @@ -169,6 +170,7 @@ pub use free::*; pub use ftyp::*; pub use header::*; pub use io::*; +pub(crate) use iso639::*; pub use mdat::*; pub use meta::*; pub use mfra::*; diff --git a/src/moov/mod.rs b/src/moov/mod.rs index 3d7137c..563298c 100644 --- a/src/moov/mod.rs +++ b/src/moov/mod.rs @@ -180,7 +180,6 @@ mod test { blue: 0 } }), - smhd: None, dinf: Dinf { dref: Dref { urls: vec![Url::default()] @@ -224,8 +223,10 @@ mod test { .into()], }, stco: Some(Stco::default()), + stsz: Some(Stsz::default()), ..Default::default() - } + }, + ..Default::default() } }, senc: None, diff --git a/src/moov/trak/mdia/mdhd.rs b/src/moov/trak/mdia/mdhd.rs index 846ea61..fdd78a4 100644 --- a/src/moov/trak/mdia/mdhd.rs +++ b/src/moov/trak/mdia/mdhd.rs @@ -65,42 +65,10 @@ impl AtomExt for Mdhd { } } -fn language_string(language: u16) -> String { - let mut lang: [u16; 3] = [0; 3]; - - lang[0] = ((language >> 10) & 0x1F) + 0x60; - lang[1] = ((language >> 5) & 0x1F) + 0x60; - lang[2] = ((language) & 0x1F) + 0x60; - - // Decode utf-16 encoded bytes into a string. - String::from_utf16_lossy(&lang) -} - -fn language_code(language: &str) -> u16 { - let mut lang = language.encode_utf16(); - let mut code = (lang.next().unwrap_or(0) & 0x1F) << 10; - code += (lang.next().unwrap_or(0) & 0x1F) << 5; - code += lang.next().unwrap_or(0) & 0x1F; - code -} - #[cfg(test)] mod tests { use super::*; - fn test_language_code(lang: &str) { - let code = language_code(lang); - let lang2 = language_string(code); - assert_eq!(lang, lang2); - } - - #[test] - fn test_language_codes() { - test_language_code("und"); - test_language_code("eng"); - test_language_code("kor"); - } - #[test] fn test_mdhd32() { let expected = Mdhd { diff --git a/src/moov/trak/mdia/minf/mod.rs b/src/moov/trak/mdia/minf/mod.rs index c57ef11..e3b3f95 100644 --- a/src/moov/trak/mdia/minf/mod.rs +++ b/src/moov/trak/mdia/minf/mod.rs @@ -1,11 +1,15 @@ mod dinf; +mod nmhd; mod smhd; mod stbl; +mod sthd; mod vmhd; pub use dinf::*; +pub use nmhd::*; pub use smhd::*; pub use stbl::*; +pub use sthd::*; pub use vmhd::*; use crate::*; @@ -15,6 +19,8 @@ use crate::*; pub struct Minf { pub vmhd: Option, pub smhd: Option, + pub nmhd: Option, + pub sthd: Option, pub dinf: Dinf, pub stbl: Stbl, } @@ -24,7 +30,7 @@ impl Atom for Minf { nested! { required: [ Dinf, Stbl ], - optional: [ Vmhd, Smhd ], + optional: [ Vmhd, Smhd, Nmhd, Sthd ], multiple: [], } } diff --git a/src/moov/trak/mdia/minf/nmhd.rs b/src/moov/trak/mdia/minf/nmhd.rs new file mode 100644 index 0000000..2786659 --- /dev/null +++ b/src/moov/trak/mdia/minf/nmhd.rs @@ -0,0 +1,39 @@ +use crate::*; + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Nmhd {} + +impl AtomExt for Nmhd { + type Ext = (); + + const KIND_EXT: FourCC = FourCC::new(b"nmhd"); + + fn decode_body_ext(_buf: &mut B, _ext: ()) -> Result { + Ok(Nmhd {}) + } + + fn encode_body_ext(&self, _buf: &mut B) -> Result<()> { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_nmhd() { + let nmhd = Nmhd {}; + let mut buf = Vec::new(); + nmhd.encode(&mut buf).unwrap(); + assert_eq!( + buf, + vec![0x00, 0x00, 0x00, 0x0c, 0x6e, 0x6d, 0x68, 0x64, 0x00, 0x00, 0x00, 0x00] + ); + + let mut buf = buf.as_ref(); + let decoded = Nmhd::decode(&mut buf).unwrap(); + assert_eq!(decoded, nmhd); + } +} diff --git a/src/moov/trak/mdia/minf/stbl/mod.rs b/src/moov/trak/mdia/minf/stbl/mod.rs index 6c02aaf..da0dac1 100644 --- a/src/moov/trak/mdia/minf/stbl/mod.rs +++ b/src/moov/trak/mdia/minf/stbl/mod.rs @@ -10,6 +10,7 @@ mod stsd; mod stss; mod stsz; mod stts; +mod stz2; mod subs; pub use co64::*; @@ -24,6 +25,7 @@ pub use stsd::*; pub use stss::*; pub use stsz::*; pub use stts::*; +pub use stz2::*; pub use subs::*; use crate::*; @@ -36,7 +38,8 @@ pub struct Stbl { pub ctts: Option, pub stss: Option, pub stsc: Stsc, - pub stsz: Stsz, + pub stsz: Option, + pub stz2: Option, pub stco: Option, pub co64: Option, pub sbgp: Vec, @@ -47,12 +50,28 @@ pub struct Stbl { pub cslg: Option, } +impl Stbl { + fn do_validation(&self) -> Result<()> { + if self.stsz.is_none() && self.stz2.is_none() { + return Err(Error::MissingContent( + "one of stsz or stz2 is required in stbl box", + )); + } + if self.stsz.is_some() && self.stz2.is_some() { + // TODO: some kind of better error + return Err(Error::UnexpectedBox(Stz2::KIND)); + } + Ok(()) + } +} + impl Atom for Stbl { const KIND: FourCC = FourCC::new(b"stbl"); nested! { - required: [ Stsd, Stts, Stsc, Stsz ], - optional: [ Ctts, Stss, Stco, Co64, Cslg ], + required: [ Stsd, Stts, Stsc ], + optional: [ Ctts, Stss, Stco, Co64, Cslg, Stsz, Stz2 ], multiple: [ Sbgp, Sgpd, Subs, Saiz, Saio ], + post_parse: do_validation, } } diff --git a/src/moov/trak/mdia/minf/stbl/sgpd.rs b/src/moov/trak/mdia/minf/stbl/sgpd.rs index 194043d..04b0982 100644 --- a/src/moov/trak/mdia/minf/stbl/sgpd.rs +++ b/src/moov/trak/mdia/minf/stbl/sgpd.rs @@ -129,19 +129,49 @@ impl AtomExt for Sgpd { } } +const REFS_4CC: FourCC = FourCC::new(b"refs"); + #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum AnySampleGroupEntry { + DirectReferenceSampleList(u32, Vec), UnknownGroupingType(FourCC, Vec), } impl AnySampleGroupEntry { fn decode(grouping_type: FourCC, buf: &mut B) -> Result { - Ok(Self::UnknownGroupingType(grouping_type, Vec::decode(buf)?)) + match grouping_type { + REFS_4CC => { + let sample_id = u32::decode(buf)?; + let num_direct_reference_samples = u8::decode(buf)? as usize; + let mut direct_reference_samples = + Vec::with_capacity(std::cmp::min(num_direct_reference_samples, 16)); + for _ in 0..num_direct_reference_samples { + direct_reference_samples.push(u32::decode(buf)?); + } + Ok(Self::DirectReferenceSampleList( + sample_id, + direct_reference_samples, + )) + } + _ => Ok(Self::UnknownGroupingType(grouping_type, Vec::decode(buf)?)), + } } fn encode(&self, buf: &mut B) -> Result<()> { match self { + Self::DirectReferenceSampleList(sample_id, direct_reference_samples) => { + sample_id.encode(buf)?; + let num_direct_reference_samples: u8 = direct_reference_samples + .len() + .try_into() + .map_err(|_| Error::TooLarge(REFS_4CC))?; + num_direct_reference_samples.encode(buf)?; + for direct_reference_sample in direct_reference_samples { + direct_reference_sample.encode(buf)?; + } + Ok(()) + } Self::UnknownGroupingType(_, bytes) => bytes.encode(buf), } } @@ -206,4 +236,44 @@ mod tests { sgpd.encode(&mut buf).expect("encode should be successful"); assert_eq!(SIMPLE_SGPD, &buf); } + + // From the MPEG File Format Conformance suite, heif/C041.heic + const SGPD_ENCODED_C041: &[u8] = &[ + 0x00, 0x00, 0x00, 0x2e, 0x73, 0x67, 0x70, 0x64, 0x01, 0x00, 0x00, 0x00, 0x72, 0x65, 0x66, + 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, + 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, + 0x00, + ]; + + #[test] + fn sgpd_c041_decode() { + let mut buf = Cursor::new(SGPD_ENCODED_C041); + let sgpd = Sgpd::decode(&mut buf).expect("sgpd should decode successfully"); + assert_eq!( + sgpd, + Sgpd { + grouping_type: FourCC::from(b"refs"), + default_length: Some(0), + default_group_description_index: None, + static_group_description: false, + static_mapping: false, + essential: false, + entries: vec![ + SgpdEntry { + description_length: Some(9), + entry: AnySampleGroupEntry::DirectReferenceSampleList(0, vec![1]) + }, + SgpdEntry { + description_length: Some(5), + entry: AnySampleGroupEntry::DirectReferenceSampleList(1, vec![]) + } + ], + } + ); + + let mut encoded = Vec::new(); + sgpd.encode(&mut encoded).unwrap(); + + assert_eq!(encoded, SGPD_ENCODED_C041); + } } diff --git a/src/moov/trak/mdia/minf/stbl/stsd/hevc/hvc1.rs b/src/moov/trak/mdia/minf/stbl/stsd/hevc/hvc1.rs index ebd9648..9991344 100644 --- a/src/moov/trak/mdia/minf/stbl/stsd/hevc/hvc1.rs +++ b/src/moov/trak/mdia/minf/stbl/stsd/hevc/hvc1.rs @@ -11,6 +11,7 @@ pub struct Hvc1 { pub pasp: Option, pub taic: Option, pub fiel: Option, + pub ccst: Option, } impl Atom for Hvc1 { @@ -25,6 +26,7 @@ impl Atom for Hvc1 { let mut pasp = None; let mut taic = None; let mut fiel = None; + let mut ccst = None; while let Some(atom) = Any::decode_maybe(buf)? { match atom { Any::Hvcc(atom) => hvcc = atom.into(), @@ -33,6 +35,7 @@ impl Atom for Hvc1 { Any::Pasp(atom) => pasp = atom.into(), Any::Taic(atom) => taic = atom.into(), Any::Fiel(atom) => fiel = atom.into(), + Any::Ccst(atom) => ccst = atom.into(), unknown => Self::decode_unknown(&unknown)?, } } @@ -45,6 +48,7 @@ impl Atom for Hvc1 { pasp, taic, fiel, + ccst, }) } @@ -56,6 +60,109 @@ impl Atom for Hvc1 { self.pasp.encode(buf)?; self.taic.encode(buf)?; self.fiel.encode(buf)?; + self.ccst.encode(buf)?; Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + + // From MPEG File Format Conformance, heif/C001.heif + const ENCODED_HVC1_HEIF: &[u8] = &[ + 0x00, 0x00, 0x00, 0xd2, 0x68, 0x76, 0x63, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x05, 0x00, 0x02, 0xd0, 0x00, 0x48, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x1f, 0x48, 0x45, 0x56, 0x43, 0x20, 0x43, 0x6f, 0x64, 0x69, + 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x6c, + 0x68, 0x76, 0x63, 0x43, 0x01, 0x01, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x78, 0xf0, 0x00, 0xfc, 0xfd, 0xf8, 0xf8, 0x00, 0x00, 0x0f, 0x03, 0xa0, 0x00, 0x01, + 0x00, 0x18, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x00, + 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x78, 0xf0, 0x24, 0xa1, 0x00, 0x01, 0x00, + 0x1f, 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, + 0x00, 0x00, 0x03, 0x00, 0x78, 0xa0, 0x02, 0x80, 0x80, 0x2d, 0x1f, 0xe5, 0xf9, 0x24, 0x6d, + 0x9e, 0xd9, 0xa2, 0x00, 0x01, 0x00, 0x07, 0x44, 0x01, 0xc1, 0x90, 0x95, 0x81, 0x12, 0x00, + 0x00, 0x00, 0x10, 0x63, 0x63, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, + ]; + + #[test] + fn test_hvc1_with_ccst() { + let mut buf = std::io::Cursor::new(ENCODED_HVC1_HEIF); + + let hvc1 = Hvc1::decode(&mut buf).expect("failed to decode hvc1"); + + assert_eq!( + hvc1, + Hvc1 { + visual: Visual { + data_reference_index: 1, + width: 1280, + height: 720, + horizresolution: 72.into(), + vertresolution: 72.into(), + frame_count: 1, + compressor: "\x1fHEVC Coding".into(), + depth: 24 + }, + hvcc: Hvcc { + configuration_version: 1, + general_profile_space: 0, + general_tier_flag: false, + general_profile_idc: 1, + general_profile_compatibility_flags: [96, 0, 0, 0], + general_constraint_indicator_flags: [0, 0, 0, 0, 0, 0], + general_level_idc: 120, + min_spatial_segmentation_idc: 0, + parallelism_type: 0, + chroma_format_idc: 1, + bit_depth_luma_minus8: 0, + bit_depth_chroma_minus8: 0, + avg_frame_rate: 0, + constant_frame_rate: 0, + num_temporal_layers: 1, + temporal_id_nested: true, + length_size_minus_one: 3, + arrays: vec![ + HvcCArray { + completeness: true, + nal_unit_type: 32, + nalus: vec![vec![ + 64, 1, 12, 1, 255, 255, 1, 96, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, 0, 3, + 0, 120, 240, 36 + ]] + }, + HvcCArray { + completeness: true, + nal_unit_type: 33, + nalus: vec![vec![ + 66, 1, 1, 1, 96, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, 0, 3, 0, 120, 160, + 2, 128, 128, 45, 31, 229, 249, 36, 109, 158, 217 + ]] + }, + HvcCArray { + completeness: true, + nal_unit_type: 34, + nalus: vec![vec![68, 1, 193, 144, 149, 129, 18]] + } + ] + }, + btrt: None, + colr: None, + pasp: None, + taic: None, + fiel: None, + ccst: Some(Ccst { + all_ref_pics_intra: true, + intra_pred_used: false, + max_ref_per_pic: 0 + }) + } + ); + + let mut encoded = Vec::new(); + hvc1.encode(&mut encoded).expect("failed to encode hvc1"); + assert_eq!(encoded, ENCODED_HVC1_HEIF); + } +} diff --git a/src/moov/trak/mdia/minf/stbl/stz2.rs b/src/moov/trak/mdia/minf/stbl/stz2.rs new file mode 100644 index 0000000..0b04262 --- /dev/null +++ b/src/moov/trak/mdia/minf/stbl/stz2.rs @@ -0,0 +1,209 @@ +use num::Integer; + +use crate::*; + +/// Compact Sample Size Box (stz2) +/// +/// Lists the size of each sample in the track. +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Stz2 { + pub entry_sizes: Vec, +} + +impl AtomExt for Stz2 { + type Ext = (); + + const KIND_EXT: FourCC = FourCC::new(b"stz2"); + + fn decode_body_ext(buf: &mut B, _ext: ()) -> Result { + buf.advance(3); // reserved = 0 + let field_size = u8::decode(buf)?; + let sample_count = u32::decode(buf)?; + let mut entry_sizes = Vec::::with_capacity(std::cmp::min(128, sample_count as usize)); + + match field_size { + 4 => { + // This is to handle the case where there are an odd number entry_sizes, and + // we don't want to add the last one. + let mut remaining = 0u16; + for i in 0..sample_count { + if i.is_even() { + let entry_pair = u8::decode(buf)? as u16; + entry_sizes.push(entry_pair >> 4); + remaining = entry_pair & 0x0f; + } else { + entry_sizes.push(remaining); + } + } + } + 8 => { + for _ in 0..sample_count { + entry_sizes.push(u8::decode(buf)? as u16); + } + } + 16 => { + for _ in 0..sample_count { + entry_sizes.push(u16::decode(buf)?); + } + } + _ => { + return Err(Error::InvalidSize); + } + } + + Ok(Stz2 { entry_sizes }) + } + + fn encode_body_ext(&self, buf: &mut B) -> Result<()> { + // field_size is u8, but treating it as u32 is a convenient way + // to handle the 3 reserved bytes. + let mut field_size = 4u32; + for entry_size in &self.entry_sizes { + if *entry_size >= 16 { + // need more than 4 bits + field_size = 8u32; + } + if *entry_size > u8::MAX.into() { + field_size = 16u32; + break; + } + } + field_size.encode(buf)?; + let sample_count: u32 = self + .entry_sizes + .len() + .try_into() + .map_err(|_| Error::TooLarge(Self::KIND))?; + sample_count.encode(buf)?; + match field_size { + 16u32 => { + for entry_size in &self.entry_sizes { + entry_size.encode(buf)?; + } + } + 8u32 => { + for entry_size in &self.entry_sizes { + let entry_size_u8: u8 = *entry_size as u8; + entry_size_u8.encode(buf)?; + } + } + 4u32 => { + let mut packed_entries = + Vec::::with_capacity(self.entry_sizes.len().div_ceil(2)); + for i in 0..self.entry_sizes.len() { + let sample_size = self + .entry_sizes + .get(i) + .expect("there should be a value given we are iterating over the length"); + if (sample_size & 0b1111) != *sample_size { + // There is no way this should be able to happen. + return Err(Error::InvalidSize); + } + let sample_size_u4 = (sample_size & 0b1111) as u8; + if i.is_even() { + packed_entries.push(sample_size_u4 << 4); + } else { + let i = packed_entries.get_mut(i / 2).expect( + "there should be a value given we are iterating over the length", + ); + *i |= sample_size_u4; + } + } + for packed_entry in packed_entries { + packed_entry.encode(buf)?; + } + } + _ => { + // There is no way this should be able to happen - only ever assign those 3 values, and those are the only valid values. + return Err(Error::InvalidSize); + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_stz2_8() { + let expected = Stz2 { + entry_sizes: vec![15, 16, 3], + }; + let mut buf = Vec::new(); + expected.encode(&mut buf).unwrap(); + + let mut buf = buf.as_ref(); + assert_eq!( + buf, + vec![ + 0x00, 0x00, 0x00, 23, b's', b't', b'z', b'2', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x08, 0x00, 0x00, 0x00, 0x03, 0x0f, 0x10, 0x03 + ] + ); + let decoded = Stz2::decode(&mut buf).unwrap(); + assert_eq!(decoded, expected); + } + + #[test] + fn test_stz2_4() { + let expected = Stz2 { + entry_sizes: vec![15, 3, 6], + }; + let mut buf = Vec::new(); + expected.encode(&mut buf).unwrap(); + + let mut buf = buf.as_ref(); + assert_eq!( + buf, + vec![ + 0x00, 0x00, 0x00, 22, b's', b't', b'z', b'2', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0xf3, 0x60 + ] + ); + let decoded = Stz2::decode(&mut buf).unwrap(); + assert_eq!(decoded, expected); + } + + #[test] + fn test_stz2_4_even() { + let expected = Stz2 { + entry_sizes: vec![15, 3, 6, 8], + }; + let mut buf = Vec::new(); + expected.encode(&mut buf).unwrap(); + + let mut buf = buf.as_ref(); + assert_eq!( + buf, + vec![ + 0x00, 0x00, 0x00, 22, b's', b't', b'z', b'2', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0xf3, 0x68 + ] + ); + let decoded = Stz2::decode(&mut buf).unwrap(); + assert_eq!(decoded, expected); + } + + #[test] + fn test_stz2_16() { + let expected = Stz2 { + entry_sizes: vec![255, 256, 65535], + }; + let mut buf = Vec::new(); + expected.encode(&mut buf).unwrap(); + + let mut buf = buf.as_ref(); + assert_eq!( + buf, + vec![ + 0x00, 0x00, 0x00, 26, b's', b't', b'z', b'2', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x10, 0x00, 0x00, 0x00, 0x03, 0x00, 0xff, 0x01, 0x00, 0xff, 0xff, + ] + ); + let decoded = Stz2::decode(&mut buf).unwrap(); + assert_eq!(decoded, expected); + } +} diff --git a/src/moov/trak/mdia/minf/sthd.rs b/src/moov/trak/mdia/minf/sthd.rs new file mode 100644 index 0000000..4e54495 --- /dev/null +++ b/src/moov/trak/mdia/minf/sthd.rs @@ -0,0 +1,39 @@ +use crate::*; + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Sthd {} + +impl AtomExt for Sthd { + type Ext = (); + + const KIND_EXT: FourCC = FourCC::new(b"sthd"); + + fn decode_body_ext(_buf: &mut B, _ext: ()) -> Result { + Ok(Sthd {}) + } + + fn encode_body_ext(&self, _buf: &mut B) -> Result<()> { + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_sthd() { + let sthd = Sthd {}; + let mut buf = Vec::new(); + sthd.encode(&mut buf).unwrap(); + assert_eq!( + buf, + vec![0x00, 0x00, 0x00, 0x0c, b's', b't', b'h', b'd', 0x00, 0x00, 0x00, 0x00] + ); + + let mut buf = buf.as_ref(); + let decoded = Sthd::decode(&mut buf).unwrap(); + assert_eq!(decoded, sthd); + } +} diff --git a/src/moov/udta/cprt.rs b/src/moov/udta/cprt.rs new file mode 100644 index 0000000..9d46cdf --- /dev/null +++ b/src/moov/udta/cprt.rs @@ -0,0 +1,31 @@ +use crate::*; + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Cprt { + pub language: String, + pub notice: String, +} + +impl AtomExt for Cprt { + type Ext = (); + + const KIND_EXT: FourCC = FourCC::new(b"cprt"); + + fn decode_body_ext(buf: &mut B, _ext: ()) -> Result { + let language_code = u16::decode(buf)?; + let language = language_string(language_code); + + Ok(Cprt { + language, + notice: String::decode(buf)?, + }) + } + + fn encode_body_ext(&self, buf: &mut B) -> Result<()> { + let language_code = language_code(&self.language); + (language_code).encode(buf)?; + self.notice.as_str().encode(buf)?; + Ok(()) + } +} diff --git a/src/moov/udta/kind.rs b/src/moov/udta/kind.rs new file mode 100644 index 0000000..fb1c65d --- /dev/null +++ b/src/moov/udta/kind.rs @@ -0,0 +1,27 @@ +use crate::*; + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Kind { + pub scheme_uri: String, + pub value: String, +} + +impl AtomExt for Kind { + type Ext = (); + + const KIND_EXT: FourCC = FourCC::new(b"kind"); + + fn decode_body_ext(buf: &mut B, _ext: ()) -> Result { + Ok(Kind { + scheme_uri: String::decode(buf)?, + value: String::decode(buf)?, + }) + } + + fn encode_body_ext(&self, buf: &mut B) -> Result<()> { + self.scheme_uri.as_str().encode(buf)?; + self.value.as_str().encode(buf)?; + Ok(()) + } +} diff --git a/src/moov/udta/mod.rs b/src/moov/udta/mod.rs index b4caf99..65b3f0a 100644 --- a/src/moov/udta/mod.rs +++ b/src/moov/udta/mod.rs @@ -1,5 +1,9 @@ +mod cprt; +mod kind; mod skip; +pub use cprt::*; +pub use kind::*; pub use skip::*; use crate::*; @@ -7,6 +11,8 @@ use crate::*; #[derive(Debug, Clone, PartialEq, Eq, Default)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Udta { + pub cprt: Option, + pub kind: Option, pub meta: Option, } @@ -15,7 +21,7 @@ impl Atom for Udta { nested! { required: [ ], - optional: [ Meta ], + optional: [ Cprt, Meta, Kind], multiple: [ ], } } @@ -26,7 +32,11 @@ mod tests { #[test] fn test_udta_empty() { - let expected = Udta { meta: None }; + let expected = Udta { + cprt: None, + meta: None, + kind: None, + }; let mut buf = Vec::new(); expected.encode(&mut buf).unwrap(); @@ -39,6 +49,10 @@ mod tests { #[test] fn test_udta() { let expected = Udta { + cprt: Some(Cprt { + language: "und".into(), + notice: "MIT or Apache".into(), + }), meta: Some(Meta { hdlr: Hdlr { handler: FourCC::new(b"fake"), @@ -46,6 +60,10 @@ mod tests { }, items: vec![], }), + kind: Some(Kind { + scheme_uri: "http://www.w3.org/TR/html5/".into(), + value: "".into(), + }), }; let mut buf = Vec::new(); @@ -55,4 +73,69 @@ mod tests { let output = Udta::decode(&mut buf).unwrap(); assert_eq!(output, expected); } + + // From MPEG File Format Conformance, isobmff/02_dref_edts_img.mp4 + const ENCODED_UDTA_WITH_CPRT: &[u8] = &[ + 0x00, 0x00, 0x00, 0x70, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00, 0x68, 0x63, 0x70, 0x72, + 0x74, 0x00, 0x00, 0x00, 0x00, 0x55, 0xc4, 0x45, 0x4e, 0x53, 0x54, 0x20, 0x49, 0x73, 0x6f, + 0x4d, 0x65, 0x64, 0x69, 0x61, 0x20, 0x43, 0x6f, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, + 0x63, 0x65, 0x20, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x20, 0x2d, 0x20, 0x45, 0x4e, 0x53, 0x54, + 0x20, 0x28, 0x63, 0x29, 0x20, 0x32, 0x30, 0x30, 0x36, 0x20, 0x2d, 0x20, 0x52, 0x69, 0x67, + 0x68, 0x74, 0x73, 0x20, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x64, 0x20, 0x66, 0x6f, + 0x72, 0x20, 0x49, 0x53, 0x4f, 0x20, 0x43, 0x6f, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x6e, + 0x63, 0x65, 0x20, 0x75, 0x73, 0x65, 0x00, + ]; + + #[test] + fn test_udta_cprt() { + let mut buf = std::io::Cursor::new(ENCODED_UDTA_WITH_CPRT); + + let udta = Udta::decode(&mut buf).expect("failed to decode udta"); + + assert_eq!( + udta, + Udta { + cprt: Some(Cprt { language: "und".into(), notice: "ENST IsoMedia Conformance Files - ENST (c) 2006 - Rights released for ISO Conformance use".into() }), + meta: None, + kind: None, + } + ); + + let mut buf = Vec::new(); + udta.encode(&mut buf).unwrap(); + + assert_eq!(buf, ENCODED_UDTA_WITH_CPRT); + } + + // From MPEG File Format Conformance, nalu/hevc/hev1_clg1_header.mp4 + const ENCODED_UDTA_WITH_KIND: &[u8] = &[ + 0x00, 0x00, 0x00, 0x31, 0x75, 0x64, 0x74, 0x61, 0x00, 0x00, 0x00, 0x29, 0x6b, 0x69, 0x6e, + 0x64, 0x00, 0x00, 0x00, 0x00, 0x75, 0x72, 0x6e, 0x3a, 0x6d, 0x70, 0x65, 0x67, 0x3a, 0x64, + 0x61, 0x73, 0x68, 0x3a, 0x72, 0x6f, 0x6c, 0x65, 0x3a, 0x32, 0x30, 0x31, 0x31, 0x00, 0x6d, + 0x61, 0x69, 0x6e, 0x00, + ]; + + #[test] + fn test_udta_kind() { + let mut buf = std::io::Cursor::new(ENCODED_UDTA_WITH_KIND); + + let udta = Udta::decode(&mut buf).expect("failed to decode udta"); + + assert_eq!( + udta, + Udta { + cprt: None, + meta: None, + kind: Some(Kind { + scheme_uri: "urn:mpeg:dash:role:2011".into(), + value: "main".into() + }) + } + ); + + let mut buf = Vec::new(); + udta.encode(&mut buf).unwrap(); + + assert_eq!(buf, ENCODED_UDTA_WITH_KIND); + } } diff --git a/src/test/av1.rs b/src/test/av1.rs index 161776e..11afbc1 100644 --- a/src/test/av1.rs +++ b/src/test/av1.rs @@ -103,7 +103,6 @@ fn av1() { blue: 0 } }), - smhd: None, dinf: Dinf { dref: Dref { urls: vec![Url { @@ -154,7 +153,8 @@ fn av1() { ctts: None, stss: None, stsc: Stsc::default(), - stsz: Stsz::default(), + stsz: Some(Stsz::default()), + stz2: None, stco: Some(Stco::default()), co64: None, sbgp: vec![], @@ -163,7 +163,8 @@ fn av1() { saio: vec![], saiz: vec![], cslg: None, - } + }, + ..Default::default() } }, senc: None, @@ -188,6 +189,7 @@ fn av1() { } .into(),], }), + ..Default::default() }) } ); diff --git a/src/test/bbb.rs b/src/test/bbb.rs index 0deea90..f213b45 100644 --- a/src/test/bbb.rs +++ b/src/test/bbb.rs @@ -62,7 +62,6 @@ fn bbb() { name: "(C) 2007 Google Inc. v08.13.2007.".into(), }, minf: Minf { - smhd: None, vmhd: Vmhd { ..Default::default() } @@ -114,12 +113,11 @@ fn bbb() { stsc: Stsc { ..Default::default() }, - stsz: Stsz { - ..Default::default() - }, + stsz: Some(Stsz::default()), stco: Some(Stco { ..Default::default() }), ..Default::default() }, + ..Default::default() }, }, ..Default::default() @@ -191,9 +189,7 @@ fn bbb() { stsc: Stsc { ..Default::default() }, - stsz: Stsz { - ..Default::default() - }, + stsz: Some(Stsz::default()), stco: Some(Stco { ..Default::default() }), ..Default::default() }, @@ -207,6 +203,7 @@ fn bbb() { hdlr: Hdlr{ handler: FourCC::new(b"mdir"), name: "".into() }, items: vec![Ilst { name: None, year: None, covr: None, desc: None, ctoo: Some(Tool { country_indicator: 0, language_indicator: 0, text: "Lavf61.1.100".into()}) }.into(),], }), + ..Default::default() }), ..Default::default() diff --git a/src/test/esds.rs b/src/test/esds.rs index c5c9ec2..cc2ff58 100644 --- a/src/test/esds.rs +++ b/src/test/esds.rs @@ -62,7 +62,6 @@ fn esds() { name: "(C) 2007 Google Inc. v08.13.2007.".into(), }, minf: Minf { - smhd: None, vmhd: Vmhd { ..Default::default() } @@ -114,12 +113,11 @@ fn esds() { stsc: Stsc { ..Default::default() }, - stsz: Stsz { - ..Default::default() - }, + stsz: Some(Stsz::default()), stco: Some(Stco { ..Default::default() }), ..Default::default() }, + ..Default::default() }, }, ..Default::default() @@ -191,9 +189,7 @@ fn esds() { stsc: Stsc { ..Default::default() }, - stsz: Stsz { - ..Default::default() - }, + stsz: Some(Stsz::default()), stco: Some(Stco { ..Default::default() }), ..Default::default() }, @@ -202,10 +198,7 @@ fn esds() { }, ..Default::default() }], - udta: Some(Udta { - meta: None, - }), - + udta: Some(Udta::default()), ..Default::default() }, ); diff --git a/src/test/flac.rs b/src/test/flac.rs index 15b113c..f7fdb5f 100644 --- a/src/test/flac.rs +++ b/src/test/flac.rs @@ -91,7 +91,6 @@ fn flac() { name: "SoundHandler".into(), }, minf: Minf { - vmhd: None, smhd: Some(Smhd { balance: 0.into() }), dinf: Dinf { dref: Dref { @@ -142,7 +141,8 @@ fn flac() { ctts: None, stss: None, stsc: Stsc { entries: vec![] }, - stsz: Stsz::default(), + stsz: Some(Stsz::default()), + stz2: None, stco: Some(Stco { entries: [].into() }), co64: None, sbgp: vec![], @@ -151,7 +151,8 @@ fn flac() { saio: vec![], saiz: vec![], cslg: None, - } + }, + ..Default::default() } }, senc: None, diff --git a/src/test/h264.rs b/src/test/h264.rs index 8e74366..44556bf 100644 --- a/src/test/h264.rs +++ b/src/test/h264.rs @@ -108,7 +108,6 @@ fn avcc_ext() { blue: 0, }, }), - smhd: None, dinf: Dinf { dref: Dref { urls: vec![Url::default()], @@ -169,7 +168,8 @@ fn avcc_ext() { ctts: None, stss: None, stsc: Stsc::default(), - stsz: Stsz::default(), + stsz: Some(Stsz::default()), + stz2: None, stco: Some(Stco::default()), co64: None, sbgp: vec![], @@ -179,6 +179,7 @@ fn avcc_ext() { saiz: vec![], cslg: None, }, + ..Default::default() }, }, senc: None, @@ -223,7 +224,6 @@ fn avcc_ext() { name: "L-SMASH Audio Handler".into(), }, minf: Minf { - vmhd: None, smhd: Some(Smhd::default()), dinf: Dinf { dref: Dref { @@ -271,7 +271,8 @@ fn avcc_ext() { ctts: None, stss: None, stsc: Stsc::default(), - stsz: Stsz::default(), + stsz: Some(Stsz::default()), + stz2: None, stco: Some(Stco::default()), co64: None, sbgp: vec![], @@ -281,13 +282,14 @@ fn avcc_ext() { saiz: vec![], cslg: None, }, + ..Default::default() }, }, senc: None, udta: None, }, ], - udta: Some(Udta { meta: None }), + udta: Some(Udta::default()), }; assert_eq!(moov, expected, "different decoded result"); diff --git a/src/test/hevc.rs b/src/test/hevc.rs index 1078922..a248a24 100644 --- a/src/test/hevc.rs +++ b/src/test/hevc.rs @@ -98,7 +98,6 @@ fn hevc() { blue: 0 } }), - smhd: None, dinf: Dinf { dref: Dref { urls: vec![Url { @@ -379,8 +378,10 @@ fn hevc() { .into()], }, stco: Some(Stco { entries: vec![] }), + stsz: Some(Stsz::default()), ..Default::default() - } + }, + ..Default::default() } }, senc: None, @@ -405,6 +406,7 @@ fn hevc() { } .into(),], }), + ..Default::default() }) } ); diff --git a/src/test/mod.rs b/src/test/mod.rs index 70b5800..9b80f55 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -5,5 +5,6 @@ mod flac; mod h264; mod hevc; mod image; +mod mpeg_file_format_conformance; mod uncompressed; mod vp9; diff --git a/src/test/mpeg_file_format_conformance.rs b/src/test/mpeg_file_format_conformance.rs new file mode 100644 index 0000000..67fee16 --- /dev/null +++ b/src/test/mpeg_file_format_conformance.rs @@ -0,0 +1,167 @@ +use std::path::{Path, PathBuf}; + +use crate::{Any, ReadFrom}; + +#[test] +fn test_published() { + let expected_fails: Vec = vec![ + + "FileFormatConformance/data/file_features/published/3gp/pdin_example.3gp".into(), + "FileFormatConformance/data/file_features/published/3gp/female_amr67DTX_hinted.3gp".into(), + "FileFormatConformance/data/file_features/published/3gp/female_amr67_hinted.3gp".into(), + "FileFormatConformance/data/file_features/published/3gp/male_amr122.3gp".into(), + "FileFormatConformance/data/file_features/published/3gp/male_amr122DTX.3gp".into(), + "FileFormatConformance/data/file_features/published/3gp/rs_example_r1.3gp".into(), + "FileFormatConformance/data/file_features/published/mpeg-audio-conformance/ac01.mp4".into(), + "FileFormatConformance/data/file_features/published/mpeg-audio-conformance/sls2100_aot02_048_16.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/01_simple.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/02_dref_edts_img.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/03_hinted.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/06_bifs.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/07_bifs_sprite.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/timed-metadata.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/a5-foreman-AVC.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/a6_tone_multifile.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/a7-tone-oddities.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/04_bifs_video.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/10_fragments.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/12_metas_v2.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/13_long.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/FX-VY-9436R.3_qhd-variant.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/FX-VY-9436R.3_qhd.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/05_bifs_video_protected_v2.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/20_stxt.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/a4-tone-fragmented.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/21_segment.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/sg-tl-st.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/restricted.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/17_negative_ctso.mp4".into(), + "FileFormatConformance/data/file_features/published/green/meta_2500000bps_0.mp4m".into(), + "FileFormatConformance/data/file_features/published/green/video_2500000bps_0.mp4".into(), + "FileFormatConformance/data/file_features/published/heif/C032.heic".into(), + "FileFormatConformance/data/file_features/published/isobmff/compact-no-code-fec-1.iso3" + .into(), + "FileFormatConformance/data/file_features/published/isobmff/compact-no-code-fec-2.iso3" + .into(), + "FileFormatConformance/data/file_features/published/isobmff/mbms-fec.iso3".into(), + "FileFormatConformance/data/file_features/published/isobmff/fragment_random_access-2.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/fragment-random-access-1+AF8-rev1.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/a9-aac-samplegroups-edit.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/a1-foreman-QCIF.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/a2-foreman-QCIF-hinted.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/a10-foreman_QCIF-raw.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/a3-tone-protected.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/a3b-tone-deprot.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/f1.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/f2.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/08_bifs_carousel_v2.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/16_vtt.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/19_ttml.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/18_pssh_v2.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/a8-foreman_QCIF_edit.mp4".into(), + "FileFormatConformance/data/file_features/published/isobmff/rtp_rtcp_reception_hint_tracks_v2.mp4".into(), + "FileFormatConformance/data/file_features/published/maf/vsaf/1.mp4".into(), + "FileFormatConformance/data/file_features/published/uvvu/Solekai002_1280_23_1x1_v7clear.uvvu".into(), + "FileFormatConformance/data/file_features/published/uvvu/Solekai007_1920_29_1x1_v7clear.uvu".into(), + "FileFormatConformance/data/file_features/published/nalu/hevc/hevc_tiles_single_track_trif_full_picture.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/hevc/alst_hvc1.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/hevc/hevc_tiles_multiple_tracks.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/hevc/hevc_tiles_single_track_nalm_rle.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/hevc/trgr_hvc1.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/hevc/hvc2_extractors.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/hevc/aggr_hvc1.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/hevc/hevc_hvc1_hvc2_extractors.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/hevc/hevc_hvc1_hvc2_implicit.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/hevc/hevc_hev1_hev2_extractors.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/hevc/hevc_hev1_hev2_implicit.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/hevc/hevc_tiles_single_track_nalm.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/hevc/subs_tile_hvc1.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/hevc/subs_slice_hvc1.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/hevc/hevc_tiles_single_track_nalm_all_intra.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/hevc/hevc_tiles_multiple_tracks_empty_base.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/svc/mp4-live-LastTime-depRep.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/mvc/DDF_10s_25fps.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/mvc/DDF_10s_25fps-dynamic.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/shvc_hvc2_single_track.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/mhvc_hvc1_hvc2_multiple_tracks_extractors.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/shvc_hev2_single_track.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/lhevc_avc3_lhe1.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/shvc_hvc1_lhv1_multiple_tracks_implicit.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/lhevc_avc3_lhv1.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/shvc_hev1_single_track.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/mhvc_hvc1_single_track.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/lhevc_avc1_lhe1.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/mhvc_hev1_lhe1_multiple_tracks_implicit.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/mhvc_hvc1_lhv1_multiple_tracks_implicit.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/mhvc_hev1_single_track.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/shvc_hev1_hev2_multiple_tracks_extractors.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/mhvc_hev1_hev2_multiple_tracks_extractors.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/lhevc_avc1_lhv1.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/shvc_hvc1_single_track.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/mhvc_hev2_single_track.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/shvc_hev1_lhe1_multiple_tracks_implicit.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/shvc_hvc1_hvc2_multiple_tracks_extractors.mp4".into(), + "FileFormatConformance/data/file_features/published/nalu/l-hevc/mhvc_hvc2_single_track.mp4".into(), + + ]; + + for entry in std::fs::read_dir("FileFormatConformance/data/file_features/published").unwrap() { + let direntry = entry.unwrap(); + let path = direntry.path(); + if path.is_dir() { + check_directory(&expected_fails, &path); + } + } +} + +fn check_directory(expected_fails: &Vec, directory: &Path) { + for entry in std::fs::read_dir(directory).unwrap() { + let direntry = entry.unwrap(); + let path = direntry.path(); + if path.is_dir() { + check_directory(expected_fails, &path); + } else { + let filepath = direntry.path().into_os_string().into_string().unwrap(); + if !filepath.ends_with(".json") + && !filepath.ends_with(".dat") + && !filepath.ends_with(".zip") + && !filepath.ends_with(".txt") + && !filepath.ends_with(".xml") + { + println!("checking {:?}", direntry); + match check_one_file(&direntry.path()) { + true => assert!( + !expected_fails.contains(&filepath), + "expected {filepath} to fail, but it unexpectedly passed" + ), + false => assert!( + expected_fails.contains(&filepath), + "expected {filepath} to pass, but it unexpectedly failed" + ), + } + } + } + } +} + +fn check_one_file(path: &PathBuf) -> bool { + let mut input = std::fs::File::open(path).unwrap(); + let mut full_parse = true; + loop { + let parse_result = Option::::read_from(&mut input); + match parse_result { + Ok(maybe_atom) => match maybe_atom { + Some(_) => {} + None => { + break; + } + }, + Err(err) => { + println!("{err:#?}"); + full_parse = false; + break; + } + } + } + full_parse +} diff --git a/src/test/uncompressed.rs b/src/test/uncompressed.rs index 51ab967..d4e243b 100644 --- a/src/test/uncompressed.rs +++ b/src/test/uncompressed.rs @@ -93,7 +93,6 @@ fn uncompressed() { blue: 0 } }), - smhd: None, dinf: Dinf { dref: Dref { urls: vec![Url { @@ -199,9 +198,10 @@ fn uncompressed() { ] .into() }, - stsz: Stsz { + stsz: Some(Stsz { samples: StszSamples::Identical { count: 2, size: 6 }, - }, + }), + stz2: None, stco: Some(Stco { entries: [856, 862].into() }), @@ -212,7 +212,8 @@ fn uncompressed() { saiz: vec![], saio: vec![], cslg: None, - } + }, + ..Default::default() } }, senc: None, @@ -238,6 +239,7 @@ fn uncompressed() { } .into(),], }), + ..Default::default() }) } ); diff --git a/src/test/vp9.rs b/src/test/vp9.rs index 218e4d6..2cca3ef 100644 --- a/src/test/vp9.rs +++ b/src/test/vp9.rs @@ -122,7 +122,6 @@ fn vp9() { blue: 0 } }), - smhd: None, dinf: Dinf { dref: Dref { urls: vec![Url { @@ -164,7 +163,8 @@ fn vp9() { ctts: None, stss: None, stsc: Stsc { entries: vec![] }, - stsz: Stsz::default(), + stsz: Some(Stsz::default()), + stz2: None, stco: Some(Stco { entries: vec![] }), co64: None, sbgp: vec![], @@ -173,7 +173,8 @@ fn vp9() { saio: vec![], saiz: vec![], cslg: None, - } + }, + ..Default::default() } }, senc: None,