--- /dev/null
+{
+ "git": {
+ "sha1": "2174e0e15647918cbbcb965ba142c285a6ef457f"
+ },
+ "path_in_vcs": ""
+}
\ No newline at end of file
--- /dev/null
+# 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"
--- /dev/null
+[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"]
--- /dev/null
+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.
--- /dev/null
+# 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);
+```
--- /dev/null
+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.
--- /dev/null
+# 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).
--- /dev/null
+# 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
+
+
+
+ - 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:
+
+
+
+### 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) |
--- /dev/null
+#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
--- /dev/null
+// 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"
+);
--- /dev/null
+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);
+}
--- /dev/null
+//! 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)
+}
--- /dev/null
+// 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"));
+ }
+ };
+}
--- /dev/null
+//! 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());
+}