summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorHubert Figuière <hub@figuiere.net>2024-01-06 18:45:40 -0500
committerHubert Figuière <hub@figuiere.net>2024-01-06 18:46:40 -0500
commitcb721e556ef996a39d8ec002f87788adc7a3f6c4 (patch)
treeed60d3701eb2d05ffdc6a7fb506bff30cb67546a /lib
parent8b2e12d993a0aec74367982371d3c51113e809c7 (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')
-rw-r--r--lib/Makefile.am20
-rw-r--r--lib/mp4/Cargo.lock266
-rw-r--r--lib/mp4/Cargo.toml18
-rw-r--r--lib/mp4/build-rs.patch27
-rw-r--r--lib/mp4/lib.rs1
-rw-r--r--lib/mp4/mp4parse-cargo.patch58
-rw-r--r--lib/mp4/mp4parse.h10
-rw-r--r--lib/mp4/mp4parse/Cargo.toml51
-rw-r--r--lib/mp4/mp4parse/README.md12
-rw-r--r--lib/mp4/mp4parse/src/boxes.rs251
-rw-r--r--lib/mp4/mp4parse/src/craw.rs154
-rw-r--r--lib/mp4/mp4parse/src/lib.rs5320
-rw-r--r--lib/mp4/mp4parse/src/macros.rs12
-rw-r--r--lib/mp4/mp4parse/src/tests.rs1330
-rw-r--r--lib/mp4/mp4parse/src/unstable.rs546
-rw-r--r--lib/mp4/mp4parse/tests/bipbop-cenc-audioinit.mp4bin1000 -> 0 bytes
-rw-r--r--lib/mp4/mp4parse/tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4bin1094 -> 0 bytes
-rw-r--r--lib/mp4/mp4parse/tests/minimal.mp4bin2591 -> 0 bytes
-rw-r--r--lib/mp4/mp4parse/tests/overflow.rs15
-rw-r--r--lib/mp4/mp4parse/tests/public.rs1215
-rw-r--r--lib/mp4/mp4parse_capi/Cargo.toml42
-rw-r--r--lib/mp4/mp4parse_capi/src/lib.rs1777
-rw-r--r--lib/mp4/mp4parse_ffi.h569
-rwxr-xr-xlib/mp4/update-rust.sh55
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
deleted file mode 100644
index b827aa4..0000000
--- a/lib/mp4/mp4parse/tests/bipbop-cenc-audioinit.mp4
+++ /dev/null
Binary files differ
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
deleted file mode 100644
index 5db21d0..0000000
--- a/lib/mp4/mp4parse/tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4
+++ /dev/null
Binary files differ
diff --git a/lib/mp4/mp4parse/tests/minimal.mp4 b/lib/mp4/mp4parse/tests/minimal.mp4
deleted file mode 100644
index 9fe1e67..0000000
--- a/lib/mp4/mp4parse/tests/minimal.mp4
+++ /dev/null
Binary files differ
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}."