/// Check if needed fields are still public. // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. use mp4parse as mp4; use crate::mp4::{ParseStrictness, Status}; use std::convert::TryInto; use std::fs::File; use std::io::{Cursor, Read, Seek}; static MINI_MP4: &str = "tests/minimal.mp4"; static MINI_MP4_WITH_METADATA: &str = "tests/metadata.mp4"; static MINI_MP4_WITH_METADATA_STD_GENRE: &str = "tests/metadata_gnre.mp4"; static AUDIO_EME_CENC_MP4: &str = "tests/bipbop-cenc-audioinit.mp4"; static VIDEO_EME_CENC_MP4: &str = "tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4"; // The cbcs files were created via shaka-packager from Firefox's test suite's bipbop.mp4 using: // packager-win.exe // in=bipbop.mp4,stream=audio,init_segment=bipbop_cbcs_audio_init.mp4,segment_template=bipbop_cbcs_audio_$Number$.m4s // in=bipbop.mp4,stream=video,init_segment=bipbop_cbcs_video_init.mp4,segment_template=bipbop_cbcs_video_$Number$.m4s // --protection_scheme cbcs --enable_raw_key_encryption // --keys label=:key_id=7e571d047e571d047e571d047e571d21:key=7e5744447e5744447e5744447e574421 // --iv 11223344556677889900112233445566 // --generate_static_mpd --mpd_output bipbop_cbcs.mpd // note: only the init files are needed for these tests static AUDIO_EME_CBCS_MP4: &str = "tests/bipbop_cbcs_audio_init.mp4"; static VIDEO_EME_CBCS_MP4: &str = "tests/bipbop_cbcs_video_init.mp4"; static VIDEO_AV1_MP4: &str = "tests/tiny_av1.mp4"; // This file contains invalid userdata in its copyright userdata. See // https://bugzilla.mozilla.org/show_bug.cgi?id=1687357 for more information. static VIDEO_INVALID_USERDATA: &str = "tests/invalid_userdata.mp4"; static IMAGE_AVIF: &str = "tests/valid.avif"; static IMAGE_AVIF_EXTENTS: &str = "tests/multiple-extents.avif"; static IMAGE_AVIF_ALPHA: &str = "tests/valid-alpha.avif"; static IMAGE_AVIF_ALPHA_PREMULTIPLIED: &str = "tests/1x1-black-alpha-50pct-premultiplied.avif"; static IMAGE_AVIF_CORRUPT: &str = "tests/corrupt/bug-1655846.avif"; static IMAGE_AVIF_CORRUPT_2: &str = "tests/corrupt/bug-1661347.avif"; static IMAGE_AVIF_IPMA_BAD_VERSION: &str = "tests/bad-ipma-version.avif"; static IMAGE_AVIF_IPMA_BAD_FLAGS: &str = "tests/bad-ipma-flags.avif"; static IMAGE_AVIF_IPMA_DUPLICATE_VERSION_AND_FLAGS: &str = "tests/corrupt/ipma-duplicate-version-and-flags.avif"; static IMAGE_AVIF_IPMA_DUPLICATE_ITEM_ID: &str = "tests/corrupt/ipma-duplicate-item_id.avif"; static IMAGE_AVIF_IPMA_INVALID_PROPERTY_INDEX: &str = "tests/corrupt/ipma-invalid-property-index.avif"; static IMAGE_AVIF_NO_HDLR: &str = "tests/corrupt/hdlr-not-first.avif"; static IMAGE_AVIF_HDLR_NOT_FIRST: &str = "tests/corrupt/no-hdlr.avif"; static IMAGE_AVIF_HDLR_NOT_PICT: &str = "tests/corrupt/hdlr-not-pict.avif"; static IMAGE_AVIF_HDLR_NONZERO_RESERVED: &str = "tests/hdlr-nonzero-reserved.avif"; static IMAGE_AVIF_HDLR_MULTIPLE_NUL: &str = "tests/invalid-avif-hdlr-name-multiple-nul.avif"; static IMAGE_AVIF_NO_MIF1: &str = "tests/no-mif1.avif"; static IMAGE_AVIF_NO_PITM: &str = "tests/corrupt/no-pitm.avif"; static IMAGE_AVIF_NO_PIXI: &str = "tests/corrupt/no-pixi.avif"; static IMAGE_AVIF_NO_AV1C: &str = "tests/corrupt/no-av1C.avif"; static IMAGE_AVIF_NO_ISPE: &str = "tests/corrupt/no-ispe.avif"; static IMAGE_AVIF_NO_ALPHA_ISPE: &str = "tests/corrupt/no-alpha-ispe.avif"; static IMAGE_AVIF_TRANSFORM_ORDER: &str = "tests/corrupt/invalid-transformation-order.avif"; static IMAGE_AVIF_TRANSFORM_BEFORE_ISPE: &str = "tests/corrupt/transformation-before-ispe.avif"; static IMAGE_AVIF_NO_ALPHA_AV1C: &str = "tests/corrupt/no-alpha-av1C.avif"; static IMAGE_AVIF_NO_ALPHA_PIXI: &str = "tests/corrupt/no-pixi-for-alpha.avif"; static IMAGE_AVIF_AV1C_MISSING_ESSENTIAL: &str = "tests/av1C-missing-essential.avif"; static IMAGE_AVIF_A1LX_MARKED_ESSENTIAL: &str = "tests/corrupt/a1lx-marked-essential.avif"; static IMAGE_AVIF_A1OP_MISSING_ESSENTIAL: &str = "tests/corrupt/a1op-missing-essential.avif"; static IMAGE_AVIF_IMIR_MISSING_ESSENTIAL: &str = "tests/imir-missing-essential.avif"; static IMAGE_AVIF_IROT_MISSING_ESSENTIAL: &str = "tests/irot-missing-essential.avif"; static IMAGE_AVIF_LSEL_MISSING_ESSENTIAL: &str = "tests/corrupt/lsel-missing-essential.avif"; static IMAGE_AVIF_CLAP_MISSING_ESSENTIAL: &str = "tests/clap-missing-essential.avif"; static IMAGE_AVIF_UNKNOWN_MDAT_SIZE: &str = "tests/unknown_mdat.avif"; static IMAGE_AVIF_UNKNOWN_MDAT_SIZE_IN_OVERSIZED_META: &str = "tests/unknown_mdat_in_oversized_meta.avif"; static IMAGE_AVIF_VALID_WITH_GARBAGE_OVERREAD_AT_END: &str = "tests/valid_with_garbage_overread.avif"; static IMAGE_AVIF_VALID_WITH_GARBAGE_BYTE_AT_END: &str = "tests/valid_with_garbage_byte.avif"; static IMAGE_AVIF_WIDE_BOX_SIZE_0: &str = "tests/wide_box_size_0.avif"; static AVIF_TEST_DIRS: &[&str] = &["tests", "av1-avif/testFiles", "link-u-avif-sample-images"]; // These files are // av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1op.avif // av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1lx.avif // av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_lsel.avif // respectively, but with https://github.com/AOMediaCodec/av1-avif/issues/174 fixed static AVIF_A1OP: &str = "tests/a1op.avif"; static AVIF_A1LX: &str = "tests/a1lx.avif"; static AVIF_LSEL: &str = "tests/lsel.avif"; static AVIF_CLAP: &str = "tests/clap-basic-1_3x3-to-1x1.avif"; static AVIF_GRID: &str = "av1-avif/testFiles/Microsoft/Summer_in_Tomsk_720p_5x4_grid.avif"; static AVIF_GRID_A1LX: &str = "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_grid_a1lx.avif"; static AVIF_AVIS_MAJOR_NO_PITM: &str = "av1-avif/testFiles/Netflix/avis/Chimera-AV1-10bit-480x270.avif"; /// This is av1-avif/testFiles/Netflix/avis/alpha_video.avif /// but with https://github.com/AOMediaCodec/av1-avif/issues/177 fixed static AVIF_AVIS_MAJOR_WITH_PITM_AND_ALPHA: &str = "tests/alpha_video_fixed.avif"; static AVIF_AVIS_WITH_NO_PITM_NO_ILOC: &str = "tests/avis_with_no_ptim_no_iloc.avif"; static AVIF_AVIS_WITH_PITM_NO_ILOC: &str = "tests/avis_with_pitm_no_iloc.avif"; static AVIF_AVIS_MAJOR_NO_MOOV: &str = "tests/corrupt/alpha_video_moov_is_moop.avif"; static AVIF_AVIS_NO_LOOP: &str = "tests/loop_none.avif"; static AVIF_AVIS_LOOP_FOREVER: &str = "tests/loop_forever.avif"; static AVIF_NO_PIXI_IMAGES: &[&str] = &[IMAGE_AVIF_NO_PIXI, IMAGE_AVIF_NO_ALPHA_PIXI]; static AVIF_UNSUPPORTED_IMAGES: &[&str] = &[ AVIF_A1LX, AVIF_A1OP, AVIF_CLAP, IMAGE_AVIF_CLAP_MISSING_ESSENTIAL, AVIF_GRID, AVIF_GRID_A1LX, AVIF_LSEL, "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1lx.avif", "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1op.avif", "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1op_lsel.avif", "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_lsel.avif", "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_grid_lsel.avif", "av1-avif/testFiles/Link-U/kimono.crop.avif", "av1-avif/testFiles/Link-U/kimono.mirror-vertical.rotate270.crop.avif", "av1-avif/testFiles/Microsoft/Chimera_10bit_cropped_to_1920x1008.avif", "av1-avif/testFiles/Microsoft/Chimera_10bit_cropped_to_1920x1008_with_HDR_metadata.avif", "av1-avif/testFiles/Microsoft/Chimera_8bit_cropped_480x256.avif", "av1-avif/testFiles/Xiph/abandoned_filmgrain.avif", "av1-avif/testFiles/Xiph/fruits_2layer_thumbsize.avif", "av1-avif/testFiles/Xiph/quebec_3layer_op2.avif", "av1-avif/testFiles/Xiph/tiger_3layer_1res.avif", "av1-avif/testFiles/Xiph/tiger_3layer_3res.avif", "link-u-avif-sample-images/kimono.crop.avif", "link-u-avif-sample-images/kimono.mirror-vertical.rotate270.crop.avif", ]; /// See https://github.com/AOMediaCodec/av1-avif/issues/150 /// https://github.com/AOMediaCodec/av1-avif/issues/174 /// https://github.com/AOMediaCodec/av1-avif/issues/177 /// and https://github.com/AOMediaCodec/av1-avif/issues/178 // TODO: make this into a map of expected errors? static AV1_AVIF_CORRUPT_IMAGES: &[&str] = &[ IMAGE_AVIF_UNKNOWN_MDAT_SIZE_IN_OVERSIZED_META, IMAGE_AVIF_WIDE_BOX_SIZE_0, "av1-avif/testFiles/Link-U/kimono.crop.avif", "av1-avif/testFiles/Link-U/kimono.mirror-horizontal.avif", "av1-avif/testFiles/Link-U/kimono.mirror-vertical.avif", "av1-avif/testFiles/Link-U/kimono.mirror-vertical.rotate270.avif", "av1-avif/testFiles/Link-U/kimono.mirror-vertical.rotate270.crop.avif", "av1-avif/testFiles/Link-U/kimono.rotate90.avif", "av1-avif/testFiles/Link-U/kimono.rotate270.avif", "link-u-avif-sample-images/kimono.crop.avif", "link-u-avif-sample-images/kimono.mirror-horizontal.avif", "link-u-avif-sample-images/kimono.mirror-vertical.avif", "link-u-avif-sample-images/kimono.mirror-vertical.rotate270.avif", "link-u-avif-sample-images/kimono.mirror-vertical.rotate270.crop.avif", "link-u-avif-sample-images/kimono.rotate90.avif", "link-u-avif-sample-images/kimono.rotate270.avif", "link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-full.monochrome.avif", "link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.monochrome.avif", "link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-full.monochrome.avif", "link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.monochrome.avif", "link-u-avif-sample-images/plum-blossom-large.profile1.10bpc.yuv444.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-large.profile1.10bpc.yuv444.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-large.profile1.8bpc.yuv444.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-large.profile1.8bpc.yuv444.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-full.monochrome.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.monochrome.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-full.monochrome.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.monochrome.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-full.monochrome.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.monochrome.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-full.monochrome.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.monochrome.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-full.monochrome.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.monochrome.avif", "link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-full.monochrome.avif", "link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.monochrome.avif", "link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-full.monochrome.avif", "link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.monochrome.avif", "link-u-avif-sample-images/plum-blossom-small.profile1.10bpc.yuv444.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-small.profile1.10bpc.yuv444.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-small.profile1.8bpc.yuv444.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-small.profile1.8bpc.yuv444.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-full.monochrome.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.monochrome.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-full.monochrome.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.monochrome.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-full.monochrome.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.monochrome.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-full.monochrome.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.monochrome.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-full.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-full.monochrome.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.avif", "link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.monochrome.avif", ]; static AVIF_CORRUPT_IMAGES_DIR: &str = "tests/corrupt"; // The 1 frame h263 3gp file can be generated by ffmpeg with command // "ffmpeg -i [input file] -f 3gp -vcodec h263 -vf scale=176x144 -frames:v 1 -an output.3gp" static VIDEO_H263_3GP: &str = "tests/bbb_sunflower_QCIF_30fps_h263_noaudio_1f.3gp"; // The 1 frame AMR-NB 3gp file can be generated by ffmpeg with command // "ffmpeg -i [input file] -f 3gp -acodec amr_nb -ar 8000 -ac 1 -frames:a 1 -vn output.3gp" #[cfg(feature = "3gpp")] static AUDIO_AMRNB_3GP: &str = "tests/amr_nb_1f.3gp"; // The 1 frame AMR-WB 3gp file can be generated by ffmpeg with command // "ffmpeg -i [input file] -f 3gp -acodec amr_wb -ar 16000 -ac 1 -frames:a 1 -vn output.3gp" #[cfg(feature = "3gpp")] static AUDIO_AMRWB_3GP: &str = "tests/amr_wb_1f.3gp"; #[cfg(feature = "mp4v")] // The 1 frame mp4v mp4 file can be generated by ffmpeg with command // "ffmpeg -i [input file] -f mp4 -c:v mpeg4 -vf scale=176x144 -frames:v 1 -an output.mp4" static VIDEO_MP4V_MP4: &str = "tests/bbb_sunflower_QCIF_30fps_mp4v_noaudio_1f.mp4"; // Adapted from https://github.com/GuillaumeGomez/audio-video-metadata/blob/9dff40f565af71d5502e03a2e78ae63df95cfd40/src/metadata.rs#L53 #[test] fn public_api() { let mut fd = File::open(MINI_MP4).expect("Unknown file"); let mut buf = Vec::new(); fd.read_to_end(&mut buf).expect("File error"); let mut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); assert_eq!(context.timescale, Some(mp4::MediaTimeScale(1000))); for track in context.tracks { match track.track_type { mp4::TrackType::Video => { // track part assert_eq!(track.duration, Some(mp4::TrackScaledTime(512, 0))); assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0))); assert_eq!(track.media_time, Some(mp4::TrackScaledTime(0, 0))); assert_eq!(track.timescale, Some(mp4::TrackTimeScale(12800, 0))); // track.tkhd part let tkhd = track.tkhd.unwrap(); assert!(!tkhd.disabled); assert_eq!(tkhd.duration, 40); assert_eq!(tkhd.width, 20_971_520); assert_eq!(tkhd.height, 15_728_640); // track.stsd part let stsd = track.stsd.expect("expected an stsd"); let v = match stsd.descriptions.first().expect("expected a SampleEntry") { mp4::SampleEntry::Video(v) => v, _ => panic!("expected a VideoSampleEntry"), }; assert_eq!(v.width, 320); assert_eq!(v.height, 240); assert_eq!( match v.codec_specific { mp4::VideoCodecSpecific::AVCConfig(ref avc) => { assert!(!avc.is_empty()); "AVC" } mp4::VideoCodecSpecific::VPxConfig(ref vpx) => { // We don't enter in here, we just check if fields are public. assert!(vpx.bit_depth > 0); assert!(vpx.colour_primaries > 0); assert!(vpx.chroma_subsampling > 0); assert!(!vpx.codec_init.is_empty()); "VPx" } mp4::VideoCodecSpecific::ESDSConfig(ref mp4v) => { assert!(!mp4v.is_empty()); "MP4V" } mp4::VideoCodecSpecific::AV1Config(ref _av1c) => { "AV1" } mp4::VideoCodecSpecific::H263Config(ref _h263) => { "H263" } }, "AVC" ); } mp4::TrackType::Audio => { // track part assert_eq!(track.duration, Some(mp4::TrackScaledTime(2944, 1))); assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0))); assert_eq!(track.media_time, Some(mp4::TrackScaledTime(1024, 1))); assert_eq!(track.timescale, Some(mp4::TrackTimeScale(48000, 1))); // track.tkhd part let tkhd = track.tkhd.unwrap(); assert!(!tkhd.disabled); assert_eq!(tkhd.duration, 62); assert_eq!(tkhd.width, 0); assert_eq!(tkhd.height, 0); // track.stsd part let stsd = track.stsd.expect("expected an stsd"); let a = match stsd.descriptions.first().expect("expected a SampleEntry") { mp4::SampleEntry::Audio(a) => a, _ => panic!("expected a AudioSampleEntry"), }; assert_eq!( match a.codec_specific { mp4::AudioCodecSpecific::ES_Descriptor(ref esds) => { assert_eq!(esds.audio_codec, mp4::CodecType::AAC); assert_eq!(esds.audio_sample_rate.unwrap(), 48000); assert_eq!(esds.audio_object_type.unwrap(), 2); "ES" } mp4::AudioCodecSpecific::FLACSpecificBox(ref flac) => { // STREAMINFO block must be present and first. assert!(!flac.blocks.is_empty()); assert_eq!(flac.blocks[0].block_type, 0); assert_eq!(flac.blocks[0].data.len(), 34); "FLAC" } mp4::AudioCodecSpecific::OpusSpecificBox(ref opus) => { // We don't enter in here, we just check if fields are public. assert!(opus.version > 0); "Opus" } mp4::AudioCodecSpecific::ALACSpecificBox(ref alac) => { assert!(alac.data.len() == 24 || alac.data.len() == 48); "ALAC" } mp4::AudioCodecSpecific::MP3 => { "MP3" } mp4::AudioCodecSpecific::LPCM => { "LPCM" } #[cfg(feature = "3gpp")] mp4::AudioCodecSpecific::AMRSpecificBox(_) => { "AMR" } }, "ES" ); assert!(a.samplesize > 0); assert!(a.samplerate > 0.0); } _ => {} } } } #[test] fn public_metadata() { let mut fd = File::open(MINI_MP4_WITH_METADATA).expect("Unknown file"); let mut buf = Vec::new(); fd.read_to_end(&mut buf).expect("File error"); let mut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); let udta = context .userdata .expect("didn't find udta") .expect("failed to parse udta"); let meta = udta.meta.expect("didn't find meta"); assert_eq!(meta.title.unwrap(), "Title"); assert_eq!(meta.artist.unwrap(), "Artist"); assert_eq!(meta.album_artist.unwrap(), "Album Artist"); assert_eq!(meta.comment.unwrap(), "Comments"); assert_eq!(meta.year.unwrap(), "2019"); assert_eq!( meta.genre.unwrap(), mp4::Genre::CustomGenre("Custom Genre".try_into().unwrap()) ); assert_eq!(meta.encoder.unwrap(), "Lavf56.40.101"); assert_eq!(meta.encoded_by.unwrap(), "Encoded-by"); assert_eq!(meta.copyright.unwrap(), "Copyright"); assert_eq!(meta.track_number.unwrap(), 3); assert_eq!(meta.total_tracks.unwrap(), 6); assert_eq!(meta.disc_number.unwrap(), 5); assert_eq!(meta.total_discs.unwrap(), 10); assert_eq!(meta.beats_per_minute.unwrap(), 128); assert_eq!(meta.composer.unwrap(), "Composer"); assert!(meta.compilation.unwrap()); assert!(!meta.gapless_playback.unwrap()); assert!(!meta.podcast.unwrap()); assert_eq!(meta.advisory.unwrap(), mp4::AdvisoryRating::Clean); assert_eq!(meta.media_type.unwrap(), mp4::MediaType::Normal); assert_eq!(meta.rating.unwrap(), "50"); assert_eq!(meta.grouping.unwrap(), "Grouping"); assert_eq!(meta.category.unwrap(), "Category"); assert_eq!(meta.keyword.unwrap(), "Keyword"); assert_eq!(meta.description.unwrap(), "Description"); assert_eq!(meta.lyrics.unwrap(), "Lyrics"); assert_eq!(meta.long_description.unwrap(), "Long Description"); assert_eq!(meta.tv_episode_name.unwrap(), "Episode Name"); assert_eq!(meta.tv_network_name.unwrap(), "Network Name"); assert_eq!(meta.tv_episode_number.unwrap(), 15); assert_eq!(meta.tv_season.unwrap(), 10); assert_eq!(meta.tv_show_name.unwrap(), "Show Name"); assert!(meta.hd_video.unwrap()); assert_eq!(meta.owner.unwrap(), "Owner"); assert_eq!(meta.sort_name.unwrap(), "Sort Name"); assert_eq!(meta.sort_album.unwrap(), "Sort Album"); assert_eq!(meta.sort_artist.unwrap(), "Sort Artist"); assert_eq!(meta.sort_album_artist.unwrap(), "Sort Album Artist"); assert_eq!(meta.sort_composer.unwrap(), "Sort Composer"); // Check for valid JPEG header let covers = meta.cover_art.unwrap(); let cover = &covers[0]; let mut bytes = [0u8; 4]; bytes[0] = cover[0]; bytes[1] = cover[1]; bytes[2] = cover[2]; assert_eq!(u32::from_le_bytes(bytes), 0x00ff_d8ff); } #[test] fn public_metadata_gnre() { let mut fd = File::open(MINI_MP4_WITH_METADATA_STD_GENRE).expect("Unknown file"); let mut buf = Vec::new(); fd.read_to_end(&mut buf).expect("File error"); let mut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); let udta = context .userdata .expect("didn't find udta") .expect("failed to parse udta"); let meta = udta.meta.expect("didn't find meta"); assert_eq!(meta.title.unwrap(), "Title"); assert_eq!(meta.artist.unwrap(), "Artist"); assert_eq!(meta.album_artist.unwrap(), "Album Artist"); assert_eq!(meta.comment.unwrap(), "Comments"); assert_eq!(meta.year.unwrap(), "2019"); assert_eq!(meta.genre.unwrap(), mp4::Genre::StandardGenre(3)); assert_eq!(meta.encoder.unwrap(), "Lavf56.40.101"); assert_eq!(meta.encoded_by.unwrap(), "Encoded-by"); assert_eq!(meta.copyright.unwrap(), "Copyright"); assert_eq!(meta.track_number.unwrap(), 3); assert_eq!(meta.total_tracks.unwrap(), 6); assert_eq!(meta.disc_number.unwrap(), 5); assert_eq!(meta.total_discs.unwrap(), 10); assert_eq!(meta.beats_per_minute.unwrap(), 128); assert_eq!(meta.composer.unwrap(), "Composer"); assert!(meta.compilation.unwrap()); assert!(!meta.gapless_playback.unwrap()); assert!(!meta.podcast.unwrap()); assert_eq!(meta.advisory.unwrap(), mp4::AdvisoryRating::Clean); assert_eq!(meta.media_type.unwrap(), mp4::MediaType::Normal); assert_eq!(meta.rating.unwrap(), "50"); assert_eq!(meta.grouping.unwrap(), "Grouping"); assert_eq!(meta.category.unwrap(), "Category"); assert_eq!(meta.keyword.unwrap(), "Keyword"); assert_eq!(meta.description.unwrap(), "Description"); assert_eq!(meta.lyrics.unwrap(), "Lyrics"); assert_eq!(meta.long_description.unwrap(), "Long Description"); assert_eq!(meta.tv_episode_name.unwrap(), "Episode Name"); assert_eq!(meta.tv_network_name.unwrap(), "Network Name"); assert_eq!(meta.tv_episode_number.unwrap(), 15); assert_eq!(meta.tv_season.unwrap(), 10); assert_eq!(meta.tv_show_name.unwrap(), "Show Name"); assert!(meta.hd_video.unwrap()); assert_eq!(meta.owner.unwrap(), "Owner"); assert_eq!(meta.sort_name.unwrap(), "Sort Name"); assert_eq!(meta.sort_album.unwrap(), "Sort Album"); assert_eq!(meta.sort_artist.unwrap(), "Sort Artist"); assert_eq!(meta.sort_album_artist.unwrap(), "Sort Album Artist"); assert_eq!(meta.sort_composer.unwrap(), "Sort Composer"); // Check for valid JPEG header let covers = meta.cover_art.unwrap(); let cover = &covers[0]; let mut bytes = [0u8; 4]; bytes[0] = cover[0]; bytes[1] = cover[1]; bytes[2] = cover[2]; assert_eq!(u32::from_le_bytes(bytes), 0x00ff_d8ff); } #[test] fn public_invalid_metadata() { // Test that reading userdata containing invalid metadata is not fatal to parsing and that // expected values are still found. let mut fd = File::open(VIDEO_INVALID_USERDATA).expect("Unknown file"); let mut buf = Vec::new(); fd.read_to_end(&mut buf).expect("File error"); let mut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); // Should have userdata. assert!(context.userdata.is_some()); // But it should contain an error. assert!(context.userdata.unwrap().is_err()); // Smoke test that other data has been parsed. Don't check everything, just make sure some // values are as expected. assert_eq!(context.tracks.len(), 2); for track in context.tracks { match track.track_type { mp4::TrackType::Video => { // Check some of the values in the video tkhd. let tkhd = track.tkhd.unwrap(); assert!(!tkhd.disabled); assert_eq!(tkhd.duration, 231232); assert_eq!(tkhd.width, 83_886_080); assert_eq!(tkhd.height, 47_185_920); } mp4::TrackType::Audio => { // Check some of the values in the audio tkhd. let tkhd = track.tkhd.unwrap(); assert!(!tkhd.disabled); assert_eq!(tkhd.duration, 231338); assert_eq!(tkhd.width, 0); assert_eq!(tkhd.height, 0); } _ => panic!("File should not contain other tracks."), } } } #[test] fn public_audio_tenc() { let kid = vec![ 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, ]; let mut fd = File::open(AUDIO_EME_CENC_MP4).expect("Unknown file"); let mut buf = Vec::new(); fd.read_to_end(&mut buf).expect("File error"); let mut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd"); let a = match stsd.descriptions.first().expect("expected a SampleEntry") { mp4::SampleEntry::Audio(a) => a, _ => panic!("expected a AudioSampleEntry"), }; assert_eq!(a.codec_type, mp4::CodecType::EncryptedAudio); match a.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { Some(p) => { assert_eq!(p.original_format, b"mp4a"); if let Some(ref schm) = p.scheme_type { assert_eq!(schm.scheme_type, b"cenc"); } else { panic!("Expected scheme type info"); } if let Some(ref tenc) = p.tenc { assert!(tenc.is_encrypted > 0); assert_eq!(tenc.iv_size, 16); assert_eq!(tenc.kid, kid); assert_eq!(tenc.crypt_byte_block_count, None); assert_eq!(tenc.skip_byte_block_count, None); assert_eq!(tenc.constant_iv, None); } else { panic!("Invalid test condition"); } } _ => { panic!("Invalid test condition"); } } } } #[test] fn public_video_cenc() { let system_id = vec![ 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, ]; let kid = vec![ 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x11, ]; let pssh_box = vec![ 0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68, 0x01, 0x00, 0x00, 0x00, 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, 0x00, 0x00, 0x00, 0x01, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x11, 0x00, 0x00, 0x00, 0x00, ]; let mut fd = File::open(VIDEO_EME_CENC_MP4).expect("Unknown file"); let mut buf = Vec::new(); fd.read_to_end(&mut buf).expect("File error"); let mut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd"); let v = match stsd.descriptions.first().expect("expected a SampleEntry") { mp4::SampleEntry::Video(ref v) => v, _ => panic!("expected a VideoSampleEntry"), }; assert_eq!(v.codec_type, mp4::CodecType::EncryptedVideo); match v.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { Some(p) => { assert_eq!(p.original_format, b"avc1"); if let Some(ref schm) = p.scheme_type { assert_eq!(schm.scheme_type, b"cenc"); } else { panic!("Expected scheme type info"); } if let Some(ref tenc) = p.tenc { assert!(tenc.is_encrypted > 0); assert_eq!(tenc.iv_size, 16); assert_eq!(tenc.kid, kid); assert_eq!(tenc.crypt_byte_block_count, None); assert_eq!(tenc.skip_byte_block_count, None); assert_eq!(tenc.constant_iv, None); } else { panic!("Invalid test condition"); } } _ => { panic!("Invalid test condition"); } } } for pssh in context.psshs { assert_eq!(pssh.system_id, system_id); for kid_id in pssh.kid { assert_eq!(kid_id, kid); } assert!(pssh.data.is_empty()); assert_eq!(pssh.box_content, pssh_box); } } #[test] fn public_audio_cbcs() { let system_id = vec![ 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, ]; let kid = vec![ 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x21, ]; let default_iv = vec![ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, ]; let pssh_box = vec![ 0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68, 0x01, 0x00, 0x00, 0x00, 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, 0x00, 0x00, 0x00, 0x01, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x21, 0x00, 0x00, 0x00, 0x00, ]; let mut fd = File::open(AUDIO_EME_CBCS_MP4).expect("Unknown file"); let mut buf = Vec::new(); fd.read_to_end(&mut buf).expect("File error"); let mut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd"); assert_eq!(stsd.descriptions.len(), 2); let mut found_encrypted_sample_description = false; for description in stsd.descriptions { match description { mp4::SampleEntry::Audio(ref a) => { if let Some(p) = a.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { found_encrypted_sample_description = true; assert_eq!(p.original_format, b"mp4a"); if let Some(ref schm) = p.scheme_type { assert_eq!(schm.scheme_type, b"cbcs"); } else { panic!("Expected scheme type info"); } if let Some(ref tenc) = p.tenc { assert!(tenc.is_encrypted > 0); assert_eq!(tenc.iv_size, 0); assert_eq!(tenc.kid, kid); // Note: 0 for both crypt and skip seems odd but // that's what shaka-packager produced. It appears // to indicate full encryption. assert_eq!(tenc.crypt_byte_block_count, Some(0)); assert_eq!(tenc.skip_byte_block_count, Some(0)); assert_eq!(tenc.constant_iv, Some(default_iv.clone().into())); } else { panic!("Invalid test condition"); } } } _ => { panic!("expected a VideoSampleEntry"); } } } assert!( found_encrypted_sample_description, "Should have found an encrypted sample description" ); } for pssh in context.psshs { assert_eq!(pssh.system_id, system_id); for kid_id in pssh.kid { assert_eq!(kid_id, kid); } assert!(pssh.data.is_empty()); assert_eq!(pssh.box_content, pssh_box); } } #[test] fn public_video_cbcs() { let system_id = vec![ 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, ]; let kid = vec![ 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x21, ]; let default_iv = vec![ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, ]; let pssh_box = vec![ 0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68, 0x01, 0x00, 0x00, 0x00, 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, 0x00, 0x00, 0x00, 0x01, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x21, 0x00, 0x00, 0x00, 0x00, ]; let mut fd = File::open(VIDEO_EME_CBCS_MP4).expect("Unknown file"); let mut buf = Vec::new(); fd.read_to_end(&mut buf).expect("File error"); let mut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd"); assert_eq!(stsd.descriptions.len(), 2); let mut found_encrypted_sample_description = false; for description in stsd.descriptions { match description { mp4::SampleEntry::Video(ref v) => { assert_eq!(v.width, 400); assert_eq!(v.height, 300); if let Some(p) = v.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { found_encrypted_sample_description = true; assert_eq!(p.original_format, b"avc1"); if let Some(ref schm) = p.scheme_type { assert_eq!(schm.scheme_type, b"cbcs"); } else { panic!("Expected scheme type info"); } if let Some(ref tenc) = p.tenc { assert!(tenc.is_encrypted > 0); assert_eq!(tenc.iv_size, 0); assert_eq!(tenc.kid, kid); assert_eq!(tenc.crypt_byte_block_count, Some(1)); assert_eq!(tenc.skip_byte_block_count, Some(9)); assert_eq!(tenc.constant_iv, Some(default_iv.clone().into())); } else { panic!("Invalid test condition"); } } } _ => { panic!("expected a VideoSampleEntry"); } } } assert!( found_encrypted_sample_description, "Should have found an encrypted sample description" ); } for pssh in context.psshs { assert_eq!(pssh.system_id, system_id); for kid_id in pssh.kid { assert_eq!(kid_id, kid); } assert!(pssh.data.is_empty()); assert_eq!(pssh.box_content, pssh_box); } } #[test] fn public_video_av1() { let mut fd = File::open(VIDEO_AV1_MP4).expect("Unknown file"); let mut buf = Vec::new(); fd.read_to_end(&mut buf).expect("File error"); let mut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); for track in context.tracks { // track part assert_eq!(track.duration, Some(mp4::TrackScaledTime(512, 0))); assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0))); assert_eq!(track.media_time, Some(mp4::TrackScaledTime(0, 0))); assert_eq!(track.timescale, Some(mp4::TrackTimeScale(12288, 0))); // track.tkhd part let tkhd = track.tkhd.unwrap(); assert!(!tkhd.disabled); assert_eq!(tkhd.duration, 42); assert_eq!(tkhd.width, 4_194_304); assert_eq!(tkhd.height, 4_194_304); // track.stsd part let stsd = track.stsd.expect("expected an stsd"); let v = match stsd.descriptions.first().expect("expected a SampleEntry") { mp4::SampleEntry::Video(ref v) => v, _ => panic!("expected a VideoSampleEntry"), }; assert_eq!(v.codec_type, mp4::CodecType::AV1); assert_eq!(v.width, 64); assert_eq!(v.height, 64); match v.codec_specific { mp4::VideoCodecSpecific::AV1Config(ref av1c) => { // TODO: test av1c fields once ffmpeg is updated assert_eq!(av1c.profile, 0); assert_eq!(av1c.level, 0); assert_eq!(av1c.tier, 0); assert_eq!(av1c.bit_depth, 8); assert!(!av1c.monochrome); assert_eq!(av1c.chroma_subsampling_x, 1); assert_eq!(av1c.chroma_subsampling_y, 1); assert_eq!(av1c.chroma_sample_position, 0); assert!(!av1c.initial_presentation_delay_present); assert_eq!(av1c.initial_presentation_delay_minus_one, 0); } _ => panic!("Invalid test condition"), } } } #[test] fn public_mp4_bug_1185230() { let input = &mut File::open("tests/test_case_1185230.mp4").expect("Unknown file"); let context = mp4::read_mp4(input).expect("read_mp4 failed"); let number_video_tracks = context .tracks .iter() .filter(|t| t.track_type == mp4::TrackType::Video) .count(); let number_audio_tracks = context .tracks .iter() .filter(|t| t.track_type == mp4::TrackType::Audio) .count(); assert_eq!(number_video_tracks, 2); assert_eq!(number_audio_tracks, 2); } #[test] fn public_mp4_ctts_overflow() { let input = &mut File::open("tests/clusterfuzz-testcase-minimized-mp4-6093954524250112") .expect("Unknown file"); assert_eq!(Status::from(mp4::read_mp4(input)), Status::CttsBadSize); } #[test] fn public_avif_primary_item() { let input = &mut File::open(IMAGE_AVIF).expect("Unknown file"); let context = mp4::read_avif(input, ParseStrictness::Normal).expect("read_avif failed"); assert_eq!( context.primary_item_coded_data().unwrap(), [ 0x12, 0x00, 0x0A, 0x07, 0x38, 0x00, 0x06, 0x90, 0x20, 0x20, 0x69, 0x32, 0x0C, 0x16, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x79, 0x4C, 0xD2, 0x02 ] ); } #[test] fn public_avif_primary_item_split_extents() { let input = &mut File::open(IMAGE_AVIF_EXTENTS).expect("Unknown file"); let context = mp4::read_avif(input, ParseStrictness::Normal).expect("read_avif failed"); assert_eq!(context.primary_item_coded_data().unwrap().len(), 52); } #[test] fn public_avif_alpha_item() { for_strictness_result(IMAGE_AVIF_ALPHA, |_strictness, result| { assert!(result.is_ok()); }); } #[test] fn public_avif_alpha_non_premultiplied() { for_strictness_result(IMAGE_AVIF_ALPHA, |_strictness, result| { let context = result.expect("read_avif failed"); assert!(context.primary_item_coded_data().is_some()); assert!(context.alpha_item_coded_data().is_some()); assert!(!context.premultiplied_alpha); }); } #[test] fn public_avif_alpha_premultiplied() { for_strictness_result(IMAGE_AVIF_ALPHA_PREMULTIPLIED, |_strictness, result| { let context = result.expect("read_avif failed"); assert!(context.primary_item_coded_data().is_some()); assert!(context.alpha_item_coded_data().is_some()); assert!(context.premultiplied_alpha); }); } #[test] fn public_avif_unknown_mdat() { let input = &mut File::open(IMAGE_AVIF_UNKNOWN_MDAT_SIZE).expect("Unknown file"); let context = mp4::read_avif(input, ParseStrictness::Normal).expect("read_avif failed"); assert_eq!( context.primary_item_coded_data().unwrap(), [ 0x12, 0x00, 0x0A, 0x07, 0x38, 0x00, 0x06, 0x90, 0x20, 0x20, 0x69, 0x32, 0x0C, 0x16, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x79, 0x4C, 0xD2, 0x02 ] ); } #[test] fn public_avif_unknown_mdat_in_oversized_meta() { let input = &mut File::open(IMAGE_AVIF_UNKNOWN_MDAT_SIZE_IN_OVERSIZED_META).expect("Unknown file"); assert_eq!( Status::from(mp4::read_avif(input, ParseStrictness::Normal)), Status::Unsupported ); } #[test] fn public_avif_bug_1655846() { let input = &mut File::open(IMAGE_AVIF_CORRUPT).expect("Unknown file"); assert!(mp4::read_avif(input, ParseStrictness::Normal).is_err()); } #[test] fn public_avif_bug_1661347() { let input = &mut File::open(IMAGE_AVIF_CORRUPT_2).expect("Unknown file"); assert!(mp4::read_avif(input, ParseStrictness::Normal).is_err()); } fn for_strictness_result( path: &str, check: impl Fn(ParseStrictness, mp4::Result), ) { let input = &mut File::open(path).expect("Unknown file"); for strictness in [ ParseStrictness::Permissive, ParseStrictness::Normal, ParseStrictness::Strict, ] { input.rewind().expect("rewind failed"); check(strictness, mp4::read_avif(input, strictness)); } } /// Check that input generates the expected error only in strict parsing mode fn assert_avif_should(path: &str, expected: Status) { for_strictness_result(path, |strictness, result| { if strictness == ParseStrictness::Strict { assert_eq!(expected, Status::from(result)); } else { assert!(result.is_ok()); } }) } /// Check that input generates the expected error unless in permissive parsing mode fn assert_avif_shall(path: &str, expected: Status) { for_strictness_result(path, |strictness, result| { if strictness == ParseStrictness::Permissive { assert!(result.is_ok()); } else { assert_eq!(expected, Status::from(result)); } }) } // Technically all transforms shall be essential, but this appears likely to change // so we only enforce it in strict parsing // See https://github.com/mozilla/mp4parse-rust/issues/284 #[test] fn public_avif_av1c_missing_essential() { assert_avif_should(IMAGE_AVIF_AV1C_MISSING_ESSENTIAL, Status::TxformNoEssential); } #[test] fn public_avif_clap_missing_essential() { for_strictness_result(IMAGE_AVIF_CLAP_MISSING_ESSENTIAL, |strictness, result| { if strictness == ParseStrictness::Strict { assert_eq!(Status::TxformNoEssential, Status::from(result)); } else { assert_unsupported_nonfatal(&result, mp4::Feature::Clap); } }) } #[test] fn public_avif_imir_missing_essential() { assert_avif_should(IMAGE_AVIF_IMIR_MISSING_ESSENTIAL, Status::TxformNoEssential); } #[test] fn public_avif_irot_missing_essential() { assert_avif_should(IMAGE_AVIF_IROT_MISSING_ESSENTIAL, Status::TxformNoEssential); } #[test] fn public_avif_ipma_bad_version() { assert_avif_should(IMAGE_AVIF_IPMA_BAD_VERSION, Status::IpmaBadVersion); } #[test] fn public_avif_ipma_bad_flags() { assert_avif_should(IMAGE_AVIF_IPMA_BAD_FLAGS, Status::IpmaFlagsNonzero); } #[test] fn public_avif_ipma_duplicate_version_and_flags() { assert_avif_shall( IMAGE_AVIF_IPMA_DUPLICATE_VERSION_AND_FLAGS, Status::IpmaBadQuantity, ); } #[test] // TODO: convert this to a `assert_avif_shall` test to cover all `ParseStrictness` modes // that would require crafting an input that validly uses multiple ipma boxes, // which is kind of annoying to make pass the "should" requirements on flags and version // as well as the "shall" requirement on duplicate version and flags fn public_avif_ipma_duplicate_item_id() { let input = &mut File::open(IMAGE_AVIF_IPMA_DUPLICATE_ITEM_ID).expect("Unknown file"); assert_eq!( Status::from(mp4::read_avif(input, ParseStrictness::Permissive)), Status::IpmaDuplicateItemId ) } #[test] fn public_avif_ipma_invalid_property_index() { assert_avif_shall(IMAGE_AVIF_IPMA_INVALID_PROPERTY_INDEX, Status::IpmaBadIndex); } #[test] fn public_avif_hdlr_first_in_meta() { assert_avif_shall(IMAGE_AVIF_NO_HDLR, Status::HdlrNotFirst); assert_avif_shall(IMAGE_AVIF_HDLR_NOT_FIRST, Status::HdlrNotFirst); } #[test] fn public_avif_hdlr_is_pict() { assert_avif_shall(IMAGE_AVIF_HDLR_NOT_PICT, Status::HdlrTypeNotPict); } #[test] fn public_avif_hdlr_nonzero_reserved() { // This is a "should" despite the spec indicating a (somewhat ambiguous) // requirement that this field is set to zero. // See comments in read_hdlr assert_avif_should( IMAGE_AVIF_HDLR_NONZERO_RESERVED, Status::HdlrReservedNonzero, ); } #[test] fn public_avif_hdlr_multiple_nul() { // This is a "should" despite the spec indicating a (somewhat ambiguous) // requirement about extra data in boxes // See comments in read_hdlr assert_avif_should(IMAGE_AVIF_HDLR_MULTIPLE_NUL, Status::Ok); } #[test] fn public_avif_no_mif1() { assert_avif_should(IMAGE_AVIF_NO_MIF1, Status::MissingMif1Brand); } #[test] fn public_avif_no_pitm() { assert_avif_shall(IMAGE_AVIF_NO_PITM, Status::PitmMissing); } #[test] fn public_avif_pixi_present_for_displayable_images() { let pixi_test = if cfg!(feature = "missing-pixi-permitted") { assert_avif_should } else { assert_avif_shall }; pixi_test(IMAGE_AVIF_NO_PIXI, Status::PixiMissing); pixi_test(IMAGE_AVIF_NO_ALPHA_PIXI, Status::PixiMissing); } #[test] fn public_avif_av1c_present_for_av01() { assert_avif_shall(IMAGE_AVIF_NO_AV1C, Status::Av1cMissing); assert_avif_shall(IMAGE_AVIF_NO_ALPHA_AV1C, Status::Av1cMissing); } #[test] fn public_avif_ispe_present() { assert_avif_shall(IMAGE_AVIF_NO_ISPE, Status::IspeMissing); assert_avif_shall(IMAGE_AVIF_NO_ALPHA_ISPE, Status::IspeMissing); } #[test] fn public_avif_transform_before_ispe() { assert_avif_shall(IMAGE_AVIF_TRANSFORM_BEFORE_ISPE, Status::TxformBeforeIspe); } #[test] fn public_avif_transform_order() { assert_avif_shall(IMAGE_AVIF_TRANSFORM_ORDER, Status::TxformOrder); } #[allow(clippy::uninlined_format_args)] fn assert_unsupported_nonfatal(result: &mp4::Result, feature: mp4::Feature) { match result { Ok(context) => { assert!( context.unsupported_features.contains(feature), "context.unsupported_features missing expected {:?}", feature ); } r => panic!( "Expected Ok with unsupported_features containing {:?}, found {:?}", feature, r ), } } // Assert that across all strictness levels the given feature is tracked as // being used, but unsupported. Additionally, if the feature is essential, // assert that the primary item is not processed unless using permissive mode. // TODO: Add similar tests for alpha fn assert_unsupported(path: &str, feature: mp4::Feature, essential: bool) { for_strictness_result(path, |strictness, result| { assert_unsupported_nonfatal(&result, feature); match result { Ok(context) if essential => assert_eq!( context.primary_item_coded_data().is_some(), strictness == ParseStrictness::Permissive ), Ok(context) if !essential => assert!(context.primary_item_coded_data().is_some()), _ => panic!("Expected Ok, got {:?}", result), } }); } fn assert_unsupported_nonessential(path: &str, feature: mp4::Feature) { assert_unsupported(path, feature, false); } fn assert_unsupported_essential(path: &str, feature: mp4::Feature) { assert_unsupported(path, feature, true); } #[test] fn public_avif_a1lx() { assert_unsupported_nonessential(AVIF_A1LX, mp4::Feature::A1lx); } #[test] fn public_avif_a1lx_marked_essential() { assert_avif_shall(IMAGE_AVIF_A1LX_MARKED_ESSENTIAL, Status::A1lxEssential); } #[test] fn public_avif_a1op() { assert_unsupported_essential(AVIF_A1OP, mp4::Feature::A1op); } #[test] fn public_avif_a1op_missing_essential() { assert_avif_shall(IMAGE_AVIF_A1OP_MISSING_ESSENTIAL, Status::A1opNoEssential); } #[test] fn public_avif_lsel() { assert_unsupported_essential(AVIF_LSEL, mp4::Feature::Lsel); } #[test] fn public_avif_lsel_missing_essential() { assert_avif_shall(IMAGE_AVIF_LSEL_MISSING_ESSENTIAL, Status::LselNoEssential); } #[test] fn public_avif_clap() { assert_unsupported_essential(AVIF_CLAP, mp4::Feature::Clap); } #[test] fn public_avif_grid() { for file in &[AVIF_GRID, AVIF_GRID_A1LX] { let input = &mut File::open(file).expect(file); assert_unsupported_nonfatal( &mp4::read_avif(input, ParseStrictness::Normal), mp4::Feature::Grid, ); } } #[test] fn public_avis_major_no_pitm() { let input = &mut File::open(AVIF_AVIS_MAJOR_NO_PITM).expect("Unknown file"); match mp4::read_avif(input, ParseStrictness::Normal) { Ok(context) => { assert_eq!(context.major_brand, mp4::AVIS_BRAND); assert!(context.primary_item_coded_data().is_none()); assert!(context.sequence.is_some()); } Err(e) => panic!("Expected Ok(_), found {:?}", e), } } #[test] fn public_avis_major_with_pitm_and_alpha() { let input = &mut File::open(AVIF_AVIS_MAJOR_WITH_PITM_AND_ALPHA).expect("Unknown file"); match mp4::read_avif(input, ParseStrictness::Normal) { Ok(context) => { assert_eq!(context.major_brand, mp4::AVIS_BRAND); assert!(context.primary_item_coded_data().is_some()); assert!(context.alpha_item_coded_data().is_some()); match context.sequence { Some(sequence) => { assert!(!sequence.tracks.is_empty()); assert_eq!(sequence.tracks[0].looped, None); } None => panic!("Expected sequence"), } } Err(e) => panic!("Expected Ok(_), found {:?}", e), } } #[test] fn public_avif_avis_major_no_moov() { assert_avif_shall(AVIF_AVIS_MAJOR_NO_MOOV, Status::MoovMissing); } #[test] fn public_avif_avis_with_no_pitm_no_iloc() { let input = &mut File::open(AVIF_AVIS_WITH_NO_PITM_NO_ILOC).expect("Unknown file"); match mp4::read_avif(input, ParseStrictness::Normal) { Ok(context) => { assert_eq!(context.major_brand, mp4::AVIS_BRAND); match context.sequence { Some(sequence) => { assert!(!sequence.tracks.is_empty()); assert_eq!(sequence.tracks[0].looped, Some(false)); } None => panic!("Expected sequence"), } } Err(e) => panic!("Expected Ok(_), found {:?}", e), } } #[test] fn public_avif_avis_with_pitm_no_iloc() { assert_avif_should(AVIF_AVIS_WITH_PITM_NO_ILOC, Status::PitmNotFound); } #[test] fn public_avif_valid_with_garbage_overread_at_end() { assert_avif_should( IMAGE_AVIF_VALID_WITH_GARBAGE_OVERREAD_AT_END, Status::CheckParserStateErr, ); } #[test] fn public_avif_valid_with_garbage_byte_at_end() { assert_avif_should(IMAGE_AVIF_VALID_WITH_GARBAGE_BYTE_AT_END, Status::Ok); } #[test] fn public_avif_bad_video_sample_entry() { let input = &mut File::open(IMAGE_AVIF_WIDE_BOX_SIZE_0).expect("Unknown file"); assert_eq!( Status::from(mp4::read_avif(input, ParseStrictness::Normal)), Status::BoxBadWideSize ); } fn public_avis_loop_impl(path: &str, looped: bool) { let input = &mut File::open(path).expect("Unknown file"); match mp4::read_avif(input, ParseStrictness::Normal) { Ok(context) => match context.sequence { Some(sequence) => { assert!(!sequence.tracks.is_empty()); assert_eq!(sequence.tracks[0].looped, Some(looped)); if looped { assert!(sequence.tracks[0].edited_duration.is_some()); } } None => panic!( "Expected sequence in {}", AVIF_AVIS_MAJOR_WITH_PITM_AND_ALPHA ), }, Err(e) => panic!("Expected Ok(_), found {:?}", e), } } #[test] fn public_avif_avis_no_loop() { public_avis_loop_impl(AVIF_AVIS_NO_LOOP, false); } #[test] fn public_avif_avis_loop_forever() { public_avis_loop_impl(AVIF_AVIS_LOOP_FOREVER, true); } #[test] fn public_avif_read_samples() { public_avif_read_samples_impl(ParseStrictness::Normal); } #[test] #[ignore] // See https://github.com/AOMediaCodec/av1-avif/issues/146 fn public_avif_read_samples_strict() { public_avif_read_samples_impl(ParseStrictness::Strict); } fn to_canonical_paths(strs: &[&str]) -> Vec { strs.iter() .map(std::fs::canonicalize) .map(Result::unwrap) .collect() } fn public_avif_read_samples_impl(strictness: ParseStrictness) { let corrupt_images = to_canonical_paths(AV1_AVIF_CORRUPT_IMAGES); let unsupported_images = to_canonical_paths(AVIF_UNSUPPORTED_IMAGES); let legal_no_pixi_images = if cfg!(feature = "missing-pixi-permitted") { to_canonical_paths(AVIF_NO_PIXI_IMAGES) } else { vec![] }; for dir in AVIF_TEST_DIRS { for entry in walkdir::WalkDir::new(dir) { let entry = entry.expect("AVIF entry"); let path = entry.path(); let extension = path.extension().unwrap_or_default(); if !path.is_file() || (extension != "avif" && extension != "avifs") { eprintln!("Skipping {path:?}"); continue; // Skip directories, ReadMe.txt, etc. } let corrupt = (path.canonicalize().unwrap().parent().unwrap() == std::fs::canonicalize(AVIF_CORRUPT_IMAGES_DIR).unwrap() || corrupt_images.contains(&path.canonicalize().unwrap())) && !legal_no_pixi_images.contains(&path.canonicalize().unwrap()); let unsupported = unsupported_images.contains(&path.canonicalize().unwrap()); println!( "parsing {}{}{:?}", if corrupt { "(corrupt) " } else { "" }, if unsupported { "(unsupported) " } else { "" }, path, ); let input = &mut File::open(path).expect("Unknow file"); match mp4::read_avif(input, strictness) { Ok(c) if unsupported || corrupt => { if unsupported { assert!(!c.unsupported_features.is_empty()); } else { panic!("Expected error parsing {:?}, found:\n{:?}", path, c) } } Ok(c) => { assert!( c.unsupported_features.is_empty(), "{:?}", c.unsupported_features ); eprintln!("Successfully parsed {path:?}") } Err(e) if corrupt => { eprintln!("Expected error parsing corrupt input {path:?}: {e:?}") } Err(e) => panic!("Unexpected error parsing {:?}: {:?}", path, e), } } } } #[test] fn public_video_h263() { let mut fd = File::open(VIDEO_H263_3GP).expect("Unknown file"); let mut buf = Vec::new(); fd.read_to_end(&mut buf).expect("File error"); let mut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd"); let v = match stsd.descriptions.first().expect("expected a SampleEntry") { mp4::SampleEntry::Video(ref v) => v, _ => panic!("expected a VideoSampleEntry"), }; assert_eq!(v.codec_type, mp4::CodecType::H263); assert_eq!(v.width, 176); assert_eq!(v.height, 144); let _codec_specific = match v.codec_specific { mp4::VideoCodecSpecific::H263Config(_) => true, _ => { panic!("expected a H263Config",); } }; } } #[test] #[cfg(feature = "3gpp")] fn public_audio_amrnb() { let mut fd = File::open(AUDIO_AMRNB_3GP).expect("Unknown file"); let mut buf = Vec::new(); fd.read_to_end(&mut buf).expect("File error"); let mut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd"); let a = match stsd.descriptions.first().expect("expected a SampleEntry") { mp4::SampleEntry::Audio(ref v) => v, _ => panic!("expected a AudioSampleEntry"), }; assert!(a.codec_type == mp4::CodecType::AMRNB); let _codec_specific = match a.codec_specific { mp4::AudioCodecSpecific::AMRSpecificBox(_) => true, _ => { panic!("expected a AMRSpecificBox",); } }; } } #[test] #[cfg(feature = "3gpp")] fn public_audio_amrwb() { let mut fd = File::open(AUDIO_AMRWB_3GP).expect("Unknown file"); let mut buf = Vec::new(); fd.read_to_end(&mut buf).expect("File error"); let mut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd"); let a = match stsd.descriptions.first().expect("expected a SampleEntry") { mp4::SampleEntry::Audio(ref v) => v, _ => panic!("expected a AudioSampleEntry"), }; assert!(a.codec_type == mp4::CodecType::AMRWB); let _codec_specific = match a.codec_specific { mp4::AudioCodecSpecific::AMRSpecificBox(_) => true, _ => { panic!("expected a AMRSpecificBox",); } }; } } #[test] #[cfg(feature = "mp4v")] fn public_video_mp4v() { let mut fd = File::open(VIDEO_MP4V_MP4).expect("Unknown file"); let mut buf = Vec::new(); fd.read_to_end(&mut buf).expect("File error"); let mut c = Cursor::new(&buf); let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); for track in context.tracks { let stsd = track.stsd.expect("expected an stsd"); let v = match stsd.descriptions.first().expect("expected a SampleEntry") { mp4::SampleEntry::Video(ref v) => v, _ => panic!("expected a VideoSampleEntry"), }; assert_eq!(v.codec_type, mp4::CodecType::MP4V); assert_eq!(v.width, 176); assert_eq!(v.height, 144); let _codec_specific = match v.codec_specific { mp4::VideoCodecSpecific::ESDSConfig(_) => true, _ => { panic!("expected a ESDSConfig",); } }; } }