Import avif-parse 1.0.0 upstream upstream/1.0.0
authorDongHun Kwak <dh0128.kwak@samsung.com>
Wed, 19 Apr 2023 05:36:49 +0000 (14:36 +0900)
committerDongHun Kwak <dh0128.kwak@samsung.com>
Wed, 19 Apr 2023 05:36:49 +0000 (14:36 +0900)
14 files changed:
.cargo_vcs_info.json [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
Cargo.toml.orig [new file with mode: 0644]
LICENSE [new file with mode: 0644]
README.md [new file with mode: 0644]
av1-avif/LICENSE [new file with mode: 0755]
av1-avif/README.md [new file with mode: 0755]
av1-avif/testFiles/Link-U/README.md [new file with mode: 0644]
avif_parse.h [new file with mode: 0644]
src/boxes.rs [new file with mode: 0644]
src/c_api.rs [new file with mode: 0644]
src/lib.rs [new file with mode: 0644]
src/macros.rs [new file with mode: 0644]
src/tests.rs [new file with mode: 0644]

diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644 (file)
index 0000000..efd23f4
--- /dev/null
@@ -0,0 +1,6 @@
+{
+  "git": {
+    "sha1": "2174e0e15647918cbbcb965ba142c285a6ef457f"
+  },
+  "path_in_vcs": ""
+}
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644 (file)
index 0000000..52f98f6
--- /dev/null
@@ -0,0 +1,55 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies.
+#
+# If you are reading this file be aware that the original Cargo.toml
+# will likely look very different (and much more reasonable).
+# See Cargo.toml.orig for the original contents.
+
+[package]
+edition = "2021"
+name = "avif-parse"
+version = "1.0.0"
+authors = ["Ralph Giles <giles@mozilla.com>", "Matthew Gregan <kinetik@flim.org>", "Alfredo Yang <ayang@mozilla.com>", "Jon Bauman <jbauman@mozilla.com>", "Kornel Lesiński <kornel@geekhood.net>"]
+include = ["README.md", "Cargo.toml", "LICENSE", "/src/*.rs", "avif_parse.h"]
+description = "Parser for AVIF image files"
+documentation = "https://docs.rs/avif-parse/"
+readme = "README.md"
+keywords = ["demuxer", "image", "parser", "heif"]
+categories = ["multimedia::images"]
+license = "MPL-2.0"
+repository = "https://github.com/kornelski/avif-parse"
+resolver = "2"
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[lib]
+crate-type = ["rlib", "staticlib"]
+[dependencies.bitreader]
+version = "0.3.5"
+
+[dependencies.byteorder]
+version = "1.4.3"
+
+[dependencies.fallible_collections]
+version = "0.4.4"
+features = ["std_io"]
+
+[dependencies.log]
+version = "0.4.14"
+
+[dependencies.static_assertions]
+version = "1.1.0"
+[dev-dependencies.env_logger]
+version = "0.9.0"
+
+[dev-dependencies.walkdir]
+version = "2.3.2"
+
+[features]
+mp4parse_fallible = []
+[badges.maintenance]
+status = "passively-maintained"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644 (file)
index 0000000..3b5a68a
--- /dev/null
@@ -0,0 +1,45 @@
+[package]
+name = "avif-parse"
+version = "1.0.0"
+authors = [
+  "Ralph Giles <giles@mozilla.com>",
+  "Matthew Gregan <kinetik@flim.org>",
+  "Alfredo Yang <ayang@mozilla.com>",
+  "Jon Bauman <jbauman@mozilla.com>",
+  "Kornel Lesiński <kornel@geekhood.net>",
+]
+edition = "2021"
+description = "Parser for AVIF image files"
+documentation = "https://docs.rs/avif-parse/"
+license = "MPL-2.0"
+categories = ["multimedia::images"]
+repository = "https://github.com/kornelski/avif-parse"
+readme = "README.md"
+include = ["README.md", "Cargo.toml", "LICENSE", "/src/*.rs", "avif_parse.h"]
+keywords = ["demuxer", "image", "parser", "heif"]
+
+[lib]
+crate-type = ["rlib", "staticlib"]
+
+[dependencies]
+byteorder = "1.4.3"
+bitreader = "0.3.5"
+log = "0.4.14"
+static_assertions = "1.1.0"
+fallible_collections = { version = "0.4.4", features = ["std_io"] }
+
+[dev-dependencies]
+env_logger = "0.9.0"
+walkdir = "2.3.2"
+
+[features]
+# 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 = []
+
+[badges]
+maintenance = { status = "passively-maintained" }
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..14e2f77
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+    means each individual or legal entity that creates, contributes to
+    the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+    means the combination of the Contributions of others (if any) used
+    by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+    means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+    means Source Code Form to which the initial Contributor has attached
+    the notice in Exhibit A, the Executable Form of such Source Code
+    Form, and Modifications of such Source Code Form, in each case
+    including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+    means
+
+    (a) that the initial Contributor has attached the notice described
+        in Exhibit B to the Covered Software; or
+
+    (b) that the Covered Software was made available under the terms of
+        version 1.1 or earlier of the License, but not also under the
+        terms of a Secondary License.
+
+1.6. "Executable Form"
+    means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+    means a work that combines Covered Software with other material, in 
+    a separate file or files, that is not Covered Software.
+
+1.8. "License"
+    means this document.
+
+1.9. "Licensable"
+    means having the right to grant, to the maximum extent possible,
+    whether at the time of the initial grant or subsequently, any and
+    all of the rights conveyed by this License.
+
+1.10. "Modifications"
+    means any of the following:
+
+    (a) any file in Source Code Form that results from an addition to,
+        deletion from, or modification of the contents of Covered
+        Software; or
+
+    (b) any new file in Source Code Form that contains any Covered
+        Software.
+
+1.11. "Patent Claims" of a Contributor
+    means any patent claim(s), including without limitation, method,
+    process, and apparatus claims, in any patent Licensable by such
+    Contributor that would be infringed, but for the grant of the
+    License, by the making, using, selling, offering for sale, having
+    made, import, or transfer of either its Contributions or its
+    Contributor Version.
+
+1.12. "Secondary License"
+    means either the GNU General Public License, Version 2.0, the GNU
+    Lesser General Public License, Version 2.1, the GNU Affero General
+    Public License, Version 3.0, or any later versions of those
+    licenses.
+
+1.13. "Source Code Form"
+    means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+    means an individual or a legal entity exercising rights under this
+    License. For legal entities, "You" includes any entity that
+    controls, is controlled by, or is under common control with You. For
+    purposes of this definition, "control" means (a) the power, direct
+    or indirect, to cause the direction or management of such entity,
+    whether by contract or otherwise, or (b) ownership of more than
+    fifty percent (50%) of the outstanding shares or beneficial
+    ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+    Licensable by such Contributor to use, reproduce, make available,
+    modify, display, perform, distribute, and otherwise exploit its
+    Contributions, either on an unmodified basis, with Modifications, or
+    as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+    for sale, have made, import, and otherwise transfer either its
+    Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+    or
+
+(b) for infringements caused by: (i) Your and any other third party's
+    modifications of Covered Software, or (ii) the combination of its
+    Contributions with other software (except as part of its Contributor
+    Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+    its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+    Form, as described in Section 3.1, and You must inform recipients of
+    the Executable Form how they can obtain a copy of such Source Code
+    Form by reasonable means in a timely manner, at a charge no more
+    than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+    License, or sublicense it under different terms, provided that the
+    license for the Executable Form does not attempt to limit or alter
+    the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+*                                                                      *
+*  6. Disclaimer of Warranty                                           *
+*  -------------------------                                           *
+*                                                                      *
+*  Covered Software is provided under this License on an "as is"       *
+*  basis, without warranty of any kind, either expressed, implied, or  *
+*  statutory, including, without limitation, warranties that the       *
+*  Covered Software is free of defects, merchantable, fit for a        *
+*  particular purpose or non-infringing. The entire risk as to the     *
+*  quality and performance of the Covered Software is with You.        *
+*  Should any Covered Software prove defective in any respect, You     *
+*  (not any Contributor) assume the cost of any necessary servicing,   *
+*  repair, or correction. This disclaimer of warranty constitutes an   *
+*  essential part of this License. No use of any Covered Software is   *
+*  authorized under this License except under this disclaimer.         *
+*                                                                      *
+************************************************************************
+
+************************************************************************
+*                                                                      *
+*  7. Limitation of Liability                                          *
+*  --------------------------                                          *
+*                                                                      *
+*  Under no circumstances and under no legal theory, whether tort      *
+*  (including negligence), contract, or otherwise, shall any           *
+*  Contributor, or anyone who distributes Covered Software as          *
+*  permitted above, be liable to You for any direct, indirect,         *
+*  special, incidental, or consequential damages of any character      *
+*  including, without limitation, damages for lost profits, loss of    *
+*  goodwill, work stoppage, computer failure or malfunction, or any    *
+*  and all other commercial damages or losses, even if such party      *
+*  shall have been informed of the possibility of such damages. This   *
+*  limitation of liability shall not apply to liability for death or   *
+*  personal injury resulting from such party's negligence to the       *
+*  extent applicable law prohibits such limitation. Some               *
+*  jurisdictions do not allow the exclusion or limitation of           *
+*  incidental or consequential damages, so this exclusion and          *
+*  limitation may not apply to You.                                    *
+*                                                                      *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+  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/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+  This Source Code Form is "Incompatible With Secondary Licenses", as
+  defined by the Mozilla Public License, v. 2.0.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..94d278d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,53 @@
+# AVIF file structure parser (demuxer)
+
+Get AV1 payload and alpha channel metadata out of AVIF image files. The parser is a fork of Mozilla's MP4 parser used in Firefox, so it's designed to be robust and safely handle untrusted data.
+
+The parser is compatible with files supported by libavif, Chrome 85 and Firefox 81a.
+
+[API documentation](https://docs.rs/avif-parse/)
+
+This crate doesn't include an AV1 decoder. To display the pixels you will additionally need [dav1d](https://code.videolan.org/videolan/dav1d) or [libaom](//lib.rs/libaom-sys).
+
+## Usage from Rust
+
+It takes `io::Read`, so you can use any readable input, such as a byte slice (`vec.as_slice()`), or a `File` wrapped in `BufReader`, etc.
+
+```rust
+let data = read_avif(&mut slice)?;
+av1_decode(&data.primary_item)?;
+if let Some(alpha) = &data.alpha_item {
+    av1_decode(alpha)?;
+}
+if data.premultiplied_alpha {
+    // after decoding, remember to divide R,G,B values by A
+}
+```
+
+## Usage from C
+
+Install Rust 1.45 or later, preferably via [rustup](//rustup.rs), and run:
+
+```bash
+cargo build --release
+```
+
+It will build `./target/release/libavif_parse.a` (or `avif_parse.lib` on Windows). Link it with your project.
+
+Cargo supports cross-compilation, so you can easily build it for other platforms (e.g. iOS).
+
+```c
+#include "avif_parse.h"
+avif_data_t data = avif_parse(file_data, file_length);
+
+if (data) {
+    av1_decode(data.primary_data, data.primary_size);
+    if (data.alpha_data) {
+        av1_decode(data.alpha_data, data.alpha_size);
+    }
+    if (data.premultiplied_alpha) {
+        // after decoding, remember to divide R,G,B values by A
+    }
+}
+
+avif_data_free(data);
+```
diff --git a/av1-avif/LICENSE b/av1-avif/LICENSE
new file mode 100755 (executable)
index 0000000..70a47fe
--- /dev/null
@@ -0,0 +1,25 @@
+BSD 2-Clause License
+
+Copyright (c) 2017, AOMediaCodec
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this
+  list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice,
+  this list of conditions and the following disclaimer in the documentation
+  and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/av1-avif/README.md b/av1-avif/README.md
new file mode 100755 (executable)
index 0000000..724e48f
--- /dev/null
@@ -0,0 +1,15 @@
+# av1-avif
+
+This document describes how to use ISO-BMFF structures to generate a HEIF/MIAF
+compatible file that contains one or more still images encoded using AV1.
+
+The specification is written using a special syntax (mixing markup and markdown)
+to enable generation of cross-references, syntax highlighting, ...
+The file using this syntax is index.bs.
+
+index.bs is processed to produce an HTML version (index.html) by a tool
+called Bikeshed (https://github.com/tabatkins/bikeshed).
+
+The repository contains both the input and the result of the Bikeshed processing.
+
+Pull requests should contain updates to both files index.html and index.bs, and possibly to other files (Bibliography biblio.json).
diff --git a/av1-avif/testFiles/Link-U/README.md b/av1-avif/testFiles/Link-U/README.md
new file mode 100644 (file)
index 0000000..243fa90
--- /dev/null
@@ -0,0 +1,232 @@
+# AVIF Example files from [Link-U](https://www.link-u.co.jp/)
+
+ - All files do not contain Exif metadata.
+ - All files are tagged as MIAF compatible.
+ - All files are tagged as compatible with the AVIF Baseline or Advanced Profile if possible.
+ - All images have the "reduced_still_picture_header" and "still_picture" flags set to 1 in the AV1 Sequence Header.
+ - All images are licensed under [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en).
+
+[Makefile](Makefile) describes how they were created. To generate files yourself, you have to install [cavif](https://github.com/link-u/cavif) and [davif](https://github.com/link-u/davif)
+
+Additional test images may to be added. Please check [our repositories](https://github.com/link-u/avif-sample-images) for them.
+
+## hato
+
+![hato.jpg](hato.jpg)
+
+ - size: 3082x2048
+ - Author: Kaede Fujisaki ([@ledyba](https://github.com/ledyba))
+ - Retrieved from [her website](https://hexe.net/2017/11/27/12:27:02/).
+
+### AVIF version
+
+#### YUV 420
+
+| profile | bit depth | Monochrome | CDEF | Loop Restoration | file                                                                      |
+|---------|-----------|------------|------|------------------|---------------------------------------------------------------------------|
+| 0       | 8         |            | NO   | YES              | [here](hato.profile0.8bpc.yuv420.no-cdef.avif)                            |
+| 0       | 8         | YES        | NO   | YES              | [here](hato.profile0.8bpc.yuv420.monochrome.no-cdef.avif)                 |
+| 0       | 10        |            | NO   | NO               | [here](hato.profile0.10bpc.yuv420.no-cdef.no-restoration.avif)            |
+| 0       | 10        | YES        | NO   | NO               | [here](hato.profile0.10bpc.yuv420.monochrome.no-cdef.no-restoration.avif) |
+
+#### YUV422
+
+| profile | bit depth | Monochrome | CDEF | Loop Restroation | file                                                                      |
+|---------|-----------|------------|------|------------------|---------------------------------------------------------------------------|
+| 1       | 8         |            | NO   | YES              | [here](hato.profile2.8bpc.yuv422.no-cdef.avif)                            |
+| 1       | 8         | YES        | NO   | YES              | [here](hato.profile2.8bpc.yuv422.monochrome.no-cdef.avif)                 |
+| 1       | 10        |            | NO   | NO               | [here](hato.profile2.10bpc.yuv422.no-cdef.no-restoration.avif)            |
+| 1       | 10        | YES        | NO   | NO               | [here](hato.profile2.10bpc.yuv422.monochrome.no-cdef.no-restoration.avif) |
+| 1       | 12        |            | NO   | NO               | [here](hato.profile2.12bpc.yuv422.no-cdef.no-restoration.avif)            |
+| 1       | 12        | YES        | NO   | NO               | [here](hato.profile2.12bpc.yuv422.monochrome.no-cdef.no-restoration.avif) |
+
+## Kimono - Transformation tests
+
+[<img src="https://raw.githubusercontent.com/link-u/avif-sample-images/master/kimono.jpg" alt="kimono.jpg" height="512">](kimono.jpg)
+
+ - size: 722x1024
+ - Authors: Momiji Jinzamomi([@momiji-san](https://github.com/momiji-san)) and Kaede Fujisaki ([@ledyba](https://github.com/ledyba))
+ - Retrieved from [their website](https://hexe.net/2018/12/24/18:59:01/).
+
+Test images for rotation(`irot`), mirroring(`imir`), cropping(`clap`).
+
+All AVIF images are encoded in these settings:
+
+ - Profile 0
+ - YUV420
+ - 8 bits per component
+
+### FYI: Transform operation order
+
+[MIAF](https://www.iso.org/standard/74417.html) defines the transform operation order(p.16):
+
+> These properties, if used, shall be indicated to be applied in the following order:  
+>  clean aperture first, then rotation, then mirror.
+
+### Identity
+
+[kimono.avif](./kimono.avif)
+
+No operation is applied.
+
+### Rotation 90
+
+[kimono.rotate90.avif](./kimono.rotate90.avif)
+
+[Encoded image is rotated at 90 degree in counter-clockwise](kimono.rotate90.png), and marked to rotate it 270 degree in counter-clockwise when displaying. Thus, resulted image is as the same as the original.
+
+### Rotation 270
+
+[kimono.rotate270.avif](./kimono.rotate270.avif)
+
+[Encoded image is rotated at 270 degree in counter-clockwise](kimono.rotate270.png), and marked to rotate it 90 degree in counter-clockwise when displaying. Thus, resulted image is as the same as the original.
+
+
+### Mirroring horizontally
+
+[kimono.mirror-horizontal.avif](./kimono.mirror-horizontal.avif)
+
+[Encoded image is mirrored horizontally](kimono.mirror-horizontal.png), and marked to mirror it horizontally again when displaying. Thus, resulted image is as the same as the original.
+
+### Mirroring vertically
+
+[kimono.mirror-vertical.avif](./kimono.mirror-vertical.avif)
+
+Vertical version. Same as above.
+
+### Mirroring vertically + Rotating at 90 degrees.
+
+[kimono.mirror-vertical.rotate270.avif](./kimono.mirror-vertical.rotate270.avif)
+
+[Encoded image is mirrored vertically, then rorated at 90 degree in clockwise](kimono.mirror-vertical.rotate270.png), and marked to rotate it at 90 degree in counter-clockwise and then mirror it vertically when displaying.
+
+Thus, resulted image is as the same as the original.
+
+### Cropping
+
+Displaying image will be cropped from the original image, using `CleanApertureBox`(See: ISO/IEC 14496-12:2015).
+
+Cropped under these condition:
+
+ - cleanApertureWidthN: 385
+ - cleanApertureWidthD: 1
+ - cleanApertureHeightN: 330
+ - cleanApertureHeightD: 1
+ - horizOffN: 103
+ - horizOffD: 1
+ - vertOffN: -308 (This can be negative, as mensioned in ISO/IEC 14496-12:2015).
+ - vertOffD: 1
+
+Resulted image should be:
+
+![kimono.crop.png](kimono.crop.png)
+
+### Cropping + Mirroring vertically + Rotating at 90 degrees.
+
+[Encoded image is mirrored vertically, then rorated at 90 degree in clockwise](kimono.mirror-vertical.rotate270.png), and marked to crop it first, rotate it at 90 degree in counter-clockwise, and then mirror it vertically.
+
+Cropping condition is:
+
+- cleanApertureWidthN: 330
+- cleanApertureWidthD: 1
+- cleanApertureHeightN: 385
+- cleanApertureHeightD: 1
+- horizOffN: -308
+- horizOffD: 1
+- vertOffN: 103
+- vertOffD: 1
+
+Resulted image should be as the same as above.
+
+## Fox Parade - Odd dimensions images
+
+### Original
+
+[<img src="https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.jpg" alt="fox.jpg" height="512">](fox.jpg)
+
+ - size: 1204 x 800
+ - Author: Kaede Fujisaki ([@ledyba](https://github.com/ledyba))
+ - Retrieved from [her website](https://hexe.net/2017/12/02/16:33:53/).
+
+#### Odd-Width
+
+ - [fox.odd-width.png](fox.odd-width.png)
+ - size: 1203 x 800
+
+#### Odd-Height
+
+  - [fox.odd-width.png](fox.odd-height.png)
+  - size: 1204 x 799
+
+### AVIF version
+
+| profile | bit depth | pix fmt | Monochrome | odd width | odd height | file                                                                   |
+|---------|-----------|---------|------------|-----------|------------|------------------------------------------------------------------------|
+| 0       | 8         | YUV420  |            |           |            | [here](fox.profile0.8bpc.yuv420.avif)                                  |
+| 0       | 8         | YUV420  |            | YES       |            | [here](fox.profile0.8bpc.yuv420.odd-width.avif)                        |
+| 0       | 8         | YUV420  |            |           | YES        | [here](fox.profile0.8bpc.yuv420.odd-height.avif)                       |
+| 0       | 8         | YUV420  |            | YES       | YES        | [here](fox.profile0.8bpc.yuv420.odd-width.odd-height.avif)             |
+| 0       | 8         | YUV420  | YES        |           |            | [here](fox.profile0.8bpc.yuv420.monochrome.avif)                       |
+| 0       | 8         | YUV420  | YES        | YES       |            | [here](fox.profile0.8bpc.yuv420.monochrome.odd-width.avif)             |
+| 0       | 8         | YUV420  | YES        |           | YES        | [here](fox.profile0.8bpc.yuv420.monochrome.odd-height.avif)            |
+| 0       | 8         | YUV420  | YES        | YES       | YES        | [here](fox.profile0.8bpc.yuv420.monochrome.odd-width.odd-height.avif)  |
+| 0       | 10        | YUV420  |            |           |            | [here](fox.profile0.10bpc.yuv420.avif)                                 |
+| 0       | 10        | YUV420  |            | YES       |            | [here](fox.profile0.10bpc.yuv420.odd-width.avif)                       |
+| 0       | 10        | YUV420  |            |           | YES        | [here](fox.profile0.10bpc.yuv420.odd-height.avif)                      |
+| 0       | 10        | YUV420  |            | YES       | YES        | [here](fox.profile0.10bpc.yuv420.odd-width.odd-height.avif)            |
+| 0       | 10        | YUV420  | YES        |           |            | [here](fox.profile0.10bpc.yuv420.monochrome.avif)                      |
+| 0       | 10        | YUV420  | YES        | YES       |            | [here](fox.profile0.10bpc.yuv420.monochrome.odd-width.avif)            |
+| 0       | 10        | YUV420  | YES        |           | YES        | [here](fox.profile0.10bpc.yuv420.monochrome.odd-height.avif)           |
+| 0       | 10        | YUV420  | YES        | YES       | YES        | [here](fox.profile0.10bpc.yuv420.monochrome.odd-width.odd-height.avif) |
+| 2       | 12        | YUV420  |            |           |            | [here](fox.profile2.12bpc.yuv420.avif)                                 |
+| 2       | 12        | YUV420  |            | YES       |            | [here](fox.profile2.12bpc.yuv420.odd-width.avif)                       |
+| 2       | 12        | YUV420  |            |           | YES        | [here](fox.profile2.12bpc.yuv420.odd-height.avif)                      |
+| 2       | 12        | YUV420  |            | YES       | YES        | [here](fox.profile2.12bpc.yuv420.odd-width.odd-height.avif)            |
+| 2       | 12        | YUV420  | YES        |           |            | [here](fox.profile2.12bpc.yuv420.monochrome.avif)                      |
+| 2       | 12        | YUV420  | YES        | YES       |            | [here](fox.profile2.12bpc.yuv420.monochrome.odd-width.avif)            |
+| 2       | 12        | YUV420  | YES        |           | YES        | [here](fox.profile2.12bpc.yuv420.monochrome.odd-height.avif)           |
+| 2       | 12        | YUV420  | YES        | YES       | YES        | [here](fox.profile2.12bpc.yuv420.monochrome.odd-width.odd-height.avif) |
+| 2       | 8         | YUV422  |            |           |            | [here](fox.profile2.8bpc.yuv422.avif)                                  |
+| 2       | 8         | YUV422  |            | YES       |            | [here](fox.profile2.8bpc.yuv422.odd-width.avif)                        |
+| 2       | 8         | YUV422  |            |           | YES        | [here](fox.profile2.8bpc.yuv422.odd-height.avif)                       |
+| 2       | 8         | YUV422  |            | YES       | YES        | [here](fox.profile2.8bpc.yuv422.odd-width.odd-height.avif)             |
+| 2       | 8         | YUV422  | YES        |           |            | [here](fox.profile2.8bpc.yuv422.monochrome.avif)                       |
+| 2       | 8         | YUV422  | YES        | YES       |            | [here](fox.profile2.8bpc.yuv422.monochrome.odd-width.avif)             |
+| 2       | 8         | YUV422  | YES        |           | YES        | [here](fox.profile2.8bpc.yuv422.monochrome.odd-height.avif)            |
+| 2       | 8         | YUV422  | YES        | YES       | YES        | [here](fox.profile2.8bpc.yuv422.monochrome.odd-width.odd-height.avif)  |
+| 2       | 10        | YUV422  |            |           |            | [here](fox.profile2.10bpc.yuv422.avif)                                 |
+| 2       | 10        | YUV422  |            | YES       |            | [here](fox.profile2.10bpc.yuv422.odd-width.avif)                       |
+| 2       | 10        | YUV422  |            |           | YES        | [here](fox.profile2.10bpc.yuv422.odd-height.avif)                      |
+| 2       | 10        | YUV422  |            | YES       | YES        | [here](fox.profile2.10bpc.yuv422.odd-width.odd-height.avif)            |
+| 2       | 10        | YUV422  | YES        |           |            | [here](fox.profile2.10bpc.yuv422.monochrome.avif)                      |
+| 2       | 10        | YUV422  | YES        | YES       |            | [here](fox.profile2.10bpc.yuv422.monochrome.odd-width.avif)            |
+| 2       | 10        | YUV422  | YES        |           | YES        | [here](fox.profile2.10bpc.yuv422.monochrome.odd-height.avif)           |
+| 2       | 10        | YUV422  | YES        | YES       | YES        | [here](fox.profile2.10bpc.yuv422.monochrome.odd-width.odd-height.avif) |
+| 2       | 12        | YUV422  |            |           |            | [here](fox.profile2.12bpc.yuv422.avif)                                 |
+| 2       | 12        | YUV422  |            | YES       |            | [here](fox.profile2.12bpc.yuv422.odd-width.avif)                       |
+| 2       | 12        | YUV422  |            |           | YES        | [here](fox.profile2.12bpc.yuv422.odd-height.avif)                      |
+| 2       | 12        | YUV422  |            | YES       | YES        | [here](fox.profile2.12bpc.yuv422.odd-width.odd-height.avif)            |
+| 2       | 12        | YUV422  | YES        |           |            | [here](fox.profile2.12bpc.yuv422.monochrome.avif)                      |
+| 2       | 12        | YUV422  | YES        | YES       |            | [here](fox.profile2.12bpc.yuv422.monochrome.odd-width.avif)            |
+| 2       | 12        | YUV422  | YES        |           | YES        | [here](fox.profile2.12bpc.yuv422.monochrome.odd-height.avif)           |
+| 2       | 12        | YUV422  | YES        | YES       | YES        | [here](fox.profile2.12bpc.yuv422.monochrome.odd-width.odd-height.avif) |
+| 1       | 8         | YUV444  |            |           |            | [here](fox.profile1.8bpc.yuv444.avif)                                  |
+| 1       | 8         | YUV444  |            | YES       |            | [here](fox.profile1.8bpc.yuv444.odd-width.avif)                        |
+| 1       | 8         | YUV444  |            |           | YES        | [here](fox.profile1.8bpc.yuv444.odd-height.avif)                       |
+| 1       | 8         | YUV444  |            | YES       | YES        | [here](fox.profile1.8bpc.yuv444.odd-width.odd-height.avif)             |
+| 1       | 8         | YUV444  |            |           |            | [here](fox.profile1.8bpc.yuv444.avif)                                  |
+| 1       | 8         | YUV444  |            | YES       |            | [here](fox.profile1.8bpc.yuv444.odd-width.avif)                        |
+| 1       | 8         | YUV444  |            |           | YES        | [here](fox.profile1.8bpc.yuv444.odd-height.avif)                       |
+| 1       | 8         | YUV444  |            | YES       | YES        | [here](fox.profile1.8bpc.yuv444.odd-width.odd-height.avif)             |
+| 1       | 10        | YUV444  |            |           |            | [here](fox.profile1.10bpc.yuv444.avif)                                 |
+| 1       | 10        | YUV444  |            | YES       |            | [here](fox.profile1.10bpc.yuv444.odd-width.avif)                       |
+| 1       | 10        | YUV444  |            |           | YES        | [here](fox.profile1.10bpc.yuv444.odd-height.avif)                      |
+| 1       | 10        | YUV444  |            | YES       | YES        | [here](fox.profile1.10bpc.yuv444.odd-width.odd-height.avif)            |
+| 2       | 12        | YUV444  |            |           |            | [here](fox.profile2.12bpc.yuv444.avif)                                 |
+| 2       | 12        | YUV444  |            | YES       |            | [here](fox.profile2.12bpc.yuv444.odd-width.avif)                       |
+| 2       | 12        | YUV444  |            |           | YES        | [here](fox.profile2.12bpc.yuv444.odd-height.avif)                      |
+| 2       | 12        | YUV444  |            | YES       | YES        | [here](fox.profile2.12bpc.yuv444.odd-width.odd-height.avif)            |
+| 2       | 12        | YUV444  | YES        |           |            | [here](fox.profile2.12bpc.yuv444.monochrome.avif)                      |
+| 2       | 12        | YUV444  | YES        | YES       |            | [here](fox.profile2.12bpc.yuv444.monochrome.odd-width.avif)            |
+| 2       | 12        | YUV444  | YES        |           | YES        | [here](fox.profile2.12bpc.yuv444.monochrome.odd-height.avif)           |
+| 2       | 12        | YUV444  | YES        | YES       | YES        | [here](fox.profile2.12bpc.yuv444.monochrome.odd-width.odd-height.avif) |
diff --git a/avif_parse.h b/avif_parse.h
new file mode 100644 (file)
index 0000000..2865649
--- /dev/null
@@ -0,0 +1,47 @@
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+/**
+ * Result of parsing an AVIF file. Contains AV1-compressed data.
+ */
+typedef struct avif_data_t {
+  /**
+   * AV1 data for color channels
+   */
+  const unsigned char *primary_data;
+  size_t primary_size;
+  /**
+   * AV1 data for alpha channel (may be NULL if the image is fully opaque)
+   */
+  const unsigned char *alpha_data;
+  size_t alpha_size;
+  /**
+   * 0 = normal, uncorrelated alpha channel
+   * 1 = premultiplied alpha. You must divide RGB values by alpha.
+   *
+   * ```c
+   * if (a != 0) {r = r * 255 / a}
+   * ```
+   */
+  char premultiplied_alpha;
+  void *reserved;
+} avif_data_t;
+
+/**
+ * Parse AVIF image file and return results. Returns `NULL` if the file can't be parsed.
+ *
+ * Call `avif_data_free` on the result when done.
+ */
+const avif_data_t *avif_parse(const unsigned char *bytes, size_t bytes_len);
+
+/**
+ * Free all data related to `avif_data_t`
+ */
+void avif_data_free(const avif_data_t *data);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif // __cplusplus
diff --git a/src/boxes.rs b/src/boxes.rs
new file mode 100644 (file)
index 0000000..3c64de6
--- /dev/null
@@ -0,0 +1,215 @@
+// 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 Into<u32> for BoxType {
+            fn into(self) -> u32 {
+                use self::BoxType::*;
+                match self {
+                    $($(#[$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.clone());
+                fourcc.fmt(f)
+            }
+        }
+    }
+}
+
+#[derive(Default, 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)
+    }
+}
+
+#[deny(unreachable_patterns)]
+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"
+    ImageReferenceBox                 0x6972_6566, // "iref"
+    ImagePropertiesBox                0x6970_7270, // "iprp"
+    ItemPropertyContainerBox          0x6970_636f, // "ipco"
+    ItemPropertyAssociationBox        0x6970_6d61, // "ipma"
+    ColorInformationBox               0x636f_6c72, // "colr"
+    PixelInformationBox               0x7069_7869, // "pixi"
+    AuxiliaryTypeProperty             0x6175_7843, // "auxC"
+    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"
+    MP4AudioSampleEntry               0x6d70_3461, // "mp4a"
+    MP4VideoSampleEntry               0x6d70_3476, // "mp4v"
+    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"
+    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"
+);
diff --git a/src/c_api.rs b/src/c_api.rs
new file mode 100644 (file)
index 0000000..50eb077
--- /dev/null
@@ -0,0 +1,56 @@
+use crate::AvifData as AvifDataRust;
+
+/// Result of parsing an AVIF file. Contains AV1-compressed data.
+#[allow(bad_style)]
+#[repr(C)]
+pub struct avif_data_t {
+    /// AV1 data for color channels
+    pub primary_data: *const u8,
+    pub primary_size: usize,
+    /// AV1 data for alpha channel (may be NULL if the image is fully opaque)
+    pub alpha_data: *const u8,
+    pub alpha_size: usize,
+    /// 0 = normal, uncorrelated alpha channel
+    /// 1 = premultiplied alpha. You must divide RGB values by alpha.
+    ///
+    /// ```c
+    /// if (a != 0) {r = r * 255 / a}
+    /// ```
+    pub premultiplied_alpha: u8,
+    _rusty_handle: *mut AvifDataRust,
+}
+
+/// Parse AVIF image file and return results. Returns `NULL` if the file can't be parsed.
+///
+/// Call `avif_data_free` on the result when done.
+#[no_mangle]
+pub unsafe extern "C" fn avif_parse(bytes: *const u8, bytes_len: usize) -> *const avif_data_t {
+    if bytes.is_null() || bytes_len == 0 {
+        return std::ptr::null();
+    }
+    let mut data = std::slice::from_raw_parts(bytes, bytes_len);
+    match crate::read_avif(&mut data) {
+        Ok(data) => Box::into_raw(Box::new(avif_data_t {
+            primary_data: data.primary_item.as_ptr(),
+            primary_size: data.primary_item.len(),
+            alpha_data: data
+                .alpha_item
+                .as_ref()
+                .map_or(std::ptr::null(), |a| a.as_ptr()),
+            alpha_size: data.alpha_item.as_ref().map_or(0, |a| a.len()),
+            premultiplied_alpha: data.premultiplied_alpha as u8,
+            _rusty_handle: Box::into_raw(Box::new(data)),
+        })),
+        Err(_) => std::ptr::null(),
+    }
+}
+
+/// Free all data related to `avif_data_t`
+#[no_mangle]
+pub unsafe extern "C" fn avif_data_free(data: *const avif_data_t) {
+    if data.is_null() {
+        return;
+    }
+    let _ = Box::from_raw((*data)._rusty_handle);
+    let _ = Box::from_raw(data as *mut avif_data_t);
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644 (file)
index 0000000..75d5665
--- /dev/null
@@ -0,0 +1,1290 @@
+//! 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/.
+
+#[macro_use]
+extern crate log;
+
+use bitreader::BitReader;
+use byteorder::ReadBytesExt;
+use fallible_collections::TryClone;
+use fallible_collections::TryRead;
+use fallible_collections::TryReserveError;
+use std::convert::{TryFrom, TryInto as _};
+
+use std::io::{Read, Take};
+use std::ops::{Range, RangeFrom};
+
+#[macro_use]
+mod macros;
+
+mod boxes;
+use crate::boxes::{BoxType, FourCC};
+
+/// This crate can be used from C.
+pub mod c_api;
+
+// Unit tests.
+#[cfg(test)]
+mod tests;
+
+// Arbitrary buffer size limit used for raw read_bufs on a box.
+// const BUF_SIZE_LIMIT: u64 = 10 * 1024 * 1024;
+
+/// 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 {
+    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(crate) 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
+trait Offset {
+    fn offset(&self) -> u64;
+}
+
+/// Wraps a reader to track the current offset
+struct OffsetReader<'a, T> {
+    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;
+
+/// 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),
+    /// 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::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 ISO 14496-12:2015 § 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.
+    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 {
+    major_brand: FourCC,
+    minor_version: u32,
+    compatible_brands: TryVec<FourCC>,
+}
+
+// Handler reference box 'hdlr'
+#[derive(Debug)]
+struct HandlerBox {
+    handler_type: FourCC,
+}
+
+#[derive(Debug)]
+pub(crate) struct AV1ConfigBox {
+    pub(crate) profile: u8,
+    pub(crate) level: u8,
+    pub(crate) tier: u8,
+    pub(crate) bit_depth: u8,
+    pub(crate) monochrome: bool,
+    pub(crate) chroma_subsampling_x: u8,
+    pub(crate) chroma_subsampling_y: u8,
+    pub(crate) chroma_sample_position: u8,
+    pub(crate) initial_presentation_delay_present: bool,
+    pub(crate) initial_presentation_delay_minus_one: u8,
+    pub(crate) config_obus: TryVec<u8>,
+}
+
+#[derive(Debug, Default)]
+pub struct AvifData {
+    /// AV1 data for the color channels.
+    ///
+    /// The collected data indicated by the `pitm` box, See ISO 14496-12:2015 § 8.11.4
+    pub primary_item: TryVec<u8>,
+    /// AV1 data for alpha channel.
+    ///
+    /// Associated alpha channel for the primary item, if any
+    pub alpha_item: Option<TryVec<u8>>,
+    /// If true, divide RGB values by the alpha value.
+    ///
+    /// See `prem` in MIAF § 7.3.5.2
+    pub premultiplied_alpha: bool,
+}
+
+struct AvifMeta {
+    item_references: TryVec<SingleItemTypeReferenceBox>,
+    properties: TryVec<AssociatedProperty>,
+    primary_item_id: u32,
+    iloc_items: TryVec<ItemLocationBoxItem>,
+}
+
+/// A Media Data Box
+/// See ISO 14496-12:2015 § 8.1.1
+struct MediaDataBox {
+    /// Offset of `data` from the beginning of the file. See ConstructionMethod::File
+    offset: u64,
+    data: TryVec<u8>,
+}
+
+impl MediaDataBox {
+    /// Check whether the beginning of `extent` is within the bounds of the `MediaDataBox`.
+    /// We assume extents to not cross box boundaries. If so, this will cause an error
+    /// in `read_extent`.
+    fn contains_extent(&self, extent: &ExtentRange) -> bool {
+        if self.offset <= extent.start() {
+            let start_offset = extent.start() - self.offset;
+            start_offset < self.data.len().to_u64()
+        } else {
+            false
+        }
+    }
+
+    /// Check whether `extent` covers the `MediaDataBox` exactly.
+    fn matches_extent(&self, extent: &ExtentRange) -> bool {
+        if self.offset == extent.start() {
+            match extent {
+                ExtentRange::WithLength(range) => {
+                    if let Some(end) = self.offset.checked_add(self.data.len().to_u64()) {
+                        end == range.end
+                    } else {
+                        false
+                    }
+                }
+                ExtentRange::ToEnd(_) => true,
+            }
+        } else {
+            false
+        }
+    }
+
+    /// Copy the range specified by `extent` to the end of `buf` or return an error if the range
+    /// is not fully contained within `MediaDataBox`.
+    fn read_extent(&mut self, extent: &ExtentRange, buf: &mut TryVec<u8>) -> Result<()> {
+        let start_offset = extent
+            .start()
+            .checked_sub(self.offset)
+            .expect("mdat does not contain extent");
+        let slice = match extent {
+            ExtentRange::WithLength(range) => {
+                let range_len = range
+                    .end
+                    .checked_sub(range.start)
+                    .expect("range start > end");
+                let end = start_offset
+                    .checked_add(range_len)
+                    .expect("extent end overflow");
+                self.data.get(start_offset.try_into()?..end.try_into()?)
+            }
+            ExtentRange::ToEnd(_) => self.data.get(start_offset.try_into()?..),
+        };
+        let slice = slice.ok_or(Error::InvalidData("extent crosses box boundary"))?;
+        buf.extend_from_slice(slice)?;
+        Ok(())
+    }
+}
+
+/// Used for 'infe' boxes within 'iinf' boxes
+/// See ISO 14496-12:2015 § 8.11.6
+/// Only versions {2, 3} are supported
+#[derive(Debug)]
+struct ItemInfoEntry {
+    item_id: u32,
+    item_type: u32,
+}
+
+/// See ISO 14496-12:2015 § 8.11.12
+#[derive(Debug)]
+struct SingleItemTypeReferenceBox {
+    item_type: FourCC,
+    from_item_id: u32,
+    to_item_id: u32,
+}
+
+/// Potential sizes (in bytes) of variable-sized fields of the 'iloc' box
+/// See ISO 14496-12:2015 § 8.11.3
+#[derive(Debug)]
+enum IlocFieldSize {
+    Zero,
+    Four,
+    Eight,
+}
+
+impl IlocFieldSize {
+    fn to_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 ISO 14496-12:2015 § 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 {
+    item_id: u32,
+    construction_method: ConstructionMethod,
+    /// Unused for ConstructionMethod::Idat
+    extents: TryVec<ItemLocationBoxExtent>,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+enum ConstructionMethod {
+    File,
+    Idat,
+    #[allow(dead_code)] // TODO: see https://github.com/mozilla/mp4parse-rust/issues/196
+    Item,
+}
+
+/// `extent_index` is omitted since it's only used for ConstructionMethod::Item which
+/// is currently not implemented.
+#[derive(Clone, Debug)]
+struct ItemLocationBoxExtent {
+    extent_range: ExtentRange,
+}
+
+#[derive(Clone, Debug)]
+enum ExtentRange {
+    WithLength(Range<u64>),
+    ToEnd(RangeFrom<u64>),
+}
+
+impl ExtentRange {
+    fn start(&self) -> u64 {
+        match self {
+            Self::WithLength(r) => r.start,
+            Self::ToEnd(r) => r.start,
+        }
+    }
+}
+
+/// See ISO 14496-12:2015 § 4.2
+struct BMFFBox<'a, T> {
+    head: BoxHeader,
+    content: Take<&'a mut T>,
+}
+
+struct BoxIter<'a, T> {
+    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 ISO 14496-12:2015 § 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
+///
+/// Metadata is accumulated and returned in [`AvifData`] struct,
+pub fn read_avif<T: Read>(f: &mut T) -> Result<AvifData> {
+    let mut f = OffsetReader::new(f);
+
+    let mut iter = BoxIter::new(&mut f);
+
+    // 'ftyp' box must occur first; see ISO 14496-12:2015 § 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.major_brand != b"avif" {
+                return Err(Error::InvalidData("ftyp must be 'avif'"));
+            }
+        } else {
+            return Err(Error::InvalidData("'ftyp' box must occur first"));
+        }
+    }
+
+    let mut meta = None;
+    let mut mdats = TryVec::new();
+
+    while let Some(mut b) = iter.next_box()? {
+        match b.head.name {
+            BoxType::MetadataBox => {
+                if meta.is_some() {
+                    return Err(Error::InvalidData(
+                        "There should be zero or one meta boxes per ISO 14496-12:2015 § 8.11.1.1",
+                    ));
+                }
+                meta = Some(read_avif_meta(&mut b)?);
+            }
+            BoxType::MediaDataBox => {
+                if b.bytes_left() > 0 {
+                    let offset = b.offset();
+                    let data = b.read_into_try_vec()?;
+                    mdats.push(MediaDataBox { offset, data })?;
+                }
+            }
+            _ => skip_box_content(&mut b)?,
+        }
+
+        check_parser_state!(b.content);
+    }
+
+    let meta = meta.ok_or(Error::InvalidData("missing meta"))?;
+
+    let alpha_item_id = meta
+        .item_references
+        .iter()
+        // Auxiliary image for the primary image
+        .filter(|iref| {
+            iref.to_item_id == meta.primary_item_id
+                && iref.from_item_id != meta.primary_item_id
+                && iref.item_type == b"auxl"
+        })
+        .map(|iref| iref.from_item_id)
+        // which has the alpha property
+        .find(|&item_id| {
+            meta.properties.iter().any(|prop| {
+                prop.item_id == item_id
+                    && match &prop.property {
+                        ItemProperty::AuxiliaryType(urn) => {
+                            urn.aux_type.as_slice()
+                                == "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha".as_bytes()
+                        }
+                        _ => false,
+                    }
+            })
+        });
+
+    let mut context = AvifData::default();
+    context.premultiplied_alpha = alpha_item_id.map_or(false, |alpha_item_id| {
+        meta.item_references.iter().any(|iref| {
+            iref.from_item_id == meta.primary_item_id
+                && iref.to_item_id == alpha_item_id
+                && iref.item_type == b"prem"
+        })
+    });
+
+    // load data of relevant items
+    for loc in meta.iloc_items.iter() {
+        let item_data = if loc.item_id == meta.primary_item_id {
+            &mut context.primary_item
+        } else if Some(loc.item_id) == alpha_item_id {
+            context.alpha_item.get_or_insert_with(TryVec::new)
+        } else {
+            continue;
+        };
+
+        if loc.construction_method != ConstructionMethod::File {
+            return Err(Error::Unsupported("unsupported construction_method"));
+        }
+        for extent in loc.extents.iter() {
+            let mut found = false;
+            // try to find an overlapping mdat
+            for mdat in mdats.iter_mut() {
+                if mdat.matches_extent(&extent.extent_range) {
+                    item_data.append(&mut mdat.data)?;
+                    found = true;
+                    break;
+                } else if mdat.contains_extent(&extent.extent_range) {
+                    mdat.read_extent(&extent.extent_range, item_data)?;
+                    found = true;
+                    break;
+                }
+            }
+            if !found {
+                return Err(Error::InvalidData(
+                    "iloc contains an extent that is not in mdat",
+                ));
+            }
+        }
+    }
+
+    Ok(context)
+}
+
+/// 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 ISO 14496-12:2015 § 8.11.1
+fn read_avif_meta<T: Read + Offset>(src: &mut BMFFBox<'_, T>) -> Result<AvifMeta> {
+    let version = read_fullbox_version_no_flags(src)?;
+
+    if version != 0 {
+        return Err(Error::Unsupported("unsupported meta version"));
+    }
+
+    let mut primary_item_id = None;
+    let mut item_infos = None;
+    let mut iloc_items = None;
+    let mut item_references = TryVec::new();
+    let mut properties = TryVec::new();
+
+    let mut iter = src.box_iter();
+    while let Some(mut b) = iter.next_box()? {
+        match b.head.name {
+            BoxType::ItemInfoBox => {
+                if item_infos.is_some() {
+                    return Err(Error::InvalidData(
+                        "There should be zero or one iinf boxes per ISO 14496-12:2015 § 8.11.6.1",
+                    ));
+                }
+                item_infos = Some(read_iinf(&mut b)?);
+            }
+            BoxType::ItemLocationBox => {
+                if iloc_items.is_some() {
+                    return Err(Error::InvalidData(
+                        "There should be zero or one iloc boxes per ISO 14496-12:2015 § 8.11.3.1",
+                    ));
+                }
+                iloc_items = Some(read_iloc(&mut b)?);
+            }
+            BoxType::PrimaryItemBox => {
+                if primary_item_id.is_some() {
+                    return Err(Error::InvalidData(
+                        "There should be zero or one iloc boxes per ISO 14496-12:2015 § 8.11.4.1",
+                    ));
+                }
+                primary_item_id = Some(read_pitm(&mut b)?);
+            }
+            BoxType::ImageReferenceBox => {
+                item_references.append(&mut read_iref(&mut b)?)?;
+            }
+            BoxType::ImagePropertiesBox => {
+                properties = read_iprp(&mut b)?;
+            }
+            _ => 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) {
+        if &item_info.item_type.to_be_bytes() != b"av01" {
+            warn!("primary_item_id type: {}", U32BE(item_info.item_type));
+            return Err(Error::InvalidData("primary_item_id type is not av01"));
+        }
+    } else {
+        return Err(Error::InvalidData(
+            "primary_item_id not present in iinf box",
+        ));
+    }
+
+    Ok(AvifMeta {
+        properties,
+        item_references,
+        primary_item_id,
+        iloc_items: iloc_items.ok_or(Error::InvalidData("iloc missing"))?,
+    })
+}
+
+/// Parse a Primary Item Box
+/// See ISO 14496-12:2015 § 8.11.4
+fn read_pitm<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<u32> {
+    let version = read_fullbox_version_no_flags(src)?;
+
+    let item_id = 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 ISO 14496-12:2015 § 8.11.6
+fn read_iinf<T: Read>(src: &mut BMFFBox<'_, T>) -> 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 should contain only infe boxes",
+            ));
+        }
+
+        item_infos.push(read_infe(&mut b)?)?;
+
+        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 ISO 14496-12:2015 § 8.11.6.2
+fn read_infe<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<ItemInfoEntry> {
+    // According to the standard, it seems the flags field should be 0, but
+    // at least one sample AVIF image has a nonzero value.
+    let (version, _) = read_fullbox_extra(src)?;
+
+    // mif1 brand (see ISO 23008-12:2017 § 10.2.1) only requires v2 and 3
+    let item_id = 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::Unsupported(
+            "protected items (infe.item_protection_index != 0) are not supported",
+        ));
+    }
+
+    let item_type = be_u32(src)?;
+    debug!("infe item_id {} 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 })
+}
+
+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()? {
+        let from_item_id = if version == 0 {
+            be_u16(&mut b)?.into()
+        } else {
+            be_u32(&mut b)?
+        };
+        let reference_count = be_u16(&mut b)?;
+        for _ in 0..reference_count {
+            let to_item_id = if version == 0 {
+                be_u16(&mut b)?.into()
+            } else {
+                be_u32(&mut b)?
+            };
+            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)
+}
+
+fn read_iprp<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<AssociatedProperty>> {
+    let mut iter = src.box_iter();
+    let mut properties = TryVec::new();
+    let mut associations = TryVec::new();
+
+    while let Some(mut b) = iter.next_box()? {
+        match b.head.name {
+            BoxType::ItemPropertyContainerBox => {
+                properties = read_ipco(&mut b)?;
+            }
+            BoxType::ItemPropertyAssociationBox => {
+                associations = read_ipma(&mut b)?;
+            }
+            _ => return Err(Error::InvalidData("unexpected ipco child")),
+        }
+    }
+
+    let mut associated = TryVec::new();
+    for a in associations {
+        let index = match a.property_index {
+            0 => continue,
+            x => x as usize - 1,
+        };
+        if let Some(prop) = properties.get(index) {
+            if *prop != ItemProperty::Unsupported {
+                associated.push(AssociatedProperty {
+                    item_id: a.item_id,
+                    property: prop.try_clone()?,
+                })?;
+            }
+        }
+    }
+    Ok(associated)
+}
+
+#[derive(Debug, PartialEq)]
+pub(crate) enum ItemProperty {
+    Channels(TryVec<u8>),
+    AuxiliaryType(AuxiliaryTypeProperty),
+    Unsupported,
+}
+
+impl TryClone for ItemProperty {
+    fn try_clone(&self) -> Result<Self, TryReserveError> {
+        Ok(match self {
+            Self::Channels(val) => Self::Channels(val.try_clone()?),
+            Self::AuxiliaryType(val) => Self::AuxiliaryType(val.try_clone()?),
+            Self::Unsupported => Self::Unsupported,
+        })
+    }
+}
+
+struct Association {
+    item_id: u32,
+    essential: bool,
+    property_index: u16,
+}
+
+pub(crate) struct AssociatedProperty {
+    pub item_id: u32,
+    pub property: ItemProperty,
+}
+
+fn read_ipma<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<Association>> {
+    let (version, flags) = read_fullbox_extra(src)?;
+
+    let mut associations = TryVec::new();
+
+    let entry_count = be_u32(src)?;
+    for _ in 0..entry_count {
+        let item_id = if version == 0 {
+            be_u16(src)?.into()
+        } else {
+            be_u32(src)?
+        };
+        let association_count = src.read_u8()?;
+        for _ in 0..association_count {
+            let num_association_bytes = if flags & 1 == 1 { 2 } else { 1 };
+            let association = src.take(num_association_bytes).read_into_try_vec()?;
+            let mut association = BitReader::new(association.as_slice());
+            let essential = association.read_bool()?;
+            let property_index = association.read_u16(association.remaining().try_into()?)?;
+            associations.push(Association {
+                item_id,
+                essential,
+                property_index,
+            })?;
+        }
+    }
+    Ok(associations)
+}
+
+fn read_ipco<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<ItemProperty>> {
+    let mut properties = TryVec::new();
+
+    let mut iter = src.box_iter();
+    while let Some(mut b) = iter.next_box()? {
+        // Must push for every property to have correct index for them
+        properties.push(match b.head.name {
+            BoxType::PixelInformationBox => ItemProperty::Channels(read_pixi(&mut b)?),
+            BoxType::AuxiliaryTypeProperty => ItemProperty::AuxiliaryType(read_auxc(&mut b)?),
+            _ => {
+                skip_box_remain(&mut b)?;
+                ItemProperty::Unsupported
+            }
+        })?;
+    }
+    Ok(properties)
+}
+
+fn read_pixi<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<u8>> {
+    let version = read_fullbox_version_no_flags(src)?;
+    if version != 0 {
+        return Err(Error::Unsupported("pixi version"));
+    }
+
+    let num_channels = src.read_u8()?.into();
+    let mut channels = TryVec::with_capacity(num_channels)?;
+    let num_channels_read = src.try_read_to_end(&mut channels)?;
+
+    if num_channels_read != num_channels {
+        return Err(Error::InvalidData("invalid num_channels"));
+    }
+
+    check_parser_state!(src.content);
+    Ok(channels)
+}
+
+#[derive(Debug, PartialEq)]
+pub struct AuxiliaryTypeProperty {
+    aux_type: TryString,
+    aux_subtype: TryString,
+}
+
+impl TryClone for AuxiliaryTypeProperty {
+    fn try_clone(&self) -> Result<Self, TryReserveError> {
+        Ok(AuxiliaryTypeProperty {
+            aux_type: self.aux_type.try_clone()?,
+            aux_subtype: self.aux_subtype.try_clone()?,
+        })
+    }
+}
+
+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 ISO 14496-12:2015 § 8.11.3
+fn read_iloc<T: Read>(src: &mut BMFFBox<'_, T>) -> Result<TryVec<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 = TryVec::with_capacity(item_count.to_usize())?;
+
+    for _ in 0..item_count {
+        let item_id = 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 ISO 14496-12:2015 § 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.to_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 ISO 14496-12:2015 § 8.11.3.3",
+            ));
+        }
+
+        let mut extents = TryVec::with_capacity(extent_count.to_usize())?;
+
+        for _ in 0..extent_count {
+            // Parsed but currently ignored, see `ItemLocationBoxExtent`
+            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.to_bits())?)
+                }
+            };
+
+            // Per ISO 14496-12:2015 § 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.to_bits())?;
+            let extent_length = iloc.read_u64(length_size.to_bits())?;
+
+            // "If the length is not specified, or specified as zero, then the entire length of
+            //  the source is implied" (ibid)
+            let start = base_offset
+                .checked_add(extent_offset)
+                .ok_or(Error::InvalidData("offset calculation overflow"))?;
+            let extent_range = if extent_length == 0 {
+                ExtentRange::ToEnd(RangeFrom { start })
+            } else {
+                let end = start
+                    .checked_add(extent_length)
+                    .ok_or(Error::InvalidData("end calculation overflow"))?;
+                ExtentRange::WithLength(Range { start, end })
+            };
+
+            extents.push(ItemLocationBoxExtent { extent_range })?;
+        }
+
+        items.push(ItemLocationBoxItem {
+            item_id,
+            construction_method,
+            extents,
+        })?;
+    }
+
+    if iloc.remaining() == 0 {
+        Ok(items)
+    } else {
+        Err(Error::InvalidData("invalid iloc size"))
+    }
+}
+
+/// Parse an ftyp box.
+/// See ISO 14496-12:2015 § 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,
+    })
+}
+
+/// 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(())
+}
+
+fn be_u16<T: ReadBytesExt>(src: &mut T) -> Result<u16> {
+    src.read_u16::<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)
+}
diff --git a/src/macros.rs b/src/macros.rs
new file mode 100644 (file)
index 0000000..a893f7e
--- /dev/null
@@ -0,0 +1,12 @@
+// 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/src/tests.rs b/src/tests.rs
new file mode 100644 (file)
index 0000000..4097df5
--- /dev/null
@@ -0,0 +1,26 @@
+//! 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 fallible_collections::TryRead as _;
+
+use std::convert::TryInto as _;
+
+use std::io::Read as _;
+
+#[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());
+}