/// 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 hevc mp4 file generated by ffmpeg with command
// "ffmpeg -f lavfi -i color=c=white:s=640x480 -c:v libx265 -frames:v 1 -pix_fmt yuv420p hevc_white_frame.mp4"
static VIDEO_HEVC_MP4: &str =
"tests/hevc_white_frame.mp4" ;
// 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"
}
mp4::VideoCodecSpecific::HEVCConfig(
ref hevc) => {
assert!(!hevc.is_empty());
"HEVC"
}
},
"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 = [
0 u8;
4 ];
bytes[
0 ] = cover[
0 ];
bytes[
1 ] = cover[
1 ];
bytes[
2 ] = cover[
2 ];
assert_eq!(u32::from_le_bytes(bytes),
0 x00ff_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 = [
0 u8;
4 ];
bytes[
0 ] = cover[
0 ];
bytes[
1 ] = cover[
1 ];
bytes[
2 ] = cover[
2 ];
assert_eq!(u32::from_le_bytes(bytes),
0 x00ff_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![
0 x7e,
0 x57,
0 x1d,
0 x04,
0 x7e,
0 x57,
0 x1d,
0 x04,
0 x7e,
0 x57,
0 x1d,
0 x04,
0 x7e,
0 x57,
0 x1d,
0 x04,
];
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![
0 x10,
0 x77,
0 xef,
0 xec,
0 xc0,
0 xb2,
0 x4d,
0 x02,
0 xac,
0 xe3,
0 x3c,
0 x1e,
0 x52,
0 xe2,
0 xfb,
0 x4b,
];
let kid = vec![
0 x7e,
0 x57,
0 x1d,
0 x03,
0 x7e,
0 x57,
0 x1d,
0 x03,
0 x7e,
0 x57,
0 x1d,
0 x03,
0 x7e,
0 x57,
0 x1d,
0 x11,
];
let pssh_box = vec![
0 x00,
0 x00,
0 x00,
0 x34,
0 x70,
0 x73,
0 x73,
0 x68,
0 x01,
0 x00,
0 x00,
0 x00,
0 x10,
0 x77,
0 xef,
0 xec,
0 xc0,
0 xb2,
0 x4d,
0 x02,
0 xac,
0 xe3,
0 x3c,
0 x1e,
0 x52,
0 xe2,
0 xfb,
0 x4b,
0 x00,
0 x00,
0 x00,
0 x01,
0 x7e,
0 x57,
0 x1d,
0 x03,
0 x7e,
0 x57,
0 x1d,
0 x03,
0 x7e,
0 x57,
0 x1d,
0 x03,
0 x7e,
0 x57,
0 x1d,
0 x11,
0 x00,
0 x00,
0 x00,
0 x00,
];
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![
0 x10,
0 x77,
0 xef,
0 xec,
0 xc0,
0 xb2,
0 x4d,
0 x02,
0 xac,
0 xe3,
0 x3c,
0 x1e,
0 x52,
0 xe2,
0 xfb,
0 x4b,
];
let kid = vec![
0 x7e,
0 x57,
0 x1d,
0 x04,
0 x7e,
0 x57,
0 x1d,
0 x04,
0 x7e,
0 x57,
0 x1d,
0 x04,
0 x7e,
0 x57,
0 x1d,
0 x21,
];
let default_iv = vec![
0 x11,
0 x22,
0 x33,
0 x44,
0 x55,
0 x66,
0 x77,
0 x88,
0 x99,
0 x00,
0 x11,
0 x22,
0 x33,
0 x44,
0 x55,
0 x66,
];
let pssh_box = vec![
0 x00,
0 x00,
0 x00,
0 x34,
0 x70,
0 x73,
0 x73,
0 x68,
0 x01,
0 x00,
0 x00,
0 x00,
0 x10,
0 x77,
0 xef,
0 xec,
0 xc0,
0 xb2,
0 x4d,
0 x02,
0 xac,
0 xe3,
0 x3c,
0 x1e,
0 x52,
0 xe2,
0 xfb,
0 x4b,
0 x00,
0 x00,
0 x00,
0 x01,
0 x7e,
0 x57,
0 x1d,
0 x04,
0 x7e,
0 x57,
0 x1d,
0 x04,
0 x7e,
0 x57,
0 x1d,
0 x04,
0 x7e,
0 x57,
0 x1d,
0 x21,
0 x00,
0 x00,
0 x00,
0 x00,
];
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![
0 x10,
0 x77,
0 xef,
0 xec,
0 xc0,
0 xb2,
0 x4d,
0 x02,
0 xac,
0 xe3,
0 x3c,
0 x1e,
0 x52,
0 xe2,
0 xfb,
0 x4b,
];
let kid = vec![
0 x7e,
0 x57,
0 x1d,
0 x04,
0 x7e,
0 x57,
0 x1d,
0 x04,
0 x7e,
0 x57,
0 x1d,
0 x04,
0 x7e,
0 x57,
0 x1d,
0 x21,
];
let default_iv = vec![
0 x11,
0 x22,
0 x33,
0 x44,
0 x55,
0 x66,
0 x77,
0 x88,
0 x99,
0 x00,
0 x11,
0 x22,
0 x33,
0 x44,
0 x55,
0 x66,
];
let pssh_box = vec![
0 x00,
0 x00,
0 x00,
0 x34,
0 x70,
0 x73,
0 x73,
0 x68,
0 x01,
0 x00,
0 x00,
0 x00,
0 x10,
0 x77,
0 xef,
0 xec,
0 xc0,
0 xb2,
0 x4d,
0 x02,
0 xac,
0 xe3,
0 x3c,
0 x1e,
0 x52,
0 xe2,
0 xfb,
0 x4b,
0 x00,
0 x00,
0 x00,
0 x01,
0 x7e,
0 x57,
0 x1d,
0 x04,
0 x7e,
0 x57,
0 x1d,
0 x04,
0 x7e,
0 x57,
0 x1d,
0 x04,
0 x7e,
0 x57,
0 x1d,
0 x21,
0 x00,
0 x00,
0 x00,
0 x00,
];
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" ).exp
ect("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(),
[
0 x12, 0 x00, 0 x0A, 0 x07, 0 x38, 0 x00, 0 x06, 0 x90, 0 x20, 0 x20, 0 x69, 0 x32, 0 x0C, 0 x16,
0 x00, 0 x00, 0 x00, 0 x48, 0 x00, 0 x00, 0 x00, 0 x79, 0 x4C, 0 xD2, 0 x02
]
);
}
#[ 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(),
[
0 x12, 0 x00, 0 x0A, 0 x07, 0 x38, 0 x00, 0 x06, 0 x90, 0 x20, 0 x20, 0 x69, 0 x32, 0 x0C, 0 x16,
0 x00, 0 x00, 0 x00, 0 x48, 0 x00, 0 x00, 0 x00, 0 x79, 0 x4C, 0 xD2, 0 x02
]
);
}
#[ 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<mp4::AvifContext>),
) {
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<mp4::AvifContext>, 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<std::path::PathBuf> {
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]
fn public_video_hevc() {
let mut fd = File::open(VIDEO_HEVC_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::HEVC);
assert_eq!(v.width, 640 );
assert_eq!(v.height, 480 );
let _codec_specific = match &v.codec_specific {
mp4::VideoCodecSpecific::HEVCConfig(_) => true ,
_ => {
panic!("expected a HEVCConfig" ,);
}
};
}
}
#[ 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" ,);
}
};
}
}
Messung V0.5 in Prozent C=95 H=100 G=97
¤ Dauer der Verarbeitung: 0.38 Sekunden
(vorverarbeitet am 2026-06-19)
¤
*© Formatika GbR, Deutschland