diff options
author | Hubert Figuière <hub@figuiere.net> | 2024-01-06 18:45:40 -0500 |
---|---|---|
committer | Hubert Figuière <hub@figuiere.net> | 2024-01-06 18:46:40 -0500 |
commit | cb721e556ef996a39d8ec002f87788adc7a3f6c4 (patch) | |
tree | ed60d3701eb2d05ffdc6a7fb506bff30cb67546a /lib | |
parent | 8b2e12d993a0aec74367982371d3c51113e809c7 (diff) |
mp4: Remove mp4parse and move the code into mp4 module
Remove the leftover mp4parse_capi
Add some clippy annotations to the mp4 code.
This mostly cancel previous commit.
Signed-off-by: Hubert Figuière <hub@figuiere.net>
Diffstat (limited to 'lib')
24 files changed, 1 insertions, 11748 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am index c87bf9d..819ba03 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -2,26 +2,8 @@ AM_CPPFLAGS = -I$(top_srcdir)/include @BOOST_CPPFLAGS@ -RUST_FILES = mp4/Cargo.lock \ - mp4/Cargo.toml \ - mp4/lib.rs \ - mp4/mp4parse_capi/src/lib.rs \ - mp4/mp4parse_capi/Cargo.toml \ - mp4/mp4parse/src/boxes.rs \ - mp4/mp4parse/src/lib.rs \ - mp4/mp4parse/src/macros.rs \ - mp4/mp4parse/src/tests.rs \ - mp4/mp4parse/src/craw.rs \ - mp4/mp4parse/src/unstable.rs \ - mp4/mp4parse/Cargo.toml \ - $(NULL) - - EXTRA_DIST = \ - exif/exif-tags.pl \ - mp4/mp4parse/README.md \ - $(RUST_FILES) - + exif/exif-tags.pl update_tags: $(srcdir)/exif/exif-tags.pl > $(srcdir)/../src/tiff/exif/generated.rs diff --git a/lib/mp4/Cargo.lock b/lib/mp4/Cargo.lock deleted file mode 100644 index 2c285b4..0000000 --- a/lib/mp4/Cargo.lock +++ /dev/null @@ -1,266 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - -[[package]] -name = "aho-corasick" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" -dependencies = [ - "memchr", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" - -[[package]] -name = "bitreader" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fa7f0adf37cd5472c978a1ff4be89c1880a923d10df4cfef6a10855a666e09b" -dependencies = [ - "cfg-if 0.1.10", -] - -[[package]] -name = "byteorder" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b5bdfe7ee3ad0b99c9801d58807a9dbc9e09196365b0203853b99889ab3c87" - -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "env_logger" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "fallible_collections" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52db5973b6a19247baf19b30f41c23a1bfffc2e9ce0a5db2f60e3cd5dc8895f7" -dependencies = [ - "hashbrown", -] - -[[package]] -name = "getrandom" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" -dependencies = [ - "cfg-if 1.0.0", - "libc", - "wasi", -] - -[[package]] -name = "hashbrown" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" -dependencies = [ - "ahash", -] - -[[package]] -name = "hermit-abi" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" -dependencies = [ - "libc", -] - -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - -[[package]] -name = "libc" -version = "0.2.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if 1.0.0", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "mp4" -version = "0.1.0" -dependencies = [ - "mp4parse_capi", -] - -[[package]] -name = "mp4parse" -version = "0.12.0" -dependencies = [ - "bitreader", - "byteorder", - "env_logger", - "fallible_collections", - "log", - "num-traits", - "static_assertions", -] - -[[package]] -name = "mp4parse_capi" -version = "0.12.0" -dependencies = [ - "byteorder", - "fallible_collections", - "log", - "mp4parse", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" - -[[package]] -name = "regex" -version = "1.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" - -[[package]] -name = "static_assertions" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" - -[[package]] -name = "termcolor" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "winapi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/lib/mp4/Cargo.toml b/lib/mp4/Cargo.toml deleted file mode 100644 index 12e6daf..0000000 --- a/lib/mp4/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "mp4" -version = "0.1.0" -license = "LPGPL-3.0" -description = "mp4 container parsing" - -[dependencies] -mp4parse_capi = { path = "mp4parse_capi", features = [ "craw" ] } - -[lib] -path = "lib.rs" -crate-type = ["staticlib"] -test = false -doctest = false -bench = false -doc = false -plugin = false -harness = false diff --git a/lib/mp4/build-rs.patch b/lib/mp4/build-rs.patch deleted file mode 100644 index 87b83ad..0000000 --- a/lib/mp4/build-rs.patch +++ /dev/null @@ -1,27 +0,0 @@ -commit 452fe8cac70bcd8672c9a33b94c5a43af845bd20 -Author: Hubert Figuière <hub@figuiere.net> -Date: Fri Jun 26 22:00:59 2020 -0400 - - Fix make distcheck: - - - the mp4parse.h is generated in the builddir - - the Cargo.lock for mp4parse_capi is disted - - mp4parse.h is not disted - -diff --git a/lib/mp4/mp4parse_capi/build.rs b/lib/mp4/mp4parse_capi/build.rs -index b25eb1a..c785fca 100644 ---- a/lib/mp4/mp4parse_capi/build.rs -+++ b/lib/mp4/mp4parse_capi/build.rs -@@ -40,7 +40,11 @@ extern "C" { - - // Generate mp4parse.h. - let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); -+ let header_path = std::env::var("CARGO_TARGET_DIR").unwrap(); -+ let mut header_path = std::path::PathBuf::from(header_path); -+ header_path.push(".."); -+ header_path.push("mp4parse.h"); - cbindgen::generate_with_config(&crate_dir, config) - .expect("Could not generate header") -- .write_to_file("include/mp4parse.h"); -+ .write_to_file(header_path); - } diff --git a/lib/mp4/lib.rs b/lib/mp4/lib.rs deleted file mode 100644 index 3a353f3..0000000 --- a/lib/mp4/lib.rs +++ /dev/null @@ -1 +0,0 @@ -extern crate mp4parse_capi; diff --git a/lib/mp4/mp4parse-cargo.patch b/lib/mp4/mp4parse-cargo.patch deleted file mode 100644 index f99ea2a..0000000 --- a/lib/mp4/mp4parse-cargo.patch +++ /dev/null @@ -1,58 +0,0 @@ -commit c6293098c5538ff9ba7272b61eeb7410fb22462b -Author: Hubert Figuière <hub@figuiere.net> -Date: Wed May 2 00:03:21 2018 -0400 - - cargo-patch - -diff --git a/mp4parse/Cargo.toml b/mp4parse/Cargo.toml -index ad74383..c62182f 100644 ---- a/mp4parse/Cargo.toml -+++ b/mp4parse/Cargo.toml -@@ -19,13 +19,9 @@ exclude = [ - "*.mp4", - ] - --[badges] --travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" } - - [dependencies] - byteorder = "1.2.1" --afl = { version = "0.3", optional = true } --abort_on_panic = { version = "1.0.0", optional = true } - bitreader = { version = "0.3.0" } - num-traits = "0.2.0" - mp4parse_fallible = { version = "0.0.1", optional = true } -@@ -35,5 +31,4 @@ log = "0.4" - test-assembler = "0.1.2" - - [features] --fuzz = ["afl", "abort_on_panic"] - craw = [] -diff --git a/mp4parse_capi/Cargo.toml b/mp4parse_capi/Cargo.toml -index b1e2690..ec37951 100644 ---- a/mp4parse_capi/Cargo.toml -+++ b/mp4parse_capi/Cargo.toml -@@ -18,8 +18,7 @@ exclude = [ - "*.mp4", - ] - --[badges] --travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" } -+build = false - - [dependencies] - byteorder = "1.2.1" -@@ -33,9 +32,5 @@ num-traits = "0.2.0" - [dev-dependencies] - env_logger = "0.5.3" - --[build-dependencies] --cbindgen = "0.5.2" -- - [features] --fuzz = ["mp4parse/fuzz"] - craw = ["mp4parse/craw"] - # Enable mp4parse_fallible to use fallible memory allocation rather than - # panicking on OOM. Note that this is only safe within Gecko where the system - # allocator has been globally overridden (see BMO 1457359). - mp4parse_fallible = ["mp4parse/mp4parse_fallible"] diff --git a/lib/mp4/mp4parse.h b/lib/mp4/mp4parse.h deleted file mode 100644 index f22f903..0000000 --- a/lib/mp4/mp4parse.h +++ /dev/null @@ -1,10 +0,0 @@ - -#pragma once - -// This must be defined before including mp4parse_ffi.h -// which is the cbindgen generated file. -#define mp4parse_rust_mp4parse_h - -// This is needed for the header -#define MP4PARSE_UNSTABLE_API -#include "mp4parse_ffi.h" diff --git a/lib/mp4/mp4parse/Cargo.toml b/lib/mp4/mp4parse/Cargo.toml deleted file mode 100644 index a30ea38..0000000 --- a/lib/mp4/mp4parse/Cargo.toml +++ /dev/null @@ -1,51 +0,0 @@ -[package] -name = "mp4parse-craw" -version = "0.12.0" -authors = [ - "Ralph Giles <giles@mozilla.com>", - "Matthew Gregan <kinetik@flim.org>", - "Alfredo Yang <ayang@mozilla.com>", - "Jon Bauman <jbauman@mozilla.com>", - "Bryce Seager van Dyk <bvandyk@mozilla.com>", - "Hubert Figuière <hub@figuiere.net>", -] - -description = "Parser for ISO base media file format (mp4) with CRAW support, a fork of mp4parse" -documentation = "https://docs.rs/mp4parse-craw/" -license = "MPL-2.0" -categories = ["multimedia::video", "multimedia::images"] - -repository = "https://gitlab.freedesktop.org/libopenraw/libopenraw" - -# Avoid complaints about trying to package test files. -exclude = [ - "*.mp4", - "av1-avif/*" -] - -[dependencies] -byteorder = "1.2.1" -bitreader = { version = "0.3.2" } -env_logger = "0.8" -fallible_collections = { version = "0.4", features = ["std_io"] } -num-traits = "0.2.14" -log = "0.4" -static_assertions = "1.1.0" - -[dev-dependencies] -test-assembler = "0.1.2" -walkdir = "2.3.1" -criterion = "0.3" - -[features] -default = ["craw"] -craw = [] -missing-pixi-permitted = [] -3gpp = [] -meta-xml = [] -unstable-api = [] -mp4v = [] - -# See https://bheisler.github.io/criterion.rs/book/faq.html#cargo-bench-gives-unrecognized-option-errors-for-valid-command-line-options -[lib] -bench = false diff --git a/lib/mp4/mp4parse/README.md b/lib/mp4/mp4parse/README.md deleted file mode 100644 index ff74a87..0000000 --- a/lib/mp4/mp4parse/README.md +++ /dev/null @@ -1,12 +0,0 @@ -This is a fork of Mozilla mp4parse that adds support for craw brand -(Canon CR3 raw files). It is used by libopenraw, and is only published -on crates.io to allow publishing the crate. - -File issues at: - -https://gitlab.freedesktop.org/libopenraw/libopenraw/-/issues - -The original project: - -https://github.com/mozilla/mp4parse-rust/ - diff --git a/lib/mp4/mp4parse/src/boxes.rs b/lib/mp4/mp4parse/src/boxes.rs deleted file mode 100644 index 9a45fb8..0000000 --- a/lib/mp4/mp4parse/src/boxes.rs +++ /dev/null @@ -1,251 +0,0 @@ -// 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 std::fmt; - -// To ensure we don't use stdlib allocating types by accident -#[allow(dead_code)] -struct Vec; -#[allow(dead_code)] -struct Box; -#[allow(dead_code)] -struct HashMap; -#[allow(dead_code)] -struct String; - -macro_rules! box_database { - ($($(#[$attr:meta])* $boxenum:ident $boxtype:expr),*,) => { - #[derive(Clone, Copy, PartialEq)] - pub enum BoxType { - $($(#[$attr])* $boxenum),*, - UnknownBox(u32), - } - - impl From<u32> for BoxType { - fn from(t: u32) -> BoxType { - use self::BoxType::*; - match t { - $($(#[$attr])* $boxtype => $boxenum),*, - _ => UnknownBox(t), - } - } - } - - impl From<BoxType> for u32 { - fn from(b: BoxType) -> u32 { - use self::BoxType::*; - match b { - $($(#[$attr])* $boxenum => $boxtype),*, - UnknownBox(t) => t, - } - } - } - - } -} - -impl fmt::Debug for BoxType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let fourcc: FourCC = From::from(*self); - fourcc.fmt(f) - } -} - -#[derive(Default, Eq, Hash, PartialEq, Clone)] -pub struct FourCC { - pub value: [u8; 4], -} - -impl From<u32> for FourCC { - fn from(number: u32) -> FourCC { - FourCC { - value: number.to_be_bytes(), - } - } -} - -impl From<BoxType> for FourCC { - fn from(t: BoxType) -> FourCC { - let box_num: u32 = Into::into(t); - From::from(box_num) - } -} - -impl From<[u8; 4]> for FourCC { - fn from(v: [u8; 4]) -> FourCC { - FourCC { value: v } - } -} - -impl fmt::Debug for FourCC { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match std::str::from_utf8(&self.value) { - Ok(s) => f.write_str(s), - Err(_) => self.value.fmt(f), - } - } -} - -impl fmt::Display for FourCC { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(std::str::from_utf8(&self.value).unwrap_or("null")) - } -} - -impl PartialEq<&[u8; 4]> for FourCC { - fn eq(&self, other: &&[u8; 4]) -> bool { - self.value.eq(*other) - } -} - -box_database!( - FileTypeBox 0x6674_7970, // "ftyp" - MediaDataBox 0x6d64_6174, // "mdat" - PrimaryItemBox 0x7069_746d, // "pitm" - ItemInfoBox 0x6969_6e66, // "iinf" - ItemInfoEntry 0x696e_6665, // "infe" - ItemLocationBox 0x696c_6f63, // "iloc" - MovieBox 0x6d6f_6f76, // "moov" - MovieHeaderBox 0x6d76_6864, // "mvhd" - TrackBox 0x7472_616b, // "trak" - TrackHeaderBox 0x746b_6864, // "tkhd" - EditBox 0x6564_7473, // "edts" - MediaBox 0x6d64_6961, // "mdia" - EditListBox 0x656c_7374, // "elst" - MediaHeaderBox 0x6d64_6864, // "mdhd" - HandlerBox 0x6864_6c72, // "hdlr" - MediaInformationBox 0x6d69_6e66, // "minf" - ItemReferenceBox 0x6972_6566, // "iref" - ItemPropertiesBox 0x6970_7270, // "iprp" - ItemPropertyContainerBox 0x6970_636f, // "ipco" - ItemPropertyAssociationBox 0x6970_6d61, // "ipma" - ColourInformationBox 0x636f_6c72, // "colr" - ImageSpatialExtentsProperty 0x6973_7065, // "ispe" - PixelInformationBox 0x7069_7869, // "pixi" - AuxiliaryTypeProperty 0x6175_7843, // "auxC" - CleanApertureBox 0x636c_6170, // "clap" - ImageRotation 0x6972_6f74, // "irot" - ImageMirror 0x696d_6972, // "imir" - OperatingPointSelectorProperty 0x6131_6f70, // "a1op" - AV1LayeredImageIndexingProperty 0x6131_6c78, // "a1lx" - LayerSelectorProperty 0x6c73_656c, // "lsel" - SampleTableBox 0x7374_626c, // "stbl" - SampleDescriptionBox 0x7374_7364, // "stsd" - TimeToSampleBox 0x7374_7473, // "stts" - SampleToChunkBox 0x7374_7363, // "stsc" - SampleSizeBox 0x7374_737a, // "stsz" - ChunkOffsetBox 0x7374_636f, // "stco" - ChunkLargeOffsetBox 0x636f_3634, // "co64" - SyncSampleBox 0x7374_7373, // "stss" - AVCSampleEntry 0x6176_6331, // "avc1" - AVC3SampleEntry 0x6176_6333, // "avc3" - Need to check official name in spec. - AVCConfigurationBox 0x6176_6343, // "avcC" - H263SampleEntry 0x7332_3633, // "s263" - H263SpecificBox 0x6432_3633, // "d263" - MP4AudioSampleEntry 0x6d70_3461, // "mp4a" - MP4VideoSampleEntry 0x6d70_3476, // "mp4v" - #[cfg(feature = "3gpp")] - AMRNBSampleEntry 0x7361_6d72, // "samr" - AMR narrow-band - #[cfg(feature = "3gpp")] - AMRWBSampleEntry 0x7361_7762, // "sawb" - AMR wide-band - #[cfg(feature = "3gpp")] - AMRSpecificBox 0x6461_6d72, // "damr" - ESDBox 0x6573_6473, // "esds" - VP8SampleEntry 0x7670_3038, // "vp08" - VP9SampleEntry 0x7670_3039, // "vp09" - VPCodecConfigurationBox 0x7670_6343, // "vpcC" - AV1SampleEntry 0x6176_3031, // "av01" - AV1CodecConfigurationBox 0x6176_3143, // "av1C" - FLACSampleEntry 0x664c_6143, // "fLaC" - FLACSpecificBox 0x6466_4c61, // "dfLa" - OpusSampleEntry 0x4f70_7573, // "Opus" - OpusSpecificBox 0x644f_7073, // "dOps" - ProtectedVisualSampleEntry 0x656e_6376, // "encv" - Need to check official name in spec. - ProtectedAudioSampleEntry 0x656e_6361, // "enca" - Need to check official name in spec. - MovieExtendsBox 0x6d76_6578, // "mvex" - MovieExtendsHeaderBox 0x6d65_6864, // "mehd" - QTWaveAtom 0x7761_7665, // "wave" - quicktime atom - ProtectionSystemSpecificHeaderBox 0x7073_7368, // "pssh" - SchemeInformationBox 0x7363_6869, // "schi" - TrackEncryptionBox 0x7465_6e63, // "tenc" - ProtectionSchemeInfoBox 0x7369_6e66, // "sinf" - OriginalFormatBox 0x6672_6d61, // "frma" - SchemeTypeBox 0x7363_686d, // "schm" - MP3AudioSampleEntry 0x2e6d_7033, // ".mp3" - from F4V. - CompositionOffsetBox 0x6374_7473, // "ctts" - LPCMAudioSampleEntry 0x6c70_636d, // "lpcm" - quicktime atom - ALACSpecificBox 0x616c_6163, // "alac" - Also used by ALACSampleEntry - UuidBox 0x7575_6964, // "uuid" - MetadataBox 0x6d65_7461, // "meta" - MetadataHeaderBox 0x6d68_6472, // "mhdr" - MetadataItemKeysBox 0x6b65_7973, // "keys" - MetadataItemListEntry 0x696c_7374, // "ilst" - MetadataItemDataEntry 0x6461_7461, // "data" - MetadataItemNameBox 0x6e61_6d65, // "name" - #[cfg(feature = "meta-xml")] - MetadataXMLBox 0x786d_6c20, // "xml " - #[cfg(feature = "meta-xml")] - MetadataBXMLBox 0x6278_6d6c, // "bxml" - UserdataBox 0x7564_7461, // "udta" - AlbumEntry 0xa961_6c62, // "©alb" - ArtistEntry 0xa941_5254, // "©ART" - ArtistLowercaseEntry 0xa961_7274, // "©art" - AlbumArtistEntry 0x6141_5254, // "aART" - CommentEntry 0xa963_6d74, // "©cmt" - DateEntry 0xa964_6179, // "©day" - TitleEntry 0xa96e_616d, // "©nam" - CustomGenreEntry 0xa967_656e, // "©gen" - StandardGenreEntry 0x676e_7265, // "gnre" - TrackNumberEntry 0x7472_6b6e, // "trkn" - DiskNumberEntry 0x6469_736b, // "disk" - ComposerEntry 0xa977_7274, // "©wrt" - EncoderEntry 0xa974_6f6f, // "©too" - EncodedByEntry 0xa965_6e63, // "©enc" - TempoEntry 0x746d_706f, // "tmpo" - CopyrightEntry 0x6370_7274, // "cprt" - CompilationEntry 0x6370_696c, // "cpil" - CoverArtEntry 0x636f_7672, // "covr" - AdvisoryEntry 0x7274_6e67, // "rtng" - RatingEntry 0x7261_7465, // "rate" - GroupingEntry 0xa967_7270, // "©grp" - MediaTypeEntry 0x7374_696b, // "stik" - PodcastEntry 0x7063_7374, // "pcst" - CategoryEntry 0x6361_7467, // "catg" - KeywordEntry 0x6b65_7977, // "keyw" - PodcastUrlEntry 0x7075_726c, // "purl" - PodcastGuidEntry 0x6567_6964, // "egid" - DescriptionEntry 0x6465_7363, // "desc" - LongDescriptionEntry 0x6c64_6573, // "ldes" - LyricsEntry 0xa96c_7972, // "©lyr" - TVNetworkNameEntry 0x7476_6e6e, // "tvnn" - TVShowNameEntry 0x7476_7368, // "tvsh" - TVEpisodeNameEntry 0x7476_656e, // "tven" - TVSeasonNumberEntry 0x7476_736e, // "tvsn" - TVEpisodeNumberEntry 0x7476_6573, // "tves" - PurchaseDateEntry 0x7075_7264, // "purd" - GaplessPlaybackEntry 0x7067_6170, // "pgap" - OwnerEntry 0x6f77_6e72, // "ownr" - HDVideoEntry 0x6864_7664, // "hdvd" - SortNameEntry 0x736f_6e6d, // "sonm" - SortAlbumEntry 0x736f_616c, // "soal" - SortArtistEntry 0x736f_6172, // "soar" - SortAlbumArtistEntry 0x736f_6161, // "soaa" - SortComposerEntry 0x736f_636f, // "soco" - - QTJPEGAtom 0x4a50_4547, // "JPEG" - FreeBox 0x6672_6565, // "free" - - // All the Canon CR3 stuff. - CanonCRAWEntry 0x4352_4157, // "CRAW" - CanonMetadataEntry 0x4354_4d44, // "CTMD" - CanonCMP1 0x434d_5031, // "CMP1" - CanonCDI1 0x4344_4931, // "CDI1" - CanonPreview 0x5052_5657, // "PRVW" - CanonCompressorVersion 0x434e_4356, // "CNCV" - CanonThumbnail 0x5448_4d42, // "THMB" - CanonTableOffset 0x4354_424f, // "CTBO" - CanonMeta1 0x434d_5431, // "CMT1" - CanonMeta2 0x434d_5432, // "CMT2" - CanonMeta3 0x434d_5433, // "CMT3" - CanonMeta4 0x434d_5434, // "CMT4" -); diff --git a/lib/mp4/mp4parse/src/craw.rs b/lib/mp4/mp4parse/src/craw.rs deleted file mode 100644 index 5a9436a..0000000 --- a/lib/mp4/mp4parse/src/craw.rs +++ /dev/null @@ -1,154 +0,0 @@ -//! Module for parsing Canon CR3 files that are ISO Base Media Format -//! aka video/mp4 streams. - -// 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 super::{ - be_u16, be_u32, be_u64, read_buf, skip, skip_box_content, skip_box_remain, BMFFBox, Error, -}; -use boxes::BoxType; -use std::io::Read; - -pub const HEADER_UUID: [u8; 16] = [ - 0x85, 0xc0, 0xb6, 0x87, 0x82, 0x0f, 0x11, 0xe0, 0x81, 0x11, 0xf4, 0xce, 0x46, 0x2b, 0x6a, 0x48, -]; - -#[allow(dead_code)] -pub const XPACKET_UUID: [u8; 16] = [ - 0xbe, 0x7a, 0xcf, 0xcb, 0x97, 0xa9, 0x42, 0xe8, 0x9c, 0x71, 0x99, 0x94, 0x91, 0xe3, 0xaf, 0xac, -]; - -#[allow(dead_code)] -pub const PREVIEW_UUID: [u8; 16] = [ - 0xea, 0xf4, 0x2b, 0x5e, 0x1c, 0x98, 0x4b, 0x88, 0xb9, 0xfb, 0xb7, 0xdc, 0x40, 0x6e, 0x4d, 0x16, -]; - -/// Canon Thumbnail -#[derive(Debug, Default)] -pub struct CanonThumbnail { - pub width: u16, - pub height: u16, - pub data: Vec<u8>, -} - -/// Canon CRAW data ('crx ' brand files) -#[derive(Debug, Default)] -pub struct CrawHeader { - pub cncv: Vec<u8>, - pub offsets: Vec<(u64, u64)>, - pub meta1: Option<Vec<u8>>, - pub meta2: Option<Vec<u8>>, - pub meta3: Option<Vec<u8>>, - pub meta4: Option<Vec<u8>>, - pub thumbnail: CanonThumbnail, -} - -#[derive(Debug, Clone)] -pub struct CanonCRAWEntry { - pub data_reference_index: u16, - pub width: u16, - pub height: u16, - pub is_jpeg: bool, -} - -/// Parse the CRAW entry inside the video sample entry. -pub(crate) fn read_craw_entry<T: Read>( - src: &mut BMFFBox<T>, - width: u16, - height: u16, - data_reference_index: u16, -) -> super::Result<super::SampleEntry> { - skip(src, 54)?; - let mut is_jpeg = false; - { - let mut iter = src.box_iter(); - while let Some(mut b) = iter.next_box()? { - debug!("Box size {}", b.head.size); - match b.head.name { - BoxType::QTJPEGAtom => { - is_jpeg = true; - } - BoxType::CanonCMP1 => {} - _ => { - debug!("Unsupported box '{:?}' in CRAW", b.head.name); - } - } - skip_box_remain(&mut b)?; - } - } - skip_box_remain(src)?; - check_parser_state!(src.content); - - Ok(super::SampleEntry::CanonCRAW(CanonCRAWEntry { - data_reference_index, - width, - height, - is_jpeg, - })) -} - -pub(crate) fn parse_craw_header<T: Read>(f: &mut BMFFBox<T>) -> super::Result<CrawHeader> { - let mut header = CrawHeader::default(); - let mut iter = f.box_iter(); - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::CanonCompressorVersion => { - let size = b.head.size - b.head.offset; - let data = read_buf(&mut b, size)?; - header.cncv = data.to_vec(); - skip_box_remain(&mut b)?; - } - BoxType::CanonTableOffset => { - let count = be_u32(&mut b)?; - for _i in 0..count { - skip(&mut b, 4)?; // index. We do not care. - let offset = be_u64(&mut b)?; - let size = be_u64(&mut b)?; - if offset == 0 || size == 0 { - break; - } - header.offsets.push((offset, size)); - } - skip_box_remain(&mut b)?; - } - BoxType::CanonMeta1 - | BoxType::CanonMeta2 - | BoxType::CanonMeta3 - | BoxType::CanonMeta4 => { - let len = b.head.size - b.head.offset; - let data = read_buf(&mut b, len)?; - let data = data.to_vec(); - match b.head.name { - BoxType::CanonMeta1 => header.meta1 = Some(data), - BoxType::CanonMeta2 => header.meta2 = Some(data), - BoxType::CanonMeta3 => header.meta3 = Some(data), - BoxType::CanonMeta4 => header.meta4 = Some(data), - _ => unreachable!(), - } - } - BoxType::CanonThumbnail => { - skip(&mut b, 4)?; - let width = be_u16(&mut b)?; - let height = be_u16(&mut b)?; - let jpeg_size = be_u32(&mut b)?; - skip(&mut b, 4)?; - if (jpeg_size as u64) + b.head.offset + 16u64 > b.head.size { - return Err(Error::InvalidData("short box size for JPEG data")); - } - let data = read_buf(&mut b, jpeg_size as u64)?; - header.thumbnail = CanonThumbnail { - width, - height, - data: data.to_vec(), - }; - skip_box_remain(&mut b)?; - } - _ => skip_box_content(&mut b)?, - } - } - - debug!("{:?}", header); - Ok(header) -} diff --git a/lib/mp4/mp4parse/src/lib.rs b/lib/mp4/mp4parse/src/lib.rs deleted file mode 100644 index e5516bc..0000000 --- a/lib/mp4/mp4parse/src/lib.rs +++ /dev/null @@ -1,5320 +0,0 @@ -//! Module for parsing ISO Base Media Format aka video/mp4 streams. - -// 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/. - -// `clippy::upper_case_acronyms` is a nightly-only lint as of 2021-03-15, so we -// allow `clippy::unknown_clippy_lints` to ignore it on stable - but -// `clippy::unknown_clippy_lints` has been renamed in nightly, so we need to -// allow `renamed_and_removed_lints` to ignore a warning for that. -#![allow(renamed_and_removed_lints)] -#![allow(clippy::unknown_clippy_lints)] -#![allow(clippy::upper_case_acronyms)] - -#[macro_use] -extern crate log; - -extern crate bitreader; -extern crate byteorder; -extern crate fallible_collections; -extern crate num_traits; -use bitreader::{BitReader, ReadInto}; -use byteorder::{ReadBytesExt, WriteBytesExt}; - -use fallible_collections::TryRead; -use fallible_collections::TryReserveError; - -use num_traits::Num; -use std::convert::{TryFrom, TryInto as _}; -use std::fmt; -use std::io::Cursor; -use std::io::{Read, Take}; - -#[macro_use] -mod macros; - -mod boxes; -use boxes::{BoxType, FourCC}; - -#[cfg(feature = "craw")] -pub mod craw; - -// Unit tests. -#[cfg(test)] -mod tests; - -#[cfg(feature = "unstable-api")] -pub mod unstable; - -/// The 'mif1' brand indicates structural requirements on files -/// See HEIF (ISO 23008-12:2017) § 10.2.1 -const MIF1_BRAND: FourCC = FourCC { value: *b"mif1" }; - -/// A trait to indicate a type can be infallibly converted to `u64`. -/// This should only be implemented for infallible conversions, so only unsigned types are valid. -trait ToU64 { - // Remove when https://github.com/rust-lang/rust-clippy/issues/6727 is resolved - #[allow(clippy::wrong_self_convention)] - fn to_u64(self) -> u64; -} - -/// Statically verify that the platform `usize` can fit within a `u64`. -/// If the size won't fit on the given platform, this will fail at compile time, but if a type -/// which can fail TryInto<usize> is used, it may panic. -impl ToU64 for usize { - fn to_u64(self) -> u64 { - static_assertions::const_assert!( - std::mem::size_of::<usize>() <= std::mem::size_of::<u64>() - ); - self.try_into().expect("usize -> u64 conversion failed") - } -} - -/// A trait to indicate a type can be infallibly converted to `usize`. -/// This should only be implemented for infallible conversions, so only unsigned types are valid. -pub trait ToUsize { - fn to_usize(self) -> usize; -} - -/// Statically verify that the given type can fit within a `usize`. -/// If the size won't fit on the given platform, this will fail at compile time, but if a type -/// which can fail TryInto<usize> is used, it may panic. -macro_rules! impl_to_usize_from { - ( $from_type:ty ) => { - impl ToUsize for $from_type { - fn to_usize(self) -> usize { - static_assertions::const_assert!( - std::mem::size_of::<$from_type>() <= std::mem::size_of::<usize>() - ); - self.try_into().expect(concat!( - stringify!($from_type), - " -> usize conversion failed" - )) - } - } - }; -} - -impl_to_usize_from!(u8); -impl_to_usize_from!(u16); -impl_to_usize_from!(u32); - -/// Indicate the current offset (i.e., bytes already read) in a reader -pub trait Offset { - fn offset(&self) -> u64; -} - -/// Wraps a reader to track the current offset -struct OffsetReader<'a, T: 'a> { - reader: &'a mut T, - offset: u64, -} - -impl<'a, T> OffsetReader<'a, T> { - fn new(reader: &'a mut T) -> Self { - Self { reader, offset: 0 } - } -} - -impl<'a, T> Offset for OffsetReader<'a, T> { - fn offset(&self) -> u64 { - self.offset - } -} - -impl<'a, T: Read> Read for OffsetReader<'a, T> { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { - let bytes_read = self.reader.read(buf)?; - self.offset = self - .offset - .checked_add(bytes_read.to_u64()) - .expect("total bytes read too large for offset type"); - Ok(bytes_read) - } -} - -pub type TryVec<T> = fallible_collections::TryVec<T>; -pub type TryString = fallible_collections::TryVec<u8>; -pub type TryHashMap<K, V> = fallible_collections::TryHashMap<K, V>; -pub type TryBox<T> = fallible_collections::TryBox<T>; - -// To ensure we don't use stdlib allocating types by accident -#[allow(dead_code)] -struct Vec; -#[allow(dead_code)] -struct Box; -#[allow(dead_code)] -struct HashMap; -#[allow(dead_code)] -struct String; - -/// The return value to the C API -/// Any detail that needs to be communicated to the caller must be encoded here -/// since the [`Error`] type's associated data is part of the FFI. -#[repr(C)] -#[derive(PartialEq, Debug)] -pub enum Status { - Ok = 0, - BadArg = 1, - Invalid = 2, - Unsupported = 3, - Eof = 4, - Io = 5, - Oom = 6, - UnsupportedA1lx, - UnsupportedA1op, - UnsupportedClap, - UnsupportedGrid, - UnsupportedIpro, - UnsupportedLsel, -} - -/// For convenience of creating an error for an unsupported feature which we -/// want to communicate the specific feature back to the C API caller -impl From<Status> for Error { - fn from(parse_status: Status) -> Self { - let msg = match parse_status { - Status::Ok - | Status::BadArg - | Status::Invalid - | Status::Unsupported - | Status::Eof - | Status::Io - | Status::Oom => { - panic!("Status -> Error is only for Status:UnsupportedXXX errors") - } - - Status::UnsupportedA1lx => "AV1 layered image indexing (a1lx) is unsupported", - Status::UnsupportedA1op => "Operating point selection (a1op) is unsupported", - Status::UnsupportedClap => "Clean aperture (clap) transform is unsupported", - Status::UnsupportedGrid => "Grid-based images are unsupported", - Status::UnsupportedIpro => "Item protection (ipro) is unsupported", - Status::UnsupportedLsel => "Layer selection (lsel) is unsupported", - }; - Self::UnsupportedDetail(parse_status, msg) - } -} - -impl From<Error> for Status { - fn from(error: Error) -> Self { - match error { - Error::NoMoov | Error::InvalidData(_) => Self::Invalid, - Error::Unsupported(_) => Self::Unsupported, - Error::UnsupportedDetail(parse_status, _msg) => parse_status, - Error::UnexpectedEOF => Self::Eof, - Error::Io(_) => { - // Getting std::io::ErrorKind::UnexpectedEof is normal - // but our From trait implementation should have converted - // those to our Error::UnexpectedEOF variant. - Self::Io - } - Error::OutOfMemory => Self::Oom, - } - } -} - -impl From<Result<(), Status>> for Status { - fn from(result: Result<(), Status>) -> Self { - match result { - Ok(()) => Status::Ok, - Err(Status::Ok) => unreachable!(), - Err(e) => e, - } - } -} - -impl From<fallible_collections::TryReserveError> for Status { - fn from(_: fallible_collections::TryReserveError) -> Self { - Status::Oom - } -} - -/// Describes parser failures. -/// -/// This enum wraps the standard `io::Error` type, unified with -/// our own parser error states and those of crates we use. -#[derive(Debug)] -pub enum Error { - /// Parse error caused by corrupt or malformed data. - InvalidData(&'static str), - /// Parse error caused by limited parser support rather than invalid data. - Unsupported(&'static str), - /// Similar to [`Self::Unsupported`], but for errors that have a specific - /// [`Status`] variant for communicating the detail across FFI. - /// See the helper [`From<Status> for Error`](enum.Error.html#impl-From<Status>) - UnsupportedDetail(Status, &'static str), - /// Reflect `std::io::ErrorKind::UnexpectedEof` for short data. - UnexpectedEOF, - /// Propagate underlying errors from `std::io`. - Io(std::io::Error), - /// read_mp4 terminated without detecting a moov box. - NoMoov, - /// Out of memory - OutOfMemory, -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - -impl std::error::Error for Error {} - -impl From<bitreader::BitReaderError> for Error { - fn from(_: bitreader::BitReaderError) -> Error { - Error::InvalidData("invalid data") - } -} - -impl From<std::io::Error> for Error { - fn from(err: std::io::Error) -> Error { - match err.kind() { - std::io::ErrorKind::UnexpectedEof => Error::UnexpectedEOF, - _ => Error::Io(err), - } - } -} - -impl From<std::string::FromUtf8Error> for Error { - fn from(_: std::string::FromUtf8Error) -> Error { - Error::InvalidData("invalid utf8") - } -} - -impl From<std::str::Utf8Error> for Error { - fn from(_: std::str::Utf8Error) -> Error { - Error::InvalidData("invalid utf8") - } -} - -impl From<std::num::TryFromIntError> for Error { - fn from(_: std::num::TryFromIntError) -> Error { - Error::Unsupported("integer conversion failed") - } -} - -impl From<Error> for std::io::Error { - fn from(err: Error) -> Self { - let kind = match err { - Error::InvalidData(_) => std::io::ErrorKind::InvalidData, - Error::UnexpectedEOF => std::io::ErrorKind::UnexpectedEof, - Error::Io(io_err) => return io_err, - _ => std::io::ErrorKind::Other, - }; - Self::new(kind, err) - } -} - -impl From<TryReserveError> for Error { - fn from(_: TryReserveError) -> Error { - Error::OutOfMemory - } -} - -/// Result shorthand using our Error enum. -pub type Result<T, E = Error> = std::result::Result<T, E>; - -/// Basic ISO box structure. -/// -/// mp4 files are a sequence of possibly-nested 'box' structures. Each box -/// begins with a header describing the length of the box's data and a -/// four-byte box type which identifies the type of the box. Together these -/// are enough to interpret the contents of that section of the file. -/// -/// See ISOBMFF (ISO 14496-12:2020) § 4.2 -#[derive(Debug, Clone, Copy)] -struct BoxHeader { - /// Box type. - name: BoxType, - /// Size of the box in bytes. - size: u64, - /// Offset to the start of the contained data (or header size). - offset: u64, - /// Uuid for extended type. - #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 - uuid: Option<[u8; 16]>, -} - -impl BoxHeader { - const MIN_SIZE: u64 = 8; // 4-byte size + 4-byte type - const MIN_LARGE_SIZE: u64 = 16; // 4-byte size + 4-byte type + 16-byte size -} - -/// File type box 'ftyp'. -#[derive(Debug)] -struct FileTypeBox { - #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 - major_brand: FourCC, - #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 - minor_version: u32, - compatible_brands: TryVec<FourCC>, -} - -/// Movie header box 'mvhd'. -#[derive(Debug)] -struct MovieHeaderBox { - pub timescale: u32, - #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 - duration: u64, -} - -#[derive(Debug, Clone, Copy)] -pub struct Matrix { - pub a: i32, // 16.16 fix point - pub b: i32, // 16.16 fix point - pub u: i32, // 2.30 fix point - pub c: i32, // 16.16 fix point - pub d: i32, // 16.16 fix point - pub v: i32, // 2.30 fix point - pub x: i32, // 16.16 fix point - pub y: i32, // 16.16 fix point - pub w: i32, // 2.30 fix point -} - -/// Track header box 'tkhd' -#[derive(Debug, Clone)] -pub struct TrackHeaderBox { - track_id: u32, - pub disabled: bool, - pub duration: u64, - pub width: u32, - pub height: u32, - pub matrix: Matrix, -} - -/// Edit list box 'elst' -#[derive(Debug)] -struct EditListBox { - edits: TryVec<Edit>, -} - -#[derive(Debug)] -struct Edit { - segment_duration: u64, - media_time: i64, - #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 - media_rate_integer: i16, - #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 - media_rate_fraction: i16, -} - -/// Media header box 'mdhd' -#[derive(Debug)] -struct MediaHeaderBox { - timescale: u32, - duration: u64, -} - -// Chunk offset box 'stco' or 'co64' -#[derive(Debug)] -pub struct ChunkOffsetBox { - pub offsets: TryVec<u64>, -} - -// Sync sample box 'stss' -#[derive(Debug)] -pub struct SyncSampleBox { - pub samples: TryVec<u32>, -} - -// Sample to chunk box 'stsc' -#[derive(Debug)] -pub struct SampleToChunkBox { - pub samples: TryVec<SampleToChunk>, -} - -#[derive(Debug)] -pub struct SampleToChunk { - pub first_chunk: u32, - pub samples_per_chunk: u32, - pub sample_description_index: u32, -} - -// Sample size box 'stsz' -#[derive(Debug)] -pub struct SampleSizeBox { - pub sample_size: u32, - pub sample_sizes: TryVec<u32>, -} - -// Time to sample box 'stts' -#[derive(Debug)] -pub struct TimeToSampleBox { - pub samples: TryVec<Sample>, -} - -#[repr(C)] -#[derive(Debug)] -pub struct Sample { - pub sample_count: u32, - pub sample_delta: u32, -} - -#[derive(Debug, Clone, Copy)] -pub enum TimeOffsetVersion { - Version0(u32), - Version1(i32), -} - -#[derive(Debug, Clone)] -pub struct TimeOffset { - pub sample_count: u32, - pub time_offset: TimeOffsetVersion, -} - -#[derive(Debug)] -pub struct CompositionOffsetBox { - pub samples: TryVec<TimeOffset>, -} - -// Handler reference box 'hdlr' -#[derive(Debug)] -struct HandlerBox { - handler_type: FourCC, -} - -// Sample description box 'stsd' -#[derive(Debug)] -pub struct SampleDescriptionBox { - pub descriptions: TryVec<SampleEntry>, -} - -#[derive(Debug)] -pub enum SampleEntry { - Audio(AudioSampleEntry), - Video(VideoSampleEntry), - #[cfg(feature = "craw")] - CanonCRAW(craw::CanonCRAWEntry), - Unknown, -} - -/// An Elementary Stream Descriptor -/// See MPEG-4 Systems (ISO 14496-1:2010) § 7.2.6.5 -#[allow(non_camel_case_types)] -#[derive(Debug, Default)] -pub struct ES_Descriptor { - pub audio_codec: CodecType, - pub audio_object_type: Option<u16>, - pub extended_audio_object_type: Option<u16>, - pub audio_sample_rate: Option<u32>, - pub audio_channel_count: Option<u16>, - #[cfg(feature = "mp4v")] - pub video_codec: CodecType, - pub codec_esds: TryVec<u8>, - pub decoder_specific_data: TryVec<u8>, // Data in DECODER_SPECIFIC_TAG -} - -#[allow(non_camel_case_types)] -#[derive(Debug)] -pub enum AudioCodecSpecific { - ES_Descriptor(ES_Descriptor), - FLACSpecificBox(FLACSpecificBox), - OpusSpecificBox(OpusSpecificBox), - ALACSpecificBox(ALACSpecificBox), - MP3, - LPCM, - #[cfg(feature = "3gpp")] - AMRSpecificBox(TryVec<u8>), -} - -#[derive(Debug)] -pub struct AudioSampleEntry { - pub codec_type: CodecType, - #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 - data_reference_index: u16, - pub channelcount: u32, - pub samplesize: u16, - pub samplerate: f64, - pub codec_specific: AudioCodecSpecific, - pub protection_info: TryVec<ProtectionSchemeInfoBox>, -} - -#[derive(Debug)] -pub enum VideoCodecSpecific { - AVCConfig(TryVec<u8>), - VPxConfig(VPxConfigBox), - AV1Config(AV1ConfigBox), - ESDSConfig(TryVec<u8>), - H263Config(TryVec<u8>), -} - -#[derive(Debug)] -pub struct VideoSampleEntry { - pub codec_type: CodecType, - #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 - data_reference_index: u16, - pub width: u16, - pub height: u16, - pub codec_specific: VideoCodecSpecific, - pub protection_info: TryVec<ProtectionSchemeInfoBox>, -} - -/// Represent a Video Partition Codec Configuration 'vpcC' box (aka vp9). The meaning of each -/// field is covered in detail in "VP Codec ISO Media File Format Binding". -#[derive(Debug)] -pub struct VPxConfigBox { - /// An integer that specifies the VP codec profile. - #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 - profile: u8, - /// An integer that specifies a VP codec level all samples conform to the following table. - /// For a description of the various levels, please refer to the VP9 Bitstream Specification. - #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 - level: u8, - /// An integer that specifies the bit depth of the luma and color components. Valid values - /// are 8, 10, and 12. - pub bit_depth: u8, - /// Really an enum defined by the "Colour primaries" section of ISO 23091-2:2019 § 8.1. - pub colour_primaries: u8, - /// Really an enum defined by "VP Codec ISO Media File Format Binding". - pub chroma_subsampling: u8, - /// Really an enum defined by the "Transfer characteristics" section of ISO 23091-2:2019 § 8.2. - #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 - transfer_characteristics: u8, - /// Really an enum defined by the "Matrix coefficients" section of ISO 23091-2:2019 § 8.3. - /// Available in 'VP Codec ISO Media File Format' version 1 only. - #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 - matrix_coefficients: Option<u8>, - /// Indicates the black level and range of the luma and chroma signals. 0 = legal range - /// (e.g. 16-235 for 8 bit sample depth); 1 = full range (e.g. 0-255 for 8-bit sample depth). - #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 - video_full_range_flag: bool, - /// This is not used for VP8 and VP9 . Intended for binary codec initialization data. - pub codec_init: TryVec<u8>, -} - -/// See [AV1-ISOBMFF § 2.3.3](https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax) -#[derive(Debug)] -pub struct AV1ConfigBox { - pub profile: u8, - pub level: u8, - pub tier: u8, - pub bit_depth: u8, - pub monochrome: bool, - pub chroma_subsampling_x: u8, - pub chroma_subsampling_y: u8, - pub chroma_sample_position: u8, - pub initial_presentation_delay_present: bool, - pub initial_presentation_delay_minus_one: u8, - // The raw config contained in the av1c box. Because some decoders accept this data as a binary - // blob, rather than as structured data, we store the blob here for convenience. - pub raw_config: TryVec<u8>, -} - -impl AV1ConfigBox { - const CONFIG_OBUS_OFFSET: usize = 4; - - pub fn config_obus(&self) -> &[u8] { - &self.raw_config[Self::CONFIG_OBUS_OFFSET..] - } -} - -#[derive(Debug)] -pub struct FLACMetadataBlock { - pub block_type: u8, - pub data: TryVec<u8>, -} - -/// Represents a FLACSpecificBox 'dfLa' -#[derive(Debug)] -pub struct FLACSpecificBox { - #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 - version: u8, - pub blocks: TryVec<FLACMetadataBlock>, -} - -#[derive(Debug)] -struct ChannelMappingTable { - stream_count: u8, - coupled_count: u8, - channel_mapping: TryVec<u8>, -} - -/// Represent an OpusSpecificBox 'dOps' -#[derive(Debug)] -pub struct OpusSpecificBox { - pub version: u8, - output_channel_count: u8, - pre_skip: u16, - input_sample_rate: u32, - output_gain: i16, - channel_mapping_family: u8, - channel_mapping_table: Option<ChannelMappingTable>, -} - -/// Represent an ALACSpecificBox 'alac' -#[derive(Debug)] -pub struct ALACSpecificBox { - #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 - version: u8, - pub data: TryVec<u8>, -} - -#[derive(Debug)] -pub struct MovieExtendsBox { - pub fragment_duration: Option<MediaScaledTime>, -} - -pub type ByteData = TryVec<u8>; - -#[derive(Debug, Default)] -pub struct ProtectionSystemSpecificHeaderBox { - pub system_id: ByteData, - pub kid: TryVec<ByteData>, - pub data: ByteData, - - // The entire pssh box (include header) required by Gecko. - pub box_content: ByteData, -} - -#[derive(Debug, Default, Clone)] -pub struct SchemeTypeBox { - pub scheme_type: FourCC, - pub scheme_version: u32, -} - -#[derive(Debug, Default)] -pub struct TrackEncryptionBox { - pub is_encrypted: u8, - pub iv_size: u8, - pub kid: TryVec<u8>, - // Members for pattern encryption schemes - pub crypt_byte_block_count: Option<u8>, - pub skip_byte_block_count: Option<u8>, - pub constant_iv: Option<TryVec<u8>>, - // End pattern encryption scheme members -} - -#[derive(Debug, Default)] -pub struct ProtectionSchemeInfoBox { - pub original_format: FourCC, - pub scheme_type: Option<SchemeTypeBox>, - pub tenc: Option<TrackEncryptionBox>, -} - -/// Represents a userdata box 'udta'. -/// Currently, only the metadata atom 'meta' -/// is parsed. -#[derive(Debug, Default)] -pub struct UserdataBox { - pub meta: Option<MetadataBox>, -} - -/// Represents possible contents of the -/// ©gen or gnre atoms within a metadata box. -/// 'udta.meta.ilst' may only have either a -/// standard genre box 'gnre' or a custom -/// genre box '©gen', but never both at once. -#[derive(Debug, PartialEq)] -pub enum Genre { - /// A standard ID3v1 numbered genre. - StandardGenre(u8), - /// Any custom genre string. - CustomGenre(TryString), -} - -/// Represents the contents of a 'stik' -/// atom that indicates content types within -/// iTunes. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum MediaType { - /// Movie is stored as 0 in a 'stik' atom. - Movie, // 0 - /// Normal is stored as 1 in a 'stik' atom. - Normal, // 1 - /// AudioBook is stored as 2 in a 'stik' atom. - AudioBook, // 2 - /// WhackedBookmark is stored as 5 in a 'stik' atom. - WhackedBookmark, // 5 - /// MusicVideo is stored as 6 in a 'stik' atom. - MusicVideo, // 6 - /// ShortFilm is stored as 9 in a 'stik' atom. - ShortFilm, // 9 - /// TVShow is stored as 10 in a 'stik' atom. - TVShow, // 10 - /// Booklet is stored as 11 in a 'stik' atom. - Booklet, // 11 - /// An unknown 'stik' value. - Unknown(u8), -} - -/// Represents the parental advisory rating on the track, -/// stored within the 'rtng' atom. -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum AdvisoryRating { - /// Clean is always stored as 2 in an 'rtng' atom. - Clean, // 2 - /// A value of 0 in an 'rtng' atom indicates 'Inoffensive' - Inoffensive, // 0 - /// Any non 2 or 0 value in 'rtng' indicates the track is explicit. - Explicit(u8), -} - -/// Represents the contents of 'ilst' atoms within -/// a metadata box 'meta', parsed as iTunes metadata using -/// the conventional tags. -#[derive(Debug, Default)] -pub struct MetadataBox { - /// The album name, '©alb' - pub album: Option<TryString>, - /// The artist name '©art' or '©ART' - pub artist: Option<TryString>, - /// The album artist 'aART' - pub album_artist: Option<TryString>, - /// Track comments '©cmt' - pub comment: Option<TryString>, - /// The date or year field '©day' - /// - /// This is stored as an arbitrary string, - /// and may not necessarily be in a valid date - /// format. - pub year: Option<TryString>, - /// The track title '©nam' - pub title: Option<TryString>, - /// The track genre '©gen' or 'gnre'. - pub genre: Option<Genre>, - /// The track number 'trkn'. - pub track_number: Option<u8>, - /// The disc number 'disk' - pub disc_number: Option<u8>, - /// The total number of tracks on the disc, - /// stored in 'trkn' - pub total_tracks: Option<u8>, - /// The total number of discs in the album, - /// stored in 'disk' - pub total_discs: Option<u8>, - /// The composer of the track '©wrt' - pub composer: Option<TryString>, - /// The encoder used to create this track '©too' - pub encoder: Option<TryString>, - /// The encoded-by settingo this track '©enc' - pub encoded_by: Option<TryString>, - /// The tempo or BPM of the track 'tmpo' - pub beats_per_minute: Option<u8>, - /// Copyright information of the track 'cprt' - pub copyright: Option<TryString>, - /// Whether or not this track is part of a compilation 'cpil' - pub compilation: Option<bool>, - /// The advisory rating of this track 'rtng' - pub advisory: Option<AdvisoryRating>, - /// The personal rating of this track, 'rate'. - /// - /// This is stored in the box as string data, but - /// the format is an integer percentage from 0 - 100, - /// where 100 is displayed as 5 stars out of 5. - pub rating: Option<TryString>, - /// The grouping this track belongs to '©grp' - pub grouping: Option<TryString>, - /// The media type of this track 'stik' - pub media_type: Option<MediaType>, // stik - /// Whether or not this track is a podcast 'pcst' - pub podcast: Option<bool>, - /// The category of ths track 'catg' - pub category: Option<TryString>, - /// The podcast keyword 'keyw' - pub keyword: Option<TryString>, - /// The podcast url 'purl' - pub podcast_url: Option<TryString>, - /// The podcast episode GUID 'egid' - pub podcast_guid: Option<TryString>, - /// The description of the track 'desc' - pub description: Option<TryString>, - /// The long description of the track 'ldes'. - /// - /// Unlike other string fields, the long description field - /// can be longer than 256 characters. - pub long_description: Option<TryString>, - /// The lyrics of the track '©lyr'. - /// - /// Unlike other string fields, the lyrics field - /// can be longer than 256 characters. - pub lyrics: Option<TryString>, - /// The name of the TV network this track aired on 'tvnn'. - pub tv_network_name: Option<TryString>, - /// The name of the TV Show for this track 'tvsh'. - pub tv_show_name: Option<TryString>, - /// The name of the TV Episode for this track 'tven'. - pub tv_episode_name: Option<TryString>, - /// The number of the TV Episode for this track 'tves'. - pub tv_episode_number: Option<u8>, - /// The season of the TV Episode of this track 'tvsn'. - pub tv_season: Option<u8>, - /// The date this track was purchased 'purd'. - pub purchase_date: Option<TryString>, - /// Whether or not this track supports gapless playback 'pgap' - pub gapless_playback: Option<bool>, - /// Any cover artwork attached to this track 'covr' - /// - /// 'covr' is unique in that it may contain multiple 'data' sub-entries, - /// each an image file. Here, each subentry's raw binary data is exposed, - /// which may contain image data in JPEG or PNG format. - pub cover_art: Option<TryVec<TryVec<u8>>>, - /// The owner of the track 'ownr' - pub owner: Option<TryString>, - /// Whether or not this track is HD Video 'hdvd' - pub hd_video: Option<bool>, - /// The name of the track to sort by 'sonm' - pub sort_name: Option<TryString>, - /// The name of the album to sort by 'soal' - pub sort_album: Option<TryString>, - /// The name of the artist to sort by 'soar' - pub sort_artist: Option<TryString>, - /// The name of the album artist to sort by 'soaa' - pub sort_album_artist: Option<TryString>, - /// The name of the composer to sort by 'soco' - pub sort_composer: Option<TryString>, - /// Metadata - #[cfg(feature = "meta-xml")] - pub xml: Option<XmlBox>, -} - -/// See ISOBMFF (ISO 14496-12:2020) § 8.11.2.1 -#[cfg(feature = "meta-xml")] -#[derive(Debug)] -pub enum XmlBox { - /// XML metadata - StringXmlBox(TryString), - /// Binary XML metadata - BinaryXmlBox(TryVec<u8>), -} - -/// Internal data structures. -#[derive(Debug, Default)] -pub struct MediaContext { - pub brand: FourCC, - pub timescale: Option<MediaTimeScale>, - /// Tracks found in the file. - pub tracks: TryVec<Track>, - pub mvex: Option<MovieExtendsBox>, - pub psshs: TryVec<ProtectionSystemSpecificHeaderBox>, - pub userdata: Option<Result<UserdataBox>>, - #[cfg(feature = "meta-xml")] - pub metadata: Option<Result<MetadataBox>>, - #[cfg(feature = "craw")] - pub craw: Option<craw::CrawHeader>, -} - -/// An ISOBMFF item as described by an iloc box. For the sake of avoiding copies, -/// this can either be represented by the `Location` variant, which indicates -/// where the data exists within a `MediaDataBox` stored separately, or the -/// `Data` variant which owns the data. Unfortunately, it's not simple to -/// represent this as a [`std::borrow::Cow`], or other reference-based type, because -/// multiple instances may references different parts of the same [`MediaDataBox`] -/// and we want to avoid the copy that splitting the storage would entail. -#[derive(Debug)] -enum IsobmffItem { - Location(Extent), - Data(TryVec<u8>), -} - -#[derive(Debug)] -struct AvifItem { - /// The `item_ID` from ISOBMFF (ISO 14496-12:2020) § 8.11.3 - /// - /// See [`read_iloc`] - id: ItemId, - - /// AV1 Image Item per <https://aomediacodec.github.io/av1-avif/#image-item> - image_data: IsobmffItem, -} - -impl AvifItem { - fn with_data_location(id: ItemId, extent: Extent) -> Self { - Self { - id, - image_data: IsobmffItem::Location(extent), - } - } - - fn with_inline_data(id: ItemId) -> Self { - Self { - id, - image_data: IsobmffItem::Data(TryVec::new()), - } - } -} - -#[derive(Debug)] -pub struct AvifContext { - /// Level of deviation from the specification before failing the parse - strictness: ParseStrictness, - /// Referred to by the `Location` variants of the `AvifItem`s in this struct - item_storage: TryVec<MediaDataBox>, - /// The item indicated by the `pitm` box, See ISOBMFF (ISO 14496-12:2020) § 8.11.4 - primary_item: AvifItem, - /// Associated alpha channel for the primary item, if any - alpha_item: Option<AvifItem>, - /// If true, divide RGB values by the alpha value. - /// See `prem` in MIAF (ISO 23000-22:2019) § 7.3.5.2 - pub premultiplied_alpha: bool, - /// All properties associated with `primary_item` or `alpha_item` - item_properties: ItemPropertiesBox, -} - -impl AvifContext { - pub fn primary_item_coded_data(&self) -> &[u8] { - self.item_as_slice(&self.primary_item) - } - - pub fn primary_item_bits_per_channel(&self) -> Result<&[u8]> { - self.image_bits_per_channel(self.primary_item.id) - } - - pub fn alpha_item_coded_data(&self) -> &[u8] { - self.alpha_item - .as_ref() - .map_or(&[], |item| self.item_as_slice(item)) - } - - pub fn alpha_item_bits_per_channel(&self) -> Result<&[u8]> { - self.alpha_item - .as_ref() - .map_or(Ok(&[]), |item| self.image_bits_per_channel(item.id)) - } - - fn image_bits_per_channel(&self, item_id: ItemId) -> Result<&[u8]> { - match self - .item_properties - .get(item_id, BoxType::PixelInformationBox)? - { - Some(ItemProperty::Channels(pixi)) => Ok(pixi.bits_per_channel.as_slice()), - Some(other_property) => panic!("property key mismatch: {:?}", other_property), - None => Ok(&[]), - } - } - - pub fn spatial_extents_ptr(&self) -> Result<*const ImageSpatialExtentsProperty> { - match self - .item_properties - .get(self.primary_item.id, BoxType::ImageSpatialExtentsProperty)? - { - Some(ItemProperty::ImageSpatialExtents(ispe)) => Ok(ispe), - Some(other_property) => panic!("property key mismatch: {:?}", other_property), - None => { - fail_if( - self.strictness == ParseStrictness::Permissive, - "ispe is a mandatory property", - )?; - Ok(std::ptr::null()) - } - } - } - - pub fn nclx_colour_information_ptr(&self) -> Result<*const NclxColourInformation> { - let nclx_colr_boxes = self - .item_properties - .get_multiple(self.primary_item.id, |prop| { - matches!(prop, ItemProperty::Colour(ColourInformation::Nclx(_))) - })?; - - match *nclx_colr_boxes.as_slice() { - [] => Ok(std::ptr::null()), - [ItemProperty::Colour(ColourInformation::Nclx(nclx)), ..] => { - if nclx_colr_boxes.len() > 1 { - warn!("Multiple nclx colr boxes, using first"); - } - Ok(nclx) - } - _ => unreachable!("Expect only ColourInformation::Nclx(_) matches"), - } - } - - pub fn icc_colour_information(&self) -> Result<&[u8]> { - let icc_colr_boxes = self - .item_properties - .get_multiple(self.primary_item.id, |prop| { - matches!(prop, ItemProperty::Colour(ColourInformation::Icc(_, _))) - })?; - - match *icc_colr_boxes.as_slice() { - [] => Ok(&[]), - [ItemProperty::Colour(ColourInformation::Icc(icc, _)), ..] => { - if icc_colr_boxes.len() > 1 { - warn!("Multiple ICC profiles in colr boxes, using first"); - } - Ok(icc.bytes.as_slice()) - } - _ => unreachable!("Expect only ColourInformation::Icc(_) matches"), - } - } - - pub fn image_rotation(&self) -> Result<ImageRotation> { - match self - .item_properties - .get(self.primary_item.id, BoxType::ImageRotation)? - { - Some(ItemProperty::Rotation(irot)) => Ok(*irot), - Some(other_property) => panic!("property key mismatch: {:?}", other_property), - None => Ok(ImageRotation::D0), - } - } - - pub fn image_mirror_ptr(&self) -> Result<*const ImageMirror> { - match self - .item_properties - .get(self.primary_item.id, BoxType::ImageMirror)? - { - Some(ItemProperty::Mirroring(imir)) => Ok(imir), - Some(other_property) => panic!("property key mismatch: {:?}", other_property), - None => Ok(std::ptr::null()), - } - } - - /// A helper for the various `AvifItem`s to expose a reference to the - /// underlying data while avoiding copies. - fn item_as_slice<'a>(&'a self, item: &'a AvifItem) -> &'a [u8] { - match &item.image_data { - IsobmffItem::Location(extent) => { - for mdat in &self.item_storage { - if let Some(slice) = mdat.get(extent) { - return slice; - } - } - unreachable!( - "IsobmffItem::Location requires the location exists in AvifContext::item_storage" - ); - } - IsobmffItem::Data(data) => data.as_slice(), - } - } -} - -struct AvifMeta { - item_references: TryVec<SingleItemTypeReferenceBox>, - item_properties: ItemPropertiesBox, - primary_item_id: ItemId, - iloc_items: TryHashMap<ItemId, ItemLocationBoxItem>, -} - -/// A Media Data Box -/// See ISOBMFF (ISO 14496-12:2020) § 8.1.1 -struct MediaDataBox { - /// Offset of `data` from the beginning of the "file". See ConstructionMethod::File. - /// Note: the file may not be an actual file, read_avif supports any `&mut impl Read` - /// source for input. However we try to match the terminology used in the spec. - file_offset: u64, - data: TryVec<u8>, -} - -impl fmt::Debug for MediaDataBox { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("MediaDataBox") - .field("file_offset", &self.file_offset) - .field("data", &format_args!("{} bytes", self.data.len())) - .finish() - } -} - -impl MediaDataBox { - /// Convert an absolute offset to an offset relative to the beginning of the - /// `self.data` field. Returns None if the offset would be negative. - /// - /// # Panics - /// - /// Panics if the offset would overflow a `usize`. - fn file_offset_to_data_offset(&self, offset: u64) -> Option<usize> { - let start = offset - .checked_sub(self.file_offset)? - .try_into() - .expect("usize overflow"); - Some(start) - } - - /// Return a slice from the MediaDataBox specified by the provided `extent`. - /// Returns `None` if the extent isn't fully contained by the MediaDataBox. - /// - /// # Panics - /// - /// Panics if either the offset or length (if the extent is bounded) of the - /// slice would overflow a `usize`. - pub fn get<'a>(&'a self, extent: &'a Extent) -> Option<&'a [u8]> { - match extent { - Extent::WithLength { offset, len } => { - let start = self.file_offset_to_data_offset(*offset)?; - let end = start.checked_add(*len).expect("usize overflow"); - self.data.get(start..end) - } - Extent::ToEnd { offset } => { - let start = self.file_offset_to_data_offset(*offset)?; - self.data.get(start..) - } - } - } -} - -#[cfg(test)] -mod media_data_box_tests { - use super::*; - - impl MediaDataBox { - fn at_offset(file_offset: u64, data: std::vec::Vec<u8>) -> Self { - MediaDataBox { - file_offset, - data: data.into(), - } - } - } - - #[test] - fn extent_with_length_before_mdat_returns_none() { - let mdat = MediaDataBox::at_offset(100, vec![1; 5]); - let extent = Extent::WithLength { offset: 0, len: 2 }; - - assert!(mdat.get(&extent).is_none()); - } - - #[test] - fn extent_to_end_before_mdat_returns_none() { - let mdat = MediaDataBox::at_offset(100, vec![1; 5]); - let extent = Extent::ToEnd { offset: 0 }; - - assert!(mdat.get(&extent).is_none()); - } - - #[test] - fn extent_with_length_crossing_front_mdat_boundary_returns_none() { - let mdat = MediaDataBox::at_offset(100, vec![1; 5]); - let extent = Extent::WithLength { offset: 99, len: 3 }; - - assert!(mdat.get(&extent).is_none()); - } - - #[test] - fn extent_with_length_which_is_subset_of_mdat() { - let mdat = MediaDataBox::at_offset(100, vec![1; 5]); - let extent = Extent::WithLength { - offset: 101, - len: 2, - }; - - assert_eq!(mdat.get(&extent), Some(&[1, 1][..])); - } - - #[test] - fn extent_to_end_which_is_subset_of_mdat() { - let mdat = MediaDataBox::at_offset(100, vec![1; 5]); - let extent = Extent::ToEnd { offset: 101 }; - - assert_eq!(mdat.get(&extent), Some(&[1, 1, 1, 1][..])); - } - - #[test] - fn extent_with_length_which_is_all_of_mdat() { - let mdat = MediaDataBox::at_offset(100, vec![1; 5]); - let extent = Extent::WithLength { - offset: 100, - len: 5, - }; - - assert_eq!(mdat.get(&extent), Some(mdat.data.as_slice())); - } - - #[test] - fn extent_to_end_which_is_all_of_mdat() { - let mdat = MediaDataBox::at_offset(100, vec![1; 5]); - let extent = Extent::ToEnd { offset: 100 }; - - assert_eq!(mdat.get(&extent), Some(mdat.data.as_slice())); - } - - #[test] - fn extent_with_length_crossing_back_mdat_boundary_returns_none() { - let mdat = MediaDataBox::at_offset(100, vec![1; 5]); - let extent = Extent::WithLength { - offset: 103, - len: 3, - }; - - assert!(mdat.get(&extent).is_none()); - } - - #[test] - fn extent_with_length_after_mdat_returns_none() { - let mdat = MediaDataBox::at_offset(100, vec![1; 5]); - let extent = Extent::WithLength { - offset: 200, - len: 2, - }; - - assert!(mdat.get(&extent).is_none()); - } - - #[test] - fn extent_to_end_after_mdat_returns_none() { - let mdat = MediaDataBox::at_offset(100, vec![1; 5]); - let extent = Extent::ToEnd { offset: 200 }; - - assert!(mdat.get(&extent).is_none()); - } - - #[test] - #[should_panic(expected = "usize overflow")] - fn extent_with_length_which_overflows_usize_panics() { - let mdat = MediaDataBox::at_offset(std::u64::MAX - 1, vec![1; 5]); - let extent = Extent::WithLength { - offset: std::u64::MAX, - len: std::usize::MAX, - }; - - mdat.get(&extent); - } - - // The end of the range would overflow `usize` if it were calculated, but - // because the range end is unbounded, we don't calculate it. - #[test] - fn extent_to_end_which_overflows_usize() { - let mdat = MediaDataBox::at_offset(std::u64::MAX - 1, vec![1; 5]); - let extent = Extent::ToEnd { - offset: std::u64::MAX, - }; - - assert_eq!(mdat.get(&extent), Some(&[1, 1, 1, 1][..])); - } -} - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -struct PropertyIndex(u16); -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] -struct ItemId(u32); - -impl ItemId { - fn read(src: &mut impl ReadBytesExt, version: u8) -> Result<ItemId> { - Ok(ItemId(if version == 0 { - be_u16(src)?.into() - } else { - be_u32(src)? - })) - } -} - -/// Used for 'infe' boxes within 'iinf' boxes -/// See ISOBMFF (ISO 14496-12:2020) § 8.11.6 -/// Only versions {2, 3} are supported -#[derive(Debug)] -struct ItemInfoEntry { - item_id: ItemId, - item_type: u32, -} - -/// See ISOBMFF (ISO 14496-12:2020) § 8.11.12 -#[derive(Debug)] -struct SingleItemTypeReferenceBox { - item_type: FourCC, - from_item_id: ItemId, - to_item_id: ItemId, -} - -/// Potential sizes (in bytes) of variable-sized fields of the 'iloc' box -/// See ISOBMFF (ISO 14496-12:2020) § 8.11.3 -#[derive(Debug, Clone, Copy, PartialEq)] -enum IlocFieldSize { - Zero, - Four, - Eight, -} - -impl IlocFieldSize { - fn as_bits(&self) -> u8 { - match self { - IlocFieldSize::Zero => 0, - IlocFieldSize::Four => 32, - IlocFieldSize::Eight => 64, - } - } -} - -impl TryFrom<u8> for IlocFieldSize { - type Error = Error; - - fn try_from(value: u8) -> Result<Self> { - match value { - 0 => Ok(Self::Zero), - 4 => Ok(Self::Four), - 8 => Ok(Self::Eight), - _ => Err(Error::InvalidData("value must be in the set {0, 4, 8}")), - } - } -} - -#[derive(PartialEq)] -enum IlocVersion { - Zero, - One, - Two, -} - -impl TryFrom<u8> for IlocVersion { - type Error = Error; - - fn try_from(value: u8) -> Result<Self> { - match value { - 0 => Ok(Self::Zero), - 1 => Ok(Self::One), - 2 => Ok(Self::Two), - _ => Err(Error::Unsupported("unsupported version in 'iloc' box")), - } - } -} - -/// Used for 'iloc' boxes -/// See ISOBMFF (ISO 14496-12:2020) § 8.11.3 -/// `base_offset` is omitted since it is integrated into the ranges in `extents` -/// `data_reference_index` is omitted, since only 0 (i.e., this file) is supported -#[derive(Debug)] -struct ItemLocationBoxItem { - construction_method: ConstructionMethod, - /// Unused for ConstructionMethod::Idat - extents: TryVec<Extent>, -} - -/// See ISOBMFF (ISO 14496-12:2020) § 8.11.3 -/// -/// Note: per MIAF (ISO 23000-22:2019) § 7.2.1.7:<br /> -/// > MIAF image items are constrained as follows:<br /> -/// > — `construction_method` shall be equal to 0 for MIAF image items that are coded image items.<br /> -/// > — `construction_method` shall be equal to 0 or 1 for MIAF image items that are derived image items. -#[derive(Clone, Copy, Debug, PartialEq)] -enum ConstructionMethod { - File = 0, - Idat = 1, - #[allow(dead_code)] // TODO: see https://github.com/mozilla/mp4parse-rust/issues/196 - Item = 2, -} - -/// Describes a region where a item specified by an `ItemLocationBoxItem` is stored. -/// The offset is `u64` since that's the maximum possible size and since the relative -/// nature of `MediaDataBox` means this can still possibly succeed even in the case -/// that the raw value exceeds std::usize::MAX on platforms where that type is smaller -/// than u64. However, `len` is stored as a `usize` since no value larger than -/// `std::usize::MAX` can be used in a successful indexing operation in rust. -/// `extent_index` is omitted since it's only used for ConstructionMethod::Item which -/// is currently not implemented. -#[derive(Clone, Debug)] -enum Extent { - WithLength { offset: u64, len: usize }, - ToEnd { offset: u64 }, -} - -#[derive(Debug, PartialEq)] -pub enum TrackType { - Audio, - Video, - Metadata, - Unknown, -} - -impl Default for TrackType { - fn default() -> Self { - TrackType::Unknown - } -} - -// This type is used by mp4parse_capi since it needs to be passed from FFI consumers -// The C-visible struct is renamed via mp4parse_capi/cbindgen.toml to match naming conventions -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum ParseStrictness { - Permissive, // Error only on ambiguous inputs - Normal, // Error on "shall" directives, log warnings for "should" - Strict, // Error on "should" directives -} - -fn fail_if(violation: bool, message: &'static str) -> Result<()> { - if violation { - Err(Error::InvalidData(message)) - } else { - warn!("{}", message); - Ok(()) - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum CodecType { - Unknown, - MP3, - AAC, - FLAC, - Opus, - H264, // 14496-10 - MP4V, // 14496-2 - AV1, - VP9, - VP8, - EncryptedVideo, - EncryptedAudio, - LPCM, // QT - ALAC, - H263, - #[cfg(feature = "3gpp")] - AMRNB, - #[cfg(feature = "3gpp")] - AMRWB, - #[cfg(feature = "craw")] - CRAW, // Canon CRAW -} - -impl Default for CodecType { - fn default() -> Self { - CodecType::Unknown - } -} - -/// The media's global (mvhd) timescale in units per second. -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct MediaTimeScale(pub u64); - -/// A time to be scaled by the media's global (mvhd) timescale. -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct MediaScaledTime(pub u64); - -/// The track's local (mdhd) timescale. -/// Members are timescale units per second and the track id. -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct TrackTimeScale<T: Num>(pub T, pub usize); - -/// A time to be scaled by the track's local (mdhd) timescale. -/// Members are time in scale units and the track id. -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct TrackScaledTime<T>(pub T, pub usize); - -impl<T> std::ops::Add for TrackScaledTime<T> -where - T: num_traits::CheckedAdd, -{ - type Output = Option<Self>; - - fn add(self, other: TrackScaledTime<T>) -> Self::Output { - self.0.checked_add(&other.0).map(|sum| Self(sum, self.1)) - } -} - -#[derive(Debug, Default)] -pub struct Track { - pub id: usize, - pub track_type: TrackType, - pub empty_duration: Option<MediaScaledTime>, - pub media_time: Option<TrackScaledTime<u64>>, - pub timescale: Option<TrackTimeScale<u64>>, - pub duration: Option<TrackScaledTime<u64>>, - pub track_id: Option<u32>, - pub tkhd: Option<TrackHeaderBox>, // TODO(kinetik): find a nicer way to export this. - pub stsd: Option<SampleDescriptionBox>, - pub stts: Option<TimeToSampleBox>, - pub stsc: Option<SampleToChunkBox>, - pub stsz: Option<SampleSizeBox>, - pub stco: Option<ChunkOffsetBox>, // It is for stco or co64. - pub stss: Option<SyncSampleBox>, - pub ctts: Option<CompositionOffsetBox>, -} - -impl Track { - fn new(id: usize) -> Track { - Track { - id, - ..Default::default() - } - } -} - -/// See ISOBMFF (ISO 14496-12:2020) § 4.2 -pub(crate) struct BMFFBox<'a, T: 'a> { - head: BoxHeader, - content: Take<&'a mut T>, -} - -struct BoxIter<'a, T: 'a> { - src: &'a mut T, -} - -impl<'a, T: Read> BoxIter<'a, T> { - fn new(src: &mut T) -> BoxIter<T> { - BoxIter { src } - } - - fn next_box(&mut self) -> Result<Option<BMFFBox<T>>> { - let r = read_box_header(self.src); - match r { - Ok(h) => Ok(Some(BMFFBox { - head: h, - content: self.src.take(h.size - h.offset), - })), - Err(Error::UnexpectedEOF) => Ok(None), - Err(e) => Err(e), - } - } -} - -impl<'a, T: Read> Read for BMFFBox<'a, T> { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { - self.content.read(buf) - } -} - -impl<'a, T: Read> TryRead for BMFFBox<'a, T> { - fn try_read_to_end(&mut self, buf: &mut TryVec<u8>) -> std::io::Result<usize> { - fallible_collections::try_read_up_to(self, self.bytes_left(), buf) - } -} - -impl<'a, T: Offset> Offset for BMFFBox<'a, T> { - fn offset(&self) -> u64 { - self.content.get_ref().offset() - } -} - -impl<'a, T: Read> BMFFBox<'a, T> { - fn bytes_left(&self) -> u64 { - self.content.limit() - } - - fn get_header(&self) -> &BoxHeader { - &self.head - } - - fn box_iter<'b>(&'b mut self) -> BoxIter<BMFFBox<'a, T>> { - BoxIter::new(self) - } -} - -impl<'a, T> Drop for BMFFBox<'a, T> { - fn drop(&mut self) { - if self.content.limit() > 0 { - let name: FourCC = From::from(self.head.name); - debug!("Dropping {} bytes in '{}'", self.content.limit(), name); - } - } -} - -/// Read and parse a box header. -/// -/// Call this first to determine the type of a particular mp4 box -/// and its length. Used internally for dispatching to specific -/// parsers for the internal content, or to get the length to -/// skip unknown or uninteresting boxes. -/// -/// See ISOBMFF (ISO 14496-12:2020) § 4.2 -fn read_box_header<T: ReadBytesExt>(src: &mut T) -> Result<BoxHeader> { - let size32 = be_u32(src)?; - let name = BoxType::from(be_u32(src)?); - let size = match size32 { - // valid only for top-level box and indicates it's the last box in the file. usually mdat. - 0 => return Err(Error::Unsupported("unknown sized box")), - 1 => { - let size64 = be_u64(src)?; - if size64 < BoxHeader::MIN_LARGE_SIZE { - return Err(Error::InvalidData("malformed wide size")); - } - size64 - } - _ => { - if u64::from(size32) < BoxHeader::MIN_SIZE { - return Err(Error::InvalidData("malformed size")); - } - u64::from(size32) - } - }; - let mut offset = match size32 { - 1 => BoxHeader::MIN_LARGE_SIZE, - _ => BoxHeader::MIN_SIZE, - }; - let uuid = if name == BoxType::UuidBox { - if size >= offset + 16 { - let mut buffer = [0u8; 16]; - let count = src.read(&mut buffer)?; - offset += count.to_u64(); - if count == 16 { - Some(buffer) - } else { - debug!("malformed uuid (short read), skipping"); - None - } - } else { - debug!("malformed uuid, skipping"); - None - } - } else { - None - }; - assert!(offset <= size); - Ok(BoxHeader { - name, - size, - offset, - uuid, - }) -} - -/// Parse the extra header fields for a full box. -fn read_fullbox_extra<T: ReadBytesExt>(src: &mut T) -> Result<(u8, u32)> { - let version = src.read_u8()?; - let flags_a = src.read_u8()?; - let flags_b = src.read_u8()?; - let flags_c = src.read_u8()?; - Ok(( - version, - u32::from(flags_a) << 16 | u32::from(flags_b) << 8 | u32::from(flags_c), - )) -} - -// Parse the extra fields for a full box whose flag fields must be zero. -fn read_fullbox_version_no_flags<T: ReadBytesExt>(src: &mut T) -> Result<u8> { - let (version, flags) = read_fullbox_extra(src)?; - - if flags != 0 { - return Err(Error::Unsupported("expected flags to be 0")); - } - - Ok(version) -} - -/// Skip over the entire contents of a box. -fn skip_box_content<T: Read>(src: &mut BMFFBox<T>) -> Result<()> { - // Skip the contents of unknown chunks. - let to_skip = { - let header = src.get_header(); - debug!("{:?} (skipped)", header); - header - .size - .checked_sub(header.offset) - .expect("header offset > size") - }; - assert_eq!(to_skip, src.bytes_left()); - skip(src, to_skip) -} - -/// Skip over the remain data of a box. -fn skip_box_remain<T: Read>(src: &mut BMFFBox<T>) -> Result<()> { - let remain = { - let header = src.get_header(); - let len = src.bytes_left(); - debug!("remain {} (skipped) in {:?}", len, header); - len - }; - skip(src, remain) -} - -/// Read the contents of an AVIF file -pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<AvifContext> { - let _ = env_logger::try_init(); - - debug!("read_avif(strictness: {:?})", strictness); - - let mut f = OffsetReader::new(f); - - let mut iter = BoxIter::new(&mut f); - - // 'ftyp' box must occur first; see ISOBMFF (ISO 14496-12:2020) § 4.3.1 - if let Some(mut b) = iter.next_box()? { - if b.head.name == BoxType::FileTypeBox { - let ftyp = read_ftyp(&mut b)?; - if !ftyp.compatible_brands.contains(&MIF1_BRAND) { - // This mandatory inclusion of this brand is in the process of being changed - // to optional. In anticipation of that, only give an error in strict mode - // See https://github.com/MPEGGroup/MIAF/issues/5 - // and https://github.com/MPEGGroup/FileFormat/issues/23 - fail_if( - strictness == ParseStrictness::Strict, - "The FileTypeBox should contain 'mif1' in the compatible_brands list \ - per MIAF (ISO 23000-22:2019) § 7.2.1.2", - )?; - } - } else { - return Err(Error::InvalidData("'ftyp' box must occur first")); - } - } - - let mut meta = None; - let mut item_storage = TryVec::new(); - - while let Some(mut b) = iter.next_box()? { - trace!("read_avif parsing {:?} box", b.head.name); - match b.head.name { - BoxType::MetadataBox => { - if meta.is_some() { - return Err(Error::InvalidData( - "There should be zero or one meta boxes per ISOBMFF (ISO 14496-12:2020) § 8.11.1.1", - )); - } - meta = Some(read_avif_meta(&mut b, strictness)?); - } - BoxType::MediaDataBox => { - if b.bytes_left() > 0 { - let file_offset = b.offset(); - let data = b.read_into_try_vec()?; - item_storage.push(MediaDataBox { file_offset, data })?; - } - } - _ => skip_box_content(&mut b)?, - } - - check_parser_state!(b.content); - } - - let AvifMeta { - item_references, - item_properties, - primary_item_id, - iloc_items, - } = meta.ok_or(Error::InvalidData("missing meta"))?; - - let mut alpha_item_ids = item_references - .iter() - // Auxiliary image for the primary image - .filter(|iref| { - iref.to_item_id == primary_item_id - && iref.from_item_id != primary_item_id - && iref.item_type == b"auxl" - }) - .map(|iref| iref.from_item_id) - // which has the alpha property - .filter(|&item_id| item_properties.is_alpha(item_id)); - let alpha_item_id = alpha_item_ids.next(); - if alpha_item_ids.next().is_some() { - return Err(Error::InvalidData("multiple alpha planes")); - } - - let premultiplied_alpha = alpha_item_id.map_or(false, |alpha_item_id| { - item_references.iter().any(|iref| { - iref.from_item_id == primary_item_id - && iref.to_item_id == alpha_item_id - && iref.item_type == b"prem" - }) - }); - - let mut primary_item = None; - let mut alpha_item = None; - - // store data or record location of relevant items - for (item_id, loc) in iloc_items { - let item = if item_id == primary_item_id { - &mut primary_item - } else if Some(item_id) == alpha_item_id { - &mut alpha_item - } else { - continue; - }; - - if loc.construction_method != ConstructionMethod::File { - return Err(Error::Unsupported("unsupported construction_method")); - } - - assert!(item.is_none()); - - // If our item is spread over multiple extents, we'll need to copy it - // into a contiguous buffer. Otherwise, we can just store the extent - // and return a pointer into the mdat later to avoid the copy. - if loc.extents.len() > 1 { - *item = Some(AvifItem::with_inline_data(item_id)) - } - - for extent in loc.extents { - let mut found = false; - // try to find an mdat which contains the extent - for mdat in item_storage.iter_mut() { - if let Some(extent_slice) = mdat.get(&extent) { - match item { - None => { - trace!("Using IsobmffItem::Location"); - *item = Some(AvifItem::with_data_location(item_id, extent)); - } - Some(AvifItem { - image_data: IsobmffItem::Data(item_data), - .. - }) => { - trace!("Using IsobmffItem::Data"); - // We could potentially optimize memory usage by trying to avoid reading - // or storing mdat boxes which aren't used by our API, but for now it seems - // like unnecessary complexity - item_data.extend_from_slice(extent_slice)?; - } - _ => unreachable!(), - } - found = true; - break; - } - } - - if !found { - return Err(Error::InvalidData( - "iloc contains an extent that is not in any mdat", - )); - } - } - - assert!(item.is_some()); - } - - let primary_item = primary_item.ok_or(Error::InvalidData( - "Missing 'pitm' box, required per HEIF (ISO/IEC 23008-12:2017) § 10.2.1", - ))?; - - let has_pixi = |item_id| { - item_properties - .get(item_id, BoxType::PixelInformationBox) - .map_or(false, |opt| opt.is_some()) - }; - if !has_pixi(primary_item_id) || !alpha_item_id.map_or(true, has_pixi) { - // The requirement to include pixi is in the process of being changed - // to allowing its omission to imply a default value. In anticipation - // of that, only give an error in strict mode - // See https://github.com/MPEGGroup/MIAF/issues/9 - fail_if( - if cfg!(feature = "missing-pixi-permitted") { - strictness == ParseStrictness::Strict - } else { - strictness != ParseStrictness::Permissive - }, - "The pixel information property shall be associated with every image \ - that is displayable (not hidden) \ - per MIAF (ISO/IEC 23000-22:2019) specification § 7.3.6.6", - )?; - } - - let has_av1c = |item_id| { - item_properties - .get(item_id, BoxType::AV1CodecConfigurationBox) - .map_or(false, |opt| opt.is_some()) - }; - if !has_av1c(primary_item_id) || !alpha_item_id.map_or(true, has_av1c) { - fail_if( - strictness != ParseStrictness::Permissive, - "One AV1 Item Configuration Property (av1C) is mandatory for an \ - image item of type 'av01' \ - per AVIF specification § 2.2.1", - )?; - } - - if item_properties.get_ispe(primary_item_id)?.is_none() { - fail_if( - strictness != ParseStrictness::Permissive, - "Missing 'ispe' property for primary item, required \ - per HEIF (ISO/IEC 23008-12:2017) § 6.5.3.1", - )?; - } - - Ok(AvifContext { - strictness, - item_storage, - primary_item, - alpha_item, - premultiplied_alpha, - item_properties, - }) -} - -/// Parse a metadata box in the context of an AVIF -/// Currently requires the primary item to be an av01 item type and generates -/// an error otherwise. -/// See ISOBMFF (ISO 14496-12:2020) § 8.11.1 -fn read_avif_meta<T: Read + Offset>( - src: &mut BMFFBox<T>, - strictness: ParseStrictness, -) -> Result<AvifMeta> { - let version = read_fullbox_version_no_flags(src)?; - - if version != 0 { - return Err(Error::Unsupported("unsupported meta version")); - } - - let mut read_handler_box = false; - let mut primary_item_id = None; - let mut item_infos = None; - let mut iloc_items = None; - let mut item_references = None; - let mut item_properties = None; - - let mut iter = src.box_iter(); - while let Some(mut b) = iter.next_box()? { - trace!("read_avif_meta parsing {:?} box", b.head.name); - - if !read_handler_box && b.head.name != BoxType::HandlerBox { - fail_if( - strictness != ParseStrictness::Permissive, - "The HandlerBox shall be the first contained box within the MetaBox \ - per MIAF (ISO 23000-22:2019) § 7.2.1.5", - )?; - } - - match b.head.name { - BoxType::HandlerBox => { - if read_handler_box { - return Err(Error::InvalidData( - "There shall be exactly one hdlr box per ISOBMFF (ISO 14496-12:2020) § 8.4.3.1", - )); - } - let HandlerBox { handler_type } = read_hdlr(&mut b, strictness)?; - if handler_type != b"pict" { - fail_if( - strictness != ParseStrictness::Permissive, - "The HandlerBox handler_type must be 'pict' \ - per MIAF (ISO 23000-22:2019) § 7.2.1.5", - )?; - } - read_handler_box = true; - } - BoxType::ItemInfoBox => { - if item_infos.is_some() { - return Err(Error::InvalidData( - "There shall be zero or one iinf boxes per ISOBMFF (ISO 14496-12:2020) § 8.11.6.1", - )); - } - item_infos = Some(read_iinf(&mut b, strictness)?); - } - BoxType::ItemLocationBox => { - if iloc_items.is_some() { - return Err(Error::InvalidData( - "There shall be zero or one iloc boxes per ISOBMFF (ISO 14496-12:2020) § 8.11.3.1", - )); - } - iloc_items = Some(read_iloc(&mut b)?); - } - BoxType::PrimaryItemBox => { - if primary_item_id.is_some() { - return Err(Error::InvalidData( - "There shall be zero or one pitm boxes per ISOBMFF (ISO 14496-12:2020) § 8.11.4.1", - )); - } - primary_item_id = Some(read_pitm(&mut b)?); - } - BoxType::ItemReferenceBox => { - if item_references.is_some() { - return Err(Error::InvalidData("There shall be zero or one iref boxes per ISOBMFF (ISO 14496-12:2020) § 8.11.12.1")); - } - item_references = Some(read_iref(&mut b)?); - } - BoxType::ItemPropertiesBox => { - if item_properties.is_some() { - return Err(Error::InvalidData("There shall be zero or one iprp boxes per ISOBMFF (ISO 14496-12:2020) § 8.11.14.1")); - } - item_properties = Some(read_iprp(&mut b, MIF1_BRAND, strictness)?); - } - _ => skip_box_content(&mut b)?, - } - - check_parser_state!(b.content); - } - - let primary_item_id = primary_item_id.ok_or(Error::InvalidData( - "Required pitm box not present in meta box", - ))?; - - let item_infos = item_infos.ok_or(Error::InvalidData("iinf missing"))?; - - if let Some(item_info) = item_infos.iter().find(|x| x.item_id == primary_item_id) { - debug!("primary_item_id type: {}", U32BE(item_info.item_type)); - match &item_info.item_type.to_be_bytes() { - b"av01" => {} - b"grid" => return Err(Error::from(Status::UnsupportedGrid)), - _ => { - return Err(Error::InvalidData( - "primary_item_id type is neither 'av01' nor 'grid'", - )) - } - } - } else { - return Err(Error::InvalidData( - "primary_item_id not present in iinf box", - )); - } - - Ok(AvifMeta { - item_properties: item_properties.unwrap_or_default(), - item_references: item_references.unwrap_or_default(), - primary_item_id, - iloc_items: iloc_items.ok_or(Error::InvalidData("iloc missing"))?, - }) -} - -/// Parse a Primary Item Box -/// See ISOBMFF (ISO 14496-12:2020) § 8.11.4 -fn read_pitm<T: Read>(src: &mut BMFFBox<T>) -> Result<ItemId> { - let version = read_fullbox_version_no_flags(src)?; - - let item_id = ItemId(match version { - 0 => be_u16(src)?.into(), - 1 => be_u32(src)?, - _ => return Err(Error::Unsupported("unsupported pitm version")), - }); - - Ok(item_id) -} - -/// Parse an Item Information Box -/// See ISOBMFF (ISO 14496-12:2020) § 8.11.6 -fn read_iinf<T: Read>( - src: &mut BMFFBox<T>, - strictness: ParseStrictness, -) -> Result<TryVec<ItemInfoEntry>> { - let version = read_fullbox_version_no_flags(src)?; - - match version { - 0 | 1 => (), - _ => return Err(Error::Unsupported("unsupported iinf version")), - } - - let entry_count = if version == 0 { - be_u16(src)?.to_usize() - } else { - be_u32(src)?.to_usize() - }; - let mut item_infos = TryVec::with_capacity(entry_count)?; - - let mut iter = src.box_iter(); - while let Some(mut b) = iter.next_box()? { - if b.head.name != BoxType::ItemInfoEntry { - return Err(Error::InvalidData( - "iinf box shall contain only infe boxes per ISOBMFF (ISO 14496-12:2020) § 8.11.6.2", - )); - } - - item_infos.push(read_infe(&mut b, strictness)?)?; - - check_parser_state!(b.content); - } - - Ok(item_infos) -} - -/// A simple wrapper to interpret a u32 as a 4-byte string in big-endian -/// order without requiring any allocation. -struct U32BE(u32); - -impl std::fmt::Display for U32BE { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match std::str::from_utf8(&self.0.to_be_bytes()) { - Ok(s) => f.write_str(s), - Err(_) => write!(f, "{:x?}", self.0), - } - } -} - -/// Parse an Item Info Entry -/// See ISOBMFF (ISO 14496-12:2020) § 8.11.6.2 -fn read_infe<T: Read>(src: &mut BMFFBox<T>, strictness: ParseStrictness) -> Result<ItemInfoEntry> { - let (version, flags) = read_fullbox_extra(src)?; - - // According to the standard, it seems the flags field shall be 0, but at - // least one sample AVIF image has a nonzero value. - // See https://github.com/AOMediaCodec/av1-avif/issues/146 - if flags != 0 { - fail_if( - strictness == ParseStrictness::Strict, - "'infe' flags field shall be 0 \ - per ISOBMFF (ISO 14496-12:2020) § 8.11.6.2", - )?; - } - - // mif1 brand (see HEIF (ISO 23008-12:2017) § 10.2.1) only requires v2 and 3 - let item_id = ItemId(match version { - 2 => be_u16(src)?.into(), - 3 => be_u32(src)?, - _ => return Err(Error::Unsupported("unsupported version in 'infe' box")), - }); - - let item_protection_index = be_u16(src)?; - - if item_protection_index != 0 { - return Err(Error::from(Status::UnsupportedIpro)); - } - - let item_type = be_u32(src)?; - debug!("infe {:?} item_type: {}", item_id, U32BE(item_type)); - - // There are some additional fields here, but they're not of interest to us - skip_box_remain(src)?; - - Ok(ItemInfoEntry { item_id, item_type }) -} - -/// Parse an Item Reference Box -/// See ISOBMFF (ISO 14496-12:2020) § 8.11.12 -fn read_iref<T: Read>(src: &mut BMFFBox<T>) -> Result<TryVec<SingleItemTypeReferenceBox>> { - let mut item_references = TryVec::new(); - let version = read_fullbox_version_no_flags(src)?; - if version > 1 { - return Err(Error::Unsupported("iref version")); - } - - let mut iter = src.box_iter(); - while let Some(mut b) = iter.next_box()? { - trace!("read_iref parsing {:?} referenceType", b.head.name); - let from_item_id = ItemId::read(&mut b, version)?; - let reference_count = be_u16(&mut b)?; - item_references.reserve(reference_count.to_usize())?; - for _ in 0..reference_count { - let to_item_id = ItemId::read(&mut b, version)?; - if from_item_id == to_item_id { - return Err(Error::InvalidData( - "from_item_id and to_item_id must be different", - )); - } - item_references.push(SingleItemTypeReferenceBox { - item_type: b.head.name.into(), - from_item_id, - to_item_id, - })?; - } - check_parser_state!(b.content); - } - Ok(item_references) -} - -/// Parse an Item Properties Box -/// -/// See ISOBMFF (ISO 14496-12:2020 § 8.11.14) -/// -/// Note: HEIF (ISO 23008-12:2017) § 9.3.1 also defines the `iprp` box and -/// related types, but lacks additional requirements specified in 14496-12:2020. -/// -/// Note: Currently HEIF (ISO 23008-12:2017) § 6.5.5.1 specifies "At most one" -/// `colr` box per item, but this is being amended in [DIS 23008-12](https://www.iso.org/standard/83650.html). -/// The new text is likely to be "At most one for a given value of `colour_type`", -/// so this implementation adheres to that language for forward compatibility. -fn read_iprp<T: Read>( - src: &mut BMFFBox<T>, - brand: FourCC, - strictness: ParseStrictness, -) -> Result<ItemPropertiesBox> { - let mut iter = src.box_iter(); - - let properties = match iter.next_box()? { - Some(mut b) if b.head.name == BoxType::ItemPropertyContainerBox => { - read_ipco(&mut b, strictness) - } - Some(_) => Err(Error::InvalidData("unexpected iprp child")), - None => Err(Error::UnexpectedEOF), - }?; - - let mut ipma_version_and_flag_values_seen = TryVec::with_capacity(1)?; - let mut association_entries = TryVec::<ItemPropertyAssociationEntry>::new(); - - while let Some(mut b) = iter.next_box()? { - if b.head.name != BoxType::ItemPropertyAssociationBox { - return Err(Error::InvalidData("unexpected iprp child")); - } - - let (version, flags) = read_fullbox_extra(&mut b)?; - if ipma_version_and_flag_values_seen.contains(&(version, flags)) { - fail_if( - strictness != ParseStrictness::Permissive, - "There shall be at most one ItemPropertyAssociationbox with a given pair of \ - values of version and flags \ - per ISOBMFF (ISO 14496-12:2020 § 8.11.14.1", - )?; - } - if flags != 0 && properties.len() <= 127 { - fail_if( - strictness == ParseStrictness::Strict, - "Unless there are more than 127 properties in the ItemPropertyContainerBox, \ - flags should be equal to 0 \ - per ISOBMFF (ISO 14496-12:2020 § 8.11.14.1", - )?; - } - ipma_version_and_flag_values_seen.push((version, flags))?; - for association_entry in read_ipma(&mut b, strictness, version, flags)? { - if let Some(previous_entry) = association_entries - .iter() - .find(|e| association_entry.item_id == e.item_id) - { - error!( - "Duplicate ipma entries for item_id\n1: {:?}\n2: {:?}", - previous_entry, association_entry - ); - // It's technically possible to make sense of this situation by merging ipma - // boxes, but this is a "shall" requirement, so we'd only do it in - // ParseStrictness::Permissive mode, and this hasn't shown up in the wild - return Err(Error::InvalidData( - "There shall be at most one occurrence of a given item_ID, \ - in the set of ItemPropertyAssociationBox boxes \ - per ISOBMFF (ISO 14496-12:2020) § 8.11.14.1", - )); - } - - const TRANSFORM_ORDER_ERROR: &str = - "These properties, if used, shall be indicated to be applied \ - in the following order: clean aperture first, then rotation, \ - then mirror. \ - per MIAF (ISO/IEC 23000-22:2019) § 7.3.6.7"; - const TRANSFORM_ORDER: &[BoxType] = &[ - BoxType::CleanApertureBox, - BoxType::ImageRotation, - BoxType::ImageMirror, - ]; - let mut prev_transform_index = None; - // Realistically, there should only ever be 1 nclx and 1 icc - let mut colour_type_indexes: TryHashMap<FourCC, PropertyIndex> = - TryHashMap::with_capacity(2)?; - - for a in &association_entry.associations { - if a.property_index == PropertyIndex(0) { - if a.essential { - fail_if( - strictness != ParseStrictness::Permissive, - "the essential indicator shall be 0 for property index 0 \ - per ISOBMFF (ISO 14496-12:2020 § 8.11.14.3", - )?; - } - continue; - } - - if let Some(property) = properties.get(&a.property_index) { - assert!(brand == MIF1_BRAND); - match property { - ItemProperty::AV1Config(_) - | ItemProperty::Mirroring(_) - | ItemProperty::Rotation(_) => { - if !a.essential { - warn!("{:?} is invalid", property); - // This is a "shall", but it is likely to change, so only - // fail if using strict parsing. - // See https://github.com/mozilla/mp4parse-rust/issues/284 - fail_if( - strictness == ParseStrictness::Strict, - "All transformative properties associated with coded and \ - derived images required or conditionally required by this \ - document shall be marked as essential \ - per MIAF (ISO 23000-22:2019) § 7.3.9", - )?; - } - } - // XXX this is contrary to the published specification; see doc comment - // at the beginning of this function for more details - ItemProperty::Colour(colr) => { - let colour_type = colr.colour_type(); - if let Some(prev_colr_index) = colour_type_indexes.get(&colour_type) { - warn!( - "Multiple '{}' type colr associations with {:?}: {:?} and {:?}", - colour_type, - association_entry.item_id, - a.property_index, - prev_colr_index - ); - fail_if( - strictness != ParseStrictness::Permissive, - "Each item shall have at most one property association with a - ColourInformationBox (colr) for a given value of colour_type \ - per HEIF (ISO/IEC DIS 23008-12) § 6.5.5.1", - )?; - } else { - colour_type_indexes.insert(colour_type, a.property_index)?; - } - } - _ => {} - } - - if let Some(transform_index) = TRANSFORM_ORDER - .iter() - .position(|t| *t == property.box_type()) - { - if let Some(prev) = prev_transform_index { - if prev >= transform_index { - error!( - "{:?} after {:?}", - TRANSFORM_ORDER[transform_index], TRANSFORM_ORDER[prev] - ); - return Err(Error::InvalidData(TRANSFORM_ORDER_ERROR)); - } - } - prev_transform_index = Some(transform_index); - } - } else { - error!( - "Missing property at {:?} for {:?}", - a.property_index, association_entry.item_id - ); - fail_if( - strictness != ParseStrictness::Permissive, - "Invalid property index in ipma", - )?; - } - } - association_entries.push(association_entry)? - } - - check_parser_state!(b.content); - } - - let iprp = ItemPropertiesBox { - properties, - association_entries, - }; - trace!("read_iprp -> {:#?}", iprp); - Ok(iprp) -} - -/// See ISOBMFF (ISO 14496-12:2020 § 8.11.14.1 -#[derive(Debug)] -pub enum ItemProperty { - AuxiliaryType(AuxiliaryTypeProperty), - AV1Config(AV1ConfigBox), - Channels(PixelInformation), - Colour(ColourInformation), - ImageSpatialExtents(ImageSpatialExtentsProperty), - Mirroring(ImageMirror), - Rotation(ImageRotation), - /// Necessary to validate property indices in read_iprp - Unsupported(BoxType), -} - -impl ItemProperty { - fn box_type(&self) -> BoxType { - match self { - ItemProperty::AuxiliaryType(_) => BoxType::AuxiliaryTypeProperty, - ItemProperty::AV1Config(_) => BoxType::AV1CodecConfigurationBox, - ItemProperty::Colour(_) => BoxType::ColourInformationBox, - ItemProperty::Mirroring(_) => BoxType::ImageMirror, - ItemProperty::Rotation(_) => BoxType::ImageRotation, - ItemProperty::ImageSpatialExtents(_) => BoxType::ImageSpatialExtentsProperty, - ItemProperty::Channels(_) => BoxType::PixelInformationBox, - ItemProperty::Unsupported(box_type) => *box_type, - } - } -} - -#[derive(Debug)] -struct ItemPropertyAssociationEntry { - item_id: ItemId, - associations: TryVec<Association>, -} - -/// For storing ItemPropertyAssociation data -/// See ISOBMFF (ISO 14496-12:2020 § 8.11.14.1 -#[derive(Debug)] -struct Association { - essential: bool, - property_index: PropertyIndex, -} - -/// See ISOBMFF (ISO 14496-12:2020 § 8.11.14.1 -/// -/// The properties themselves are stored in `properties`, but the items they're -/// associated with are stored in `association_entries`. It's necessary to -/// maintain this indirection because multiple items can reference the same -/// property. For example, both the primary item and alpha item can share the -/// same [`ImageSpatialExtentsProperty`]. -#[derive(Debug, Default)] -pub struct ItemPropertiesBox { - /// `ItemPropertyContainerBox property_container` in the spec - properties: TryHashMap<PropertyIndex, ItemProperty>, - /// `ItemPropertyAssociationBox association[]` in the spec - association_entries: TryVec<ItemPropertyAssociationEntry>, -} - -impl ItemPropertiesBox { - /// For displayable images `av1C`, `pixi` and `ispe` are mandatory, `colr` - /// is typically included too, so we might as well use an even power of 2. - const MIN_PROPERTIES: usize = 4; - - fn is_alpha(&self, item_id: ItemId) -> bool { - match self.get(item_id, BoxType::AuxiliaryTypeProperty) { - Ok(Some(ItemProperty::AuxiliaryType(urn))) => { - urn.aux_type.as_slice() == "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha".as_bytes() - } - Ok(Some(other_property)) => panic!("property key mismatch: {:?}", other_property), - Ok(None) => false, - Err(e) => { - error!( - "is_alpha: Error checking AuxiliaryTypeProperty ({}), returning false", - e - ); - false - } - } - } - - fn get_ispe(&self, item_id: ItemId) -> Result<Option<&ImageSpatialExtentsProperty>> { - if let Some(ItemProperty::ImageSpatialExtents(ispe)) = - self.get(item_id, BoxType::ImageSpatialExtentsProperty)? - { - Ok(Some(ispe)) - } else { - Ok(None) - } - } - - fn get(&self, item_id: ItemId, property_type: BoxType) -> Result<Option<&ItemProperty>> { - match self - .get_multiple(item_id, |prop| prop.box_type() == property_type)? - .as_slice() - { - &[] => Ok(None), - &[single_value] => Ok(Some(single_value)), - multiple_values => { - error!( - "Multiple values for {:?}: {:?}", - property_type, multiple_values - ); - // TODO: add test - Err(Error::InvalidData("conflicting item property values")) - } - } - } - - fn get_multiple( - &self, - item_id: ItemId, - filter: impl Fn(&ItemProperty) -> bool, - ) -> Result<TryVec<&ItemProperty>> { - let mut values = TryVec::new(); - for entry in &self.association_entries { - for a in &entry.associations { - if entry.item_id == item_id { - match self.properties.get(&a.property_index) { - Some(ItemProperty::Unsupported(_)) => {} - Some(property) if filter(property) => values.push(property)?, - _ => {} - } - } - } - } - - Ok(values) - } -} - -/// An upper bound which can be used to check overflow at compile time -trait UpperBounded { - const MAX: u64; -} - -/// Implement type $name as a newtype wrapper around an unsigned int which -/// implements the UpperBounded trait. -macro_rules! impl_bounded { - ( $name:ident, $inner:ty ) => { - #[derive(Clone, Copy)] - pub struct $name($inner); - - impl $name { - pub const fn new(n: $inner) -> Self { - Self(n) - } - - #[allow(dead_code)] - pub fn get(self) -> $inner { - self.0 - } - } - - impl UpperBounded for $name { - const MAX: u64 = <$inner>::MAX as u64; - } - }; -} - -/// Implement type $name as a type representing the product of two unsigned ints -/// which implements the UpperBounded trait. -macro_rules! impl_bounded_product { - ( $name:ident, $multiplier:ty, $multiplicand:ty, $inner:ty) => { - #[derive(Clone, Copy)] - pub struct $name($inner); - - impl $name { - pub fn new(value: $inner) -> Self { - assert!(value <= Self::MAX); - Self(value) - } - - pub fn get(self) -> $inner { - self.0 - } - } - - impl UpperBounded for $name { - const MAX: u64 = <$multiplier>::MAX * <$multiplicand>::MAX; - } - }; -} - -mod bounded_uints { - use UpperBounded; - - impl_bounded!(U8, u8); - impl_bounded!(U16, u16); - impl_bounded!(U32, u32); - impl_bounded!(U64, u64); - - impl_bounded_product!(U32MulU8, U32, U8, u64); - impl_bounded_product!(U32MulU16, U32, U16, u64); - - impl UpperBounded for std::num::NonZeroU8 { - const MAX: u64 = u8::MAX as u64; - } -} - -use bounded_uints::*; - -/// Implement the multiplication operator for $lhs * $rhs giving $output, which -/// is internally represented as $inner. The operation is statically checked -/// to ensure the product won't overflow $inner, nor exceed <$output>::MAX. -macro_rules! impl_mul { - ( ($lhs:ty , $rhs:ty) => ($output:ty, $inner:ty) ) => { - impl std::ops::Mul<$rhs> for $lhs { - type Output = $output; - - fn mul(self, rhs: $rhs) -> Self::Output { - static_assertions::const_assert!( - <$output as UpperBounded>::MAX <= <$inner>::MAX as u64 - ); - static_assertions::const_assert!( - <$lhs as UpperBounded>::MAX * <$rhs as UpperBounded>::MAX - <= <$output as UpperBounded>::MAX - ); - - let lhs: $inner = self.get().into(); - let rhs: $inner = rhs.get().into(); - Self::Output::new(lhs.checked_mul(rhs).expect("infallible")) - } - } - }; -} - -impl_mul!((U8, std::num::NonZeroU8) => (U16, u16)); -impl_mul!((U32, std::num::NonZeroU8) => (U32MulU8, u64)); -impl_mul!((U32, U16) => (U32MulU16, u64)); - -impl std::ops::Add<U32MulU16> for U32MulU8 { - type Output = U64; - - fn add(self, rhs: U32MulU16) -> Self::Output { - static_assertions::const_assert!(U32MulU8::MAX + U32MulU16::MAX < U64::MAX); - let lhs: u64 = self.get(); - let rhs: u64 = rhs.get(); - Self::Output::new(lhs.checked_add(rhs).expect("infallible")) - } -} - -const MAX_IPMA_ASSOCIATION_COUNT: U8 = U8::new(u8::MAX); - -/// After reading only the `entry_count` field of an ipma box, we can check its -/// basic validity and calculate (assuming validity) the number of associations -/// which will be contained (allowing preallocation of the storage). -/// All the arithmetic is compile-time verified to not overflow via supporting -/// types implementing the UpperBounded trait. Types are declared explicitly to -/// show there isn't any accidental inference to primitive types. -/// -/// See ISOBMFF (ISO 14496-12:2020 § 8.11.14.1 -fn calculate_ipma_total_associations( - version: u8, - bytes_left: u64, - entry_count: U32, - num_association_bytes: std::num::NonZeroU8, -) -> Result<usize> { - let min_entry_bytes = - std::num::NonZeroU8::new(1 /* association_count */ + if version == 0 { 2 } else { 4 }) - .unwrap(); - - let total_non_association_bytes: U32MulU8 = entry_count * min_entry_bytes; - let total_association_bytes: u64; - - if let Some(difference) = bytes_left.checked_sub(total_non_association_bytes.get()) { - // All the storage for the `essential` and `property_index` parts (assuming a valid ipma box size) - total_association_bytes = difference; - } else { - return Err(Error::InvalidData( - "ipma box below minimum size for entry_count", - )); - } - - let max_association_bytes_per_entry: U16 = MAX_IPMA_ASSOCIATION_COUNT * num_association_bytes; - let max_total_association_bytes: U32MulU16 = entry_count * max_association_bytes_per_entry; - let max_bytes_left: U64 = total_non_association_bytes + max_total_association_bytes; - - if bytes_left > max_bytes_left.get() { - return Err(Error::InvalidData( - "ipma box exceeds maximum size for entry_count", - )); - } - - let total_associations: u64 = total_association_bytes / u64::from(num_association_bytes.get()); - - Ok(total_associations.try_into()?) -} - -/// Parse an ItemPropertyAssociation box -/// -/// See ISOBMFF (ISO 14496-12:2020 § 8.11.14.1 -fn read_ipma<T: Read>( - src: &mut BMFFBox<T>, - strictness: ParseStrictness, - version: u8, - flags: u32, -) -> Result<TryVec<ItemPropertyAssociationEntry>> { - let entry_count = be_u32(src)?; - let num_association_bytes = - std::num::NonZeroU8::new(if flags & 1 == 1 { 2 } else { 1 }).unwrap(); - - let total_associations = calculate_ipma_total_associations( - version, - src.bytes_left(), - U32::new(entry_count), - num_association_bytes, - )?; - // Assuming most items will have at least `MIN_PROPERTIES` and knowing the - // total number of item -> property associations (`total_associations`), - // we can provide a good estimate for how many elements we'll need in this - // vector, even though we don't know precisely how many items there will be - // properties for. - let mut entries = TryVec::<ItemPropertyAssociationEntry>::with_capacity( - total_associations / ItemPropertiesBox::MIN_PROPERTIES, - )?; - - for _ in 0..entry_count { - let item_id = ItemId::read(src, version)?; - - if let Some(previous_association) = entries.last() { - #[allow(clippy::comparison_chain)] - if previous_association.item_id > item_id { - return Err(Error::InvalidData( - "Each ItemPropertyAssociation box shall be ordered by increasing item_ID", - )); - } else if previous_association.item_id == item_id { - return Err(Error::InvalidData("There shall be at most one association box for each item_ID, in any ItemPropertyAssociation box")); - } - } - - let association_count = src.read_u8()?; - let mut associations = TryVec::with_capacity(association_count.to_usize())?; - for _ in 0..association_count { - let association = src - .take(num_association_bytes.get().into()) - .read_into_try_vec()?; - let mut association = BitReader::new(association.as_slice()); - let essential = association.read_bool()?; - let property_index = - PropertyIndex(association.read_u16(association.remaining().try_into()?)?); - associations.push(Association { - essential, - property_index, - })?; - } - - entries.push(ItemPropertyAssociationEntry { - item_id, - associations, - })?; - } - - check_parser_state!(src.content); - - if version != 0 { - if let Some(ItemPropertyAssociationEntry { - item_id: max_item_id, - .. - }) = entries.last() - { - if *max_item_id <= ItemId(u16::MAX.into()) { - fail_if( - strictness == ParseStrictness::Strict, - "The ipma version 0 should be used unless 32-bit item_ID values are needed \ - per ISOBMFF (ISO 14496-12:2020 § 8.11.14.1", - )?; - } - } - } - - trace!("read_ipma -> {:#?}", entries); - - Ok(entries) -} - -/// Parse an ItemPropertyContainerBox -/// -/// For unsupported properties that we know about, return specific -/// [`Status`] UnsupportedXXXX variants. Unless running in -/// [`ParseStrictness::Permissive`] mode, in which case, unsupported properties -/// will be ignored. -/// -/// See ISOBMFF (ISO 14496-12:2020 § 8.11.14.1 -fn read_ipco<T: Read>( - src: &mut BMFFBox<T>, - strictness: ParseStrictness, -) -> Result<TryHashMap<PropertyIndex, ItemProperty>> { - let mut properties = TryHashMap::with_capacity(ItemPropertiesBox::MIN_PROPERTIES)?; - - let mut index = PropertyIndex(1); // ipma uses 1-based indexing - let mut iter = src.box_iter(); - while let Some(mut b) = iter.next_box()? { - if let Some(property) = match b.head.name { - BoxType::AuxiliaryTypeProperty => Some(ItemProperty::AuxiliaryType(read_auxc(&mut b)?)), - BoxType::AV1CodecConfigurationBox => Some(ItemProperty::AV1Config(read_av1c(&mut b)?)), - BoxType::AV1LayeredImageIndexingProperty - if strictness != ParseStrictness::Permissive => - { - return Err(Error::from(Status::UnsupportedA1lx)) - } - BoxType::CleanApertureBox if strictness != ParseStrictness::Permissive => { - return Err(Error::from(Status::UnsupportedClap)) - } - BoxType::ColourInformationBox => { - Some(ItemProperty::Colour(read_colr(&mut b, strictness)?)) - } - BoxType::ImageMirror => Some(ItemProperty::Mirroring(read_imir(&mut b)?)), - BoxType::ImageRotation => Some(ItemProperty::Rotation(read_irot(&mut b)?)), - BoxType::ImageSpatialExtentsProperty => { - Some(ItemProperty::ImageSpatialExtents(read_ispe(&mut b)?)) - } - BoxType::LayerSelectorProperty if strictness != ParseStrictness::Permissive => { - return Err(Error::from(Status::UnsupportedLsel)) - } - BoxType::OperatingPointSelectorProperty - if strictness != ParseStrictness::Permissive => - { - return Err(Error::from(Status::UnsupportedA1op)) - } - BoxType::PixelInformationBox => Some(ItemProperty::Channels(read_pixi(&mut b)?)), - other_box_type => { - // Though we don't do anything with other property types, we still store - // a record at the index to identify invalid indices in ipma boxes - skip_box_remain(&mut b)?; - let item_property = ItemProperty::Unsupported(other_box_type); - debug!("Storing empty record {:?}", item_property); - Some(item_property) - } - } { - properties.insert(index, property)?; - } - - index = PropertyIndex( - index - .0 - .checked_add(1) // must include ignored properties to have correct indexes - .ok_or(Error::InvalidData("ipco index overflow"))?, - ); - - check_parser_state!(b.content); - } - - Ok(properties) -} - -#[repr(C)] -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct ImageSpatialExtentsProperty { - image_width: u32, - image_height: u32, -} - -/// Parse image spatial extents property -/// -/// See HEIF (ISO 23008-12:2017) § 6.5.3.1 -fn read_ispe<T: Read>(src: &mut BMFFBox<T>) -> Result<ImageSpatialExtentsProperty> { - if read_fullbox_version_no_flags(src)? != 0 { - return Err(Error::Unsupported("ispe version")); - } - - let image_width = be_u32(src)?; - let image_height = be_u32(src)?; - - Ok(ImageSpatialExtentsProperty { - image_width, - image_height, - }) -} - -#[derive(Debug)] -pub struct PixelInformation { - bits_per_channel: TryVec<u8>, -} - -/// Parse pixel information -/// See HEIF (ISO 23008-12:2017) § 6.5.6 -fn read_pixi<T: Read>(src: &mut BMFFBox<T>) -> Result<PixelInformation> { - let version = read_fullbox_version_no_flags(src)?; - if version != 0 { - return Err(Error::Unsupported("pixi version")); - } - - let num_channels = src.read_u8()?; - let mut bits_per_channel = TryVec::with_capacity(num_channels.to_usize())?; - let num_channels_read = src.try_read_to_end(&mut bits_per_channel)?; - - if u8::try_from(num_channels_read)? != num_channels { - return Err(Error::InvalidData("invalid num_channels")); - } - - check_parser_state!(src.content); - Ok(PixelInformation { bits_per_channel }) -} - -/// Despite [Rec. ITU-T H.273] (12/2016) defining the CICP fields as having a -/// range of 0-255, and only a small fraction of those values being used, -/// ISOBMFF (ISO 14496-12:2020) § 12.1.5 defines them as 16-bit values in the -/// `colr` box. Since we have no use for the additional range, and it would -/// complicate matters later, we fallibly convert before storing the input. -/// -/// [Rec. ITU-T H.273]: https://www.itu.int/rec/T-REC-H.273-201612-I/en -#[repr(C)] -#[derive(Debug)] -pub struct NclxColourInformation { - colour_primaries: u8, - transfer_characteristics: u8, - matrix_coefficients: u8, - full_range_flag: bool, -} - -/// The raw bytes of the ICC profile -#[repr(C)] -pub struct IccColourInformation { - bytes: TryVec<u8>, -} - -impl fmt::Debug for IccColourInformation { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("IccColourInformation") - .field("data", &format_args!("{} bytes", self.bytes.len())) - .finish() - } -} - -#[repr(C)] -#[derive(Debug)] -pub enum ColourInformation { - Nclx(NclxColourInformation), - Icc(IccColourInformation, FourCC), -} - -impl ColourInformation { - fn colour_type(&self) -> FourCC { - match self { - Self::Nclx(_) => FourCC::from(*b"nclx"), - Self::Icc(_, colour_type) => colour_type.clone(), - } - } -} - -/// Parse colour information -/// See ISOBMFF (ISO 14496-12:2020) § 12.1.5 -fn read_colr<T: Read>( - src: &mut BMFFBox<T>, - strictness: ParseStrictness, -) -> Result<ColourInformation> { - let colour_type = be_u32(src)?.to_be_bytes(); - - match &colour_type { - b"nclx" => { - const NUM_RESERVED_BITS: u8 = 7; - let colour_primaries = be_u16(src)?.try_into()?; - let transfer_characteristics = be_u16(src)?.try_into()?; - let matrix_coefficients = be_u16(src)?.try_into()?; - let bytes = src.read_into_try_vec()?; - let mut bit_reader = BitReader::new(&bytes); - let full_range_flag = bit_reader.read_bool()?; - if bit_reader.remaining() != NUM_RESERVED_BITS.into() { - error!( - "read_colr expected {} reserved bits, found {}", - NUM_RESERVED_BITS, - bit_reader.remaining() - ); - return Err(Error::InvalidData("Unexpected size for colr box")); - } - if bit_reader.read_u8(NUM_RESERVED_BITS)? != 0 { - fail_if( - strictness != ParseStrictness::Permissive, - "The 7 reserved bits at the end of the ColourInformationBox \ - for colour_type == 'nclx' must be 0 \ - per ISOBMFF (ISO 14496-12:2020) § 12.1.5.2", - )?; - } - - Ok(ColourInformation::Nclx(NclxColourInformation { - colour_primaries, - transfer_characteristics, - matrix_coefficients, - full_range_flag, - })) - } - b"rICC" | b"prof" => Ok(ColourInformation::Icc( - IccColourInformation { - bytes: src.read_into_try_vec()?, - }, - FourCC::from(colour_type), - )), - _ => { - error!("read_colr colour_type: {:?}", colour_type); - Err(Error::InvalidData( - "Unsupported colour_type for ColourInformationBox", - )) - } - } -} - -#[repr(C)] -#[derive(Clone, Copy, Debug)] -/// Rotation in the positive (that is, anticlockwise) direction -/// Visualized in terms of starting with (⥠) UPWARDS HARPOON WITH BARB LEFT FROM BAR -/// similar to a DIGIT ONE (1) -pub enum ImageRotation { - /// ⥠ UPWARDS HARPOON WITH BARB LEFT FROM BAR - D0, - /// ⥞ LEFTWARDS HARPOON WITH BARB DOWN FROM BAR - D90, - /// ⥝ DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR - D180, - /// ⥛ RIGHTWARDS HARPOON WITH BARB UP FROM BAR - D270, -} - -/// Parse image rotation box -/// See HEIF (ISO 23008-12:2017) § 6.5.10 -fn read_irot<T: Read>(src: &mut BMFFBox<T>) -> Result<ImageRotation> { - let irot = src.read_into_try_vec()?; - let mut irot = BitReader::new(&irot); - let _reserved = irot.read_u8(6)?; - let image_rotation = match irot.read_u8(2)? { - 0 => ImageRotation::D0, - 1 => ImageRotation::D90, - 2 => ImageRotation::D180, - 3 => ImageRotation::D270, - _ => unreachable!(), - }; - - check_parser_state!(src.content); - - Ok(image_rotation) -} - -/// The axis about which the image is mirrored (opposite of flip) -/// Visualized in terms of starting with (⥠) UPWARDS HARPOON WITH BARB LEFT FROM BAR -/// similar to a DIGIT ONE (1) -#[repr(C)] -#[derive(Debug)] -pub enum ImageMirror { - /// top and bottom parts exchanged - /// ⥡ DOWNWARDS HARPOON WITH BARB LEFT FROM BAR - TopBottom, - /// left and right parts exchanged - /// ⥜ UPWARDS HARPOON WITH BARB RIGHT FROM BAR - LeftRight, -} - -/// Parse image mirroring box -/// See HEIF (ISO 23008-12:2017) § 6.5.12<br /> -/// Note: [ISO/IEC 23008-12:2017/DAmd 2](https://www.iso.org/standard/81688.html) -/// reverses the interpretation of the 'imir' box in § 6.5.12.3: -/// > `axis` specifies a vertical (`axis` = 0) or horizontal (`axis` = 1) axis -/// > for the mirroring operation. -/// -/// is replaced with: -/// > `mode` specifies how the mirroring is performed: 0 indicates that the top -/// > and bottom parts of the image are exchanged; 1 specifies that the left and -/// > right parts are exchanged. -/// > -/// > NOTE: In Exif, orientation tag can be used to signal mirroring operations. -/// > Exif orientation tag 4 corresponds to `mode` = 0 of `ImageMirror`, and -/// > Exif orientation tag 2 corresponds to `mode` = 1 accordingly. -/// -/// This implementation conforms to the text in Draft Amendment 2, which is the -/// opposite of the published standard as of 4 June 2021. -fn read_imir<T: Read>(src: &mut BMFFBox<T>) -> Result<ImageMirror> { - let imir = src.read_into_try_vec()?; - let mut imir = BitReader::new(&imir); - let _reserved = imir.read_u8(7)?; - let image_mirror = match imir.read_u8(1)? { - 0 => ImageMirror::TopBottom, - 1 => ImageMirror::LeftRight, - _ => unreachable!(), - }; - - check_parser_state!(src.content); - - Ok(image_mirror) -} - -/// See HEIF (ISO 23008-12:2017) § 6.5.8 -#[derive(Debug, PartialEq)] -pub struct AuxiliaryTypeProperty { - aux_type: TryString, - aux_subtype: TryString, -} - -/// Parse image properties for auxiliary images -/// See HEIF (ISO 23008-12:2017) § 6.5.8 -fn read_auxc<T: Read>(src: &mut BMFFBox<T>) -> Result<AuxiliaryTypeProperty> { - let version = read_fullbox_version_no_flags(src)?; - if version != 0 { - return Err(Error::Unsupported("auxC version")); - } - - let mut aux = TryString::new(); - src.try_read_to_end(&mut aux)?; - - let (aux_type, aux_subtype): (TryString, TryVec<u8>); - if let Some(nul_byte_pos) = aux.iter().position(|&b| b == b'\0') { - let (a, b) = aux.as_slice().split_at(nul_byte_pos); - aux_type = a.try_into()?; - aux_subtype = (&b[1..]).try_into()?; - } else { - aux_type = aux; - aux_subtype = TryVec::new(); - } - - Ok(AuxiliaryTypeProperty { - aux_type, - aux_subtype, - }) -} - -/// Parse an item location box inside a meta box -/// See ISOBMFF (ISO 14496-12:2020) § 8.11.3 -fn read_iloc<T: Read>(src: &mut BMFFBox<T>) -> Result<TryHashMap<ItemId, ItemLocationBoxItem>> { - let version: IlocVersion = read_fullbox_version_no_flags(src)?.try_into()?; - - let iloc = src.read_into_try_vec()?; - let mut iloc = BitReader::new(&iloc); - - let offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?; - let length_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?; - let base_offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?; - - let index_size: Option<IlocFieldSize> = match version { - IlocVersion::One | IlocVersion::Two => Some(iloc.read_u8(4)?.try_into()?), - IlocVersion::Zero => { - let _reserved = iloc.read_u8(4)?; - None - } - }; - - let item_count = match version { - IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?, - IlocVersion::Two => iloc.read_u32(32)?, - }; - - let mut items = TryHashMap::with_capacity(item_count.to_usize())?; - - for _ in 0..item_count { - let item_id = ItemId(match version { - IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?, - IlocVersion::Two => iloc.read_u32(32)?, - }); - - // The spec isn't entirely clear how an `iloc` should be interpreted for version 0, - // which has no `construction_method` field. It does say: - // "For maximum compatibility, version 0 of this box should be used in preference to - // version 1 with `construction_method==0`, or version 2 when possible." - // We take this to imply version 0 can be interpreted as using file offsets. - let construction_method = match version { - IlocVersion::Zero => ConstructionMethod::File, - IlocVersion::One | IlocVersion::Two => { - let _reserved = iloc.read_u16(12)?; - match iloc.read_u16(4)? { - 0 => ConstructionMethod::File, - 1 => ConstructionMethod::Idat, - 2 => return Err(Error::Unsupported("construction_method 'item_offset' is not supported")), - _ => return Err(Error::InvalidData("construction_method is taken from the set 0, 1 or 2 per ISOBMFF (ISO 14496-12:2020) § 8.11.3.3")) - } - } - }; - - let data_reference_index = iloc.read_u16(16)?; - - if data_reference_index != 0 { - return Err(Error::Unsupported( - "external file references (iloc.data_reference_index != 0) are not supported", - )); - } - - let base_offset = iloc.read_u64(base_offset_size.as_bits())?; - let extent_count = iloc.read_u16(16)?; - - if extent_count < 1 { - return Err(Error::InvalidData( - "extent_count must have a value 1 or greater per ISOBMFF (ISO 14496-12:2020) § 8.11.3.3", - )); - } - - // "If only one extent is used (extent_count = 1) then either or both of the - // offset and length may be implied" - if extent_count != 1 - && (offset_size == IlocFieldSize::Zero || length_size == IlocFieldSize::Zero) - { - return Err(Error::InvalidData( - "extent_count != 1 requires explicit offset and length per ISOBMFF (ISO 14496-12:2020) § 8.11.3.3", - )); - } - - let mut extents = TryVec::with_capacity(extent_count.to_usize())?; - - for _ in 0..extent_count { - // Parsed but currently ignored, see `Extent` - let _extent_index = match &index_size { - None | Some(IlocFieldSize::Zero) => None, - Some(index_size) => { - debug_assert!(version == IlocVersion::One || version == IlocVersion::Two); - Some(iloc.read_u64(index_size.as_bits())?) - } - }; - - // Per ISOBMFF (ISO 14496-12:2020) § 8.11.3.1: - // "If the offset is not identified (the field has a length of zero), then the - // beginning of the source (offset 0) is implied" - // This behavior will follow from BitReader::read_u64(0) -> 0. - let extent_offset = iloc.read_u64(offset_size.as_bits())?; - let extent_length = iloc.read_u64(length_size.as_bits())?.try_into()?; - - // "If the length is not specified, or specified as zero, then the entire length of - // the source is implied" (ibid) - let offset = base_offset - .checked_add(extent_offset) - .ok_or(Error::InvalidData("offset calculation overflow"))?; - let extent = if extent_length == 0 { - Extent::ToEnd { offset } - } else { - Extent::WithLength { - offset, - len: extent_length, - } - }; - - extents.push(extent)?; - } - - let loc = ItemLocationBoxItem { - construction_method, - extents, - }; - - if items.insert(item_id, loc)?.is_some() { - return Err(Error::InvalidData("duplicate item_ID in iloc")); - } - } - - if iloc.remaining() == 0 { - Ok(items) - } else { - Err(Error::InvalidData("invalid iloc size")) - } -} - -/// Read the contents of a box, including sub boxes. -pub fn read_mp4<T: Read>(f: &mut T) -> Result<MediaContext> { - let mut context = None; - let mut found_ftyp = false; - let mut brand: Option<FourCC> = None; - // TODO(kinetik): Top-level parsing should handle zero-sized boxes - // rather than throwing an error. - let mut iter = BoxIter::new(f); - while let Some(mut b) = iter.next_box()? { - // box ordering: ftyp before any variable length box (inc. moov), - // but may not be first box in file if file signatures etc. present - // fragmented mp4 order: ftyp, moov, pairs of moof/mdat (1-multiple), mfra - - // "special": uuid, wide (= 8 bytes) - // isom: moov, mdat, free, skip, udta, ftyp, moof, mfra - // iso2: pdin, meta - // iso3: meco - // iso5: styp, sidx, ssix, prft - // unknown, maybe: id32 - - // qt: pnot - - // possibly allow anything where all printable and/or all lowercase printable - // "four printable characters from the ISO 8859-1 character set" - match b.head.name { - BoxType::FileTypeBox => { - let ftyp = read_ftyp(&mut b)?; - found_ftyp = true; - debug!("{:?}", ftyp); - brand = Some(ftyp.major_brand); - } - BoxType::MovieBox => { - if let Some(brand) = &brand { - if context.is_none() { - context = Some(MediaContext::default()); - } - context.as_mut().unwrap().brand = brand.clone(); - } - context = Some(read_moov(&mut b, context)?); - } - #[cfg(feature = "meta-xml")] - BoxType::MetadataBox => { - if let Some(ctx) = &mut context { - ctx.metadata = Some(read_meta(&mut b)); - } - } - _ => skip_box_content(&mut b)?, - }; - check_parser_state!(b.content); - if context.is_some() { - debug!( - "found moov {}, could stop pure 'moov' parser now", - if found_ftyp { - "and ftyp" - } else { - "but no ftyp" - } - ); - } - } - - // XXX(kinetik): This isn't perfect, as a "moov" with no contents is - // treated as okay but we haven't found anything useful. Needs more - // thought for clearer behaviour here. - context.ok_or(Error::NoMoov) -} - -/// Parse a Movie Header Box -/// See ISOBMFF (ISO 14496-12:2020) § 8.2.2 -fn parse_mvhd<T: Read>(f: &mut BMFFBox<T>) -> Result<Option<MediaTimeScale>> { - let mvhd = read_mvhd(f)?; - debug!("{:?}", mvhd); - if mvhd.timescale == 0 { - return Err(Error::InvalidData("zero timescale in mdhd")); - } - let timescale = Some(MediaTimeScale(u64::from(mvhd.timescale))); - Ok(timescale) -} - -/// Parse a Movie Box -/// See ISOBMFF (ISO 14496-12:2020) § 8.2.1 -/// Note that despite the spec indicating "exactly one" moov box should exist at -/// the file container level, we support reading and merging multiple moov boxes -/// such as with tests/test_case_1185230.mp4. -fn read_moov<T: Read>(f: &mut BMFFBox<T>, context: Option<MediaContext>) -> Result<MediaContext> { - let MediaContext { - brand, - mut timescale, - mut tracks, - mut mvex, - mut psshs, - mut userdata, - #[cfg(feature = "meta-xml")] - metadata, - #[cfg(feature = "craw")] - mut craw, - } = context.unwrap_or_default(); - - let mut iter = f.box_iter(); - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::UuidBox => { - debug!("{:?}", b.head); - let mut box_known = false; - #[cfg(feature = "craw")] - { - if brand == FourCC::from(*b"crx ") && b.head.uuid == Some(craw::HEADER_UUID) { - let crawheader = craw::parse_craw_header(&mut b)?; - craw = Some(crawheader); - box_known = true; - } - } - if !box_known { - debug!( - "Unknown UUID box {:?} (skipping)", - b.head.uuid.as_ref().unwrap() - ); - skip_box_content(&mut b)?; - } - } - BoxType::MovieHeaderBox => { - timescale = parse_mvhd(&mut b)?; - } - BoxType::TrackBox => { - let mut track = Track::new(tracks.len()); - read_trak(&mut b, &mut track, &brand)?; - tracks.push(track)?; - } - BoxType::MovieExtendsBox => { - mvex = Some(read_mvex(&mut b)?); - debug!("{:?}", mvex); - } - BoxType::ProtectionSystemSpecificHeaderBox => { - let pssh = read_pssh(&mut b)?; - debug!("{:?}", pssh); - psshs.push(pssh)?; - } - BoxType::UserdataBox => { - userdata = Some(read_udta(&mut b)); - debug!("{:?}", userdata); - if let Some(Err(_)) = userdata { - // There was an error parsing userdata. Such failures are not fatal to overall - // parsing, just skip the rest of the box. - skip_box_remain(&mut b)?; - } - } - _ => skip_box_content(&mut b)?, - }; - check_parser_state!(b.content); - } - - Ok(MediaContext { - brand, - timescale, - tracks, - mvex, - psshs, - userdata, - #[cfg(feature = "meta-xml")] - metadata, - #[cfg(feature = "craw")] - craw, - }) -} - -fn read_pssh<T: Read>(src: &mut BMFFBox<T>) -> Result<ProtectionSystemSpecificHeaderBox> { - let len = src.bytes_left(); - let mut box_content = read_buf(src, len)?; - let (system_id, kid, data) = { - let pssh = &mut Cursor::new(&box_content); - - let (version, _) = read_fullbox_extra(pssh)?; - - let system_id = read_buf(pssh, 16)?; - - let mut kid = TryVec::<ByteData>::new(); - if version > 0 { - let count = be_u32(pssh)?; - for _ in 0..count { - let item = read_buf(pssh, 16)?; - kid.push(item)?; - } - } - - let data_size = be_u32(pssh)?; - let data = read_buf(pssh, data_size.into())?; - - (system_id, kid, data) - }; - - let mut pssh_box = TryVec::new(); - write_be_u32(&mut pssh_box, src.head.size.try_into()?)?; - pssh_box.extend_from_slice(b"pssh")?; - pssh_box.append(&mut box_content)?; - - Ok(ProtectionSystemSpecificHeaderBox { - system_id, - kid, - data, - box_content: pssh_box, - }) -} - -/// Parse a Movie Extends Box -/// See ISOBMFF (ISO 14496-12:2020) § 8.8.1 -fn read_mvex<T: Read>(src: &mut BMFFBox<T>) -> Result<MovieExtendsBox> { - let mut iter = src.box_iter(); - let mut fragment_duration = None; - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::MovieExtendsHeaderBox => { - let duration = read_mehd(&mut b)?; - fragment_duration = Some(duration); - } - _ => skip_box_content(&mut b)?, - } - } - Ok(MovieExtendsBox { fragment_duration }) -} - -fn read_mehd<T: Read>(src: &mut BMFFBox<T>) -> Result<MediaScaledTime> { - let (version, _) = read_fullbox_extra(src)?; - let fragment_duration = match version { - 1 => be_u64(src)?, - 0 => u64::from(be_u32(src)?), - _ => return Err(Error::InvalidData("unhandled mehd version")), - }; - Ok(MediaScaledTime(fragment_duration)) -} - -/// Parse a Track Box -/// See ISOBMFF (ISO 14496-12:2020) § 8.3.1. -fn read_trak<T: Read>(f: &mut BMFFBox<T>, track: &mut Track, brand: &FourCC) -> Result<()> { - let mut iter = f.box_iter(); - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::TrackHeaderBox => { - let tkhd = read_tkhd(&mut b)?; - track.track_id = Some(tkhd.track_id); - track.tkhd = Some(tkhd.clone()); - debug!("{:?}", tkhd); - } - BoxType::EditBox => read_edts(&mut b, track)?, - BoxType::MediaBox => read_mdia(&mut b, track, brand)?, - _ => skip_box_content(&mut b)?, - }; - check_parser_state!(b.content); - } - Ok(()) -} - -fn read_edts<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> { - let mut iter = f.box_iter(); - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::EditListBox => { - let elst = read_elst(&mut b)?; - if elst.edits.is_empty() { - debug!("empty edit list"); - continue; - } - let mut empty_duration = 0; - let mut idx = 0; - if elst.edits[idx].media_time == -1 { - if elst.edits.len() < 2 { - debug!("expected additional edit, ignoring edit list"); - continue; - } - empty_duration = elst.edits[idx].segment_duration; - idx += 1; - } - track.empty_duration = Some(MediaScaledTime(empty_duration)); - let media_time = elst.edits[idx].media_time; - if media_time < 0 { - debug!("unexpected negative media time in edit"); - } - track.media_time = Some(TrackScaledTime::<u64>( - std::cmp::max(0, media_time) as u64, - track.id, - )); - if elst.edits.len() > 2 { - debug!("ignoring edit list with {} entries", elst.edits.len()); - } - debug!("{:?}", elst); - } - _ => skip_box_content(&mut b)?, - }; - check_parser_state!(b.content); - } - Ok(()) -} - -#[allow(clippy::type_complexity)] // Allow the complex return, maybe rework in future -fn parse_mdhd<T: Read>( - f: &mut BMFFBox<T>, - track: &mut Track, -) -> Result<( - MediaHeaderBox, - Option<TrackScaledTime<u64>>, - Option<TrackTimeScale<u64>>, -)> { - let mdhd = read_mdhd(f)?; - let duration = match mdhd.duration { - std::u64::MAX => None, - duration => Some(TrackScaledTime::<u64>(duration, track.id)), - }; - if mdhd.timescale == 0 { - return Err(Error::InvalidData("zero timescale in mdhd")); - } - let timescale = Some(TrackTimeScale::<u64>(u64::from(mdhd.timescale), track.id)); - Ok((mdhd, duration, timescale)) -} - -fn read_mdia<T: Read>(f: &mut BMFFBox<T>, track: &mut Track, brand: &FourCC) -> Result<()> { - let mut iter = f.box_iter(); - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::MediaHeaderBox => { - let (mdhd, duration, timescale) = parse_mdhd(&mut b, track)?; - track.duration = duration; - track.timescale = timescale; - debug!("{:?}", mdhd); - } - BoxType::HandlerBox => { - let hdlr = read_hdlr(&mut b, ParseStrictness::Permissive)?; - - match hdlr.handler_type.value.as_ref() { - b"vide" => track.track_type = TrackType::Video, - b"soun" => track.track_type = TrackType::Audio, - b"meta" => track.track_type = TrackType::Metadata, - _ => (), - } - debug!("{:?}", hdlr); - } - BoxType::MediaInformationBox => read_minf(&mut b, track, brand)?, - _ => skip_box_content(&mut b)?, - }; - check_parser_state!(b.content); - } - Ok(()) -} - -fn read_minf<T: Read>(f: &mut BMFFBox<T>, track: &mut Track, brand: &FourCC) -> Result<()> { - let mut iter = f.box_iter(); - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::SampleTableBox => read_stbl(&mut b, track, brand)?, - _ => skip_box_content(&mut b)?, - }; - check_parser_state!(b.content); - } - Ok(()) -} - -fn read_stbl<T: Read>(f: &mut BMFFBox<T>, track: &mut Track, brand: &FourCC) -> Result<()> { - let mut iter = f.box_iter(); - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::SampleDescriptionBox => { - let stsd = read_stsd(&mut b, track, brand)?; - debug!("{:?}", stsd); - track.stsd = Some(stsd); - } - BoxType::TimeToSampleBox => { - let stts = read_stts(&mut b)?; - debug!("{:?}", stts); - track.stts = Some(stts); - } - BoxType::SampleToChunkBox => { - let stsc = read_stsc(&mut b)?; - debug!("{:?}", stsc); - track.stsc = Some(stsc); - } - BoxType::SampleSizeBox => { - let stsz = read_stsz(&mut b)?; - debug!("{:?}", stsz); - track.stsz = Some(stsz); - } - BoxType::ChunkOffsetBox => { - let stco = read_stco(&mut b)?; - debug!("{:?}", stco); - track.stco = Some(stco); - } - BoxType::ChunkLargeOffsetBox => { - let co64 = read_co64(&mut b)?; - debug!("{:?}", co64); - track.stco = Some(co64); - } - BoxType::SyncSampleBox => { - let stss = read_stss(&mut b)?; - debug!("{:?}", stss); - track.stss = Some(stss); - } - BoxType::CompositionOffsetBox => { - let ctts = read_ctts(&mut b)?; - debug!("{:?}", ctts); - track.ctts = Some(ctts); - } - _ => skip_box_content(&mut b)?, - }; - check_parser_state!(b.content); - } - Ok(()) -} - -/// Parse an ftyp box. -/// See ISOBMFF (ISO 14496-12:2020) § 4.3 -fn read_ftyp<T: Read>(src: &mut BMFFBox<T>) -> Result<FileTypeBox> { - let major = be_u32(src)?; - let minor = be_u32(src)?; - let bytes_left = src.bytes_left(); - if bytes_left % 4 != 0 { - return Err(Error::InvalidData("invalid ftyp size")); - } - // Is a brand_count of zero valid? - let brand_count = bytes_left / 4; - let mut brands = TryVec::with_capacity(brand_count.try_into()?)?; - for _ in 0..brand_count { - brands.push(be_u32(src)?.into())?; - } - Ok(FileTypeBox { - major_brand: From::from(major), - minor_version: minor, - compatible_brands: brands, - }) -} - -/// Parse an mvhd box. -fn read_mvhd<T: Read>(src: &mut BMFFBox<T>) -> Result<MovieHeaderBox> { - let (version, _) = read_fullbox_extra(src)?; - match version { - // 64 bit creation and modification times. - 1 => { - skip(src, 16)?; - } - // 32 bit creation and modification times. - 0 => { - skip(src, 8)?; - } - _ => return Err(Error::InvalidData("unhandled mvhd version")), - } - let timescale = be_u32(src)?; - let duration = match version { - 1 => be_u64(src)?, - 0 => { - let d = be_u32(src)?; - if d == std::u32::MAX { - std::u64::MAX - } else { - u64::from(d) - } - } - _ => return Err(Error::InvalidData("unhandled mvhd version")), - }; - // Skip remaining fields. - skip(src, 80)?; - Ok(MovieHeaderBox { - timescale, - duration, - }) -} - -/// Parse a tkhd box. -fn read_tkhd<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackHeaderBox> { - let (version, flags) = read_fullbox_extra(src)?; - let disabled = flags & 0x1u32 == 0 || flags & 0x2u32 == 0; - match version { - // 64 bit creation and modification times. - 1 => { - skip(src, 16)?; - } - // 32 bit creation and modification times. - 0 => { - skip(src, 8)?; - } - _ => return Err(Error::InvalidData("unhandled tkhd version")), - } - let track_id = be_u32(src)?; - skip(src, 4)?; - let duration = match version { - 1 => be_u64(src)?, - 0 => u64::from(be_u32(src)?), - _ => return Err(Error::InvalidData("unhandled tkhd version")), - }; - // Skip uninteresting fields. - skip(src, 16)?; - - let matrix = Matrix { - a: be_i32(src)?, - b: be_i32(src)?, - u: be_i32(src)?, - c: be_i32(src)?, - d: be_i32(src)?, - v: be_i32(src)?, - x: be_i32(src)?, - y: be_i32(src)?, - w: be_i32(src)?, - }; - - let width = be_u32(src)?; - let height = be_u32(src)?; - Ok(TrackHeaderBox { - track_id, - disabled, - duration, - width, - height, - matrix, - }) -} - -/// Parse a elst box. -/// See ISOBMFF (ISO 14496-12:2020) § 8.6.6 -fn read_elst<T: Read>(src: &mut BMFFBox<T>) -> Result<EditListBox> { - let (version, _) = read_fullbox_extra(src)?; - let edit_count = be_u32(src)?; - let mut edits = TryVec::with_capacity(edit_count.to_usize())?; - for _ in 0..edit_count { - let (segment_duration, media_time) = match version { - 1 => { - // 64 bit segment duration and media times. - (be_u64(src)?, be_i64(src)?) - } - 0 => { - // 32 bit segment duration and media times. - (u64::from(be_u32(src)?), i64::from(be_i32(src)?)) - } - _ => return Err(Error::InvalidData("unhandled elst version")), - }; - let media_rate_integer = be_i16(src)?; - let media_rate_fraction = be_i16(src)?; - edits.push(Edit { - segment_duration, - media_time, - media_rate_integer, - media_rate_fraction, - })?; - } - - // Padding could be added in some contents. - skip_box_remain(src)?; - - Ok(EditListBox { edits }) -} - -/// Parse a mdhd box. -fn read_mdhd<T: Read>(src: &mut BMFFBox<T>) -> Result<MediaHeaderBox> { - let (version, _) = read_fullbox_extra(src)?; - let (timescale, duration) = match version { - 1 => { - // Skip 64-bit creation and modification times. - skip(src, 16)?; - - // 64 bit duration. - (be_u32(src)?, be_u64(src)?) - } - 0 => { - // Skip 32-bit creation and modification times. - skip(src, 8)?; - - // 32 bit duration. - let timescale = be_u32(src)?; - let duration = { - // Since we convert the 32-bit duration to 64-bit by - // upcasting, we need to preserve the special all-1s - // ("unknown") case by hand. - let d = be_u32(src)?; - if d == std::u32::MAX { - std::u64::MAX - } else { - u64::from(d) - } - }; - (timescale, duration) - } - _ => return Err(Error::InvalidData("unhandled mdhd version")), - }; - - // Skip uninteresting fields. - skip(src, 4)?; - - Ok(MediaHeaderBox { - timescale, - duration, - }) -} - -/// Parse a stco box. -/// See ISOBMFF (ISO 14496-12:2020) § 8.7.5 -fn read_stco<T: Read>(src: &mut BMFFBox<T>) -> Result<ChunkOffsetBox> { - let (_, _) = read_fullbox_extra(src)?; - let offset_count = be_u32(src)?; - let mut offsets = TryVec::with_capacity(offset_count.to_usize())?; - for _ in 0..offset_count { - offsets.push(be_u32(src)?.into())?; - } - - // Padding could be added in some contents. - skip_box_remain(src)?; - - Ok(ChunkOffsetBox { offsets }) -} - -/// Parse a co64 box. -/// See ISOBMFF (ISO 14496-12:2020) § 8.7.5 -fn read_co64<T: Read>(src: &mut BMFFBox<T>) -> Result<ChunkOffsetBox> { - let (_, _) = read_fullbox_extra(src)?; - let offset_count = be_u32(src)?; - let mut offsets = TryVec::with_capacity(offset_count.to_usize())?; - for _ in 0..offset_count { - offsets.push(be_u64(src)?)?; - } - - // Padding could be added in some contents. - skip_box_remain(src)?; - - Ok(ChunkOffsetBox { offsets }) -} - -/// Parse a stss box. -/// See ISOBMFF (ISO 14496-12:2020) § 8.6.2 -fn read_stss<T: Read>(src: &mut BMFFBox<T>) -> Result<SyncSampleBox> { - let (_, _) = read_fullbox_extra(src)?; - let sample_count = be_u32(src)?; - let mut samples = TryVec::with_capacity(sample_count.to_usize())?; - for _ in 0..sample_count { - samples.push(be_u32(src)?)?; - } - - // Padding could be added in some contents. - skip_box_remain(src)?; - - Ok(SyncSampleBox { samples }) -} - -/// Parse a stsc box. -/// See ISOBMFF (ISO 14496-12:2020) § 8.7.4 -fn read_stsc<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleToChunkBox> { - let (_, _) = read_fullbox_extra(src)?; - let sample_count = be_u32(src)?; - let mut samples = TryVec::with_capacity(sample_count.to_usize())?; - for _ in 0..sample_count { - let first_chunk = be_u32(src)?; - let samples_per_chunk = be_u32(src)?; - let sample_description_index = be_u32(src)?; - samples.push(SampleToChunk { - first_chunk, - samples_per_chunk, - sample_description_index, - })?; - } - - // Padding could be added in some contents. - skip_box_remain(src)?; - - Ok(SampleToChunkBox { samples }) -} - -/// Parse a Composition Time to Sample Box -/// See ISOBMFF (ISO 14496-12:2020) § 8.6.1.3 -fn read_ctts<T: Read>(src: &mut BMFFBox<T>) -> Result<CompositionOffsetBox> { - let (version, _) = read_fullbox_extra(src)?; - - let counts = be_u32(src)?; - - if counts - .checked_mul(8) - .map_or(true, |bytes| u64::from(bytes) > src.bytes_left()) - { - return Err(Error::InvalidData("insufficient data in 'ctts' box")); - } - - let mut offsets = TryVec::with_capacity(counts.to_usize())?; - for _ in 0..counts { - let (sample_count, time_offset) = match version { - // According to spec, Version0 shoule be used when version == 0; - // however, some buggy contents have negative value when version == 0. - // So we always use Version1 here. - 0..=1 => { - let count = be_u32(src)?; - let offset = TimeOffsetVersion::Version1(be_i32(src)?); - (count, offset) - } - _ => { - return Err(Error::InvalidData("unsupported version in 'ctts' box")); - } - }; - offsets.push(TimeOffset { - sample_count, - time_offset, - })?; - } - - check_parser_state!(src.content); - - Ok(CompositionOffsetBox { samples: offsets }) -} - -/// Parse a stsz box. -/// See ISOBMFF (ISO 14496-12:2020) § 8.7.3.2 -fn read_stsz<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleSizeBox> { - let (_, _) = read_fullbox_extra(src)?; - let sample_size = be_u32(src)?; - let sample_count = be_u32(src)?; - let mut sample_sizes = TryVec::new(); - if sample_size == 0 { - sample_sizes.reserve(sample_count.to_usize())?; - for _ in 0..sample_count { - sample_sizes.push(be_u32(src)?)?; - } - } - - // Padding could be added in some contents. - skip_box_remain(src)?; - - Ok(SampleSizeBox { - sample_size, - sample_sizes, - }) -} - -/// Parse a stts box. -/// See ISOBMFF (ISO 14496-12:2020) § 8.6.1.2 -fn read_stts<T: Read>(src: &mut BMFFBox<T>) -> Result<TimeToSampleBox> { - let (_, _) = read_fullbox_extra(src)?; - let sample_count = be_u32(src)?; - let mut samples = TryVec::with_capacity(sample_count.to_usize())?; - for _ in 0..sample_count { - let sample_count = be_u32(src)?; - let sample_delta = be_u32(src)?; - samples.push(Sample { - sample_count, - sample_delta, - })?; - } - - // Padding could be added in some contents. - skip_box_remain(src)?; - - Ok(TimeToSampleBox { samples }) -} - -/// Parse a VPx Config Box. -fn read_vpcc<T: Read>(src: &mut BMFFBox<T>) -> Result<VPxConfigBox> { - let (version, _) = read_fullbox_extra(src)?; - let supported_versions = [0, 1]; - if !supported_versions.contains(&version) { - return Err(Error::Unsupported("unknown vpcC version")); - } - - let profile = src.read_u8()?; - let level = src.read_u8()?; - let ( - bit_depth, - colour_primaries, - chroma_subsampling, - transfer_characteristics, - matrix_coefficients, - video_full_range_flag, - ) = if version == 0 { - let (bit_depth, colour_primaries) = { - let byte = src.read_u8()?; - ((byte >> 4) & 0x0f, byte & 0x0f) - }; - // Note, transfer_characteristics was known as transfer_function in v0 - let (chroma_subsampling, transfer_characteristics, video_full_range_flag) = { - let byte = src.read_u8()?; - ((byte >> 4) & 0x0f, (byte >> 1) & 0x07, (byte & 1) == 1) - }; - ( - bit_depth, - colour_primaries, - chroma_subsampling, - transfer_characteristics, - None, - video_full_range_flag, - ) - } else { - let (bit_depth, chroma_subsampling, video_full_range_flag) = { - let byte = src.read_u8()?; - ((byte >> 4) & 0x0f, (byte >> 1) & 0x07, (byte & 1) == 1) - }; - let colour_primaries = src.read_u8()?; - let transfer_characteristics = src.read_u8()?; - let matrix_coefficients = src.read_u8()?; - - ( - bit_depth, - colour_primaries, - chroma_subsampling, - transfer_characteristics, - Some(matrix_coefficients), - video_full_range_flag, - ) - }; - - let codec_init_size = be_u16(src)?; - let codec_init = read_buf(src, codec_init_size.into())?; - - // TODO(rillian): validate field value ranges. - Ok(VPxConfigBox { - profile, - level, - bit_depth, - colour_primaries, - chroma_subsampling, - transfer_characteristics, - matrix_coefficients, - video_full_range_flag, - codec_init, - }) -} - -/// See [AV1-ISOBMFF § 2.3.3](https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax) -fn read_av1c<T: Read>(src: &mut BMFFBox<T>) -> Result<AV1ConfigBox> { - // We want to store the raw config as well as a structured (parsed) config, so create a copy of - // the raw config so we have it later, and then parse the structured data from that. - let raw_config = src.read_into_try_vec()?; - let mut raw_config_slice = raw_config.as_slice(); - let marker_byte = raw_config_slice.read_u8()?; - if marker_byte & 0x80 != 0x80 { - return Err(Error::Unsupported("missing av1C marker bit")); - } - if marker_byte & 0x7f != 0x01 { - return Err(Error::Unsupported("missing av1C marker bit")); - } - let profile_byte = raw_config_slice.read_u8()?; - let profile = (profile_byte & 0xe0) >> 5; - let level = profile_byte & 0x1f; - let flags_byte = raw_config_slice.read_u8()?; - let tier = (flags_byte & 0x80) >> 7; - let bit_depth = match flags_byte & 0x60 { - 0x60 => 12, - 0x40 => 10, - _ => 8, - }; - let monochrome = flags_byte & 0x10 == 0x10; - let chroma_subsampling_x = (flags_byte & 0x08) >> 3; - let chroma_subsampling_y = (flags_byte & 0x04) >> 2; - let chroma_sample_position = flags_byte & 0x03; - let delay_byte = raw_config_slice.read_u8()?; - let initial_presentation_delay_present = (delay_byte & 0x10) == 0x10; - let initial_presentation_delay_minus_one = if initial_presentation_delay_present { - delay_byte & 0x0f - } else { - 0 - }; - - Ok(AV1ConfigBox { - profile, - level, - tier, - bit_depth, - monochrome, - chroma_subsampling_x, - chroma_subsampling_y, - chroma_sample_position, - initial_presentation_delay_present, - initial_presentation_delay_minus_one, - raw_config, - }) -} - -fn read_flac_metadata<T: Read>(src: &mut BMFFBox<T>) -> Result<FLACMetadataBlock> { - let temp = src.read_u8()?; - let block_type = temp & 0x7f; - let length = be_u24(src)?.into(); - if length > src.bytes_left() { - return Err(Error::InvalidData( - "FLACMetadataBlock larger than parent box", - )); - } - let data = read_buf(src, length)?; - Ok(FLACMetadataBlock { block_type, data }) -} - -/// See MPEG-4 Systems (ISO 14496-1:2010) § 7.2.6.5 -fn find_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { - // Tags for elementary stream description - const ESDESCR_TAG: u8 = 0x03; - const DECODER_CONFIG_TAG: u8 = 0x04; - const DECODER_SPECIFIC_TAG: u8 = 0x05; - - let mut remains = data; - - // Descriptor length should be more than 2 bytes. - while remains.len() > 2 { - let des = &mut Cursor::new(remains); - let tag = des.read_u8()?; - - // See MPEG-4 Systems (ISO 14496-1:2010) § 8.3.3 for interpreting size of expandable classes - - let mut end: u32 = 0; // It's u8 without declaration type that is incorrect. - // MSB of extend_or_len indicates more bytes, up to 4 bytes. - for _ in 0..4 { - if des.position() == remains.len().to_u64() { - // There's nothing more to read, the 0x80 was actually part of - // the content, and not an extension size. - end = des.position() as u32; - break; - } - let extend_or_len = des.read_u8()?; - end = (end << 7) + u32::from(extend_or_len & 0x7F); - if (extend_or_len & 0b1000_0000) == 0 { - end += des.position() as u32; - break; - } - } - - if end.to_usize() > remains.len() || u64::from(end) < des.position() { - return Err(Error::InvalidData("Invalid descriptor.")); - } - - let descriptor = &remains[des.position().try_into()?..end.to_usize()]; - - match tag { - ESDESCR_TAG => { - read_es_descriptor(descriptor, esds)?; - } - DECODER_CONFIG_TAG => { - read_dc_descriptor(descriptor, esds)?; - } - DECODER_SPECIFIC_TAG => { - read_ds_descriptor(descriptor, esds)?; - } - _ => { - debug!("Unsupported descriptor, tag {}", tag); - } - } - - remains = &remains[end.to_usize()..remains.len()]; - debug!("remains.len(): {}", remains.len()); - } - - Ok(()) -} - -fn get_audio_object_type(bit_reader: &mut BitReader) -> Result<u16> { - let mut audio_object_type: u16 = ReadInto::read(bit_reader, 5)?; - - // Extend audio object type, for example, HE-AAC. - if audio_object_type == 31 { - let audio_object_type_ext: u16 = ReadInto::read(bit_reader, 6)?; - audio_object_type = 32 + audio_object_type_ext; - } - Ok(audio_object_type) -} - -/// See MPEG-4 Systems (ISO 14496-1:2010) § 7.2.6.7 and probably 14496-3 somewhere? -fn read_ds_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { - #[cfg(feature = "mp4v")] - // Check if we are in a Visual esda Box. - if esds.video_codec != CodecType::Unknown { - esds.decoder_specific_data.extend_from_slice(data)?; - return Ok(()); - } - - // We are in an Audio esda Box. - let frequency_table = vec![ - (0x0, 96000), - (0x1, 88200), - (0x2, 64000), - (0x3, 48000), - (0x4, 44100), - (0x5, 32000), - (0x6, 24000), - (0x7, 22050), - (0x8, 16000), - (0x9, 12000), - (0xa, 11025), - (0xb, 8000), - (0xc, 7350), - ]; - - let bit_reader = &mut BitReader::new(data); - - let mut audio_object_type = get_audio_object_type(bit_reader)?; - - let sample_index: u32 = ReadInto::read(bit_reader, 4)?; - - // Sample frequency could be from table, or retrieved from stream directly - // if index is 0x0f. - let sample_frequency = match sample_index { - 0x0F => Some(ReadInto::read(bit_reader, 24)?), - _ => frequency_table - .iter() - .find(|item| item.0 == sample_index) - .map(|x| x.1), - }; - - let channel_configuration: u16 = ReadInto::read(bit_reader, 4)?; - - let extended_audio_object_type = match audio_object_type { - 5 | 29 => Some(5), - _ => None, - }; - - if audio_object_type == 5 || audio_object_type == 29 { - // We have an explicit signaling for BSAC extension, should the decoder - // decode the BSAC extension (all Gecko's AAC decoders do), then this is - // what the stream will actually look like once decoded. - let _extended_sample_index = ReadInto::read(bit_reader, 4)?; - let _extended_sample_frequency: Option<u32> = match _extended_sample_index { - 0x0F => Some(ReadInto::read(bit_reader, 24)?), - _ => frequency_table - .iter() - .find(|item| item.0 == sample_index) - .map(|x| x.1), - }; - audio_object_type = get_audio_object_type(bit_reader)?; - let _extended_channel_configuration = match audio_object_type { - 22 => ReadInto::read(bit_reader, 4)?, - _ => channel_configuration, - }; - }; - - match audio_object_type { - 1..=4 | 6 | 7 | 17 | 19..=23 => { - if sample_frequency.is_none() { - return Err(Error::Unsupported("unknown frequency")); - } - - // parsing GASpecificConfig - - // If the sampling rate is not one of the rates listed in the right - // column in Table 4.82, the sampling frequency dependent tables - // (code tables, scale factor band tables etc.) must be deduced in - // order for the bitstream payload to be parsed. Since a given - // sampling frequency is associated with only one sampling frequency - // table, and since maximum flexibility is desired in the range of - // possible sampling frequencies, the following table shall be used - // to associate an implied sampling frequency with the desired - // sampling frequency dependent tables. - let sample_frequency_value = match sample_frequency.unwrap() { - 0..=9390 => 8000, - 9391..=11501 => 11025, - 11502..=13855 => 12000, - 13856..=18782 => 16000, - 18783..=23003 => 22050, - 23004..=27712 => 24000, - 27713..=37565 => 32000, - 37566..=46008 => 44100, - 46009..=55425 => 48000, - 55426..=75131 => 64000, - 75132..=92016 => 88200, - _ => 96000, - }; - - bit_reader.skip(1)?; // frameLengthFlag - let depend_on_core_order: u8 = ReadInto::read(bit_reader, 1)?; - if depend_on_core_order > 0 { - bit_reader.skip(14)?; // codeCoderDelay - } - bit_reader.skip(1)?; // extensionFlag - - let channel_counts = match channel_configuration { - 0 => { - debug!("Parsing program_config_element for channel counts"); - - bit_reader.skip(4)?; // element_instance_tag - bit_reader.skip(2)?; // object_type - bit_reader.skip(4)?; // sampling_frequency_index - let num_front_channel: u8 = ReadInto::read(bit_reader, 4)?; - let num_side_channel: u8 = ReadInto::read(bit_reader, 4)?; - let num_back_channel: u8 = ReadInto::read(bit_reader, 4)?; - let num_lfe_channel: u8 = ReadInto::read(bit_reader, 2)?; - bit_reader.skip(3)?; // num_assoc_data - bit_reader.skip(4)?; // num_valid_cc - - let mono_mixdown_present: bool = ReadInto::read(bit_reader, 1)?; - if mono_mixdown_present { - bit_reader.skip(4)?; // mono_mixdown_element_number - } - - let stereo_mixdown_present: bool = ReadInto::read(bit_reader, 1)?; - if stereo_mixdown_present { - bit_reader.skip(4)?; // stereo_mixdown_element_number - } - - let matrix_mixdown_idx_present: bool = ReadInto::read(bit_reader, 1)?; - if matrix_mixdown_idx_present { - bit_reader.skip(2)?; // matrix_mixdown_idx - bit_reader.skip(1)?; // pseudo_surround_enable - } - let mut _channel_counts = 0; - _channel_counts += read_surround_channel_count(bit_reader, num_front_channel)?; - _channel_counts += read_surround_channel_count(bit_reader, num_side_channel)?; - _channel_counts += read_surround_channel_count(bit_reader, num_back_channel)?; - _channel_counts += read_surround_channel_count(bit_reader, num_lfe_channel)?; - _channel_counts - } - 1..=7 => channel_configuration, - // Amendment 4 of the AAC standard in 2013 below - 11 => 7, // 6.1 Amendment 4 of the AAC standard in 2013 - 12 | 14 => 8, // 7.1 (a/d) of ITU BS.2159 - _ => { - return Err(Error::Unsupported("invalid channel configuration")); - } - }; - - esds.audio_object_type = Some(audio_object_type); - esds.extended_audio_object_type = extended_audio_object_type; - esds.audio_sample_rate = Some(sample_frequency_value); - esds.audio_channel_count = Some(channel_counts); - if !esds.decoder_specific_data.is_empty() { - return Err(Error::InvalidData( - "There can be only one DecSpecificInfoTag descriptor", - )); - } - esds.decoder_specific_data.extend_from_slice(data)?; - - Ok(()) - } - _ => Err(Error::Unsupported("unknown aac audio object type")), - } -} - -fn read_surround_channel_count(bit_reader: &mut BitReader, channels: u8) -> Result<u16> { - let mut count = 0; - for _ in 0..channels { - let is_cpe: bool = ReadInto::read(bit_reader, 1)?; - count += if is_cpe { 2 } else { 1 }; - bit_reader.skip(4)?; - } - Ok(count) -} - -/// See MPEG-4 Systems (ISO 14496-1:2010) § 7.2.6.6 -fn read_dc_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { - let des = &mut Cursor::new(data); - let object_profile = des.read_u8()?; - - #[cfg(feature = "mp4v")] - { - esds.video_codec = match object_profile { - 0x20..=0x24 => CodecType::MP4V, - _ => CodecType::Unknown, - }; - } - - // Skip uninteresting fields. - skip(des, 12)?; - - if data.len().to_u64() > des.position() { - find_descriptor(&data[des.position().try_into()?..data.len()], esds)?; - } - - esds.audio_codec = match object_profile { - 0x40 | 0x41 => CodecType::AAC, - 0x69 | 0x6B => CodecType::MP3, - _ => CodecType::Unknown, - }; - - debug!( - "read_dc_descriptor: esds.audio_codec = {:?}", - esds.audio_codec - ); - - Ok(()) -} - -/// See MPEG-4 Systems (ISO 14496-1:2010) § 7.2.6.5 -fn read_es_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { - let des = &mut Cursor::new(data); - - skip(des, 2)?; - - let esds_flags = des.read_u8()?; - - // Stream dependency flag, first bit from left most. - if esds_flags & 0x80 > 0 { - // Skip uninteresting fields. - skip(des, 2)?; - } - - // Url flag, second bit from left most. - if esds_flags & 0x40 > 0 { - // Skip uninteresting fields. - let skip_es_len = u64::from(des.read_u8()?) + 2; - skip(des, skip_es_len)?; - } - - if data.len().to_u64() > des.position() { - find_descriptor(&data[des.position().try_into()?..data.len()], esds)?; - } - - Ok(()) -} - -/// See MP4 (ISO 14496-14:2020) § 6.7.2 -fn read_esds<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> { - let (_, _) = read_fullbox_extra(src)?; - - let esds_array = read_buf(src, src.bytes_left())?; - - let mut es_data = ES_Descriptor::default(); - find_descriptor(&esds_array, &mut es_data)?; - - es_data.codec_esds = esds_array; - - Ok(es_data) -} - -/// Parse `FLACSpecificBox`. -/// See [Encapsulation of FLAC in ISO Base Media File Format](https://github.com/xiph/flac/blob/master/doc/isoflac.txt) § 3.3.2 -fn read_dfla<T: Read>(src: &mut BMFFBox<T>) -> Result<FLACSpecificBox> { - let (version, flags) = read_fullbox_extra(src)?; - if version != 0 { - return Err(Error::Unsupported("unknown dfLa (FLAC) version")); - } - if flags != 0 { - return Err(Error::InvalidData("no-zero dfLa (FLAC) flags")); - } - let mut blocks = TryVec::new(); - while src.bytes_left() > 0 { - let block = read_flac_metadata(src)?; - blocks.push(block)?; - } - // The box must have at least one meta block, and the first block - // must be the METADATA_BLOCK_STREAMINFO - if blocks.is_empty() { - return Err(Error::InvalidData("FLACSpecificBox missing metadata")); - } else if blocks[0].block_type != 0 { - return Err(Error::InvalidData( - "FLACSpecificBox must have STREAMINFO metadata first", - )); - } else if blocks[0].data.len() != 34 { - return Err(Error::InvalidData( - "FLACSpecificBox STREAMINFO block is the wrong size", - )); - } - Ok(FLACSpecificBox { version, blocks }) -} - -/// Parse `OpusSpecificBox`. -fn read_dops<T: Read>(src: &mut BMFFBox<T>) -> Result<OpusSpecificBox> { - let version = src.read_u8()?; - if version != 0 { - return Err(Error::Unsupported("unknown dOps (Opus) version")); - } - - let output_channel_count = src.read_u8()?; - let pre_skip = be_u16(src)?; - let input_sample_rate = be_u32(src)?; - let output_gain = be_i16(src)?; - let channel_mapping_family = src.read_u8()?; - - let channel_mapping_table = if channel_mapping_family == 0 { - None - } else { - let stream_count = src.read_u8()?; - let coupled_count = src.read_u8()?; - let channel_mapping = read_buf(src, output_channel_count.into())?; - - Some(ChannelMappingTable { - stream_count, - coupled_count, - channel_mapping, - }) - }; - - // TODO(kinetik): validate field value ranges. - Ok(OpusSpecificBox { - version, - output_channel_count, - pre_skip, - input_sample_rate, - output_gain, - channel_mapping_family, - channel_mapping_table, - }) -} - -/// Re-serialize the Opus codec-specific config data as an `OpusHead` packet. -/// -/// Some decoders expect the initialization data in the format used by the -/// Ogg and WebM encapsulations. To support this we prepend the `OpusHead` -/// tag and byte-swap the data from big- to little-endian relative to the -/// dOps box. -pub fn serialize_opus_header<W: byteorder::WriteBytesExt + std::io::Write>( - opus: &OpusSpecificBox, - dst: &mut W, -) -> Result<()> { - match dst.write(b"OpusHead") { - Err(e) => return Err(Error::from(e)), - Ok(bytes) => { - if bytes != 8 { - return Err(Error::InvalidData("Couldn't write OpusHead tag.")); - } - } - } - // In mp4 encapsulation, the version field is 0, but in ogg - // it is 1. While decoders generally accept zero as well, write - // out the version of the header we're supporting rather than - // whatever we parsed out of mp4. - dst.write_u8(1)?; - dst.write_u8(opus.output_channel_count)?; - dst.write_u16::<byteorder::LittleEndian>(opus.pre_skip)?; - dst.write_u32::<byteorder::LittleEndian>(opus.input_sample_rate)?; - dst.write_i16::<byteorder::LittleEndian>(opus.output_gain)?; - dst.write_u8(opus.channel_mapping_family)?; - match opus.channel_mapping_table { - None => {} - Some(ref table) => { - dst.write_u8(table.stream_count)?; - dst.write_u8(table.coupled_count)?; - match dst.write(&table.channel_mapping) { - Err(e) => return Err(Error::from(e)), - Ok(bytes) => { - if bytes != table.channel_mapping.len() { - return Err(Error::InvalidData( - "Couldn't write channel mapping table data.", - )); - } - } - } - } - }; - Ok(()) -} - -/// Parse `ALACSpecificBox`. -fn read_alac<T: Read>(src: &mut BMFFBox<T>) -> Result<ALACSpecificBox> { - let (version, flags) = read_fullbox_extra(src)?; - if version != 0 { - return Err(Error::Unsupported("unknown alac (ALAC) version")); - } - if flags != 0 { - return Err(Error::InvalidData("no-zero alac (ALAC) flags")); - } - - let length = match src.bytes_left() { - x @ 24 | x @ 48 => x, - _ => { - return Err(Error::InvalidData( - "ALACSpecificBox magic cookie is the wrong size", - )) - } - }; - let data = read_buf(src, length)?; - - Ok(ALACSpecificBox { version, data }) -} - -/// Parse a Handler Reference Box.<br /> -/// See ISOBMFF (ISO 14496-12:2020) § 8.4.3<br /> -/// See [\[ISOBMFF\]: reserved (field = 0;) handling is ambiguous](https://github.com/MPEGGroup/FileFormat/issues/36) -fn read_hdlr<T: Read>(src: &mut BMFFBox<T>, strictness: ParseStrictness) -> Result<HandlerBox> { - if read_fullbox_version_no_flags(src)? != 0 { - return Err(Error::Unsupported("hdlr version")); - } - - let pre_defined = be_u32(src)?; - if pre_defined != 0 { - fail_if( - strictness == ParseStrictness::Strict, - "The HandlerBox 'pre_defined' field shall be 0 \ - per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2", - )?; - } - - let handler_type = FourCC::from(be_u32(src)?); - - for _ in 1..=3 { - let reserved = be_u32(src)?; - if reserved != 0 { - fail_if( - strictness == ParseStrictness::Strict, - "The HandlerBox 'reserved' fields shall be 0 \ - per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2", - )?; - } - } - - match std::str::from_utf8(src.read_into_try_vec()?.as_slice()) { - Ok(name) => { - match name.bytes().filter(|&b| b == b'\0').count() { - 0 => fail_if( - strictness != ParseStrictness::Permissive, - "The HandlerBox 'name' field shall be null-terminated \ - per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2", - )?, - 1 => (), - _ => - // See https://github.com/MPEGGroup/FileFormat/issues/35 - { - fail_if( - strictness == ParseStrictness::Strict, - "The HandlerBox 'name' field shall have a NUL byte \ - only in the final position \ - per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2", - )? - } - } - } - Err(_) => fail_if( - strictness != ParseStrictness::Permissive, - "The HandlerBox 'name' field shall be valid utf8 \ - per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2", - )?, - } - - Ok(HandlerBox { handler_type }) -} - -/// Parse an video description inside an stsd box. -fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>, brand: &FourCC) -> Result<SampleEntry> { - let name = src.get_header().name; - let codec_type = match name { - BoxType::AVCSampleEntry | BoxType::AVC3SampleEntry => CodecType::H264, - BoxType::MP4VideoSampleEntry => CodecType::MP4V, - BoxType::VP8SampleEntry => CodecType::VP8, - BoxType::VP9SampleEntry => CodecType::VP9, - BoxType::AV1SampleEntry => CodecType::AV1, - BoxType::CanonCRAWEntry => { - if brand == &FourCC::from(*b"crx ") { - CodecType::CRAW - } else { - debug!("Unsupported CRAW codec found in '{:?}'.", brand); - CodecType::Unknown - } - } - BoxType::ProtectedVisualSampleEntry => CodecType::EncryptedVideo, - BoxType::H263SampleEntry => CodecType::H263, - _ => { - debug!("Unsupported video codec, box {:?} found", name); - CodecType::Unknown - } - }; - - // Skip uninteresting fields. - skip(src, 6)?; - - let data_reference_index = be_u16(src)?; - - // Skip uninteresting fields. - skip(src, 16)?; - let width = be_u16(src)?; - let height = be_u16(src)?; - - #[cfg(feature = "craw")] - { - if codec_type == CodecType::CRAW { - return craw::read_craw_entry(src, width, height, data_reference_index); - } - } - - // Skip uninteresting fields. - skip(src, 50)?; - - // Skip clap/pasp/etc. for now. - let mut codec_specific = None; - let mut protection_info = TryVec::new(); - let mut iter = src.box_iter(); - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::AVCConfigurationBox => { - if (name != BoxType::AVCSampleEntry - && name != BoxType::AVC3SampleEntry - && name != BoxType::ProtectedVisualSampleEntry) - || codec_specific.is_some() - { - return Err(Error::InvalidData("malformed video sample entry")); - } - let avcc_size = b - .head - .size - .checked_sub(b.head.offset) - .expect("offset invalid"); - let avcc = read_buf(&mut b.content, avcc_size)?; - debug!("{:?} (avcc)", avcc); - // TODO(kinetik): Parse avcC box? For now we just stash the data. - codec_specific = Some(VideoCodecSpecific::AVCConfig(avcc)); - } - BoxType::H263SpecificBox => { - if (name != BoxType::H263SampleEntry) || codec_specific.is_some() { - return Err(Error::InvalidData("malformed video sample entry")); - } - let h263_dec_spec_struc_size = b - .head - .size - .checked_sub(b.head.offset) - .expect("offset invalid"); - let h263_dec_spec_struc = read_buf(&mut b.content, h263_dec_spec_struc_size)?; - debug!("{:?} (h263DecSpecStruc)", h263_dec_spec_struc); - - codec_specific = Some(VideoCodecSpecific::H263Config(h263_dec_spec_struc)); - } - BoxType::VPCodecConfigurationBox => { - // vpcC - if (name != BoxType::VP8SampleEntry - && name != BoxType::VP9SampleEntry - && name != BoxType::ProtectedVisualSampleEntry) - || codec_specific.is_some() - { - return Err(Error::InvalidData("malformed video sample entry")); - } - let vpcc = read_vpcc(&mut b)?; - codec_specific = Some(VideoCodecSpecific::VPxConfig(vpcc)); - } - BoxType::AV1CodecConfigurationBox => { - if name != BoxType::AV1SampleEntry && name != BoxType::ProtectedVisualSampleEntry { - return Err(Error::InvalidData("malformed video sample entry")); - } - let av1c = read_av1c(&mut b)?; - codec_specific = Some(VideoCodecSpecific::AV1Config(av1c)); - } - BoxType::ESDBox => { - if name != BoxType::MP4VideoSampleEntry || codec_specific.is_some() { - return Err(Error::InvalidData("malformed video sample entry")); - } - #[cfg(not(feature = "mp4v"))] - { - let (_, _) = read_fullbox_extra(&mut b.content)?; - // Subtract 4 extra to offset the members of fullbox not - // accounted for in head.offset - let esds_size = b - .head - .size - .checked_sub(b.head.offset + 4) - .expect("offset invalid"); - let esds = read_buf(&mut b.content, esds_size)?; - codec_specific = Some(VideoCodecSpecific::ESDSConfig(esds)); - } - #[cfg(feature = "mp4v")] - { - // Read ES_Descriptor inside an esds box. - // See ISOBMFF (ISO 14496-1:2010 §7.2.6.5) - let esds = read_esds(&mut b)?; - codec_specific = - Some(VideoCodecSpecific::ESDSConfig(esds.decoder_specific_data)); - } - } - BoxType::ProtectionSchemeInfoBox => { - if name != BoxType::ProtectedVisualSampleEntry { - return Err(Error::InvalidData("malformed video sample entry")); - } - let sinf = read_sinf(&mut b)?; - debug!("{:?} (sinf)", sinf); - protection_info.push(sinf)?; - } - _ => { - debug!("Unsupported video codec, box {:?} found", b.head.name); - skip_box_content(&mut b)?; - } - } - check_parser_state!(b.content); - } - - Ok( - codec_specific.map_or(SampleEntry::Unknown, |codec_specific| { - SampleEntry::Video(VideoSampleEntry { - codec_type, - data_reference_index, - width, - height, - codec_specific, - protection_info, - }) - }), - ) -} - -fn read_qt_wave_atom<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> { - let mut codec_specific = None; - let mut iter = src.box_iter(); - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::ESDBox => { - let esds = read_esds(&mut b)?; - codec_specific = Some(esds); - } - _ => skip_box_content(&mut b)?, - } - } - - codec_specific.ok_or(Error::InvalidData("malformed audio sample entry")) -} - -/// Parse an audio description inside an stsd box. -/// See ISOBMFF (ISO 14496-12:2020) § 12.2.3 -fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleEntry> { - let name = src.get_header().name; - - // Skip uninteresting fields. - skip(src, 6)?; - - let data_reference_index = be_u16(src)?; - - // XXX(kinetik): This is "reserved" in BMFF, but some old QT MOV variant - // uses it, need to work out if we have to support it. Without checking - // here and reading extra fields after samplerate (or bailing with an - // error), the parser loses sync completely. - let version = be_u16(src)?; - - // Skip uninteresting fields. - skip(src, 6)?; - - let mut channelcount = u32::from(be_u16(src)?); - let samplesize = be_u16(src)?; - - // Skip uninteresting fields. - skip(src, 4)?; - - let mut samplerate = f64::from(be_u32(src)? >> 16); // 16.16 fixed point; - - match version { - 0 => (), - 1 => { - // Quicktime sound sample description version 1. - // Skip uninteresting fields. - skip(src, 16)?; - } - 2 => { - // Quicktime sound sample description version 2. - skip(src, 4)?; - samplerate = f64::from_bits(be_u64(src)?); - channelcount = be_u32(src)?; - skip(src, 20)?; - } - _ => { - return Err(Error::Unsupported( - "unsupported non-isom audio sample entry", - )) - } - } - - let (mut codec_type, mut codec_specific) = match name { - BoxType::MP3AudioSampleEntry => (CodecType::MP3, Some(AudioCodecSpecific::MP3)), - BoxType::LPCMAudioSampleEntry => (CodecType::LPCM, Some(AudioCodecSpecific::LPCM)), - // Some mp4 file with AMR doesn't have AMRSpecificBox "damr" in followed while loop, - // we use empty box by default. - #[cfg(feature = "3gpp")] - BoxType::AMRNBSampleEntry => ( - CodecType::AMRNB, - Some(AudioCodecSpecific::AMRSpecificBox(Default::default())), - ), - #[cfg(feature = "3gpp")] - BoxType::AMRWBSampleEntry => ( - CodecType::AMRWB, - Some(AudioCodecSpecific::AMRSpecificBox(Default::default())), - ), - _ => (CodecType::Unknown, None), - }; - let mut protection_info = TryVec::new(); - let mut iter = src.box_iter(); - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::ESDBox => { - if (name != BoxType::MP4AudioSampleEntry - && name != BoxType::ProtectedAudioSampleEntry) - || codec_specific.is_some() - { - return Err(Error::InvalidData("malformed audio sample entry")); - } - let esds = read_esds(&mut b)?; - codec_type = esds.audio_codec; - codec_specific = Some(AudioCodecSpecific::ES_Descriptor(esds)); - } - BoxType::FLACSpecificBox => { - if (name != BoxType::FLACSampleEntry && name != BoxType::ProtectedAudioSampleEntry) - || codec_specific.is_some() - { - return Err(Error::InvalidData("malformed audio sample entry")); - } - let dfla = read_dfla(&mut b)?; - codec_type = CodecType::FLAC; - codec_specific = Some(AudioCodecSpecific::FLACSpecificBox(dfla)); - } - BoxType::OpusSpecificBox => { - if (name != BoxType::OpusSampleEntry && name != BoxType::ProtectedAudioSampleEntry) - || codec_specific.is_some() - { - return Err(Error::InvalidData("malformed audio sample entry")); - } - let dops = read_dops(&mut b)?; - codec_type = CodecType::Opus; - codec_specific = Some(AudioCodecSpecific::OpusSpecificBox(dops)); - } - BoxType::ALACSpecificBox => { - if name != BoxType::ALACSpecificBox || codec_specific.is_some() { - return Err(Error::InvalidData("malformed audio sample entry")); - } - let alac = read_alac(&mut b)?; - codec_type = CodecType::ALAC; - codec_specific = Some(AudioCodecSpecific::ALACSpecificBox(alac)); - } - BoxType::QTWaveAtom => { - let qt_esds = read_qt_wave_atom(&mut b)?; - codec_type = qt_esds.audio_codec; - codec_specific = Some(AudioCodecSpecific::ES_Descriptor(qt_esds)); - } - BoxType::ProtectionSchemeInfoBox => { - if name != BoxType::ProtectedAudioSampleEntry { - return Err(Error::InvalidData("malformed audio sample entry")); - } - let sinf = read_sinf(&mut b)?; - debug!("{:?} (sinf)", sinf); - codec_type = CodecType::EncryptedAudio; - protection_info.push(sinf)?; - } - #[cfg(feature = "3gpp")] - BoxType::AMRSpecificBox => { - if codec_type != CodecType::AMRNB && codec_type != CodecType::AMRWB { - return Err(Error::InvalidData("malformed audio sample entry")); - } - let amr_dec_spec_struc_size = b - .head - .size - .checked_sub(b.head.offset) - .expect("offset invalid"); - let amr_dec_spec_struc = read_buf(&mut b.content, amr_dec_spec_struc_size)?; - debug!("{:?} (AMRDecSpecStruc)", amr_dec_spec_struc); - codec_specific = Some(AudioCodecSpecific::AMRSpecificBox(amr_dec_spec_struc)); - } - _ => { - debug!("Unsupported audio codec, box {:?} found", b.head.name); - skip_box_content(&mut b)?; - } - } - check_parser_state!(b.content); - } - - Ok( - codec_specific.map_or(SampleEntry::Unknown, |codec_specific| { - SampleEntry::Audio(AudioSampleEntry { - codec_type, - data_reference_index, - channelcount, - samplesize, - samplerate, - codec_specific, - protection_info, - }) - }), - ) -} - -/// Parse a stsd box. -/// See ISOBMFF (ISO 14496-12:2020) § 8.5.2 -/// See MP4 (ISO 14496-14:2020) § 6.7.2 -fn read_stsd<T: Read>( - src: &mut BMFFBox<T>, - track: &mut Track, - brand: &FourCC, -) -> Result<SampleDescriptionBox> { - let (_, _) = read_fullbox_extra(src)?; - - let description_count = be_u32(src)?; - let mut descriptions = TryVec::new(); - - { - let mut iter = src.box_iter(); - while let Some(mut b) = iter.next_box()? { - let description = match track.track_type { - TrackType::Video => read_video_sample_entry(&mut b, brand), - TrackType::Audio => read_audio_sample_entry(&mut b), - TrackType::Metadata => Err(Error::Unsupported("metadata track")), - TrackType::Unknown => Err(Error::Unsupported("unknown track type")), - }; - let description = match description { - Ok(desc) => desc, - Err(Error::Unsupported(_)) => { - // read_{audio,video}_desc may have returned Unsupported - // after partially reading the box content, so we can't - // simply use skip_box_content here. - let to_skip = b.bytes_left(); - skip(&mut b, to_skip)?; - SampleEntry::Unknown - } - Err(e) => return Err(e), - }; - descriptions.push(description)?; - check_parser_state!(b.content); - if descriptions.len() == description_count.to_usize() { - break; - } - } - } - - // Padding could be added in some contents. - skip_box_remain(src)?; - - Ok(SampleDescriptionBox { descriptions }) -} - -fn read_sinf<T: Read>(src: &mut BMFFBox<T>) -> Result<ProtectionSchemeInfoBox> { - let mut sinf = ProtectionSchemeInfoBox::default(); - - let mut iter = src.box_iter(); - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::OriginalFormatBox => { - sinf.original_format = FourCC::from(be_u32(&mut b)?); - } - BoxType::SchemeTypeBox => { - sinf.scheme_type = Some(read_schm(&mut b)?); - } - BoxType::SchemeInformationBox => { - // We only need tenc box in schi box so far. - sinf.tenc = read_schi(&mut b)?; - } - _ => skip_box_content(&mut b)?, - } - check_parser_state!(b.content); - } - - Ok(sinf) -} - -fn read_schi<T: Read>(src: &mut BMFFBox<T>) -> Result<Option<TrackEncryptionBox>> { - let mut tenc = None; - let mut iter = src.box_iter(); - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::TrackEncryptionBox => { - if tenc.is_some() { - return Err(Error::InvalidData( - "tenc box should be only one at most in sinf box", - )); - } - tenc = Some(read_tenc(&mut b)?); - } - _ => skip_box_content(&mut b)?, - } - } - - Ok(tenc) -} - -fn read_tenc<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackEncryptionBox> { - let (version, _) = read_fullbox_extra(src)?; - - // reserved byte - skip(src, 1)?; - // the next byte is used to signal the default pattern in version >= 1 - let (default_crypt_byte_block, default_skip_byte_block) = match version { - 0 => { - skip(src, 1)?; - (None, None) - } - _ => { - let pattern_byte = src.read_u8()?; - let crypt_bytes = pattern_byte >> 4; - let skip_bytes = pattern_byte & 0x0f; - (Some(crypt_bytes), Some(skip_bytes)) - } - }; - let default_is_encrypted = src.read_u8()?; - let default_iv_size = src.read_u8()?; - let default_kid = read_buf(src, 16)?; - // If default_is_encrypted == 1 && default_iv_size == 0 we expect a default_constant_iv - let default_constant_iv = match (default_is_encrypted, default_iv_size) { - (1, 0) => { - let default_constant_iv_size = src.read_u8()?; - Some(read_buf(src, default_constant_iv_size.into())?) - } - _ => None, - }; - - Ok(TrackEncryptionBox { - is_encrypted: default_is_encrypted, - iv_size: default_iv_size, - kid: default_kid, - crypt_byte_block_count: default_crypt_byte_block, - skip_byte_block_count: default_skip_byte_block, - constant_iv: default_constant_iv, - }) -} - -fn read_schm<T: Read>(src: &mut BMFFBox<T>) -> Result<SchemeTypeBox> { - // Flags can be used to signal presence of URI in the box, but we don't - // use the URI so don't bother storing the flags. - let (_, _) = read_fullbox_extra(src)?; - let scheme_type = FourCC::from(be_u32(src)?); - let scheme_version = be_u32(src)?; - // Null terminated scheme URI may follow, but we don't use it right now. - skip_box_remain(src)?; - Ok(SchemeTypeBox { - scheme_type, - scheme_version, - }) -} - -/// Parse a metadata box inside a moov, trak, or mdia box. -/// See ISOBMFF (ISO 14496-12:2020) § 8.10.1. -fn read_udta<T: Read>(src: &mut BMFFBox<T>) -> Result<UserdataBox> { - let mut iter = src.box_iter(); - let mut udta = UserdataBox { meta: None }; - - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::MetadataBox => { - let meta = read_meta(&mut b)?; - udta.meta = Some(meta); - } - _ => skip_box_content(&mut b)?, - }; - check_parser_state!(b.content); - } - Ok(udta) -} - -/// Parse the meta box -/// See ISOBMFF (ISO 14496-12:2020) § 8.11.1 -fn read_meta<T: Read>(src: &mut BMFFBox<T>) -> Result<MetadataBox> { - let (_, _) = read_fullbox_extra(src)?; - let mut iter = src.box_iter(); - let mut meta = MetadataBox::default(); - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::MetadataItemListEntry => read_ilst(&mut b, &mut meta)?, - #[cfg(feature = "meta-xml")] - BoxType::MetadataXMLBox => read_xml_(&mut b, &mut meta)?, - #[cfg(feature = "meta-xml")] - BoxType::MetadataBXMLBox => read_bxml(&mut b, &mut meta)?, - _ => skip_box_content(&mut b)?, - }; - check_parser_state!(b.content); - } - Ok(meta) -} - -/// Parse a XML box inside a meta box -/// See ISOBMFF (ISO 14496-12:2020) § 8.11.2 -#[cfg(feature = "meta-xml")] -fn read_xml_<T: Read>(src: &mut BMFFBox<T>, meta: &mut MetadataBox) -> Result<()> { - if read_fullbox_version_no_flags(src)? != 0 { - return Err(Error::Unsupported("unsupported XmlBox version")); - } - meta.xml = Some(XmlBox::StringXmlBox(src.read_into_try_vec()?)); - Ok(()) -} - -/// Parse a Binary XML box inside a meta box -/// See ISOBMFF (ISO 14496-12:2020) § 8.11.2 -#[cfg(feature = "meta-xml")] -fn read_bxml<T: Read>(src: &mut BMFFBox<T>, meta: &mut MetadataBox) -> Result<()> { - if read_fullbox_version_no_flags(src)? != 0 { - return Err(Error::Unsupported("unsupported XmlBox version")); - } - meta.xml = Some(XmlBox::BinaryXmlBox(src.read_into_try_vec()?)); - Ok(()) -} - -/// Parse a metadata box inside a udta box -fn read_ilst<T: Read>(src: &mut BMFFBox<T>, meta: &mut MetadataBox) -> Result<()> { - let mut iter = src.box_iter(); - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::AlbumEntry => meta.album = read_ilst_string_data(&mut b)?, - BoxType::ArtistEntry | BoxType::ArtistLowercaseEntry => { - meta.artist = read_ilst_string_data(&mut b)? - } - BoxType::AlbumArtistEntry => meta.album_artist = read_ilst_string_data(&mut b)?, - BoxType::CommentEntry => meta.comment = read_ilst_string_data(&mut b)?, - BoxType::DateEntry => meta.year = read_ilst_string_data(&mut b)?, - BoxType::TitleEntry => meta.title = read_ilst_string_data(&mut b)?, - BoxType::CustomGenreEntry => { - meta.genre = read_ilst_string_data(&mut b)?.map(Genre::CustomGenre) - } - BoxType::StandardGenreEntry => { - meta.genre = read_ilst_u8_data(&mut b)? - .and_then(|gnre| Some(Genre::StandardGenre(gnre.get(1).copied()?))) - } - BoxType::ComposerEntry => meta.composer = read_ilst_string_data(&mut b)?, - BoxType::EncoderEntry => meta.encoder = read_ilst_string_data(&mut b)?, - BoxType::EncodedByEntry => meta.encoded_by = read_ilst_string_data(&mut b)?, - BoxType::CopyrightEntry => meta.copyright = read_ilst_string_data(&mut b)?, - BoxType::GroupingEntry => meta.grouping = read_ilst_string_data(&mut b)?, - BoxType::CategoryEntry => meta.category = read_ilst_string_data(&mut b)?, - BoxType::KeywordEntry => meta.keyword = read_ilst_string_data(&mut b)?, - BoxType::PodcastUrlEntry => meta.podcast_url = read_ilst_string_data(&mut b)?, - BoxType::PodcastGuidEntry => meta.podcast_guid = read_ilst_string_data(&mut b)?, - BoxType::DescriptionEntry => meta.description = read_ilst_string_data(&mut b)?, - BoxType::LongDescriptionEntry => meta.long_description = read_ilst_string_data(&mut b)?, - BoxType::LyricsEntry => meta.lyrics = read_ilst_string_data(&mut b)?, - BoxType::TVNetworkNameEntry => meta.tv_network_name = read_ilst_string_data(&mut b)?, - BoxType::TVEpisodeNameEntry => meta.tv_episode_name = read_ilst_string_data(&mut b)?, - BoxType::TVShowNameEntry => meta.tv_show_name = read_ilst_string_data(&mut b)?, - BoxType::PurchaseDateEntry => meta.purchase_date = read_ilst_string_data(&mut b)?, - BoxType::RatingEntry => meta.rating = read_ilst_string_data(&mut b)?, - BoxType::OwnerEntry => meta.owner = read_ilst_string_data(&mut b)?, - BoxType::HDVideoEntry => meta.hd_video = read_ilst_bool_data(&mut b)?, - BoxType::SortNameEntry => meta.sort_name = read_ilst_string_data(&mut b)?, - BoxType::SortArtistEntry => meta.sort_artist = read_ilst_string_data(&mut b)?, - BoxType::SortAlbumEntry => meta.sort_album = read_ilst_string_data(&mut b)?, - BoxType::SortAlbumArtistEntry => { - meta.sort_album_artist = read_ilst_string_data(&mut b)? - } - BoxType::SortComposerEntry => meta.sort_composer = read_ilst_string_data(&mut b)?, - BoxType::TrackNumberEntry => { - if let Some(trkn) = read_ilst_u8_data(&mut b)? { - meta.track_number = trkn.get(3).copied(); - meta.total_tracks = trkn.get(5).copied(); - }; - } - BoxType::DiskNumberEntry => { - if let Some(disk) = read_ilst_u8_data(&mut b)? { - meta.disc_number = disk.get(3).copied(); - meta.total_discs = disk.get(5).copied(); - }; - } - BoxType::TempoEntry => { - meta.beats_per_minute = - read_ilst_u8_data(&mut b)?.and_then(|tmpo| tmpo.get(1).copied()) - } - BoxType::CompilationEntry => meta.compilation = read_ilst_bool_data(&mut b)?, - BoxType::AdvisoryEntry => { - meta.advisory = read_ilst_u8_data(&mut b)?.and_then(|rtng| { - Some(match rtng.get(0)? { - 2 => AdvisoryRating::Clean, - 0 => AdvisoryRating::Inoffensive, - r => AdvisoryRating::Explicit(*r), - }) - }) - } - BoxType::MediaTypeEntry => { - meta.media_type = read_ilst_u8_data(&mut b)?.and_then(|stik| { - Some(match stik.get(0)? { - 0 => MediaType::Movie, - 1 => MediaType::Normal, - 2 => MediaType::AudioBook, - 5 => MediaType::WhackedBookmark, - 6 => MediaType::MusicVideo, - 9 => MediaType::ShortFilm, - 10 => MediaType::TVShow, - 11 => MediaType::Booklet, - s => MediaType::Unknown(*s), - }) - }) - } - BoxType::PodcastEntry => meta.podcast = read_ilst_bool_data(&mut b)?, - BoxType::TVSeasonNumberEntry => { - meta.tv_season = read_ilst_u8_data(&mut b)?.and_then(|tvsn| tvsn.get(3).copied()) - } - BoxType::TVEpisodeNumberEntry => { - meta.tv_episode_number = - read_ilst_u8_data(&mut b)?.and_then(|tves| tves.get(3).copied()) - } - BoxType::GaplessPlaybackEntry => meta.gapless_playback = read_ilst_bool_data(&mut b)?, - BoxType::CoverArtEntry => meta.cover_art = read_ilst_multiple_u8_data(&mut b).ok(), - _ => skip_box_content(&mut b)?, - }; - check_parser_state!(b.content); - } - Ok(()) -} - -fn read_ilst_bool_data<T: Read>(src: &mut BMFFBox<T>) -> Result<Option<bool>> { - Ok(read_ilst_u8_data(src)?.and_then(|d| Some(d.get(0)? == &1))) -} - -fn read_ilst_string_data<T: Read>(src: &mut BMFFBox<T>) -> Result<Option<TryString>> { - read_ilst_u8_data(src) -} - -fn read_ilst_u8_data<T: Read>(src: &mut BMFFBox<T>) -> Result<Option<TryVec<u8>>> { - // For all non-covr atoms, there must only be one data atom. - Ok(read_ilst_multiple_u8_data(src)?.pop()) -} - -fn read_ilst_multiple_u8_data<T: Read>(src: &mut BMFFBox<T>) -> Result<TryVec<TryVec<u8>>> { - let mut iter = src.box_iter(); - let mut data = TryVec::new(); - while let Some(mut b) = iter.next_box()? { - match b.head.name { - BoxType::MetadataItemDataEntry => { - data.push(read_ilst_data(&mut b)?)?; - } - _ => skip_box_content(&mut b)?, - }; - check_parser_state!(b.content); - } - Ok(data) -} - -fn read_ilst_data<T: Read>(src: &mut BMFFBox<T>) -> Result<TryVec<u8>> { - // Skip past the padding bytes - skip(&mut src.content, src.head.offset)?; - let size = src.content.limit(); - read_buf(&mut src.content, size) -} - -/// Skip a number of bytes that we don't care to parse. -fn skip<T: Read>(src: &mut T, bytes: u64) -> Result<()> { - std::io::copy(&mut src.take(bytes), &mut std::io::sink())?; - Ok(()) -} - -/// Read size bytes into a Vector or return error. -fn read_buf<T: Read>(src: &mut T, size: u64) -> Result<TryVec<u8>> { - let buf = src.take(size).read_into_try_vec()?; - if buf.len().to_u64() != size { - return Err(Error::InvalidData("failed buffer read")); - } - - Ok(buf) -} - -fn be_i16<T: ReadBytesExt>(src: &mut T) -> Result<i16> { - src.read_i16::<byteorder::BigEndian>().map_err(From::from) -} - -fn be_i32<T: ReadBytesExt>(src: &mut T) -> Result<i32> { - src.read_i32::<byteorder::BigEndian>().map_err(From::from) -} - -fn be_i64<T: ReadBytesExt>(src: &mut T) -> Result<i64> { - src.read_i64::<byteorder::BigEndian>().map_err(From::from) -} - -fn be_u16<T: ReadBytesExt>(src: &mut T) -> Result<u16> { - src.read_u16::<byteorder::BigEndian>().map_err(From::from) -} - -fn be_u24<T: ReadBytesExt>(src: &mut T) -> Result<u32> { - src.read_u24::<byteorder::BigEndian>().map_err(From::from) -} - -fn be_u32<T: ReadBytesExt>(src: &mut T) -> Result<u32> { - src.read_u32::<byteorder::BigEndian>().map_err(From::from) -} - -fn be_u64<T: ReadBytesExt>(src: &mut T) -> Result<u64> { - src.read_u64::<byteorder::BigEndian>().map_err(From::from) -} - -fn write_be_u32<T: WriteBytesExt>(des: &mut T, num: u32) -> Result<()> { - des.write_u32::<byteorder::BigEndian>(num) - .map_err(From::from) -} diff --git a/lib/mp4/mp4parse/src/macros.rs b/lib/mp4/mp4parse/src/macros.rs deleted file mode 100644 index a893f7e..0000000 --- a/lib/mp4/mp4parse/src/macros.rs +++ /dev/null @@ -1,12 +0,0 @@ -// 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/. - -macro_rules! check_parser_state { - ( $src:expr ) => { - if $src.limit() > 0 { - debug!("bad parser state: {} content bytes left", $src.limit()); - return Err(Error::InvalidData("unread box content or bad parser sync")); - } - }; -} diff --git a/lib/mp4/mp4parse/src/tests.rs b/lib/mp4/mp4parse/src/tests.rs deleted file mode 100644 index 6a54e98..0000000 --- a/lib/mp4/mp4parse/src/tests.rs +++ /dev/null @@ -1,1330 +0,0 @@ -//! Module for parsing ISO Base Media Format aka video/mp4 streams. -//! Internal unit tests. - -// 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 super::read_mp4; -use super::Error; -use super::FourCC; -use super::ParseStrictness; -use fallible_collections::TryRead as _; - -use std::convert::TryInto as _; -use std::io::Cursor; -use std::io::Read as _; -extern crate test_assembler; -use self::test_assembler::*; - -use boxes::BoxType; - -enum BoxSize { - Short(u32), - Long(u64), - UncheckedShort(u32), - UncheckedLong(u64), - Auto, -} - -#[allow(clippy::trivially_copy_pass_by_ref)] // TODO: Consider reworking to a copy -fn make_box<F>(size: BoxSize, name: &[u8; 4], func: F) -> Cursor<Vec<u8>> -where - F: Fn(Section) -> Section, -{ - let mut section = Section::new(); - let box_size = Label::new(); - section = match size { - BoxSize::Short(size) | BoxSize::UncheckedShort(size) => section.B32(size), - BoxSize::Long(_) | BoxSize::UncheckedLong(_) => section.B32(1), - BoxSize::Auto => section.B32(&box_size), - }; - section = section.append_bytes(name); - section = match size { - // The spec allows the 32-bit size to be 0 to indicate unknown - // length streams. It's not clear if this is valid when using a - // 64-bit size, so prohibit it for now. - BoxSize::Long(size) => { - assert!(size > 0); - section.B64(size) - } - BoxSize::UncheckedLong(size) => section.B64(size), - _ => section, - }; - section = func(section); - match size { - BoxSize::Short(size) => { - if size > 0 { - assert_eq!(u64::from(size), section.size()) - } - } - BoxSize::Long(size) => assert_eq!(size, section.size()), - BoxSize::Auto => { - assert!( - section.size() <= u64::from(u32::max_value()), - "Tried to use a long box with BoxSize::Auto" - ); - box_size.set_const(section.size()); - } - // Skip checking BoxSize::Unchecked* cases. - _ => (), - } - Cursor::new(section.get_contents().unwrap()) -} - -fn make_uuid_box<F>(size: BoxSize, uuid: &[u8; 16], func: F) -> Cursor<Vec<u8>> -where - F: Fn(Section) -> Section, -{ - make_box(size, b"uuid", |mut s| { - for b in uuid { - s = s.B8(*b); - } - func(s) - }) -} - -#[allow(clippy::trivially_copy_pass_by_ref)] // TODO: Consider reworking to a copy -fn make_fullbox<F>(size: BoxSize, name: &[u8; 4], version: u8, func: F) -> Cursor<Vec<u8>> -where - F: Fn(Section) -> Section, -{ - make_box(size, name, |s| func(s.B8(version).B8(0).B8(0).B8(0))) -} - -#[test] -fn read_box_header_short() { - let mut stream = make_box(BoxSize::Short(8), b"test", |s| s); - let header = super::read_box_header(&mut stream).unwrap(); - assert_eq!(header.name, BoxType::UnknownBox(0x7465_7374)); // "test" - assert_eq!(header.size, 8); - assert!(header.uuid.is_none()); -} - -#[test] -fn read_box_header_long() { - let mut stream = make_box(BoxSize::Long(16), b"test", |s| s); - let header = super::read_box_header(&mut stream).unwrap(); - assert_eq!(header.name, BoxType::UnknownBox(0x7465_7374)); // "test" - assert_eq!(header.size, 16); - assert!(header.uuid.is_none()); -} - -#[test] -fn read_box_header_short_unknown_size() { - let mut stream = make_box(BoxSize::Short(0), b"test", |s| s); - match super::read_box_header(&mut stream) { - Err(Error::Unsupported(s)) => assert_eq!(s, "unknown sized box"), - _ => panic!("unexpected result reading box with unknown size"), - }; -} - -#[test] -fn read_box_header_short_invalid_size() { - let mut stream = make_box(BoxSize::UncheckedShort(2), b"test", |s| s); - match super::read_box_header(&mut stream) { - Err(Error::InvalidData(s)) => assert_eq!(s, "malformed size"), - _ => panic!("unexpected result reading box with invalid size"), - }; -} - -#[test] -fn read_box_header_long_invalid_size() { - let mut stream = make_box(BoxSize::UncheckedLong(2), b"test", |s| s); - match super::read_box_header(&mut stream) { - Err(Error::InvalidData(s)) => assert_eq!(s, "malformed wide size"), - _ => panic!("unexpected result reading box with invalid size"), - }; -} - -#[test] -fn read_box_header_uuid() { - const HEADER_UUID: [u8; 16] = [ - 0x85, 0xc0, 0xb6, 0x87, 0x82, 0x0f, 0x11, 0xe0, 0x81, 0x11, 0xf4, 0xce, 0x46, 0x2b, 0x6a, - 0x48, - ]; - - let mut stream = make_uuid_box(BoxSize::Short(24), &HEADER_UUID, |s| s); - let mut iter = super::BoxIter::new(&mut stream); - let stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::UuidBox); - assert_eq!(stream.head.size, 24); - assert!(stream.head.uuid.is_some()); - assert_eq!(stream.head.uuid.unwrap(), HEADER_UUID); -} - -#[test] -fn read_box_header_truncated_uuid() { - const HEADER_UUID: [u8; 16] = [ - 0x85, 0xc0, 0xb6, 0x87, 0x82, 0x0f, 0x11, 0xe0, 0x81, 0x11, 0xf4, 0xce, 0x46, 0x2b, 0x6a, - 0x48, - ]; - - let mut stream = make_uuid_box(BoxSize::UncheckedShort(23), &HEADER_UUID, |s| s); - let mut iter = super::BoxIter::new(&mut stream); - let stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::UuidBox); - assert_eq!(stream.head.size, 23); - assert!(stream.head.uuid.is_none()); -} - -#[test] -fn read_ftyp() { - let mut stream = make_box(BoxSize::Short(24), b"ftyp", |s| { - s.append_bytes(b"mp42") - .B32(0) // minor version - .append_bytes(b"isom") - .append_bytes(b"mp42") - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::FileTypeBox); - assert_eq!(stream.head.size, 24); - let parsed = super::read_ftyp(&mut stream).unwrap(); - assert_eq!(parsed.major_brand, b"mp42"); // mp42 - assert_eq!(parsed.minor_version, 0); - assert_eq!(parsed.compatible_brands.len(), 2); - assert_eq!(parsed.compatible_brands[0], b"isom"); // isom - assert_eq!(parsed.compatible_brands[1], b"mp42"); // mp42 -} - -#[test] -fn read_truncated_ftyp() { - // We declare a 24 byte box, but only write 20 bytes. - let mut stream = make_box(BoxSize::UncheckedShort(24), b"ftyp", |s| { - s.append_bytes(b"mp42") - .B32(0) // minor version - .append_bytes(b"isom") - }); - match read_mp4(&mut stream) { - Err(Error::UnexpectedEOF) => (), - Ok(_) => panic!("expected an error result"), - _ => panic!("expected a different error result"), - } -} - -#[test] -fn read_ftyp_case() { - // Brands in BMFF are represented as a u32, so it would seem clear that - // 0x6d703432 ("mp42") is not equal to 0x4d503432 ("MP42"), but some - // demuxers treat these as case-insensitive strings, e.g. street.mp4's - // major brand is "MP42". I haven't seen case-insensitive - // compatible_brands (which we also test here), but it doesn't seem - // unlikely given the major_brand behaviour. - let mut stream = make_box(BoxSize::Auto, b"ftyp", |s| { - s.append_bytes(b"MP42") - .B32(0) // minor version - .append_bytes(b"ISOM") - .append_bytes(b"MP42") - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::FileTypeBox); - assert_eq!(stream.head.size, 24); - let parsed = super::read_ftyp(&mut stream).unwrap(); - assert_eq!(parsed.major_brand, b"MP42"); - assert_eq!(parsed.minor_version, 0); - assert_eq!(parsed.compatible_brands.len(), 2); - assert_eq!(parsed.compatible_brands[0], b"ISOM"); // ISOM - assert_eq!(parsed.compatible_brands[1], b"MP42"); // MP42 -} - -#[test] -fn read_elst_v0() { - let mut stream = make_fullbox(BoxSize::Short(28), b"elst", 0, |s| { - s.B32(1) // list count - // first entry - .B32(1234) // duration - .B32(5678) // time - .B16(12) // rate integer - .B16(34) // rate fraction - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::EditListBox); - assert_eq!(stream.head.size, 28); - let parsed = super::read_elst(&mut stream).unwrap(); - assert_eq!(parsed.edits.len(), 1); - assert_eq!(parsed.edits[0].segment_duration, 1234); - assert_eq!(parsed.edits[0].media_time, 5678); - assert_eq!(parsed.edits[0].media_rate_integer, 12); - assert_eq!(parsed.edits[0].media_rate_fraction, 34); -} - -#[test] -fn read_elst_v1() { - let mut stream = make_fullbox(BoxSize::Short(56), b"elst", 1, |s| { - s.B32(2) // list count - // first entry - .B64(1234) // duration - .B64(5678) // time - .B16(12) // rate integer - .B16(34) // rate fraction - // second entry - .B64(1234) // duration - .B64(5678) // time - .B16(12) // rate integer - .B16(34) // rate fraction - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::EditListBox); - assert_eq!(stream.head.size, 56); - let parsed = super::read_elst(&mut stream).unwrap(); - assert_eq!(parsed.edits.len(), 2); - assert_eq!(parsed.edits[1].segment_duration, 1234); - assert_eq!(parsed.edits[1].media_time, 5678); - assert_eq!(parsed.edits[1].media_rate_integer, 12); - assert_eq!(parsed.edits[1].media_rate_fraction, 34); -} - -#[test] -fn read_mdhd_v0() { - let mut stream = make_fullbox(BoxSize::Short(32), b"mdhd", 0, |s| { - s.B32(0) - .B32(0) - .B32(1234) // timescale - .B32(5678) // duration - .B32(0) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::MediaHeaderBox); - assert_eq!(stream.head.size, 32); - let parsed = super::read_mdhd(&mut stream).unwrap(); - assert_eq!(parsed.timescale, 1234); - assert_eq!(parsed.duration, 5678); -} - -#[test] -fn read_mdhd_v1() { - let mut stream = make_fullbox(BoxSize::Short(44), b"mdhd", 1, |s| { - s.B64(0) - .B64(0) - .B32(1234) // timescale - .B64(5678) // duration - .B32(0) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::MediaHeaderBox); - assert_eq!(stream.head.size, 44); - let parsed = super::read_mdhd(&mut stream).unwrap(); - assert_eq!(parsed.timescale, 1234); - assert_eq!(parsed.duration, 5678); -} - -#[test] -fn read_mdhd_unknown_duration() { - let mut stream = make_fullbox(BoxSize::Short(32), b"mdhd", 0, |s| { - s.B32(0) - .B32(0) - .B32(1234) // timescale - .B32(::std::u32::MAX) // duration - .B32(0) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::MediaHeaderBox); - assert_eq!(stream.head.size, 32); - let parsed = super::read_mdhd(&mut stream).unwrap(); - assert_eq!(parsed.timescale, 1234); - assert_eq!(parsed.duration, ::std::u64::MAX); -} - -#[test] -fn read_mdhd_invalid_timescale() { - let mut stream = make_fullbox(BoxSize::Short(44), b"mdhd", 1, |s| { - s.B64(0) - .B64(0) - .B32(0) // timescale - .B64(5678) // duration - .B32(0) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::MediaHeaderBox); - assert_eq!(stream.head.size, 44); - let r = super::parse_mdhd(&mut stream, &mut super::Track::new(0)); - assert!(r.is_err()); -} - -#[test] -fn read_mvhd_v0() { - let mut stream = make_fullbox(BoxSize::Short(108), b"mvhd", 0, |s| { - s.B32(0).B32(0).B32(1234).B32(5678).append_repeated(0, 80) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::MovieHeaderBox); - assert_eq!(stream.head.size, 108); - let parsed = super::read_mvhd(&mut stream).unwrap(); - assert_eq!(parsed.timescale, 1234); - assert_eq!(parsed.duration, 5678); -} - -#[test] -fn read_mvhd_v1() { - let mut stream = make_fullbox(BoxSize::Short(120), b"mvhd", 1, |s| { - s.B64(0).B64(0).B32(1234).B64(5678).append_repeated(0, 80) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::MovieHeaderBox); - assert_eq!(stream.head.size, 120); - let parsed = super::read_mvhd(&mut stream).unwrap(); - assert_eq!(parsed.timescale, 1234); - assert_eq!(parsed.duration, 5678); -} - -#[test] -fn read_mvhd_invalid_timescale() { - let mut stream = make_fullbox(BoxSize::Short(120), b"mvhd", 1, |s| { - s.B64(0).B64(0).B32(0).B64(5678).append_repeated(0, 80) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::MovieHeaderBox); - assert_eq!(stream.head.size, 120); - let r = super::parse_mvhd(&mut stream); - assert!(r.is_err()); -} - -#[test] -fn read_mvhd_unknown_duration() { - let mut stream = make_fullbox(BoxSize::Short(108), b"mvhd", 0, |s| { - s.B32(0) - .B32(0) - .B32(1234) - .B32(::std::u32::MAX) - .append_repeated(0, 80) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::MovieHeaderBox); - assert_eq!(stream.head.size, 108); - let parsed = super::read_mvhd(&mut stream).unwrap(); - assert_eq!(parsed.timescale, 1234); - assert_eq!(parsed.duration, ::std::u64::MAX); -} - -#[test] -fn read_vpcc_version_0() { - let data_length = 12u16; - let mut stream = make_fullbox(BoxSize::Auto, b"vpcC", 0, |s| { - s.B8(2) - .B8(0) - .B8(0x82) - .B8(0) - .B16(data_length) - .append_repeated(42, data_length as usize) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::VPCodecConfigurationBox); - let r = super::read_vpcc(&mut stream); - assert!(r.is_ok()); -} - -// TODO: it'd be better to find a real sample here. -#[test] -#[allow(clippy::unusual_byte_groupings)] // Allow odd grouping for test readability. -fn read_vpcc_version_1() { - let data_length = 12u16; - let mut stream = make_fullbox(BoxSize::Auto, b"vpcC", 1, |s| { - s.B8(2) // profile - .B8(0) // level - .B8(0b1000_011_0) // bitdepth (4 bits), chroma (3 bits), video full range (1 bit) - .B8(1) // color primaries - .B8(1) // transfer characteristics - .B8(1) // matrix - .B16(data_length) - .append_repeated(42, data_length as usize) - }); - - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::VPCodecConfigurationBox); - let r = super::read_vpcc(&mut stream); - match r { - Ok(vpcc) => { - assert_eq!(vpcc.bit_depth, 8); - assert_eq!(vpcc.chroma_subsampling, 3); - assert!(!vpcc.video_full_range_flag); - assert_eq!(vpcc.matrix_coefficients.unwrap(), 1); - } - _ => panic!("vpcc parsing error"), - } -} - -#[test] -fn read_hdlr() { - let mut stream = make_fullbox(BoxSize::Short(45), b"hdlr", 0, |s| { - s.B32(0) - .append_bytes(b"vide") - .B32(0) - .B32(0) - .B32(0) - .append_bytes(b"VideoHandler") - .B8(0) // null-terminate string - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::HandlerBox); - assert_eq!(stream.head.size, 45); - let parsed = super::read_hdlr(&mut stream, ParseStrictness::Normal).unwrap(); - assert_eq!(parsed.handler_type, b"vide"); -} - -#[test] -fn read_hdlr_multiple_nul_in_name() { - let mut stream = make_fullbox(BoxSize::Short(45), b"hdlr", 0, |s| { - s.B32(0) - .append_bytes(b"vide") - .B32(0) - .B32(0) - .B32(0) - .append_bytes(b"Vide\0Handler") - .B8(0) // null-terminate string - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::HandlerBox); - assert_eq!(stream.head.size, 45); - assert!(super::read_hdlr(&mut stream, ParseStrictness::Strict).is_err()); -} - -#[test] -fn read_hdlr_short_name() { - let mut stream = make_fullbox(BoxSize::Short(33), b"hdlr", 0, |s| { - s.B32(0).append_bytes(b"vide").B32(0).B32(0).B32(0).B8(0) // null-terminate string - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::HandlerBox); - assert_eq!(stream.head.size, 33); - let parsed = super::read_hdlr(&mut stream, ParseStrictness::Normal).unwrap(); - assert_eq!(parsed.handler_type, b"vide"); -} - -#[test] -fn read_hdlr_unsupported_version() { - let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 1, |s| { - s.B32(0).append_bytes(b"vide").B32(0).B32(0).B32(0) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::HandlerBox); - assert_eq!(stream.head.size, 32); - match super::read_hdlr(&mut stream, ParseStrictness::Normal) { - Err(Error::Unsupported(msg)) => assert_eq!("hdlr version", msg), - result => { - eprintln!("{:?}", result); - panic!("expected Error::Unsupported") - } - } -} - -#[test] -fn read_hdlr_invalid_pre_defined_field() { - let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 0, |s| { - s.B32(1).append_bytes(b"vide").B32(0).B32(0).B32(0) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::HandlerBox); - assert_eq!(stream.head.size, 32); - match super::read_hdlr(&mut stream, ParseStrictness::Strict) { - Err(Error::InvalidData(msg)) => assert_eq!( - "The HandlerBox 'pre_defined' field shall be 0 \ - per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2", - msg - ), - result => { - eprintln!("{:?}", result); - panic!("expected Error::InvalidData") - } - } -} - -#[test] -fn read_hdlr_invalid_reserved_field() { - let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 0, |s| { - s.B32(0).append_bytes(b"vide").B32(0).B32(1).B32(0) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::HandlerBox); - assert_eq!(stream.head.size, 32); - match super::read_hdlr(&mut stream, ParseStrictness::Strict) { - Err(Error::InvalidData(msg)) => assert_eq!( - "The HandlerBox 'reserved' fields shall be 0 \ - per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2", - msg - ), - result => { - eprintln!("{:?}", result); - panic!("expected Error::InvalidData") - } - } -} - -#[test] -fn read_hdlr_zero_length_name() { - let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 0, |s| { - s.B32(0).append_bytes(b"vide").B32(0).B32(0).B32(0) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::HandlerBox); - assert_eq!(stream.head.size, 32); - match super::read_hdlr(&mut stream, ParseStrictness::Normal) { - Err(Error::InvalidData(msg)) => assert_eq!( - "The HandlerBox 'name' field shall be null-terminated \ - per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2", - msg - ), - result => { - eprintln!("{:?}", result); - panic!("expected Error::InvalidData") - } - } -} - -#[test] -fn read_hdlr_zero_length_name_permissive() { - let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 0, |s| { - s.B32(0).append_bytes(b"vide").B32(0).B32(0).B32(0) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::HandlerBox); - assert_eq!(stream.head.size, 32); - let parsed = super::read_hdlr(&mut stream, ParseStrictness::Permissive).unwrap(); - assert_eq!(parsed.handler_type, b"vide"); -} - -fn flac_streaminfo() -> Vec<u8> { - vec![ - 0x10, 0x00, 0x10, 0x00, 0x00, 0x0a, 0x11, 0x00, 0x38, 0x32, 0x0a, 0xc4, 0x42, 0xf0, 0x00, - 0xc9, 0xdf, 0xae, 0xb5, 0x66, 0xfc, 0x02, 0x15, 0xa3, 0xb1, 0x54, 0x61, 0x47, 0x0f, 0xfb, - 0x05, 0x00, 0x33, 0xad, - ] -} - -#[test] -fn read_flac() { - let mut stream = make_box(BoxSize::Auto, b"fLaC", |s| { - s.append_repeated(0, 6) // reserved - .B16(1) // data reference index - .B32(0) // reserved - .B32(0) // reserved - .B16(2) // channel count - .B16(16) // bits per sample - .B16(0) // pre_defined - .B16(0) // reserved - .B32(44100 << 16) // Sample rate - .append_bytes( - &make_dfla( - FlacBlockType::StreamInfo, - true, - &flac_streaminfo(), - FlacBlockLength::Correct, - ) - .into_inner(), - ) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - let r = super::read_audio_sample_entry(&mut stream); - assert!(r.is_ok()); -} - -#[derive(Clone, Copy)] -enum FlacBlockType { - StreamInfo = 0, - _Padding = 1, - _Application = 2, - _Seektable = 3, - _Comment = 4, - _Cuesheet = 5, - _Picture = 6, - _Reserved, - _Invalid = 127, -} - -enum FlacBlockLength { - Correct, - Incorrect(usize), -} - -fn make_dfla( - block_type: FlacBlockType, - last: bool, - data: &[u8], - data_length: FlacBlockLength, -) -> Cursor<Vec<u8>> { - assert!(data.len() < 1 << 24); - make_fullbox(BoxSize::Auto, b"dfLa", 0, |s| { - let flag = if last { 1 } else { 0 }; - let size = match data_length { - FlacBlockLength::Correct => (data.len() as u32) & 0x00ff_ffff, - FlacBlockLength::Incorrect(size) => { - assert!(size < 1 << 24); - (size as u32) & 0x00ff_ffff - } - }; - let block_type = (block_type as u32) & 0x7f; - s.B32(flag << 31 | block_type << 24 | size) - .append_bytes(data) - }) -} - -#[test] -fn read_dfla() { - let mut stream = make_dfla( - FlacBlockType::StreamInfo, - true, - &flac_streaminfo(), - FlacBlockLength::Correct, - ); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::FLACSpecificBox); - let dfla = super::read_dfla(&mut stream).unwrap(); - assert_eq!(dfla.version, 0); -} - -#[test] -fn long_flac_metadata() { - let streaminfo = flac_streaminfo(); - let mut stream = make_dfla( - FlacBlockType::StreamInfo, - true, - &streaminfo, - FlacBlockLength::Incorrect(streaminfo.len() + 4), - ); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::FLACSpecificBox); - let r = super::read_dfla(&mut stream); - assert!(r.is_err()); -} - -#[test] -fn read_opus() { - let mut stream = make_box(BoxSize::Auto, b"Opus", |s| { - s.append_repeated(0, 6) - .B16(1) // data reference index - .B32(0) - .B32(0) - .B16(2) // channel count - .B16(16) // bits per sample - .B16(0) - .B16(0) - .B32(48000 << 16) // Sample rate is always 48 kHz for Opus. - .append_bytes(&make_dops().into_inner()) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - let r = super::read_audio_sample_entry(&mut stream); - assert!(r.is_ok()); -} - -fn make_dops() -> Cursor<Vec<u8>> { - make_box(BoxSize::Auto, b"dOps", |s| { - s.B8(0) // version - .B8(2) // channel count - .B16(348) // pre-skip - .B32(44100) // original sample rate - .B16(0) // gain - .B8(0) // channel mapping - }) -} - -#[test] -fn read_dops() { - let mut stream = make_dops(); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - assert_eq!(stream.head.name, BoxType::OpusSpecificBox); - let r = super::read_dops(&mut stream); - assert!(r.is_ok()); -} - -#[test] -fn serialize_opus_header() { - let opus = super::OpusSpecificBox { - version: 0, - output_channel_count: 1, - pre_skip: 342, - input_sample_rate: 24000, - output_gain: 0, - channel_mapping_family: 0, - channel_mapping_table: None, - }; - let mut v = Vec::<u8>::new(); - super::serialize_opus_header(&opus, &mut v).unwrap(); - assert_eq!(v.len(), 19); - assert_eq!( - v, - vec![ - 0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x01, 0x56, 0x01, 0xc0, 0x5d, - 0x00, 0x00, 0x00, 0x00, 0x00, - ] - ); - let opus = super::OpusSpecificBox { - version: 0, - output_channel_count: 6, - pre_skip: 152, - input_sample_rate: 48000, - output_gain: 0, - channel_mapping_family: 1, - channel_mapping_table: Some(super::ChannelMappingTable { - stream_count: 4, - coupled_count: 2, - channel_mapping: vec![0, 4, 1, 2, 3, 5].into(), - }), - }; - let mut v = Vec::<u8>::new(); - super::serialize_opus_header(&opus, &mut v).unwrap(); - assert_eq!(v.len(), 27); - assert_eq!( - v, - vec![ - 0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x06, 0x98, 0x00, 0x80, 0xbb, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x02, 0x00, 0x04, 0x01, 0x02, 0x03, 0x05, - ] - ); -} - -#[test] -fn read_alac() { - let mut stream = make_box(BoxSize::Auto, b"alac", |s| { - s.append_repeated(0, 6) // reserved - .B16(1) // data reference index - .B32(0) // reserved - .B32(0) // reserved - .B16(2) // channel count - .B16(16) // bits per sample - .B16(0) // pre_defined - .B16(0) // reserved - .B32(44100 << 16) // Sample rate - .append_bytes( - &make_fullbox(BoxSize::Auto, b"alac", 0, |s| s.append_bytes(&[0xfa; 24])) - .into_inner(), - ) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - let r = super::read_audio_sample_entry(&mut stream); - assert!(r.is_ok()); -} - -#[test] -fn esds_limit() { - let mut stream = make_box(BoxSize::Auto, b"mp4a", |s| { - s.append_repeated(0, 6) - .B16(1) - .B32(0) - .B32(0) - .B16(2) - .B16(16) - .B16(0) - .B16(0) - .B32(48000 << 16) - .B32(8) - .append_bytes(b"esds") - .append_repeated(0, 4) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - match super::read_audio_sample_entry(&mut stream) { - Err(Error::UnexpectedEOF) => (), - Ok(_) => panic!("expected an error result"), - _ => panic!("expected a different error result"), - } -} - -#[test] -fn read_elst_zero_entries() { - let mut stream = make_fullbox(BoxSize::Auto, b"elst", 0, |s| s.B32(0).B16(12).B16(34)); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - match super::read_elst(&mut stream) { - Ok(elst) => assert_eq!(elst.edits.len(), 0), - _ => panic!("expected no error"), - } -} - -fn make_elst() -> Cursor<Vec<u8>> { - make_fullbox(BoxSize::Auto, b"elst", 1, |s| { - s.B32(1) - // first entry - .B64(1234) // duration - .B64(0xffff_ffff_ffff_ffff) // time - .B16(12) // rate integer - .B16(34) // rate fraction - }) -} - -#[test] -fn read_edts_bogus() { - // First edit list entry has a media_time of -1, so we expect a second - // edit list entry to be present to provide a valid media_time. - // Bogus edts are ignored. - let mut stream = make_box(BoxSize::Auto, b"edts", |s| { - s.append_bytes(&make_elst().into_inner()) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - let mut track = super::Track::new(0); - match super::read_edts(&mut stream, &mut track) { - Ok(_) => { - assert_eq!(track.media_time, None); - assert_eq!(track.empty_duration, None); - } - _ => panic!("expected no error"), - } -} - -#[test] -fn skip_padding_in_boxes() { - // Padding data could be added in the end of these boxes. Parser needs to skip - // them instead of returning error. - let box_names = vec![b"stts", b"stsc", b"stsz", b"stco", b"co64", b"stss"]; - - for name in box_names { - let mut stream = make_fullbox(BoxSize::Auto, name, 1, |s| { - s.append_repeated(0, 100) // add padding data - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - match name { - b"stts" => { - super::read_stts(&mut stream).expect("fail to skip padding: stts"); - } - b"stsc" => { - super::read_stsc(&mut stream).expect("fail to skip padding: stsc"); - } - b"stsz" => { - super::read_stsz(&mut stream).expect("fail to skip padding: stsz"); - } - b"stco" => { - super::read_stco(&mut stream).expect("fail to skip padding: stco"); - } - b"co64" => { - super::read_co64(&mut stream).expect("fail to skip padding: co64"); - } - b"stss" => { - super::read_stss(&mut stream).expect("fail to skip padding: stss"); - } - _ => (), - } - } -} - -#[test] -fn skip_padding_in_stsd() { - // Padding data could be added in the end of stsd boxes. Parser needs to skip - // them instead of returning error. - let avc = make_box(BoxSize::Auto, b"avc1", |s| { - s.append_repeated(0, 6) - .B16(1) - .append_repeated(0, 16) - .B16(320) - .B16(240) - .append_repeated(0, 14) - .append_repeated(0, 32) - .append_repeated(0, 4) - .B32(0xffff_ffff) - .append_bytes(b"avcC") - .append_repeated(0, 100) - }) - .into_inner(); - let mut stream = make_fullbox(BoxSize::Auto, b"stsd", 0, |s| { - s.B32(1) - .append_bytes(avc.as_slice()) - .append_repeated(0, 100) // add padding data - }); - - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - super::read_stsd( - &mut stream, - &mut super::Track::new(0), - &FourCC::from(*b"isom"), - ) - .expect("fail to skip padding: stsd"); -} - -#[test] -fn read_qt_wave_atom() { - let esds = make_fullbox(BoxSize::Auto, b"esds", 0, |s| { - s.B8(0x03) // elementary stream descriptor tag - .B8(0x12) // esds length - .append_repeated(0, 2) - .B8(0x00) // flags - .B8(0x04) // decoder config descriptor tag - .B8(0x0d) // dcds length - .B8(0x6b) // mp3 - .append_repeated(0, 12) - }) - .into_inner(); - let chan = make_box(BoxSize::Auto, b"chan", |s| { - s.append_repeated(0, 10) // we don't care its data. - }) - .into_inner(); - let wave = make_box(BoxSize::Auto, b"wave", |s| s.append_bytes(esds.as_slice())).into_inner(); - let mut stream = make_box(BoxSize::Auto, b"mp4a", |s| { - s.append_repeated(0, 6) - .B16(1) // data_reference_count - .B16(1) // verion: qt -> 1 - .append_repeated(0, 6) - .B16(2) - .B16(16) - .append_repeated(0, 4) - .B32(48000 << 16) - .append_repeated(0, 16) - .append_bytes(wave.as_slice()) - .append_bytes(chan.as_slice()) - }); - - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - let sample_entry = - super::read_audio_sample_entry(&mut stream).expect("fail to read qt wave atom"); - match sample_entry { - super::SampleEntry::Audio(sample_entry) => { - assert_eq!(sample_entry.codec_type, super::CodecType::MP3) - } - _ => panic!("fail to read audio sample enctry"), - } -} - -#[test] -fn read_descriptor_80() { - let aac_esds = vec![ - 0x03, 0x80, 0x80, 0x80, 0x22, 0x00, 0x02, 0x00, 0x04, 0x80, 0x80, 0x80, 0x17, 0x40, 0x15, - 0x00, 0x00, 0x00, 0x00, 0x03, 0x22, 0xBC, 0x00, 0x01, 0xF5, 0x83, 0x05, 0x80, 0x80, 0x80, - 0x02, 0x11, 0x90, 0x06, 0x80, 0x80, 0x80, 0x01, 0x02, - ]; - let aac_dc_descriptor = &aac_esds[31..33]; - let mut stream = make_box(BoxSize::Auto, b"esds", |s| { - s.B32(0) // reserved - .append_bytes(aac_esds.as_slice()) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - - let es = super::read_esds(&mut stream).unwrap(); - - assert_eq!(es.audio_codec, super::CodecType::AAC); - assert_eq!(es.audio_object_type, Some(2)); - assert_eq!(es.extended_audio_object_type, None); - assert_eq!(es.audio_sample_rate, Some(48000)); - assert_eq!(es.audio_channel_count, Some(2)); - assert_eq!(es.codec_esds, aac_esds); - assert_eq!(es.decoder_specific_data, aac_dc_descriptor); -} - -#[test] -fn read_esds() { - let aac_esds = vec![ - 0x03, 0x24, 0x00, 0x00, 0x00, 0x04, 0x1c, 0x40, 0x15, 0x00, 0x12, 0x00, 0x00, 0x01, 0xf4, - 0x00, 0x00, 0x01, 0xf4, 0x00, 0x05, 0x0d, 0x13, 0x00, 0x05, 0x88, 0x05, 0x00, 0x48, 0x21, - 0x10, 0x00, 0x56, 0xe5, 0x98, 0x06, 0x01, 0x02, - ]; - let aac_dc_descriptor = &aac_esds[22..35]; - - let mut stream = make_box(BoxSize::Auto, b"esds", |s| { - s.B32(0) // reserved - .append_bytes(aac_esds.as_slice()) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - - let es = super::read_esds(&mut stream).unwrap(); - - assert_eq!(es.audio_codec, super::CodecType::AAC); - assert_eq!(es.audio_object_type, Some(2)); - assert_eq!(es.extended_audio_object_type, None); - assert_eq!(es.audio_sample_rate, Some(24000)); - assert_eq!(es.audio_channel_count, Some(6)); - assert_eq!(es.codec_esds, aac_esds); - assert_eq!(es.decoder_specific_data, aac_dc_descriptor); -} - -#[test] -fn read_esds_aac_type5() { - let aac_esds = vec![ - 0x03, 0x80, 0x80, 0x80, 0x2F, 0x00, 0x00, 0x00, 0x04, 0x80, 0x80, 0x80, 0x21, 0x40, 0x15, - 0x00, 0x15, 0x00, 0x00, 0x03, 0xED, 0xAA, 0x00, 0x03, 0x6B, 0x00, 0x05, 0x80, 0x80, 0x80, - 0x0F, 0x2B, 0x01, 0x88, 0x02, 0xC4, 0x04, 0x90, 0x2C, 0x10, 0x8C, 0x80, 0x00, 0x00, 0xED, - 0x40, 0x06, 0x80, 0x80, 0x80, 0x01, 0x02, - ]; - - let aac_dc_descriptor = &aac_esds[31..46]; - - let mut stream = make_box(BoxSize::Auto, b"esds", |s| { - s.B32(0) // reserved - .append_bytes(aac_esds.as_slice()) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - - let es = super::read_esds(&mut stream).unwrap(); - - assert_eq!(es.audio_codec, super::CodecType::AAC); - assert_eq!(es.audio_object_type, Some(2)); - assert_eq!(es.extended_audio_object_type, Some(5)); - assert_eq!(es.audio_sample_rate, Some(24000)); - assert_eq!(es.audio_channel_count, Some(8)); - assert_eq!(es.codec_esds, aac_esds); - assert_eq!(es.decoder_specific_data, aac_dc_descriptor); -} - -#[test] -fn read_stsd_mp4v() { - let mp4v = vec![ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd0, 0x01, 0xe0, 0x00, 0x48, - 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, - 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x73, 0x64, 0x73, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x3e, 0x00, 0x00, 0x1f, 0x04, 0x36, 0x20, 0x11, 0x01, 0x77, 0x00, 0x00, 0x03, 0xe8, - 0x00, 0x00, 0x03, 0xe8, 0x00, 0x05, 0x27, 0x00, 0x00, 0x01, 0xb0, 0x05, 0x00, 0x00, 0x01, - 0xb5, 0x0e, 0xcf, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20, 0x00, 0x86, 0xe0, 0x00, - 0x2e, 0xa6, 0x60, 0x16, 0xf4, 0x01, 0xf4, 0x24, 0xc8, 0x01, 0xe5, 0x16, 0x84, 0x3c, 0x14, - 0x63, 0x06, 0x01, 0x02, - ]; - #[cfg(not(feature = "mp4v"))] - let esds_specific_data = &mp4v[90..]; - #[cfg(feature = "mp4v")] - let esds_specific_data = &mp4v[112..151]; - println!("esds_specific_data {:?}", esds_specific_data); - - let mut stream = make_box(BoxSize::Auto, b"mp4v", |s| s.append_bytes(mp4v.as_slice())); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - - let sample_entry = - super::read_video_sample_entry(&mut stream, &FourCC::from(*b"isom")).unwrap(); - - match sample_entry { - super::SampleEntry::Video(v) => { - assert_eq!(v.codec_type, super::CodecType::MP4V); - assert_eq!(v.width, 720); - assert_eq!(v.height, 480); - match v.codec_specific { - super::VideoCodecSpecific::ESDSConfig(esds_data) => { - assert_eq!(esds_data.as_slice(), esds_specific_data); - } - _ => panic!("it should be ESDSConfig!"), - } - } - _ => panic!("it should be a video sample entry!"), - } -} - -#[test] -fn read_esds_one_byte_extension_descriptor() { - let esds = vec![ - 0x00, 0x03, 0x80, 0x1b, 0x00, 0x00, 0x00, 0x04, 0x80, 0x12, 0x40, 0x15, 0x00, 0x06, 0x00, - 0x00, 0x01, 0xfe, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x05, 0x80, 0x02, 0x11, 0x90, 0x06, 0x01, - 0x02, - ]; - - let mut stream = make_box(BoxSize::Auto, b"esds", |s| { - s.B32(0) // reserved - .append_bytes(esds.as_slice()) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - - let es = super::read_esds(&mut stream).unwrap(); - - assert_eq!(es.audio_codec, super::CodecType::AAC); - assert_eq!(es.audio_object_type, Some(2)); - assert_eq!(es.extended_audio_object_type, None); - assert_eq!(es.audio_sample_rate, Some(48000)); - assert_eq!(es.audio_channel_count, Some(2)); -} - -#[test] -fn read_esds_byte_extension_descriptor() { - let mut stream = make_box(BoxSize::Auto, b"esds", |s| { - s.B32(0) // reserved - .B16(0x0003) - .B16(0x8181) // extension byte length 0x81 - .append_repeated(0, 0x81) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - - match super::read_esds(&mut stream) { - Ok(_) => (), - _ => panic!("fail to parse descriptor extension byte length"), - } -} - -#[test] -fn read_f4v_stsd() { - let mut stream = make_box(BoxSize::Auto, b".mp3", |s| { - s.append_repeated(0, 6) - .B16(1) - .B16(0) - .append_repeated(0, 6) - .B16(2) - .B16(16) - .append_repeated(0, 4) - .B32(48000 << 16) - }); - - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - let sample_entry = - super::read_audio_sample_entry(&mut stream).expect("failed to read f4v stsd atom"); - match sample_entry { - super::SampleEntry::Audio(sample_entry) => { - assert_eq!(sample_entry.codec_type, super::CodecType::MP3) - } - _ => panic!("fail to read audio sample enctry"), - } -} - -#[test] -fn unknown_video_sample_entry() { - let unknown_codec = make_box(BoxSize::Auto, b"yyyy", |s| s.append_repeated(0, 16)).into_inner(); - let mut stream = make_box(BoxSize::Auto, b"xxxx", |s| { - s.append_repeated(0, 6) - .B16(1) - .append_repeated(0, 16) - .B16(0) - .B16(0) - .append_repeated(0, 14) - .append_repeated(0, 32) - .append_repeated(0, 4) - .append_bytes(unknown_codec.as_slice()) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - match super::read_video_sample_entry(&mut stream, &FourCC::from(*b"isom")) { - Ok(super::SampleEntry::Unknown) => (), - _ => panic!("expected a different error result"), - } -} - -#[test] -fn unknown_audio_sample_entry() { - let unknown_codec = make_box(BoxSize::Auto, b"yyyy", |s| s.append_repeated(0, 16)).into_inner(); - let mut stream = make_box(BoxSize::Auto, b"xxxx", |s| { - s.append_repeated(0, 6) - .B16(1) - .B32(0) - .B32(0) - .B16(2) - .B16(16) - .B16(0) - .B16(0) - .B32(48000 << 16) - .append_bytes(unknown_codec.as_slice()) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - match super::read_audio_sample_entry(&mut stream) { - Ok(super::SampleEntry::Unknown) => (), - _ => panic!("expected a different error result"), - } -} - -#[test] -fn read_esds_invalid_descriptor() { - // tag 0x06, 0xff, 0x7f is incorrect. - let esds = vec![ - 0x03, 0x80, 0x80, 0x80, 0x22, 0x00, 0x00, 0x00, 0x04, 0x80, 0x80, 0x80, 0x14, 0x40, 0x01, - 0x00, 0x04, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x05, 0x80, 0x80, 0x80, - 0x02, 0xe8, 0x35, 0x06, 0xff, 0x7f, 0x00, 0x00, - ]; - - let mut stream = make_box(BoxSize::Auto, b"esds", |s| { - s.B32(0) // reserved - .append_bytes(esds.as_slice()) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - - match super::read_esds(&mut stream) { - Err(Error::InvalidData(s)) => assert_eq!(s, "Invalid descriptor."), - _ => panic!("unexpected result with invalid descriptor"), - } -} - -#[test] -fn read_esds_redundant_descriptor() { - // the '2' at the end is redundant data. - let esds = vec![ - 3, 25, 0, 1, 0, 4, 19, 64, 21, 0, 0, 0, 0, 0, 0, 0, 0, 1, 119, 0, 5, 2, 18, 16, 6, 1, 2, - ]; - - let mut stream = make_box(BoxSize::Auto, b"esds", |s| { - s.B32(0) // reserved - .append_bytes(esds.as_slice()) - }); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - - match super::read_esds(&mut stream) { - Ok(esds) => assert_eq!(esds.audio_codec, super::CodecType::AAC), - _ => panic!("unexpected result with invalid descriptor"), - } -} - -#[test] -fn read_stsd_lpcm() { - // Extract from sample converted by ffmpeg. - // "ffmpeg -i ./gizmo-short.mp4 -acodec pcm_s16le -ar 96000 -vcodec copy -f mov gizmo-short.mov" - let lpcm = vec![ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x03, 0x00, 0x10, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x48, 0x40, 0xf7, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x02, - 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x63, 0x68, 0x61, 0x6e, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x64, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - ]; - - let mut stream = make_box(BoxSize::Auto, b"lpcm", |s| s.append_bytes(lpcm.as_slice())); - let mut iter = super::BoxIter::new(&mut stream); - let mut stream = iter.next_box().unwrap().unwrap(); - - let sample_entry = super::read_audio_sample_entry(&mut stream).unwrap(); - - match sample_entry { - #[allow(clippy::float_cmp)] // The float comparison below is valid and intended. - super::SampleEntry::Audio(a) => { - assert_eq!(a.codec_type, super::CodecType::LPCM); - assert_eq!(a.samplerate, 96000.0); - assert_eq!(a.channelcount, 1); - match a.codec_specific { - super::AudioCodecSpecific::LPCM => (), - _ => panic!("it should be LPCM!"), - } - } - _ => panic!("it should be a audio sample entry!"), - } -} - -#[test] -fn read_to_end_() { - let mut src = b"1234567890".take(5); - let buf = src.read_into_try_vec().unwrap(); - assert_eq!(buf.len(), 5); - assert_eq!(buf, b"12345".as_ref()); -} - -#[test] -fn read_to_end_oom() { - let mut src = b"1234567890".take(std::usize::MAX.try_into().expect("usize < u64")); - assert!(src.read_into_try_vec().is_err()); -} diff --git a/lib/mp4/mp4parse/src/unstable.rs b/lib/mp4/mp4parse/src/unstable.rs deleted file mode 100644 index eeb16f8..0000000 --- a/lib/mp4/mp4parse/src/unstable.rs +++ /dev/null @@ -1,546 +0,0 @@ -// 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 num_traits::{CheckedAdd, CheckedSub, PrimInt, Zero}; -use std::ops::{Add, Neg, Sub}; - -use super::*; - -/// A zero-overhead wrapper around integer types for the sake of always -/// requiring checked arithmetic -#[repr(transparent)] -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct CheckedInteger<T>(pub T); - -impl<T> From<T> for CheckedInteger<T> { - fn from(i: T) -> Self { - Self(i) - } -} - -// Orphan rules prevent a more general implementation, but this suffices -impl From<CheckedInteger<i64>> for i64 { - fn from(checked: CheckedInteger<i64>) -> i64 { - checked.0 - } -} - -impl<T, U: Into<T>> Add<U> for CheckedInteger<T> -where - T: CheckedAdd, -{ - type Output = Option<Self>; - - fn add(self, other: U) -> Self::Output { - self.0.checked_add(&other.into()).map(Into::into) - } -} - -impl<T, U: Into<T>> Sub<U> for CheckedInteger<T> -where - T: CheckedSub, -{ - type Output = Option<Self>; - - fn sub(self, other: U) -> Self::Output { - self.0.checked_sub(&other.into()).map(Into::into) - } -} - -/// Implement subtraction of checked `u64`s returning i64 -// This is necessary for handling Mp4parseTrackInfo::media_time gracefully -impl Sub for CheckedInteger<u64> { - type Output = Option<CheckedInteger<i64>>; - - fn sub(self, other: Self) -> Self::Output { - if self >= other { - self.0 - .checked_sub(other.0) - .and_then(|u| i64::try_from(u).ok()) - .map(CheckedInteger) - } else { - other - .0 - .checked_sub(self.0) - .and_then(|u| i64::try_from(u).ok()) - .map(i64::neg) - .map(CheckedInteger) - } - } -} - -#[test] -fn u64_subtraction_returning_i64() { - // self > other - assert_eq!( - CheckedInteger(2u64) - CheckedInteger(1u64), - Some(CheckedInteger(1i64)) - ); - - // self == other - assert_eq!( - CheckedInteger(1u64) - CheckedInteger(1u64), - Some(CheckedInteger(0i64)) - ); - - // difference too large to store in i64 - assert_eq!(CheckedInteger(u64::MAX) - CheckedInteger(1u64), None); - - // self < other - assert_eq!( - CheckedInteger(1u64) - CheckedInteger(2u64), - Some(CheckedInteger(-1i64)) - ); - - // difference not representable due to overflow - assert_eq!(CheckedInteger(1u64) - CheckedInteger(u64::MAX), None); -} - -impl<T: std::cmp::PartialEq> PartialEq<T> for CheckedInteger<T> { - fn eq(&self, other: &T) -> bool { - self.0 == *other - } -} - -/// Provides the following information about a sample in the source file: -/// sample data offset (start and end), composition time in microseconds -/// (start and end) and whether it is a sync sample -#[repr(C)] -#[derive(Default, Debug, PartialEq)] -pub struct Indice { - /// The byte offset in the file where the indexed sample begins. - pub start_offset: CheckedInteger<u64>, - /// The byte offset in the file where the indexed sample ends. This is - /// equivalent to `start_offset` + the length in bytes of the indexed - /// sample. Typically this will be the `start_offset` of the next sample - /// in the file. - pub end_offset: CheckedInteger<u64>, - /// The time in microseconds when the indexed sample should be displayed. - /// Analogous to the concept of presentation time stamp (pts). - pub start_composition: CheckedInteger<i64>, - /// The time in microseconds when the indexed sample should stop being - /// displayed. Typically this would be the `start_composition` time of the - /// next sample if samples were ordered by composition time. - pub end_composition: CheckedInteger<i64>, - /// The time in microseconds that the indexed sample should be decoded at. - /// Analogous to the concept of decode time stamp (dts). - pub start_decode: CheckedInteger<i64>, - /// Set if the indexed sample is a sync sample. The meaning of sync is - /// somewhat codec specific, but essentially amounts to if the sample is a - /// key frame. - pub sync: bool, -} - -/// Create a vector of `Indice`s with the information about track samples. -/// It uses `stsc`, `stco`, `stsz` and `stts` boxes to construct a list of -/// every sample in the file and provides offsets which can be used to read -/// raw sample data from the file. -#[allow(clippy::reversed_empty_ranges)] -pub fn create_sample_table( - track: &Track, - track_offset_time: CheckedInteger<i64>, -) -> Option<TryVec<Indice>> { - let timescale = match track.timescale { - Some(ref t) => TrackTimeScale::<i64>(t.0 as i64, t.1), - _ => return None, - }; - - let (stsc, stco, stsz, stts) = match (&track.stsc, &track.stco, &track.stsz, &track.stts) { - (&Some(ref a), &Some(ref b), &Some(ref c), &Some(ref d)) => (a, b, c, d), - _ => return None, - }; - - // According to spec, no sync table means every sample is sync sample. - let has_sync_table = matches!(track.stss, Some(_)); - - let mut sample_size_iter = stsz.sample_sizes.iter(); - - // Get 'stsc' iterator for (chunk_id, chunk_sample_count) and calculate the sample - // offset address. - - // With large numbers of samples, the cost of many allocations dominates, - // so it's worth iterating twice to allocate sample_table just once. - let total_sample_count = sample_to_chunk_iter(&stsc.samples, &stco.offsets) - .map(|(_, sample_counts)| sample_counts.to_usize()) - .try_fold(0usize, usize::checked_add)?; - let mut sample_table = TryVec::with_capacity(total_sample_count).ok()?; - - for i in sample_to_chunk_iter(&stsc.samples, &stco.offsets) { - let chunk_id = i.0 as usize; - let sample_counts = i.1; - let mut cur_position = match stco.offsets.get(chunk_id) { - Some(&i) => i.into(), - _ => return None, - }; - for _ in 0..sample_counts { - let start_offset = cur_position; - let end_offset = match (stsz.sample_size, sample_size_iter.next()) { - (_, Some(t)) => (start_offset + *t)?, - (t, _) if t > 0 => (start_offset + t)?, - _ => 0.into(), - }; - if end_offset == 0 { - return None; - } - cur_position = end_offset; - - sample_table - .push(Indice { - start_offset, - end_offset, - sync: !has_sync_table, - ..Default::default() - }) - .ok()?; - } - } - - // Mark the sync sample in sample_table according to 'stss'. - if let Some(ref v) = track.stss { - for iter in &v.samples { - match iter - .checked_sub(&1) - .and_then(|idx| sample_table.get_mut(idx as usize)) - { - Some(elem) => elem.sync = true, - _ => return None, - } - } - } - - let ctts_iter = track.ctts.as_ref().map(|v| v.samples.as_slice().iter()); - - let mut ctts_offset_iter = TimeOffsetIterator { - cur_sample_range: (0..0), - cur_offset: 0, - ctts_iter, - track_id: track.id, - }; - - let mut stts_iter = TimeToSampleIterator { - cur_sample_count: (0..0), - cur_sample_delta: 0, - stts_iter: stts.samples.as_slice().iter(), - track_id: track.id, - }; - - // sum_delta is the sum of stts_iter delta. - // According to spec: - // decode time => DT(n) = DT(n-1) + STTS(n) - // composition time => CT(n) = DT(n) + CTTS(n) - // Note: - // composition time needs to add the track offset time from 'elst' table. - let mut sum_delta = TrackScaledTime::<i64>(0, track.id); - for sample in sample_table.as_mut_slice() { - let decode_time = sum_delta; - sum_delta = (sum_delta + stts_iter.next_delta())?; - - // ctts_offset is the current sample offset time. - let ctts_offset = ctts_offset_iter.next_offset_time(); - - let start_composition = track_time_to_us((decode_time + ctts_offset)?, timescale)?.0; - - let end_composition = track_time_to_us((sum_delta + ctts_offset)?, timescale)?.0; - - let start_decode = track_time_to_us(decode_time, timescale)?.0; - - sample.start_composition = (track_offset_time + start_composition)?; - sample.end_composition = (track_offset_time + end_composition)?; - sample.start_decode = start_decode.into(); - } - - // Correct composition end time due to 'ctts' causes composition time re-ordering. - // - // Composition end time is not in specification. However, gecko needs it, so we need to - // calculate to correct the composition end time. - if !sample_table.is_empty() { - // Create an index table refers to sample_table and sorted by start_composisiton time. - let mut sort_table = TryVec::with_capacity(sample_table.len()).ok()?; - - for i in 0..sample_table.len() { - sort_table.push(i).ok()?; - } - - sort_table.sort_by_key(|i| match sample_table.get(*i) { - Some(v) => v.start_composition, - _ => 0.into(), - }); - - for indices in sort_table.windows(2) { - if let [current_index, peek_index] = *indices { - let next_start_composition_time = sample_table[peek_index].start_composition; - let sample = &mut sample_table[current_index]; - sample.end_composition = next_start_composition_time; - } - } - } - - Some(sample_table) -} - -// Convert a 'ctts' compact table to full table by iterator, -// (sample_with_the_same_offset_count, offset) => (offset), (offset), (offset) ... -// -// For example: -// (2, 10), (4, 9) into (10, 10, 9, 9, 9, 9) by calling next_offset_time(). -struct TimeOffsetIterator<'a> { - cur_sample_range: std::ops::Range<u32>, - cur_offset: i64, - ctts_iter: Option<std::slice::Iter<'a, TimeOffset>>, - track_id: usize, -} - -impl<'a> Iterator for TimeOffsetIterator<'a> { - type Item = i64; - - #[allow(clippy::reversed_empty_ranges)] - fn next(&mut self) -> Option<i64> { - let has_sample = self.cur_sample_range.next().or_else(|| { - // At end of current TimeOffset, find the next TimeOffset. - let iter = match self.ctts_iter { - Some(ref mut v) => v, - _ => return None, - }; - let offset_version; - self.cur_sample_range = match iter.next() { - Some(v) => { - offset_version = v.time_offset; - 0..v.sample_count - } - _ => { - offset_version = TimeOffsetVersion::Version0(0); - 0..0 - } - }; - - self.cur_offset = match offset_version { - TimeOffsetVersion::Version0(i) => i64::from(i), - TimeOffsetVersion::Version1(i) => i64::from(i), - }; - - self.cur_sample_range.next() - }); - - has_sample.and(Some(self.cur_offset)) - } -} - -impl<'a> TimeOffsetIterator<'a> { - fn next_offset_time(&mut self) -> TrackScaledTime<i64> { - match self.next() { - Some(v) => TrackScaledTime::<i64>(v as i64, self.track_id), - _ => TrackScaledTime::<i64>(0, self.track_id), - } - } -} - -// Convert 'stts' compact table to full table by iterator, -// (sample_count_with_the_same_time, time) => (time, time, time) ... repeats -// sample_count_with_the_same_time. -// -// For example: -// (2, 3000), (1, 2999) to (3000, 3000, 2999). -struct TimeToSampleIterator<'a> { - cur_sample_count: std::ops::Range<u32>, - cur_sample_delta: u32, - stts_iter: std::slice::Iter<'a, Sample>, - track_id: usize, -} - -impl<'a> Iterator for TimeToSampleIterator<'a> { - type Item = u32; - - #[allow(clippy::reversed_empty_ranges)] - fn next(&mut self) -> Option<u32> { - let has_sample = self.cur_sample_count.next().or_else(|| { - self.cur_sample_count = match self.stts_iter.next() { - Some(v) => { - self.cur_sample_delta = v.sample_delta; - 0..v.sample_count - } - _ => 0..0, - }; - - self.cur_sample_count.next() - }); - - has_sample.and(Some(self.cur_sample_delta)) - } -} - -impl<'a> TimeToSampleIterator<'a> { - fn next_delta(&mut self) -> TrackScaledTime<i64> { - match self.next() { - Some(v) => TrackScaledTime::<i64>(i64::from(v), self.track_id), - _ => TrackScaledTime::<i64>(0, self.track_id), - } - } -} - -// Convert 'stco' compact table to full table by iterator. -// (start_chunk_num, sample_number) => (start_chunk_num, sample_number), -// (start_chunk_num + 1, sample_number), -// (start_chunk_num + 2, sample_number), -// ... -// (next start_chunk_num, next sample_number), -// ... -// -// For example: -// (1, 5), (5, 10), (9, 2) => (1, 5), (2, 5), (3, 5), (4, 5), (5, 10), (6, 10), -// (7, 10), (8, 10), (9, 2) -fn sample_to_chunk_iter<'a>( - stsc_samples: &'a TryVec<SampleToChunk>, - stco_offsets: &'a TryVec<u64>, -) -> SampleToChunkIterator<'a> { - SampleToChunkIterator { - chunks: (0..0), - sample_count: 0, - stsc_peek_iter: stsc_samples.as_slice().iter().peekable(), - remain_chunk_count: stco_offsets - .len() - .try_into() - .expect("stco.entry_count is u32"), - } -} - -struct SampleToChunkIterator<'a> { - chunks: std::ops::Range<u32>, - sample_count: u32, - stsc_peek_iter: std::iter::Peekable<std::slice::Iter<'a, SampleToChunk>>, - remain_chunk_count: u32, // total chunk number from 'stco'. -} - -impl<'a> Iterator for SampleToChunkIterator<'a> { - type Item = (u32, u32); - - fn next(&mut self) -> Option<(u32, u32)> { - let has_chunk = self.chunks.next().or_else(|| { - self.chunks = self.locate(); - self.remain_chunk_count - .checked_sub( - self.chunks - .len() - .try_into() - .expect("len() of a Range<u32> must fit in u32"), - ) - .and_then(|res| { - self.remain_chunk_count = res; - self.chunks.next() - }) - }); - - has_chunk.map(|id| (id, self.sample_count)) - } -} - -impl<'a> SampleToChunkIterator<'a> { - #[allow(clippy::reversed_empty_ranges)] - fn locate(&mut self) -> std::ops::Range<u32> { - loop { - return match (self.stsc_peek_iter.next(), self.stsc_peek_iter.peek()) { - (Some(next), Some(peek)) if next.first_chunk == peek.first_chunk => { - // Invalid entry, skip it and will continue searching at - // next loop iteration. - continue; - } - (Some(next), Some(peek)) if next.first_chunk > 0 && peek.first_chunk > 0 => { - self.sample_count = next.samples_per_chunk; - (next.first_chunk - 1)..(peek.first_chunk - 1) - } - (Some(next), None) if next.first_chunk > 0 => { - self.sample_count = next.samples_per_chunk; - // Total chunk number in 'stsc' could be different to 'stco', - // there could be more chunks at the last 'stsc' record. - match next.first_chunk.checked_add(self.remain_chunk_count) { - Some(r) => (next.first_chunk - 1)..r - 1, - _ => 0..0, - } - } - _ => 0..0, - }; - } - } -} - -/// Calculate numerator * scale / denominator, if possible. -/// -/// Applying the associativity of integer arithmetic, we divide first -/// and add the remainder after multiplying each term separately -/// to preserve precision while leaving more headroom. That is, -/// (n * s) / d is split into floor(n / d) * s + (n % d) * s / d. -/// -/// Return None on overflow or if the denominator is zero. -fn rational_scale<T, S>(numerator: T, denominator: T, scale2: S) -> Option<T> -where - T: PrimInt + Zero, - S: PrimInt, -{ - if denominator.is_zero() { - return None; - } - - let integer = numerator / denominator; - let remainder = numerator % denominator; - num_traits::cast(scale2).and_then(|s| match integer.checked_mul(&s) { - Some(integer) => remainder - .checked_mul(&s) - .and_then(|remainder| (remainder / denominator).checked_add(&integer)), - None => None, - }) -} - -#[derive(Debug, PartialEq)] -pub struct Microseconds<T>(pub T); - -/// Convert `time` in media's global (mvhd) timescale to microseconds, -/// using provided `MediaTimeScale` -pub fn media_time_to_us(time: MediaScaledTime, scale: MediaTimeScale) -> Option<Microseconds<u64>> { - let microseconds_per_second = 1_000_000; - rational_scale(time.0, scale.0, microseconds_per_second).map(Microseconds) -} - -/// Convert `time` in track's local (mdhd) timescale to microseconds, -/// using provided `TrackTimeScale<T>` -pub fn track_time_to_us<T>( - time: TrackScaledTime<T>, - scale: TrackTimeScale<T>, -) -> Option<Microseconds<T>> -where - T: PrimInt + Zero, -{ - assert_eq!(time.1, scale.1); - let microseconds_per_second = 1_000_000; - rational_scale(time.0, scale.0, microseconds_per_second).map(Microseconds) -} - -#[test] -fn rational_scale_overflow() { - assert_eq!(rational_scale::<u64, u64>(17, 3, 1000), Some(5666)); - let large = 0x4000_0000_0000_0000; - assert_eq!(rational_scale::<u64, u64>(large, 2, 2), Some(large)); - assert_eq!(rational_scale::<u64, u64>(large, 4, 4), Some(large)); - assert_eq!(rational_scale::<u64, u64>(large, 2, 8), None); - assert_eq!(rational_scale::<u64, u64>(large, 8, 4), Some(large / 2)); - assert_eq!(rational_scale::<u64, u64>(large + 1, 4, 4), Some(large + 1)); - assert_eq!(rational_scale::<u64, u64>(large, 40, 1000), None); -} - -#[test] -fn media_time_overflow() { - let scale = MediaTimeScale(90000); - let duration = MediaScaledTime(9_007_199_254_710_000); - assert_eq!( - media_time_to_us(duration, scale), - Some(Microseconds(100_079_991_719_000_000u64)) - ); -} - -#[test] -fn track_time_overflow() { - let scale = TrackTimeScale(44100u64, 0); - let duration = TrackScaledTime(4_413_527_634_807_900u64, 0); - assert_eq!( - track_time_to_us(duration, scale), - Some(Microseconds(100_079_991_719_000_000u64)) - ); -} diff --git a/lib/mp4/mp4parse/tests/bipbop-cenc-audioinit.mp4 b/lib/mp4/mp4parse/tests/bipbop-cenc-audioinit.mp4 Binary files differdeleted file mode 100644 index b827aa4..0000000 --- a/lib/mp4/mp4parse/tests/bipbop-cenc-audioinit.mp4 +++ /dev/null diff --git a/lib/mp4/mp4parse/tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4 b/lib/mp4/mp4parse/tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4 Binary files differdeleted file mode 100644 index 5db21d0..0000000 --- a/lib/mp4/mp4parse/tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4 +++ /dev/null diff --git a/lib/mp4/mp4parse/tests/minimal.mp4 b/lib/mp4/mp4parse/tests/minimal.mp4 Binary files differdeleted file mode 100644 index 9fe1e67..0000000 --- a/lib/mp4/mp4parse/tests/minimal.mp4 +++ /dev/null diff --git a/lib/mp4/mp4parse/tests/overflow.rs b/lib/mp4/mp4parse/tests/overflow.rs deleted file mode 100644 index ce5dc52..0000000 --- a/lib/mp4/mp4parse/tests/overflow.rs +++ /dev/null @@ -1,15 +0,0 @@ -/// Verify we're built with run-time integer overflow detection. - -// 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/. - -#[test] -#[should_panic(expected = "attempt to add with overflow")] -fn overflow_protection() { - let edge = u32::max_value(); - assert_eq!(0u32, edge + 1); - - let edge = u64::max_value(); - assert_eq!(0u64, edge + 1); -} diff --git a/lib/mp4/mp4parse/tests/public.rs b/lib/mp4/mp4parse/tests/public.rs deleted file mode 100644 index 30e2bff..0000000 --- a/lib/mp4/mp4parse/tests/public.rs +++ /dev/null @@ -1,1215 +0,0 @@ -/// 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/. -extern crate mp4parse as mp4; - -use mp4::Error; -use mp4::ParseStrictness; -use std::convert::TryInto; -use std::fs::File; -use std::io::{Cursor, Read, Seek, SeekFrom}; - -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_NO_MIF1: &str = "tests/no-mif1.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_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_IMIR_MISSING_ESSENTIAL: &str = "tests/imir-missing-essential.avif"; -static IMAGE_AVIF_IROT_MISSING_ESSENTIAL: &str = "tests/irot-missing-essential.avif"; -static AVIF_TEST_DIRS: &[&str] = &["tests", "av1-avif/testFiles"]; -static AVIF_A1OP: &str = - "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1op.avif"; -static AVIF_A1LX: &str = - "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_grid_a1lx.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_LSEL: &str = - "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_lsel.avif"; -static AVIF_NO_PIXI_IMAGES: &[&str] = &[IMAGE_AVIF_NO_PIXI, IMAGE_AVIF_NO_ALPHA_PIXI]; -static AVIF_UNSUPPORTED_IMAGES: &[&str] = &[ - AVIF_A1OP, - AVIF_A1LX, - AVIF_CLAP, - AVIF_GRID, - AVIF_LSEL, - "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1lx.avif", - "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1op_lsel.avif", - "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_grid_lsel.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", -]; -/// See https://github.com/AOMediaCodec/av1-avif/issues/150 -static AV1_AVIF_CORRUPT_IMAGES: &[&str] = &[ - "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/Link-U/kimono.crop.avif", - "av1-avif/testFiles/Link-U/kimono.mirror-vertical.rotate270.crop.avif", - "av1-avif/testFiles/Netflix/avis/Chimera-AV1-10bit-480x270.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); - } - mp4::TrackType::Metadata | mp4::TrackType::Unknown => {} - } - } -} - -#[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_invalid_data(mp4::read_mp4(input), "insufficient data in 'ctts' box"); -} - -#[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(), - [ - 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().len(), 52); -} - -#[test] -fn public_avif_alpha_item() { - let input = &mut File::open(IMAGE_AVIF_ALPHA).expect("Unknown file"); - assert_avif_valid(input); -} - -#[test] -fn public_avif_alpha_non_premultiplied() { - let input = &mut File::open(IMAGE_AVIF_ALPHA).expect("Unknown file"); - let context = mp4::read_avif(input, ParseStrictness::Normal).expect("read_avif failed"); - assert!(!context.alpha_item_coded_data().is_empty()); - assert!(!context.premultiplied_alpha); -} - -#[test] -fn public_avif_alpha_premultiplied() { - let input = &mut File::open(IMAGE_AVIF_ALPHA_PREMULTIPLIED).expect("Unknown file"); - let context = mp4::read_avif(input, ParseStrictness::Normal).expect("read_avif failed"); - assert!(!context.alpha_item_coded_data().is_empty()); - assert!(context.premultiplied_alpha); - assert_avif_valid(input); -} - -#[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 assert_invalid_data<T: std::fmt::Debug>(result: mp4::Result<T>, expected_msg: &str) { - match result { - Err(Error::InvalidData(msg)) if msg == expected_msg => {} - Err(Error::InvalidData(msg)) if msg != expected_msg => { - panic!( - "Error message mismatch\nExpected: {}\nFound: {}", - expected_msg, msg - ); - } - r => panic!( - "Expected Err(Error::InvalidData({:?}), found {:?}", - expected_msg, r - ), - } -} - -/// Check that input generates no errors in any parsing mode -fn assert_avif_valid(input: &mut File) { - for strictness in &[ - ParseStrictness::Permissive, - ParseStrictness::Normal, - ParseStrictness::Strict, - ] { - input.seek(SeekFrom::Start(0)).expect("rewind failed"); - assert!( - mp4::read_avif(input, *strictness).is_ok(), - "read_avif with {:?} failed", - strictness - ); - println!("{:?} succeeded", strictness); - } -} - -/// Check that input generates the expected error only in strict parsing mode -fn assert_avif_should(path: &str, expected_msg: &str) { - let input = &mut File::open(path).expect("Unknown file"); - assert_invalid_data(mp4::read_avif(input, ParseStrictness::Strict), expected_msg); - input.seek(SeekFrom::Start(0)).expect("rewind failed"); - mp4::read_avif(input, ParseStrictness::Normal).expect("ParseStrictness::Normal failed"); - input.seek(SeekFrom::Start(0)).expect("rewind failed"); - mp4::read_avif(input, ParseStrictness::Permissive).expect("ParseStrictness::Permissive failed"); -} - -/// Check that input generates the expected error unless in permissive parsing mode -fn assert_avif_shall(path: &str, expected_msg: &str) { - let input = &mut File::open(path).expect("Unknown file"); - assert_invalid_data(mp4::read_avif(input, ParseStrictness::Strict), expected_msg); - input.seek(SeekFrom::Start(0)).expect("rewind failed"); - assert_invalid_data(mp4::read_avif(input, ParseStrictness::Normal), expected_msg); - input.seek(SeekFrom::Start(0)).expect("rewind failed"); - mp4::read_avif(input, ParseStrictness::Permissive).expect("ParseStrictness::Permissive failed"); -} - -#[test] -fn public_avif_ipma_missing_essential() { - let expected_msg = "All transformative properties associated with \ - coded and derived images required or conditionally \ - required by this document shall be marked as essential \ - per MIAF (ISO 23000-22:2019) § 7.3.9"; - assert_avif_should(IMAGE_AVIF_AV1C_MISSING_ESSENTIAL, expected_msg); - assert_avif_should(IMAGE_AVIF_IMIR_MISSING_ESSENTIAL, expected_msg); - assert_avif_should(IMAGE_AVIF_IROT_MISSING_ESSENTIAL, expected_msg); -} - -#[test] -fn public_avif_ipma_bad_version() { - let expected_msg = "The ipma version 0 should be used unless 32-bit \ - item_ID values are needed \ - per ISOBMFF (ISO 14496-12:2020 § 8.11.14.1"; - assert_avif_should(IMAGE_AVIF_IPMA_BAD_VERSION, expected_msg); -} - -#[test] -fn public_avif_ipma_bad_flags() { - let expected_msg = "Unless there are more than 127 properties in the \ - ItemPropertyContainerBox, flags should be equal to 0 \ - per ISOBMFF (ISO 14496-12:2020 § 8.11.14.1"; - assert_avif_should(IMAGE_AVIF_IPMA_BAD_FLAGS, expected_msg); -} - -#[test] -fn public_avif_ipma_duplicate_version_and_flags() { - let expected_msg = "There shall be at most one ItemPropertyAssociationbox \ - with a given pair of values of version and flags \ - per ISOBMFF (ISO 14496-12:2020 § 8.11.14.1"; - assert_avif_shall(IMAGE_AVIF_IPMA_DUPLICATE_VERSION_AND_FLAGS, expected_msg); -} - -#[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 expected_msg = "There shall be at most one occurrence of a given item_ID, \ - in the set of ItemPropertyAssociationBox boxes \ - per ISOBMFF (ISO 14496-12:2020) § 8.11.14.1"; - let input = &mut File::open(IMAGE_AVIF_IPMA_DUPLICATE_ITEM_ID).expect("Unknown file"); - assert_invalid_data( - mp4::read_avif(input, ParseStrictness::Permissive), - expected_msg, - ) -} - -#[test] -fn public_avif_ipma_invalid_property_index() { - let expected_msg = "Invalid property index in ipma"; - assert_avif_shall(IMAGE_AVIF_IPMA_INVALID_PROPERTY_INDEX, expected_msg); -} - -#[test] -fn public_avif_hdlr_first_in_meta() { - let expected_msg = "The HandlerBox shall be the first contained box within \ - the MetaBox \ - per MIAF (ISO 23000-22:2019) § 7.2.1.5"; - assert_avif_shall(IMAGE_AVIF_NO_HDLR, expected_msg); - assert_avif_shall(IMAGE_AVIF_HDLR_NOT_FIRST, expected_msg); -} - -#[test] -fn public_avif_hdlr_is_pict() { - let expected_msg = "The HandlerBox handler_type must be 'pict' \ - per MIAF (ISO 23000-22:2019) § 7.2.1.5"; - assert_avif_shall(IMAGE_AVIF_HDLR_NOT_PICT, expected_msg); -} - -#[test] -fn public_avif_hdlr_nonzero_reserved() { - let expected_msg = "The HandlerBox 'reserved' fields shall be 0 \ - per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2"; - // This is a "should" despite the spec indicating a (somewhat ambiguous) - // requirement that this field is set to zero. - assert_avif_should(IMAGE_AVIF_HDLR_NONZERO_RESERVED, expected_msg); -} - -#[test] -fn public_avif_no_mif1() { - let expected_msg = "The FileTypeBox should contain 'mif1' in the compatible_brands list \ - per MIAF (ISO 23000-22:2019) § 7.2.1.2"; - assert_avif_should(IMAGE_AVIF_NO_MIF1, expected_msg); -} - -#[test] -fn public_avif_pixi_present_for_displayable_images() { - let expected_msg = "The pixel information property shall be associated with every image \ - that is displayable (not hidden) \ - per MIAF (ISO/IEC 23000-22:2019) specification § 7.3.6.6"; - let pixi_test = if cfg!(feature = "missing-pixi-permitted") { - assert_avif_should - } else { - assert_avif_shall - }; - - pixi_test(IMAGE_AVIF_NO_PIXI, expected_msg); - pixi_test(IMAGE_AVIF_NO_ALPHA_PIXI, expected_msg); -} - -#[test] -fn public_avif_av1c_present_for_av01() { - let expected_msg = "One AV1 Item Configuration Property (av1C) \ - is mandatory for an image item of type 'av01' \ - per AVIF specification § 2.2.1"; - assert_avif_shall(IMAGE_AVIF_NO_AV1C, expected_msg); - assert_avif_shall(IMAGE_AVIF_NO_ALPHA_AV1C, expected_msg); -} - -#[test] -fn public_avif_ispe_present() { - let expected_msg = "Missing 'ispe' property for primary item, required \ - per HEIF (ISO/IEC 23008-12:2017) § 6.5.3.1"; - assert_avif_shall(IMAGE_AVIF_NO_ISPE, expected_msg); -} - -fn assert_unsupported<T: std::fmt::Debug>(result: mp4::Result<T>, expected_status: mp4::Status) { - match result { - Err(Error::UnsupportedDetail(status, _msg)) if status == expected_status => {} - Err(Error::UnsupportedDetail(status, _msg)) if status != expected_status => { - panic!( - "Error message mismatch\nExpected: {:?}\nFound: {:?}", - expected_status, status - ); - } - r => panic!( - "Expected Err({:?}), found {:?}", - Error::from(expected_status), - r - ), - } -} - -#[test] -fn public_avif_a1lx() { - let input = &mut File::open(AVIF_A1LX).expect("Unknown file"); - assert_unsupported( - mp4::read_avif(input, ParseStrictness::Normal), - mp4::Status::UnsupportedA1lx, - ); -} - -#[test] -fn public_avif_a1op() { - let input = &mut File::open(AVIF_A1OP).expect("Unknown file"); - assert_unsupported( - mp4::read_avif(input, ParseStrictness::Normal), - mp4::Status::UnsupportedA1op, - ); -} - -#[test] -fn public_avif_clap() { - let input = &mut File::open(AVIF_CLAP).expect("Unknown file"); - assert_unsupported( - mp4::read_avif(input, ParseStrictness::Normal), - mp4::Status::UnsupportedClap, - ); -} - -#[test] -fn public_avif_grid() { - let input = &mut File::open(AVIF_GRID).expect("Unknown file"); - assert_unsupported( - mp4::read_avif(input, ParseStrictness::Normal), - mp4::Status::UnsupportedGrid, - ); -} - -#[test] -fn public_avif_lsel() { - let input = &mut File::open(AVIF_LSEL).expect("Unknown file"); - assert_unsupported( - mp4::read_avif(input, ParseStrictness::Normal), - mp4::Status::UnsupportedLsel, - ); -} - -#[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(); - if !path.is_file() || path.extension().unwrap_or_default() != "avif" { - 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(_) if unsupported || corrupt => panic!("Expected error parsing {:?}", path), - Ok(_) => eprintln!("Successfully parsed {:?}", path), - Err(e @ Error::Unsupported(_)) | Err(e @ Error::UnsupportedDetail(..)) - if unsupported => - { - eprintln!( - "Expected error parsing unsupported input {:?}: {:?}", - path, e - ) - } - Err(e) if corrupt => { - eprintln!("Expected error parsing corrupt input {:?}: {:?}", path, e) - } - Err(e) => panic!("Unexected 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",); - } - }; - } -} diff --git a/lib/mp4/mp4parse_capi/Cargo.toml b/lib/mp4/mp4parse_capi/Cargo.toml deleted file mode 100644 index 7624cfb..0000000 --- a/lib/mp4/mp4parse_capi/Cargo.toml +++ /dev/null @@ -1,42 +0,0 @@ -[package] -name = "mp4parse_capi" -version = "0.12.0" -authors = [ - "Ralph Giles <giles@mozilla.com>", - "Matthew Gregan <kinetik@flim.org>", - "Alfredo Yang <ayang@mozilla.com>", - "Jon Bauman <jbauman@mozilla.com>", - "Bryce Seager van Dyk <bvandyk@mozilla.com>", -] - -description = "Parser for ISO base media file format (mp4)" -documentation = "https://docs.rs/mp4parse_capi/" -license = "MPL-2.0" - -repository = "https://github.com/mozilla/mp4parse-rust" - -# Avoid complaints about trying to package test files. -exclude = [ - "*.mp4", -] - -[badges] -travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" } - -[dependencies] -byteorder = "1.2.1" -fallible_collections = { version = "0.4", features = ["std_io"] } -log = "0.4" -mp4parse = { version = "0.12.0", path = "../mp4parse", features = ["unstable-api"] } -num-traits = "0.2.14" - -[dev-dependencies] -env_logger = "0.8" - -[features] -default = ["craw"] -craw = ["mp4parse/craw"] -missing-pixi-permitted = ["mp4parse/missing-pixi-permitted"] -3gpp = ["mp4parse/3gpp"] -meta-xml = ["mp4parse/meta-xml"] -mp4v = ["mp4parse/mp4v"] diff --git a/lib/mp4/mp4parse_capi/src/lib.rs b/lib/mp4/mp4parse_capi/src/lib.rs deleted file mode 100644 index 0fc4310..0000000 --- a/lib/mp4/mp4parse_capi/src/lib.rs +++ /dev/null @@ -1,1777 +0,0 @@ -//! C API for mp4parse module. -//! -//! Parses ISO Base Media Format aka video/mp4 streams. -//! -//! # Examples -//! -//! ```rust -//! extern crate mp4parse_capi; -//! use std::io::Read; -//! -//! extern fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { -//! let mut input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; -//! let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; -//! match input.read(&mut buf) { -//! Ok(n) => n as isize, -//! Err(_) => -1, -//! } -//! } -//! let capi_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); -//! let mut file = std::fs::File::open(capi_dir + "/../mp4parse/tests/minimal.mp4").unwrap(); -//! let io = mp4parse_capi::Mp4parseIo { -//! read: Some(buf_read), -//! userdata: &mut file as *mut _ as *mut std::os::raw::c_void -//! }; -//! let mut parser = std::ptr::null_mut(); -//! unsafe { -//! let rv = mp4parse_capi::mp4parse_new(&io, &mut parser); -//! assert_eq!(rv, mp4parse_capi::Mp4parseStatus::Ok); -//! assert!(!parser.is_null()); -//! mp4parse_capi::mp4parse_free(parser); -//! } -//! ``` - -// 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/. - -extern crate byteorder; -extern crate log; -extern crate mp4parse; -extern crate num_traits; - -use byteorder::WriteBytesExt; -use std::convert::TryFrom; -use std::convert::TryInto; - -use std::io::Read; - -// Symbols we need from our rust api. -use mp4parse::serialize_opus_header; -use mp4parse::unstable::{ - create_sample_table, media_time_to_us, track_time_to_us, CheckedInteger, Indice, Microseconds, -}; -use mp4parse::AudioCodecSpecific; -use mp4parse::AvifContext; -use mp4parse::CodecType; -use mp4parse::MediaContext; -// Re-exported so consumers don't have to depend on mp4parse as well -pub use mp4parse::ParseStrictness; -use mp4parse::SampleEntry; -pub use mp4parse::Status as Mp4parseStatus; -use mp4parse::TrackType; -use mp4parse::TryBox; -use mp4parse::TryHashMap; -use mp4parse::TryVec; -use mp4parse::VideoCodecSpecific; - -// To ensure we don't use stdlib allocating types by accident -#[allow(dead_code)] -struct Vec; -#[allow(dead_code)] -struct Box; -#[allow(dead_code)] -struct HashMap; -#[allow(dead_code)] -struct String; - -#[repr(C)] -#[derive(PartialEq, Debug)] -pub enum Mp4parseTrackType { - Video = 0, - Audio = 1, - Metadata = 2, -} - -impl Default for Mp4parseTrackType { - fn default() -> Self { - Mp4parseTrackType::Video - } -} - -#[allow(non_camel_case_types, clippy::upper_case_acronyms)] -#[repr(C)] -#[derive(PartialEq, Debug)] -pub enum Mp4parseCodec { - Unknown, - Aac, - Flac, - Opus, - Avc, - Vp9, - Av1, - Mp3, - Mp4v, - Jpeg, // for QT JPEG atom in video track - Ac3, - Ec3, - Alac, - H263, - #[cfg(feature = "3gpp")] - AMRNB, - #[cfg(feature = "3gpp")] - AMRWB, - #[cfg(feature = "craw")] - Craw, // Canon RAW -} - -impl Default for Mp4parseCodec { - fn default() -> Self { - Mp4parseCodec::Unknown - } -} - -#[repr(C)] -#[derive(PartialEq, Debug)] -pub enum Mp4ParseEncryptionSchemeType { - None, - Cenc, - Cbc1, - Cens, - Cbcs, - // Schemes also have a version component. At the time of writing, this does - // not impact handling, so we do not expose it. Note that this may need to - // be exposed in future, should the spec change. -} - -impl Default for Mp4ParseEncryptionSchemeType { - fn default() -> Self { - Mp4ParseEncryptionSchemeType::None - } -} - -#[repr(C)] -#[derive(Default, Debug)] -pub struct Mp4parseTrackInfo { - pub track_type: Mp4parseTrackType, - pub track_id: u32, - pub duration: u64, - pub media_time: CheckedInteger<i64>, // wants to be u64? understand how elst adjustment works - // TODO(kinetik): include crypto guff - // If this changes to u64, we can get rid of the strange - // impl Sub for CheckedInteger<u64> -} - -#[repr(C)] -#[derive(Debug)] -pub struct Mp4parseByteData { - pub length: usize, - // cheddar can't handle generic type, so it needs to be multiple data types here. - pub data: *const u8, - pub indices: *const Indice, -} - -impl Mp4parseByteData { - fn with_data(slice: &[u8]) -> Self { - Self { - length: slice.len(), - data: if !slice.is_empty() { - slice.as_ptr() - } else { - std::ptr::null() - }, - indices: std::ptr::null(), - } - } -} - -impl Default for Mp4parseByteData { - fn default() -> Self { - Self { - length: 0, - data: std::ptr::null(), - indices: std::ptr::null(), - } - } -} - -impl Mp4parseByteData { - fn set_data(&mut self, data: &[u8]) { - self.length = data.len(); - self.data = data.as_ptr(); - } - - fn set_indices(&mut self, data: &[Indice]) { - self.length = data.len(); - self.indices = data.as_ptr(); - } -} - -#[repr(C)] -#[derive(Default)] -pub struct Mp4parsePsshInfo { - pub data: Mp4parseByteData, -} - -#[repr(u8)] -#[derive(Debug, PartialEq)] -pub enum OptionalFourCc { - None, - Some([u8; 4]), -} - -impl Default for OptionalFourCc { - fn default() -> Self { - Self::None - } -} - -#[repr(C)] -#[derive(Default, Debug)] -pub struct Mp4parseSinfInfo { - pub original_format: OptionalFourCc, - pub scheme_type: Mp4ParseEncryptionSchemeType, - pub is_encrypted: u8, - pub iv_size: u8, - pub kid: Mp4parseByteData, - // Members for pattern encryption schemes, may be 0 (u8) or empty - // (Mp4parseByteData) if pattern encryption is not in use - pub crypt_byte_block: u8, - pub skip_byte_block: u8, - pub constant_iv: Mp4parseByteData, - // End pattern encryption scheme members -} - -#[repr(C)] -#[derive(Default, Debug)] -pub struct Mp4parseTrackAudioSampleInfo { - pub codec_type: Mp4parseCodec, - pub channels: u16, - pub bit_depth: u16, - pub sample_rate: u32, - pub profile: u16, - pub extended_profile: u16, - pub codec_specific_config: Mp4parseByteData, - pub extra_data: Mp4parseByteData, - pub protected_data: Mp4parseSinfInfo, -} - -#[repr(C)] -#[derive(Debug)] -pub struct Mp4parseTrackAudioInfo { - pub sample_info_count: u32, - pub sample_info: *const Mp4parseTrackAudioSampleInfo, -} - -impl Default for Mp4parseTrackAudioInfo { - fn default() -> Self { - Self { - sample_info_count: 0, - sample_info: std::ptr::null(), - } - } -} - -#[repr(C)] -#[derive(Default, Debug)] -pub struct Mp4parseTrackVideoSampleInfo { - pub codec_type: Mp4parseCodec, - pub image_width: u16, - pub image_height: u16, - pub extra_data: Mp4parseByteData, - pub protected_data: Mp4parseSinfInfo, -} - -#[cfg(feature = "craw")] -#[repr(C)] -#[derive(Default, Debug)] -pub struct Mp4parseTrackRawInfo { - pub image_width: u16, - pub image_height: u16, - pub is_jpeg: bool, - pub offset: u64, - pub size: u64, -} - -#[repr(C)] -#[derive(Debug)] -pub struct Mp4parseTrackVideoInfo { - pub display_width: u32, - pub display_height: u32, - pub rotation: u16, - pub sample_info_count: u32, - pub sample_info: *const Mp4parseTrackVideoSampleInfo, -} - -impl Default for Mp4parseTrackVideoInfo { - fn default() -> Self { - Self { - display_width: 0, - display_height: 0, - rotation: 0, - sample_info_count: 0, - sample_info: std::ptr::null(), - } - } -} - -#[repr(C)] -#[derive(Default, Debug)] -pub struct Mp4parseFragmentInfo { - pub fragment_duration: u64, - // TODO: - // info in trex box. -} - -#[cfg(feature = "craw")] -#[repr(C)] -#[derive(Default)] -pub struct Mp4parseCrawHeader { - pub cncv: Mp4parseByteData, - pub thumb_w: u16, - pub thumb_h: u16, - pub thumbnail: Mp4parseByteData, - pub meta1: Mp4parseByteData, - pub meta2: Mp4parseByteData, - pub meta3: Mp4parseByteData, - pub meta4: Mp4parseByteData, -} - -#[derive(Default)] -pub struct Mp4parseParser { - context: MediaContext, - opus_header: TryHashMap<u32, TryVec<u8>>, - pssh_data: TryVec<u8>, - sample_table: TryHashMap<u32, TryVec<Indice>>, - // Store a mapping from track index (not id) to associated sample - // descriptions. Because each track has a variable number of sample - // descriptions, and because we need the data to live long enough to be - // copied out by callers, we store these on the parser struct. - audio_track_sample_descriptions: TryHashMap<u32, TryVec<Mp4parseTrackAudioSampleInfo>>, - video_track_sample_descriptions: TryHashMap<u32, TryVec<Mp4parseTrackVideoSampleInfo>>, -} - -#[repr(C)] -#[derive(Debug)] -pub struct Mp4parseAvifImageItem { - pub coded_data: Mp4parseByteData, - pub bits_per_channel: Mp4parseByteData, -} - -#[repr(C)] -#[derive(Debug)] -pub struct Mp4parseAvifImage { - pub primary_image: Mp4parseAvifImageItem, - /// The size of the image; should never be null unless using permissive parsing - pub spatial_extents: *const mp4parse::ImageSpatialExtentsProperty, - pub nclx_colour_information: *const mp4parse::NclxColourInformation, - pub icc_colour_information: Mp4parseByteData, - pub image_rotation: mp4parse::ImageRotation, - pub image_mirror: *const mp4parse::ImageMirror, - /// If no alpha item exists, members' `.length` will be 0 and `.data` will be null - pub alpha_image: Mp4parseAvifImageItem, - pub premultiplied_alpha: bool, -} - -/// A unified interface for the parsers which have different contexts, but -/// share the same pattern of construction. This allows unification of -/// argument validation from C and minimizes the surface of unsafe code. -trait ContextParser -where - Self: Sized, -{ - type Context; - - fn with_context(context: Self::Context) -> Self; - - fn read<T: Read>(io: &mut T, strictness: ParseStrictness) -> mp4parse::Result<Self::Context>; -} - -impl Mp4parseParser { - fn context(&self) -> &MediaContext { - &self.context - } - - fn context_mut(&mut self) -> &mut MediaContext { - &mut self.context - } -} - -impl ContextParser for Mp4parseParser { - type Context = MediaContext; - - fn with_context(context: Self::Context) -> Self { - Self { - context, - ..Default::default() - } - } - - fn read<T: Read>(io: &mut T, _strictness: ParseStrictness) -> mp4parse::Result<Self::Context> { - let r = mp4parse::read_mp4(io); - log::debug!("mp4parse::read_mp4 -> {:?}", r); - r - } -} - -pub struct Mp4parseAvifParser { - context: AvifContext, -} - -impl Mp4parseAvifParser { - fn context(&self) -> &AvifContext { - &self.context - } -} - -impl ContextParser for Mp4parseAvifParser { - type Context = AvifContext; - - fn with_context(context: Self::Context) -> Self { - Self { context } - } - - fn read<T: Read>(io: &mut T, strictness: ParseStrictness) -> mp4parse::Result<Self::Context> { - let r = mp4parse::read_avif(io, strictness); - if r.is_err() { - log::debug!("{:?}", r); - } - log::trace!("mp4parse::read_avif -> {:?}", r); - r - } -} - -#[repr(C)] -#[derive(Clone)] -pub struct Mp4parseIo { - pub read: Option< - extern "C" fn(buffer: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize, - >, - pub userdata: *mut std::os::raw::c_void, -} - -impl Read for Mp4parseIo { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { - if buf.len() > isize::max_value() as usize { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "buf length overflow in Mp4parseIo Read impl", - )); - } - let rv = self.read.unwrap()(buf.as_mut_ptr(), buf.len(), self.userdata); - if rv >= 0 { - Ok(rv as usize) - } else { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "I/O error in Mp4parseIo Read impl", - )) - } - } -} - -// C API wrapper functions. - -/// Allocate an `Mp4parseParser*` to read from the supplied `Mp4parseIo` and -/// parse the content from the `Mp4parseIo` argument until EOF or error. -/// -/// # Safety -/// -/// This function is unsafe because it dereferences the `io` and `parser_out` -/// pointers given to it. The caller should ensure that the `Mp4ParseIo` -/// struct passed in is a valid pointer. The caller should also ensure the -/// members of io are valid: the `read` function should be sanely implemented, -/// and the `userdata` pointer should be valid. The `parser_out` should be a -/// valid pointer to a location containing a null pointer. Upon successful -/// return (`Mp4parseStatus::Ok`), that location will contain the address of -/// an `Mp4parseParser` allocated by this function. -/// -/// To avoid leaking memory, any successful return of this function must be -/// paired with a call to `mp4parse_free`. In the event of error, no memory -/// will be allocated and `mp4parse_free` must *not* be called. -#[no_mangle] -pub unsafe extern "C" fn mp4parse_new( - io: *const Mp4parseIo, - parser_out: *mut *mut Mp4parseParser, -) -> Mp4parseStatus { - mp4parse_new_common(io, ParseStrictness::Normal, parser_out) -} - -/// Allocate an `Mp4parseAvifParser*` to read from the supplied `Mp4parseIo`. -/// -/// See mp4parse_new; this function is identical except that it allocates an -/// `Mp4parseAvifParser`, which (when successful) must be paired with a call -/// to mp4parse_avif_free. -/// -/// # Safety -/// -/// Same as mp4parse_new. -#[no_mangle] -pub unsafe extern "C" fn mp4parse_avif_new( - io: *const Mp4parseIo, - strictness: ParseStrictness, - parser_out: *mut *mut Mp4parseAvifParser, -) -> Mp4parseStatus { - mp4parse_new_common(io, strictness, parser_out) -} - -unsafe fn mp4parse_new_common<P: ContextParser>( - io: *const Mp4parseIo, - strictness: ParseStrictness, - parser_out: *mut *mut P, -) -> Mp4parseStatus { - // Validate arguments from C. - if io.is_null() - || (*io).userdata.is_null() - || (*io).read.is_none() - || parser_out.is_null() - || !(*parser_out).is_null() - { - Mp4parseStatus::BadArg - } else { - match mp4parse_new_common_safe(&mut (*io).clone(), strictness) { - Ok(parser) => { - *parser_out = parser; - Mp4parseStatus::Ok - } - Err(status) => status, - } - } -} - -fn mp4parse_new_common_safe<T: Read, P: ContextParser>( - io: &mut T, - strictness: ParseStrictness, -) -> Result<*mut P, Mp4parseStatus> { - P::read(io, strictness) - .map(P::with_context) - .and_then(|x| TryBox::try_new(x).map_err(mp4parse::Error::from)) - .map(TryBox::into_raw) - .map_err(Mp4parseStatus::from) -} - -/// Free an `Mp4parseParser*` allocated by `mp4parse_new()`. -/// -/// # Safety -/// -/// This function is unsafe because it creates a box from a raw pointer. -/// Callers should ensure that the parser pointer points to a valid -/// `Mp4parseParser` created by `mp4parse_new`. -#[no_mangle] -pub unsafe extern "C" fn mp4parse_free(parser: *mut Mp4parseParser) { - assert!(!parser.is_null()); - let _ = TryBox::from_raw(parser); -} - -/// Free an `Mp4parseAvifParser*` allocated by `mp4parse_avif_new()`. -/// -/// # Safety -/// -/// This function is unsafe because it creates a box from a raw pointer. -/// Callers should ensure that the parser pointer points to a valid -/// `Mp4parseAvifParser` created by `mp4parse_avif_new`. -#[no_mangle] -pub unsafe extern "C" fn mp4parse_avif_free(parser: *mut Mp4parseAvifParser) { - assert!(!parser.is_null()); - let _ = TryBox::from_raw(parser); -} - -/// Return the number of tracks parsed by previous `mp4parse_read()` call. -/// -/// # Safety -/// -/// This function is unsafe because it dereferences both the parser and count -/// raw pointers passed into it. Callers should ensure the parser pointer -/// points to a valid `Mp4parseParser`, and that the count pointer points an -/// appropriate memory location to have a `u32` written to. -#[no_mangle] -pub unsafe extern "C" fn mp4parse_get_track_count( - parser: *const Mp4parseParser, - count: *mut u32, -) -> Mp4parseStatus { - // Validate arguments from C. - if parser.is_null() || count.is_null() { - return Mp4parseStatus::BadArg; - } - let context = (*parser).context(); - - // Make sure the track count fits in a u32. - if context.tracks.len() > u32::max_value() as usize { - return Mp4parseStatus::Invalid; - } - *count = context.tracks.len() as u32; - Mp4parseStatus::Ok -} - -/// Fill the supplied `Mp4parseTrackInfo` with metadata for `track`. -/// -/// # Safety -/// -/// This function is unsafe because it dereferences the the parser and info raw -/// pointers passed to it. Callers should ensure the parser pointer points to a -/// valid `Mp4parseParser` and that the info pointer points to a valid -/// `Mp4parseTrackInfo`. -#[no_mangle] -pub unsafe extern "C" fn mp4parse_get_track_info( - parser: *mut Mp4parseParser, - track_index: u32, - info: *mut Mp4parseTrackInfo, -) -> Mp4parseStatus { - if parser.is_null() || info.is_null() { - return Mp4parseStatus::BadArg; - } - - // Initialize fields to default values to ensure all fields are always valid. - *info = Default::default(); - - let context = (*parser).context_mut(); - let track_index: usize = track_index as usize; - let info: &mut Mp4parseTrackInfo = &mut *info; - - if track_index >= context.tracks.len() { - return Mp4parseStatus::BadArg; - } - - info.track_type = match context.tracks[track_index].track_type { - TrackType::Video => Mp4parseTrackType::Video, - TrackType::Audio => Mp4parseTrackType::Audio, - TrackType::Metadata => Mp4parseTrackType::Metadata, - TrackType::Unknown => return Mp4parseStatus::Unsupported, - }; - - let track = &context.tracks[track_index]; - - if let (Some(track_timescale), Some(context_timescale)) = (track.timescale, context.timescale) { - let media_time: CheckedInteger<_> = match track - .media_time - .map_or(Some(Microseconds(0)), |media_time| { - track_time_to_us(media_time, track_timescale) - }) { - Some(time) => time.0.into(), - None => return Mp4parseStatus::Invalid, - }; - let empty_duration: CheckedInteger<_> = match track - .empty_duration - .map_or(Some(Microseconds(0)), |empty_duration| { - media_time_to_us(empty_duration, context_timescale) - }) { - Some(time) => time.0.into(), - None => return Mp4parseStatus::Invalid, - }; - info.media_time = match media_time - empty_duration { - Some(difference) => difference, - None => return Mp4parseStatus::Invalid, - }; - - if let Some(track_duration) = track.duration { - match track_time_to_us(track_duration, track_timescale) { - Some(duration) => info.duration = duration.0, - None => return Mp4parseStatus::Invalid, - } - } else { - // Duration unknown; stagefright returns 0 for this. - info.duration = 0 - } - } else { - return Mp4parseStatus::Invalid; - } - - info.track_id = match track.track_id { - Some(track_id) => track_id, - None => return Mp4parseStatus::Invalid, - }; - - Mp4parseStatus::Ok -} - -/// Fill the supplied `Mp4parseTrackAudioInfo` with metadata for `track`. -/// -/// # Safety -/// -/// This function is unsafe because it dereferences the the parser and info raw -/// pointers passed to it. Callers should ensure the parser pointer points to a -/// valid `Mp4parseParser` and that the info pointer points to a valid -/// `Mp4parseTrackAudioInfo`. -#[no_mangle] -pub unsafe extern "C" fn mp4parse_get_track_audio_info( - parser: *mut Mp4parseParser, - track_index: u32, - info: *mut Mp4parseTrackAudioInfo, -) -> Mp4parseStatus { - if parser.is_null() || info.is_null() { - return Mp4parseStatus::BadArg; - } - - // Initialize fields to default values to ensure all fields are always valid. - *info = Default::default(); - - get_track_audio_info(&mut *parser, track_index, &mut *info).into() -} - -fn get_track_audio_info( - parser: &mut Mp4parseParser, - track_index: u32, - info: &mut Mp4parseTrackAudioInfo, -) -> Result<(), Mp4parseStatus> { - let Mp4parseParser { - context, - opus_header, - .. - } = parser; - - if track_index as usize >= context.tracks.len() { - return Err(Mp4parseStatus::BadArg); - } - - let track = &context.tracks[track_index as usize]; - - if track.track_type != TrackType::Audio { - return Err(Mp4parseStatus::Invalid); - } - - // Handle track.stsd - let stsd = match track.stsd { - Some(ref stsd) => stsd, - None => return Err(Mp4parseStatus::Invalid), // Stsd should be present - }; - - if stsd.descriptions.is_empty() { - return Err(Mp4parseStatus::Invalid); // Should have at least 1 description - } - - let mut audio_sample_infos = TryVec::with_capacity(stsd.descriptions.len())?; - for description in stsd.descriptions.iter() { - let mut sample_info = Mp4parseTrackAudioSampleInfo::default(); - let audio = match description { - SampleEntry::Audio(a) => a, - _ => return Err(Mp4parseStatus::Invalid), - }; - - // UNKNOWN for unsupported format. - sample_info.codec_type = match audio.codec_specific { - AudioCodecSpecific::OpusSpecificBox(_) => Mp4parseCodec::Opus, - AudioCodecSpecific::FLACSpecificBox(_) => Mp4parseCodec::Flac, - AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC => { - Mp4parseCodec::Aac - } - AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 => { - Mp4parseCodec::Mp3 - } - AudioCodecSpecific::ES_Descriptor(_) | AudioCodecSpecific::LPCM => { - Mp4parseCodec::Unknown - } - AudioCodecSpecific::MP3 => Mp4parseCodec::Mp3, - AudioCodecSpecific::ALACSpecificBox(_) => Mp4parseCodec::Alac, - #[cfg(feature = "3gpp")] - AudioCodecSpecific::AMRSpecificBox(_) => { - if audio.codec_type == CodecType::AMRNB { - Mp4parseCodec::AMRNB - } else { - Mp4parseCodec::AMRWB - } - } - }; - sample_info.channels = audio.channelcount as u16; - sample_info.bit_depth = audio.samplesize; - sample_info.sample_rate = audio.samplerate as u32; - // sample_info.profile is handled below on a per case basis - - match audio.codec_specific { - AudioCodecSpecific::ES_Descriptor(ref esds) => { - if esds.codec_esds.len() > std::u32::MAX as usize { - return Err(Mp4parseStatus::Invalid); - } - sample_info.extra_data.length = esds.codec_esds.len(); - sample_info.extra_data.data = esds.codec_esds.as_ptr(); - sample_info.codec_specific_config.length = esds.decoder_specific_data.len(); - sample_info.codec_specific_config.data = esds.decoder_specific_data.as_ptr(); - if let Some(rate) = esds.audio_sample_rate { - sample_info.sample_rate = rate; - } - if let Some(channels) = esds.audio_channel_count { - sample_info.channels = channels; - } - if let Some(profile) = esds.audio_object_type { - sample_info.profile = profile; - } - sample_info.extended_profile = match esds.extended_audio_object_type { - Some(extended_profile) => extended_profile, - _ => sample_info.profile, - }; - } - AudioCodecSpecific::FLACSpecificBox(ref flac) => { - // Return the STREAMINFO metadata block in the codec_specific. - let streaminfo = &flac.blocks[0]; - if streaminfo.block_type != 0 || streaminfo.data.len() != 34 { - return Err(Mp4parseStatus::Invalid); - } - sample_info.codec_specific_config.length = streaminfo.data.len(); - sample_info.codec_specific_config.data = streaminfo.data.as_ptr(); - } - AudioCodecSpecific::OpusSpecificBox(ref opus) => { - let mut v = TryVec::new(); - match serialize_opus_header(opus, &mut v) { - Err(_) => { - return Err(Mp4parseStatus::Invalid); - } - Ok(_) => { - opus_header.insert(track_index, v)?; - if let Some(v) = opus_header.get(&track_index) { - if v.len() > std::u32::MAX as usize { - return Err(Mp4parseStatus::Invalid); - } - sample_info.codec_specific_config.length = v.len(); - sample_info.codec_specific_config.data = v.as_ptr(); - } - } - } - } - AudioCodecSpecific::ALACSpecificBox(ref alac) => { - sample_info.codec_specific_config.length = alac.data.len(); - sample_info.codec_specific_config.data = alac.data.as_ptr(); - } - AudioCodecSpecific::MP3 | AudioCodecSpecific::LPCM => (), - #[cfg(feature = "3gpp")] - AudioCodecSpecific::AMRSpecificBox(_) => (), - } - - if let Some(p) = audio - .protection_info - .iter() - .find(|sinf| sinf.tenc.is_some()) - { - sample_info.protected_data.original_format = - OptionalFourCc::Some(p.original_format.value); - sample_info.protected_data.scheme_type = match p.scheme_type { - Some(ref scheme_type_box) => { - match scheme_type_box.scheme_type.value.as_ref() { - b"cenc" => Mp4ParseEncryptionSchemeType::Cenc, - b"cbcs" => Mp4ParseEncryptionSchemeType::Cbcs, - // We don't support other schemes, and shouldn't reach - // this case. Try to gracefully handle by treating as - // no encryption case. - _ => Mp4ParseEncryptionSchemeType::None, - } - } - None => Mp4ParseEncryptionSchemeType::None, - }; - if let Some(ref tenc) = p.tenc { - sample_info.protected_data.is_encrypted = tenc.is_encrypted; - sample_info.protected_data.iv_size = tenc.iv_size; - sample_info.protected_data.kid.set_data(&(tenc.kid)); - sample_info.protected_data.crypt_byte_block = - tenc.crypt_byte_block_count.unwrap_or(0); - sample_info.protected_data.skip_byte_block = - tenc.skip_byte_block_count.unwrap_or(0); - if let Some(ref iv_vec) = tenc.constant_iv { - if iv_vec.len() > std::u32::MAX as usize { - return Err(Mp4parseStatus::Invalid); - } - sample_info.protected_data.constant_iv.set_data(iv_vec); - }; - } - } - audio_sample_infos.push(sample_info)?; - } - - parser - .audio_track_sample_descriptions - .insert(track_index, audio_sample_infos)?; - match parser.audio_track_sample_descriptions.get(&track_index) { - Some(sample_info) => { - if sample_info.len() > std::u32::MAX as usize { - // Should never happen due to upper limits on number of sample - // descriptions a track can have, but lets be safe. - return Err(Mp4parseStatus::Invalid); - } - info.sample_info_count = sample_info.len() as u32; - info.sample_info = sample_info.as_ptr(); - } - None => return Err(Mp4parseStatus::Invalid), // Shouldn't happen, we just inserted the info! - } - - Ok(()) -} - -/// File the supplied `Mp4parseTrackRawInfo` with metadata for `track`. -/// -/// # Safety -/// This function dereference `parser` and `info`. If they are null, -/// `Mp4parseStatus::BadArg` is returned. -#[cfg(feature = "craw")] -#[no_mangle] -pub unsafe extern "C" fn mp4parse_get_track_raw_info( - parser: *mut Mp4parseParser, - track_index: u32, - info: *mut Mp4parseTrackRawInfo, -) -> Mp4parseStatus { - if parser.is_null() || info.is_null() { - return Mp4parseStatus::BadArg; - } - - // Initialize fields to default values to ensure all fields are always valid. - *info = Default::default(); - - let context = (*parser).context_mut(); - - if track_index as usize >= context.tracks.len() { - return Mp4parseStatus::BadArg; - } - - let track = &context.tracks[track_index as usize]; - - match track.track_type { - TrackType::Video => {} - _ => return Mp4parseStatus::Invalid, - }; - - let video = match track.stsd { - // We assume there is only one. - Some(ref data) => &data.descriptions[0], - None => return Mp4parseStatus::Invalid, - }; - - let raw = match *video { - SampleEntry::CanonCRAW(ref x) => x, - _ => return Mp4parseStatus::Invalid, - }; - - (*info).image_width = raw.width; - (*info).image_height = raw.height; - (*info).is_jpeg = raw.is_jpeg; - // assume there is an offset and samples size is constant - (*info).size = if let Some(ref stsz) = track.stsz { - stsz.sample_size as u64 - } else { - 0 - }; - (*info).offset = if let Some(ref stco) = track.stco { - stco.offsets[0] - } else { - 0 - }; - - Mp4parseStatus::Ok -} - -/// Fill the supplied `Mp4parseTrackVideoInfo` with metadata for `track`. -/// -/// # Safety -/// -/// This function is unsafe because it dereferences the the parser and info raw -/// pointers passed to it. Callers should ensure the parser pointer points to a -/// valid `Mp4parseParser` and that the info pointer points to a valid -/// `Mp4parseTrackVideoInfo`. -#[no_mangle] -pub unsafe extern "C" fn mp4parse_get_track_video_info( - parser: *mut Mp4parseParser, - track_index: u32, - info: *mut Mp4parseTrackVideoInfo, -) -> Mp4parseStatus { - if parser.is_null() || info.is_null() { - return Mp4parseStatus::BadArg; - } - - // Initialize fields to default values to ensure all fields are always valid. - *info = Default::default(); - - mp4parse_get_track_video_info_safe(&mut *parser, track_index, &mut *info).into() -} - -fn mp4parse_get_track_video_info_safe( - parser: &mut Mp4parseParser, - track_index: u32, - info: &mut Mp4parseTrackVideoInfo, -) -> Result<(), Mp4parseStatus> { - let context = parser.context(); - - if track_index as usize >= context.tracks.len() { - return Err(Mp4parseStatus::BadArg); - } - - let track = &context.tracks[track_index as usize]; - - if track.track_type != TrackType::Video { - return Err(Mp4parseStatus::Invalid); - } - - // Handle track.tkhd - if let Some(ref tkhd) = track.tkhd { - info.display_width = tkhd.width >> 16; // 16.16 fixed point - info.display_height = tkhd.height >> 16; // 16.16 fixed point - let matrix = ( - tkhd.matrix.a >> 16, - tkhd.matrix.b >> 16, - tkhd.matrix.c >> 16, - tkhd.matrix.d >> 16, - ); - info.rotation = match matrix { - (0, 1, -1, 0) => 90, // rotate 90 degrees - (-1, 0, 0, -1) => 180, // rotate 180 degrees - (0, -1, 1, 0) => 270, // rotate 270 degrees - _ => 0, - }; - } else { - return Err(Mp4parseStatus::Invalid); - } - - // Handle track.stsd - let stsd = match track.stsd { - Some(ref stsd) => stsd, - None => return Err(Mp4parseStatus::Invalid), // Stsd should be present - }; - - if stsd.descriptions.is_empty() { - return Err(Mp4parseStatus::Invalid); // Should have at least 1 description - } - - let mut video_sample_infos = TryVec::with_capacity(stsd.descriptions.len())?; - for description in stsd.descriptions.iter() { - let mut sample_info = Mp4parseTrackVideoSampleInfo::default(); - let video = match description { - SampleEntry::Video(v) => v, - _ => return Err(Mp4parseStatus::Invalid), - }; - - // UNKNOWN for unsupported format. - sample_info.codec_type = match video.codec_specific { - VideoCodecSpecific::VPxConfig(_) => Mp4parseCodec::Vp9, - VideoCodecSpecific::AV1Config(_) => Mp4parseCodec::Av1, - VideoCodecSpecific::AVCConfig(_) => Mp4parseCodec::Avc, - VideoCodecSpecific::H263Config(_) => Mp4parseCodec::H263, - #[cfg(feature = "mp4v")] - VideoCodecSpecific::ESDSConfig(_) => Mp4parseCodec::Mp4v, - #[cfg(not(feature = "mp4v"))] - VideoCodecSpecific::ESDSConfig(_) => - // MP4V (14496-2) video is unsupported. - { - Mp4parseCodec::Unknown - } - }; - sample_info.image_width = video.width; - sample_info.image_height = video.height; - - match video.codec_specific { - VideoCodecSpecific::AV1Config(ref config) => { - sample_info.extra_data.set_data(&config.raw_config); - } - VideoCodecSpecific::AVCConfig(ref data) | VideoCodecSpecific::ESDSConfig(ref data) => { - sample_info.extra_data.set_data(data); - } - _ => {} - } - - if let Some(p) = video - .protection_info - .iter() - .find(|sinf| sinf.tenc.is_some()) - { - sample_info.protected_data.original_format = - OptionalFourCc::Some(p.original_format.value); - sample_info.protected_data.scheme_type = match p.scheme_type { - Some(ref scheme_type_box) => { - match scheme_type_box.scheme_type.value.as_ref() { - b"cenc" => Mp4ParseEncryptionSchemeType::Cenc, - b"cbcs" => Mp4ParseEncryptionSchemeType::Cbcs, - // We don't support other schemes, and shouldn't reach - // this case. Try to gracefully handle by treating as - // no encryption case. - _ => Mp4ParseEncryptionSchemeType::None, - } - } - None => Mp4ParseEncryptionSchemeType::None, - }; - if let Some(ref tenc) = p.tenc { - sample_info.protected_data.is_encrypted = tenc.is_encrypted; - sample_info.protected_data.iv_size = tenc.iv_size; - sample_info.protected_data.kid.set_data(&(tenc.kid)); - sample_info.protected_data.crypt_byte_block = - tenc.crypt_byte_block_count.unwrap_or(0); - sample_info.protected_data.skip_byte_block = - tenc.skip_byte_block_count.unwrap_or(0); - if let Some(ref iv_vec) = tenc.constant_iv { - if iv_vec.len() > std::u32::MAX as usize { - return Err(Mp4parseStatus::Invalid); - } - sample_info.protected_data.constant_iv.set_data(iv_vec); - }; - } - } - video_sample_infos.push(sample_info)?; - } - - parser - .video_track_sample_descriptions - .insert(track_index, video_sample_infos)?; - match parser.video_track_sample_descriptions.get(&track_index) { - Some(sample_info) => { - if sample_info.len() > std::u32::MAX as usize { - // Should never happen due to upper limits on number of sample - // descriptions a track can have, but lets be safe. - return Err(Mp4parseStatus::Invalid); - } - info.sample_info_count = sample_info.len() as u32; - info.sample_info = sample_info.as_ptr(); - } - None => return Err(Mp4parseStatus::Invalid), // Shouldn't happen, we just inserted the info! - } - Ok(()) -} - -/// Return a pointer to the primary item parsed by previous `mp4parse_avif_new()` call. -/// -/// # Safety -/// -/// This function is unsafe because it dereferences both the parser and -/// avif_image raw pointers passed into it. Callers should ensure the parser -/// pointer points to a valid `Mp4parseAvifParser`, and that the avif_image -/// pointer points to a valid `Mp4parseAvifImage`. If there was not a previous -/// successful call to `mp4parse_avif_read()`, no guarantees are made as to -/// the state of `avif_image`. If `avif_image.alpha_image.coded_data` is set to -/// a positive `length` and non-null `data`, then the `avif_image` contains a -/// valid alpha channel data. Otherwise, the image is opaque. -#[no_mangle] -pub unsafe extern "C" fn mp4parse_avif_get_image( - parser: *const Mp4parseAvifParser, - avif_image: *mut Mp4parseAvifImage, -) -> Mp4parseStatus { - if parser.is_null() || avif_image.is_null() { - return Mp4parseStatus::BadArg; - } - - if let Ok(image) = mp4parse_avif_get_image_safe(&*parser) { - *avif_image = image; - Mp4parseStatus::Ok - } else { - Mp4parseStatus::Invalid - } -} - -pub fn mp4parse_avif_get_image_safe( - parser: &Mp4parseAvifParser, -) -> mp4parse::Result<Mp4parseAvifImage> { - let context = parser.context(); - - let primary_image = Mp4parseAvifImageItem { - coded_data: Mp4parseByteData::with_data(context.primary_item_coded_data()), - bits_per_channel: Mp4parseByteData::with_data(context.primary_item_bits_per_channel()?), - }; - - // If there is no alpha present, all the `Mp4parseByteData`s will be zero length - let alpha_image = Mp4parseAvifImageItem { - coded_data: Mp4parseByteData::with_data(context.alpha_item_coded_data()), - bits_per_channel: Mp4parseByteData::with_data(context.alpha_item_bits_per_channel()?), - }; - - Ok(Mp4parseAvifImage { - primary_image, - spatial_extents: context.spatial_extents_ptr()?, - nclx_colour_information: context.nclx_colour_information_ptr()?, - icc_colour_information: Mp4parseByteData::with_data(context.icc_colour_information()?), - image_rotation: context.image_rotation()?, - image_mirror: context.image_mirror_ptr()?, - alpha_image, - premultiplied_alpha: context.premultiplied_alpha, - }) -} - -/// Fill the supplied `Mp4parseByteData` with index information from `track`. -/// -/// # Safety -/// -/// This function is unsafe because it dereferences the the parser and indices -/// raw pointers passed to it. Callers should ensure the parser pointer points -/// to a valid `Mp4parseParser` and that the indices pointer points to a valid -/// `Mp4parseByteData`. -#[no_mangle] -pub unsafe extern "C" fn mp4parse_get_indice_table( - parser: *mut Mp4parseParser, - track_id: u32, - indices: *mut Mp4parseByteData, -) -> Mp4parseStatus { - if parser.is_null() { - return Mp4parseStatus::BadArg; - } - - // Initialize fields to default values to ensure all fields are always valid. - *indices = Default::default(); - - get_indice_table(&mut *parser, track_id, &mut *indices).into() -} - -fn get_indice_table( - parser: &mut Mp4parseParser, - track_id: u32, - indices: &mut Mp4parseByteData, -) -> Result<(), Mp4parseStatus> { - let Mp4parseParser { - context, - sample_table: index_table, - .. - } = parser; - let tracks = &context.tracks; - let track = match tracks.iter().find(|track| track.track_id == Some(track_id)) { - Some(t) => t, - _ => return Err(Mp4parseStatus::Invalid), - }; - - if let Some(v) = index_table.get(&track_id) { - indices.set_indices(v); - return Ok(()); - } - - let media_time = match (&track.media_time, &track.timescale) { - (&Some(t), &Some(s)) => track_time_to_us(t, s) - .and_then(|v| i64::try_from(v.0).ok()) - .map(Into::into), - _ => None, - }; - - let empty_duration: Option<CheckedInteger<_>> = - match (&track.empty_duration, &context.timescale) { - (&Some(e), &Some(s)) => media_time_to_us(e, s) - .and_then(|v| i64::try_from(v.0).ok()) - .map(Into::into), - _ => None, - }; - - // Find the track start offset time from 'elst'. - // 'media_time' maps start time onward, 'empty_duration' adds time offset - // before first frame is displayed. - let offset_time = match (empty_duration, media_time) { - (Some(e), Some(m)) => (e - m).ok_or(Err(Mp4parseStatus::Invalid))?, - (Some(e), None) => e, - (None, Some(m)) => m, - _ => 0.into(), - }; - - if let Some(v) = create_sample_table(track, offset_time) { - indices.set_indices(&v); - index_table.insert(track_id, v)?; - return Ok(()); - } - - Err(Mp4parseStatus::Invalid) -} - -/// Fill the supplied `Mp4parseFragmentInfo` with metadata from fragmented file. -/// -/// # Safety -/// -/// This function is unsafe because it dereferences the the parser and -/// info raw pointers passed to it. Callers should ensure the parser -/// pointer points to a valid `Mp4parseParser` and that the info pointer points -/// to a valid `Mp4parseFragmentInfo`. - -#[no_mangle] -pub unsafe extern "C" fn mp4parse_get_fragment_info( - parser: *mut Mp4parseParser, - info: *mut Mp4parseFragmentInfo, -) -> Mp4parseStatus { - if parser.is_null() || info.is_null() { - return Mp4parseStatus::BadArg; - } - - // Initialize fields to default values to ensure all fields are always valid. - *info = Default::default(); - - let context = (*parser).context(); - let info: &mut Mp4parseFragmentInfo = &mut *info; - - info.fragment_duration = 0; - - let duration = match context.mvex { - Some(ref mvex) => mvex.fragment_duration, - None => return Mp4parseStatus::Invalid, - }; - - if let (Some(time), Some(scale)) = (duration, context.timescale) { - info.fragment_duration = match media_time_to_us(time, scale) { - Some(time_us) => time_us.0 as u64, - None => return Mp4parseStatus::Invalid, - } - } - - Mp4parseStatus::Ok -} - -/// Determine if an mp4 file is fragmented. A fragmented file needs mvex table -/// and contains no data in stts, stsc, and stco boxes. -/// -/// # Safety -/// -/// This function is unsafe because it dereferences the the parser and -/// fragmented raw pointers passed to it. Callers should ensure the parser -/// pointer points to a valid `Mp4parseParser` and that the fragmented pointer -/// points to an appropriate memory location to have a `u8` written to. -#[no_mangle] -pub unsafe extern "C" fn mp4parse_is_fragmented( - parser: *mut Mp4parseParser, - track_id: u32, - fragmented: *mut u8, -) -> Mp4parseStatus { - if parser.is_null() { - return Mp4parseStatus::BadArg; - } - - let context = (*parser).context_mut(); - let tracks = &context.tracks; - (*fragmented) = false as u8; - - if context.mvex.is_none() { - return Mp4parseStatus::Ok; - } - - // check sample tables. - let mut iter = tracks.iter(); - iter.find(|track| track.track_id == Some(track_id)) - .map_or(Mp4parseStatus::BadArg, |track| { - match (&track.stsc, &track.stco, &track.stts) { - (&Some(ref stsc), &Some(ref stco), &Some(ref stts)) - if stsc.samples.is_empty() - && stco.offsets.is_empty() - && stts.samples.is_empty() => - { - (*fragmented) = true as u8 - } - _ => {} - }; - Mp4parseStatus::Ok - }) -} - -/// Get 'pssh' system id and 'pssh' box content for eme playback. -/// -/// The data format of the `info` struct passed to gecko is: -/// -/// - system id (16 byte uuid) -/// - pssh box size (32-bit native endian) -/// - pssh box content (including header) -/// -/// # Safety -/// -/// This function is unsafe because it dereferences the the parser and -/// info raw pointers passed to it. Callers should ensure the parser -/// pointer points to a valid `Mp4parseParser` and that the fragmented pointer -/// points to a valid `Mp4parsePsshInfo`. -#[no_mangle] -pub unsafe extern "C" fn mp4parse_get_pssh_info( - parser: *mut Mp4parseParser, - info: *mut Mp4parsePsshInfo, -) -> Mp4parseStatus { - if parser.is_null() || info.is_null() { - return Mp4parseStatus::BadArg; - } - - // Initialize fields to default values to ensure all fields are always valid. - *info = Default::default(); - - get_pssh_info(&mut *parser, &mut *info).into() -} - -fn get_pssh_info( - parser: &mut Mp4parseParser, - info: &mut Mp4parsePsshInfo, -) -> Result<(), Mp4parseStatus> { - let Mp4parseParser { - context, pssh_data, .. - } = parser; - - pssh_data.clear(); - for pssh in &context.psshs { - let content_len = pssh - .box_content - .len() - .try_into() - .map_err(|_| Mp4parseStatus::Invalid)?; - let mut data_len = TryVec::new(); - if data_len - .write_u32::<byteorder::NativeEndian>(content_len) - .is_err() - { - return Err(Mp4parseStatus::Io); - } - pssh_data.extend_from_slice(pssh.system_id.as_slice())?; - pssh_data.extend_from_slice(data_len.as_slice())?; - pssh_data.extend_from_slice(pssh.box_content.as_slice())?; - } - - info.data.set_data(pssh_data); - - Ok(()) -} - -/// Get the Craw hader for the file. -/// -/// # Safety -/// This function is unsafe as it derefenece the Parser -/// and the CrawHeader -#[cfg(feature = "craw")] -#[no_mangle] -pub unsafe extern "C" fn mp4parse_get_craw_header( - parser: *mut Mp4parseParser, - header: *mut Mp4parseCrawHeader, -) -> Mp4parseStatus { - if parser.is_null() || header.is_null() { - return Mp4parseStatus::BadArg; - } - - // Initialize fields to default values to ensure all fields are always valid. - *header = Default::default(); - - let context = (*parser).context_mut(); - let header: &mut Mp4parseCrawHeader = &mut *header; - - if context.craw.is_none() { - return Mp4parseStatus::Invalid; - } - - let craw = context.craw.as_ref().unwrap(); - header.cncv.set_data(&craw.cncv); - header.thumb_w = craw.thumbnail.width; - header.thumb_h = craw.thumbnail.height; - header.thumbnail.set_data(&craw.thumbnail.data); - if let Some(ref meta) = craw.meta1 { - header.meta1.set_data(meta); - } - if let Some(ref meta) = craw.meta2 { - header.meta2.set_data(meta); - } - if let Some(ref meta) = craw.meta3 { - header.meta3.set_data(meta); - } - if let Some(ref meta) = craw.meta4 { - header.meta4.set_data(meta); - } - - Mp4parseStatus::Ok -} - -/// Get the Craw table entry -/// -/// # Safety -/// The function dereference the parser, and return offset -/// and size by pointer. They can't be null, otherwise -/// `Mp4parseStatus::BadArg` is returned. -#[cfg(feature = "craw")] -#[no_mangle] -pub unsafe extern "C" fn mp4parse_get_craw_table_entry( - parser: *mut Mp4parseParser, - idx: usize, - offset: *mut u64, - size: *mut u64, -) -> Mp4parseStatus { - if parser.is_null() || offset.is_null() || size.is_null() { - return Mp4parseStatus::BadArg; - } - *offset = 0; - *size = 0; - let context = (*parser).context_mut(); - if context.craw.is_none() { - return Mp4parseStatus::Invalid; - } - - let craw = context.craw.as_ref().unwrap(); - if craw.offsets.len() <= idx { - return Mp4parseStatus::Invalid; - } - let entry = craw.offsets[idx]; - *offset = entry.0; - *size = entry.1; - - Mp4parseStatus::Ok -} - -#[cfg(test)] -extern "C" fn error_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize { - -1 -} - -#[cfg(test)] -extern "C" fn valid_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize { - let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) }; - - let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) }; - match input.read(buf) { - Ok(n) => n as isize, - Err(_) => -1, - } -} - -#[test] -fn get_track_count_null_parser() { - unsafe { - let mut count: u32 = 0; - let rv = mp4parse_get_track_count(std::ptr::null(), std::ptr::null_mut()); - assert_eq!(rv, Mp4parseStatus::BadArg); - let rv = mp4parse_get_track_count(std::ptr::null(), &mut count); - assert_eq!(rv, Mp4parseStatus::BadArg); - } -} - -#[test] -fn arg_validation() { - unsafe { - let rv = mp4parse_new(std::ptr::null(), std::ptr::null_mut()); - assert_eq!(rv, Mp4parseStatus::BadArg); - - // Passing a null Mp4parseIo is an error. - let mut parser = std::ptr::null_mut(); - let rv = mp4parse_new(std::ptr::null(), &mut parser); - assert_eq!(rv, Mp4parseStatus::BadArg); - assert!(parser.is_null()); - - let null_mut: *mut std::os::raw::c_void = std::ptr::null_mut(); - - // Passing an Mp4parseIo with null members is an error. - let io = Mp4parseIo { - read: None, - userdata: null_mut, - }; - let mut parser = std::ptr::null_mut(); - let rv = mp4parse_new(&io, &mut parser); - assert_eq!(rv, Mp4parseStatus::BadArg); - assert!(parser.is_null()); - - let mut dummy_value = 42; - let io = Mp4parseIo { - read: None, - userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void, - }; - let mut parser = std::ptr::null_mut(); - let rv = mp4parse_new(&io, &mut parser); - assert_eq!(rv, Mp4parseStatus::BadArg); - assert!(parser.is_null()); - - let mut dummy_info = Mp4parseTrackInfo { - track_type: Mp4parseTrackType::Video, - ..Default::default() - }; - assert_eq!( - Mp4parseStatus::BadArg, - mp4parse_get_track_info(std::ptr::null_mut(), 0, &mut dummy_info) - ); - - let mut dummy_video = Mp4parseTrackVideoInfo { - display_width: 0, - display_height: 0, - rotation: 0, - sample_info_count: 0, - sample_info: std::ptr::null(), - }; - assert_eq!( - Mp4parseStatus::BadArg, - mp4parse_get_track_video_info(std::ptr::null_mut(), 0, &mut dummy_video) - ); - - let mut dummy_audio = Default::default(); - assert_eq!( - Mp4parseStatus::BadArg, - mp4parse_get_track_audio_info(std::ptr::null_mut(), 0, &mut dummy_audio) - ); - } -} - -#[test] -fn parser_input_must_be_null() { - let mut dummy_value = 42; - let io = Mp4parseIo { - read: Some(error_read), - userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void, - }; - let mut parser = 0xDEAD_BEEF as *mut _; - let rv = unsafe { mp4parse_new(&io, &mut parser) }; - assert_eq!(rv, Mp4parseStatus::BadArg); -} - -#[test] -fn arg_validation_with_parser() { - unsafe { - let mut dummy_value = 42; - let io = Mp4parseIo { - read: Some(error_read), - userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void, - }; - let mut parser = std::ptr::null_mut(); - let rv = mp4parse_new(&io, &mut parser); - assert_eq!(rv, Mp4parseStatus::Io); - assert!(parser.is_null()); - - // Null info pointers are an error. - assert_eq!( - Mp4parseStatus::BadArg, - mp4parse_get_track_info(parser, 0, std::ptr::null_mut()) - ); - assert_eq!( - Mp4parseStatus::BadArg, - mp4parse_get_track_video_info(parser, 0, std::ptr::null_mut()) - ); - assert_eq!( - Mp4parseStatus::BadArg, - mp4parse_get_track_audio_info(parser, 0, std::ptr::null_mut()) - ); - - let mut dummy_info = Mp4parseTrackInfo { - track_type: Mp4parseTrackType::Video, - ..Default::default() - }; - assert_eq!( - Mp4parseStatus::BadArg, - mp4parse_get_track_info(parser, 0, &mut dummy_info) - ); - - let mut dummy_video = Mp4parseTrackVideoInfo { - display_width: 0, - display_height: 0, - rotation: 0, - sample_info_count: 0, - sample_info: std::ptr::null(), - }; - assert_eq!( - Mp4parseStatus::BadArg, - mp4parse_get_track_video_info(parser, 0, &mut dummy_video) - ); - - let mut dummy_audio = Default::default(); - assert_eq!( - Mp4parseStatus::BadArg, - mp4parse_get_track_audio_info(parser, 0, &mut dummy_audio) - ); - } -} - -#[cfg(test)] -fn parse_minimal_mp4() -> *mut Mp4parseParser { - let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap(); - let io = Mp4parseIo { - read: Some(valid_read), - userdata: &mut file as *mut _ as *mut std::os::raw::c_void, - }; - let mut parser = std::ptr::null_mut(); - let rv = unsafe { mp4parse_new(&io, &mut parser) }; - assert_eq!(Mp4parseStatus::Ok, rv); - parser -} - -#[test] -fn minimal_mp4_parse_ok() { - let parser = parse_minimal_mp4(); - - assert!(!parser.is_null()); - - unsafe { - mp4parse_free(parser); - } -} - -#[test] -fn minimal_mp4_get_track_cout() { - let parser = parse_minimal_mp4(); - - let mut count: u32 = 0; - assert_eq!(Mp4parseStatus::Ok, unsafe { - mp4parse_get_track_count(parser, &mut count) - }); - assert_eq!(2, count); - - unsafe { - mp4parse_free(parser); - } -} - -#[test] -fn minimal_mp4_get_track_info() { - let parser = parse_minimal_mp4(); - - let mut info = Mp4parseTrackInfo { - track_type: Mp4parseTrackType::Video, - ..Default::default() - }; - assert_eq!(Mp4parseStatus::Ok, unsafe { - mp4parse_get_track_info(parser, 0, &mut info) - }); - assert_eq!(info.track_type, Mp4parseTrackType::Video); - assert_eq!(info.track_id, 1); - assert_eq!(info.duration, 40000); - assert_eq!(info.media_time, 0); - - assert_eq!(Mp4parseStatus::Ok, unsafe { - mp4parse_get_track_info(parser, 1, &mut info) - }); - assert_eq!(info.track_type, Mp4parseTrackType::Audio); - assert_eq!(info.track_id, 2); - assert_eq!(info.duration, 61333); - assert_eq!(info.media_time, 21333); - - unsafe { - mp4parse_free(parser); - } -} - -#[test] -fn minimal_mp4_get_track_video_info() { - let parser = parse_minimal_mp4(); - - let mut video = Mp4parseTrackVideoInfo::default(); - assert_eq!(Mp4parseStatus::Ok, unsafe { - mp4parse_get_track_video_info(parser, 0, &mut video) - }); - assert_eq!(video.display_width, 320); - assert_eq!(video.display_height, 240); - assert_eq!(video.sample_info_count, 1); - - unsafe { - assert_eq!((*video.sample_info).image_width, 320); - assert_eq!((*video.sample_info).image_height, 240); - } - - unsafe { - mp4parse_free(parser); - } -} - -#[test] -fn minimal_mp4_get_track_audio_info() { - let parser = parse_minimal_mp4(); - - let mut audio = Mp4parseTrackAudioInfo::default(); - assert_eq!(Mp4parseStatus::Ok, unsafe { - mp4parse_get_track_audio_info(parser, 1, &mut audio) - }); - assert_eq!(audio.sample_info_count, 1); - - unsafe { - assert_eq!((*audio.sample_info).channels, 1); - assert_eq!((*audio.sample_info).bit_depth, 16); - assert_eq!((*audio.sample_info).sample_rate, 48000); - } - - unsafe { - mp4parse_free(parser); - } -} - -#[test] -fn minimal_mp4_get_track_info_invalid_track_number() { - let parser = parse_minimal_mp4(); - - let mut info = Mp4parseTrackInfo { - track_type: Mp4parseTrackType::Video, - ..Default::default() - }; - assert_eq!(Mp4parseStatus::BadArg, unsafe { - mp4parse_get_track_info(parser, 3, &mut info) - }); - assert_eq!(info.track_type, Mp4parseTrackType::Video); - assert_eq!(info.track_id, 0); - assert_eq!(info.duration, 0); - assert_eq!(info.media_time, 0); - - let mut video = Mp4parseTrackVideoInfo::default(); - assert_eq!(Mp4parseStatus::BadArg, unsafe { - mp4parse_get_track_video_info(parser, 3, &mut video) - }); - assert_eq!(video.display_width, 0); - assert_eq!(video.display_height, 0); - assert_eq!(video.sample_info_count, 0); - - let mut audio = Default::default(); - assert_eq!(Mp4parseStatus::BadArg, unsafe { - mp4parse_get_track_audio_info(parser, 3, &mut audio) - }); - assert_eq!(audio.sample_info_count, 0); - - unsafe { - mp4parse_free(parser); - } -} diff --git a/lib/mp4/mp4parse_ffi.h b/lib/mp4/mp4parse_ffi.h deleted file mode 100644 index f26e707..0000000 --- a/lib/mp4/mp4parse_ffi.h +++ /dev/null @@ -1,569 +0,0 @@ -/* 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 http://mozilla.org/MPL/2.0/. */ - -/* Generated with cbindgen:0.24.3 */ - -/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */ -#ifndef mp4parse_rust_mp4parse_h -#error "Don't include this file directly, instead include mp4parse.h" -#endif - - -#include <stdarg.h> -#include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> - -/** - * The axis about which the image is mirrored (opposite of flip) - * Visualized in terms of starting with (⥠) UPWARDS HARPOON WITH BARB LEFT FROM BAR - * similar to a DIGIT ONE (1) - */ -typedef enum Mp4parseImir { - /** - * top and bottom parts exchanged - * ⥡ DOWNWARDS HARPOON WITH BARB LEFT FROM BAR - */ - MP4PARSE_IMIR_TOP_BOTTOM, - /** - * left and right parts exchanged - * ⥜ UPWARDS HARPOON WITH BARB RIGHT FROM BAR - */ - MP4PARSE_IMIR_LEFT_RIGHT, -} Mp4parseImir; - -/** - * Rotation in the positive (that is, anticlockwise) direction - * Visualized in terms of starting with (⥠) UPWARDS HARPOON WITH BARB LEFT FROM BAR - * similar to a DIGIT ONE (1) - */ -typedef enum Mp4parseIrot { - /** - * ⥠ UPWARDS HARPOON WITH BARB LEFT FROM BAR - */ - MP4PARSE_IROT_D0, - /** - * ⥞ LEFTWARDS HARPOON WITH BARB DOWN FROM BAR - */ - MP4PARSE_IROT_D90, - /** - * ⥝ DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR - */ - MP4PARSE_IROT_D180, - /** - * ⥛ RIGHTWARDS HARPOON WITH BARB UP FROM BAR - */ - MP4PARSE_IROT_D270, -} Mp4parseIrot; - -typedef enum Mp4ParseEncryptionSchemeType { - MP4_PARSE_ENCRYPTION_SCHEME_TYPE_NONE, - MP4_PARSE_ENCRYPTION_SCHEME_TYPE_CENC, - MP4_PARSE_ENCRYPTION_SCHEME_TYPE_CBC1, - MP4_PARSE_ENCRYPTION_SCHEME_TYPE_CENS, - MP4_PARSE_ENCRYPTION_SCHEME_TYPE_CBCS, -} Mp4ParseEncryptionSchemeType; - -typedef enum Mp4parseCodec { - MP4PARSE_CODEC_UNKNOWN, - MP4PARSE_CODEC_AAC, - MP4PARSE_CODEC_FLAC, - MP4PARSE_CODEC_OPUS, - MP4PARSE_CODEC_AVC, - MP4PARSE_CODEC_VP9, - MP4PARSE_CODEC_AV1, - MP4PARSE_CODEC_MP3, - MP4PARSE_CODEC_MP4V, - MP4PARSE_CODEC_JPEG, - MP4PARSE_CODEC_AC3, - MP4PARSE_CODEC_EC3, - MP4PARSE_CODEC_ALAC, - MP4PARSE_CODEC_H263, -#if defined(MP4PARSE_FEATURE_3GPP) - MP4PARSE_CODEC_AMRNB, -#endif -#if defined(MP4PARSE_FEATURE_3GPP) - MP4PARSE_CODEC_AMRWB, -#endif - MP4PARSE_CODEC_CRAW, -} Mp4parseCodec; - -typedef enum Mp4parseTrackType { - MP4PARSE_TRACK_TYPE_VIDEO = 0, - MP4PARSE_TRACK_TYPE_AUDIO = 1, - MP4PARSE_TRACK_TYPE_METADATA = 2, -} Mp4parseTrackType; - -typedef enum Mp4parseStrictness { - MP4PARSE_STRICTNESS_PERMISSIVE, - MP4PARSE_STRICTNESS_NORMAL, - MP4PARSE_STRICTNESS_STRICT, -} Mp4parseStrictness; - -/** - * The return value to the C API - * Any detail that needs to be communicated to the caller must be encoded here - * since the [`Error`] type's associated data is part of the FFI. - */ -typedef enum Mp4parseStatus { - MP4PARSE_STATUS_OK = 0, - MP4PARSE_STATUS_BAD_ARG = 1, - MP4PARSE_STATUS_INVALID = 2, - MP4PARSE_STATUS_UNSUPPORTED = 3, - MP4PARSE_STATUS_EOF = 4, - MP4PARSE_STATUS_IO = 5, - MP4PARSE_STATUS_OOM = 6, - MP4PARSE_STATUS_UNSUPPORTED_A1LX, - MP4PARSE_STATUS_UNSUPPORTED_A1OP, - MP4PARSE_STATUS_UNSUPPORTED_CLAP, - MP4PARSE_STATUS_UNSUPPORTED_GRID, - MP4PARSE_STATUS_UNSUPPORTED_IPRO, - MP4PARSE_STATUS_UNSUPPORTED_LSEL, -} Mp4parseStatus; - -typedef struct Mp4parseAvifParser Mp4parseAvifParser; - -typedef struct Mp4parseParser Mp4parseParser; - -typedef struct Mp4parseIo { - intptr_t (*read)(uint8_t *buffer, uintptr_t size, void *userdata); - void *userdata; -} Mp4parseIo; - -#if defined(MP4PARSE_UNSTABLE_API) -/** - * A zero-overhead wrapper around integer types for the sake of always - * requiring checked arithmetic - */ -typedef int64_t CheckedInteger_i64; -#endif - -typedef struct Mp4parseTrackInfo { - enum Mp4parseTrackType track_type; - uint32_t track_id; - uint64_t duration; - CheckedInteger_i64 media_time; -} Mp4parseTrackInfo; - -#if defined(MP4PARSE_UNSTABLE_API) -/** - * A zero-overhead wrapper around integer types for the sake of always - * requiring checked arithmetic - */ -typedef uint64_t CheckedInteger_u64; -#endif - -#if defined(MP4PARSE_UNSTABLE_API) -/** - * Provides the following information about a sample in the source file: - * sample data offset (start and end), composition time in microseconds - * (start and end) and whether it is a sync sample - */ -typedef struct Mp4parseIndice { - /** - * The byte offset in the file where the indexed sample begins. - */ - CheckedInteger_u64 start_offset; - /** - * The byte offset in the file where the indexed sample ends. This is - * equivalent to `start_offset` + the length in bytes of the indexed - * sample. Typically this will be the `start_offset` of the next sample - * in the file. - */ - CheckedInteger_u64 end_offset; - /** - * The time in microseconds when the indexed sample should be displayed. - * Analogous to the concept of presentation time stamp (pts). - */ - CheckedInteger_i64 start_composition; - /** - * The time in microseconds when the indexed sample should stop being - * displayed. Typically this would be the `start_composition` time of the - * next sample if samples were ordered by composition time. - */ - CheckedInteger_i64 end_composition; - /** - * The time in microseconds that the indexed sample should be decoded at. - * Analogous to the concept of decode time stamp (dts). - */ - CheckedInteger_i64 start_decode; - /** - * Set if the indexed sample is a sync sample. The meaning of sync is - * somewhat codec specific, but essentially amounts to if the sample is a - * key frame. - */ - bool sync; -} Mp4parseIndice; -#endif - -typedef struct Mp4parseByteData { - uintptr_t length; - const uint8_t *data; - const struct Mp4parseIndice *indices; -} Mp4parseByteData; - -enum OptionalFourCc_Tag -#ifdef __cplusplus - : uint8_t -#endif // __cplusplus - { - OPTIONAL_FOUR_CC_NONE, - OPTIONAL_FOUR_CC_SOME, -}; -#ifndef __cplusplus -typedef uint8_t OptionalFourCc_Tag; -#endif // __cplusplus - -typedef union OptionalFourCc { - OptionalFourCc_Tag tag; - struct { - OptionalFourCc_Tag some_tag; - uint8_t some[4]; - }; -} OptionalFourCc; - -typedef struct Mp4parseSinfInfo { - union OptionalFourCc original_format; - enum Mp4ParseEncryptionSchemeType scheme_type; - uint8_t is_encrypted; - uint8_t iv_size; - struct Mp4parseByteData kid; - uint8_t crypt_byte_block; - uint8_t skip_byte_block; - struct Mp4parseByteData constant_iv; -} Mp4parseSinfInfo; - -typedef struct Mp4parseTrackAudioSampleInfo { - enum Mp4parseCodec codec_type; - uint16_t channels; - uint16_t bit_depth; - uint32_t sample_rate; - uint16_t profile; - uint16_t extended_profile; - struct Mp4parseByteData codec_specific_config; - struct Mp4parseByteData extra_data; - struct Mp4parseSinfInfo protected_data; -} Mp4parseTrackAudioSampleInfo; - -typedef struct Mp4parseTrackAudioInfo { - uint32_t sample_info_count; - const struct Mp4parseTrackAudioSampleInfo *sample_info; -} Mp4parseTrackAudioInfo; - -typedef struct Mp4parseTrackRawInfo { - uint16_t image_width; - uint16_t image_height; - bool is_jpeg; - uint64_t offset; - uint64_t size; -} Mp4parseTrackRawInfo; - -typedef struct Mp4parseTrackVideoSampleInfo { - enum Mp4parseCodec codec_type; - uint16_t image_width; - uint16_t image_height; - struct Mp4parseByteData extra_data; - struct Mp4parseSinfInfo protected_data; -} Mp4parseTrackVideoSampleInfo; - -typedef struct Mp4parseTrackVideoInfo { - uint32_t display_width; - uint32_t display_height; - uint16_t rotation; - uint32_t sample_info_count; - const struct Mp4parseTrackVideoSampleInfo *sample_info; -} Mp4parseTrackVideoInfo; - -typedef struct Mp4parseAvifImageItem { - struct Mp4parseByteData coded_data; - struct Mp4parseByteData bits_per_channel; -} Mp4parseAvifImageItem; - -typedef struct Mp4parseImageSpatialExtents { - uint32_t image_width; - uint32_t image_height; -} Mp4parseImageSpatialExtents; - -/** - * Despite [Rec. ITU-T H.273] (12/2016) defining the CICP fields as having a - * range of 0-255, and only a small fraction of those values being used, - * ISOBMFF (ISO 14496-12:2020) § 12.1.5 defines them as 16-bit values in the - * `colr` box. Since we have no use for the additional range, and it would - * complicate matters later, we fallibly convert before storing the input. - * - * [Rec. ITU-T H.273]: https://www.itu.int/rec/T-REC-H.273-201612-I/en - */ -typedef struct Mp4parseNclxColourInformation { - uint8_t colour_primaries; - uint8_t transfer_characteristics; - uint8_t matrix_coefficients; - bool full_range_flag; -} Mp4parseNclxColourInformation; - -typedef struct Mp4parseAvifImage { - struct Mp4parseAvifImageItem primary_image; - /** - * The size of the image; should never be null unless using permissive parsing - */ - const struct Mp4parseImageSpatialExtents *spatial_extents; - const struct Mp4parseNclxColourInformation *nclx_colour_information; - struct Mp4parseByteData icc_colour_information; - enum Mp4parseIrot image_rotation; - const enum Mp4parseImir *image_mirror; - /** - * If no alpha item exists, members' `.length` will be 0 and `.data` will be null - */ - struct Mp4parseAvifImageItem alpha_image; - bool premultiplied_alpha; -} Mp4parseAvifImage; - -typedef struct Mp4parseFragmentInfo { - uint64_t fragment_duration; -} Mp4parseFragmentInfo; - -typedef struct Mp4parsePsshInfo { - struct Mp4parseByteData data; -} Mp4parsePsshInfo; - -typedef struct Mp4parseCrawHeader { - struct Mp4parseByteData cncv; - uint16_t thumb_w; - uint16_t thumb_h; - struct Mp4parseByteData thumbnail; - struct Mp4parseByteData meta1; - struct Mp4parseByteData meta2; - struct Mp4parseByteData meta3; - struct Mp4parseByteData meta4; -} Mp4parseCrawHeader; - -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -/** - * Allocate an `Mp4parseParser*` to read from the supplied `Mp4parseIo` and - * parse the content from the `Mp4parseIo` argument until EOF or error. - * - * # Safety - * - * This function is unsafe because it dereferences the `io` and `parser_out` - * pointers given to it. The caller should ensure that the `Mp4ParseIo` - * struct passed in is a valid pointer. The caller should also ensure the - * members of io are valid: the `read` function should be sanely implemented, - * and the `userdata` pointer should be valid. The `parser_out` should be a - * valid pointer to a location containing a null pointer. Upon successful - * return (`Mp4parseStatus::Ok`), that location will contain the address of - * an `Mp4parseParser` allocated by this function. - * - * To avoid leaking memory, any successful return of this function must be - * paired with a call to `mp4parse_free`. In the event of error, no memory - * will be allocated and `mp4parse_free` must *not* be called. - */ -Mp4parseStatus mp4parse_new(const struct Mp4parseIo *io, struct Mp4parseParser **parser_out); - -/** - * Allocate an `Mp4parseAvifParser*` to read from the supplied `Mp4parseIo`. - * - * See mp4parse_new; this function is identical except that it allocates an - * `Mp4parseAvifParser`, which (when successful) must be paired with a call - * to mp4parse_avif_free. - * - * # Safety - * - * Same as mp4parse_new. - */ -Mp4parseStatus mp4parse_avif_new(const struct Mp4parseIo *io, - enum Mp4parseStrictness strictness, - struct Mp4parseAvifParser **parser_out); - -/** - * Free an `Mp4parseParser*` allocated by `mp4parse_new()`. - * - * # Safety - * - * This function is unsafe because it creates a box from a raw pointer. - * Callers should ensure that the parser pointer points to a valid - * `Mp4parseParser` created by `mp4parse_new`. - */ -void mp4parse_free(struct Mp4parseParser *parser); - -/** - * Free an `Mp4parseAvifParser*` allocated by `mp4parse_avif_new()`. - * - * # Safety - * - * This function is unsafe because it creates a box from a raw pointer. - * Callers should ensure that the parser pointer points to a valid - * `Mp4parseAvifParser` created by `mp4parse_avif_new`. - */ -void mp4parse_avif_free(struct Mp4parseAvifParser *parser); - -/** - * Return the number of tracks parsed by previous `mp4parse_read()` call. - * - * # Safety - * - * This function is unsafe because it dereferences both the parser and count - * raw pointers passed into it. Callers should ensure the parser pointer - * points to a valid `Mp4parseParser`, and that the count pointer points an - * appropriate memory location to have a `u32` written to. - */ -Mp4parseStatus mp4parse_get_track_count(const struct Mp4parseParser *parser, uint32_t *count); - -/** - * Fill the supplied `Mp4parseTrackInfo` with metadata for `track`. - * - * # Safety - * - * This function is unsafe because it dereferences the the parser and info raw - * pointers passed to it. Callers should ensure the parser pointer points to a - * valid `Mp4parseParser` and that the info pointer points to a valid - * `Mp4parseTrackInfo`. - */ -Mp4parseStatus mp4parse_get_track_info(struct Mp4parseParser *parser, - uint32_t track_index, - struct Mp4parseTrackInfo *info); - -/** - * Fill the supplied `Mp4parseTrackAudioInfo` with metadata for `track`. - * - * # Safety - * - * This function is unsafe because it dereferences the the parser and info raw - * pointers passed to it. Callers should ensure the parser pointer points to a - * valid `Mp4parseParser` and that the info pointer points to a valid - * `Mp4parseTrackAudioInfo`. - */ -Mp4parseStatus mp4parse_get_track_audio_info(struct Mp4parseParser *parser, - uint32_t track_index, - struct Mp4parseTrackAudioInfo *info); - -/** - * File the supplied `Mp4parseTrackRawInfo` with metadata for `track`. - * - * # Safety - * This function dereference `parser` and `info`. If they are null, - * `Mp4parseStatus::BadArg` is returned. - */ -Mp4parseStatus mp4parse_get_track_raw_info(struct Mp4parseParser *parser, - uint32_t track_index, - struct Mp4parseTrackRawInfo *info); - -/** - * Fill the supplied `Mp4parseTrackVideoInfo` with metadata for `track`. - * - * # Safety - * - * This function is unsafe because it dereferences the the parser and info raw - * pointers passed to it. Callers should ensure the parser pointer points to a - * valid `Mp4parseParser` and that the info pointer points to a valid - * `Mp4parseTrackVideoInfo`. - */ -Mp4parseStatus mp4parse_get_track_video_info(struct Mp4parseParser *parser, - uint32_t track_index, - struct Mp4parseTrackVideoInfo *info); - -/** - * Return a pointer to the primary item parsed by previous `mp4parse_avif_new()` call. - * - * # Safety - * - * This function is unsafe because it dereferences both the parser and - * avif_image raw pointers passed into it. Callers should ensure the parser - * pointer points to a valid `Mp4parseAvifParser`, and that the avif_image - * pointer points to a valid `Mp4parseAvifImage`. If there was not a previous - * successful call to `mp4parse_avif_read()`, no guarantees are made as to - * the state of `avif_image`. If `avif_image.alpha_image.coded_data` is set to - * a positive `length` and non-null `data`, then the `avif_image` contains a - * valid alpha channel data. Otherwise, the image is opaque. - */ -Mp4parseStatus mp4parse_avif_get_image(const struct Mp4parseAvifParser *parser, - struct Mp4parseAvifImage *avif_image); - -/** - * Fill the supplied `Mp4parseByteData` with index information from `track`. - * - * # Safety - * - * This function is unsafe because it dereferences the the parser and indices - * raw pointers passed to it. Callers should ensure the parser pointer points - * to a valid `Mp4parseParser` and that the indices pointer points to a valid - * `Mp4parseByteData`. - */ -Mp4parseStatus mp4parse_get_indice_table(struct Mp4parseParser *parser, - uint32_t track_id, - struct Mp4parseByteData *indices); - -/** - * Fill the supplied `Mp4parseFragmentInfo` with metadata from fragmented file. - * - * # Safety - * - * This function is unsafe because it dereferences the the parser and - * info raw pointers passed to it. Callers should ensure the parser - * pointer points to a valid `Mp4parseParser` and that the info pointer points - * to a valid `Mp4parseFragmentInfo`. - */ -Mp4parseStatus mp4parse_get_fragment_info(struct Mp4parseParser *parser, - struct Mp4parseFragmentInfo *info); - -/** - * Determine if an mp4 file is fragmented. A fragmented file needs mvex table - * and contains no data in stts, stsc, and stco boxes. - * - * # Safety - * - * This function is unsafe because it dereferences the the parser and - * fragmented raw pointers passed to it. Callers should ensure the parser - * pointer points to a valid `Mp4parseParser` and that the fragmented pointer - * points to an appropriate memory location to have a `u8` written to. - */ -Mp4parseStatus mp4parse_is_fragmented(struct Mp4parseParser *parser, - uint32_t track_id, - uint8_t *fragmented); - -/** - * Get 'pssh' system id and 'pssh' box content for eme playback. - * - * The data format of the `info` struct passed to gecko is: - * - * - system id (16 byte uuid) - * - pssh box size (32-bit native endian) - * - pssh box content (including header) - * - * # Safety - * - * This function is unsafe because it dereferences the the parser and - * info raw pointers passed to it. Callers should ensure the parser - * pointer points to a valid `Mp4parseParser` and that the fragmented pointer - * points to a valid `Mp4parsePsshInfo`. - */ -Mp4parseStatus mp4parse_get_pssh_info(struct Mp4parseParser *parser, struct Mp4parsePsshInfo *info); - -/** - * Get the Craw hader for the file. - * - * # Safety - * This function is unsafe as it derefenece the Parser - * and the CrawHeader - */ -Mp4parseStatus mp4parse_get_craw_header(struct Mp4parseParser *parser, - struct Mp4parseCrawHeader *header); - -/** - * Get the Craw table entry - * - * # Safety - * The function dereference the parser, and return offset - * and size by pointer. They can't be null, otherwise - * `Mp4parseStatus::BadArg` is returned. - */ -Mp4parseStatus mp4parse_get_craw_table_entry(struct Mp4parseParser *parser, - uintptr_t idx, - uint64_t *offset, - uint64_t *size); - -#ifdef __cplusplus -} // extern "C" -#endif // __cplusplus diff --git a/lib/mp4/update-rust.sh b/lib/mp4/update-rust.sh deleted file mode 100755 index f441aee..0000000 --- a/lib/mp4/update-rust.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/bin/sh -e -# Script to update mp4parse-rust sources to latest upstream -# Copiesd from -# https://hg.mozilla.org/mozilla-central/raw-file/tip/media/mp4parse-rust/update-rust.sh - -which -s cbindgen - -# Default version. -VER="fe7852f59255a73303c1c1dc6a1cf36bf2310a8a" - -# Accept version or commit from the command line. -if test -n "$1"; then - VER=$1 -fi - -echo "Fetching sources..." -rm -rf _upstream -git clone --recurse-submodules https://github.com/hfiguiere/mp4parse-rust _upstream/mp4parse -# git clone https://github.com/alfredoyang/mp4parse_fallible _upstream/mp4parse_fallible -pushd _upstream/mp4parse -git checkout ${VER} -echo "Verifying sources..." -cargo build -#cargo test -cd mp4parse_capi && cbindgen --output mp4parse_ffi.h -popd -rm -rf mp4parse -mkdir -p mp4parse/src -cp _upstream/mp4parse/mp4parse/Cargo.toml mp4parse/ -cp _upstream/mp4parse/mp4parse/src/*.rs mp4parse/src/ -mkdir -p mp4parse/tests -cp _upstream/mp4parse/mp4parse/tests/*.rs mp4parse/tests/ -cp _upstream/mp4parse/mp4parse/tests/*.mp4 mp4parse/tests/ -cp _upstream/mp4parse/mp4parse_capi/mp4parse_ffi.h . - - -# Remove everything but the cbindgen.toml, since it's needed to configure the -# creation of the bindings as part of moz.build -find mp4parse_capi -not -name cbindgen.toml -delete -mkdir -p mp4parse_capi/src -cp _upstream/mp4parse/mp4parse_capi/Cargo.toml mp4parse_capi/ -cp _upstream/mp4parse/mp4parse_capi/src/*.rs mp4parse_capi/src/ - -echo "Cleaning up..." -rm -rf _upstream - -#echo "Updating gecko Cargo.lock..." -#pushd ../../toolkit/library/rust/ -#cargo update --package mp4parse_capi -#popd -#pushd ../../toolkit/library/gtest/rust/ -#cargo update --package mp4parse_capi -#popd - -echo "Updated to ${VER}." |