diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/rust/mp4parse | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/mp4parse')
58 files changed, 13606 insertions, 0 deletions
diff --git a/third_party/rust/mp4parse/.cargo-checksum.json b/third_party/rust/mp4parse/.cargo-checksum.json new file mode 100644 index 0000000000..1a1081f89a --- /dev/null +++ b/third_party/rust/mp4parse/.cargo-checksum.json @@ -0,0 +1 @@ +{"files":{"Cargo.toml":"c67d586b5c36ce9ad893bf3a79bec9904b08e47de71376fe820785321ba4f8a5","LICENSE":"fab3dd6bdab226f1c08630b1dd917e11fcb4ec5e1e020e2c16f83a0a13863e85","README.md":"86cb40854b93f988e3a63ce6fe39d2ce95367f8ca301a5ba50676ff98a0ad791","benches/avif_benchmark.rs":"17105ee0ec4ff0e3eec90699252939101edd5323514ceb404f367e67ef16cf95","link-u-avif-sample-images/.github/workflows/encode-and-decode-daily.yml":"84b787f721024a100ce09ac5714a1d78a4811893861e89495313f435b9d02359","link-u-avif-sample-images/.gitignore":"ac16d40779ab2d608843a3cb1b0418a1ffdc0e71a06c4d140386fadf007a54a7","link-u-avif-sample-images/LICENSE.txt":"da89f9867822be4b8adb1e601d9e9226c195016c6508015eb7593e68ead0c98a","link-u-avif-sample-images/Makefile":"b5697e8685d2a9ce0f4b4c976a5f707022ed113782d16dc59ae280d3a8ce77b1","link-u-avif-sample-images/README.md":"d249fb7bef4f21359cfc4f2977e1b2f2c6e6dd6e57cb1cdc1da1f0edd8aa55d0","link-u-avif-sample-images/fox.jpg":"927997a90ae88ead007283bf9c1392159d0acd2e9890522146211fda2112a2d9","link-u-avif-sample-images/fox.odd-height.png":"6136247772bd1c0edd50426bca4f3485473ac25a784e5ec8777f7491598e96db","link-u-avif-sample-images/fox.odd-width.odd-height.png":"6f91dc21c137f318d0443ce28bbf3f74d5502180c254327b46e41040a33f1363","link-u-avif-sample-images/fox.odd-width.png":"a8b2328c8700c16280c5ab40a34147edac598d4d48ca101bef649e468ae1492e","link-u-avif-sample-images/fox.png":"c45bfb5780843c70a37426340020e3e7ff41d7cf1df9fec614a5cf429d078573","link-u-avif-sample-images/hato.16bpc.png":"53b550c587cd1d19a1997184e47f4a3ff2a05cedf7cb4e42a9466a6d6cb60d8d","link-u-avif-sample-images/hato.jpg":"6d4804e5e4adf36a6b138544c81b743ed7abdd9a495a43e883ec77689ca28943","link-u-avif-sample-images/hato.png":"313880f4cc51160fec522d78f1fb7f06df70fe1929a731fc86c68ecefd312277","link-u-avif-sample-images/images.html":"9e18453dfe5b205600f158282c6896265281e3b04b2fbc332804fab1dbdb3faf","link-u-avif-sample-images/kimono.crop.png":"0d5605bae0ec9d39aad9dc8e1a371d0327c6a224643983e3ee1f4d44cb00f19d","link-u-avif-sample-images/kimono.jpg":"a6ad58e3cea437ee0c841115ba67ae7354de7af734de50de9d0853dd4e571577","link-u-avif-sample-images/kimono.mirror-horizontal.png":"9af9e839fe6bf6342831970c20291f619570d2fc687951ae00cd81ea766f53fe","link-u-avif-sample-images/kimono.mirror-vertical.png":"4ed003c5868fd2e78c7b2dcbd54a67a0e7593dabb3ac82b1c9e5e2dbdf09b8ec","link-u-avif-sample-images/kimono.mirror-vertical.rotate270.png":"74b9b7ffa8955761f747a0e6e81d5b7ecb5e325383546110e1b6aa9986728035","link-u-avif-sample-images/kimono.png":"84fd6cfb97a27739608e21779f874b4ae7e80342b2588e8b0b092dee2d57c881","link-u-avif-sample-images/kimono.rotate270.png":"1918a47c02b378945a705301abd4250ddc65bb95afce9424572ffd0fdd1f45ef","link-u-avif-sample-images/kimono.rotate90.png":"1a73c61692abe96d0a7a9accdb36a83d51bceac79bbb83a00571570f494cca49","link-u-avif-sample-images/plum-blossom-large.png":"af6ea005b726ca39f342e946aa53bed88e5a140413ce896d166bb35ab0aa3b4f","link-u-avif-sample-images/plum-blossom-small.png":"c859fd97b647e494461f65835b9c1c3476807aee77076599adf18a832b3617a4","link-u-avif-sample-images/plum-blossom.svg":"be1f03dd05f63292c85a96b1c48fb06727283610cc69b1e116d547bab27b171d","link-u-avif-sample-images/red-at-12-oclock-with-color-profile.jpg":"d56f809ea5eda74578af57e2f80b41856a1fe2ff436c741aa58757387af998bd","link-u-avif-sample-images/red-at-12-oclock-with-color-profile.png":"4eab95e358eb48e052c7b8c94d30a8c6cb1c9c3c2dfd9845240281dd5dd7b800","link-u-avif-sample-images/scripts/compare.sh":"0562689bcd40e9fc1322bf037d6f999aa4406a2229f19e74b96cc450e370e429","link-u-avif-sample-images/star-10bpc-with-alpha.avifs":"5643ac1f235ae6599186dd66c66507db6fa46a17b2b18e82ea9344870eb98a9b","link-u-avif-sample-images/star-10bpc.avifs":"c61d899a59dbd8c7b2f7bcfca9069a0e13ff1606899af227938a28502e6cbf88","link-u-avif-sample-images/star-12bpc-with-alpha.avifs":"88a350c3550ce36c1777fe7eb1e906c6829d3ed8b241aa1e0e46f1a4e2567c4b","link-u-avif-sample-images/star-12bpc.avifs":"c1a59db6f180208a3177d77c7f9ab08290e903c7bdaf929331b807a510f8c619","link-u-avif-sample-images/star-8bpc-with-alpha.avifs":"13a12908cb162a855cccc9221a5f9f736e8ea07902ffbdcf007f8fde5ed255f2","link-u-avif-sample-images/star-8bpc.avifs":"ae35b161de67a5afeb195ee401f369c34990f0ff8662f70ab4065bc6931f0a66","link-u-avif-sample-images/star.gif":"389cdd02efbdce4f0205cae6e91c1f64e34fa0ca1fe02351da1b37e16cbb642a","link-u-avif-sample-images/star.input.txt":"970163b942843618616f42233abe91d40fb68f6f5451860db259551711867b55","link-u-avif-sample-images/star.png":"18569167cf7ebd265ab6973d071d259aacfbb46c0408b7d4874c8cc9df9bb1ad","link-u-avif-sample-images/star.svg":"13089d0986b31b87919029fa69f2b68981af4023306bf0f79922f6772396008a","link-u-avif-sample-images/star180.png":"21bc11be2b51334fe4589634507612e7edce96d36e6a99219d029e440164e8b8","link-u-avif-sample-images/star270.png":"5c93f538dcdc70840b9925b4089083acc9c25e95265b3f3dea18d695451b441e","link-u-avif-sample-images/star90.png":"2defc5d21e70447653fec5dc14a697d9dd555d7a0c14e79cb2d9f80796a51a6d","src/boxes.rs":"f6588ae051b76bef8a4c33e4bf221a5fd12bc610e9d2958637f2da75ee8607d3","src/lib.rs":"09bd3ed50fab985b1961118af323275411c150fe6d4d3c2f28a1fe3e4800621d","src/macros.rs":"498bef25c8ea468d8ae0cd89593807d9e9253385cb9456d04f8cb00b721a54cb","src/tests.rs":"468cfb1bbe8908be39280318ea03c32116924cad5811f5067a661a5bd17f77ef","src/unstable.rs":"4fe948d8f2b12844e5bb6bbdf1cea26061eca464e75e8537ed317db80a44698b","tests/amr_nb_1f.3gp":"d1423e3414ad06b69f8b58d5c916ec353ba2d0402d99dec9f1c88acc33b6a127","tests/amr_wb_1f.3gp":"be635b24097e8757b0c04d70ab28e00417ca113e86108b6c269b79b64b89bcd5","tests/bbb_sunflower_QCIF_30fps_h263_noaudio_1f.3gp":"03e5b1264d0a188d77b9e676ba3ce23a801b17aaa11c0343dfd851d6ea4e3a40","tests/clusterfuzz-testcase-minimized-mp4-6093954524250112":"af7044a470732d4e7e34ac7ab5ff038c58b66f09702cbcd774931d7766bbfd35","tests/corrupt/invalid-avif-colr-multiple.zip":"9abddcbc47fde6da20263a29b770c6a9e76c8ab8dc785ef8512f35d9cb3206ed","tests/overflow.rs":"16b591d8def1a155b3b997622f6ea255536870d99c3d8f97c51755b77a50de3c","tests/public.rs":"81be1a2c8019dff3848259e237a7e5040fd59bc1027c6d85295fccacc6ce1fe7"},"package":null}
\ No newline at end of file diff --git a/third_party/rust/mp4parse/Cargo.toml b/third_party/rust/mp4parse/Cargo.toml new file mode 100644 index 0000000000..de277ab39f --- /dev/null +++ b/third_party/rust/mp4parse/Cargo.toml @@ -0,0 +1,68 @@ +# 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 = "2018" +name = "mp4parse" +version = "0.17.0" +authors = [ + "Ralph Giles <giles@mozilla.com>", + "Matthew Gregan <kinetik@flim.org>", + "Alfredo Yang <ayang@mozilla.com>", + "Jon Bauman <jbauman@mozilla.com>", + "Bryce Seager van Dyk <bvandyk@mozilla.com>", +] +exclude = [ + "*.mp4", + "*.avif", + "av1-avif/*", +] +description = "Parser for ISO base media file format (mp4)" +documentation = "https://docs.rs/mp4parse/" +readme = "README.md" +categories = ["multimedia::video"] +license = "MPL-2.0" +repository = "https://github.com/mozilla/mp4parse-rust" + +[lib] +bench = false + +[[bench]] +name = "avif_benchmark" +harness = false + +[dependencies] +byteorder = "1.2.1" +log = "0.4" +num-traits = "0.2.14" +static_assertions = "1.1.0" + +[dependencies.bitreader] +version = "0.3.2" + +[dependencies.fallible_collections] +version = "0.4" +features = ["std_io"] + +[dev-dependencies] +criterion = "0.4" +test-assembler = "0.1.2" +walkdir = "2.3.1" + +[features] +3gpp = [] +meta-xml = [] +missing-pixi-permitted = [] +mp4v = [] +unstable-api = [] + +[badges.travis-ci] +repository = "https://github.com/mozilla/mp4parse-rust" diff --git a/third_party/rust/mp4parse/LICENSE b/third_party/rust/mp4parse/LICENSE new file mode 100644 index 0000000000..14e2f777f6 --- /dev/null +++ b/third_party/rust/mp4parse/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/third_party/rust/mp4parse/README.md b/third_party/rust/mp4parse/README.md new file mode 100644 index 0000000000..c9e65a4388 --- /dev/null +++ b/third_party/rust/mp4parse/README.md @@ -0,0 +1,2 @@ +`mp4parse` is a parser for ISO base media file format (mp4) written in rust. +See [the README in the mp4parse-rust repo](https://github.com/mozilla/mp4parse-rust/blob/master/README.md) for more details.
\ No newline at end of file diff --git a/third_party/rust/mp4parse/benches/avif_benchmark.rs b/third_party/rust/mp4parse/benches/avif_benchmark.rs new file mode 100644 index 0000000000..f7810f8caf --- /dev/null +++ b/third_party/rust/mp4parse/benches/avif_benchmark.rs @@ -0,0 +1,21 @@ +// 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 criterion::{criterion_group, criterion_main, Criterion}; +use std::fs::File; + +fn criterion_benchmark(c: &mut Criterion) { + c.bench_function("avif_largest", |b| b.iter(avif_largest)); +} + +criterion_group!(benches, criterion_benchmark); +criterion_main!(benches); + +fn avif_largest() { + let input = &mut File::open( + "av1-avif/testFiles/Netflix/avif/cosmos_frame05000_yuv444_12bpc_bt2020_pq_qlossless.avif", + ) + .expect("Unknown file"); + assert!(mp4parse::read_avif(input, mp4parse::ParseStrictness::Normal).is_ok()); +} diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/.github/workflows/encode-and-decode-daily.yml b/third_party/rust/mp4parse/link-u-avif-sample-images/.github/workflows/encode-and-decode-daily.yml new file mode 100644 index 0000000000..08733dec70 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/.github/workflows/encode-and-decode-daily.yml @@ -0,0 +1,146 @@ +name: Encode all images and decode them again weekly. + +on: + push: + schedule: + - cron: '0 20 * * 0' # https://crontab.guru/#0_2_*_*_0 + +jobs: + check-on-ubuntu: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-18.04, ubuntu-20.04] + include: + - os: ubuntu-18.04 + codename: 'bionic' + cavif-flag: '' + - os: ubuntu-20.04 + codename: 'focal' + cavif-flag: '' + steps: + - uses: actions/checkout@v2 + - name: Install required tools + run: sudo apt install -y curl jq unzip coreutils imagemagick + - name: Download latest cavif + shell: bash + run: | + runId=$(curl https://api.github.com/repos/link-u/cavif/actions/workflows/${WORKFLOW_ID}/runs | jq '[.workflow_runs[] | select( .conclusion == "success")][0].id') + artifactId=$(curl https://api.github.com/repos/link-u/cavif/actions/runs/${runId}/artifacts | jq '[.artifacts[] | select( .name == "${{ matrix.codename }}")][0].id') + curl --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' -o cavif.zip -L https://api.github.com/repos/link-u/cavif/actions/artifacts/${artifactId}/zip + unzip cavif.zip + env: + #id of https://github.com/link-u/cavif/actions?query=workflow%3A%22Build+debian+package+on+push+or+release-tags.%22 + # curl https://api.github.com/repos/link-u/cavif/actions/workflows + WORKFLOW_ID: '4521995' + - name: Download latest davif + shell: bash + run: | + runId=$(curl https://api.github.com/repos/link-u/davif/actions/workflows/${WORKFLOW_ID}/runs | jq '[.workflow_runs[] | select( .conclusion == "success")][0].id') + artifactId=$(curl https://api.github.com/repos/link-u/davif/actions/runs/${runId}/artifacts | jq '[.artifacts[] | select( .name == "${{ matrix.codename }}")][0].id') + curl --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' -o davif.zip -L https://api.github.com/repos/link-u/davif/actions/artifacts/${artifactId}/zip + unzip davif.zip + env: + #id of https://github.com/link-u/davif/actions?query=workflow%3A%22Build+debian+package+on+push+or+release-tags.%22 + # curl https://api.github.com/repos/link-u/davif/actions/workflows + WORKFLOW_ID: '452394' + - name: Install davif and cavif + run: sudo dpkg -i *.deb + - name: Use installed cavif and davif + run: | + sed -i -e 's/^CAVIF=.*$/CAVIF=cavif ${{ matrix.cavif-flag }}/' Makefile + sed -i -e 's/^DAVIF=.*$/DAVIF=davif/' Makefile + - name: Clean all images. + run: make clean + - name: Encode them all. + run: make all -j $(nproc) + - name: Decode them all. + run: make decode -j $(nproc) + - name: Copy images to upload. + run: | + mkdir -p ${{ matrix.codename }}/decoded + mkdir -p ${{ matrix.codename }}/encoded + cp decoded/* ${{ matrix.codename }}/decoded + cp *.avif ${{ matrix.codename }}/encoded + - name: Upload result + uses: actions/upload-artifact@v1 + with: + name: ${{ matrix.codename }} + path: ${{ matrix.codename }} + - name: Compare the result + run: make compare -j $(nproc) + check-on-windows: + runs-on: windows-latest + steps: + - uses: actions/checkout@v2 + - name: Install msys2 + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + path-type: inherit + - name: Install dependencies + shell: msys2 {0} + run: | + set -eux + pacman --noconfirm -S make + pacman --noconfirm -S bc + pacman --noconfirm -S mingw-w64-x86_64-imagemagick + pacman --noconfirm -S mingw-w64-x86_64-curl + pacman --noconfirm -S mingw-w64-x86_64-jq + make --version + echo '2+2' | bc + magick -version + - name: Download latest cavif + shell: bash + run: | + runId=$(curl https://api.github.com/repos/link-u/cavif/actions/workflows/${WORKFLOW_ID}/runs | jq '[.workflow_runs[] | select( .conclusion == "success")][0].id') + artifactId=$(curl https://api.github.com/repos/link-u/cavif/actions/runs/${runId}/artifacts | jq '[.artifacts[] | select( .name == "cavif-win64")][0].id') + curl --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' -o cavif.zip -L https://api.github.com/repos/link-u/cavif/actions/artifacts/${artifactId}/zip + unzip cavif.zip + rm cavif.zip + ./cavif.exe -h + env: + WORKFLOW_ID: '4517759' + - name: Download latest davif + shell: bash + run: | + runId=$(curl https://api.github.com/repos/link-u/davif/actions/workflows/${WORKFLOW_ID}/runs | jq '[.workflow_runs[] | select( .conclusion == "success")][0].id') + artifactId=$(curl https://api.github.com/repos/link-u/davif/actions/runs/${runId}/artifacts | jq '[.artifacts[] | select( .name == "davif-win64")][0].id') + curl --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' -o davif.zip -L https://api.github.com/repos/link-u/davif/actions/artifacts/${artifactId}/zip + unzip davif.zip + rm davif.zip + ./davif.exe -h + env: + WORKFLOW_ID: '4521970' + - name: Rewrite Makefile to installed cavif and davif + shell: msys2 {0} + run: | + sed -i -e 's/^CAVIF=.*$/CAVIF=.\/cavif.exe/' Makefile + sed -i -e 's/^DAVIF=.*$/DAVIF=.\/davif.exe/' Makefile + - name: Clean all images. + shell: msys2 {0} + run: make clean + - name: Encode them all + shell: msys2 {0} + run: make all -j $(nproc) + - name: Decode them all + shell: msys2 {0} + run: make decode -j $(nproc) + - name: Copy images to upload. + shell: msys2 {0} + run: | + mkdir -p win64/decoded + mkdir -p win64/encoded + cp decoded/* win64/decoded + cp *.avif win64/encoded + - name: Upload result + uses: actions/upload-artifact@v1 + with: + name: win64 + path: win64 + - name: Compare the result + shell: msys2 {0} + run: | + export PATH="/mingw64/bin:${PATH}" + make compare -j $(nproc) diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/.gitignore b/third_party/rust/mp4parse/link-u-avif-sample-images/.gitignore new file mode 100644 index 0000000000..69b09be52a --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/.gitignore @@ -0,0 +1,4 @@ +*.avif.png +/core +/decoded/ +/.alpha-masks/ diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/LICENSE.txt b/third_party/rust/mp4parse/link-u-avif-sample-images/LICENSE.txt new file mode 100644 index 0000000000..33bec29d51 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/LICENSE.txt @@ -0,0 +1,427 @@ +Attribution-ShareAlike 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More_considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution-ShareAlike 4.0 International Public +License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution-ShareAlike 4.0 International Public License ("Public +License"). To the extent this Public License may be interpreted as a +contract, You are granted the Licensed Rights in consideration of Your +acceptance of these terms and conditions, and the Licensor grants You +such rights in consideration of benefits the Licensor receives from +making the Licensed Material available under these terms and +conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. BY-SA Compatible License means a license listed at + creativecommons.org/compatiblelicenses, approved by Creative + Commons as essentially the equivalent of this Public License. + + d. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + e. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + f. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + g. License Elements means the license attributes listed in the name + of a Creative Commons Public License. The License Elements of this + Public License are Attribution and ShareAlike. + + h. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + i. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + j. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + k. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + l. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + m. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. Additional offer from the Licensor -- Adapted Material. + Every recipient of Adapted Material from You + automatically receives an offer from the Licensor to + exercise the Licensed Rights in the Adapted Material + under the conditions of the Adapter's License You apply. + + c. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + b. ShareAlike. + + In addition to the conditions in Section 3(a), if You Share + Adapted Material You produce, the following conditions also apply. + + 1. The Adapter's License You apply must be a Creative Commons + license with the same License Elements, this version or + later, or a BY-SA Compatible License. + + 2. You must include the text of, or the URI or hyperlink to, the + Adapter's License You apply. You may satisfy this condition + in any reasonable manner based on the medium, means, and + context in which You Share Adapted Material. + + 3. You may not offer or impose any additional or different terms + or conditions on, or apply any Effective Technological + Measures to, Adapted Material that restrict exercise of the + rights granted under the Adapter's License You apply. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material, + + including for purposes of Section 3(b); and + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org.
\ No newline at end of file diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/Makefile b/third_party/rust/mp4parse/link-u-avif-sample-images/Makefile new file mode 100644 index 0000000000..2cabb6101b --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/Makefile @@ -0,0 +1,911 @@ +CAVIF=../cavif/cmake-build-debug/cavif +DAVIF=../davif/cmake-build-debug/davif + +all: hato kimono fox plum; + +HATO=\ + hato.profile2.8bpc.yuv422.avif \ + hato.profile2.8bpc.yuv422.monochrome.avif \ + hato.profile2.10bpc.yuv422.avif \ + hato.profile2.10bpc.yuv422.monochrome.avif \ + hato.profile2.12bpc.yuv422.avif \ + hato.profile2.12bpc.yuv422.monochrome.avif \ + hato.profile0.8bpc.yuv420.avif \ + hato.profile0.8bpc.yuv420.monochrome.avif \ + hato.profile0.10bpc.yuv420.avif \ + hato.profile0.10bpc.yuv420.monochrome.avif + +hato: $(HATO); + +KIMONO=\ + kimono.avif \ + kimono.rotate90.avif \ + kimono.rotate270.avif \ + kimono.mirror-horizontal.avif \ + kimono.mirror-vertical.avif \ + kimono.mirror-vertical.rotate270.avif \ + kimono.crop.avif \ + kimono.mirror-vertical.rotate270.crop.avif + +kimono: $(KIMONO); + +FOX=\ + fox.profile0.8bpc.yuv420.avif \ + fox.profile0.8bpc.yuv420.odd-width.avif \ + fox.profile0.8bpc.yuv420.odd-height.avif \ + fox.profile0.8bpc.yuv420.odd-width.odd-height.avif \ + fox.profile0.8bpc.yuv420.monochrome.avif \ + fox.profile0.8bpc.yuv420.monochrome.odd-width.avif \ + fox.profile0.8bpc.yuv420.monochrome.odd-height.avif \ + fox.profile0.8bpc.yuv420.monochrome.odd-width.odd-height.avif \ + fox.profile0.10bpc.yuv420.avif \ + fox.profile0.10bpc.yuv420.odd-width.avif \ + fox.profile0.10bpc.yuv420.odd-height.avif \ + fox.profile0.10bpc.yuv420.odd-width.odd-height.avif \ + fox.profile0.10bpc.yuv420.monochrome.avif \ + fox.profile0.10bpc.yuv420.monochrome.odd-width.avif \ + fox.profile0.10bpc.yuv420.monochrome.odd-height.avif \ + fox.profile0.10bpc.yuv420.monochrome.odd-width.odd-height.avif \ + fox.profile2.12bpc.yuv420.avif \ + fox.profile2.12bpc.yuv420.odd-width.avif \ + fox.profile2.12bpc.yuv420.odd-height.avif \ + fox.profile2.12bpc.yuv420.odd-width.odd-height.avif \ + fox.profile2.12bpc.yuv420.monochrome.avif \ + fox.profile2.12bpc.yuv420.monochrome.odd-width.avif \ + fox.profile2.12bpc.yuv420.monochrome.odd-height.avif \ + fox.profile2.12bpc.yuv420.monochrome.odd-width.odd-height.avif \ + fox.profile2.8bpc.yuv422.avif \ + fox.profile2.8bpc.yuv422.odd-width.avif \ + fox.profile2.8bpc.yuv422.odd-height.avif \ + fox.profile2.8bpc.yuv422.odd-width.odd-height.avif \ + fox.profile2.8bpc.yuv422.monochrome.avif \ + fox.profile2.8bpc.yuv422.monochrome.odd-width.avif \ + fox.profile2.8bpc.yuv422.monochrome.odd-height.avif \ + fox.profile2.8bpc.yuv422.monochrome.odd-width.odd-height.avif \ + fox.profile2.10bpc.yuv422.avif \ + fox.profile2.10bpc.yuv422.odd-width.avif \ + fox.profile2.10bpc.yuv422.odd-height.avif \ + fox.profile2.10bpc.yuv422.odd-width.odd-height.avif \ + fox.profile2.10bpc.yuv422.monochrome.avif \ + fox.profile2.10bpc.yuv422.monochrome.odd-width.avif \ + fox.profile2.10bpc.yuv422.monochrome.odd-height.avif \ + fox.profile2.10bpc.yuv422.monochrome.odd-width.odd-height.avif \ + fox.profile2.12bpc.yuv422.avif \ + fox.profile2.12bpc.yuv422.odd-width.avif \ + fox.profile2.12bpc.yuv422.odd-height.avif \ + fox.profile2.12bpc.yuv422.odd-width.odd-height.avif \ + fox.profile2.12bpc.yuv422.monochrome.avif \ + fox.profile2.12bpc.yuv422.monochrome.odd-width.avif \ + fox.profile2.12bpc.yuv422.monochrome.odd-height.avif \ + fox.profile2.12bpc.yuv422.monochrome.odd-width.odd-height.avif \ + fox.profile1.8bpc.yuv444.avif \ + fox.profile1.8bpc.yuv444.odd-width.avif \ + fox.profile1.8bpc.yuv444.odd-height.avif \ + fox.profile1.8bpc.yuv444.odd-width.odd-height.avif \ + fox.profile1.10bpc.yuv444.avif \ + fox.profile1.10bpc.yuv444.odd-width.avif \ + fox.profile1.10bpc.yuv444.odd-height.avif \ + fox.profile1.10bpc.yuv444.odd-width.odd-height.avif \ + fox.profile2.12bpc.yuv444.avif \ + fox.profile2.12bpc.yuv444.odd-width.avif \ + fox.profile2.12bpc.yuv444.odd-height.avif \ + fox.profile2.12bpc.yuv444.odd-width.odd-height.avif \ + fox.profile2.12bpc.yuv444.monochrome.avif \ + fox.profile2.12bpc.yuv444.monochrome.odd-width.avif \ + fox.profile2.12bpc.yuv444.monochrome.odd-height.avif \ + fox.profile2.12bpc.yuv444.monochrome.odd-width.odd-height.avif + +fox: $(FOX); + +PLUM_LARGE=\ + plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.avif \ + plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.monochrome.avif \ + plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.avif \ + plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.monochrome.avif \ + plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.avif \ + plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.monochrome.avif \ + plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.avif \ + plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.monochrome.avif \ + plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.avif \ + plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.monochrome.avif \ + plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.avif \ + plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.monochrome.avif \ + plum-blossom-large.profile1.8bpc.yuv444.alpha-limited.avif \ + plum-blossom-large.profile1.10bpc.yuv444.alpha-limited.avif \ + plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.avif \ + plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.monochrome.avif \ + plum-blossom-large.profile0.8bpc.yuv420.alpha-full.avif \ + plum-blossom-large.profile0.8bpc.yuv420.alpha-full.monochrome.avif \ + plum-blossom-large.profile0.10bpc.yuv420.alpha-full.avif \ + plum-blossom-large.profile0.10bpc.yuv420.alpha-full.monochrome.avif \ + plum-blossom-large.profile2.12bpc.yuv420.alpha-full.avif \ + plum-blossom-large.profile2.12bpc.yuv420.alpha-full.monochrome.avif \ + plum-blossom-large.profile2.8bpc.yuv422.alpha-full.avif \ + plum-blossom-large.profile2.8bpc.yuv422.alpha-full.monochrome.avif \ + plum-blossom-large.profile2.10bpc.yuv422.alpha-full.avif \ + plum-blossom-large.profile2.10bpc.yuv422.alpha-full.monochrome.avif \ + plum-blossom-large.profile2.12bpc.yuv422.alpha-full.avif \ + plum-blossom-large.profile2.12bpc.yuv422.alpha-full.monochrome.avif \ + plum-blossom-large.profile1.8bpc.yuv444.alpha-full.avif \ + plum-blossom-large.profile1.10bpc.yuv444.alpha-full.avif \ + plum-blossom-large.profile2.12bpc.yuv444.alpha-full.avif \ + plum-blossom-large.profile2.12bpc.yuv444.alpha-full.monochrome.avif + +PLUM_SMALL=\ + plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.avif \ + plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.monochrome.avif \ + plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.avif \ + plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.monochrome.avif \ + plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.avif \ + plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.monochrome.avif \ + plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.avif \ + plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.monochrome.avif \ + plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.avif \ + plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.monochrome.avif \ + plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.avif \ + plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.monochrome.avif \ + plum-blossom-small.profile1.8bpc.yuv444.alpha-limited.avif \ + plum-blossom-small.profile1.10bpc.yuv444.alpha-limited.avif \ + plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.avif \ + plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.monochrome.avif \ + plum-blossom-small.profile0.8bpc.yuv420.alpha-full.avif \ + plum-blossom-small.profile0.8bpc.yuv420.alpha-full.monochrome.avif \ + plum-blossom-small.profile0.10bpc.yuv420.alpha-full.avif \ + plum-blossom-small.profile0.10bpc.yuv420.alpha-full.monochrome.avif \ + plum-blossom-small.profile2.12bpc.yuv420.alpha-full.avif \ + plum-blossom-small.profile2.12bpc.yuv420.alpha-full.monochrome.avif \ + plum-blossom-small.profile2.8bpc.yuv422.alpha-full.avif \ + plum-blossom-small.profile2.8bpc.yuv422.alpha-full.monochrome.avif \ + plum-blossom-small.profile2.10bpc.yuv422.alpha-full.avif \ + plum-blossom-small.profile2.10bpc.yuv422.alpha-full.monochrome.avif \ + plum-blossom-small.profile2.12bpc.yuv422.alpha-full.avif \ + plum-blossom-small.profile2.12bpc.yuv422.alpha-full.monochrome.avif \ + plum-blossom-small.profile1.8bpc.yuv444.alpha-full.avif \ + plum-blossom-small.profile1.10bpc.yuv444.alpha-full.avif \ + plum-blossom-small.profile2.12bpc.yuv444.alpha-full.avif \ + plum-blossom-small.profile2.12bpc.yuv444.alpha-full.monochrome.avif + +PLUM=$(PLUM_LARGE) $(PLUM_SMALL) + +plum: $(PLUM); + +STAR=\ + star-8bpc.avifs \ + star-8bpc-with-alpha.avifs \ + star-10bpc.avifs \ + star-10bpc-with-alpha.avifs \ + star-12bpc.avifs \ + star-12bpc-with-alpha.avifs + +star: $(STAR); + +ALL_AVIF=$(HATO) $(KIMONO) $(FOX) $(PLUM) +ALL_AVIFS=$(STAR) +DECODED_PNG=$(ALL_AVIF:%.avif=decoded/%.png) +DUMMY_CHECK_TARGETS=$(ALL_AVIF:%.avif=%.check) + +.PHONY: all clean \ + hato kimono fox plum \ + star \ + decode decode-clean decode-images \ + url hato-url kimono-url fox-url plum-url\ + compare $(DUMMY_CHECK_TARGETS) + +decode-clean: + rm -Rf decoded/ + +$(DECODED_PNG): | decoded + +decoded: + mkdir -p decoded + +decode-images: $(DECODED_PNG); + +decode: + $(MAKE) decode-clean + $(MAKE) decode-images + +compare: $(DUMMY_CHECK_TARGETS); + +decoded/%.png: %.avif + $(DAVIF) -i $< -o $@ + +$(DUMMY_CHECK_TARGETS): %.check: %.avif decoded/%.png + bash -e scripts/compare.sh $@ $(word 1,$^) $(word 2,$^) + +url: + cat Makefile | grep '^.*\?\.avif:' | sort -d | sed 's/^\(.*\)\:\s*\(.*\)$\/https\:\/\/raw.githubusercontent.com\/link-u\/avif-sample-images\/master\/\1, https\:\/\/raw.githubusercontent.com\/link-u\/avif-sample-images\/master\/\2/' + +hato-url: + $(MAKE) url | grep hato + +kimono-url: + $(MAKE) url | grep kimono + +fox-url: + $(MAKE) url | grep fox + +plum-url: + $(MAKE) url | grep plum-blossom + +clean: + rm -Rf *.avif decoded .alpha-masks + +## hato + +### YUV422 + +hato.profile2.8bpc.yuv422.avif: hato.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +hato.profile2.8bpc.yuv422.monochrome.avif: hato.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 8 --pix-fmt yuv422 --monochrome --cpu-used 0 --rate-control q --crf 18 + +hato.profile2.10bpc.yuv422.avif: hato.16bpc.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +hato.profile2.10bpc.yuv422.monochrome.avif: hato.16bpc.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 10 --pix-fmt yuv422 --monochrome --cpu-used 0 --rate-control q --crf 18 + +hato.profile2.12bpc.yuv422.avif: hato.16bpc.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +hato.profile2.12bpc.yuv422.monochrome.avif: hato.16bpc.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv422 --monochrome --cpu-used 0 --rate-control q --crf 18 + +### YUV420 + +hato.profile0.8bpc.yuv420.avif: hato.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +hato.profile0.8bpc.yuv420.monochrome.avif: hato.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --monochrome --cpu-used 0 --rate-control q --crf 18 + +hato.profile0.10bpc.yuv420.avif: hato.16bpc.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +hato.profile0.10bpc.yuv420.monochrome.avif: hato.16bpc.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 0 --bit-depth 10 --pix-fmt yuv420 --monochrome --cpu-used 0 --rate-control q --crf 18 + +## Kimono + +kimono.avif: kimono.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +kimono.rotate90.avif: kimono.rotate90.png + $(CAVIF) -i $< -o $@ --rotation 270 --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +kimono.rotate270.avif: kimono.rotate270.png + $(CAVIF) -i $< -o $@ --rotation 90 --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +kimono.mirror-horizontal.avif: kimono.mirror-horizontal.png + $(CAVIF) -i $< -o $@ --mirror horizontal --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +kimono.mirror-vertical.avif: kimono.mirror-vertical.png + $(CAVIF) -i $< -o $@ --mirror vertical --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +kimono.mirror-vertical.rotate270.avif: kimono.mirror-vertical.rotate270.png + $(CAVIF) -i $< -o $@ --mirror vertical --rotation 90 --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +kimono.crop.avif: kimono.png + $(CAVIF) -i $< -o $@ --crop-offset 103,-308 --crop-size 385,330 --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +kimono.mirror-vertical.rotate270.crop.avif: kimono.mirror-vertical.rotate270.png + $(CAVIF) -i $< -o $@ --crop-offset -308,103 --crop-size 330,385 --mirror vertical --rotation 90 --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +## Fox Parade + +### YUV420 + +#### 8bit + +fox.profile0.8bpc.yuv420.avif: fox.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile0.8bpc.yuv420.odd-width.avif: fox.odd-width.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile0.8bpc.yuv420.odd-height.avif: fox.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile0.8bpc.yuv420.odd-width.odd-height.avif: fox.odd-width.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile0.8bpc.yuv420.monochrome.avif: fox.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile0.8bpc.yuv420.monochrome.odd-width.avif: fox.odd-width.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile0.8bpc.yuv420.monochrome.odd-height.avif: fox.odd-height.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile0.8bpc.yuv420.monochrome.odd-width.odd-height.avif: fox.odd-width.odd-height.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +#### 10bit + +fox.profile0.10bpc.yuv420.avif: fox.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile0.10bpc.yuv420.odd-width.avif: fox.odd-width.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile0.10bpc.yuv420.odd-height.avif: fox.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile0.10bpc.yuv420.odd-width.odd-height.avif: fox.odd-width.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile0.10bpc.yuv420.monochrome.avif: fox.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile0.10bpc.yuv420.monochrome.odd-width.avif: fox.odd-width.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile0.10bpc.yuv420.monochrome.odd-height.avif: fox.odd-height.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile0.10bpc.yuv420.monochrome.odd-width.odd-height.avif: fox.odd-width.odd-height.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +#### 12bit + +fox.profile2.12bpc.yuv420.avif: fox.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv420.odd-width.avif: fox.odd-width.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv420.odd-height.avif: fox.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv420.odd-width.odd-height.avif: fox.odd-width.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv420.monochrome.avif: fox.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv420.monochrome.odd-width.avif: fox.odd-width.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv420.monochrome.odd-height.avif: fox.odd-height.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv420.monochrome.odd-width.odd-height.avif: fox.odd-width.odd-height.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +### YUV422 + +#### 8bit + +fox.profile2.8bpc.yuv422.avif: fox.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.8bpc.yuv422.odd-width.avif: fox.odd-width.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.8bpc.yuv422.odd-height.avif: fox.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.8bpc.yuv422.odd-width.odd-height.avif: fox.odd-width.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.8bpc.yuv422.monochrome.avif: fox.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.8bpc.yuv422.monochrome.odd-width.avif: fox.odd-width.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.8bpc.yuv422.monochrome.odd-height.avif: fox.odd-height.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.8bpc.yuv422.monochrome.odd-width.odd-height.avif: fox.odd-width.odd-height.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +#### 10bit + +fox.profile2.10bpc.yuv422.avif: fox.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.10bpc.yuv422.odd-width.avif: fox.odd-width.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.10bpc.yuv422.odd-height.avif: fox.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.10bpc.yuv422.odd-width.odd-height.avif: fox.odd-width.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.10bpc.yuv422.monochrome.avif: fox.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.10bpc.yuv422.monochrome.odd-width.avif: fox.odd-width.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.10bpc.yuv422.monochrome.odd-height.avif: fox.odd-height.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.10bpc.yuv422.monochrome.odd-width.odd-height.avif: fox.odd-width.odd-height.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +#### 12bit + +fox.profile2.12bpc.yuv422.avif: fox.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv422.odd-width.avif: fox.odd-width.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv422.odd-height.avif: fox.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv422.odd-width.odd-height.avif: fox.odd-width.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv422.monochrome.avif: fox.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv422.monochrome.odd-width.avif: fox.odd-width.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv422.monochrome.odd-height.avif: fox.odd-height.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv422.monochrome.odd-width.odd-height.avif: fox.odd-width.odd-height.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +### YUV444 + +#### 8bit + +fox.profile1.8bpc.yuv444.avif: fox.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 1 --bit-depth 8 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +fox.profile1.8bpc.yuv444.odd-width.avif: fox.odd-width.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 1 --bit-depth 8 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +fox.profile1.8bpc.yuv444.odd-height.avif: fox.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 1 --bit-depth 8 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +fox.profile1.8bpc.yuv444.odd-width.odd-height.avif: fox.odd-width.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 1 --bit-depth 8 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +#### 10bit + +fox.profile1.10bpc.yuv444.avif: fox.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 1 --bit-depth 10 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +fox.profile1.10bpc.yuv444.odd-width.avif: fox.odd-width.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 1 --bit-depth 10 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +fox.profile1.10bpc.yuv444.odd-height.avif: fox.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 1 --bit-depth 10 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +fox.profile1.10bpc.yuv444.odd-width.odd-height.avif: fox.odd-width.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 1 --bit-depth 10 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +#### 12bit + +fox.profile2.12bpc.yuv444.avif: fox.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv444.odd-width.avif: fox.odd-width.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv444.odd-height.avif: fox.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv444.odd-width.odd-height.avif: fox.odd-width.odd-height.png + $(CAVIF) -i $< -o $@ --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv444.monochrome.avif: fox.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv444.monochrome.odd-width.avif: fox.odd-width.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv444.monochrome.odd-height.avif: fox.odd-height.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +fox.profile2.12bpc.yuv444.monochrome.odd-width.odd-height.avif: fox.odd-width.odd-height.png + $(CAVIF) -i $< -o $@ --monochrome --tune psnr --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +## Alpha mask + +.alpha-masks: + mkdir -p .alpha-masks + +$(PLUM): | .alpha-masks + +## Plum blossom - large version + +### YUV420 + +#### 8bit + +plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.monochrome.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 --monochrome + +plum-blossom-large.profile0.8bpc.yuv420.alpha-full.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-large.profile0.8bpc.yuv420.alpha-full.monochrome.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 --monochrome + +#### 10bit + +plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.monochrome.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 --monochrome + +plum-blossom-large.profile0.10bpc.yuv420.alpha-full.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-large.profile0.10bpc.yuv420.alpha-full.monochrome.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 --monochrome + +#### 12bit + +plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.monochrome.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 --monochrome + +plum-blossom-large.profile2.12bpc.yuv420.alpha-full.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-large.profile2.12bpc.yuv420.alpha-full.monochrome.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 --monochrome + +## YUV422 + +#### 8bit + +plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.monochrome.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 --monochrome + +plum-blossom-large.profile2.8bpc.yuv422.alpha-full.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-large.profile2.8bpc.yuv422.alpha-full.monochrome.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 --monochrome + +#### 10bit + +plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.monochrome.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 --monochrome + +plum-blossom-large.profile2.10bpc.yuv422.alpha-full.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-large.profile2.10bpc.yuv422.alpha-full.monochrome.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 --monochrome + +#### 12bit + +plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.monochrome.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 --monochrome + +plum-blossom-large.profile2.12bpc.yuv422.alpha-full.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-large.profile2.12bpc.yuv422.alpha-full.monochrome.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 --monochrome + +### YUV444 + +#### 8bit + +plum-blossom-large.profile1.8bpc.yuv444.alpha-limited.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 1 --bit-depth 8 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-large.profile1.8bpc.yuv444.alpha-full.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 1 --bit-depth 8 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +#### 10bit + +plum-blossom-large.profile1.10bpc.yuv444.alpha-limited.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 1 --bit-depth 10 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-large.profile1.10bpc.yuv444.alpha-full.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 1 --bit-depth 10 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +#### 12bit + +plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.monochrome.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 --monochrome + +plum-blossom-large.profile2.12bpc.yuv444.alpha-full.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-large.profile2.12bpc.yuv444.alpha-full.monochrome.avif: plum-blossom-large.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 --monochrome + +## Plum blossom - small version + +### YUV420 + +#### 8bit + +plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.monochrome.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 --monochrome + +plum-blossom-small.profile0.8bpc.yuv420.alpha-full.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-small.profile0.8bpc.yuv420.alpha-full.monochrome.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 0 --bit-depth 8 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 --monochrome + +#### 10bit + +plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.monochrome.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 --monochrome + +plum-blossom-small.profile0.10bpc.yuv420.alpha-full.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-small.profile0.10bpc.yuv420.alpha-full.monochrome.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 0 --bit-depth 10 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 --monochrome + +#### 12bit + +plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.monochrome.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 --monochrome + +plum-blossom-small.profile2.12bpc.yuv420.alpha-full.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-small.profile2.12bpc.yuv420.alpha-full.monochrome.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv420 --cpu-used 0 --rate-control q --crf 18 --monochrome + +## YUV422 + +#### 8bit + +plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.monochrome.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 --monochrome + +plum-blossom-small.profile2.8bpc.yuv422.alpha-full.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-small.profile2.8bpc.yuv422.alpha-full.monochrome.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 --monochrome + +#### 10bit + +plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.monochrome.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 --monochrome + +plum-blossom-small.profile2.10bpc.yuv422.alpha-full.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-small.profile2.10bpc.yuv422.alpha-full.monochrome.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 --monochrome + +#### 12bit + +plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.monochrome.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 --monochrome + +plum-blossom-small.profile2.12bpc.yuv422.alpha-full.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-small.profile2.12bpc.yuv422.alpha-full.monochrome.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv422 --cpu-used 0 --rate-control q --crf 18 --monochrome + +### YUV444 + +#### 8bit + +plum-blossom-small.profile1.8bpc.yuv444.alpha-limited.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 1 --bit-depth 8 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-small.profile1.8bpc.yuv444.alpha-full.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 8 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 1 --bit-depth 8 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +#### 10bit + +plum-blossom-small.profile1.10bpc.yuv444.alpha-limited.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 1 --bit-depth 10 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-small.profile1.10bpc.yuv444.alpha-full.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 10 --pix-fmt yuv422 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 1 --bit-depth 10 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +#### 12bit + +plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.monochrome.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --lossless --monochrome + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 --monochrome + +plum-blossom-small.profile2.12bpc.yuv444.alpha-full.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 + +plum-blossom-small.profile2.12bpc.yuv444.alpha-full.monochrome.avif: plum-blossom-small.png + $(CAVIF) -i $< -o .alpha-masks/$@ --encode-target alpha --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --lossless --monochrome --enable-full-color-range + $(CAVIF) -i $< -o $@ --tune psnr --encode-target image --attach-alpha .alpha-masks/$@ --profile 2 --bit-depth 12 --pix-fmt yuv444 --cpu-used 0 --rate-control q --crf 18 --monochrome + +star-8bpc.avifs: star.input.txt + $(eval TMP := $(shell mktemp -d)) + ~/umi/src/git.ffmpeg.org/ffmpeg/ffmpeg -r 10 -f concat -i star.input.txt -strict -2 -vcodec av1 -pix_fmt yuv420p -color_range jpeg -b:v 0 -crf 0 -lossless 1 $(TMP)/star.mp4 + # You need the latest version of gpac. + # Go to https://github.com/gpac/gpac + # then, `make deb -j32` + MP4Box -add-image $(TMP)/star.mp4:id=1:primary -new $@ + MP4Box -ab avis -ab msf1 -ab miaf -ab MA1B -rb mif1 -brand avis $@ + MP4Box -add $(TMP)/star.mp4:hdlr=pict:ccst:name="GPAC avifs" $@ + rm -Rfv $(TMP) + +# FIXME(ledya-z): WORK IN PROGRESS +star-8bpc-with-alpha.avifs: star.input.txt + $(eval TMP := $(shell mktemp -d)) + ~/umi/src/git.ffmpeg.org/ffmpeg/ffmpeg -r 10 -f concat -i star.input.txt -strict -2 -vcodec av1 -pix_fmt yuv420p -color_range mpeg -b:v 0 -crf 0 -lossless 1 "$(TMP)/star-video.mp4" + # FIXME(ledyba-z): It does not generate monochrome OBUs. + ~/umi/src/git.ffmpeg.org/ffmpeg/ffmpeg -r 10 -f concat -i star.input.txt -strict -2 -vcodec av1 -pix_fmt gray -color_range mpeg -b:v 0 -crf 0 -lossless 1 "$(TMP)/star-alpha.mp4" + # You need the latest version of gpac. + # Go to https://github.com/gpac/gpac + # then, `make deb -j32` + + MP4Box -raw-layer "1:output=$(TMP)/star-video" "$(TMP)/star-video.mp4" + MP4Box -raw-layer "1:output=$(TMP)/star-alpha" "$(TMP)/star-alpha.mp4" + + MP4Box -add-image "$(TMP)/star-alpha.av1:id=3:ref=auxl,4:alpha:name=Alpha" -add-image "$(TMP)/star-video.av1:id=4:name=Color" -set-primary 4 -ab avif -new $@ + MP4Box -add "$(TMP)/star-video.av1:hdlr=pict:ccst:name=\"GPAC avifs\"" -add "$(TMP)/star-alpha.av1:hdlr=auxv:ccst:alpha:name=\"GPAC avifs alpha\"" -ref 2:auxl:1 -ab msf1 -ab miaf -ab MA1B -brand avis $@ + rm -Rfv $(TMP) + +star-10bpc.avifs: star.input.txt + $(eval TMP := $(shell mktemp -d)) + ~/umi/src/git.ffmpeg.org/ffmpeg/ffmpeg -r 10 -f concat -i star.input.txt -strict -2 -vcodec av1 -pix_fmt yuv422p10 -color_range jpeg -b:v 0 -crf 0 -lossless 1 $(TMP)/star.mp4 + # You need the latest version of gpac. + # Go to https://github.com/gpac/gpac + # then, `make deb -j32` + MP4Box -add-image $(TMP)/star.mp4:id=1:primary -new $@ + MP4Box -ab avis -ab msf1 -ab miaf -ab MA1B -rb mif1 -brand avis $@ + MP4Box -add $(TMP)/star.mp4:hdlr=pict:ccst:name="GPAC avifs" $@ + rm -Rfv $(TMP) + +# FIXME(ledya-z): WORK IN PROGRESS +star-10bpc-with-alpha.avifs: star.input.txt + $(eval TMP := $(shell mktemp -d)) + ~/umi/src/git.ffmpeg.org/ffmpeg/ffmpeg -r 10 -f concat -i star.input.txt -strict -2 -vcodec av1 -pix_fmt yuv422p10 -color_range mpeg -b:v 0 -crf 0 -lossless 1 "$(TMP)/star-video.mp4" + # FIXME(ledyba-z): It does not generate monochrome OBUs. + ~/umi/src/git.ffmpeg.org/ffmpeg/ffmpeg -r 10 -f concat -i star.input.txt -strict -2 -vcodec av1 -pix_fmt gray10 -color_range mpeg -b:v 0 -crf 0 -lossless 1 "$(TMP)/star-alpha.mp4" + # You need the latest version of gpac. + # Go to https://github.com/gpac/gpac + # then, `make deb -j32` + + MP4Box -raw-layer "1:output=$(TMP)/star-video" "$(TMP)/star-video.mp4" + MP4Box -raw-layer "1:output=$(TMP)/star-alpha" "$(TMP)/star-alpha.mp4" + + MP4Box -add-image "$(TMP)/star-alpha.av1:id=3:ref=auxl,4:alpha:name=Alpha" -add-image "$(TMP)/star-video.av1:id=4:name=Color" -set-primary 4 -ab avif -new $@ + MP4Box -add "$(TMP)/star-video.av1:hdlr=pict:ccst:name=\"GPAC avifs\"" -add "$(TMP)/star-alpha.av1:hdlr=auxv:ccst:alpha:name=\"GPAC avifs alpha\"" -ref 2:auxl:1 -ab msf1 -ab miaf -ab MA1B -brand avis $@ + rm -Rfv $(TMP) + +star-12bpc.avifs: star.input.txt + $(eval TMP := $(shell mktemp -d)) + ~/umi/src/git.ffmpeg.org/ffmpeg/ffmpeg -r 10 -f concat -i star.input.txt -strict -2 -vcodec av1 -pix_fmt yuv444p12 -color_range jpeg -b:v 0 -crf 0 -lossless 1 $(TMP)/star.mp4 + # You need the latest version of gpac. + # Go to https://github.com/gpac/gpac + # then, `make deb -j32` + MP4Box -add-image $(TMP)/star.mp4:id=1:primary -new $@ + MP4Box -ab avis -ab msf1 -ab miaf -ab MA1B -rb mif1 -brand avis $@ + MP4Box -add $(TMP)/star.mp4:hdlr=pict:ccst:name="GPAC avifs" $@ + rm -Rfv $(TMP) + +# FIXME(ledya-z): WORK IN PROGRESS +star-12bpc-with-alpha.avifs: star.input.txt + $(eval TMP := $(shell mktemp -d)) + ~/umi/src/git.ffmpeg.org/ffmpeg/ffmpeg -r 10 -f concat -i star.input.txt -strict -2 -vcodec av1 -pix_fmt yuv444p12 -color_range mpeg -b:v 0 -crf 0 -lossless 1 "$(TMP)/star-video.mp4" + # FIXME(ledyba-z): It does not generate monochrome OBUs. + ~/umi/src/git.ffmpeg.org/ffmpeg/ffmpeg -r 10 -f concat -i star.input.txt -strict -2 -vcodec av1 -pix_fmt gray12 -color_range mpeg -b:v 0 -crf 0 -lossless 1 "$(TMP)/star-alpha.mp4" + # You need the latest version of gpac. + # Go to https://github.com/gpac/gpac + # then, `make deb -j32` + + MP4Box -raw-layer "1:output=$(TMP)/star-video" "$(TMP)/star-video.mp4" + MP4Box -raw-layer "1:output=$(TMP)/star-alpha" "$(TMP)/star-alpha.mp4" + + MP4Box -add-image "$(TMP)/star-alpha.av1:id=3:ref=auxl,4:alpha:name=Alpha" -add-image "$(TMP)/star-video.av1:id=4:name=Color" -set-primary 4 -ab avif -new $@ + MP4Box -add "$(TMP)/star-video.av1:hdlr=pict:ccst:name=\"GPAC avifs\"" -add "$(TMP)/star-alpha.av1:hdlr=auxv:ccst:alpha:name=\"GPAC avifs alpha\"" -ref 2:auxl:1 -ab msf1 -ab miaf -ab MA1B -brand avis $@ + rm -Rfv $(TMP) diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/README.md b/third_party/rust/mp4parse/link-u-avif-sample-images/README.md new file mode 100644 index 0000000000..6b803ec823 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/README.md @@ -0,0 +1,582 @@ +# AVIF Example files. + +![Encode all images and decode them again weekly.](https://github.com/link-u/avif-sample-images/workflows/Encode%20all%20images%20and%20decode%20them%20again%20weekly./badge.svg) + +- 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. +- Most images are licensed under [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en), but some files are licensed different license. Please check. + +[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) + +## hato + +![hato.jpg](hato.jpg) + + - size: 3082x2048 + - License: [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en) + - 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 | file | +|---------|-----------|------------|--------------------------------------------------| +| 0 | 8 | | [here](hato.profile0.8bpc.yuv420.avif) | +| 0 | 8 | YES | [here](hato.profile0.8bpc.yuv420.monochromeavif) | +| 0 | 10 | | [here](hato.profile0.10bpc.yuv420.avif) | +| 0 | 10 | YES | [here](hato.profile0.10bpc.yuv420.avif) | + +#### YUV422 + +| profile | bit depth | Monochrome | file | +|---------|-----------|------------|---------------------------------------------------| +| 2 | 8 | | [here](hato.profile2.8bpc.yuv422.avif) | +| 2 | 8 | YES | [here](hato.profile2.8bpc.yuv422.monochrome.avif) | +| 2 | 10 | | [here](hato.profile2.10bpc.yuv422.avif) | +| 2 | 10 | YES | [here](hato.profile2.10bpc.yuv422.avif) | +| 2 | 12 | | [here](hato.profile2.12bpc.yuv422.avif) | +| 2 | 12 | YES | [here](hato.profile2.12bpc.yuv422.avif) | + +#### URLS + +You can obtain this list with `make hato-url`. + +``` +https://raw.githubusercontent.com/link-u/avif-sample-images/master/hato.profile0.10bpc.yuv420.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/hato.profile0.10bpc.yuv420.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/hato.profile0.8bpc.yuv420.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/hato.profile0.8bpc.yuv420.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/hato.profile2.10bpc.yuv422.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/hato.profile2.10bpc.yuv422.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/hato.profile2.12bpc.yuv422.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/hato.profile2.12bpc.yuv422.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/hato.profile2.8bpc.yuv422.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/hato.profile2.8bpc.yuv422.monochrome.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 + - License: [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en) + - 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 + +[kimono.crop.avif](kimono.crop.avif) + +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. + +[kimono.mirror-vertical.rotate270.crop.avif](kimono.mirror-vertical.rotate270.crop.avif) + +[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. + +![kimono.crop.png](kimono.crop.png) + +### URLS + +You can obtain this list with `make kimono-url`. + +``` +https://raw.githubusercontent.com/link-u/avif-sample-images/master/kimono.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/kimono.crop.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/kimono.mirror-horizontal.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/kimono.mirror-vertical.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/kimono.mirror-vertical.rotate270.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/kimono.mirror-vertical.rotate270.crop.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/kimono.rotate270.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/kimono.rotate90.avif +``` + +## 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 + - License: [CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/deed.en) + - 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-height.png](fox.odd-height.png) + - size: 1204 x 799 + +#### Odd-Width x Odd-Height + + - [fox.odd-width.odd-height.png](fox.odd-width.odd-height.png) + - size: 1203 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 | 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) | + +### URLs + +You can obtain this list with `make fox-url`. + +``` +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.10bpc.yuv420.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.10bpc.yuv420.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.10bpc.yuv420.monochrome.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.10bpc.yuv420.monochrome.odd-width.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.10bpc.yuv420.monochrome.odd-width.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.10bpc.yuv420.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.10bpc.yuv420.odd-width.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.10bpc.yuv420.odd-width.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.8bpc.yuv420.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.8bpc.yuv420.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.8bpc.yuv420.monochrome.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.8bpc.yuv420.monochrome.odd-width.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.8bpc.yuv420.monochrome.odd-width.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.8bpc.yuv420.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.8bpc.yuv420.odd-width.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile0.8bpc.yuv420.odd-width.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile1.10bpc.yuv444.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile1.10bpc.yuv444.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile1.10bpc.yuv444.odd-width.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile1.10bpc.yuv444.odd-width.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile1.8bpc.yuv444.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile1.8bpc.yuv444.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile1.8bpc.yuv444.odd-width.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile1.8bpc.yuv444.odd-width.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.10bpc.yuv422.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.10bpc.yuv422.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.10bpc.yuv422.monochrome.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.10bpc.yuv422.monochrome.odd-width.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.10bpc.yuv422.monochrome.odd-width.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.10bpc.yuv422.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.10bpc.yuv422.odd-width.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.10bpc.yuv422.odd-width.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv420.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv420.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv420.monochrome.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv420.monochrome.odd-width.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv420.monochrome.odd-width.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv420.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv420.odd-width.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv420.odd-width.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv422.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv422.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv422.monochrome.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv422.monochrome.odd-width.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv422.monochrome.odd-width.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv422.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv422.odd-width.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv422.odd-width.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv444.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv444.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv444.monochrome.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv444.monochrome.odd-width.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv444.monochrome.odd-width.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv444.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv444.odd-width.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.12bpc.yuv444.odd-width.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.8bpc.yuv422.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.8bpc.yuv422.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.8bpc.yuv422.monochrome.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.8bpc.yuv422.monochrome.odd-width.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.8bpc.yuv422.monochrome.odd-width.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.8bpc.yuv422.odd-height.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.8bpc.yuv422.odd-width.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/fox.profile2.8bpc.yuv422.odd-width.odd-height.avif +``` +## Plum blossom - test images for alpha planes + +### Original (SVG) + +[![plum-blossom.svg](./plum-blossom.svg)](plum-blossom.svg) + + - License: [CC-BY](https://creativecommons.org/licenses/by/4.0/deed.en) + - Author: Ryo Hirafuji ([@ledyba-z](https://github.com/ledyba-z)) + +#### Large Version (PNG) + + - [plum-blossom-large.png](plum-blossom-large.png) + - size: 2048x2048 + +#### Small Version (PNG) + + - [plum-blossom-small.png](plum-blossom-small.png) + - size: 128x128 + +### AVIF version (Large Version) + +#### Limited-ranged alpha + +| profile | bit depth | pix fmt | Monochrome | alpha | file | +|---------|-----------|---------|------------|-------- |--------------------------------------------------------------------------------| +| 0 | 8 | YUV420 | | limited | [here](plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.avif) | +| 0 | 8 | YUV420 | YES | limited | [here](plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.monochrome.avif) | +| 0 | 10 | YUV420 | | limited | [here](plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.avif) | +| 0 | 10 | YUV420 | YES | limited | [here](plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.monochrome.avif) | +| 2 | 12 | YUV420 | | limited | [here](plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.avif) | +| 2 | 12 | YUV420 | YES | limited | [here](plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.monochrome.avif) | +| 2 | 8 | YUV422 | | limited | [here](plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.avif) | +| 2 | 8 | YUV422 | YES | limited | [here](plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.monochrome.avif) | +| 2 | 10 | YUV422 | | limited | [here](plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.avif) | +| 2 | 10 | YUV422 | YES | limited | [here](plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.monochrome.avif) | +| 2 | 12 | YUV422 | | limited | [here](plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.avif) | +| 2 | 12 | YUV422 | YES | limited | [here](plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.monochrome.avif) | +| 1 | 8 | YUV444 | | limited | [here](plum-blossom-large.profile1.8bpc.yuv444.alpha-limited.avif) | +| 1 | 10 | YUV444 | | limited | [here](plum-blossom-large.profile1.10bpc.yuv444.alpha-limited.avif) | +| 2 | 12 | YUV444 | | limited | [here](plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.avif) | +| 2 | 12 | YUV444 | YES | limited | [here](plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.monochrome.avif) | + +#### Full-ranged alpha + +| profile | bit depth | pix fmt | Monochrome | alpha | file | +|---------|-----------|---------|------------|-------- |--------------------------------------------------------------------------------| +| 0 | 8 | YUV420 | | full | [here](plum-blossom-large.profile0.8bpc.yuv420.alpha-full.avif) | +| 0 | 8 | YUV420 | YES | full | [here](plum-blossom-large.profile0.8bpc.yuv420.alpha-full.monochrome.avif) | +| 0 | 10 | YUV420 | | full | [here](plum-blossom-large.profile0.10bpc.yuv420.alpha-full.avif) | +| 0 | 10 | YUV420 | YES | full | [here](plum-blossom-large.profile0.10bpc.yuv420.alpha-full.monochrome.avif) | +| 2 | 12 | YUV420 | | full | [here](plum-blossom-large.profile2.12bpc.yuv420.alpha-full.avif) | +| 2 | 12 | YUV420 | YES | full | [here](plum-blossom-large.profile2.12bpc.yuv420.alpha-full.monochrome.avif) | +| 2 | 8 | YUV422 | | full | [here](plum-blossom-large.profile2.8bpc.yuv422.alpha-full.avif) | +| 2 | 8 | YUV422 | YES | full | [here](plum-blossom-large.profile2.8bpc.yuv422.alpha-full.monochrome.avif) | +| 2 | 10 | YUV422 | | full | [here](plum-blossom-large.profile2.10bpc.yuv422.alpha-full.avif) | +| 2 | 10 | YUV422 | YES | full | [here](plum-blossom-large.profile2.10bpc.yuv422.alpha-full.monochrome.avif) | +| 2 | 12 | YUV422 | | full | [here](plum-blossom-large.profile2.12bpc.yuv422.alpha-full.avif) | +| 2 | 12 | YUV422 | YES | full | [here](plum-blossom-large.profile2.12bpc.yuv422.alpha-full.monochrome.avif) | +| 1 | 8 | YUV444 | | full | [here](plum-blossom-large.profile1.8bpc.yuv444.alpha-full.avif) | +| 1 | 10 | YUV444 | | full | [here](plum-blossom-large.profile1.10bpc.yuv444.alpha-full.avif) | +| 2 | 12 | YUV444 | | full | [here](plum-blossom-large.profile2.12bpc.yuv444.alpha-full.avif) | +| 2 | 12 | YUV444 | YES | full | [here](plum-blossom-large.profile2.12bpc.yuv444.alpha-full.monochrome.avif) | + +### AVIF version (Small Version) + +#### Limited-ranged alpha + +| profile | bit depth | pix fmt | Monochrome | alpha | file | +|---------|-----------|---------|------------|-------- |--------------------------------------------------------------------------------| +| 0 | 8 | YUV420 | | limited | [here](plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.avif) | +| 0 | 8 | YUV420 | YES | limited | [here](plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.monochrome.avif) | +| 0 | 10 | YUV420 | | limited | [here](plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.avif) | +| 0 | 10 | YUV420 | YES | limited | [here](plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.monochrome.avif) | +| 2 | 12 | YUV420 | | limited | [here](plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.avif) | +| 2 | 12 | YUV420 | YES | limited | [here](plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.monochrome.avif) | +| 2 | 8 | YUV422 | | limited | [here](plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.avif) | +| 2 | 8 | YUV422 | YES | limited | [here](plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.monochrome.avif) | +| 2 | 10 | YUV422 | | limited | [here](plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.avif) | +| 2 | 10 | YUV422 | YES | limited | [here](plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.monochrome.avif) | +| 2 | 12 | YUV422 | | limited | [here](plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.avif) | +| 2 | 12 | YUV422 | YES | limited | [here](plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.monochrome.avif) | +| 1 | 8 | YUV444 | | limited | [here](plum-blossom-small.profile1.8bpc.yuv444.alpha-limited.avif) | +| 1 | 10 | YUV444 | | limited | [here](plum-blossom-small.profile1.10bpc.yuv444.alpha-limited.avif) | +| 2 | 12 | YUV444 | | limited | [here](plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.avif) | +| 2 | 12 | YUV444 | YES | limited | [here](plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.monochrome.avif) | + +#### Full-ranged alpha + +| profile | bit depth | pix fmt | Monochrome | alpha | file | +|---------|-----------|---------|------------|-------- |--------------------------------------------------------------------------------| +| 0 | 8 | YUV420 | | full | [here](plum-blossom-small.profile0.8bpc.yuv420.alpha-full.avif) | +| 0 | 8 | YUV420 | YES | full | [here](plum-blossom-small.profile0.8bpc.yuv420.alpha-full.monochrome.avif) | +| 0 | 10 | YUV420 | | full | [here](plum-blossom-small.profile0.10bpc.yuv420.alpha-full.avif) | +| 0 | 10 | YUV420 | YES | full | [here](plum-blossom-small.profile0.10bpc.yuv420.alpha-full.monochrome.avif) | +| 2 | 12 | YUV420 | | full | [here](plum-blossom-small.profile2.12bpc.yuv420.alpha-full.avif) | +| 2 | 12 | YUV420 | YES | full | [here](plum-blossom-small.profile2.12bpc.yuv420.alpha-full.monochrome.avif) | +| 2 | 8 | YUV422 | | full | [here](plum-blossom-small.profile2.8bpc.yuv422.alpha-full.avif) | +| 2 | 8 | YUV422 | YES | full | [here](plum-blossom-small.profile2.8bpc.yuv422.alpha-full.monochrome.avif) | +| 2 | 10 | YUV422 | | full | [here](plum-blossom-small.profile2.10bpc.yuv422.alpha-full.avif) | +| 2 | 10 | YUV422 | YES | full | [here](plum-blossom-small.profile2.10bpc.yuv422.alpha-full.monochrome.avif) | +| 2 | 12 | YUV422 | | full | [here](plum-blossom-small.profile2.12bpc.yuv422.alpha-full.avif) | +| 2 | 12 | YUV422 | YES | full | [here](plum-blossom-small.profile2.12bpc.yuv422.alpha-full.monochrome.avif) | +| 1 | 8 | YUV444 | | full | [here](plum-blossom-small.profile1.8bpc.yuv444.alpha-full.avif) | +| 1 | 10 | YUV444 | | full | [here](plum-blossom-small.profile1.10bpc.yuv444.alpha-full.avif) | +| 2 | 12 | YUV444 | | full | [here](plum-blossom-small.profile2.12bpc.yuv444.alpha-full.avif) | +| 2 | 12 | YUV444 | YES | full | [here](plum-blossom-small.profile2.12bpc.yuv444.alpha-full.monochrome.avif) | + +### URLs + +You can obtain this list with `make plum-url`. + +``` +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile0.10bpc.yuv420.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile0.10bpc.yuv420.alpha-full.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile0.8bpc.yuv420.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile0.8bpc.yuv420.alpha-full.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile1.10bpc.yuv444.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile1.10bpc.yuv444.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile1.8bpc.yuv444.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile1.8bpc.yuv444.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.10bpc.yuv422.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.10bpc.yuv422.alpha-full.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.12bpc.yuv420.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.12bpc.yuv420.alpha-full.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.12bpc.yuv422.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.12bpc.yuv422.alpha-full.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.12bpc.yuv444.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.12bpc.yuv444.alpha-full.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.8bpc.yuv422.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.8bpc.yuv422.alpha-full.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile0.10bpc.yuv420.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile0.10bpc.yuv420.alpha-full.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile0.8bpc.yuv420.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile0.8bpc.yuv420.alpha-full.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile1.10bpc.yuv444.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile1.10bpc.yuv444.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile1.8bpc.yuv444.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile1.8bpc.yuv444.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.10bpc.yuv422.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.10bpc.yuv422.alpha-full.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.12bpc.yuv420.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.12bpc.yuv420.alpha-full.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.12bpc.yuv422.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.12bpc.yuv422.alpha-full.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.12bpc.yuv444.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.12bpc.yuv444.alpha-full.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.8bpc.yuv422.alpha-full.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.8bpc.yuv422.alpha-full.monochrome.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.avif +https://raw.githubusercontent.com/link-u/avif-sample-images/master/plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.monochrome.avif +``` + +## Red at 12 o'clock with color profile - ICC Profile tests + +![red-at-12-oclock-with-color-profile.jpg](red-at-12-oclock-with-color-profile) + + - License: GNU LGPL v2.1 or 2 claused BSD License + - Author: Tony Payne <tpayne@chromium.org> + - [commit](https://chromium.googlesource.com/chromium/src/+/e89ab1941644ff34b262cac05f23e82b7e249377) + +### AVIF version + + - [red-at-12-oclock-with-color-profile-lossy.avif](red-at-12-oclock-with-color-profile-lossy.avif) + - [red-at-12-oclock-with-color-profile-8bpc.avif](red-at-12-oclock-with-color-profile-8bpc.avif) + - [red-at-12-oclock-with-color-profile-10bpc.avif](red-at-12-oclock-with-color-profile-10bpc.avif) + - [red-at-12-oclock-with-color-profile-12bpc.avif](red-at-12-oclock-with-color-profile-12bpc.avif) + +## Twinkle Star - Image Sequence Test + +[![star.gif](star.gif)](star.gif) + + - [AV1 mp4 version](star.mp4) + +### Original (SVG) + +[![star.svg](./star.svg)](star.svg) + + - License: [CC-BY](https://creativecommons.org/licenses/by/4.0/deed.en) + - Author: Ryo Hirafuji ([@ledyba-z](https://github.com/ledyba-z)) + - Special Thanks: [Shigatake's Pixel Art Lesson](http://shigatake.sakura.ne.jp/gallery/dot/dot_1.html) + +### AVIFS version + +#### Normal + +- [star-8bpc.avifs](star-8bpc.avifs) + - YUV420 + - full-ranged color +- [star-10bpc.avifs](star-10bpc.avifs) + - YUV422 + - full-ranged color +- [star-12bpc.avifs](star-12bpc.avifs) + - YUV444 + - full-ranged color + +- [star-8bpc-with-alpha.avifs](star-8bpc-with-alpha.avifs) + - YUV420 + - 8bit + - limited-ranged color + - limited-ranged alpha +- [star-8bpc-with-alpha.avifs](star-10bpc-with-alpha.avifs) + - YUV422 + - 10bit + - limited-ranged color + - limited-ranged alpha +- [star-8bpc-with-alpha.avifs](star-12bpc-with-alpha.avifs) + - YUV444 + - 12bit + - limited-ranged color + - limited-ranged alpha diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.jpg b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.jpg Binary files differnew file mode 100644 index 0000000000..748f2786b5 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.jpg diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.odd-height.png b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.odd-height.png Binary files differnew file mode 100644 index 0000000000..831ab7529a --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.odd-height.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.odd-width.odd-height.png b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.odd-width.odd-height.png Binary files differnew file mode 100644 index 0000000000..c858205691 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.odd-width.odd-height.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.odd-width.png b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.odd-width.png Binary files differnew file mode 100644 index 0000000000..f5fe9fce9b --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.odd-width.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.png b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.png Binary files differnew file mode 100644 index 0000000000..a1e59a2a11 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/hato.16bpc.png b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.16bpc.png Binary files differnew file mode 100644 index 0000000000..2ce57305bd --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.16bpc.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/hato.jpg b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.jpg Binary files differnew file mode 100644 index 0000000000..f760cb9a8e --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.jpg diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/hato.png b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.png Binary files differnew file mode 100644 index 0000000000..3667c72467 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/images.html b/third_party/rust/mp4parse/link-u-avif-sample-images/images.html new file mode 100644 index 0000000000..e5a69ae6db --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/images.html @@ -0,0 +1,745 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <title>All images</title> + </head> + <body> +<h1>AVIF images</h1> + <h2>hato.profile2.8bpc.yuv422.avif<h2> + <h3>PNG version<h3> + <img src="./hato.png" width="400"> + <h3>AVIF version<h3> + <img src="./hato.profile2.8bpc.yuv422.avif" width="400"> + <h2>hato.profile2.8bpc.yuv422.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./hato.png" width="400"> + <h3>AVIF version<h3> + <img src="./hato.profile2.8bpc.yuv422.monochrome.avif" width="400"> + <h2>hato.profile2.10bpc.yuv422.avif<h2> + <h3>PNG version<h3> + <img src="./hato.16bpc.png" width="400"> + <h3>AVIF version<h3> + <img src="./hato.profile2.10bpc.yuv422.avif" width="400"> + <h2>hato.profile2.10bpc.yuv422.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./hato.16bpc.png" width="400"> + <h3>AVIF version<h3> + <img src="./hato.profile2.10bpc.yuv422.monochrome.avif" width="400"> + <h2>hato.profile2.12bpc.yuv422.avif<h2> + <h3>PNG version<h3> + <img src="./hato.16bpc.png" width="400"> + <h3>AVIF version<h3> + <img src="./hato.profile2.12bpc.yuv422.avif" width="400"> + <h2>hato.profile2.12bpc.yuv422.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./hato.16bpc.png" width="400"> + <h3>AVIF version<h3> + <img src="./hato.profile2.12bpc.yuv422.monochrome.avif" width="400"> + <h2>hato.profile0.8bpc.yuv420.avif<h2> + <h3>PNG version<h3> + <img src="./hato.png" width="400"> + <h3>AVIF version<h3> + <img src="./hato.profile0.8bpc.yuv420.avif" width="400"> + <h2>hato.profile0.8bpc.yuv420.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./hato.png" width="400"> + <h3>AVIF version<h3> + <img src="./hato.profile0.8bpc.yuv420.monochrome.avif" width="400"> + <h2>hato.profile0.10bpc.yuv420.avif<h2> + <h3>PNG version<h3> + <img src="./hato.16bpc.png" width="400"> + <h3>AVIF version<h3> + <img src="./hato.profile0.10bpc.yuv420.avif" width="400"> + <h2>hato.profile0.10bpc.yuv420.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./hato.16bpc.png" width="400"> + <h3>AVIF version<h3> + <img src="./hato.profile0.10bpc.yuv420.monochrome.avif" width="400"> + <h2>fox.profile0.8bpc.yuv420.avif<h2> + <h3>PNG version<h3> + <img src="./fox.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile0.8bpc.yuv420.avif" width="400"> + <h2>fox.profile0.8bpc.yuv420.odd-width.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile0.8bpc.yuv420.odd-width.avif" width="400"> + <h2>fox.profile0.8bpc.yuv420.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile0.8bpc.yuv420.odd-height.avif" width="400"> + <h2>fox.profile0.8bpc.yuv420.odd-width.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile0.8bpc.yuv420.odd-width.odd-height.avif" width="400"> + <h2>fox.profile0.8bpc.yuv420.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./fox.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile0.8bpc.yuv420.monochrome.avif" width="400"> + <h2>fox.profile0.8bpc.yuv420.monochrome.odd-width.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile0.8bpc.yuv420.monochrome.odd-width.avif" width="400"> + <h2>fox.profile0.8bpc.yuv420.monochrome.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile0.8bpc.yuv420.monochrome.odd-height.avif" width="400"> + <h2>fox.profile0.8bpc.yuv420.monochrome.odd-width.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile0.8bpc.yuv420.monochrome.odd-width.odd-height.avif" width="400"> + <h2>fox.profile0.10bpc.yuv420.avif<h2> + <h3>PNG version<h3> + <img src="./fox.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile0.10bpc.yuv420.avif" width="400"> + <h2>fox.profile0.10bpc.yuv420.odd-width.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile0.10bpc.yuv420.odd-width.avif" width="400"> + <h2>fox.profile0.10bpc.yuv420.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile0.10bpc.yuv420.odd-height.avif" width="400"> + <h2>fox.profile0.10bpc.yuv420.odd-width.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile0.10bpc.yuv420.odd-width.odd-height.avif" width="400"> + <h2>fox.profile0.10bpc.yuv420.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./fox.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile0.10bpc.yuv420.monochrome.avif" width="400"> + <h2>fox.profile0.10bpc.yuv420.monochrome.odd-width.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile0.10bpc.yuv420.monochrome.odd-width.avif" width="400"> + <h2>fox.profile0.10bpc.yuv420.monochrome.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile0.10bpc.yuv420.monochrome.odd-height.avif" width="400"> + <h2>fox.profile0.10bpc.yuv420.monochrome.odd-width.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile0.10bpc.yuv420.monochrome.odd-width.odd-height.avif" width="400"> + <h2>fox.profile2.12bpc.yuv420.avif<h2> + <h3>PNG version<h3> + <img src="./fox.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv420.avif" width="400"> + <h2>fox.profile2.12bpc.yuv420.odd-width.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv420.odd-width.avif" width="400"> + <h2>fox.profile2.12bpc.yuv420.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv420.odd-height.avif" width="400"> + <h2>fox.profile2.12bpc.yuv420.odd-width.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv420.odd-width.odd-height.avif" width="400"> + <h2>fox.profile2.12bpc.yuv420.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./fox.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv420.monochrome.avif" width="400"> + <h2>fox.profile2.12bpc.yuv420.monochrome.odd-width.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv420.monochrome.odd-width.avif" width="400"> + <h2>fox.profile2.12bpc.yuv420.monochrome.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv420.monochrome.odd-height.avif" width="400"> + <h2>fox.profile2.12bpc.yuv420.monochrome.odd-width.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv420.monochrome.odd-width.odd-height.avif" width="400"> + <h2>fox.profile2.8bpc.yuv422.avif<h2> + <h3>PNG version<h3> + <img src="./fox.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.8bpc.yuv422.avif" width="400"> + <h2>fox.profile2.8bpc.yuv422.odd-width.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.8bpc.yuv422.odd-width.avif" width="400"> + <h2>fox.profile2.8bpc.yuv422.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.8bpc.yuv422.odd-height.avif" width="400"> + <h2>fox.profile2.8bpc.yuv422.odd-width.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.8bpc.yuv422.odd-width.odd-height.avif" width="400"> + <h2>fox.profile2.8bpc.yuv422.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./fox.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.8bpc.yuv422.monochrome.avif" width="400"> + <h2>fox.profile2.8bpc.yuv422.monochrome.odd-width.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.8bpc.yuv422.monochrome.odd-width.avif" width="400"> + <h2>fox.profile2.8bpc.yuv422.monochrome.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.8bpc.yuv422.monochrome.odd-height.avif" width="400"> + <h2>fox.profile2.8bpc.yuv422.monochrome.odd-width.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.8bpc.yuv422.monochrome.odd-width.odd-height.avif" width="400"> + <h2>fox.profile2.10bpc.yuv422.avif<h2> + <h3>PNG version<h3> + <img src="./fox.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.10bpc.yuv422.avif" width="400"> + <h2>fox.profile2.10bpc.yuv422.odd-width.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.10bpc.yuv422.odd-width.avif" width="400"> + <h2>fox.profile2.10bpc.yuv422.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.10bpc.yuv422.odd-height.avif" width="400"> + <h2>fox.profile2.10bpc.yuv422.odd-width.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.10bpc.yuv422.odd-width.odd-height.avif" width="400"> + <h2>fox.profile2.10bpc.yuv422.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./fox.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.10bpc.yuv422.monochrome.avif" width="400"> + <h2>fox.profile2.10bpc.yuv422.monochrome.odd-width.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.10bpc.yuv422.monochrome.odd-width.avif" width="400"> + <h2>fox.profile2.10bpc.yuv422.monochrome.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.10bpc.yuv422.monochrome.odd-height.avif" width="400"> + <h2>fox.profile2.10bpc.yuv422.monochrome.odd-width.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.10bpc.yuv422.monochrome.odd-width.odd-height.avif" width="400"> + <h2>fox.profile2.12bpc.yuv422.avif<h2> + <h3>PNG version<h3> + <img src="./fox.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv422.avif" width="400"> + <h2>fox.profile2.12bpc.yuv422.odd-width.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv422.odd-width.avif" width="400"> + <h2>fox.profile2.12bpc.yuv422.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv422.odd-height.avif" width="400"> + <h2>fox.profile2.12bpc.yuv422.odd-width.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv422.odd-width.odd-height.avif" width="400"> + <h2>fox.profile2.12bpc.yuv422.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./fox.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv422.monochrome.avif" width="400"> + <h2>fox.profile2.12bpc.yuv422.monochrome.odd-width.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv422.monochrome.odd-width.avif" width="400"> + <h2>fox.profile2.12bpc.yuv422.monochrome.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv422.monochrome.odd-height.avif" width="400"> + <h2>fox.profile2.12bpc.yuv422.monochrome.odd-width.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv422.monochrome.odd-width.odd-height.avif" width="400"> + <h2>fox.profile1.8bpc.yuv444.avif<h2> + <h3>PNG version<h3> + <img src="./fox.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile1.8bpc.yuv444.avif" width="400"> + <h2>fox.profile1.8bpc.yuv444.odd-width.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile1.8bpc.yuv444.odd-width.avif" width="400"> + <h2>fox.profile1.8bpc.yuv444.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile1.8bpc.yuv444.odd-height.avif" width="400"> + <h2>fox.profile1.8bpc.yuv444.odd-width.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile1.8bpc.yuv444.odd-width.odd-height.avif" width="400"> + <h2>fox.profile1.10bpc.yuv444.avif<h2> + <h3>PNG version<h3> + <img src="./fox.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile1.10bpc.yuv444.avif" width="400"> + <h2>fox.profile1.10bpc.yuv444.odd-width.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile1.10bpc.yuv444.odd-width.avif" width="400"> + <h2>fox.profile1.10bpc.yuv444.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile1.10bpc.yuv444.odd-height.avif" width="400"> + <h2>fox.profile1.10bpc.yuv444.odd-width.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile1.10bpc.yuv444.odd-width.odd-height.avif" width="400"> + <h2>fox.profile2.12bpc.yuv444.avif<h2> + <h3>PNG version<h3> + <img src="./fox.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv444.avif" width="400"> + <h2>fox.profile2.12bpc.yuv444.odd-width.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv444.odd-width.avif" width="400"> + <h2>fox.profile2.12bpc.yuv444.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv444.odd-height.avif" width="400"> + <h2>fox.profile2.12bpc.yuv444.odd-width.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv444.odd-width.odd-height.avif" width="400"> + <h2>fox.profile2.12bpc.yuv444.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./fox.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv444.monochrome.avif" width="400"> + <h2>fox.profile2.12bpc.yuv444.monochrome.odd-width.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv444.monochrome.odd-width.avif" width="400"> + <h2>fox.profile2.12bpc.yuv444.monochrome.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv444.monochrome.odd-height.avif" width="400"> + <h2>fox.profile2.12bpc.yuv444.monochrome.odd-width.odd-height.avif<h2> + <h3>PNG version<h3> + <img src="./fox.odd-width.odd-height.png" width="400"> + <h3>AVIF version<h3> + <img src="./fox.profile2.12bpc.yuv444.monochrome.odd-width.odd-height.avif" width="400"> + <h2>plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.avif" width="400"> + <h2>plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.monochrome.avif" width="400"> + <h2>plum-blossom-large.profile0.8bpc.yuv420.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile0.8bpc.yuv420.alpha-full.avif" width="400"> + <h2>plum-blossom-large.profile0.8bpc.yuv420.alpha-full.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile0.8bpc.yuv420.alpha-full.monochrome.avif" width="400"> + <h2>plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.avif" width="400"> + <h2>plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.monochrome.avif" width="400"> + <h2>plum-blossom-large.profile0.10bpc.yuv420.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile0.10bpc.yuv420.alpha-full.avif" width="400"> + <h2>plum-blossom-large.profile0.10bpc.yuv420.alpha-full.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile0.10bpc.yuv420.alpha-full.monochrome.avif" width="400"> + <h2>plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.avif" width="400"> + <h2>plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.monochrome.avif" width="400"> + <h2>plum-blossom-large.profile2.12bpc.yuv420.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.12bpc.yuv420.alpha-full.avif" width="400"> + <h2>plum-blossom-large.profile2.12bpc.yuv420.alpha-full.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.12bpc.yuv420.alpha-full.monochrome.avif" width="400"> + <h2>plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.avif" width="400"> + <h2>plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.monochrome.avif" width="400"> + <h2>plum-blossom-large.profile2.8bpc.yuv422.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.8bpc.yuv422.alpha-full.avif" width="400"> + <h2>plum-blossom-large.profile2.8bpc.yuv422.alpha-full.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.8bpc.yuv422.alpha-full.monochrome.avif" width="400"> + <h2>plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.avif" width="400"> + <h2>plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.monochrome.avif" width="400"> + <h2>plum-blossom-large.profile2.10bpc.yuv422.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.10bpc.yuv422.alpha-full.avif" width="400"> + <h2>plum-blossom-large.profile2.10bpc.yuv422.alpha-full.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.10bpc.yuv422.alpha-full.monochrome.avif" width="400"> + <h2>plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.avif" width="400"> + <h2>plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.monochrome.avif" width="400"> + <h2>plum-blossom-large.profile2.12bpc.yuv422.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.12bpc.yuv422.alpha-full.avif" width="400"> + <h2>plum-blossom-large.profile2.12bpc.yuv422.alpha-full.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.12bpc.yuv422.alpha-full.monochrome.avif" width="400"> + <h2>plum-blossom-large.profile1.8bpc.yuv444.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile1.8bpc.yuv444.alpha-limited.avif" width="400"> + <h2>plum-blossom-large.profile1.8bpc.yuv444.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile1.8bpc.yuv444.alpha-full.avif" width="400"> + <h2>plum-blossom-large.profile1.10bpc.yuv444.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile1.10bpc.yuv444.alpha-limited.avif" width="400"> + <h2>plum-blossom-large.profile1.10bpc.yuv444.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile1.10bpc.yuv444.alpha-full.avif" width="400"> + <h2>plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.avif" width="400"> + <h2>plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.monochrome.avif" width="400"> + <h2>plum-blossom-large.profile2.12bpc.yuv444.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.12bpc.yuv444.alpha-full.avif" width="400"> + <h2>plum-blossom-large.profile2.12bpc.yuv444.alpha-full.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-large.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-large.profile2.12bpc.yuv444.alpha-full.monochrome.avif" width="400"> + <h2>plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.avif" width="400"> + <h2>plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.monochrome.avif" width="400"> + <h2>plum-blossom-small.profile0.8bpc.yuv420.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile0.8bpc.yuv420.alpha-full.avif" width="400"> + <h2>plum-blossom-small.profile0.8bpc.yuv420.alpha-full.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile0.8bpc.yuv420.alpha-full.monochrome.avif" width="400"> + <h2>plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.avif" width="400"> + <h2>plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.monochrome.avif" width="400"> + <h2>plum-blossom-small.profile0.10bpc.yuv420.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile0.10bpc.yuv420.alpha-full.avif" width="400"> + <h2>plum-blossom-small.profile0.10bpc.yuv420.alpha-full.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile0.10bpc.yuv420.alpha-full.monochrome.avif" width="400"> + <h2>plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.avif" width="400"> + <h2>plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.monochrome.avif" width="400"> + <h2>plum-blossom-small.profile2.12bpc.yuv420.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.12bpc.yuv420.alpha-full.avif" width="400"> + <h2>plum-blossom-small.profile2.12bpc.yuv420.alpha-full.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.12bpc.yuv420.alpha-full.monochrome.avif" width="400"> + <h2>plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.avif" width="400"> + <h2>plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.monochrome.avif" width="400"> + <h2>plum-blossom-small.profile2.8bpc.yuv422.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.8bpc.yuv422.alpha-full.avif" width="400"> + <h2>plum-blossom-small.profile2.8bpc.yuv422.alpha-full.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.8bpc.yuv422.alpha-full.monochrome.avif" width="400"> + <h2>plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.avif" width="400"> + <h2>plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.monochrome.avif" width="400"> + <h2>plum-blossom-small.profile2.10bpc.yuv422.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.10bpc.yuv422.alpha-full.avif" width="400"> + <h2>plum-blossom-small.profile2.10bpc.yuv422.alpha-full.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.10bpc.yuv422.alpha-full.monochrome.avif" width="400"> + <h2>plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.avif" width="400"> + <h2>plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.monochrome.avif" width="400"> + <h2>plum-blossom-small.profile2.12bpc.yuv422.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.12bpc.yuv422.alpha-full.avif" width="400"> + <h2>plum-blossom-small.profile2.12bpc.yuv422.alpha-full.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.12bpc.yuv422.alpha-full.monochrome.avif" width="400"> + <h2>plum-blossom-small.profile1.8bpc.yuv444.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile1.8bpc.yuv444.alpha-limited.avif" width="400"> + <h2>plum-blossom-small.profile1.8bpc.yuv444.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile1.8bpc.yuv444.alpha-full.avif" width="400"> + <h2>plum-blossom-small.profile1.10bpc.yuv444.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile1.10bpc.yuv444.alpha-limited.avif" width="400"> + <h2>plum-blossom-small.profile1.10bpc.yuv444.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile1.10bpc.yuv444.alpha-full.avif" width="400"> + <h2>plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.avif" width="400"> + <h2>plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.monochrome.avif" width="400"> + <h2>plum-blossom-small.profile2.12bpc.yuv444.alpha-full.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.12bpc.yuv444.alpha-full.avif" width="400"> + <h2>plum-blossom-small.profile2.12bpc.yuv444.alpha-full.monochrome.avif<h2> + <h3>PNG version<h3> + <img src="./plum-blossom-small.png" width="400"> + <h3>AVIF version<h3> + <img src="./plum-blossom-small.profile2.12bpc.yuv444.alpha-full.monochrome.avif" width="400"> +<h2>kimono.avif</h2> + <h3>PNG version<h3> + <img src="./kimono.png" width="400"> + <h3>AVIF version<h3> + <img src="./kimono.avif" width="400"> +<h2>kimono.rotate90.avif</h2> + <h3>PNG version<h3> + <img src="./kimono.png" width="400"> + <h3>AVIF version<h3> + <img src="./kimono.rotate90.avif" width="400"> +<h2>kimono.rotate270.avif</h2> + <h3>PNG version<h3> + <img src="./kimono.png" width="400"> + <h3>AVIF version<h3> + <img src="./kimono.rotate270.avif" width="400"> +<h2>kimono.mirror-horizontal.avif</h2> + <h3>PNG version<h3> + <img src="./kimono.png" width="400"> + <h3>AVIF version<h3> + <img src="./kimono.mirror-horizontal.avif" width="400"> +<h2>kimono.mirror-vertical.rotate270.avif</h2> + <h3>PNG version<h3> + <img src="./kimono.png" width="400"> + <h3>AVIF version<h3> + <img src="./kimono.mirror-vertical.rotate270.avif" width="400"> +<h2>kimono.crop.avif</h2> + <h3>PNG version<h3> + <img src="./kimono.crop.png" width="400"> + <h3>AVIF version<h3> + <img src="./kimono.crop.avif" width="400"> +<h2>kimono.mirror-vertical.rotate270.crop.avif</h2> + <h3>PNG version<h3> + <img src="./kimono.crop.png" width="400"> + <h3>AVIF version<h3> + <img src="./kimono.mirror-vertical.rotate270.crop.avif" width="400"> +<h1>AVIFS images</h1> +<h2>star.avifs<h2> + <h3>GIF version</h3> + <img src="./star.gif" width="400"> + <h3>AVIFS version (without alpha)</h3> + <img src="./star.avifs" width="400"> +<h2>star-with-alpha.avifs<h2> + <h3>GIF version</h3> + <img src="./star.gif" width="400"> + <h3>AVIFS version (with alpha)</h3> + <img src="./star-with-alpha.avifs" width="400"> + </body> +</html> diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.crop.png b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.crop.png Binary files differnew file mode 100644 index 0000000000..ea47f4c9a2 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.crop.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.jpg b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.jpg Binary files differnew file mode 100644 index 0000000000..6c4894a43d --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.jpg diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-horizontal.png b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-horizontal.png Binary files differnew file mode 100644 index 0000000000..de61ec89f0 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-horizontal.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.png b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.png Binary files differnew file mode 100644 index 0000000000..2eeb69d89b --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.rotate270.png b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.rotate270.png Binary files differnew file mode 100644 index 0000000000..bbd9dc766e --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.rotate270.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.png b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.png Binary files differnew file mode 100644 index 0000000000..2bddce177a --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate270.png b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate270.png Binary files differnew file mode 100644 index 0000000000..e9916b527c --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate270.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate90.png b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate90.png Binary files differnew file mode 100644 index 0000000000..02ede290b4 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate90.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.png b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.png Binary files differnew file mode 100644 index 0000000000..22f0c8627a --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.png b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.png Binary files differnew file mode 100644 index 0000000000..901b3edd4a --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom.svg b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom.svg new file mode 100644 index 0000000000..e6b9b07f2f --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom.svg @@ -0,0 +1,176 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1000mm" + height="1000mm" + viewBox="0 0 1000 1000" + version="1.1" + id="svg8" + inkscape:version="0.92.4 (5da689c313, 2019-01-14)" + sodipodi:docname="plum-blossom.svg" + inkscape:export-filename="/home/psi/src/github.com/link-u/avif-sample-images/plum-blossom-small.png" + inkscape:export-xdpi="13.0048" + inkscape:export-ydpi="13.0048"> + <defs + id="defs2" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.35" + inkscape:cx="1367.1429" + inkscape:cy="2035.7143" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + inkscape:window-width="2560" + inkscape:window-height="1403" + inkscape:window-x="2560" + inkscape:window-y="0" + inkscape:window-maximized="1" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(0,703)"> + <circle + id="path10" + cx="500" + cy="-453" + style="fill:#ffd5d5;stroke-width:0.24955437" + r="200" /> + <circle + id="path10-9" + cx="262.2359" + cy="-280.25427" + style="fill:#ffd5d5;stroke-width:0.24955437" + r="200" /> + <circle + id="path10-9-1" + cx="737.76416" + cy="-280.25427" + style="fill:#ffd5d5;stroke-width:0.24955437" + r="200" /> + <circle + id="path10-9-2" + cx="353.05371" + cy="-0.74575806" + style="fill:#ffd5d5;stroke-width:0.24955437" + r="200" /> + <circle + id="path10-9-2-7" + cx="646.94629" + cy="-0.74575806" + style="fill:#ffd5d5;stroke-width:0.24955437" + r="200" /> + <rect + style="fill:#ffffff;stroke-width:0.23664318" + id="rect215" + width="20" + height="160" + x="493.0238" + y="-430.27975" /> + <rect + style="fill:#ffffff;stroke-width:0.26458332" + id="rect217" + width="2.2678571" + height="10.583333" + x="495.46429" + y="-374.6012" /> + <circle + style="fill:#ffeeaa;stroke-width:0.22826084" + id="path221" + cx="503.0238" + cy="-437.10715" + r="30" /> + <rect + style="fill:#ffffff;stroke-width:0.23664318" + id="rect215-0" + width="20" + height="160" + x="336.11542" + y="186.29741" + transform="rotate(-72.000001)" /> + <circle + style="fill:#ffeeaa;stroke-width:0.22826084" + id="path221-9" + cx="346.11542" + cy="179.47002" + r="30.000002" + transform="rotate(-72.000001)" /> + <rect + style="fill:#ffffff;stroke-width:0.23664318" + id="rect215-0-3" + width="20" + height="160" + x="-49.702778" + y="-764.44379" + transform="rotate(72)" /> + <circle + style="fill:#ffeeaa;stroke-width:0.22826084" + id="path221-9-6" + cx="-39.702778" + cy="-771.27118" + r="30.000002" + transform="rotate(72)" /> + <rect + style="fill:#ffffff;stroke-width:0.23664318" + id="rect215-0-3-0" + width="20" + height="160" + x="-539.49518" + y="-358.71228" + transform="rotate(144)" /> + <circle + style="fill:#ffeeaa;stroke-width:0.22826084" + id="path221-9-6-6" + cx="-529.49518" + cy="-365.53967" + r="30.000002" + transform="rotate(144)" /> + <rect + style="fill:#ffffff;stroke-width:0.23664318" + id="rect215-0-3-0-2" + width="20" + height="160" + x="-292.17621" + y="228.38646" + transform="rotate(-144)" /> + <circle + style="fill:#ffeeaa;stroke-width:0.22826084" + id="path221-9-6-6-6" + cx="-282.17621" + cy="221.55907" + r="30.000002" + transform="rotate(-144)" /> + <circle + style="fill:#ffeeaa;stroke-width:0.31818181" + id="path4004" + cx="500" + cy="-203" + r="70" /> + </g> +</svg> diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile.jpg b/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile.jpg Binary files differnew file mode 100644 index 0000000000..20fee46c00 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile.jpg diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile.png b/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile.png Binary files differnew file mode 100644 index 0000000000..5320cc8dfb --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/scripts/compare.sh b/third_party/rust/mp4parse/link-u-avif-sample-images/scripts/compare.sh new file mode 100644 index 0000000000..513d19fd2b --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/scripts/compare.sh @@ -0,0 +1,23 @@ +##!/usr/bin/env bash + +avif=$2 +decoded=$3 + +orig=$(cat Makefile | grep "^${avif}" | sed "s/^${avif}: \(.*\)$/\1/") + +if (echo ${avif} | grep "monochrome"); then + # FIMXE(ledyba-z): compare monochrome images. + score="100.0" +elif (echo ${avif} | grep "\(rotate\|mirror\|crop\)"); then + # FIMXE(ledyba-z): compare transformed images + score="100.0" +else + score=$(compare -metric PSNR ${orig} ${decoded} NULL: 2>&1 || true) +fi +if test $(echo "${score} >= 35.0" | bc -l) -eq 1; then + echo "Passing: ${decoded}: ${score}" + exit 0 +else + echo "Failed: ${decoded}: ${score} (vs ${orig})" + exit -1 +fi diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/star-10bpc-with-alpha.avifs b/third_party/rust/mp4parse/link-u-avif-sample-images/star-10bpc-with-alpha.avifs Binary files differnew file mode 100644 index 0000000000..05c5a45c49 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star-10bpc-with-alpha.avifs diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/star-10bpc.avifs b/third_party/rust/mp4parse/link-u-avif-sample-images/star-10bpc.avifs Binary files differnew file mode 100644 index 0000000000..2ddcf5047f --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star-10bpc.avifs diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/star-12bpc-with-alpha.avifs b/third_party/rust/mp4parse/link-u-avif-sample-images/star-12bpc-with-alpha.avifs Binary files differnew file mode 100644 index 0000000000..c6fe1a53cd --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star-12bpc-with-alpha.avifs diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/star-12bpc.avifs b/third_party/rust/mp4parse/link-u-avif-sample-images/star-12bpc.avifs Binary files differnew file mode 100644 index 0000000000..86dfc2ee80 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star-12bpc.avifs diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/star-8bpc-with-alpha.avifs b/third_party/rust/mp4parse/link-u-avif-sample-images/star-8bpc-with-alpha.avifs Binary files differnew file mode 100644 index 0000000000..bb9dfa5c33 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star-8bpc-with-alpha.avifs diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/star-8bpc.avifs b/third_party/rust/mp4parse/link-u-avif-sample-images/star-8bpc.avifs Binary files differnew file mode 100644 index 0000000000..6c0b0e3dc0 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star-8bpc.avifs diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/star.gif b/third_party/rust/mp4parse/link-u-avif-sample-images/star.gif Binary files differnew file mode 100644 index 0000000000..52076cafdd --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star.gif diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/star.input.txt b/third_party/rust/mp4parse/link-u-avif-sample-images/star.input.txt new file mode 100644 index 0000000000..ce797225f6 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star.input.txt @@ -0,0 +1,9 @@ +file 'star.png' +duration 0.1 +file 'star90.png' +duration 0.1 +file 'star180.png' +duration 0.1 +file 'star270.png' +duration 0.1 +file 'star.png' diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/star.png b/third_party/rust/mp4parse/link-u-avif-sample-images/star.png Binary files differnew file mode 100644 index 0000000000..468dcde005 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/star.svg b/third_party/rust/mp4parse/link-u-avif-sample-images/star.svg new file mode 100644 index 0000000000..8bca22a420 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star.svg @@ -0,0 +1,83 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="310.57089mm" + height="310.57089mm" + viewBox="0 0 310.57089 310.57089" + version="1.1" + id="svg8" + inkscape:version="0.92.4 (5da689c313, 2019-01-14)" + sodipodi:docname="star.svg" + inkscape:export-filename="/home/psi/g/lu/avif-sample-images/star.png" + inkscape:export-xdpi="13.0048" + inkscape:export-ydpi="13.0048"> + <defs + id="defs2" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="0.35" + inkscape:cx="-377.14283" + inkscape:cy="435.7143" + inkscape:document-units="mm" + inkscape:current-layer="layer1" + showgrid="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:window-width="2560" + inkscape:window-height="1369" + inkscape:window-x="2560" + inkscape:window-y="1474" + inkscape:window-maximized="1" + inkscape:snap-others="true" /> + <metadata + id="metadata5"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="レイヤー 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(7.4768269e-6,13.570917)"> + <path + sodipodi:type="star" + style="fill:#ffeeaa;stroke-width:0.26458332" + id="path10" + sodipodi:sides="5" + sodipodi:cx="155.28574" + sodipodi:cy="141.64303" + sodipodi:r1="154.21428" + sodipodi:r2="77.10714" + sodipodi:arg1="-1.5707963" + sodipodi:arg2="-0.9424778" + inkscape:flatsided="false" + inkscape:rounded="0.05" + inkscape:randomized="0" + d="m 155.28574,-12.571243 c 5.12042,0 41.17993,88.823585 45.32244,91.833292 4.1425,3.009707 99.76176,9.85635 101.34405,14.726158 1.5823,4.869808 -71.75095,66.612433 -73.33325,71.482243 -1.58229,4.86981 21.45414,97.92485 17.31163,100.93456 -4.1425,3.00971 -85.52445,-47.65483 -90.64487,-47.65483 -5.12042,0 -86.50238,50.66453 -90.644886,47.65483 C 60.498348,263.3953 83.534785,170.34026 81.952489,165.47045 80.370192,160.60064 7.0369457,98.858007 8.6192422,93.988199 10.201539,89.118391 105.82079,82.271756 109.9633,79.26205 c 4.1425,-3.009707 40.20202,-91.833293 45.32244,-91.833293 z" + inkscape:export-xdpi="13.0048" + inkscape:export-ydpi="13.0048" + inkscape:transform-center-y="-14.661668" /> + </g> +</svg> diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/star180.png b/third_party/rust/mp4parse/link-u-avif-sample-images/star180.png Binary files differnew file mode 100644 index 0000000000..2c5f522211 --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star180.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/star270.png b/third_party/rust/mp4parse/link-u-avif-sample-images/star270.png Binary files differnew file mode 100644 index 0000000000..8812b9bdeb --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star270.png diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/star90.png b/third_party/rust/mp4parse/link-u-avif-sample-images/star90.png Binary files differnew file mode 100644 index 0000000000..93526260ba --- /dev/null +++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star90.png diff --git a/third_party/rust/mp4parse/src/boxes.rs b/third_party/rust/mp4parse/src/boxes.rs new file mode 100644 index 0000000000..7a9f22bed1 --- /dev/null +++ b/third_party/rust/mp4parse/src/boxes.rs @@ -0,0 +1,241 @@ +// 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, Eq)] + pub enum BoxType { + $($(#[$attr])* $boxenum),*, + UnknownBox(u32), + } + + impl From<u32> for BoxType { + fn from(t: u32) -> BoxType { + use self::BoxType::*; + match t { + $($(#[$attr])* $boxtype => $boxenum),*, + _ => UnknownBox(t), + } + } + } + + impl From<BoxType> for u32 { + fn from(b: BoxType) -> u32 { + use self::BoxType::*; + match b { + $($(#[$attr])* $boxenum => $boxtype),*, + UnknownBox(t) => t, + } + } + } + + } +} + +impl fmt::Debug for BoxType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let fourcc: FourCC = From::from(*self); + fourcc.fmt(f) + } +} + +#[derive(Default, Eq, Hash, PartialEq, Clone)] +pub struct FourCC { + pub value: [u8; 4], +} + +impl From<u32> for FourCC { + fn from(number: u32) -> FourCC { + FourCC { + value: number.to_be_bytes(), + } + } +} + +impl From<BoxType> for FourCC { + fn from(t: BoxType) -> FourCC { + let box_num: u32 = Into::into(t); + From::from(box_num) + } +} + +impl From<[u8; 4]> for FourCC { + fn from(v: [u8; 4]) -> FourCC { + FourCC { value: v } + } +} + +impl fmt::Debug for FourCC { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match std::str::from_utf8(&self.value) { + Ok(s) => f.write_str(s), + Err(_) => self.value.fmt(f), + } + } +} + +impl fmt::Display for FourCC { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(std::str::from_utf8(&self.value).unwrap_or("null")) + } +} + +impl PartialEq<&[u8; 4]> for FourCC { + fn eq(&self, other: &&[u8; 4]) -> bool { + self.value.eq(*other) + } +} + +box_database!( + FileTypeBox 0x6674_7970, // "ftyp" + MediaDataBox 0x6d64_6174, // "mdat" + PrimaryItemBox 0x7069_746d, // "pitm" + ItemDataBox 0x6964_6174, // "idat" + 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" + TrackReferenceBox 0x7472_6566, // "tref" + AuxiliaryBox 0x6175_786C, // "auxl" + EditBox 0x6564_7473, // "edts" + MediaBox 0x6d64_6961, // "mdia" + EditListBox 0x656c_7374, // "elst" + MediaHeaderBox 0x6d64_6864, // "mdhd" + HandlerBox 0x6864_6c72, // "hdlr" + MediaInformationBox 0x6d69_6e66, // "minf" + ItemReferenceBox 0x6972_6566, // "iref" + ItemPropertiesBox 0x6970_7270, // "iprp" + ItemPropertyContainerBox 0x6970_636f, // "ipco" + ItemPropertyAssociationBox 0x6970_6d61, // "ipma" + ColourInformationBox 0x636f_6c72, // "colr" + ImageSpatialExtentsProperty 0x6973_7065, // "ispe" + PixelAspectRatioBox 0x7061_7370, // "pasp" + PixelInformationBox 0x7069_7869, // "pixi" + AuxiliaryTypeProperty 0x6175_7843, // "auxC" + CleanApertureBox 0x636c_6170, // "clap" + ImageRotation 0x6972_6f74, // "irot" + ImageMirror 0x696d_6972, // "imir" + OperatingPointSelectorProperty 0x6131_6f70, // "a1op" + AV1LayeredImageIndexingProperty 0x6131_6c78, // "a1lx" + LayerSelectorProperty 0x6c73_656c, // "lsel" + SampleTableBox 0x7374_626c, // "stbl" + SampleDescriptionBox 0x7374_7364, // "stsd" + TimeToSampleBox 0x7374_7473, // "stts" + SampleToChunkBox 0x7374_7363, // "stsc" + SampleSizeBox 0x7374_737a, // "stsz" + ChunkOffsetBox 0x7374_636f, // "stco" + ChunkLargeOffsetBox 0x636f_3634, // "co64" + SyncSampleBox 0x7374_7373, // "stss" + AVCSampleEntry 0x6176_6331, // "avc1" + AVC3SampleEntry 0x6176_6333, // "avc3" - Need to check official name in spec. + AVCConfigurationBox 0x6176_6343, // "avcC" + H263SampleEntry 0x7332_3633, // "s263" + H263SpecificBox 0x6432_3633, // "d263" + HEV1SampleEntry 0x6865_7631, // "hev1" + HVC1SampleEntry 0x6876_6331, // "hvc1" + HEVCConfigurationBox 0x6876_6343, // "hvcC" + MP4AudioSampleEntry 0x6d70_3461, // "mp4a" + MP4VideoSampleEntry 0x6d70_3476, // "mp4v" + #[cfg(feature = "3gpp")] + AMRNBSampleEntry 0x7361_6d72, // "samr" - AMR narrow-band + #[cfg(feature = "3gpp")] + AMRWBSampleEntry 0x7361_7762, // "sawb" - AMR wide-band + #[cfg(feature = "3gpp")] + AMRSpecificBox 0x6461_6d72, // "damr" + ESDBox 0x6573_6473, // "esds" + VP8SampleEntry 0x7670_3038, // "vp08" + VP9SampleEntry 0x7670_3039, // "vp09" + VPCodecConfigurationBox 0x7670_6343, // "vpcC" + AV1SampleEntry 0x6176_3031, // "av01" + AV1CodecConfigurationBox 0x6176_3143, // "av1C" + FLACSampleEntry 0x664c_6143, // "fLaC" + FLACSpecificBox 0x6466_4c61, // "dfLa" + OpusSampleEntry 0x4f70_7573, // "Opus" + OpusSpecificBox 0x644f_7073, // "dOps" + ProtectedVisualSampleEntry 0x656e_6376, // "encv" - Need to check official name in spec. + ProtectedAudioSampleEntry 0x656e_6361, // "enca" - Need to check official name in spec. + MovieExtendsBox 0x6d76_6578, // "mvex" + MovieExtendsHeaderBox 0x6d65_6864, // "mehd" + QTWaveAtom 0x7761_7665, // "wave" - quicktime atom + ProtectionSystemSpecificHeaderBox 0x7073_7368, // "pssh" + SchemeInformationBox 0x7363_6869, // "schi" + TrackEncryptionBox 0x7465_6e63, // "tenc" + ProtectionSchemeInfoBox 0x7369_6e66, // "sinf" + OriginalFormatBox 0x6672_6d61, // "frma" + SchemeTypeBox 0x7363_686d, // "schm" + MP3AudioSampleEntry 0x2e6d_7033, // ".mp3" - from F4V. + CompositionOffsetBox 0x6374_7473, // "ctts" + LPCMAudioSampleEntry 0x6c70_636d, // "lpcm" - quicktime atom + ALACSpecificBox 0x616c_6163, // "alac" - Also used by ALACSampleEntry + UuidBox 0x7575_6964, // "uuid" + MetadataBox 0x6d65_7461, // "meta" + MetadataHeaderBox 0x6d68_6472, // "mhdr" + MetadataItemKeysBox 0x6b65_7973, // "keys" + MetadataItemListEntry 0x696c_7374, // "ilst" + MetadataItemDataEntry 0x6461_7461, // "data" + MetadataItemNameBox 0x6e61_6d65, // "name" + #[cfg(feature = "meta-xml")] + MetadataXMLBox 0x786d_6c20, // "xml " + #[cfg(feature = "meta-xml")] + MetadataBXMLBox 0x6278_6d6c, // "bxml" + UserdataBox 0x7564_7461, // "udta" + AlbumEntry 0xa961_6c62, // "©alb" + ArtistEntry 0xa941_5254, // "©ART" + ArtistLowercaseEntry 0xa961_7274, // "©art" + AlbumArtistEntry 0x6141_5254, // "aART" + CommentEntry 0xa963_6d74, // "©cmt" + DateEntry 0xa964_6179, // "©day" + TitleEntry 0xa96e_616d, // "©nam" + CustomGenreEntry 0xa967_656e, // "©gen" + StandardGenreEntry 0x676e_7265, // "gnre" + TrackNumberEntry 0x7472_6b6e, // "trkn" + DiskNumberEntry 0x6469_736b, // "disk" + ComposerEntry 0xa977_7274, // "©wrt" + EncoderEntry 0xa974_6f6f, // "©too" + EncodedByEntry 0xa965_6e63, // "©enc" + TempoEntry 0x746d_706f, // "tmpo" + CopyrightEntry 0x6370_7274, // "cprt" + CompilationEntry 0x6370_696c, // "cpil" + CoverArtEntry 0x636f_7672, // "covr" + AdvisoryEntry 0x7274_6e67, // "rtng" + RatingEntry 0x7261_7465, // "rate" + GroupingEntry 0xa967_7270, // "©grp" + MediaTypeEntry 0x7374_696b, // "stik" + PodcastEntry 0x7063_7374, // "pcst" + CategoryEntry 0x6361_7467, // "catg" + KeywordEntry 0x6b65_7977, // "keyw" + PodcastUrlEntry 0x7075_726c, // "purl" + PodcastGuidEntry 0x6567_6964, // "egid" + DescriptionEntry 0x6465_7363, // "desc" + LongDescriptionEntry 0x6c64_6573, // "ldes" + LyricsEntry 0xa96c_7972, // "©lyr" + TVNetworkNameEntry 0x7476_6e6e, // "tvnn" + TVShowNameEntry 0x7476_7368, // "tvsh" + TVEpisodeNameEntry 0x7476_656e, // "tven" + TVSeasonNumberEntry 0x7476_736e, // "tvsn" + TVEpisodeNumberEntry 0x7476_6573, // "tves" + PurchaseDateEntry 0x7075_7264, // "purd" + GaplessPlaybackEntry 0x7067_6170, // "pgap" + OwnerEntry 0x6f77_6e72, // "ownr" + HDVideoEntry 0x6864_7664, // "hdvd" + SortNameEntry 0x736f_6e6d, // "sonm" + SortAlbumEntry 0x736f_616c, // "soal" + SortArtistEntry 0x736f_6172, // "soar" + SortAlbumArtistEntry 0x736f_6161, // "soaa" + SortComposerEntry 0x736f_636f, // "soco" +); diff --git a/third_party/rust/mp4parse/src/lib.rs b/third_party/rust/mp4parse/src/lib.rs new file mode 100644 index 0000000000..ca110f6041 --- /dev/null +++ b/third_party/rust/mp4parse/src/lib.rs @@ -0,0 +1,6314 @@ +//! Module for parsing ISO Base Media Format aka video/mp4 streams. + +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +// `clippy::upper_case_acronyms` is a nightly-only lint as of 2021-03-15, so we +// allow `clippy::unknown_clippy_lints` to ignore it on stable - but +// `clippy::unknown_clippy_lints` has been renamed in nightly, so we need to +// allow `renamed_and_removed_lints` to ignore a warning for that. +#![allow(renamed_and_removed_lints)] +#![allow(clippy::unknown_clippy_lints)] +#![allow(clippy::upper_case_acronyms)] + +#[macro_use] +extern crate log; + +extern crate bitreader; +extern crate byteorder; +extern crate fallible_collections; +extern crate num_traits; +use bitreader::{BitReader, ReadInto}; +use byteorder::{ReadBytesExt, WriteBytesExt}; + +use fallible_collections::TryRead; +use fallible_collections::TryReserveError; + +use num_traits::Num; +use std::convert::{TryFrom, TryInto as _}; +use std::fmt; +use std::io::Cursor; +use std::io::{Read, Take}; + +#[macro_use] +mod macros; + +mod boxes; +use crate::boxes::{BoxType, FourCC}; + +// Unit tests. +#[cfg(test)] +mod tests; + +#[cfg(feature = "unstable-api")] +pub mod unstable; + +/// The HEIF image and image collection brand +/// The 'mif1' brand indicates structural requirements on files +/// See HEIF (ISO 23008-12:2017) § 10.2.1 +pub const MIF1_BRAND: FourCC = FourCC { value: *b"mif1" }; + +/// The HEIF image sequence brand +/// The 'msf1' brand indicates structural requirements on files +/// See HEIF (ISO 23008-12:2017) § 10.3.1 +pub const MSF1_BRAND: FourCC = FourCC { value: *b"msf1" }; + +/// The brand to identify AV1 image items +/// The 'avif' brand indicates structural requirements on files +/// See <https://aomediacodec.github.io/av1-avif/#image-and-image-collection-brand> +pub const AVIF_BRAND: FourCC = FourCC { value: *b"avif" }; + +/// The brand to identify AVIF image sequences +/// The 'avis' brand indicates structural requirements on files +/// See <https://aomediacodec.github.io/av1-avif/#image-and-image-collection-brand> +pub const AVIS_BRAND: FourCC = FourCC { value: *b"avis" }; + +/// 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 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: 'a> { + reader: &'a mut T, + offset: u64, +} + +impl<'a, T> OffsetReader<'a, T> { + fn new(reader: &'a mut T) -> Self { + Self { reader, offset: 0 } + } +} + +impl<'a, T> Offset for OffsetReader<'a, T> { + fn offset(&self) -> u64 { + self.offset + } +} + +impl<'a, T: Read> Read for OffsetReader<'a, T> { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { + let bytes_read = self.reader.read(buf)?; + trace!("Read {} bytes at offset {}", bytes_read, self.offset); + self.offset = self + .offset + .checked_add(bytes_read.to_u64()) + .expect("total bytes read too large for offset type"); + Ok(bytes_read) + } +} + +pub type TryVec<T> = fallible_collections::TryVec<T>; +pub type TryString = fallible_collections::TryVec<u8>; +pub type TryHashMap<K, V> = fallible_collections::TryHashMap<K, V>; +pub type TryBox<T> = fallible_collections::TryBox<T>; + +// To ensure we don't use stdlib allocating types by accident +#[allow(dead_code)] +struct Vec; +#[allow(dead_code)] +struct Box; +#[allow(dead_code)] +struct HashMap; +#[allow(dead_code)] +struct String; + +/// The return value to the C API +/// Any detail that needs to be communicated to the caller must be encoded here +/// since the [`Error`] type's associated data is part of the FFI. +#[repr(C)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Status { + Ok = 0, + BadArg = 1, + Invalid = 2, + Unsupported = 3, + Eof = 4, + Io = 5, + Oom = 6, + A1lxEssential, + A1opNoEssential, + AlacBadMagicCookieSize, + AlacFlagsNonzero, + Av1cMissing, + BitReaderError, + BoxBadSize, + BoxBadWideSize, + CheckParserStateErr, + ColrBadQuantity, + ColrBadSize, + ColrBadType, + ColrReservedNonzero, + ConstructionMethod, + CttsBadSize, + CttsBadVersion, + DflaBadMetadataBlockSize, + DflaFlagsNonzero, + DflaMissingMetadata, + DflaStreamInfoBadSize, + DflaStreamInfoNotFirst, + DopsChannelMappingWriteErr, + DopsOpusHeadWriteErr, + ElstBadVersion, + EsdsBadAudioSampleEntry, + EsdsBadDescriptor, + EsdsDecSpecificIntoTagQuantity, + FtypBadSize, + FtypNotFirst, + HdlrNameNoNul, + HdlrNameNotUtf8, + HdlrNotFirst, + HdlrPredefinedNonzero, + HdlrReservedNonzero, + HdlrTypeNotPict, + HdlrUnsupportedVersion, + HdrlBadQuantity, + IdatBadQuantity, + IdatMissing, + IinfBadChild, + IinfBadQuantity, + IlocBadConstructionMethod, + IlocBadExtent, + IlocBadExtentCount, + IlocBadFieldSize, + IlocBadQuantity, + IlocBadSize, + IlocDuplicateItemId, + IlocNotFound, + IlocOffsetOverflow, + ImageItemType, + InfeFlagsNonzero, + InvalidUtf8, + IpcoIndexOverflow, + IpmaBadIndex, + IpmaBadItemOrder, + IpmaBadQuantity, + IpmaBadVersion, + IpmaDuplicateItemId, + IpmaFlagsNonzero, + IpmaIndexZeroNoEssential, + IpmaTooBig, + IpmaTooSmall, + IprpBadChild, + IprpBadQuantity, + IprpConflict, + IrefBadQuantity, + IrefRecursion, + IspeMissing, + ItemTypeMissing, + LselNoEssential, + MdhdBadTimescale, + MdhdBadVersion, + MehdBadVersion, + MetaBadQuantity, + MissingAvifOrAvisBrand, + MissingMif1Brand, + MoovBadQuantity, + MoovMissing, + MultipleAlpha, + MvhdBadTimescale, + MvhdBadVersion, + NoImage, + PitmBadQuantity, + PitmMissing, + PitmNotFound, + PixiBadChannelCount, + PixiMissing, + PsshSizeOverflow, + ReadBufErr, + SchiQuantity, + StsdBadAudioSampleEntry, + StsdBadVideoSampleEntry, + TkhdBadVersion, + TxformBeforeIspe, + TxformNoEssential, + TxformOrder, +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Feature { + A1lx, + A1op, + Auxc, + Av1c, + Avis, + Clap, + Colr, + Grid, + Imir, + Ipro, + Irot, + Ispe, + Lsel, + Pasp, + Pixi, +} + +impl Feature { + fn supported(self) -> bool { + match self { + Self::Auxc + | Self::Av1c + | Self::Avis + | Self::Colr + | Self::Imir + | Self::Irot + | Self::Ispe + | Self::Pasp + | Self::Pixi => true, + Self::A1lx | Self::A1op | Self::Clap | Self::Grid | Self::Ipro | Self::Lsel => false, + } + } +} + +impl TryFrom<&ItemProperty> for Feature { + type Error = Error; + + fn try_from(item_property: &ItemProperty) -> Result<Self, Self::Error> { + Ok(match item_property { + ItemProperty::AuxiliaryType(_) => Self::Auxc, + ItemProperty::AV1Config(_) => Self::Av1c, + ItemProperty::Channels(_) => Self::Pixi, + ItemProperty::CleanAperture => Self::Clap, + ItemProperty::Colour(_) => Self::Colr, + ItemProperty::ImageSpatialExtents(_) => Self::Ispe, + ItemProperty::LayeredImageIndexing => Self::A1lx, + ItemProperty::LayerSelection => Self::Lsel, + ItemProperty::Mirroring(_) => Self::Imir, + ItemProperty::OperatingPointSelector => Self::A1op, + ItemProperty::PixelAspectRatio(_) => Self::Pasp, + ItemProperty::Rotation(_) => Self::Irot, + item_property => { + error!("No known Feature variant for {:?}", item_property); + return Err(Error::Unsupported("missing Feature fox ItemProperty")); + } + }) + } +} + +/// A collection to indicate unsupported features that were encountered during +/// parsing. Since the default behavior for many such features is to ignore +/// them, this often not fatal and there may be several to report. +#[derive(Debug, Default)] +pub struct UnsupportedFeatures(u32); + +impl UnsupportedFeatures { + pub fn new() -> Self { + Self(0x0) + } + + pub fn into_bitfield(&self) -> u32 { + self.0 + } + + fn feature_to_bitfield(feature: Feature) -> u32 { + let index = feature as usize; + assert!( + u8::BITS.to_usize() * std::mem::size_of::<Self>() > index, + "You're gonna need a bigger bitfield" + ); + let bitfield = 1u32 << index; + assert_eq!(bitfield.count_ones(), 1); + bitfield + } + + pub fn insert(&mut self, feature: Feature) { + warn!("Unsupported feature: {:?}", feature); + self.0 |= Self::feature_to_bitfield(feature); + } + + pub fn contains(&self, feature: Feature) -> bool { + self.0 & Self::feature_to_bitfield(feature) != 0x0 + } + + pub fn is_empty(&self) -> bool { + self.0 == 0x0 + } +} + +impl<T> From<Status> for Result<T> { + /// A convenience method to enable shortcuts like + /// ``` + /// # use mp4parse::{Result,Status}; + /// # let _: Result<()> = + /// Status::MissingAvifOrAvisBrand.into(); + /// ``` + /// instead of + /// ``` + /// # use mp4parse::{Error,Result,Status}; + /// # let _: Result<()> = + /// Err(Error::from(Status::MissingAvifOrAvisBrand)); + /// ``` + /// Note that `Status::Ok` can't be supported this way and will panic. + fn from(parse_status: Status) -> Self { + match parse_status { + Status::Ok => panic!("Can't determine Ok(_) inner value from Status"), + err_status => Err(err_status.into()), + } + } +} + +/// For convenience of creating an error for an unsupported feature which we +/// want to communicate the specific feature back to the C API caller +impl From<Status> for Error { + fn from(parse_status: Status) -> Self { + match parse_status { + Status::Ok + | Status::BadArg + | Status::Invalid + | Status::Unsupported + | Status::Eof + | Status::Io + | Status::Oom => { + panic!("Status -> Error is only for Status:InvalidData errors") + } + _ => Self::InvalidData(parse_status), + } + } +} + +impl From<Status> for &str { + fn from(status: Status) -> Self { + match status { + Status::Ok + | Status::BadArg + | Status::Invalid + | Status::Unsupported + | Status::Eof + | Status::Io + | Status::Oom => { + panic!("Status -> Error is only for specific parsing errors") + } + Status::A1lxEssential => { + "AV1LayeredImageIndexingProperty (a1lx) shall not be marked as essential \ + per https://aomediacodec.github.io/av1-avif/#layered-image-indexing-property-description" + } + Status::A1opNoEssential => { + "OperatingPointSelectorProperty (a1op) shall be marked as essential \ + per https://aomediacodec.github.io/av1-avif/#operating-point-selector-property-description" + } + Status::AlacBadMagicCookieSize => { + "ALACSpecificBox magic cookie is the wrong size" + } + Status::AlacFlagsNonzero => { + "no-zero alac (ALAC) flags" + } + Status::Av1cMissing => { + "One AV1 Item Configuration Property (av1C) is mandatory for an \ + image item of type 'av01' \ + per AVIF specification § 2.2.1" + } + Status::BitReaderError => { + "Bitwise read failed" + } + Status::BoxBadSize => { + "malformed size" + } + Status::BoxBadWideSize => { + "malformed wide size" + } + Status::CheckParserStateErr => { + "unread box content or bad parser sync" + } + Status::ColrBadQuantity => { + "Each item shall have at most one property association with a + ColourInformationBox (colr) for a given value of colour_type \ + per HEIF (ISO/IEC DIS 23008-12) § 6.5.5.1" + } + Status::ColrBadSize => { + "Unexpected size for colr box" + } + Status::ColrBadType => { + "Unsupported colour_type for ColourInformationBox" + } + Status::ColrReservedNonzero => { + "The 7 reserved bits at the end of the ColourInformationBox \ + for colour_type == 'nclx' must be 0 \ + per ISOBMFF (ISO 14496-12:2020) § 12.1.5.2" + } + Status::ConstructionMethod => { + "construction_method shall be 0 (file) or 1 (idat) per MIAF (ISO 23000-22:2019) § 7.2.1.7" + } + Status::CttsBadSize => { + "insufficient data in 'ctts' box" + } + Status::CttsBadVersion => { + "unsupported version in 'ctts' box" + } + Status::DflaBadMetadataBlockSize => { + "FLACMetadataBlock larger than parent box" + } + Status::DflaFlagsNonzero => { + "no-zero dfLa (FLAC) flags" + } + Status::DflaMissingMetadata => { + "FLACSpecificBox missing metadata" + } + Status::DflaStreamInfoBadSize => { + "FLACSpecificBox STREAMINFO block is the wrong size" + } + Status::DflaStreamInfoNotFirst => { + "FLACSpecificBox must have STREAMINFO metadata first" + } + Status::DopsChannelMappingWriteErr => { + "Couldn't write channel mapping table data." + } + Status::DopsOpusHeadWriteErr => { + "Couldn't write OpusHead tag." + } + Status::ElstBadVersion => { + "unhandled elst version" + } + Status::EsdsBadAudioSampleEntry => { + "malformed audio sample entry" + } + Status::EsdsBadDescriptor => { + "Invalid descriptor." + } + Status::EsdsDecSpecificIntoTagQuantity => { + "There can be only one DecSpecificInfoTag descriptor" + } + Status::FtypBadSize => { + "invalid ftyp size" + } + Status::FtypNotFirst => { + "The FileTypeBox shall be placed as early as possible in the file \ + per ISOBMFF (ISO 14496-12:2020) § 4.3.1" + } + Status::HdlrNameNoNul => { + "The HandlerBox 'name' field shall be null-terminated \ + per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2" + } + Status::HdlrNameNotUtf8 => { + "The HandlerBox 'name' field shall be valid utf8 \ + per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2" + } + Status::HdlrNotFirst => { + "The HandlerBox shall be the first contained box within the MetaBox \ + per MIAF (ISO 23000-22:2019) § 7.2.1.5" + } + Status::HdlrPredefinedNonzero => { + "The HandlerBox 'pre_defined' field shall be 0 \ + per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2" + } + Status::HdlrReservedNonzero => { + "The HandlerBox 'reserved' fields shall be 0 \ + per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2" + } + Status::HdlrTypeNotPict => { + "The HandlerBox handler_type must be 'pict' \ + per MIAF (ISO 23000-22:2019) § 7.2.1.5" + } + Status::HdlrUnsupportedVersion => { + "The HandlerBox version shall be 0 (zero) \ + per ISOBMFF (ISO 14496-12:2020) § 8.4.3.2" + } + Status::HdrlBadQuantity => { + "There shall be exactly one hdlr box \ + per ISOBMFF (ISO 14496-12:2020) § 8.4.3.1" + } + Status::IdatBadQuantity => { + "There shall be zero or one idat boxes \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.11" + } + Status::IdatMissing => { + "ItemLocationBox (iloc) construction_method indicates 1 (idat), \ + but no idat box is present." + } + Status::IinfBadChild => { + "iinf box shall contain only infe boxes \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.6.2" + } + Status::IinfBadQuantity => { + "There shall be zero or one iinf boxes \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.6.1" + } + Status::IlocBadConstructionMethod => { + "construction_method is taken from the set 0, 1 or 2 \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.3.3" + } + Status::IlocBadExtent => { + "extent_count != 1 requires explicit offset and length \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.3.3" + } + Status::IlocBadExtentCount => { + "extent_count must have a value 1 or greater \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.3.3" + } + Status::IlocBadFieldSize => { + "value must be in the set {0, 4, 8}" + } + Status::IlocBadQuantity => { + "There shall be zero or one iloc boxes \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.3.1" + } + Status::IlocBadSize => { + "invalid iloc size" + } + Status::IlocDuplicateItemId => { + "duplicate item_ID in iloc" + } + Status::IlocNotFound => { + "ItemLocationBox (iloc) contains an extent not present in any mdat or idat box" + } + Status::IlocOffsetOverflow => { + "offset calculation overflow" + } + Status::ImageItemType => { + "Image item type is neither 'av01' nor 'grid'" + } + Status::InfeFlagsNonzero => { + "'infe' flags field shall be 0 \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.6.2" + } + Status::InvalidUtf8 => { + "invalid utf8" + } + Status::IpcoIndexOverflow => { + "ipco index overflow" + } + Status::IpmaBadIndex => { + "Invalid property index in ipma" + } + Status::IpmaBadItemOrder => { + "Each ItemPropertyAssociation box shall be ordered by increasing item_ID" + } + Status::IpmaBadQuantity => { + "There shall be at most one ItemPropertyAssociationbox with a given pair of \ + values of version and flags \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.14.1" + } + Status::IpmaBadVersion => { + "The ipma version 0 should be used unless 32-bit item_ID values are needed \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.14.1" + } + Status::IpmaDuplicateItemId => { + "There shall be at most one occurrence of a given item_ID, \ + in the set of ItemPropertyAssociationBox boxes \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.14.1" + } + Status::IpmaFlagsNonzero => { + "Unless there are more than 127 properties in the ItemPropertyContainerBox, \ + flags should be equal to 0 \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.14.1" + } + Status::IpmaIndexZeroNoEssential => { + "the essential indicator shall be 0 for property index 0 \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.14.3" + } + Status::IpmaTooBig => { + "ipma box exceeds maximum size for entry_count" + } + Status::IpmaTooSmall => { + "ipma box below minimum size for entry_count" + } + Status::IprpBadChild => { + "unexpected iprp child" + } + Status::IprpBadQuantity => { + "There shall be zero or one iprp boxes \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.14.1" + } + Status::IprpConflict => { + "conflicting item property values" + } + Status::IrefBadQuantity => { + "There shall be zero or one iref boxes \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.12.1" + } + Status::IrefRecursion => { + "from_item_id and to_item_id must be different" + } + Status::IspeMissing => { + "Missing 'ispe' property for image item, required \ + per HEIF (ISO/IEC 23008-12:2017) § 6.5.3.1" + } + Status::ItemTypeMissing => { + "No ItemInfoEntry for item_ID" + } + Status::LselNoEssential => { + "LayerSelectorProperty (lsel) shall be marked as essential \ + per HEIF (ISO/IEC 23008-12:2017) § 6.5.11.1" + } + Status::MdhdBadTimescale => { + "zero timescale in mdhd" + } + Status::MdhdBadVersion => { + "unhandled mdhd version" + } + Status::MehdBadVersion => { + "unhandled mehd version" + } + Status::MetaBadQuantity => { + "There should be zero or one meta boxes \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.1.1" + } + Status::MissingAvifOrAvisBrand => { + "The file shall list 'avif' or 'avis' in the compatible_brands field + of the FileTypeBox \ + per https://aomediacodec.github.io/av1-avif/#file-constraints" + } + Status::MissingMif1Brand => { + "The FileTypeBox should contain 'mif1' in the compatible_brands list \ + per MIAF (ISO 23000-22:2019/Amd. 2:2021) § 7.2.1.2" + } + Status::MoovBadQuantity => { + "Multiple moov boxes found; \ + files with avis or msf1 brands shall contain exactly one moov box \ + per ISOBMFF (ISO 14496-12:2020) § 8.2.1.1" + } + Status::MoovMissing => { + "No moov box found; \ + files with avis or msf1 brands shall contain exactly one moov box \ + per ISOBMFF (ISO 14496-12:2020) § 8.2.1.1" + } + Status::MultipleAlpha => { + "multiple alpha planes" + } + Status::MvhdBadTimescale => { + "zero timescale in mvhd" + } + Status::MvhdBadVersion => { + "unhandled mvhd version" + } + Status::NoImage => "No primary image or image sequence found", + Status::PitmBadQuantity => { + "There shall be zero or one pitm boxes \ + per ISOBMFF (ISO 14496-12:2020) § 8.11.4.1" + } + Status::PitmMissing => { + "Missing required PrimaryItemBox (pitm), required \ + per HEIF (ISO/IEC 23008-12:2017) § 10.2.1" + } + Status::PitmNotFound => { + "PrimaryItemBox (pitm) referenced an item ID that was not present" + } + Status::PixiBadChannelCount => { + "invalid num_channels" + } + Status::PixiMissing => { + "The pixel information property shall be associated with every image \ + that is displayable (not hidden) \ + per MIAF (ISO/IEC 23000-22:2019) specification § 7.3.6.6" + } + Status::PsshSizeOverflow => { + "overflow in read_pssh" + } + Status::ReadBufErr => { + "failed buffer read" + } + Status::SchiQuantity => { + "tenc box should be only one at most in sinf box" + } + Status::StsdBadAudioSampleEntry => { + "malformed audio sample entry" + } + Status::StsdBadVideoSampleEntry => { + "malformed video sample entry" + } + Status::TkhdBadVersion => { + "unhandled tkhd version" + } + Status::TxformBeforeIspe => { + "Every image item shall be associated with one property of \ + type ImageSpatialExtentsProperty (ispe), prior to the \ + association of all transformative properties. \ + per HEIF (ISO/IEC 23008-12:2017) § 6.5.3.1" + } + Status::TxformNoEssential => { + "All transformative properties associated with coded and \ + derived images required or conditionally required by this \ + document shall be marked as essential \ + per MIAF (ISO 23000-22:2019) § 7.3.9" + } + Status::TxformOrder => { + "These properties, if used, shall be indicated to be applied \ + in the following order: clean aperture first, then rotation, \ + then mirror. \ + per MIAF (ISO/IEC 23000-22:2019) § 7.3.6.7" + } + } + } +} + +impl From<Error> for Status { + fn from(error: Error) -> Self { + match error { + Error::Unsupported(_) => Self::Unsupported, + Error::InvalidData(parse_status) => parse_status, + Error::UnexpectedEOF => Self::Eof, + Error::Io(_) => { + // Getting std::io::ErrorKind::UnexpectedEof is normal + // but our From trait implementation should have converted + // those to our Error::UnexpectedEOF variant. + Self::Io + } + Error::MoovMissing => Self::MoovMissing, + Error::OutOfMemory => Self::Oom, + } + } +} + +impl From<Result<(), Status>> for Status { + fn from(result: Result<(), Status>) -> Self { + match result { + Ok(()) => Status::Ok, + Err(Status::Ok) => unreachable!(), + Err(e) => e, + } + } +} + +impl<T> From<Result<T>> for Status { + fn from(result: Result<T>) -> Self { + match result { + Ok(_) => Status::Ok, + Err(e) => Status::from(e), + } + } +} + +impl From<fallible_collections::TryReserveError> for Status { + fn from(_: fallible_collections::TryReserveError) -> Self { + Status::Oom + } +} + +impl From<std::io::Error> for Status { + fn from(_: std::io::Error) -> Self { + Status::Io + } +} + +/// 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. + /// See the helper [`From<Status> for Error`](enum.Error.html#impl-From<Status>) + InvalidData(Status), + /// 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. + MoovMissing, + /// 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 { + Status::BitReaderError.into() + } +} + +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 { + Status::InvalidUtf8.into() + } +} + +impl From<std::str::Utf8Error> for Error { + fn from(_: std::str::Utf8Error) -> Error { + Status::InvalidUtf8.into() + } +} + +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::UnexpectedEOF => std::io::ErrorKind::UnexpectedEof, + Error::Io(io_err) => return io_err, + _ => std::io::ErrorKind::Other, + }; + Self::new(kind, err) + } +} + +impl From<TryReserveError> for Error { + fn from(_: TryReserveError) -> Error { + Error::OutOfMemory + } +} + +/// Result shorthand using our Error enum. +pub type Result<T, E = Error> = std::result::Result<T, E>; + +/// Basic ISO box structure. +/// +/// mp4 files are a sequence of possibly-nested 'box' structures. Each box +/// begins with a header describing the length of the box's data and a +/// four-byte box type which identifies the type of the box. Together these +/// are enough to interpret the contents of that section of the file. +/// +/// See ISOBMFF (ISO 14496-12:2020) § 4.2 +#[derive(Debug, Clone, Copy)] +struct BoxHeader { + /// Box type. + name: BoxType, + /// Size of the box in bytes. + size: u64, + /// Offset to the start of the contained data (or header size). + offset: u64, + /// Uuid for extended type. + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 + uuid: Option<[u8; 16]>, +} + +impl BoxHeader { + const MIN_SIZE: u64 = 8; // 4-byte size + 4-byte type + const MIN_LARGE_SIZE: u64 = 16; // 4-byte size + 4-byte type + 16-byte size +} + +/// File type box 'ftyp'. +#[derive(Debug)] +struct FileTypeBox { + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 + major_brand: FourCC, + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 + minor_version: u32, + compatible_brands: TryVec<FourCC>, +} + +impl FileTypeBox { + fn contains(&self, brand: &FourCC) -> bool { + self.compatible_brands.contains(brand) || self.major_brand == *brand + } +} + +/// Movie header box 'mvhd'. +#[derive(Debug)] +struct MovieHeaderBox { + pub timescale: u32, + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 + duration: u64, +} + +#[derive(Debug, Clone, Copy)] +pub struct Matrix { + pub a: i32, // 16.16 fix point + pub b: i32, // 16.16 fix point + pub u: i32, // 2.30 fix point + pub c: i32, // 16.16 fix point + pub d: i32, // 16.16 fix point + pub v: i32, // 2.30 fix point + pub x: i32, // 16.16 fix point + pub y: i32, // 16.16 fix point + pub w: i32, // 2.30 fix point +} + +/// Track header box 'tkhd' +#[derive(Debug, Clone)] +pub struct TrackHeaderBox { + track_id: u32, + pub disabled: bool, + pub duration: u64, + pub width: u32, + pub height: u32, + pub matrix: Matrix, +} + +/// Edit list box 'elst' +#[derive(Debug)] +struct EditListBox { + looped: bool, + edits: TryVec<Edit>, +} + +#[derive(Debug)] +struct Edit { + segment_duration: u64, + media_time: i64, + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 + media_rate_integer: i16, + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 + media_rate_fraction: i16, +} + +/// Media header box 'mdhd' +#[derive(Debug)] +struct MediaHeaderBox { + timescale: u32, + duration: u64, +} + +// Chunk offset box 'stco' or 'co64' +#[derive(Debug)] +pub struct ChunkOffsetBox { + pub offsets: TryVec<u64>, +} + +// Sync sample box 'stss' +#[derive(Debug)] +pub struct SyncSampleBox { + pub samples: TryVec<u32>, +} + +// Sample to chunk box 'stsc' +#[derive(Debug)] +pub struct SampleToChunkBox { + pub samples: TryVec<SampleToChunk>, +} + +#[derive(Debug)] +pub struct SampleToChunk { + pub first_chunk: u32, + pub samples_per_chunk: u32, + pub sample_description_index: u32, +} + +// Sample size box 'stsz' +#[derive(Debug)] +pub struct SampleSizeBox { + pub sample_size: u32, + pub sample_sizes: TryVec<u32>, +} + +// Time to sample box 'stts' +#[derive(Debug)] +pub struct TimeToSampleBox { + pub samples: TryVec<Sample>, +} + +#[repr(C)] +#[derive(Debug)] +pub struct Sample { + pub sample_count: u32, + pub sample_delta: u32, +} + +#[derive(Debug, Clone, Copy)] +pub enum TimeOffsetVersion { + Version0(u32), + Version1(i32), +} + +#[derive(Debug, Clone)] +pub struct TimeOffset { + pub sample_count: u32, + pub time_offset: TimeOffsetVersion, +} + +#[derive(Debug)] +pub struct CompositionOffsetBox { + pub samples: TryVec<TimeOffset>, +} + +// Handler reference box 'hdlr' +#[derive(Debug)] +struct HandlerBox { + handler_type: FourCC, +} + +// Sample description box 'stsd' +#[derive(Debug)] +pub struct SampleDescriptionBox { + pub descriptions: TryVec<SampleEntry>, +} + +#[derive(Debug)] +pub enum SampleEntry { + Audio(AudioSampleEntry), + Video(VideoSampleEntry), + Unknown, +} + +#[derive(Debug)] +pub struct TrackReferenceBox { + pub references: TryVec<TrackReferenceEntry>, +} + +impl TrackReferenceBox { + pub fn has_auxl_reference(&self, track_id: u32) -> bool { + self.references.iter().any(|entry| match entry { + TrackReferenceEntry::Auxiliary(aux_entry) => aux_entry.track_ids.contains(&track_id), + }) + } +} + +#[derive(Debug)] +pub enum TrackReferenceEntry { + Auxiliary(TrackReference), +} + +#[derive(Debug)] +pub struct TrackReference { + pub track_ids: TryVec<u32>, +} + +/// An Elementary Stream Descriptor +/// See MPEG-4 Systems (ISO 14496-1:2010) § 7.2.6.5 +#[allow(non_camel_case_types)] +#[derive(Debug, Default)] +pub struct ES_Descriptor { + pub audio_codec: CodecType, + pub audio_object_type: Option<u16>, + pub extended_audio_object_type: Option<u16>, + pub audio_sample_rate: Option<u32>, + pub audio_channel_count: Option<u16>, + #[cfg(feature = "mp4v")] + pub video_codec: CodecType, + pub codec_esds: TryVec<u8>, + pub decoder_specific_data: TryVec<u8>, // Data in DECODER_SPECIFIC_TAG +} + +#[allow(non_camel_case_types)] +#[derive(Debug)] +pub enum AudioCodecSpecific { + ES_Descriptor(ES_Descriptor), + FLACSpecificBox(FLACSpecificBox), + OpusSpecificBox(OpusSpecificBox), + ALACSpecificBox(ALACSpecificBox), + MP3, + LPCM, + #[cfg(feature = "3gpp")] + AMRSpecificBox(TryVec<u8>), +} + +#[derive(Debug)] +pub struct AudioSampleEntry { + pub codec_type: CodecType, + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 + data_reference_index: u16, + pub channelcount: u32, + pub samplesize: u16, + pub samplerate: f64, + pub codec_specific: AudioCodecSpecific, + pub protection_info: TryVec<ProtectionSchemeInfoBox>, +} + +#[derive(Debug)] +pub enum VideoCodecSpecific { + AVCConfig(TryVec<u8>), + VPxConfig(VPxConfigBox), + AV1Config(AV1ConfigBox), + ESDSConfig(TryVec<u8>), + H263Config(TryVec<u8>), + HEVCConfig(TryVec<u8>), +} + +#[derive(Debug)] +pub struct VideoSampleEntry { + pub codec_type: CodecType, + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 + data_reference_index: u16, + pub width: u16, + pub height: u16, + pub codec_specific: VideoCodecSpecific, + pub protection_info: TryVec<ProtectionSchemeInfoBox>, +} + +/// Represent a Video Partition Codec Configuration 'vpcC' box (aka vp9). The meaning of each +/// field is covered in detail in "VP Codec ISO Media File Format Binding". +#[derive(Debug)] +pub struct VPxConfigBox { + /// An integer that specifies the VP codec profile. + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 + profile: u8, + /// An integer that specifies a VP codec level all samples conform to the following table. + /// For a description of the various levels, please refer to the VP9 Bitstream Specification. + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 + level: u8, + /// An integer that specifies the bit depth of the luma and color components. Valid values + /// are 8, 10, and 12. + pub bit_depth: u8, + /// Really an enum defined by the "Colour primaries" section of ISO 23091-2:2019 § 8.1. + pub colour_primaries: u8, + /// Really an enum defined by "VP Codec ISO Media File Format Binding". + pub chroma_subsampling: u8, + /// Really an enum defined by the "Transfer characteristics" section of ISO 23091-2:2019 § 8.2. + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 + transfer_characteristics: u8, + /// Really an enum defined by the "Matrix coefficients" section of ISO 23091-2:2019 § 8.3. + /// Available in 'VP Codec ISO Media File Format' version 1 only. + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 + matrix_coefficients: Option<u8>, + /// Indicates the black level and range of the luma and chroma signals. 0 = legal range + /// (e.g. 16-235 for 8 bit sample depth); 1 = full range (e.g. 0-255 for 8-bit sample depth). + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 + video_full_range_flag: bool, + /// This is not used for VP8 and VP9 . Intended for binary codec initialization data. + pub codec_init: TryVec<u8>, +} + +/// See [AV1-ISOBMFF § 2.3.3](https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax) +#[derive(Debug)] +pub struct AV1ConfigBox { + pub profile: u8, + pub level: u8, + pub tier: u8, + pub bit_depth: u8, + pub monochrome: bool, + pub chroma_subsampling_x: u8, + pub chroma_subsampling_y: u8, + pub chroma_sample_position: u8, + pub initial_presentation_delay_present: bool, + pub initial_presentation_delay_minus_one: u8, + // The raw config contained in the av1c box. Because some decoders accept this data as a binary + // blob, rather than as structured data, we store the blob here for convenience. + pub raw_config: TryVec<u8>, +} + +impl AV1ConfigBox { + const CONFIG_OBUS_OFFSET: usize = 4; + + pub fn config_obus(&self) -> &[u8] { + &self.raw_config[Self::CONFIG_OBUS_OFFSET..] + } +} + +#[derive(Debug)] +pub struct FLACMetadataBlock { + pub block_type: u8, + pub data: TryVec<u8>, +} + +/// Represents a FLACSpecificBox 'dfLa' +#[derive(Debug)] +pub struct FLACSpecificBox { + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 + version: u8, + pub blocks: TryVec<FLACMetadataBlock>, +} + +#[derive(Debug)] +struct ChannelMappingTable { + stream_count: u8, + coupled_count: u8, + channel_mapping: TryVec<u8>, +} + +/// Represent an OpusSpecificBox 'dOps' +#[derive(Debug)] +pub struct OpusSpecificBox { + pub version: u8, + output_channel_count: u8, + pre_skip: u16, + input_sample_rate: u32, + output_gain: i16, + channel_mapping_family: u8, + channel_mapping_table: Option<ChannelMappingTable>, +} + +/// Represent an ALACSpecificBox 'alac' +#[derive(Debug)] +pub struct ALACSpecificBox { + #[allow(dead_code)] // See https://github.com/mozilla/mp4parse-rust/issues/340 + version: u8, + pub data: TryVec<u8>, +} + +#[derive(Debug)] +pub struct MovieExtendsBox { + pub fragment_duration: Option<MediaScaledTime>, +} + +pub type ByteData = TryVec<u8>; + +#[derive(Debug, Default)] +pub struct ProtectionSystemSpecificHeaderBox { + pub system_id: ByteData, + pub kid: TryVec<ByteData>, + pub data: ByteData, + + // The entire pssh box (include header) required by Gecko. + pub box_content: ByteData, +} + +#[derive(Debug, Default, Clone)] +pub struct SchemeTypeBox { + pub scheme_type: FourCC, + pub scheme_version: u32, +} + +#[derive(Debug, Default)] +pub struct TrackEncryptionBox { + pub is_encrypted: u8, + pub iv_size: u8, + pub kid: TryVec<u8>, + // Members for pattern encryption schemes + pub crypt_byte_block_count: Option<u8>, + pub skip_byte_block_count: Option<u8>, + pub constant_iv: Option<TryVec<u8>>, + // End pattern encryption scheme members +} + +#[derive(Debug, Default)] +pub struct ProtectionSchemeInfoBox { + pub original_format: FourCC, + pub scheme_type: Option<SchemeTypeBox>, + pub tenc: Option<TrackEncryptionBox>, +} + +/// Represents a userdata box 'udta'. +/// Currently, only the metadata atom 'meta' +/// is parsed. +#[derive(Debug, Default)] +pub struct UserdataBox { + pub meta: Option<MetadataBox>, +} + +/// Represents possible contents of the +/// ©gen or gnre atoms within a metadata box. +/// 'udta.meta.ilst' may only have either a +/// standard genre box 'gnre' or a custom +/// genre box '©gen', but never both at once. +#[derive(Debug, PartialEq)] +pub enum Genre { + /// A standard ID3v1 numbered genre. + StandardGenre(u8), + /// Any custom genre string. + CustomGenre(TryString), +} + +/// Represents the contents of a 'stik' +/// atom that indicates content types within +/// iTunes. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum MediaType { + /// Movie is stored as 0 in a 'stik' atom. + Movie, // 0 + /// Normal is stored as 1 in a 'stik' atom. + Normal, // 1 + /// AudioBook is stored as 2 in a 'stik' atom. + AudioBook, // 2 + /// WhackedBookmark is stored as 5 in a 'stik' atom. + WhackedBookmark, // 5 + /// MusicVideo is stored as 6 in a 'stik' atom. + MusicVideo, // 6 + /// ShortFilm is stored as 9 in a 'stik' atom. + ShortFilm, // 9 + /// TVShow is stored as 10 in a 'stik' atom. + TVShow, // 10 + /// Booklet is stored as 11 in a 'stik' atom. + Booklet, // 11 + /// An unknown 'stik' value. + Unknown(u8), +} + +/// Represents the parental advisory rating on the track, +/// stored within the 'rtng' atom. +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum AdvisoryRating { + /// Clean is always stored as 2 in an 'rtng' atom. + Clean, // 2 + /// A value of 0 in an 'rtng' atom indicates 'Inoffensive' + Inoffensive, // 0 + /// Any non 2 or 0 value in 'rtng' indicates the track is explicit. + Explicit(u8), +} + +/// Represents the contents of 'ilst' atoms within +/// a metadata box 'meta', parsed as iTunes metadata using +/// the conventional tags. +#[derive(Debug, Default)] +pub struct MetadataBox { + /// The album name, '©alb' + pub album: Option<TryString>, + /// The artist name '©art' or '©ART' + pub artist: Option<TryString>, + /// The album artist 'aART' + pub album_artist: Option<TryString>, + /// Track comments '©cmt' + pub comment: Option<TryString>, + /// The date or year field '©day' + /// + /// This is stored as an arbitrary string, + /// and may not necessarily be in a valid date + /// format. + pub year: Option<TryString>, + /// The track title '©nam' + pub title: Option<TryString>, + /// The track genre '©gen' or 'gnre'. + pub genre: Option<Genre>, + /// The track number 'trkn'. + pub track_number: Option<u8>, + /// The disc number 'disk' + pub disc_number: Option<u8>, + /// The total number of tracks on the disc, + /// stored in 'trkn' + pub total_tracks: Option<u8>, + /// The total number of discs in the album, + /// stored in 'disk' + pub total_discs: Option<u8>, + /// The composer of the track '©wrt' + pub composer: Option<TryString>, + /// The encoder used to create this track '©too' + pub encoder: Option<TryString>, + /// The encoded-by settingo this track '©enc' + pub encoded_by: Option<TryString>, + /// The tempo or BPM of the track 'tmpo' + pub beats_per_minute: Option<u8>, + /// Copyright information of the track 'cprt' + pub copyright: Option<TryString>, + /// Whether or not this track is part of a compilation 'cpil' + pub compilation: Option<bool>, + /// The advisory rating of this track 'rtng' + pub advisory: Option<AdvisoryRating>, + /// The personal rating of this track, 'rate'. + /// + /// This is stored in the box as string data, but + /// the format is an integer percentage from 0 - 100, + /// where 100 is displayed as 5 stars out of 5. + pub rating: Option<TryString>, + /// The grouping this track belongs to '©grp' + pub grouping: Option<TryString>, + /// The media type of this track 'stik' + pub media_type: Option<MediaType>, // stik + /// Whether or not this track is a podcast 'pcst' + pub podcast: Option<bool>, + /// The category of ths track 'catg' + pub category: Option<TryString>, + /// The podcast keyword 'keyw' + pub keyword: Option<TryString>, + /// The podcast url 'purl' + pub podcast_url: Option<TryString>, + /// The podcast episode GUID 'egid' + pub podcast_guid: Option<TryString>, + /// The description of the track 'desc' + pub description: Option<TryString>, + /// The long description of the track 'ldes'. + /// + /// Unlike other string fields, the long description field + /// can be longer than 256 characters. + pub long_description: Option<TryString>, + /// The lyrics of the track '©lyr'. + /// + /// Unlike other string fields, the lyrics field + /// can be longer than 256 characters. + pub lyrics: Option<TryString>, + /// The name of the TV network this track aired on 'tvnn'. + pub tv_network_name: Option<TryString>, + /// The name of the TV Show for this track 'tvsh'. + pub tv_show_name: Option<TryString>, + /// The name of the TV Episode for this track 'tven'. + pub tv_episode_name: Option<TryString>, + /// The number of the TV Episode for this track 'tves'. + pub tv_episode_number: Option<u8>, + /// The season of the TV Episode of this track 'tvsn'. + pub tv_season: Option<u8>, + /// The date this track was purchased 'purd'. + pub purchase_date: Option<TryString>, + /// Whether or not this track supports gapless playback 'pgap' + pub gapless_playback: Option<bool>, + /// Any cover artwork attached to this track 'covr' + /// + /// 'covr' is unique in that it may contain multiple 'data' sub-entries, + /// each an image file. Here, each subentry's raw binary data is exposed, + /// which may contain image data in JPEG or PNG format. + pub cover_art: Option<TryVec<TryVec<u8>>>, + /// The owner of the track 'ownr' + pub owner: Option<TryString>, + /// Whether or not this track is HD Video 'hdvd' + pub hd_video: Option<bool>, + /// The name of the track to sort by 'sonm' + pub sort_name: Option<TryString>, + /// The name of the album to sort by 'soal' + pub sort_album: Option<TryString>, + /// The name of the artist to sort by 'soar' + pub sort_artist: Option<TryString>, + /// The name of the album artist to sort by 'soaa' + pub sort_album_artist: Option<TryString>, + /// The name of the composer to sort by 'soco' + pub sort_composer: Option<TryString>, + /// Metadata + #[cfg(feature = "meta-xml")] + pub xml: Option<XmlBox>, +} + +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.2.1 +#[cfg(feature = "meta-xml")] +#[derive(Debug)] +pub enum XmlBox { + /// XML metadata + StringXmlBox(TryString), + /// Binary XML metadata + BinaryXmlBox(TryVec<u8>), +} + +/// Internal data structures. +#[derive(Debug, Default)] +pub struct MediaContext { + pub timescale: Option<MediaTimeScale>, + /// Tracks found in the file. + pub tracks: TryVec<Track>, + pub mvex: Option<MovieExtendsBox>, + pub psshs: TryVec<ProtectionSystemSpecificHeaderBox>, + pub userdata: Option<Result<UserdataBox>>, + #[cfg(feature = "meta-xml")] + pub metadata: Option<Result<MetadataBox>>, +} + +/// An ISOBMFF item as described by an iloc box. For the sake of avoiding copies, +/// this can either be represented by the `Location` variant, which indicates +/// where the data exists within a `DataBox` stored separately, or the `Data` +/// variant which owns the data. Unfortunately, it's not simple to represent +/// this as a [`std::borrow::Cow`], or other reference-based type, because +/// multiple instances may references different parts of the same [`DataBox`] +/// and we want to avoid the copy that splitting the storage would entail. +enum IsobmffItem { + MdatLocation(Extent), + IdatLocation(Extent), + Data(TryVec<u8>), +} + +impl fmt::Debug for IsobmffItem { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match &self { + IsobmffItem::MdatLocation(extent) | IsobmffItem::IdatLocation(extent) => f + .debug_struct("IsobmffItem::Location") + .field("0", &format_args!("{extent:?}")) + .finish(), + IsobmffItem::Data(data) => f + .debug_struct("IsobmffItem::Data") + .field("0", &format_args!("{} bytes", data.len())) + .finish(), + } + } +} + +#[derive(Debug)] +struct AvifItem { + /// The `item_ID` from ISOBMFF (ISO 14496-12:2020) § 8.11.3 + /// + /// See [`read_iloc`] + id: ItemId, + + /// AV1 Image Item per <https://aomediacodec.github.io/av1-avif/#image-item> + image_data: IsobmffItem, +} + +impl AvifItem { + fn with_inline_data(id: ItemId) -> Self { + Self { + id, + image_data: IsobmffItem::Data(TryVec::new()), + } + } +} + +#[derive(Default, Debug)] +pub struct AvifContext { + /// Level of deviation from the specification before failing the parse + strictness: ParseStrictness, + /// Storage elements which can be located anywhere within the "file" identified by + /// [`BoxType::ItemLocationBox`]es using [`ConstructionMethod::File`]. + /// Referred to by the [`IsobmffItem`]`::*Location` variants of the `AvifItem`s in this struct + media_storage: TryVec<DataBox>, + /// Similar to `media_storage`, but for a single optional chunk of storage within the + /// MetaBox itentified by [`BoxType::ItemLocationBox`]es using [`ConstructionMethod::Idat`]. + item_data_box: Option<DataBox>, + /// The item indicated by the `pitm` box, See ISOBMFF (ISO 14496-12:2020) § 8.11.4 + /// May be `None` in the pure image sequence case. + primary_item: Option<AvifItem>, + /// Associated alpha channel for the primary item, if any + alpha_item: Option<AvifItem>, + /// If true, divide RGB values by the alpha value. + /// See `prem` in MIAF (ISO 23000-22:2019) § 7.3.5.2 + pub premultiplied_alpha: bool, + /// All properties associated with `primary_item` or `alpha_item` + item_properties: ItemPropertiesBox, + /// Should probably only ever be [`AVIF_BRAND`] or [`AVIS_BRAND`], but other values + /// are legal as long as one of the two is the `compatible_brand` list. + pub major_brand: FourCC, + /// Information on the sequence contained in the image, or None if not present + pub sequence: Option<MediaContext>, + /// A collection of unsupported features encountered during the parse + pub unsupported_features: UnsupportedFeatures, +} + +impl AvifContext { + pub fn primary_item_is_present(&self) -> bool { + self.primary_item.is_some() + } + + pub fn primary_item_coded_data(&self) -> Option<&[u8]> { + self.primary_item + .as_ref() + .map(|item| self.item_as_slice(item)) + } + + pub fn primary_item_bits_per_channel(&self) -> Option<Result<&[u8]>> { + self.primary_item + .as_ref() + .map(|item| self.image_bits_per_channel(item.id)) + } + + pub fn alpha_item_is_present(&self) -> bool { + self.alpha_item.is_some() + } + + pub fn alpha_item_coded_data(&self) -> Option<&[u8]> { + self.alpha_item + .as_ref() + .map(|item| self.item_as_slice(item)) + } + + pub fn alpha_item_bits_per_channel(&self) -> Option<Result<&[u8]>> { + self.alpha_item + .as_ref() + .map(|item| self.image_bits_per_channel(item.id)) + } + + fn image_bits_per_channel(&self, item_id: ItemId) -> Result<&[u8]> { + match self + .item_properties + .get(item_id, BoxType::PixelInformationBox)? + { + Some(ItemProperty::Channels(pixi)) => Ok(pixi.bits_per_channel.as_slice()), + Some(other_property) => panic!("property key mismatch: {:?}", other_property), + None => Ok(&[]), + } + } + + pub fn spatial_extents_ptr(&self) -> Result<*const ImageSpatialExtentsProperty> { + if let Some(primary_item) = &self.primary_item { + match self + .item_properties + .get(primary_item.id, BoxType::ImageSpatialExtentsProperty)? + { + Some(ItemProperty::ImageSpatialExtents(ispe)) => Ok(ispe), + Some(other_property) => panic!("property key mismatch: {:?}", other_property), + None => { + fail_with_status_if( + self.strictness != ParseStrictness::Permissive, + Status::IspeMissing, + )?; + Ok(std::ptr::null()) + } + } + } else { + Ok(std::ptr::null()) + } + } + + /// Returns None if there is no primary item or it has no associated NCLX colour boxes. + pub fn nclx_colour_information_ptr(&self) -> Option<Result<*const NclxColourInformation>> { + if let Some(primary_item) = &self.primary_item { + match self.item_properties.get_multiple(primary_item.id, |prop| { + matches!(prop, ItemProperty::Colour(ColourInformation::Nclx(_))) + }) { + Ok(nclx_colr_boxes) => match *nclx_colr_boxes.as_slice() { + [] => None, + [ItemProperty::Colour(ColourInformation::Nclx(nclx)), ..] => { + if nclx_colr_boxes.len() > 1 { + warn!("Multiple nclx colr boxes, using first"); + } + Some(Ok(nclx)) + } + _ => unreachable!("Expect only ColourInformation::Nclx(_) matches"), + }, + Err(e) => Some(Err(e)), + } + } else { + None + } + } + + /// Returns None if there is no primary item or it has no associated ICC colour boxes. + pub fn icc_colour_information(&self) -> Option<Result<&[u8]>> { + if let Some(primary_item) = &self.primary_item { + match self.item_properties.get_multiple(primary_item.id, |prop| { + matches!(prop, ItemProperty::Colour(ColourInformation::Icc(_, _))) + }) { + Ok(icc_colr_boxes) => match *icc_colr_boxes.as_slice() { + [] => None, + [ItemProperty::Colour(ColourInformation::Icc(icc, _)), ..] => { + if icc_colr_boxes.len() > 1 { + warn!("Multiple ICC profiles in colr boxes, using first"); + } + Some(Ok(icc.bytes.as_slice())) + } + _ => unreachable!("Expect only ColourInformation::Icc(_) matches"), + }, + Err(e) => Some(Err(e)), + } + } else { + None + } + } + + pub fn image_rotation(&self) -> Result<ImageRotation> { + if let Some(primary_item) = &self.primary_item { + match self + .item_properties + .get(primary_item.id, BoxType::ImageRotation)? + { + Some(ItemProperty::Rotation(irot)) => Ok(*irot), + Some(other_property) => panic!("property key mismatch: {:?}", other_property), + None => Ok(ImageRotation::D0), + } + } else { + Ok(ImageRotation::D0) + } + } + + pub fn image_mirror_ptr(&self) -> Result<*const ImageMirror> { + if let Some(primary_item) = &self.primary_item { + match self + .item_properties + .get(primary_item.id, BoxType::ImageMirror)? + { + Some(ItemProperty::Mirroring(imir)) => Ok(imir), + Some(other_property) => panic!("property key mismatch: {:?}", other_property), + None => Ok(std::ptr::null()), + } + } else { + Ok(std::ptr::null()) + } + } + + pub fn pixel_aspect_ratio_ptr(&self) -> Result<*const PixelAspectRatio> { + if let Some(primary_item) = &self.primary_item { + match self + .item_properties + .get(primary_item.id, BoxType::PixelAspectRatioBox)? + { + Some(ItemProperty::PixelAspectRatio(pasp)) => Ok(pasp), + Some(other_property) => panic!("property key mismatch: {:?}", other_property), + None => Ok(std::ptr::null()), + } + } else { + Ok(std::ptr::null()) + } + } + + /// A helper for the various `AvifItem`s to expose a reference to the + /// underlying data while avoiding copies. + fn item_as_slice<'a>(&'a self, item: &'a AvifItem) -> &'a [u8] { + match &item.image_data { + IsobmffItem::MdatLocation(extent) => { + for mdat in &self.media_storage { + if let Some(slice) = mdat.get(extent) { + return slice; + } + } + unreachable!( + "IsobmffItem::MdatLocation requires the location exists in AvifContext::media_storage" + ); + } + IsobmffItem::IdatLocation(extent) => { + self.item_data_box + .as_ref() + .and_then(|idat| idat.get(extent)) + .unwrap_or_else(|| unreachable!("IsobmffItem::IdatLocation equires the location exists in AvifContext::item_data_box")) + } + IsobmffItem::Data(data) => data.as_slice(), + } + } +} + +struct AvifMeta { + item_references: TryVec<SingleItemTypeReferenceBox>, + item_properties: ItemPropertiesBox, + /// Required for AvifImageType::Primary, but optional otherwise + /// See HEIF (ISO/IEC 23008-12:2017) § 7.1, 10.2.1 + primary_item_id: Option<ItemId>, + item_infos: TryVec<ItemInfoEntry>, + iloc_items: TryHashMap<ItemId, ItemLocationBoxItem>, + item_data_box: Option<DataBox>, +} + +#[derive(Debug)] +enum DataBoxMetadata { + Idat, + Mdat { + /// Offset of `data` from the beginning of the "file". See ConstructionMethod::File. + /// Note: the file may not be an actual file, read_avif supports any `&mut impl Read` + /// source for input. However we try to match the terminology used in the spec. + file_offset: u64, + }, +} + +/// Represents either an Item Data Box (ISOBMFF (ISO 14496-12:2020) § 8.11.11) +/// Or a Media Data Box (ISOBMFF (ISO 14496-12:2020) § 8.1.1) +struct DataBox { + metadata: DataBoxMetadata, + data: TryVec<u8>, +} + +impl fmt::Debug for DataBox { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("DataBox") + .field("metadata", &self.metadata) + .field("data", &format_args!("{} bytes", self.data.len())) + .finish() + } +} + +fn u64_to_usize_logged(x: u64) -> Option<usize> { + match x.try_into() { + Ok(x) => Some(x), + Err(e) => { + error!("{:?} converting {:?}", e, x); + None + } + } +} + +impl DataBox { + fn from_mdat(file_offset: u64, data: TryVec<u8>) -> Self { + Self { + metadata: DataBoxMetadata::Mdat { file_offset }, + data, + } + } + + fn from_idat(data: TryVec<u8>) -> Self { + Self { + metadata: DataBoxMetadata::Idat, + data, + } + } + + fn data(&self) -> &[u8] { + &self.data + } + + /// Convert an absolute offset to an offset relative to the beginning of the + /// slice [`DataBox::data`] returns. Returns None if the offset would be + /// negative or if the offset would overflow a `usize`. + fn start(&self, offset: u64) -> Option<usize> { + match self.metadata { + DataBoxMetadata::Idat => u64_to_usize_logged(offset), + DataBoxMetadata::Mdat { file_offset } => { + let start = offset.checked_sub(file_offset); + if start.is_none() { + error!("Overflow subtracting {} + {}", offset, file_offset); + } + u64_to_usize_logged(start?) + } + } + } + + /// Returns an appropriate variant of [`IsobmffItem`] to describe the extent + /// referencing data within this type of box. + fn location(&self, extent: &Extent) -> IsobmffItem { + match self.metadata { + DataBoxMetadata::Idat => IsobmffItem::IdatLocation(extent.clone()), + DataBoxMetadata::Mdat { .. } => IsobmffItem::MdatLocation(extent.clone()), + } + } + + /// Return a slice from the DataBox specified by the provided `extent`. + /// Returns `None` if the extent isn't fully contained by the DataBox or if + /// either the offset or length (if the extent is bounded) of the slice + /// would overflow a `usize`. + fn get<'a>(&'a self, extent: &'a Extent) -> Option<&'a [u8]> { + match extent { + Extent::WithLength { offset, len } => { + let start = self.start(*offset)?; + let end = start.checked_add(*len); + if end.is_none() { + error!("Overflow adding {} + {}", start, len); + } + self.data().get(start..end?) + } + Extent::ToEnd { offset } => { + let start = self.start(*offset)?; + self.data().get(start..) + } + } + } +} + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +struct PropertyIndex(u16); +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd)] +struct ItemId(u32); + +impl ItemId { + fn read(src: &mut impl ReadBytesExt, version: u8) -> Result<ItemId> { + Ok(ItemId(if version == 0 { + be_u16(src)?.into() + } else { + be_u32(src)? + })) + } +} + +/// Used for 'infe' boxes within 'iinf' boxes +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.6 +/// Only versions {2, 3} are supported +#[derive(Debug)] +struct ItemInfoEntry { + item_id: ItemId, + item_type: u32, +} + +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.12 +#[derive(Debug)] +struct SingleItemTypeReferenceBox { + item_type: FourCC, + from_item_id: ItemId, + to_item_id: ItemId, +} + +/// Potential sizes (in bytes) of variable-sized fields of the 'iloc' box +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.3 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum IlocFieldSize { + Zero, + Four, + Eight, +} + +impl IlocFieldSize { + fn as_bits(&self) -> u8 { + match self { + IlocFieldSize::Zero => 0, + IlocFieldSize::Four => 32, + IlocFieldSize::Eight => 64, + } + } +} + +impl TryFrom<u8> for IlocFieldSize { + type Error = Error; + + fn try_from(value: u8) -> Result<Self> { + match value { + 0 => Ok(Self::Zero), + 4 => Ok(Self::Four), + 8 => Ok(Self::Eight), + _ => Status::IlocBadFieldSize.into(), + } + } +} + +#[derive(Debug, PartialEq, Eq)] +enum IlocVersion { + Zero, + One, + Two, +} + +impl TryFrom<u8> for IlocVersion { + type Error = Error; + + fn try_from(value: u8) -> Result<Self> { + match value { + 0 => Ok(Self::Zero), + 1 => Ok(Self::One), + 2 => Ok(Self::Two), + _ => Err(Error::Unsupported("unsupported version in 'iloc' box")), + } + } +} + +/// Used for 'iloc' boxes +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.3 +/// `base_offset` is omitted since it is integrated into the ranges in `extents` +/// `data_reference_index` is omitted, since only 0 (i.e., this file) is supported +#[derive(Debug)] +struct ItemLocationBoxItem { + construction_method: ConstructionMethod, + /// Unused for ConstructionMethod::Idat + extents: TryVec<Extent>, +} + +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.3 +/// +/// Note: per MIAF (ISO 23000-22:2019) § 7.2.1.7:<br /> +/// > MIAF image items are constrained as follows:<br /> +/// > — `construction_method` shall be equal to 0 for MIAF image items that are coded image items.<br /> +/// > — `construction_method` shall be equal to 0 or 1 for MIAF image items that are derived image items. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum ConstructionMethod { + File = 0, + Idat = 1, + Item = 2, +} + +/// Describes a region where a item specified by an `ItemLocationBoxItem` is stored. +/// The offset is `u64` since that's the maximum possible size and since the relative +/// nature of `DataBox` means this can still possibly succeed even in the case +/// that the raw value exceeds std::usize::MAX on platforms where that type is smaller +/// than u64. However, `len` is stored as a `usize` since no value larger than +/// `std::usize::MAX` can be used in a successful indexing operation in rust. +/// `extent_index` is omitted since it's only used for ConstructionMethod::Item which +/// is currently not implemented. +#[derive(Clone, Debug)] +enum Extent { + WithLength { offset: u64, len: usize }, + ToEnd { offset: u64 }, +} + +#[derive(Debug, PartialEq, Eq, Default)] +pub enum TrackType { + Audio, + Video, + Picture, + AuxiliaryVideo, + Metadata, + #[default] + Unknown, +} + +// This type is used by mp4parse_capi since it needs to be passed from FFI consumers +// The C-visible struct is renamed via mp4parse_capi/cbindgen.toml to match naming conventions +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +pub enum ParseStrictness { + Permissive, // Error only on ambiguous inputs + #[default] + Normal, // Error on "shall" directives, log warnings for "should" + Strict, // Error on "should" directives +} + +fn fail_with_status_if(violation: bool, status: Status) -> Result<()> { + let error = Error::from(status); + if violation { + Err(error) + } else { + warn!("{:?}", error); + Ok(()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum CodecType { + #[default] + Unknown, + MP3, + AAC, + FLAC, + Opus, + H264, // 14496-10 + MP4V, // 14496-2 + AV1, + VP9, + VP8, + EncryptedVideo, + EncryptedAudio, + LPCM, // QT + ALAC, + H263, + HEVC, // 23008-2 + #[cfg(feature = "3gpp")] + AMRNB, + #[cfg(feature = "3gpp")] + AMRWB, +} + +/// The media's global (mvhd) timescale in units per second. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct MediaTimeScale(pub u64); + +/// A time to be scaled by the media's global (mvhd) timescale. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct MediaScaledTime(pub u64); + +/// The track's local (mdhd) timescale. +/// Members are timescale units per second and the track id. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct TrackTimeScale<T: Num>(pub T, pub usize); + +/// A time to be scaled by the track's local (mdhd) timescale. +/// Members are time in scale units and the track id. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct TrackScaledTime<T>(pub T, pub usize); + +impl<T> std::ops::Add for TrackScaledTime<T> +where + T: num_traits::CheckedAdd, +{ + type Output = Option<Self>; + + fn add(self, other: TrackScaledTime<T>) -> Self::Output { + self.0.checked_add(&other.0).map(|sum| Self(sum, self.1)) + } +} + +#[derive(Debug, Default)] +pub struct Track { + pub id: usize, + pub track_type: TrackType, + pub looped: Option<bool>, + pub empty_duration: Option<MediaScaledTime>, + pub edited_duration: Option<MediaScaledTime>, + pub media_time: Option<TrackScaledTime<u64>>, + pub timescale: Option<TrackTimeScale<u64>>, + pub duration: Option<TrackScaledTime<u64>>, + pub track_id: Option<u32>, + pub tkhd: Option<TrackHeaderBox>, // TODO(kinetik): find a nicer way to export this. + pub stsd: Option<SampleDescriptionBox>, + pub stts: Option<TimeToSampleBox>, + pub stsc: Option<SampleToChunkBox>, + pub stsz: Option<SampleSizeBox>, + pub stco: Option<ChunkOffsetBox>, // It is for stco or co64. + pub stss: Option<SyncSampleBox>, + pub ctts: Option<CompositionOffsetBox>, + pub tref: Option<TrackReferenceBox>, +} + +impl Track { + fn new(id: usize) -> Track { + Track { + id, + ..Default::default() + } + } +} + +/// See ISOBMFF (ISO 14496-12:2020) § 4.2 +struct BMFFBox<'a, T: 'a> { + head: BoxHeader, + content: Take<&'a mut T>, +} + +struct BoxIter<'a, T: 'a> { + src: &'a mut T, +} + +impl<'a, T: Read> BoxIter<'a, T> { + fn new(src: &mut T) -> BoxIter<T> { + BoxIter { src } + } + + fn next_box(&mut self) -> Result<Option<BMFFBox<T>>> { + let r = read_box_header(self.src); + match r { + Ok(h) => Ok(Some(BMFFBox { + head: h, + content: self.src.take(h.size.saturating_sub(h.offset)), + })), + Err(Error::UnexpectedEOF) => Ok(None), + Err(e) => Err(e), + } + } +} + +impl<'a, T: Read> Read for BMFFBox<'a, T> { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { + self.content.read(buf) + } +} + +impl<'a, T: Read> TryRead for BMFFBox<'a, T> { + fn try_read_to_end(&mut self, buf: &mut TryVec<u8>) -> std::io::Result<usize> { + fallible_collections::try_read_up_to(self, self.bytes_left(), buf) + } +} + +impl<'a, T: Offset> Offset for BMFFBox<'a, T> { + fn offset(&self) -> u64 { + self.content.get_ref().offset() + } +} + +impl<'a, T: Read> BMFFBox<'a, T> { + fn bytes_left(&self) -> u64 { + self.content.limit() + } + + fn get_header(&self) -> &BoxHeader { + &self.head + } + + fn box_iter<'b>(&'b mut self) -> BoxIter<BMFFBox<'a, T>> { + BoxIter::new(self) + } +} + +impl<'a, T> Drop for BMFFBox<'a, T> { + fn drop(&mut self) { + if self.content.limit() > 0 { + let name: FourCC = From::from(self.head.name); + debug!("Dropping {} bytes in '{}'", self.content.limit(), name); + } + } +} + +/// Read and parse a box header. +/// +/// Call this first to determine the type of a particular mp4 box +/// and its length. Used internally for dispatching to specific +/// parsers for the internal content, or to get the length to +/// skip unknown or uninteresting boxes. +/// +/// See ISOBMFF (ISO 14496-12:2020) § 4.2 +fn read_box_header<T: ReadBytesExt>(src: &mut T) -> Result<BoxHeader> { + let size32 = match be_u32(src) { + Ok(v) => v, + Err(error) => return Err(error), + }; + 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 => { + if name == BoxType::MediaDataBox { + 0 + } else { + return Err(Error::Unsupported("unknown sized box")); + } + } + 1 => be_u64(src)?, + _ => u64::from(size32), + }; + trace!("read_box_header: name: {:?}, size: {}", name, size); + 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)"); + return Err(Error::UnexpectedEOF); + } + } else { + None + } + } else { + None + }; + match size32 { + 0 => (), + 1 if offset > size => return Err(Error::from(Status::BoxBadWideSize)), + _ if offset > size => return Err(Error::from(Status::BoxBadSize)), + _ => (), + } + 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) + .ok_or(Error::Unsupported("Skipping past unknown sized box"))? + }; + 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) +} + +#[derive(Debug)] +enum AvifImageType { + Primary, + Sequence, + Both, +} + +impl AvifImageType { + fn has_primary(&self) -> bool { + match self { + Self::Primary | Self::Both => true, + Self::Sequence => false, + } + } + + fn has_sequence(&self) -> bool { + match self { + Self::Primary => false, + Self::Sequence | Self::Both => true, + } + } +} + +/// Read the contents of an AVIF file +pub fn read_avif<T: Read>(f: &mut T, strictness: ParseStrictness) -> Result<AvifContext> { + debug!("read_avif(strictness: {:?})", strictness); + + let mut f = OffsetReader::new(f); + let mut iter = BoxIter::new(&mut f); + let expected_image_type; + let mut unsupported_features = UnsupportedFeatures::new(); + + // 'ftyp' box must occur first; see ISOBMFF (ISO 14496-12:2020) § 4.3.1 + let major_brand = if let Some(mut b) = iter.next_box()? { + if b.head.name == BoxType::FileTypeBox { + let ftyp = read_ftyp(&mut b)?; + + let has_avif_brand = ftyp.contains(&AVIF_BRAND); + let has_avis_brand = ftyp.contains(&AVIS_BRAND); + let has_mif1_brand = ftyp.contains(&MIF1_BRAND); + let has_msf1_brand = ftyp.contains(&MSF1_BRAND); + + let primary_image_expected = has_mif1_brand || has_avif_brand; + let image_sequence_expected = has_msf1_brand || has_avis_brand; + + expected_image_type = if primary_image_expected && image_sequence_expected { + AvifImageType::Both + } else if primary_image_expected { + AvifImageType::Primary + } else if image_sequence_expected { + AvifImageType::Sequence + } else { + return Status::NoImage.into(); + }; + debug!("expected_image_type: {:?}", expected_image_type); + + if primary_image_expected && !has_mif1_brand { + fail_with_status_if( + strictness == ParseStrictness::Strict, + Status::MissingMif1Brand, + )?; + } + + if !has_avif_brand && !has_avis_brand { + fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::MissingAvifOrAvisBrand, + )?; + } + + ftyp.major_brand + } else { + return Status::FtypNotFirst.into(); + } + } else { + return Status::FtypNotFirst.into(); + }; + + let mut meta = None; + let mut image_sequence = None; + let mut media_storage = TryVec::new(); + + loop { + let mut b = match iter.next_box() { + Ok(Some(b)) => b, + Ok(_) => break, + Err(Error::UnexpectedEOF) => { + if strictness == ParseStrictness::Strict { + return Err(Error::UnexpectedEOF); + } + break; + } + Err(error) => return Err(error), + }; + + trace!("read_avif parsing {:?} box", b.head.name); + match b.head.name { + BoxType::MetadataBox => { + if meta.is_some() { + return Status::MetaBadQuantity.into(); + } + meta = Some(read_avif_meta( + &mut b, + strictness, + &mut unsupported_features, + )?); + } + BoxType::MovieBox if expected_image_type.has_sequence() => { + if image_sequence.is_some() { + return Status::MoovBadQuantity.into(); + } + image_sequence = Some(read_moov(&mut b, None)?); + } + BoxType::MediaDataBox => { + let file_offset = b.offset(); + let data = if b.head.size == 0 { + // Unknown sized `mdat`, read in chunks until EOF. + const BUF_SIZE: usize = 64 * 1024; + let mut data = TryVec::with_capacity(BUF_SIZE)?; + loop { + let got = fallible_collections::try_read_up_to( + b.content.get_mut(), + BUF_SIZE as u64, + &mut data, + )?; + if got == 0 { + // Mark `content` as consumed. + b.content.set_limit(0); + break; + } + } + data + } else { + b.read_into_try_vec()? + }; + media_storage.push(DataBox::from_mdat(file_offset, data))?; + } + _ => { + let result = skip_box_content(&mut b); + // Allow garbage at EOF if we aren't in strict mode. + if b.bytes_left() > 0 && strictness != ParseStrictness::Strict { + break; + } + result?; + } + } + + check_parser_state!(b.content); + } + + let AvifMeta { + item_references, + item_properties, + primary_item_id, + item_infos, + iloc_items, + item_data_box, + } = meta.ok_or_else(|| Error::from(Status::MetaBadQuantity))?; + + let (alpha_item_id, premultiplied_alpha) = if let Some(primary_item_id) = primary_item_id { + let mut alpha_item_ids = item_references + .iter() + // Auxiliary image for the primary image + .filter(|iref| { + iref.to_item_id == primary_item_id + && iref.from_item_id != primary_item_id + && iref.item_type == b"auxl" + }) + .map(|iref| iref.from_item_id) + // which has the alpha property + .filter(|&item_id| item_properties.is_alpha(item_id)); + let alpha_item_id = alpha_item_ids.next(); + if alpha_item_ids.next().is_some() { + return Status::MultipleAlpha.into(); + } + + let premultiplied_alpha = alpha_item_id.map_or(false, |alpha_item_id| { + item_references.iter().any(|iref| { + iref.from_item_id == primary_item_id + && iref.to_item_id == alpha_item_id + && iref.item_type == b"prem" + }) + }); + + (alpha_item_id, premultiplied_alpha) + } else { + (None, false) + }; + + debug!("primary_item_id: {:?}", primary_item_id); + debug!("alpha_item_id: {:?}", alpha_item_id); + let mut primary_item = None; + let mut alpha_item = None; + + // store data or record location of relevant items + for (item_id, loc) in iloc_items { + let item = if Some(item_id) == primary_item_id { + &mut primary_item + } else if Some(item_id) == alpha_item_id { + &mut alpha_item + } else { + continue; + }; + + assert!(item.is_none()); + + // If our item is spread over multiple extents, we'll need to copy it + // into a contiguous buffer. Otherwise, we can just store the extent + // and return a pointer into the mdat/idat later to avoid the copy. + if loc.extents.len() > 1 { + *item = Some(AvifItem::with_inline_data(item_id)) + } + + trace!( + "{:?} construction_method: {:?}", + item_id, + loc.construction_method + ); + + // Generalize the process of connecting items to their data; returns + // true if the extent is successfully added to the AvifItem + let mut find_and_add_to_item = |extent: &Extent, dat: &DataBox| -> Result<bool> { + if let Some(extent_slice) = dat.get(extent) { + match item { + None => { + trace!("Using IsobmffItem::Location"); + *item = Some(AvifItem { + id: item_id, + image_data: dat.location(extent), + }); + } + Some(AvifItem { + image_data: IsobmffItem::Data(bytes), + .. + }) => { + trace!("Using IsobmffItem::Data"); + // We could potentially optimize memory usage by trying to avoid reading + // or storing dat boxes which aren't used by our API, but for now it seems + // like unnecessary complexity + bytes.extend_from_slice(extent_slice)?; + } + _ => unreachable!(), + } + return Ok(true); + } + Ok(false) + }; + + match loc.construction_method { + ConstructionMethod::File => { + for extent in loc.extents { + let mut found = false; + // try to find an mdat which contains the extent + for mdat in media_storage.iter() { + if find_and_add_to_item(&extent, mdat)? { + found = true; + break; + } + } + + if !found { + return Status::IlocNotFound.into(); + } + } + } + ConstructionMethod::Idat => { + if let Some(idat) = &item_data_box { + for extent in loc.extents { + let found = find_and_add_to_item(&extent, idat)?; + if !found { + return Status::IlocNotFound.into(); + } + } + } else { + return Status::IdatMissing.into(); + } + } + ConstructionMethod::Item => { + fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::ConstructionMethod, + )?; + } + } + + assert!(item.is_some()); + } + + if (primary_item_id.is_some() && primary_item.is_none()) + || (alpha_item_id.is_some() && alpha_item.is_none()) + { + fail_with_status_if(strictness == ParseStrictness::Strict, Status::PitmNotFound)?; + } + + assert!(primary_item.is_none() || primary_item_id.is_some()); + assert!(alpha_item.is_none() || alpha_item_id.is_some()); + + if expected_image_type.has_primary() && primary_item_id.is_none() { + fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::PitmMissing, + )?; + } + + // Lacking a brand that requires them, it's fine for moov boxes to exist in + // BMFF files; they're simply ignored + if expected_image_type.has_sequence() && image_sequence.is_none() { + fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::MoovMissing, + )?; + } + + // Returns true iff `id` is `Some` and there is no corresponding property for it + let missing_property_for = |id: Option<ItemId>, property: BoxType| -> bool { + id.map_or(false, |id| { + item_properties + .get(id, property) + .map_or(true, |opt| opt.is_none()) + }) + }; + + // Generalize the property checks so we can apply them to primary and alpha items + let mut check_image_item = |item: &mut Option<AvifItem>| -> Result<()> { + let item_id = item.as_ref().map(|item| item.id); + let item_type = item_id.and_then(|item_id| { + item_infos + .iter() + .find(|item_info| item_id == item_info.item_id) + .map(|item_info| item_info.item_type) + }); + + match item_type.map(u32::to_be_bytes).as_ref() { + Some(b"av01") => { + if missing_property_for(item_id, BoxType::AV1CodecConfigurationBox) { + fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::Av1cMissing, + )?; + } + + if missing_property_for(item_id, BoxType::PixelInformationBox) { + // The requirement to include pixi is in the process of being changed + // to allowing its omission to imply a default value. In anticipation + // of that, only give an error in strict mode + // See https://github.com/MPEGGroup/MIAF/issues/9 + fail_with_status_if( + if cfg!(feature = "missing-pixi-permitted") { + strictness == ParseStrictness::Strict + } else { + strictness != ParseStrictness::Permissive + }, + Status::PixiMissing, + )?; + } + + if missing_property_for(item_id, BoxType::ImageSpatialExtentsProperty) { + fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::IspeMissing, + )?; + } + } + Some(b"grid") => { + // TODO: https://github.com/mozilla/mp4parse-rust/issues/198 + unsupported_features.insert(Feature::Grid); + *item = None; + } + Some(_other_type) => return Status::ImageItemType.into(), + None => { + if item.is_some() { + return Status::ItemTypeMissing.into(); + } + } + } + + if let Some(AvifItem { id, .. }) = item { + if item_properties.forbidden_items.contains(id) { + error!("Not processing item id {:?} since it is associated with essential, but unsupported properties", id); + *item = None; + } + } + + Ok(()) + }; + + check_image_item(&mut primary_item)?; + check_image_item(&mut alpha_item)?; + + Ok(AvifContext { + strictness, + media_storage, + item_data_box, + primary_item, + alpha_item, + premultiplied_alpha, + item_properties, + major_brand, + sequence: image_sequence, + unsupported_features, + }) +} + +/// Parse a metadata box in the context of an AVIF +/// Currently requires the primary item to be an av01 item type and generates +/// an error otherwise. +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.1 +fn read_avif_meta<T: Read + Offset>( + src: &mut BMFFBox<T>, + strictness: ParseStrictness, + unsupported_features: &mut UnsupportedFeatures, +) -> Result<AvifMeta> { + let version = read_fullbox_version_no_flags(src)?; + + if version != 0 { + return Err(Error::Unsupported("unsupported meta version")); + } + + let mut read_handler_box = false; + let mut primary_item_id = None; + let mut item_infos = None; + let mut iloc_items = None; + let mut item_references = None; + let mut item_properties = None; + let mut item_data_box = None; + + let mut iter = src.box_iter(); + while let Some(mut b) = iter.next_box()? { + trace!("read_avif_meta parsing {:?} box", b.head.name); + + if !read_handler_box && b.head.name != BoxType::HandlerBox { + fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::HdlrNotFirst, + )?; + } + + match b.head.name { + BoxType::HandlerBox => { + if read_handler_box { + return Status::HdrlBadQuantity.into(); + } + let HandlerBox { handler_type } = read_hdlr(&mut b, strictness)?; + if handler_type != b"pict" { + fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::HdlrTypeNotPict, + )?; + } + read_handler_box = true; + } + BoxType::ItemInfoBox => { + if item_infos.is_some() { + return Status::IinfBadQuantity.into(); + } + item_infos = Some(read_iinf(&mut b, strictness, unsupported_features)?); + } + BoxType::ItemLocationBox => { + if iloc_items.is_some() { + return Status::IlocBadQuantity.into(); + } + iloc_items = Some(read_iloc(&mut b)?); + } + BoxType::PrimaryItemBox => { + if primary_item_id.is_some() { + return Status::PitmBadQuantity.into(); + } + primary_item_id = Some(read_pitm(&mut b)?); + } + BoxType::ItemReferenceBox => { + if item_references.is_some() { + return Status::IrefBadQuantity.into(); + } + item_references = Some(read_iref(&mut b)?); + } + BoxType::ItemPropertiesBox => { + if item_properties.is_some() { + return Status::IprpBadQuantity.into(); + } + item_properties = Some(read_iprp( + &mut b, + MIF1_BRAND, + strictness, + unsupported_features, + )?); + } + BoxType::ItemDataBox => { + if item_data_box.is_some() { + return Status::IdatBadQuantity.into(); + } + let data = b.read_into_try_vec()?; + item_data_box = Some(DataBox::from_idat(data)); + } + _ => skip_box_content(&mut b)?, + } + + check_parser_state!(b.content); + } + + Ok(AvifMeta { + item_properties: item_properties.unwrap_or_default(), + item_references: item_references.unwrap_or_default(), + primary_item_id, + item_infos: item_infos.unwrap_or_default(), + iloc_items: iloc_items.unwrap_or_default(), + item_data_box, + }) +} + +/// Parse a Primary Item Box +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.4 +fn read_pitm<T: Read>(src: &mut BMFFBox<T>) -> Result<ItemId> { + let version = read_fullbox_version_no_flags(src)?; + + let item_id = ItemId(match version { + 0 => be_u16(src)?.into(), + 1 => be_u32(src)?, + _ => return Err(Error::Unsupported("unsupported pitm version")), + }); + + Ok(item_id) +} + +/// Parse an Item Information Box +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.6 +fn read_iinf<T: Read>( + src: &mut BMFFBox<T>, + strictness: ParseStrictness, + unsupported_features: &mut UnsupportedFeatures, +) -> 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 Status::IinfBadChild.into(); + } + + if let Some(infe) = read_infe(&mut b, strictness, unsupported_features)? { + item_infos.push(infe)?; + } + + check_parser_state!(b.content); + } + + Ok(item_infos) +} + +/// A simple wrapper to interpret a u32 as a 4-byte string in big-endian +/// order without requiring any allocation. +struct U32BE(u32); + +impl std::fmt::Display for U32BE { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match std::str::from_utf8(&self.0.to_be_bytes()) { + Ok(s) => f.write_str(s), + Err(_) => write!(f, "{:x?}", self.0), + } + } +} + +/// Parse an Item Info Entry +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.6.2 +fn read_infe<T: Read>( + src: &mut BMFFBox<T>, + strictness: ParseStrictness, + unsupported_features: &mut UnsupportedFeatures, +) -> Result<Option<ItemInfoEntry>> { + let (version, flags) = read_fullbox_extra(src)?; + + // According to the standard, it seems the flags field shall be 0, but at + // least one sample AVIF image has a nonzero value. + // See https://github.com/AOMediaCodec/av1-avif/issues/146 + if flags != 0 { + fail_with_status_if( + strictness == ParseStrictness::Strict, + Status::InfeFlagsNonzero, + )?; + } + + // mif1 brand (see HEIF (ISO 23008-12:2017) § 10.2.1) only requires v2 and 3 + let item_id = ItemId(match version { + 2 => be_u16(src)?.into(), + 3 => be_u32(src)?, + _ => return Err(Error::Unsupported("unsupported version in 'infe' box")), + }); + + let item_protection_index = be_u16(src)?; + + let item_type = be_u32(src)?; + debug!("infe {:?} item_type: {}", item_id, U32BE(item_type)); + + // There are some additional fields here, but they're not of interest to us + skip_box_remain(src)?; + + if item_protection_index != 0 { + unsupported_features.insert(Feature::Ipro); + Ok(None) + } else { + Ok(Some(ItemInfoEntry { item_id, item_type })) + } +} + +/// Parse an Item Reference Box +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.12 +fn read_iref<T: Read>(src: &mut BMFFBox<T>) -> Result<TryVec<SingleItemTypeReferenceBox>> { + let mut item_references = TryVec::new(); + let version = read_fullbox_version_no_flags(src)?; + if version > 1 { + return Err(Error::Unsupported("iref version")); + } + + let mut iter = src.box_iter(); + while let Some(mut b) = iter.next_box()? { + trace!("read_iref parsing {:?} referenceType", b.head.name); + let from_item_id = ItemId::read(&mut b, version)?; + let reference_count = be_u16(&mut b)?; + item_references.reserve(reference_count.to_usize())?; + for _ in 0..reference_count { + let to_item_id = ItemId::read(&mut b, version)?; + if from_item_id == to_item_id { + return Status::IrefRecursion.into(); + } + item_references.push(SingleItemTypeReferenceBox { + item_type: b.head.name.into(), + from_item_id, + to_item_id, + })?; + } + check_parser_state!(b.content); + } + + trace!("read_iref -> {:#?}", item_references); + + Ok(item_references) +} + +/// Parse an Item Properties Box +/// +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.14) +/// +/// Note: HEIF (ISO 23008-12:2017) § 9.3.1 also defines the `iprp` box and +/// related types, but lacks additional requirements specified in 14496-12:2020. +/// +/// Note: Currently HEIF (ISO 23008-12:2017) § 6.5.5.1 specifies "At most one" +/// `colr` box per item, but this is being amended in [DIS 23008-12](https://www.iso.org/standard/83650.html). +/// The new text is likely to be "At most one for a given value of `colour_type`", +/// so this implementation adheres to that language for forward compatibility. +fn read_iprp<T: Read>( + src: &mut BMFFBox<T>, + brand: FourCC, + strictness: ParseStrictness, + unsupported_features: &mut UnsupportedFeatures, +) -> Result<ItemPropertiesBox> { + let mut iter = src.box_iter(); + + let properties = match iter.next_box()? { + Some(mut b) if b.head.name == BoxType::ItemPropertyContainerBox => { + read_ipco(&mut b, strictness) + } + Some(_) => Status::IprpBadChild.into(), + None => Err(Error::UnexpectedEOF), + }?; + + let mut ipma_version_and_flag_values_seen = TryVec::with_capacity(1)?; + let mut association_entries = TryVec::<ItemPropertyAssociationEntry>::new(); + let mut forbidden_items = TryVec::new(); + + while let Some(mut b) = iter.next_box()? { + if b.head.name != BoxType::ItemPropertyAssociationBox { + return Status::IprpBadChild.into(); + } + + let (version, flags) = read_fullbox_extra(&mut b)?; + if ipma_version_and_flag_values_seen.contains(&(version, flags)) { + fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::IpmaBadQuantity, + )?; + } + if flags != 0 && properties.len() <= 127 { + fail_with_status_if( + strictness == ParseStrictness::Strict, + Status::IpmaFlagsNonzero, + )?; + } + ipma_version_and_flag_values_seen.push((version, flags))?; + for association_entry in read_ipma(&mut b, strictness, version, flags)? { + if forbidden_items.contains(&association_entry.item_id) { + warn!( + "Skipping {:?} since the item referenced shall not be processed", + association_entry + ); + } + + if let Some(previous_entry) = association_entries + .iter() + .find(|e| association_entry.item_id == e.item_id) + { + error!( + "Duplicate ipma entries for item_id\n1: {:?}\n2: {:?}", + previous_entry, association_entry + ); + // It's technically possible to make sense of this situation by merging ipma + // boxes, but this is a "shall" requirement, so we'd only do it in + // ParseStrictness::Permissive mode, and this hasn't shown up in the wild + return Status::IpmaDuplicateItemId.into(); + } + + const TRANSFORM_ORDER: &[BoxType] = &[ + BoxType::ImageSpatialExtentsProperty, + BoxType::CleanApertureBox, + BoxType::ImageRotation, + BoxType::ImageMirror, + ]; + let mut prev_transform_index = None; + // Realistically, there should only ever be 1 nclx and 1 icc + let mut colour_type_indexes: TryHashMap<FourCC, PropertyIndex> = + TryHashMap::with_capacity(2)?; + + for a in &association_entry.associations { + if a.property_index == PropertyIndex(0) { + if a.essential { + fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::IpmaIndexZeroNoEssential, + )?; + } + continue; + } + + if let Some(property) = properties.get(&a.property_index) { + assert!(brand == MIF1_BRAND); + + let feature = Feature::try_from(property); + let property_supported = match feature { + Ok(feature) => { + if feature.supported() { + true + } else { + unsupported_features.insert(feature); + false + } + } + Err(_) => false, + }; + + if !property_supported { + if a.essential && strictness != ParseStrictness::Permissive { + error!("Unsupported essential property {:?}", property); + forbidden_items.push(association_entry.item_id)?; + } else { + debug!( + "Ignoring unknown {} property {:?}", + if a.essential { + "essential" + } else { + "non-essential" + }, + property + ); + } + } + + // Check additional requirements on specific properties + match property { + ItemProperty::AV1Config(_) + | ItemProperty::CleanAperture + | ItemProperty::Mirroring(_) + | ItemProperty::Rotation(_) => { + if !a.essential { + warn!("{:?} is missing required 'essential' bit", property); + // This is a "shall", but it is likely to change, so only + // fail if using strict parsing. + // See https://github.com/mozilla/mp4parse-rust/issues/284 + fail_with_status_if( + strictness == ParseStrictness::Strict, + Status::TxformNoEssential, + )?; + } + } + + // NOTE: this is contrary to the published specification; see doc comment + // at the beginning of this function for more details + ItemProperty::Colour(colr) => { + let colour_type = colr.colour_type(); + if let Some(prev_colr_index) = colour_type_indexes.get(&colour_type) { + warn!( + "Multiple '{}' type colr associations with {:?}: {:?} and {:?}", + colour_type, + association_entry.item_id, + a.property_index, + prev_colr_index + ); + fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::ColrBadQuantity, + )?; + } else { + colour_type_indexes.insert(colour_type, a.property_index)?; + } + } + + // The following properties are unsupported, but we still enforce that + // they've been correctly marked as essential or not. + ItemProperty::LayeredImageIndexing => { + assert!(feature.is_ok() && unsupported_features.contains(feature?)); + if a.essential { + fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::A1lxEssential, + )?; + } + } + + ItemProperty::LayerSelection => { + assert!(feature.is_ok() && unsupported_features.contains(feature?)); + if a.essential { + assert!( + forbidden_items.contains(&association_entry.item_id) + || strictness == ParseStrictness::Permissive + ); + } else { + fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::LselNoEssential, + )?; + } + } + + ItemProperty::OperatingPointSelector => { + assert!(feature.is_ok() && unsupported_features.contains(feature?)); + if a.essential { + assert!( + forbidden_items.contains(&association_entry.item_id) + || strictness == ParseStrictness::Permissive + ); + } else { + fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::A1opNoEssential, + )?; + } + } + + other_property => { + trace!("No additional checks for {:?}", other_property); + } + } + + if let Some(transform_index) = TRANSFORM_ORDER + .iter() + .position(|t| *t == BoxType::from(property)) + { + if let Some(prev) = prev_transform_index { + if prev >= transform_index { + error!( + "Invalid property order: {:?} after {:?}", + TRANSFORM_ORDER[transform_index], TRANSFORM_ORDER[prev] + ); + fail_with_status_if( + strictness != ParseStrictness::Permissive, + if TRANSFORM_ORDER[transform_index] + == BoxType::ImageSpatialExtentsProperty + { + Status::TxformBeforeIspe + } else { + Status::TxformOrder + }, + )?; + } + } + prev_transform_index = Some(transform_index); + } + } else { + error!( + "Missing property at {:?} for {:?}", + a.property_index, association_entry.item_id + ); + fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::IpmaBadIndex, + )?; + } + } + association_entries.push(association_entry)? + } + + check_parser_state!(b.content); + } + + let iprp = ItemPropertiesBox { + properties, + association_entries, + forbidden_items, + }; + trace!("read_iprp -> {:#?}", iprp); + Ok(iprp) +} + +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.14.1 +/// Variants with no associated data are recognized but not necessarily supported. +/// See [`Feature`] to determine support. +#[derive(Debug)] +pub enum ItemProperty { + AuxiliaryType(AuxiliaryTypeProperty), + AV1Config(AV1ConfigBox), + Channels(PixelInformation), + CleanAperture, + Colour(ColourInformation), + ImageSpatialExtents(ImageSpatialExtentsProperty), + LayeredImageIndexing, + LayerSelection, + Mirroring(ImageMirror), + OperatingPointSelector, + PixelAspectRatio(PixelAspectRatio), + Rotation(ImageRotation), + /// Necessary to validate property indices in read_iprp + Unsupported(BoxType), +} + +impl From<&ItemProperty> for BoxType { + fn from(item_property: &ItemProperty) -> Self { + match item_property { + ItemProperty::AuxiliaryType(_) => BoxType::AuxiliaryTypeProperty, + ItemProperty::AV1Config(_) => BoxType::AV1CodecConfigurationBox, + ItemProperty::CleanAperture => BoxType::CleanApertureBox, + ItemProperty::Colour(_) => BoxType::ColourInformationBox, + ItemProperty::LayeredImageIndexing => BoxType::AV1LayeredImageIndexingProperty, + ItemProperty::LayerSelection => BoxType::LayerSelectorProperty, + ItemProperty::Mirroring(_) => BoxType::ImageMirror, + ItemProperty::OperatingPointSelector => BoxType::OperatingPointSelectorProperty, + ItemProperty::PixelAspectRatio(_) => BoxType::PixelAspectRatioBox, + ItemProperty::Rotation(_) => BoxType::ImageRotation, + ItemProperty::ImageSpatialExtents(_) => BoxType::ImageSpatialExtentsProperty, + ItemProperty::Channels(_) => BoxType::PixelInformationBox, + ItemProperty::Unsupported(box_type) => *box_type, + } + } +} + +#[derive(Debug)] +struct ItemPropertyAssociationEntry { + item_id: ItemId, + associations: TryVec<Association>, +} + +/// For storing ItemPropertyAssociation data +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.14.1 +#[derive(Debug)] +struct Association { + essential: bool, + property_index: PropertyIndex, +} + +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.14.1 +/// +/// The properties themselves are stored in `properties`, but the items they're +/// associated with are stored in `association_entries`. It's necessary to +/// maintain this indirection because multiple items can reference the same +/// property. For example, both the primary item and alpha item can share the +/// same [`ImageSpatialExtentsProperty`]. +#[derive(Debug, Default)] +pub struct ItemPropertiesBox { + /// `ItemPropertyContainerBox property_container` in the spec + properties: TryHashMap<PropertyIndex, ItemProperty>, + /// `ItemPropertyAssociationBox association[]` in the spec + association_entries: TryVec<ItemPropertyAssociationEntry>, + /// Items that shall not be processed due to unsupported properties that + /// have been marked essential. + /// See HEIF (ISO/IEC 23008-12:2017) § 9.3.1 + forbidden_items: TryVec<ItemId>, +} + +impl ItemPropertiesBox { + /// For displayable images `av1C`, `pixi` and `ispe` are mandatory, `colr` + /// is typically included too, so we might as well use an even power of 2. + const MIN_PROPERTIES: usize = 4; + + fn is_alpha(&self, item_id: ItemId) -> bool { + match self.get(item_id, BoxType::AuxiliaryTypeProperty) { + Ok(Some(ItemProperty::AuxiliaryType(urn))) => { + urn.aux_type.as_slice() == "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha".as_bytes() + } + Ok(Some(other_property)) => panic!("property key mismatch: {:?}", other_property), + Ok(None) => false, + Err(e) => { + error!( + "is_alpha: Error checking AuxiliaryTypeProperty ({}), returning false", + e + ); + false + } + } + } + + fn get(&self, item_id: ItemId, property_type: BoxType) -> Result<Option<&ItemProperty>> { + match self + .get_multiple(item_id, |prop| BoxType::from(prop) == property_type)? + .as_slice() + { + &[] => Ok(None), + &[single_value] => Ok(Some(single_value)), + multiple_values => { + error!( + "Multiple values for {:?}: {:?}", + property_type, multiple_values + ); + // TODO: add test + Status::IprpConflict.into() + } + } + } + + fn get_multiple( + &self, + item_id: ItemId, + filter: impl Fn(&ItemProperty) -> bool, + ) -> Result<TryVec<&ItemProperty>> { + let mut values = TryVec::new(); + for entry in &self.association_entries { + for a in &entry.associations { + if entry.item_id == item_id { + match self.properties.get(&a.property_index) { + Some(ItemProperty::Unsupported(_)) => {} + Some(property) if filter(property) => values.push(property)?, + _ => {} + } + } + } + } + + Ok(values) + } +} + +/// An upper bound which can be used to check overflow at compile time +trait UpperBounded { + const MAX: u64; +} + +/// Implement type $name as a newtype wrapper around an unsigned int which +/// implements the UpperBounded trait. +macro_rules! impl_bounded { + ( $name:ident, $inner:ty ) => { + #[derive(Clone, Copy)] + pub struct $name($inner); + + impl $name { + pub const fn new(n: $inner) -> Self { + Self(n) + } + + #[allow(dead_code)] + pub fn get(self) -> $inner { + self.0 + } + } + + impl UpperBounded for $name { + const MAX: u64 = <$inner>::MAX as u64; + } + }; +} + +/// Implement type $name as a type representing the product of two unsigned ints +/// which implements the UpperBounded trait. +macro_rules! impl_bounded_product { + ( $name:ident, $multiplier:ty, $multiplicand:ty, $inner:ty) => { + #[derive(Clone, Copy)] + pub struct $name($inner); + + impl $name { + pub fn new(value: $inner) -> Self { + assert!(value <= Self::MAX); + Self(value) + } + + pub fn get(self) -> $inner { + self.0 + } + } + + impl UpperBounded for $name { + const MAX: u64 = <$multiplier>::MAX * <$multiplicand>::MAX; + } + }; +} + +mod bounded_uints { + use crate::UpperBounded; + + impl_bounded!(U8, u8); + impl_bounded!(U16, u16); + impl_bounded!(U32, u32); + impl_bounded!(U64, u64); + + impl_bounded_product!(U32MulU8, U32, U8, u64); + impl_bounded_product!(U32MulU16, U32, U16, u64); + + impl UpperBounded for std::num::NonZeroU8 { + const MAX: u64 = u8::MAX as u64; + } +} + +use crate::bounded_uints::*; + +/// Implement the multiplication operator for $lhs * $rhs giving $output, which +/// is internally represented as $inner. The operation is statically checked +/// to ensure the product won't overflow $inner, nor exceed <$output>::MAX. +macro_rules! impl_mul { + ( ($lhs:ty , $rhs:ty) => ($output:ty, $inner:ty) ) => { + impl std::ops::Mul<$rhs> for $lhs { + type Output = $output; + + fn mul(self, rhs: $rhs) -> Self::Output { + static_assertions::const_assert!( + <$output as UpperBounded>::MAX <= <$inner>::MAX as u64 + ); + static_assertions::const_assert!( + <$lhs as UpperBounded>::MAX * <$rhs as UpperBounded>::MAX + <= <$output as UpperBounded>::MAX + ); + + let lhs: $inner = self.get().into(); + let rhs: $inner = rhs.get().into(); + Self::Output::new(lhs.checked_mul(rhs).expect("infallible")) + } + } + }; +} + +impl_mul!((U8, std::num::NonZeroU8) => (U16, u16)); +impl_mul!((U32, std::num::NonZeroU8) => (U32MulU8, u64)); +impl_mul!((U32, U16) => (U32MulU16, u64)); + +impl std::ops::Add<U32MulU16> for U32MulU8 { + type Output = U64; + + fn add(self, rhs: U32MulU16) -> Self::Output { + static_assertions::const_assert!(U32MulU8::MAX + U32MulU16::MAX < U64::MAX); + let lhs: u64 = self.get(); + let rhs: u64 = rhs.get(); + Self::Output::new(lhs.checked_add(rhs).expect("infallible")) + } +} + +const MAX_IPMA_ASSOCIATION_COUNT: U8 = U8::new(u8::MAX); + +/// After reading only the `entry_count` field of an ipma box, we can check its +/// basic validity and calculate (assuming validity) the number of associations +/// which will be contained (allowing preallocation of the storage). +/// All the arithmetic is compile-time verified to not overflow via supporting +/// types implementing the UpperBounded trait. Types are declared explicitly to +/// show there isn't any accidental inference to primitive types. +/// +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.14.1 +fn calculate_ipma_total_associations( + version: u8, + bytes_left: u64, + entry_count: U32, + num_association_bytes: std::num::NonZeroU8, +) -> Result<usize> { + let min_entry_bytes = + std::num::NonZeroU8::new(1 /* association_count */ + if version == 0 { 2 } else { 4 }) + .unwrap(); + + let total_non_association_bytes: U32MulU8 = entry_count * min_entry_bytes; + let total_association_bytes: u64 = + if let Some(difference) = bytes_left.checked_sub(total_non_association_bytes.get()) { + // All the storage for the `essential` and `property_index` parts (assuming a valid ipma box size) + difference + } else { + return Status::IpmaTooSmall.into(); + }; + + let max_association_bytes_per_entry: U16 = MAX_IPMA_ASSOCIATION_COUNT * num_association_bytes; + let max_total_association_bytes: U32MulU16 = entry_count * max_association_bytes_per_entry; + let max_bytes_left: U64 = total_non_association_bytes + max_total_association_bytes; + + if bytes_left > max_bytes_left.get() { + return Status::IpmaTooBig.into(); + } + + let total_associations: u64 = total_association_bytes / u64::from(num_association_bytes.get()); + + Ok(total_associations.try_into()?) +} + +/// Parse an ItemPropertyAssociation box +/// +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.14.1 +fn read_ipma<T: Read>( + src: &mut BMFFBox<T>, + strictness: ParseStrictness, + version: u8, + flags: u32, +) -> Result<TryVec<ItemPropertyAssociationEntry>> { + let entry_count = be_u32(src)?; + let num_association_bytes = + std::num::NonZeroU8::new(if flags & 1 == 1 { 2 } else { 1 }).unwrap(); + + let total_associations = calculate_ipma_total_associations( + version, + src.bytes_left(), + U32::new(entry_count), + num_association_bytes, + )?; + // Assuming most items will have at least `MIN_PROPERTIES` and knowing the + // total number of item -> property associations (`total_associations`), + // we can provide a good estimate for how many elements we'll need in this + // vector, even though we don't know precisely how many items there will be + // properties for. + let mut entries = TryVec::<ItemPropertyAssociationEntry>::with_capacity( + total_associations / ItemPropertiesBox::MIN_PROPERTIES, + )?; + + for _ in 0..entry_count { + let item_id = ItemId::read(src, version)?; + + if let Some(previous_association) = entries.last() { + #[allow(clippy::comparison_chain)] + if previous_association.item_id > item_id { + return Status::IpmaBadItemOrder.into(); + } else if previous_association.item_id == item_id { + return Status::IpmaDuplicateItemId.into(); + } + } + + let association_count = src.read_u8()?; + let mut associations = TryVec::with_capacity(association_count.to_usize())?; + for _ in 0..association_count { + let association = src + .take(num_association_bytes.get().into()) + .read_into_try_vec()?; + let mut association = BitReader::new(association.as_slice()); + let essential = association.read_bool()?; + let property_index = + PropertyIndex(association.read_u16(association.remaining().try_into()?)?); + associations.push(Association { + essential, + property_index, + })?; + } + + entries.push(ItemPropertyAssociationEntry { + item_id, + associations, + })?; + } + + check_parser_state!(src.content); + + if version != 0 { + if let Some(ItemPropertyAssociationEntry { + item_id: max_item_id, + .. + }) = entries.last() + { + if *max_item_id <= ItemId(u16::MAX.into()) { + fail_with_status_if( + strictness == ParseStrictness::Strict, + Status::IpmaBadVersion, + )?; + } + } + } + + trace!("read_ipma -> {:#?}", entries); + + Ok(entries) +} + +/// Parse an ItemPropertyContainerBox +/// +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.14.1 +fn read_ipco<T: Read>( + src: &mut BMFFBox<T>, + strictness: ParseStrictness, +) -> Result<TryHashMap<PropertyIndex, ItemProperty>> { + let mut properties = TryHashMap::with_capacity(ItemPropertiesBox::MIN_PROPERTIES)?; + + let mut index = PropertyIndex(1); // ipma uses 1-based indexing + let mut iter = src.box_iter(); + while let Some(mut b) = iter.next_box()? { + let property = match b.head.name { + BoxType::AuxiliaryTypeProperty => ItemProperty::AuxiliaryType(read_auxc(&mut b)?), + BoxType::AV1CodecConfigurationBox => ItemProperty::AV1Config(read_av1c(&mut b)?), + BoxType::ColourInformationBox => ItemProperty::Colour(read_colr(&mut b, strictness)?), + BoxType::ImageMirror => ItemProperty::Mirroring(read_imir(&mut b)?), + BoxType::ImageRotation => ItemProperty::Rotation(read_irot(&mut b)?), + BoxType::ImageSpatialExtentsProperty => { + ItemProperty::ImageSpatialExtents(read_ispe(&mut b)?) + } + BoxType::PixelAspectRatioBox => ItemProperty::PixelAspectRatio(read_pasp(&mut b)?), + BoxType::PixelInformationBox => ItemProperty::Channels(read_pixi(&mut b)?), + + other_box_type => { + // Even if we didn't do anything with other property types, we still store + // a record at the index to identify invalid indices in ipma boxes + skip_box_remain(&mut b)?; + let item_property = match other_box_type { + BoxType::AV1LayeredImageIndexingProperty => ItemProperty::LayeredImageIndexing, + BoxType::CleanApertureBox => ItemProperty::CleanAperture, + BoxType::LayerSelectorProperty => ItemProperty::LayerSelection, + BoxType::OperatingPointSelectorProperty => ItemProperty::OperatingPointSelector, + _ => { + warn!("No ItemProperty variant for {:?}", other_box_type); + ItemProperty::Unsupported(other_box_type) + } + }; + debug!("Storing empty record {:?}", item_property); + item_property + } + }; + properties.insert(index, property)?; + + index = PropertyIndex( + index + .0 + .checked_add(1) // must include ignored properties to have correct indexes + .ok_or_else(|| Error::from(Status::IpcoIndexOverflow))?, + ); + + check_parser_state!(b.content); + } + + Ok(properties) +} + +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ImageSpatialExtentsProperty { + image_width: u32, + image_height: u32, +} + +/// Parse image spatial extents property +/// +/// See HEIF (ISO 23008-12:2017) § 6.5.3.1 +fn read_ispe<T: Read>(src: &mut BMFFBox<T>) -> Result<ImageSpatialExtentsProperty> { + if read_fullbox_version_no_flags(src)? != 0 { + return Err(Error::Unsupported("ispe version")); + } + + let image_width = be_u32(src)?; + let image_height = be_u32(src)?; + + Ok(ImageSpatialExtentsProperty { + image_width, + image_height, + }) +} + +#[repr(C)] +#[derive(Debug)] +pub struct PixelAspectRatio { + h_spacing: u32, + v_spacing: u32, +} + +/// Parse pixel aspect ratio property +/// +/// See HEIF (ISO 23008-12:2017) § 6.5.4.1 +/// See ISOBMFF (ISO 14496-12:2020) § 12.1.4.2 +fn read_pasp<T: Read>(src: &mut BMFFBox<T>) -> Result<PixelAspectRatio> { + let h_spacing = be_u32(src)?; + let v_spacing = be_u32(src)?; + + Ok(PixelAspectRatio { + h_spacing, + v_spacing, + }) +} + +#[derive(Debug)] +pub struct PixelInformation { + bits_per_channel: TryVec<u8>, +} + +/// Parse pixel information +/// See HEIF (ISO 23008-12:2017) § 6.5.6 +fn read_pixi<T: Read>(src: &mut BMFFBox<T>) -> Result<PixelInformation> { + let version = read_fullbox_version_no_flags(src)?; + if version != 0 { + return Err(Error::Unsupported("pixi version")); + } + + let num_channels = src.read_u8()?; + let mut bits_per_channel = TryVec::with_capacity(num_channels.to_usize())?; + let num_channels_read = src.try_read_to_end(&mut bits_per_channel)?; + + if u8::try_from(num_channels_read)? != num_channels { + return Status::PixiBadChannelCount.into(); + } + + check_parser_state!(src.content); + Ok(PixelInformation { bits_per_channel }) +} + +/// Despite [Rec. ITU-T H.273] (12/2016) defining the CICP fields as having a +/// range of 0-255, and only a small fraction of those values being used, +/// ISOBMFF (ISO 14496-12:2020) § 12.1.5 defines them as 16-bit values in the +/// `colr` box. Since we have no use for the additional range, and it would +/// complicate matters later, we fallibly convert before storing the input. +/// +/// [Rec. ITU-T H.273]: https://www.itu.int/rec/T-REC-H.273-201612-I/en +#[repr(C)] +#[derive(Debug)] +pub struct NclxColourInformation { + colour_primaries: u8, + transfer_characteristics: u8, + matrix_coefficients: u8, + full_range_flag: bool, +} + +/// The raw bytes of the ICC profile +#[repr(C)] +pub struct IccColourInformation { + bytes: TryVec<u8>, +} + +impl fmt::Debug for IccColourInformation { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("IccColourInformation") + .field("data", &format_args!("{} bytes", self.bytes.len())) + .finish() + } +} + +#[repr(C)] +#[derive(Debug)] +pub enum ColourInformation { + Nclx(NclxColourInformation), + Icc(IccColourInformation, FourCC), +} + +impl ColourInformation { + fn colour_type(&self) -> FourCC { + match self { + Self::Nclx(_) => FourCC::from(*b"nclx"), + Self::Icc(_, colour_type) => colour_type.clone(), + } + } +} + +/// Parse colour information +/// See ISOBMFF (ISO 14496-12:2020) § 12.1.5 +fn read_colr<T: Read>( + src: &mut BMFFBox<T>, + strictness: ParseStrictness, +) -> Result<ColourInformation> { + let colour_type = be_u32(src)?.to_be_bytes(); + + match &colour_type { + b"nclx" => { + const NUM_RESERVED_BITS: u8 = 7; + let colour_primaries = be_u16(src)?.try_into()?; + let transfer_characteristics = be_u16(src)?.try_into()?; + let matrix_coefficients = be_u16(src)?.try_into()?; + let bytes = src.read_into_try_vec()?; + let mut bit_reader = BitReader::new(&bytes); + let full_range_flag = bit_reader.read_bool()?; + if bit_reader.remaining() != NUM_RESERVED_BITS.into() { + error!( + "read_colr expected {} reserved bits, found {}", + NUM_RESERVED_BITS, + bit_reader.remaining() + ); + return Status::ColrBadSize.into(); + } + if bit_reader.read_u8(NUM_RESERVED_BITS)? != 0 { + fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::ColrReservedNonzero, + )?; + } + + Ok(ColourInformation::Nclx(NclxColourInformation { + colour_primaries, + transfer_characteristics, + matrix_coefficients, + full_range_flag, + })) + } + b"rICC" | b"prof" => Ok(ColourInformation::Icc( + IccColourInformation { + bytes: src.read_into_try_vec()?, + }, + FourCC::from(colour_type), + )), + _ => { + error!("read_colr colour_type: {:?}", colour_type); + Status::ColrBadType.into() + } + } +} + +#[repr(C)] +#[derive(Clone, Copy, Debug)] +/// Rotation in the positive (that is, anticlockwise) direction +/// Visualized in terms of starting with (⥠) UPWARDS HARPOON WITH BARB LEFT FROM BAR +/// similar to a DIGIT ONE (1) +pub enum ImageRotation { + /// ⥠ UPWARDS HARPOON WITH BARB LEFT FROM BAR + D0, + /// ⥞ LEFTWARDS HARPOON WITH BARB DOWN FROM BAR + D90, + /// ⥝ DOWNWARDS HARPOON WITH BARB RIGHT FROM BAR + D180, + /// ⥛ RIGHTWARDS HARPOON WITH BARB UP FROM BAR + D270, +} + +/// Parse image rotation box +/// See HEIF (ISO 23008-12:2017) § 6.5.10 +fn read_irot<T: Read>(src: &mut BMFFBox<T>) -> Result<ImageRotation> { + let irot = src.read_into_try_vec()?; + let mut irot = BitReader::new(&irot); + let _reserved = irot.read_u8(6)?; + let image_rotation = match irot.read_u8(2)? { + 0 => ImageRotation::D0, + 1 => ImageRotation::D90, + 2 => ImageRotation::D180, + 3 => ImageRotation::D270, + _ => unreachable!(), + }; + + check_parser_state!(src.content); + + Ok(image_rotation) +} + +/// The axis about which the image is mirrored (opposite of flip) +/// Visualized in terms of starting with (⥠) UPWARDS HARPOON WITH BARB LEFT FROM BAR +/// similar to a DIGIT ONE (1) +#[repr(C)] +#[derive(Debug)] +pub enum ImageMirror { + /// top and bottom parts exchanged + /// ⥡ DOWNWARDS HARPOON WITH BARB LEFT FROM BAR + TopBottom, + /// left and right parts exchanged + /// ⥜ UPWARDS HARPOON WITH BARB RIGHT FROM BAR + LeftRight, +} + +/// Parse image mirroring box +/// See HEIF (ISO 23008-12:2017) § 6.5.12<br /> +/// Note: [ISO/IEC 23008-12:2017/DAmd 2](https://www.iso.org/standard/81688.html) +/// reverses the interpretation of the 'imir' box in § 6.5.12.3: +/// > `axis` specifies a vertical (`axis` = 0) or horizontal (`axis` = 1) axis +/// > for the mirroring operation. +/// +/// is replaced with: +/// > `mode` specifies how the mirroring is performed: 0 indicates that the top +/// > and bottom parts of the image are exchanged; 1 specifies that the left and +/// > right parts are exchanged. +/// > +/// > NOTE: In Exif, orientation tag can be used to signal mirroring operations. +/// > Exif orientation tag 4 corresponds to `mode` = 0 of `ImageMirror`, and +/// > Exif orientation tag 2 corresponds to `mode` = 1 accordingly. +/// +/// This implementation conforms to the text in Draft Amendment 2, which is the +/// opposite of the published standard as of 4 June 2021. +fn read_imir<T: Read>(src: &mut BMFFBox<T>) -> Result<ImageMirror> { + let imir = src.read_into_try_vec()?; + let mut imir = BitReader::new(&imir); + let _reserved = imir.read_u8(7)?; + let image_mirror = match imir.read_u8(1)? { + 0 => ImageMirror::TopBottom, + 1 => ImageMirror::LeftRight, + _ => unreachable!(), + }; + + check_parser_state!(src.content); + + Ok(image_mirror) +} + +/// See HEIF (ISO 23008-12:2017) § 6.5.8 +#[derive(Debug, PartialEq)] +pub struct AuxiliaryTypeProperty { + aux_type: TryString, + aux_subtype: TryString, +} + +/// Parse image properties for auxiliary images +/// See HEIF (ISO 23008-12:2017) § 6.5.8 +fn read_auxc<T: Read>(src: &mut BMFFBox<T>) -> Result<AuxiliaryTypeProperty> { + let version = read_fullbox_version_no_flags(src)?; + if version != 0 { + return Err(Error::Unsupported("auxC version")); + } + + let mut aux = TryString::new(); + src.try_read_to_end(&mut aux)?; + + let (aux_type, aux_subtype): (TryString, TryVec<u8>); + if let Some(nul_byte_pos) = aux.iter().position(|&b| b == b'\0') { + let (a, b) = aux.as_slice().split_at(nul_byte_pos); + aux_type = a.try_into()?; + aux_subtype = (b[1..]).try_into()?; + } else { + aux_type = aux; + aux_subtype = TryVec::new(); + } + + Ok(AuxiliaryTypeProperty { + aux_type, + aux_subtype, + }) +} + +/// Parse an item location box inside a meta box +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.3 +fn read_iloc<T: Read>(src: &mut BMFFBox<T>) -> Result<TryHashMap<ItemId, ItemLocationBoxItem>> { + let version: IlocVersion = read_fullbox_version_no_flags(src)?.try_into()?; + + let iloc = src.read_into_try_vec()?; + let mut iloc = BitReader::new(&iloc); + + let offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?; + let length_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?; + let base_offset_size: IlocFieldSize = iloc.read_u8(4)?.try_into()?; + + let index_size: Option<IlocFieldSize> = match version { + IlocVersion::One | IlocVersion::Two => Some(iloc.read_u8(4)?.try_into()?), + IlocVersion::Zero => { + let _reserved = iloc.read_u8(4)?; + None + } + }; + + let item_count = match version { + IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?, + IlocVersion::Two => iloc.read_u32(32)?, + }; + + let mut items = TryHashMap::with_capacity(item_count.to_usize())?; + + for _ in 0..item_count { + let item_id = ItemId(match version { + IlocVersion::Zero | IlocVersion::One => iloc.read_u32(16)?, + IlocVersion::Two => iloc.read_u32(32)?, + }); + + // The spec isn't entirely clear how an `iloc` should be interpreted for version 0, + // which has no `construction_method` field. It does say: + // "For maximum compatibility, version 0 of this box should be used in preference to + // version 1 with `construction_method==0`, or version 2 when possible." + // We take this to imply version 0 can be interpreted as using file offsets. + let construction_method = match version { + IlocVersion::Zero => ConstructionMethod::File, + IlocVersion::One | IlocVersion::Two => { + let _reserved = iloc.read_u16(12)?; + match iloc.read_u16(4)? { + 0 => ConstructionMethod::File, + 1 => ConstructionMethod::Idat, + 2 => ConstructionMethod::Item, + _ => return Status::IlocBadConstructionMethod.into(), + } + } + }; + + let data_reference_index = iloc.read_u16(16)?; + if data_reference_index != 0 { + return Err(Error::Unsupported( + "external file references (iloc.data_reference_index != 0) are not supported", + )); + } + let base_offset = iloc.read_u64(base_offset_size.as_bits())?; + let extent_count = iloc.read_u16(16)?; + + if extent_count < 1 { + return Status::IlocBadExtentCount.into(); + } + + // "If only one extent is used (extent_count = 1) then either or both of the + // offset and length may be implied" + if extent_count != 1 + && (offset_size == IlocFieldSize::Zero || length_size == IlocFieldSize::Zero) + { + return Status::IlocBadExtent.into(); + } + + let mut extents = TryVec::with_capacity(extent_count.to_usize())?; + + for _ in 0..extent_count { + // Parsed but currently ignored, see `Extent` + let _extent_index = match &index_size { + None | Some(IlocFieldSize::Zero) => None, + Some(index_size) => { + debug_assert!(version == IlocVersion::One || version == IlocVersion::Two); + Some(iloc.read_u64(index_size.as_bits())?) + } + }; + + // Per ISOBMFF (ISO 14496-12:2020) § 8.11.3.1: + // "If the offset is not identified (the field has a length of zero), then the + // beginning of the source (offset 0) is implied" + // This behavior will follow from BitReader::read_u64(0) -> 0. + let extent_offset = iloc.read_u64(offset_size.as_bits())?; + let extent_length = iloc.read_u64(length_size.as_bits())?.try_into()?; + + // "If the length is not specified, or specified as zero, then the entire length of + // the source is implied" (ibid) + let offset = base_offset + .checked_add(extent_offset) + .ok_or_else(|| Error::from(Status::IlocOffsetOverflow))?; + let extent = if extent_length == 0 { + Extent::ToEnd { offset } + } else { + Extent::WithLength { + offset, + len: extent_length, + } + }; + + extents.push(extent)?; + } + + let loc = ItemLocationBoxItem { + construction_method, + extents, + }; + + if items.insert(item_id, loc)?.is_some() { + return Status::IlocDuplicateItemId.into(); + } + } + + if iloc.remaining() == 0 { + Ok(items) + } else { + Status::IlocBadSize.into() + } +} + +/// Read the contents of a box, including sub boxes. +pub fn read_mp4<T: Read>(f: &mut T) -> Result<MediaContext> { + let mut context = None; + let mut found_ftyp = false; + // TODO(kinetik): Top-level parsing should handle zero-sized boxes + // rather than throwing an error. + let mut iter = BoxIter::new(f); + while let Some(mut b) = iter.next_box()? { + // box ordering: ftyp before any variable length box (inc. moov), + // but may not be first box in file if file signatures etc. present + // fragmented mp4 order: ftyp, moov, pairs of moof/mdat (1-multiple), mfra + + // "special": uuid, wide (= 8 bytes) + // isom: moov, mdat, free, skip, udta, ftyp, moof, mfra + // iso2: pdin, meta + // iso3: meco + // iso5: styp, sidx, ssix, prft + // unknown, maybe: id32 + + // qt: pnot + + // possibly allow anything where all printable and/or all lowercase printable + // "four printable characters from the ISO 8859-1 character set" + match b.head.name { + BoxType::FileTypeBox => { + let ftyp = read_ftyp(&mut b)?; + found_ftyp = true; + debug!("{:?}", ftyp); + } + BoxType::MovieBox => { + context = Some(read_moov(&mut b, context)?); + } + #[cfg(feature = "meta-xml")] + BoxType::MetadataBox => { + if let Some(ctx) = &mut context { + ctx.metadata = Some(read_meta(&mut b)); + } + } + _ => skip_box_content(&mut b)?, + }; + check_parser_state!(b.content); + if context.is_some() { + debug!( + "found moov {}, could stop pure 'moov' parser now", + if found_ftyp { + "and ftyp" + } else { + "but no ftyp" + } + ); + } + } + + // XXX(kinetik): This isn't perfect, as a "moov" with no contents is + // treated as okay but we haven't found anything useful. Needs more + // thought for clearer behaviour here. + context.ok_or(Error::MoovMissing) +} + +/// Parse a Movie Header Box +/// See ISOBMFF (ISO 14496-12:2020) § 8.2.2 +fn parse_mvhd<T: Read>(f: &mut BMFFBox<T>) -> Result<Option<MediaTimeScale>> { + let mvhd = read_mvhd(f)?; + debug!("{:?}", mvhd); + if mvhd.timescale == 0 { + return Status::MvhdBadTimescale.into(); + } + let timescale = Some(MediaTimeScale(u64::from(mvhd.timescale))); + Ok(timescale) +} + +/// Parse a Movie Box +/// See ISOBMFF (ISO 14496-12:2020) § 8.2.1 +/// Note that despite the spec indicating "exactly one" moov box should exist at +/// the file container level, we support reading and merging multiple moov boxes +/// such as with tests/test_case_1185230.mp4. +fn read_moov<T: Read>(f: &mut BMFFBox<T>, context: Option<MediaContext>) -> Result<MediaContext> { + let MediaContext { + mut timescale, + mut tracks, + mut mvex, + mut psshs, + mut userdata, + #[cfg(feature = "meta-xml")] + metadata, + } = context.unwrap_or_default(); + + let mut iter = f.box_iter(); + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::MovieHeaderBox => { + timescale = parse_mvhd(&mut b)?; + } + BoxType::TrackBox => { + let mut track = Track::new(tracks.len()); + read_trak(&mut b, &mut track)?; + tracks.push(track)?; + } + BoxType::MovieExtendsBox => { + mvex = Some(read_mvex(&mut b)?); + debug!("{:?}", mvex); + } + BoxType::ProtectionSystemSpecificHeaderBox => { + let pssh = read_pssh(&mut b)?; + debug!("{:?}", pssh); + psshs.push(pssh)?; + } + BoxType::UserdataBox => { + userdata = Some(read_udta(&mut b)); + debug!("{:?}", userdata); + if let Some(Err(_)) = userdata { + // There was an error parsing userdata. Such failures are not fatal to overall + // parsing, just skip the rest of the box. + skip_box_remain(&mut b)?; + } + } + _ => skip_box_content(&mut b)?, + }; + check_parser_state!(b.content); + } + + Ok(MediaContext { + timescale, + tracks, + mvex, + psshs, + userdata, + #[cfg(feature = "meta-xml")] + metadata, + }) +} + +fn read_pssh<T: Read>(src: &mut BMFFBox<T>) -> Result<ProtectionSystemSpecificHeaderBox> { + let len = src.bytes_left(); + let mut box_content = read_buf(src, len)?; + let (system_id, kid, data) = { + let pssh = &mut Cursor::new(&box_content); + + let (version, _) = read_fullbox_extra(pssh)?; + + let system_id = read_buf(pssh, 16)?; + + let mut kid = TryVec::<ByteData>::new(); + if version > 0 { + const KID_ELEMENT_SIZE: usize = 16; + let count = be_u32(pssh)?.to_usize(); + kid.reserve( + count + .checked_mul(KID_ELEMENT_SIZE) + .ok_or_else(|| Error::from(Status::PsshSizeOverflow))?, + )?; + for _ in 0..count { + let item = read_buf(pssh, KID_ELEMENT_SIZE.to_u64())?; + kid.push(item)?; + } + } + + let data_size = be_u32(pssh)?; + let data = read_buf(pssh, data_size.into())?; + + (system_id, kid, data) + }; + + let mut pssh_box = TryVec::new(); + write_be_u32(&mut pssh_box, src.head.size.try_into()?)?; + pssh_box.extend_from_slice(b"pssh")?; + pssh_box.append(&mut box_content)?; + + Ok(ProtectionSystemSpecificHeaderBox { + system_id, + kid, + data, + box_content: pssh_box, + }) +} + +/// Parse a Movie Extends Box +/// See ISOBMFF (ISO 14496-12:2020) § 8.8.1 +fn read_mvex<T: Read>(src: &mut BMFFBox<T>) -> Result<MovieExtendsBox> { + let mut iter = src.box_iter(); + let mut fragment_duration = None; + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::MovieExtendsHeaderBox => { + let duration = read_mehd(&mut b)?; + fragment_duration = Some(duration); + } + _ => skip_box_content(&mut b)?, + } + } + Ok(MovieExtendsBox { fragment_duration }) +} + +fn read_mehd<T: Read>(src: &mut BMFFBox<T>) -> Result<MediaScaledTime> { + let (version, _) = read_fullbox_extra(src)?; + let fragment_duration = match version { + 1 => be_u64(src)?, + 0 => u64::from(be_u32(src)?), + _ => return Status::MehdBadVersion.into(), + }; + Ok(MediaScaledTime(fragment_duration)) +} + +/// Parse a Track Box +/// See ISOBMFF (ISO 14496-12:2020) § 8.3.1. +fn read_trak<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> { + let mut iter = f.box_iter(); + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::TrackHeaderBox => { + let tkhd = read_tkhd(&mut b)?; + track.track_id = Some(tkhd.track_id); + track.tkhd = Some(tkhd.clone()); + debug!("{:?}", tkhd); + } + BoxType::EditBox => read_edts(&mut b, track)?, + BoxType::MediaBox => read_mdia(&mut b, track)?, + BoxType::TrackReferenceBox => track.tref = Some(read_tref(&mut b)?), + _ => skip_box_content(&mut b)?, + }; + check_parser_state!(b.content); + } + Ok(()) +} + +fn read_edts<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> { + let mut iter = f.box_iter(); + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::EditListBox => { + let elst = read_elst(&mut b)?; + track.looped = Some(elst.looped); + if elst.edits.is_empty() { + debug!("empty edit list"); + continue; + } + let mut empty_duration = 0; + let mut idx = 0; + if elst.edits[idx].media_time == -1 { + if elst.edits.len() < 2 { + debug!("expected additional edit, ignoring edit list"); + continue; + } + empty_duration = elst.edits[idx].segment_duration; + idx += 1; + } + track.empty_duration = Some(MediaScaledTime(empty_duration)); + let media_time = elst.edits[idx].media_time; + if media_time < 0 { + debug!("unexpected negative media time in edit"); + } + track.edited_duration = Some(MediaScaledTime(elst.edits[idx].segment_duration)); + track.media_time = Some(TrackScaledTime::<u64>( + std::cmp::max(0, media_time) as u64, + track.id, + )); + if elst.edits.len() > 2 { + debug!("ignoring edit list with {} entries", elst.edits.len()); + } + debug!("{:?}", elst); + } + _ => skip_box_content(&mut b)?, + }; + check_parser_state!(b.content); + } + Ok(()) +} + +#[allow(clippy::type_complexity)] // Allow the complex return, maybe rework in future +fn parse_mdhd<T: Read>( + f: &mut BMFFBox<T>, + track: &Track, +) -> Result<( + MediaHeaderBox, + Option<TrackScaledTime<u64>>, + Option<TrackTimeScale<u64>>, +)> { + let mdhd = read_mdhd(f)?; + let duration = match mdhd.duration { + std::u64::MAX => None, + duration => Some(TrackScaledTime::<u64>(duration, track.id)), + }; + if mdhd.timescale == 0 { + return Status::MdhdBadTimescale.into(); + } + let timescale = Some(TrackTimeScale::<u64>(u64::from(mdhd.timescale), track.id)); + Ok((mdhd, duration, timescale)) +} + +fn read_mdia<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> { + let mut iter = f.box_iter(); + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::MediaHeaderBox => { + let (mdhd, duration, timescale) = parse_mdhd(&mut b, track)?; + track.duration = duration; + track.timescale = timescale; + debug!("{:?}", mdhd); + } + BoxType::HandlerBox => { + let hdlr = read_hdlr(&mut b, ParseStrictness::Permissive)?; + + match hdlr.handler_type.value.as_ref() { + b"vide" => track.track_type = TrackType::Video, + b"pict" => track.track_type = TrackType::Picture, + b"auxv" => track.track_type = TrackType::AuxiliaryVideo, + b"soun" => track.track_type = TrackType::Audio, + b"meta" => track.track_type = TrackType::Metadata, + _ => (), + } + debug!("{:?}", hdlr); + } + BoxType::MediaInformationBox => read_minf(&mut b, track)?, + _ => skip_box_content(&mut b)?, + }; + check_parser_state!(b.content); + } + Ok(()) +} + +fn read_tref<T: Read>(f: &mut BMFFBox<T>) -> Result<TrackReferenceBox> { + // Will likely only see trefs with one auxl + let mut references = TryVec::with_capacity(1)?; + let mut iter = f.box_iter(); + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::AuxiliaryBox => { + references.push(TrackReferenceEntry::Auxiliary(read_tref_auxl(&mut b)?))? + } + _ => skip_box_content(&mut b)?, + }; + check_parser_state!(b.content); + } + Ok(TrackReferenceBox { references }) +} + +fn read_tref_auxl<T: Read>(f: &mut BMFFBox<T>) -> Result<TrackReference> { + let num_track_ids = (f.bytes_left() / std::mem::size_of::<u32>().to_u64()).try_into()?; + let mut track_ids = TryVec::with_capacity(num_track_ids)?; + for _ in 0..num_track_ids { + track_ids.push(be_u32(f)?)?; + } + + Ok(TrackReference { track_ids }) +} + +fn read_minf<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> { + let mut iter = f.box_iter(); + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::SampleTableBox => read_stbl(&mut b, track)?, + _ => skip_box_content(&mut b)?, + }; + check_parser_state!(b.content); + } + Ok(()) +} + +fn read_stbl<T: Read>(f: &mut BMFFBox<T>, track: &mut Track) -> Result<()> { + let mut iter = f.box_iter(); + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::SampleDescriptionBox => { + let stsd = read_stsd(&mut b, track)?; + debug!("{:?}", stsd); + track.stsd = Some(stsd); + } + BoxType::TimeToSampleBox => { + let stts = read_stts(&mut b)?; + debug!("{:?}", stts); + track.stts = Some(stts); + } + BoxType::SampleToChunkBox => { + let stsc = read_stsc(&mut b)?; + debug!("{:?}", stsc); + track.stsc = Some(stsc); + } + BoxType::SampleSizeBox => { + let stsz = read_stsz(&mut b)?; + debug!("{:?}", stsz); + track.stsz = Some(stsz); + } + BoxType::ChunkOffsetBox => { + let stco = read_stco(&mut b)?; + debug!("{:?}", stco); + track.stco = Some(stco); + } + BoxType::ChunkLargeOffsetBox => { + let co64 = read_co64(&mut b)?; + debug!("{:?}", co64); + track.stco = Some(co64); + } + BoxType::SyncSampleBox => { + let stss = read_stss(&mut b)?; + debug!("{:?}", stss); + track.stss = Some(stss); + } + BoxType::CompositionOffsetBox => { + let ctts = read_ctts(&mut b)?; + debug!("{:?}", ctts); + track.ctts = Some(ctts); + } + _ => skip_box_content(&mut b)?, + }; + check_parser_state!(b.content); + } + Ok(()) +} + +/// Parse an ftyp box. +/// See ISOBMFF (ISO 14496-12:2020) § 4.3 +fn read_ftyp<T: Read>(src: &mut BMFFBox<T>) -> Result<FileTypeBox> { + let major = be_u32(src)?; + let minor = be_u32(src)?; + let bytes_left = src.bytes_left(); + if bytes_left % 4 != 0 { + return Status::FtypBadSize.into(); + } + // Is a brand_count of zero valid? + let brand_count = bytes_left / 4; + let mut brands = TryVec::with_capacity(brand_count.try_into()?)?; + for _ in 0..brand_count { + brands.push(be_u32(src)?.into())?; + } + Ok(FileTypeBox { + major_brand: From::from(major), + minor_version: minor, + compatible_brands: brands, + }) +} + +/// Parse an mvhd box. +fn read_mvhd<T: Read>(src: &mut BMFFBox<T>) -> Result<MovieHeaderBox> { + let (version, _) = read_fullbox_extra(src)?; + match version { + // 64 bit creation and modification times. + 1 => { + skip(src, 16)?; + } + // 32 bit creation and modification times. + 0 => { + skip(src, 8)?; + } + _ => return Status::MvhdBadVersion.into(), + } + let timescale = be_u32(src)?; + let duration = match version { + 1 => be_u64(src)?, + 0 => { + let d = be_u32(src)?; + if d == std::u32::MAX { + std::u64::MAX + } else { + u64::from(d) + } + } + _ => unreachable!("Should have returned Status::MvhdBadVersion"), + }; + // Skip remaining valid fields. + skip(src, 80)?; + + // Padding could be added in some contents. + skip_box_remain(src)?; + Ok(MovieHeaderBox { + timescale, + duration, + }) +} + +/// Parse a tkhd box. +fn read_tkhd<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackHeaderBox> { + let (version, flags) = read_fullbox_extra(src)?; + let disabled = flags & 0x1u32 == 0 || flags & 0x2u32 == 0; + match version { + // 64 bit creation and modification times. + 1 => { + skip(src, 16)?; + } + // 32 bit creation and modification times. + 0 => { + skip(src, 8)?; + } + _ => return Status::TkhdBadVersion.into(), + } + let track_id = be_u32(src)?; + skip(src, 4)?; + let duration = match version { + 1 => be_u64(src)?, + 0 => u64::from(be_u32(src)?), + _ => unreachable!("Should have returned Status::TkhdBadVersion"), + }; + // Skip uninteresting fields. + skip(src, 16)?; + + let matrix = Matrix { + a: be_i32(src)?, + b: be_i32(src)?, + u: be_i32(src)?, + c: be_i32(src)?, + d: be_i32(src)?, + v: be_i32(src)?, + x: be_i32(src)?, + y: be_i32(src)?, + w: be_i32(src)?, + }; + + let width = be_u32(src)?; + let height = be_u32(src)?; + Ok(TrackHeaderBox { + track_id, + disabled, + duration, + width, + height, + matrix, + }) +} + +/// Parse a elst box. +/// See ISOBMFF (ISO 14496-12:2020) § 8.6.6 +fn read_elst<T: Read>(src: &mut BMFFBox<T>) -> Result<EditListBox> { + let (version, flags) = read_fullbox_extra(src)?; + let edit_count = be_u32(src)?; + let mut edits = TryVec::with_capacity(edit_count.to_usize())?; + for _ in 0..edit_count { + let (segment_duration, media_time) = match version { + 1 => { + // 64 bit segment duration and media times. + (be_u64(src)?, be_i64(src)?) + } + 0 => { + // 32 bit segment duration and media times. + (u64::from(be_u32(src)?), i64::from(be_i32(src)?)) + } + _ => return Status::ElstBadVersion.into(), + }; + let media_rate_integer = be_i16(src)?; + let media_rate_fraction = be_i16(src)?; + edits.push(Edit { + segment_duration, + media_time, + media_rate_integer, + media_rate_fraction, + })?; + } + + // Padding could be added in some contents. + skip_box_remain(src)?; + + Ok(EditListBox { + looped: flags == 1, + edits, + }) +} + +/// Parse a mdhd box. +fn read_mdhd<T: Read>(src: &mut BMFFBox<T>) -> Result<MediaHeaderBox> { + let (version, _) = read_fullbox_extra(src)?; + let (timescale, duration) = match version { + 1 => { + // Skip 64-bit creation and modification times. + skip(src, 16)?; + + // 64 bit duration. + (be_u32(src)?, be_u64(src)?) + } + 0 => { + // Skip 32-bit creation and modification times. + skip(src, 8)?; + + // 32 bit duration. + let timescale = be_u32(src)?; + let duration = { + // Since we convert the 32-bit duration to 64-bit by + // upcasting, we need to preserve the special all-1s + // ("unknown") case by hand. + let d = be_u32(src)?; + if d == std::u32::MAX { + std::u64::MAX + } else { + u64::from(d) + } + }; + (timescale, duration) + } + _ => return Status::MdhdBadVersion.into(), + }; + + // Skip uninteresting fields. + skip(src, 4)?; + + Ok(MediaHeaderBox { + timescale, + duration, + }) +} + +/// Parse a stco box. +/// See ISOBMFF (ISO 14496-12:2020) § 8.7.5 +fn read_stco<T: Read>(src: &mut BMFFBox<T>) -> Result<ChunkOffsetBox> { + let (_, _) = read_fullbox_extra(src)?; + let offset_count = be_u32(src)?; + let mut offsets = TryVec::with_capacity(offset_count.to_usize())?; + for _ in 0..offset_count { + offsets.push(be_u32(src)?.into())?; + } + + // Padding could be added in some contents. + skip_box_remain(src)?; + + Ok(ChunkOffsetBox { offsets }) +} + +/// Parse a co64 box. +/// See ISOBMFF (ISO 14496-12:2020) § 8.7.5 +fn read_co64<T: Read>(src: &mut BMFFBox<T>) -> Result<ChunkOffsetBox> { + let (_, _) = read_fullbox_extra(src)?; + let offset_count = be_u32(src)?; + let mut offsets = TryVec::with_capacity(offset_count.to_usize())?; + for _ in 0..offset_count { + offsets.push(be_u64(src)?)?; + } + + // Padding could be added in some contents. + skip_box_remain(src)?; + + Ok(ChunkOffsetBox { offsets }) +} + +/// Parse a stss box. +/// See ISOBMFF (ISO 14496-12:2020) § 8.6.2 +fn read_stss<T: Read>(src: &mut BMFFBox<T>) -> Result<SyncSampleBox> { + let (_, _) = read_fullbox_extra(src)?; + let sample_count = be_u32(src)?; + let mut samples = TryVec::with_capacity(sample_count.to_usize())?; + for _ in 0..sample_count { + samples.push(be_u32(src)?)?; + } + + // Padding could be added in some contents. + skip_box_remain(src)?; + + Ok(SyncSampleBox { samples }) +} + +/// Parse a stsc box. +/// See ISOBMFF (ISO 14496-12:2020) § 8.7.4 +fn read_stsc<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleToChunkBox> { + let (_, _) = read_fullbox_extra(src)?; + let sample_count = be_u32(src)?; + let mut samples = TryVec::with_capacity(sample_count.to_usize())?; + for _ in 0..sample_count { + let first_chunk = be_u32(src)?; + let samples_per_chunk = be_u32(src)?; + let sample_description_index = be_u32(src)?; + samples.push(SampleToChunk { + first_chunk, + samples_per_chunk, + sample_description_index, + })?; + } + + // Padding could be added in some contents. + skip_box_remain(src)?; + + Ok(SampleToChunkBox { samples }) +} + +/// Parse a Composition Time to Sample Box +/// See ISOBMFF (ISO 14496-12:2020) § 8.6.1.3 +fn read_ctts<T: Read>(src: &mut BMFFBox<T>) -> Result<CompositionOffsetBox> { + let (version, _) = read_fullbox_extra(src)?; + + let counts = be_u32(src)?; + + if counts + .checked_mul(8) + .map_or(true, |bytes| u64::from(bytes) > src.bytes_left()) + { + return Status::CttsBadSize.into(); + } + + let mut offsets = TryVec::with_capacity(counts.to_usize())?; + for _ in 0..counts { + let (sample_count, time_offset) = match version { + // According to spec, Version0 shoule be used when version == 0; + // however, some buggy contents have negative value when version == 0. + // So we always use Version1 here. + 0..=1 => { + let count = be_u32(src)?; + let offset = TimeOffsetVersion::Version1(be_i32(src)?); + (count, offset) + } + _ => { + return Status::CttsBadVersion.into(); + } + }; + offsets.push(TimeOffset { + sample_count, + time_offset, + })?; + } + + check_parser_state!(src.content); + + Ok(CompositionOffsetBox { samples: offsets }) +} + +/// Parse a stsz box. +/// See ISOBMFF (ISO 14496-12:2020) § 8.7.3.2 +fn read_stsz<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleSizeBox> { + let (_, _) = read_fullbox_extra(src)?; + let sample_size = be_u32(src)?; + let sample_count = be_u32(src)?; + let mut sample_sizes = TryVec::new(); + if sample_size == 0 { + sample_sizes.reserve(sample_count.to_usize())?; + for _ in 0..sample_count { + sample_sizes.push(be_u32(src)?)?; + } + } + + // Padding could be added in some contents. + skip_box_remain(src)?; + + Ok(SampleSizeBox { + sample_size, + sample_sizes, + }) +} + +/// Parse a stts box. +/// See ISOBMFF (ISO 14496-12:2020) § 8.6.1.2 +fn read_stts<T: Read>(src: &mut BMFFBox<T>) -> Result<TimeToSampleBox> { + let (_, _) = read_fullbox_extra(src)?; + let sample_count = be_u32(src)?; + let mut samples = TryVec::with_capacity(sample_count.to_usize())?; + for _ in 0..sample_count { + let sample_count = be_u32(src)?; + let sample_delta = be_u32(src)?; + samples.push(Sample { + sample_count, + sample_delta, + })?; + } + + // Padding could be added in some contents. + skip_box_remain(src)?; + + Ok(TimeToSampleBox { samples }) +} + +/// Parse a VPx Config Box. +fn read_vpcc<T: Read>(src: &mut BMFFBox<T>) -> Result<VPxConfigBox> { + let (version, _) = read_fullbox_extra(src)?; + let supported_versions = [0, 1]; + if !supported_versions.contains(&version) { + return Err(Error::Unsupported("unknown vpcC version")); + } + + let profile = src.read_u8()?; + let level = src.read_u8()?; + let ( + bit_depth, + colour_primaries, + chroma_subsampling, + transfer_characteristics, + matrix_coefficients, + video_full_range_flag, + ) = if version == 0 { + let (bit_depth, colour_primaries) = { + let byte = src.read_u8()?; + ((byte >> 4) & 0x0f, byte & 0x0f) + }; + // Note, transfer_characteristics was known as transfer_function in v0 + let (chroma_subsampling, transfer_characteristics, video_full_range_flag) = { + let byte = src.read_u8()?; + ((byte >> 4) & 0x0f, (byte >> 1) & 0x07, (byte & 1) == 1) + }; + ( + bit_depth, + colour_primaries, + chroma_subsampling, + transfer_characteristics, + None, + video_full_range_flag, + ) + } else { + let (bit_depth, chroma_subsampling, video_full_range_flag) = { + let byte = src.read_u8()?; + ((byte >> 4) & 0x0f, (byte >> 1) & 0x07, (byte & 1) == 1) + }; + let colour_primaries = src.read_u8()?; + let transfer_characteristics = src.read_u8()?; + let matrix_coefficients = src.read_u8()?; + + ( + bit_depth, + colour_primaries, + chroma_subsampling, + transfer_characteristics, + Some(matrix_coefficients), + video_full_range_flag, + ) + }; + + let codec_init_size = be_u16(src)?; + let codec_init = read_buf(src, codec_init_size.into())?; + + // TODO(rillian): validate field value ranges. + Ok(VPxConfigBox { + profile, + level, + bit_depth, + colour_primaries, + chroma_subsampling, + transfer_characteristics, + matrix_coefficients, + video_full_range_flag, + codec_init, + }) +} + +/// See [AV1-ISOBMFF § 2.3.3](https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax) +fn read_av1c<T: Read>(src: &mut BMFFBox<T>) -> Result<AV1ConfigBox> { + // We want to store the raw config as well as a structured (parsed) config, so create a copy of + // the raw config so we have it later, and then parse the structured data from that. + let raw_config = src.read_into_try_vec()?; + let mut raw_config_slice = raw_config.as_slice(); + let marker_byte = raw_config_slice.read_u8()?; + if marker_byte & 0x80 != 0x80 { + return Err(Error::Unsupported("missing av1C marker bit")); + } + if marker_byte & 0x7f != 0x01 { + return Err(Error::Unsupported("missing av1C marker bit")); + } + let profile_byte = raw_config_slice.read_u8()?; + let profile = (profile_byte & 0xe0) >> 5; + let level = profile_byte & 0x1f; + let flags_byte = raw_config_slice.read_u8()?; + let tier = (flags_byte & 0x80) >> 7; + let bit_depth = match flags_byte & 0x60 { + 0x60 => 12, + 0x40 => 10, + _ => 8, + }; + let monochrome = flags_byte & 0x10 == 0x10; + let chroma_subsampling_x = (flags_byte & 0x08) >> 3; + let chroma_subsampling_y = (flags_byte & 0x04) >> 2; + let chroma_sample_position = flags_byte & 0x03; + let delay_byte = raw_config_slice.read_u8()?; + let initial_presentation_delay_present = (delay_byte & 0x10) == 0x10; + let initial_presentation_delay_minus_one = if initial_presentation_delay_present { + delay_byte & 0x0f + } else { + 0 + }; + + Ok(AV1ConfigBox { + profile, + level, + tier, + bit_depth, + monochrome, + chroma_subsampling_x, + chroma_subsampling_y, + chroma_sample_position, + initial_presentation_delay_present, + initial_presentation_delay_minus_one, + raw_config, + }) +} + +fn read_flac_metadata<T: Read>(src: &mut BMFFBox<T>) -> Result<FLACMetadataBlock> { + let temp = src.read_u8()?; + let block_type = temp & 0x7f; + let length = be_u24(src)?.into(); + if length > src.bytes_left() { + return Status::DflaBadMetadataBlockSize.into(); + } + let data = read_buf(src, length)?; + Ok(FLACMetadataBlock { block_type, data }) +} + +/// See MPEG-4 Systems (ISO 14496-1:2010) § 7.2.6.5 +fn find_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { + // Tags for elementary stream description + const ESDESCR_TAG: u8 = 0x03; + const DECODER_CONFIG_TAG: u8 = 0x04; + const DECODER_SPECIFIC_TAG: u8 = 0x05; + + let mut remains = data; + + // Descriptor length should be more than 2 bytes. + while remains.len() > 2 { + let des = &mut Cursor::new(remains); + let tag = des.read_u8()?; + + // See MPEG-4 Systems (ISO 14496-1:2010) § 8.3.3 for interpreting size of expandable classes + + let mut end: u32 = 0; // It's u8 without declaration type that is incorrect. + // MSB of extend_or_len indicates more bytes, up to 4 bytes. + for _ in 0..4 { + if des.position() == remains.len().to_u64() { + // There's nothing more to read, the 0x80 was actually part of + // the content, and not an extension size. + end = des.position() as u32; + break; + } + let extend_or_len = des.read_u8()?; + end = (end << 7) + u32::from(extend_or_len & 0x7F); + if (extend_or_len & 0b1000_0000) == 0 { + end += des.position() as u32; + break; + } + } + + if end.to_usize() > remains.len() || u64::from(end) < des.position() { + return Status::EsdsBadDescriptor.into(); + } + + let descriptor = &remains[des.position().try_into()?..end.to_usize()]; + + match tag { + ESDESCR_TAG => { + read_es_descriptor(descriptor, esds)?; + } + DECODER_CONFIG_TAG => { + read_dc_descriptor(descriptor, esds)?; + } + DECODER_SPECIFIC_TAG => { + read_ds_descriptor(descriptor, esds)?; + } + _ => { + debug!("Unsupported descriptor, tag {}", tag); + } + } + + remains = &remains[end.to_usize()..remains.len()]; + debug!("remains.len(): {}", remains.len()); + } + + Ok(()) +} + +fn get_audio_object_type(bit_reader: &mut BitReader) -> Result<u16> { + let mut audio_object_type: u16 = ReadInto::read(bit_reader, 5)?; + + // Extend audio object type, for example, HE-AAC. + if audio_object_type == 31 { + let audio_object_type_ext: u16 = ReadInto::read(bit_reader, 6)?; + audio_object_type = 32 + audio_object_type_ext; + } + Ok(audio_object_type) +} + +/// See MPEG-4 Systems (ISO 14496-1:2010) § 7.2.6.7 and probably 14496-3 somewhere? +fn read_ds_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { + #[cfg(feature = "mp4v")] + // Check if we are in a Visual esda Box. + if esds.video_codec != CodecType::Unknown { + esds.decoder_specific_data.extend_from_slice(data)?; + return Ok(()); + } + + // We are in an Audio esda Box. + let frequency_table = [ + (0x0, 96000), + (0x1, 88200), + (0x2, 64000), + (0x3, 48000), + (0x4, 44100), + (0x5, 32000), + (0x6, 24000), + (0x7, 22050), + (0x8, 16000), + (0x9, 12000), + (0xa, 11025), + (0xb, 8000), + (0xc, 7350), + ]; + + let bit_reader = &mut BitReader::new(data); + + let mut audio_object_type = get_audio_object_type(bit_reader)?; + + let sample_index: u32 = ReadInto::read(bit_reader, 4)?; + + // Sample frequency could be from table, or retrieved from stream directly + // if index is 0x0f. + let sample_frequency = match sample_index { + 0x0F => Some(ReadInto::read(bit_reader, 24)?), + _ => frequency_table + .iter() + .find(|item| item.0 == sample_index) + .map(|x| x.1), + }; + + let channel_configuration: u16 = ReadInto::read(bit_reader, 4)?; + + let extended_audio_object_type = match audio_object_type { + 5 | 29 => Some(5), + _ => None, + }; + + if audio_object_type == 5 || audio_object_type == 29 { + // We have an explicit signaling for BSAC extension, should the decoder + // decode the BSAC extension (all Gecko's AAC decoders do), then this is + // what the stream will actually look like once decoded. + let _extended_sample_index = ReadInto::read(bit_reader, 4)?; + let _extended_sample_frequency: Option<u32> = match _extended_sample_index { + 0x0F => Some(ReadInto::read(bit_reader, 24)?), + _ => frequency_table + .iter() + .find(|item| item.0 == sample_index) + .map(|x| x.1), + }; + audio_object_type = get_audio_object_type(bit_reader)?; + let _extended_channel_configuration = match audio_object_type { + 22 => ReadInto::read(bit_reader, 4)?, + _ => channel_configuration, + }; + }; + + match audio_object_type { + 1..=4 | 6 | 7 | 17 | 19..=23 => { + if sample_frequency.is_none() { + return Err(Error::Unsupported("unknown frequency")); + } + + // parsing GASpecificConfig + + // If the sampling rate is not one of the rates listed in the right + // column in Table 4.82, the sampling frequency dependent tables + // (code tables, scale factor band tables etc.) must be deduced in + // order for the bitstream payload to be parsed. Since a given + // sampling frequency is associated with only one sampling frequency + // table, and since maximum flexibility is desired in the range of + // possible sampling frequencies, the following table shall be used + // to associate an implied sampling frequency with the desired + // sampling frequency dependent tables. + let sample_frequency_value = match sample_frequency.unwrap() { + 0..=9390 => 8000, + 9391..=11501 => 11025, + 11502..=13855 => 12000, + 13856..=18782 => 16000, + 18783..=23003 => 22050, + 23004..=27712 => 24000, + 27713..=37565 => 32000, + 37566..=46008 => 44100, + 46009..=55425 => 48000, + 55426..=75131 => 64000, + 75132..=92016 => 88200, + _ => 96000, + }; + + bit_reader.skip(1)?; // frameLengthFlag + let depend_on_core_order: u8 = ReadInto::read(bit_reader, 1)?; + if depend_on_core_order > 0 { + bit_reader.skip(14)?; // codeCoderDelay + } + bit_reader.skip(1)?; // extensionFlag + + let channel_counts = match channel_configuration { + 0 => { + debug!("Parsing program_config_element for channel counts"); + + bit_reader.skip(4)?; // element_instance_tag + bit_reader.skip(2)?; // object_type + bit_reader.skip(4)?; // sampling_frequency_index + let num_front_channel: u8 = ReadInto::read(bit_reader, 4)?; + let num_side_channel: u8 = ReadInto::read(bit_reader, 4)?; + let num_back_channel: u8 = ReadInto::read(bit_reader, 4)?; + let num_lfe_channel: u8 = ReadInto::read(bit_reader, 2)?; + bit_reader.skip(3)?; // num_assoc_data + bit_reader.skip(4)?; // num_valid_cc + + let mono_mixdown_present: bool = ReadInto::read(bit_reader, 1)?; + if mono_mixdown_present { + bit_reader.skip(4)?; // mono_mixdown_element_number + } + + let stereo_mixdown_present: bool = ReadInto::read(bit_reader, 1)?; + if stereo_mixdown_present { + bit_reader.skip(4)?; // stereo_mixdown_element_number + } + + let matrix_mixdown_idx_present: bool = ReadInto::read(bit_reader, 1)?; + if matrix_mixdown_idx_present { + bit_reader.skip(2)?; // matrix_mixdown_idx + bit_reader.skip(1)?; // pseudo_surround_enable + } + let mut _channel_counts = 0; + _channel_counts += read_surround_channel_count(bit_reader, num_front_channel)?; + _channel_counts += read_surround_channel_count(bit_reader, num_side_channel)?; + _channel_counts += read_surround_channel_count(bit_reader, num_back_channel)?; + _channel_counts += read_surround_channel_count(bit_reader, num_lfe_channel)?; + _channel_counts + } + 1..=7 => channel_configuration, + // Amendment 4 of the AAC standard in 2013 below + 11 => 7, // 6.1 Amendment 4 of the AAC standard in 2013 + 12 | 14 => 8, // 7.1 (a/d) of ITU BS.2159 + _ => { + return Err(Error::Unsupported("invalid channel configuration")); + } + }; + + esds.audio_object_type = Some(audio_object_type); + esds.extended_audio_object_type = extended_audio_object_type; + esds.audio_sample_rate = Some(sample_frequency_value); + esds.audio_channel_count = Some(channel_counts); + if !esds.decoder_specific_data.is_empty() { + return Status::EsdsDecSpecificIntoTagQuantity.into(); + } + esds.decoder_specific_data.extend_from_slice(data)?; + + Ok(()) + } + _ => Err(Error::Unsupported("unknown aac audio object type")), + } +} + +fn read_surround_channel_count(bit_reader: &mut BitReader, channels: u8) -> Result<u16> { + let mut count = 0; + for _ in 0..channels { + let is_cpe: bool = ReadInto::read(bit_reader, 1)?; + count += if is_cpe { 2 } else { 1 }; + bit_reader.skip(4)?; + } + Ok(count) +} + +/// See MPEG-4 Systems (ISO 14496-1:2010) § 7.2.6.6 +fn read_dc_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { + let des = &mut Cursor::new(data); + let object_profile = des.read_u8()?; + + #[cfg(feature = "mp4v")] + { + esds.video_codec = match object_profile { + 0x20..=0x24 => CodecType::MP4V, + _ => CodecType::Unknown, + }; + } + + // Skip uninteresting fields. + skip(des, 12)?; + + if data.len().to_u64() > des.position() { + find_descriptor(&data[des.position().try_into()?..data.len()], esds)?; + } + + esds.audio_codec = match object_profile { + 0x40 | 0x66 | 0x67 => CodecType::AAC, + 0x69 | 0x6B => CodecType::MP3, + _ => CodecType::Unknown, + }; + + debug!( + "read_dc_descriptor: esds.audio_codec = {:?}", + esds.audio_codec + ); + + Ok(()) +} + +/// See MPEG-4 Systems (ISO 14496-1:2010) § 7.2.6.5 +fn read_es_descriptor(data: &[u8], esds: &mut ES_Descriptor) -> Result<()> { + let des = &mut Cursor::new(data); + + skip(des, 2)?; + + let esds_flags = des.read_u8()?; + + // Stream dependency flag, first bit from left most. + if esds_flags & 0x80 > 0 { + // Skip uninteresting fields. + skip(des, 2)?; + } + + // Url flag, second bit from left most. + if esds_flags & 0x40 > 0 { + // Skip uninteresting fields. + let skip_es_len = u64::from(des.read_u8()?) + 2; + skip(des, skip_es_len)?; + } + + if data.len().to_u64() > des.position() { + find_descriptor(&data[des.position().try_into()?..data.len()], esds)?; + } + + Ok(()) +} + +/// See MP4 (ISO 14496-14:2020) § 6.7.2 +fn read_esds<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> { + let (_, _) = read_fullbox_extra(src)?; + + let esds_array = read_buf(src, src.bytes_left())?; + + let mut es_data = ES_Descriptor::default(); + find_descriptor(&esds_array, &mut es_data)?; + + es_data.codec_esds = esds_array; + + Ok(es_data) +} + +/// Parse `FLACSpecificBox`. +/// See [Encapsulation of FLAC in ISO Base Media File Format](https://github.com/xiph/flac/blob/master/doc/isoflac.txt) § 3.3.2 +fn read_dfla<T: Read>(src: &mut BMFFBox<T>) -> Result<FLACSpecificBox> { + let (version, flags) = read_fullbox_extra(src)?; + if version != 0 { + return Err(Error::Unsupported("unknown dfLa (FLAC) version")); + } + if flags != 0 { + return Status::DflaFlagsNonzero.into(); + } + let mut blocks = TryVec::new(); + while src.bytes_left() > 0 { + let block = read_flac_metadata(src)?; + blocks.push(block)?; + } + // The box must have at least one meta block, and the first block + // must be the METADATA_BLOCK_STREAMINFO + if blocks.is_empty() { + return Status::DflaMissingMetadata.into(); + } else if blocks[0].block_type != 0 { + return Status::DflaStreamInfoNotFirst.into(); + } else if blocks[0].data.len() != 34 { + return Status::DflaStreamInfoBadSize.into(); + } + Ok(FLACSpecificBox { version, blocks }) +} + +/// Parse `OpusSpecificBox`. +fn read_dops<T: Read>(src: &mut BMFFBox<T>) -> Result<OpusSpecificBox> { + let version = src.read_u8()?; + if version != 0 { + return Err(Error::Unsupported("unknown dOps (Opus) version")); + } + + let output_channel_count = src.read_u8()?; + let pre_skip = be_u16(src)?; + let input_sample_rate = be_u32(src)?; + let output_gain = be_i16(src)?; + let channel_mapping_family = src.read_u8()?; + + let channel_mapping_table = if channel_mapping_family == 0 { + None + } else { + let stream_count = src.read_u8()?; + let coupled_count = src.read_u8()?; + let channel_mapping = read_buf(src, output_channel_count.into())?; + + Some(ChannelMappingTable { + stream_count, + coupled_count, + channel_mapping, + }) + }; + + // TODO(kinetik): validate field value ranges. + Ok(OpusSpecificBox { + version, + output_channel_count, + pre_skip, + input_sample_rate, + output_gain, + channel_mapping_family, + channel_mapping_table, + }) +} + +/// Re-serialize the Opus codec-specific config data as an `OpusHead` packet. +/// +/// Some decoders expect the initialization data in the format used by the +/// Ogg and WebM encapsulations. To support this we prepend the `OpusHead` +/// tag and byte-swap the data from big- to little-endian relative to the +/// dOps box. +pub fn serialize_opus_header<W: byteorder::WriteBytesExt + std::io::Write>( + opus: &OpusSpecificBox, + dst: &mut W, +) -> Result<()> { + match dst.write(b"OpusHead") { + Err(e) => return Err(Error::from(e)), + Ok(bytes) => { + if bytes != 8 { + return Status::DopsOpusHeadWriteErr.into(); + } + } + } + // In mp4 encapsulation, the version field is 0, but in ogg + // it is 1. While decoders generally accept zero as well, write + // out the version of the header we're supporting rather than + // whatever we parsed out of mp4. + dst.write_u8(1)?; + dst.write_u8(opus.output_channel_count)?; + dst.write_u16::<byteorder::LittleEndian>(opus.pre_skip)?; + dst.write_u32::<byteorder::LittleEndian>(opus.input_sample_rate)?; + dst.write_i16::<byteorder::LittleEndian>(opus.output_gain)?; + dst.write_u8(opus.channel_mapping_family)?; + match opus.channel_mapping_table { + None => {} + Some(ref table) => { + dst.write_u8(table.stream_count)?; + dst.write_u8(table.coupled_count)?; + match dst.write(&table.channel_mapping) { + Err(e) => return Err(Error::from(e)), + Ok(bytes) => { + if bytes != table.channel_mapping.len() { + return Status::DopsChannelMappingWriteErr.into(); + } + } + } + } + }; + Ok(()) +} + +/// Parse `ALACSpecificBox`. +fn read_alac<T: Read>(src: &mut BMFFBox<T>) -> Result<ALACSpecificBox> { + let (version, flags) = read_fullbox_extra(src)?; + if version != 0 { + return Err(Error::Unsupported("unknown alac (ALAC) version")); + } + if flags != 0 { + return Status::AlacFlagsNonzero.into(); + } + + let length = match src.bytes_left() { + x @ 24 | x @ 48 => x, + _ => { + return Status::AlacBadMagicCookieSize.into(); + } + }; + let data = read_buf(src, length)?; + + Ok(ALACSpecificBox { version, data }) +} + +/// Parse a Handler Reference Box.<br /> +/// See ISOBMFF (ISO 14496-12:2020) § 8.4.3<br /> +/// See [\[ISOBMFF\]: reserved (field = 0;) handling is ambiguous](https://github.com/MPEGGroup/FileFormat/issues/36) +fn read_hdlr<T: Read>(src: &mut BMFFBox<T>, strictness: ParseStrictness) -> Result<HandlerBox> { + if read_fullbox_version_no_flags(src)? != 0 { + return Status::HdlrUnsupportedVersion.into(); + } + + let pre_defined = be_u32(src)?; + if pre_defined != 0 { + fail_with_status_if( + strictness == ParseStrictness::Strict, + Status::HdlrPredefinedNonzero, + )?; + } + + let handler_type = FourCC::from(be_u32(src)?); + + for _ in 1..=3 { + let reserved = be_u32(src)?; + if reserved != 0 { + fail_with_status_if( + strictness == ParseStrictness::Strict, + Status::HdlrReservedNonzero, + )?; + } + } + + match std::str::from_utf8(src.read_into_try_vec()?.as_slice()) { + Ok(name) => { + match name.bytes().position(|b| b == b'\0') { + None => fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::HdlrNameNoNul, + )?, + // `name` must be nul-terminated and any trailing bytes after the first nul ignored. + // See https://github.com/MPEGGroup/FileFormat/issues/35 + Some(_) => (), + } + } + Err(_) => fail_with_status_if( + strictness != ParseStrictness::Permissive, + Status::HdlrNameNotUtf8, + )?, + } + + Ok(HandlerBox { handler_type }) +} + +/// Parse an video description inside an stsd box. +fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleEntry> { + let name = src.get_header().name; + let codec_type = match name { + BoxType::AVCSampleEntry | BoxType::AVC3SampleEntry => CodecType::H264, + BoxType::MP4VideoSampleEntry => CodecType::MP4V, + BoxType::VP8SampleEntry => CodecType::VP8, + BoxType::VP9SampleEntry => CodecType::VP9, + BoxType::AV1SampleEntry => CodecType::AV1, + BoxType::ProtectedVisualSampleEntry => CodecType::EncryptedVideo, + BoxType::H263SampleEntry => CodecType::H263, + BoxType::HEV1SampleEntry | BoxType::HVC1SampleEntry => CodecType::HEVC, + _ => { + debug!("Unsupported video codec, box {:?} found", name); + CodecType::Unknown + } + }; + + // Skip uninteresting fields. + skip(src, 6)?; + + let data_reference_index = be_u16(src)?; + + // Skip uninteresting fields. + skip(src, 16)?; + + let width = be_u16(src)?; + let height = be_u16(src)?; + + // Skip uninteresting fields. + skip(src, 50)?; + + // Skip clap/pasp/etc. for now. + let mut codec_specific = None; + let mut protection_info = TryVec::new(); + let mut iter = src.box_iter(); + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::AVCConfigurationBox => { + if (name != BoxType::AVCSampleEntry + && name != BoxType::AVC3SampleEntry + && name != BoxType::ProtectedVisualSampleEntry) + || codec_specific.is_some() + { + return Status::StsdBadVideoSampleEntry.into(); + } + let avcc_size = b + .head + .size + .checked_sub(b.head.offset) + .expect("offset invalid"); + let avcc = read_buf(&mut b.content, avcc_size)?; + debug!("{:?} (avcc)", avcc); + // TODO(kinetik): Parse avcC box? For now we just stash the data. + codec_specific = Some(VideoCodecSpecific::AVCConfig(avcc)); + } + BoxType::H263SpecificBox => { + if (name != BoxType::H263SampleEntry) || codec_specific.is_some() { + return Status::StsdBadVideoSampleEntry.into(); + } + let h263_dec_spec_struc_size = b + .head + .size + .checked_sub(b.head.offset) + .expect("offset invalid"); + let h263_dec_spec_struc = read_buf(&mut b.content, h263_dec_spec_struc_size)?; + debug!("{:?} (h263DecSpecStruc)", h263_dec_spec_struc); + + codec_specific = Some(VideoCodecSpecific::H263Config(h263_dec_spec_struc)); + } + BoxType::VPCodecConfigurationBox => { + // vpcC + if (name != BoxType::VP8SampleEntry + && name != BoxType::VP9SampleEntry + && name != BoxType::ProtectedVisualSampleEntry) + || codec_specific.is_some() + { + return Status::StsdBadVideoSampleEntry.into(); + } + let vpcc = read_vpcc(&mut b)?; + codec_specific = Some(VideoCodecSpecific::VPxConfig(vpcc)); + } + BoxType::AV1CodecConfigurationBox => { + if name != BoxType::AV1SampleEntry && name != BoxType::ProtectedVisualSampleEntry { + return Status::StsdBadVideoSampleEntry.into(); + } + let av1c = read_av1c(&mut b)?; + codec_specific = Some(VideoCodecSpecific::AV1Config(av1c)); + } + BoxType::ESDBox => { + if name != BoxType::MP4VideoSampleEntry || codec_specific.is_some() { + return Status::StsdBadVideoSampleEntry.into(); + } + #[cfg(not(feature = "mp4v"))] + { + let (_, _) = read_fullbox_extra(&mut b.content)?; + // Subtract 4 extra to offset the members of fullbox not + // accounted for in head.offset + let esds_size = b + .head + .size + .checked_sub(b.head.offset + 4) + .expect("offset invalid"); + let esds = read_buf(&mut b.content, esds_size)?; + codec_specific = Some(VideoCodecSpecific::ESDSConfig(esds)); + } + #[cfg(feature = "mp4v")] + { + // Read ES_Descriptor inside an esds box. + // See ISOBMFF (ISO 14496-1:2010) § 7.2.6.5 + let esds = read_esds(&mut b)?; + codec_specific = + Some(VideoCodecSpecific::ESDSConfig(esds.decoder_specific_data)); + } + } + BoxType::ProtectionSchemeInfoBox => { + if name != BoxType::ProtectedVisualSampleEntry { + return Status::StsdBadVideoSampleEntry.into(); + } + let sinf = read_sinf(&mut b)?; + debug!("{:?} (sinf)", sinf); + protection_info.push(sinf)?; + } + BoxType::HEVCConfigurationBox => { + if (name != BoxType::HEV1SampleEntry + && name != BoxType::HVC1SampleEntry + && name != BoxType::ProtectedVisualSampleEntry) + || codec_specific.is_some() + { + return Status::StsdBadVideoSampleEntry.into(); + } + let hvcc_size = b + .head + .size + .checked_sub(b.head.offset) + .expect("offset invalid"); + let hvcc = read_buf(&mut b.content, hvcc_size)?; + debug!("{:?} (hvcc)", hvcc); + codec_specific = Some(VideoCodecSpecific::HEVCConfig(hvcc)); + } + _ => { + debug!("Unsupported video codec, box {:?} found", b.head.name); + skip_box_content(&mut b)?; + } + } + check_parser_state!(b.content); + } + + Ok( + codec_specific.map_or(SampleEntry::Unknown, |codec_specific| { + SampleEntry::Video(VideoSampleEntry { + codec_type, + data_reference_index, + width, + height, + codec_specific, + protection_info, + }) + }), + ) +} + +fn read_qt_wave_atom<T: Read>(src: &mut BMFFBox<T>) -> Result<ES_Descriptor> { + let mut codec_specific = None; + let mut iter = src.box_iter(); + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::ESDBox => { + let esds = read_esds(&mut b)?; + codec_specific = Some(esds); + } + _ => skip_box_content(&mut b)?, + } + } + + codec_specific.ok_or_else(|| Error::from(Status::EsdsBadAudioSampleEntry)) +} + +/// Parse an audio description inside an stsd box. +/// See ISOBMFF (ISO 14496-12:2020) § 12.2.3 +fn read_audio_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleEntry> { + let name = src.get_header().name; + + // Skip uninteresting fields. + skip(src, 6)?; + + let data_reference_index = be_u16(src)?; + + // XXX(kinetik): This is "reserved" in BMFF, but some old QT MOV variant + // uses it, need to work out if we have to support it. Without checking + // here and reading extra fields after samplerate (or bailing with an + // error), the parser loses sync completely. + let version = be_u16(src)?; + + // Skip uninteresting fields. + skip(src, 6)?; + + let mut channelcount = u32::from(be_u16(src)?); + let samplesize = be_u16(src)?; + + // Skip uninteresting fields. + skip(src, 4)?; + + let mut samplerate = f64::from(be_u32(src)? >> 16); // 16.16 fixed point; + + match version { + 0 => (), + 1 => { + // Quicktime sound sample description version 1. + // Skip uninteresting fields. + skip(src, 16)?; + } + 2 => { + // Quicktime sound sample description version 2. + skip(src, 4)?; + samplerate = f64::from_bits(be_u64(src)?); + channelcount = be_u32(src)?; + skip(src, 20)?; + } + _ => { + return Err(Error::Unsupported( + "unsupported non-isom audio sample entry", + )) + } + } + + let (mut codec_type, mut codec_specific) = match name { + BoxType::MP3AudioSampleEntry => (CodecType::MP3, Some(AudioCodecSpecific::MP3)), + BoxType::LPCMAudioSampleEntry => (CodecType::LPCM, Some(AudioCodecSpecific::LPCM)), + // Some mp4 file with AMR doesn't have AMRSpecificBox "damr" in followed while loop, + // we use empty box by default. + #[cfg(feature = "3gpp")] + BoxType::AMRNBSampleEntry => ( + CodecType::AMRNB, + Some(AudioCodecSpecific::AMRSpecificBox(Default::default())), + ), + #[cfg(feature = "3gpp")] + BoxType::AMRWBSampleEntry => ( + CodecType::AMRWB, + Some(AudioCodecSpecific::AMRSpecificBox(Default::default())), + ), + _ => (CodecType::Unknown, None), + }; + let mut protection_info = TryVec::new(); + let mut iter = src.box_iter(); + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::ESDBox => { + if (name != BoxType::MP4AudioSampleEntry + && name != BoxType::ProtectedAudioSampleEntry) + || codec_specific.is_some() + { + return Status::StsdBadAudioSampleEntry.into(); + } + let esds = read_esds(&mut b)?; + codec_type = esds.audio_codec; + codec_specific = Some(AudioCodecSpecific::ES_Descriptor(esds)); + } + BoxType::FLACSpecificBox => { + if (name != BoxType::FLACSampleEntry && name != BoxType::ProtectedAudioSampleEntry) + || codec_specific.is_some() + { + return Status::StsdBadAudioSampleEntry.into(); + } + let dfla = read_dfla(&mut b)?; + codec_type = CodecType::FLAC; + codec_specific = Some(AudioCodecSpecific::FLACSpecificBox(dfla)); + } + BoxType::OpusSpecificBox => { + if (name != BoxType::OpusSampleEntry && name != BoxType::ProtectedAudioSampleEntry) + || codec_specific.is_some() + { + return Status::StsdBadAudioSampleEntry.into(); + } + let dops = read_dops(&mut b)?; + codec_type = CodecType::Opus; + codec_specific = Some(AudioCodecSpecific::OpusSpecificBox(dops)); + } + BoxType::ALACSpecificBox => { + if name != BoxType::ALACSpecificBox || codec_specific.is_some() { + return Status::StsdBadAudioSampleEntry.into(); + } + let alac = read_alac(&mut b)?; + codec_type = CodecType::ALAC; + codec_specific = Some(AudioCodecSpecific::ALACSpecificBox(alac)); + } + BoxType::QTWaveAtom => { + let qt_esds = read_qt_wave_atom(&mut b)?; + codec_type = qt_esds.audio_codec; + codec_specific = Some(AudioCodecSpecific::ES_Descriptor(qt_esds)); + } + BoxType::ProtectionSchemeInfoBox => { + if name != BoxType::ProtectedAudioSampleEntry { + return Status::StsdBadAudioSampleEntry.into(); + } + let sinf = read_sinf(&mut b)?; + debug!("{:?} (sinf)", sinf); + codec_type = CodecType::EncryptedAudio; + protection_info.push(sinf)?; + } + #[cfg(feature = "3gpp")] + BoxType::AMRSpecificBox => { + if codec_type != CodecType::AMRNB && codec_type != CodecType::AMRWB { + return Status::StsdBadAudioSampleEntry.into(); + } + let amr_dec_spec_struc_size = b + .head + .size + .checked_sub(b.head.offset) + .expect("offset invalid"); + let amr_dec_spec_struc = read_buf(&mut b.content, amr_dec_spec_struc_size)?; + debug!("{:?} (AMRDecSpecStruc)", amr_dec_spec_struc); + codec_specific = Some(AudioCodecSpecific::AMRSpecificBox(amr_dec_spec_struc)); + } + _ => { + debug!("Unsupported audio codec, box {:?} found", b.head.name); + skip_box_content(&mut b)?; + } + } + check_parser_state!(b.content); + } + + Ok( + codec_specific.map_or(SampleEntry::Unknown, |codec_specific| { + SampleEntry::Audio(AudioSampleEntry { + codec_type, + data_reference_index, + channelcount, + samplesize, + samplerate, + codec_specific, + protection_info, + }) + }), + ) +} + +/// Parse a stsd box. +/// See ISOBMFF (ISO 14496-12:2020) § 8.5.2 +/// See MP4 (ISO 14496-14:2020) § 6.7.2 +fn read_stsd<T: Read>(src: &mut BMFFBox<T>, track: &Track) -> Result<SampleDescriptionBox> { + let (_, flags) = read_fullbox_extra(src)?; + + if flags != 0 { + warn!( + "Unexpected `flags` value for SampleDescriptionBox (stsd): {}", + flags + ); + } + + let description_count = be_u32(src)?.to_usize(); + let mut descriptions = TryVec::with_capacity(description_count)?; + + let mut iter = src.box_iter(); + while descriptions.len() < description_count { + if let Some(mut b) = iter.next_box()? { + let description = match track.track_type { + TrackType::Video => read_video_sample_entry(&mut b), + TrackType::Picture => read_video_sample_entry(&mut b), + TrackType::AuxiliaryVideo => read_video_sample_entry(&mut b), + TrackType::Audio => read_audio_sample_entry(&mut b), + TrackType::Metadata => Err(Error::Unsupported("metadata track")), + TrackType::Unknown => Err(Error::Unsupported("unknown track type")), + }; + let description = match description { + Ok(desc) => desc, + Err(Error::Unsupported(_)) => { + // read_{audio,video}_desc may have returned Unsupported + // after partially reading the box content, so we can't + // simply use skip_box_content here. + let to_skip = b.bytes_left(); + skip(&mut b, to_skip)?; + SampleEntry::Unknown + } + Err(e) => return Err(e), + }; + descriptions.push(description)?; + check_parser_state!(b.content); + } else { + break; + } + } + + // Padding could be added in some contents. + skip_box_remain(src)?; + + Ok(SampleDescriptionBox { descriptions }) +} + +fn read_sinf<T: Read>(src: &mut BMFFBox<T>) -> Result<ProtectionSchemeInfoBox> { + let mut sinf = ProtectionSchemeInfoBox::default(); + + let mut iter = src.box_iter(); + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::OriginalFormatBox => { + sinf.original_format = FourCC::from(be_u32(&mut b)?); + } + BoxType::SchemeTypeBox => { + sinf.scheme_type = Some(read_schm(&mut b)?); + } + BoxType::SchemeInformationBox => { + // We only need tenc box in schi box so far. + sinf.tenc = read_schi(&mut b)?; + } + _ => skip_box_content(&mut b)?, + } + check_parser_state!(b.content); + } + + Ok(sinf) +} + +fn read_schi<T: Read>(src: &mut BMFFBox<T>) -> Result<Option<TrackEncryptionBox>> { + let mut tenc = None; + let mut iter = src.box_iter(); + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::TrackEncryptionBox => { + if tenc.is_some() { + return Status::SchiQuantity.into(); + } + tenc = Some(read_tenc(&mut b)?); + } + _ => skip_box_content(&mut b)?, + } + } + + Ok(tenc) +} + +fn read_tenc<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackEncryptionBox> { + let (version, _) = read_fullbox_extra(src)?; + + // reserved byte + skip(src, 1)?; + // the next byte is used to signal the default pattern in version >= 1 + let (default_crypt_byte_block, default_skip_byte_block) = match version { + 0 => { + skip(src, 1)?; + (None, None) + } + _ => { + let pattern_byte = src.read_u8()?; + let crypt_bytes = pattern_byte >> 4; + let skip_bytes = pattern_byte & 0x0f; + (Some(crypt_bytes), Some(skip_bytes)) + } + }; + let default_is_encrypted = src.read_u8()?; + let default_iv_size = src.read_u8()?; + let default_kid = read_buf(src, 16)?; + // If default_is_encrypted == 1 && default_iv_size == 0 we expect a default_constant_iv + let default_constant_iv = match (default_is_encrypted, default_iv_size) { + (1, 0) => { + let default_constant_iv_size = src.read_u8()?; + Some(read_buf(src, default_constant_iv_size.into())?) + } + _ => None, + }; + + Ok(TrackEncryptionBox { + is_encrypted: default_is_encrypted, + iv_size: default_iv_size, + kid: default_kid, + crypt_byte_block_count: default_crypt_byte_block, + skip_byte_block_count: default_skip_byte_block, + constant_iv: default_constant_iv, + }) +} + +fn read_schm<T: Read>(src: &mut BMFFBox<T>) -> Result<SchemeTypeBox> { + // Flags can be used to signal presence of URI in the box, but we don't + // use the URI so don't bother storing the flags. + let (_, _) = read_fullbox_extra(src)?; + let scheme_type = FourCC::from(be_u32(src)?); + let scheme_version = be_u32(src)?; + // Null terminated scheme URI may follow, but we don't use it right now. + skip_box_remain(src)?; + Ok(SchemeTypeBox { + scheme_type, + scheme_version, + }) +} + +/// Parse a metadata box inside a moov, trak, or mdia box. +/// See ISOBMFF (ISO 14496-12:2020) § 8.10.1. +fn read_udta<T: Read>(src: &mut BMFFBox<T>) -> Result<UserdataBox> { + let mut iter = src.box_iter(); + let mut udta = UserdataBox { meta: None }; + + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::MetadataBox => { + let meta = read_meta(&mut b)?; + udta.meta = Some(meta); + } + _ => skip_box_content(&mut b)?, + }; + check_parser_state!(b.content); + } + Ok(udta) +} + +/// Parse the meta box +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.1 +fn read_meta<T: Read>(src: &mut BMFFBox<T>) -> Result<MetadataBox> { + let (_, _) = read_fullbox_extra(src)?; + let mut iter = src.box_iter(); + let mut meta = MetadataBox::default(); + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::MetadataItemListEntry => read_ilst(&mut b, &mut meta)?, + #[cfg(feature = "meta-xml")] + BoxType::MetadataXMLBox => read_xml_(&mut b, &mut meta)?, + #[cfg(feature = "meta-xml")] + BoxType::MetadataBXMLBox => read_bxml(&mut b, &mut meta)?, + _ => skip_box_content(&mut b)?, + }; + check_parser_state!(b.content); + } + Ok(meta) +} + +/// Parse a XML box inside a meta box +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.2 +#[cfg(feature = "meta-xml")] +fn read_xml_<T: Read>(src: &mut BMFFBox<T>, meta: &mut MetadataBox) -> Result<()> { + if read_fullbox_version_no_flags(src)? != 0 { + return Err(Error::Unsupported("unsupported XmlBox version")); + } + meta.xml = Some(XmlBox::StringXmlBox(src.read_into_try_vec()?)); + Ok(()) +} + +/// Parse a Binary XML box inside a meta box +/// See ISOBMFF (ISO 14496-12:2020) § 8.11.2 +#[cfg(feature = "meta-xml")] +fn read_bxml<T: Read>(src: &mut BMFFBox<T>, meta: &mut MetadataBox) -> Result<()> { + if read_fullbox_version_no_flags(src)? != 0 { + return Err(Error::Unsupported("unsupported XmlBox version")); + } + meta.xml = Some(XmlBox::BinaryXmlBox(src.read_into_try_vec()?)); + Ok(()) +} + +/// Parse a metadata box inside a udta box +fn read_ilst<T: Read>(src: &mut BMFFBox<T>, meta: &mut MetadataBox) -> Result<()> { + let mut iter = src.box_iter(); + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::AlbumEntry => meta.album = read_ilst_string_data(&mut b)?, + BoxType::ArtistEntry | BoxType::ArtistLowercaseEntry => { + meta.artist = read_ilst_string_data(&mut b)? + } + BoxType::AlbumArtistEntry => meta.album_artist = read_ilst_string_data(&mut b)?, + BoxType::CommentEntry => meta.comment = read_ilst_string_data(&mut b)?, + BoxType::DateEntry => meta.year = read_ilst_string_data(&mut b)?, + BoxType::TitleEntry => meta.title = read_ilst_string_data(&mut b)?, + BoxType::CustomGenreEntry => { + meta.genre = read_ilst_string_data(&mut b)?.map(Genre::CustomGenre) + } + BoxType::StandardGenreEntry => { + meta.genre = read_ilst_u8_data(&mut b)? + .and_then(|gnre| Some(Genre::StandardGenre(gnre.get(1).copied()?))) + } + BoxType::ComposerEntry => meta.composer = read_ilst_string_data(&mut b)?, + BoxType::EncoderEntry => meta.encoder = read_ilst_string_data(&mut b)?, + BoxType::EncodedByEntry => meta.encoded_by = read_ilst_string_data(&mut b)?, + BoxType::CopyrightEntry => meta.copyright = read_ilst_string_data(&mut b)?, + BoxType::GroupingEntry => meta.grouping = read_ilst_string_data(&mut b)?, + BoxType::CategoryEntry => meta.category = read_ilst_string_data(&mut b)?, + BoxType::KeywordEntry => meta.keyword = read_ilst_string_data(&mut b)?, + BoxType::PodcastUrlEntry => meta.podcast_url = read_ilst_string_data(&mut b)?, + BoxType::PodcastGuidEntry => meta.podcast_guid = read_ilst_string_data(&mut b)?, + BoxType::DescriptionEntry => meta.description = read_ilst_string_data(&mut b)?, + BoxType::LongDescriptionEntry => meta.long_description = read_ilst_string_data(&mut b)?, + BoxType::LyricsEntry => meta.lyrics = read_ilst_string_data(&mut b)?, + BoxType::TVNetworkNameEntry => meta.tv_network_name = read_ilst_string_data(&mut b)?, + BoxType::TVEpisodeNameEntry => meta.tv_episode_name = read_ilst_string_data(&mut b)?, + BoxType::TVShowNameEntry => meta.tv_show_name = read_ilst_string_data(&mut b)?, + BoxType::PurchaseDateEntry => meta.purchase_date = read_ilst_string_data(&mut b)?, + BoxType::RatingEntry => meta.rating = read_ilst_string_data(&mut b)?, + BoxType::OwnerEntry => meta.owner = read_ilst_string_data(&mut b)?, + BoxType::HDVideoEntry => meta.hd_video = read_ilst_bool_data(&mut b)?, + BoxType::SortNameEntry => meta.sort_name = read_ilst_string_data(&mut b)?, + BoxType::SortArtistEntry => meta.sort_artist = read_ilst_string_data(&mut b)?, + BoxType::SortAlbumEntry => meta.sort_album = read_ilst_string_data(&mut b)?, + BoxType::SortAlbumArtistEntry => { + meta.sort_album_artist = read_ilst_string_data(&mut b)? + } + BoxType::SortComposerEntry => meta.sort_composer = read_ilst_string_data(&mut b)?, + BoxType::TrackNumberEntry => { + if let Some(trkn) = read_ilst_u8_data(&mut b)? { + meta.track_number = trkn.get(3).copied(); + meta.total_tracks = trkn.get(5).copied(); + }; + } + BoxType::DiskNumberEntry => { + if let Some(disk) = read_ilst_u8_data(&mut b)? { + meta.disc_number = disk.get(3).copied(); + meta.total_discs = disk.get(5).copied(); + }; + } + BoxType::TempoEntry => { + meta.beats_per_minute = + read_ilst_u8_data(&mut b)?.and_then(|tmpo| tmpo.get(1).copied()) + } + BoxType::CompilationEntry => meta.compilation = read_ilst_bool_data(&mut b)?, + BoxType::AdvisoryEntry => { + meta.advisory = read_ilst_u8_data(&mut b)?.and_then(|rtng| { + Some(match rtng.first()? { + 2 => AdvisoryRating::Clean, + 0 => AdvisoryRating::Inoffensive, + r => AdvisoryRating::Explicit(*r), + }) + }) + } + BoxType::MediaTypeEntry => { + meta.media_type = read_ilst_u8_data(&mut b)?.and_then(|stik| { + Some(match stik.first()? { + 0 => MediaType::Movie, + 1 => MediaType::Normal, + 2 => MediaType::AudioBook, + 5 => MediaType::WhackedBookmark, + 6 => MediaType::MusicVideo, + 9 => MediaType::ShortFilm, + 10 => MediaType::TVShow, + 11 => MediaType::Booklet, + s => MediaType::Unknown(*s), + }) + }) + } + BoxType::PodcastEntry => meta.podcast = read_ilst_bool_data(&mut b)?, + BoxType::TVSeasonNumberEntry => { + meta.tv_season = read_ilst_u8_data(&mut b)?.and_then(|tvsn| tvsn.get(3).copied()) + } + BoxType::TVEpisodeNumberEntry => { + meta.tv_episode_number = + read_ilst_u8_data(&mut b)?.and_then(|tves| tves.get(3).copied()) + } + BoxType::GaplessPlaybackEntry => meta.gapless_playback = read_ilst_bool_data(&mut b)?, + BoxType::CoverArtEntry => meta.cover_art = read_ilst_multiple_u8_data(&mut b).ok(), + _ => skip_box_content(&mut b)?, + }; + check_parser_state!(b.content); + } + Ok(()) +} + +fn read_ilst_bool_data<T: Read>(src: &mut BMFFBox<T>) -> Result<Option<bool>> { + Ok(read_ilst_u8_data(src)?.and_then(|d| Some(d.first()? == &1))) +} + +fn read_ilst_string_data<T: Read>(src: &mut BMFFBox<T>) -> Result<Option<TryString>> { + read_ilst_u8_data(src) +} + +fn read_ilst_u8_data<T: Read>(src: &mut BMFFBox<T>) -> Result<Option<TryVec<u8>>> { + // For all non-covr atoms, there must only be one data atom. + Ok(read_ilst_multiple_u8_data(src)?.pop()) +} + +fn read_ilst_multiple_u8_data<T: Read>(src: &mut BMFFBox<T>) -> Result<TryVec<TryVec<u8>>> { + let mut iter = src.box_iter(); + let mut data = TryVec::new(); + while let Some(mut b) = iter.next_box()? { + match b.head.name { + BoxType::MetadataItemDataEntry => { + data.push(read_ilst_data(&mut b)?)?; + } + _ => skip_box_content(&mut b)?, + }; + check_parser_state!(b.content); + } + Ok(data) +} + +fn read_ilst_data<T: Read>(src: &mut BMFFBox<T>) -> Result<TryVec<u8>> { + // Skip past the padding bytes + skip(&mut src.content, src.head.offset)?; + let size = src.content.limit(); + read_buf(&mut src.content, size) +} + +/// Skip a number of bytes that we don't care to parse. +fn skip<T: Read>(src: &mut T, bytes: u64) -> Result<()> { + std::io::copy(&mut src.take(bytes), &mut std::io::sink())?; + Ok(()) +} + +/// Read size bytes into a Vector or return error. +fn read_buf<T: Read>(src: &mut T, size: u64) -> Result<TryVec<u8>> { + let buf = src.take(size).read_into_try_vec()?; + if buf.len().to_u64() != size { + return Status::ReadBufErr.into(); + } + + Ok(buf) +} + +fn be_i16<T: ReadBytesExt>(src: &mut T) -> Result<i16> { + src.read_i16::<byteorder::BigEndian>().map_err(From::from) +} + +fn be_i32<T: ReadBytesExt>(src: &mut T) -> Result<i32> { + src.read_i32::<byteorder::BigEndian>().map_err(From::from) +} + +fn be_i64<T: ReadBytesExt>(src: &mut T) -> Result<i64> { + src.read_i64::<byteorder::BigEndian>().map_err(From::from) +} + +fn be_u16<T: ReadBytesExt>(src: &mut T) -> Result<u16> { + src.read_u16::<byteorder::BigEndian>().map_err(From::from) +} + +fn be_u24<T: ReadBytesExt>(src: &mut T) -> Result<u32> { + src.read_u24::<byteorder::BigEndian>().map_err(From::from) +} + +fn be_u32<T: ReadBytesExt>(src: &mut T) -> Result<u32> { + src.read_u32::<byteorder::BigEndian>().map_err(From::from) +} + +fn be_u64<T: ReadBytesExt>(src: &mut T) -> Result<u64> { + src.read_u64::<byteorder::BigEndian>().map_err(From::from) +} + +fn write_be_u32<T: WriteBytesExt>(des: &mut T, num: u32) -> Result<()> { + des.write_u32::<byteorder::BigEndian>(num) + .map_err(From::from) +} + +#[cfg(test)] +mod media_data_box_tests { + use super::*; + + impl DataBox { + fn at_offset(file_offset: u64, data: std::vec::Vec<u8>) -> Self { + DataBox { + metadata: DataBoxMetadata::Mdat { file_offset }, + data: data.into(), + } + } + } + + #[test] + fn extent_with_length_before_mdat_returns_none() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { offset: 0, len: 2 }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_to_end_before_mdat_returns_none() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::ToEnd { offset: 0 }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_with_length_crossing_front_mdat_boundary_returns_none() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { offset: 99, len: 3 }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_with_length_which_is_subset_of_mdat() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { + offset: 101, + len: 2, + }; + + assert_eq!(mdat.get(&extent), Some(&[1, 1][..])); + } + + #[test] + fn extent_to_end_which_is_subset_of_mdat() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::ToEnd { offset: 101 }; + + assert_eq!(mdat.get(&extent), Some(&[1, 1, 1, 1][..])); + } + + #[test] + fn extent_with_length_which_is_all_of_mdat() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { + offset: 100, + len: 5, + }; + + assert_eq!(mdat.get(&extent), Some(mdat.data.as_slice())); + } + + #[test] + fn extent_to_end_which_is_all_of_mdat() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::ToEnd { offset: 100 }; + + assert_eq!(mdat.get(&extent), Some(mdat.data.as_slice())); + } + + #[test] + fn extent_with_length_crossing_back_mdat_boundary_returns_none() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { + offset: 103, + len: 3, + }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_with_length_after_mdat_returns_none() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::WithLength { + offset: 200, + len: 2, + }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_to_end_after_mdat_returns_none() { + let mdat = DataBox::at_offset(100, vec![1; 5]); + let extent = Extent::ToEnd { offset: 200 }; + + assert!(mdat.get(&extent).is_none()); + } + + #[test] + fn extent_with_length_which_overflows_usize() { + let mdat = DataBox::at_offset(std::u64::MAX - 1, vec![1; 5]); + let extent = Extent::WithLength { + offset: std::u64::MAX, + len: std::usize::MAX, + }; + + assert!(mdat.get(&extent).is_none()); + } + + // The end of the range would overflow `usize` if it were calculated, but + // because the range end is unbounded, we don't calculate it. + #[test] + fn extent_to_end_which_overflows_usize() { + let mdat = DataBox::at_offset(std::u64::MAX - 1, vec![1; 5]); + let extent = Extent::ToEnd { + offset: std::u64::MAX, + }; + + assert_eq!(mdat.get(&extent), Some(&[1, 1, 1, 1][..])); + } +} diff --git a/third_party/rust/mp4parse/src/macros.rs b/third_party/rust/mp4parse/src/macros.rs new file mode 100644 index 0000000000..15244a316f --- /dev/null +++ b/third_party/rust/mp4parse/src/macros.rs @@ -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 Status::CheckParserStateErr.into(); + } + }; +} diff --git a/third_party/rust/mp4parse/src/tests.rs b/third_party/rust/mp4parse/src/tests.rs new file mode 100644 index 0000000000..3eb95f0e37 --- /dev/null +++ b/third_party/rust/mp4parse/src/tests.rs @@ -0,0 +1,1366 @@ +//! Module for parsing ISO Base Media Format aka video/mp4 streams. +//! Internal unit tests. + +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use super::read_mp4; +use super::ParseStrictness; +use super::{Error, Status}; +use fallible_collections::TryRead as _; + +use std::convert::TryInto as _; +use std::io::Cursor; +use std::io::Read as _; +use test_assembler::*; + +use crate::boxes::BoxType; + +enum BoxSize { + Short(u32), + Long(u64), + UncheckedShort(u32), + UncheckedLong(u64), + Auto, +} + +#[allow(clippy::trivially_copy_pass_by_ref)] // TODO: Consider reworking to a copy +fn make_box<F>(size: BoxSize, name: &[u8; 4], func: F) -> Cursor<Vec<u8>> +where + F: Fn(Section) -> Section, +{ + let mut section = Section::new(); + let box_size = Label::new(); + section = match size { + BoxSize::Short(size) | BoxSize::UncheckedShort(size) => section.B32(size), + BoxSize::Long(_) | BoxSize::UncheckedLong(_) => section.B32(1), + BoxSize::Auto => section.B32(&box_size), + }; + section = section.append_bytes(name); + section = match size { + // The spec allows the 32-bit size to be 0 to indicate unknown + // length streams. It's not clear if this is valid when using a + // 64-bit size, so prohibit it for now. + BoxSize::Long(size) => { + assert!(size > 0); + section.B64(size) + } + BoxSize::UncheckedLong(size) => section.B64(size), + _ => section, + }; + section = func(section); + match size { + BoxSize::Short(size) => { + if size > 0 { + assert_eq!(u64::from(size), section.size()) + } + } + BoxSize::Long(size) => assert_eq!(size, section.size()), + BoxSize::Auto => { + assert!( + section.size() <= u64::from(u32::max_value()), + "Tried to use a long box with BoxSize::Auto" + ); + box_size.set_const(section.size()); + } + // Skip checking BoxSize::Unchecked* cases. + _ => (), + } + Cursor::new(section.get_contents().unwrap()) +} + +fn make_uuid_box<F>(size: BoxSize, uuid: &[u8; 16], func: F) -> Cursor<Vec<u8>> +where + F: Fn(Section) -> Section, +{ + make_box(size, b"uuid", |mut s| { + for b in uuid { + s = s.B8(*b); + } + func(s) + }) +} + +#[allow(clippy::trivially_copy_pass_by_ref)] // TODO: Consider reworking to a copy +fn make_fullbox<F>(size: BoxSize, name: &[u8; 4], version: u8, func: F) -> Cursor<Vec<u8>> +where + F: Fn(Section) -> Section, +{ + make_box(size, name, |s| func(s.B8(version).B8(0).B8(0).B8(0))) +} + +#[test] +fn read_box_header_short() { + let mut stream = make_box(BoxSize::Short(8), b"test", |s| s); + let header = super::read_box_header(&mut stream).unwrap(); + assert_eq!(header.name, BoxType::UnknownBox(0x7465_7374)); // "test" + assert_eq!(header.size, 8); + assert!(header.uuid.is_none()); +} + +#[test] +fn read_box_header_long() { + let mut stream = make_box(BoxSize::Long(16), b"test", |s| s); + let header = super::read_box_header(&mut stream).unwrap(); + assert_eq!(header.name, BoxType::UnknownBox(0x7465_7374)); // "test" + assert_eq!(header.size, 16); + assert!(header.uuid.is_none()); +} + +#[test] +fn read_box_header_short_unknown_size() { + let mut stream = make_box(BoxSize::Short(0), b"test", |s| s); + match super::read_box_header(&mut stream) { + Err(Error::Unsupported(s)) => assert_eq!(s, "unknown sized box"), + _ => panic!("unexpected result reading box with unknown size"), + }; +} + +#[test] +fn read_box_header_short_invalid_size() { + let mut stream = make_box(BoxSize::UncheckedShort(2), b"test", |s| s); + match super::read_box_header(&mut stream) { + Err(Error::InvalidData(s)) => assert_eq!(s, Status::BoxBadSize), + _ => panic!("unexpected result reading box with invalid size"), + }; +} + +#[test] +fn read_box_header_long_invalid_size() { + let mut stream = make_box(BoxSize::UncheckedLong(2), b"test", |s| s); + match super::read_box_header(&mut stream) { + Err(Error::InvalidData(s)) => assert_eq!(s, Status::BoxBadWideSize), + _ => panic!("unexpected result reading box with invalid size"), + }; +} + +#[test] +fn read_box_header_uuid() { + const HEADER_UUID: [u8; 16] = [ + 0x85, 0xc0, 0xb6, 0x87, 0x82, 0x0f, 0x11, 0xe0, 0x81, 0x11, 0xf4, 0xce, 0x46, 0x2b, 0x6a, + 0x48, + ]; + + let mut stream = make_uuid_box(BoxSize::Short(24), &HEADER_UUID, |s| s); + let mut iter = super::BoxIter::new(&mut stream); + let stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::UuidBox); + assert_eq!(stream.head.size, 24); + assert!(stream.head.uuid.is_some()); + assert_eq!(stream.head.uuid.unwrap(), HEADER_UUID); +} + +#[test] +fn read_box_header_truncated_uuid() { + const HEADER_UUID: [u8; 16] = [ + 0x85, 0xc0, 0xb6, 0x87, 0x82, 0x0f, 0x11, 0xe0, 0x81, 0x11, 0xf4, 0xce, 0x46, 0x2b, 0x6a, + 0x48, + ]; + + let mut stream = make_uuid_box(BoxSize::UncheckedShort(23), &HEADER_UUID, |s| s); + let mut iter = super::BoxIter::new(&mut stream); + let stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::UuidBox); + assert_eq!(stream.head.size, 23); + assert!(stream.head.uuid.is_none()); +} + +#[test] +fn read_box_header_uuid_past_eof() { + const HEADER_UUID: [u8; 20] = [ + 0x00, 0x00, 0x00, 0x18, // size = 24 + 0x75, 0x75, 0x69, 0x64, // type = uuid + 0x85, 0xc0, 0xb6, 0x87, 0x82, 0x0f, 0x11, 0xe0, 0x81, 0x11, 0xf4, 0xce, + ]; + + let mut cursor = Cursor::new(HEADER_UUID); + let mut iter = super::BoxIter::new(&mut cursor); + match iter.next_box() { + Ok(None) => (), + Ok(_) => panic!("unexpected box read"), + _ => panic!("unexpected error"), + }; +} + +#[test] +fn read_ftyp() { + let mut stream = make_box(BoxSize::Short(24), b"ftyp", |s| { + s.append_bytes(b"mp42") + .B32(0) // minor version + .append_bytes(b"isom") + .append_bytes(b"mp42") + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::FileTypeBox); + assert_eq!(stream.head.size, 24); + let parsed = super::read_ftyp(&mut stream).unwrap(); + assert_eq!(parsed.major_brand, b"mp42"); // mp42 + assert_eq!(parsed.minor_version, 0); + assert_eq!(parsed.compatible_brands.len(), 2); + assert_eq!(parsed.compatible_brands[0], b"isom"); // isom + assert_eq!(parsed.compatible_brands[1], b"mp42"); // mp42 +} + +#[test] +fn read_truncated_ftyp() { + // We declare a 24 byte box, but only write 20 bytes. + let mut stream = make_box(BoxSize::UncheckedShort(24), b"ftyp", |s| { + s.append_bytes(b"mp42") + .B32(0) // minor version + .append_bytes(b"isom") + }); + match read_mp4(&mut stream) { + Err(Error::UnexpectedEOF) => (), + Ok(_) => panic!("expected an error result"), + _ => panic!("expected a different error result"), + } +} + +#[test] +fn read_ftyp_case() { + // Brands in BMFF are represented as a u32, so it would seem clear that + // 0x6d703432 ("mp42") is not equal to 0x4d503432 ("MP42"), but some + // demuxers treat these as case-insensitive strings, e.g. street.mp4's + // major brand is "MP42". I haven't seen case-insensitive + // compatible_brands (which we also test here), but it doesn't seem + // unlikely given the major_brand behaviour. + let mut stream = make_box(BoxSize::Auto, b"ftyp", |s| { + s.append_bytes(b"MP42") + .B32(0) // minor version + .append_bytes(b"ISOM") + .append_bytes(b"MP42") + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::FileTypeBox); + assert_eq!(stream.head.size, 24); + let parsed = super::read_ftyp(&mut stream).unwrap(); + assert_eq!(parsed.major_brand, b"MP42"); + assert_eq!(parsed.minor_version, 0); + assert_eq!(parsed.compatible_brands.len(), 2); + assert_eq!(parsed.compatible_brands[0], b"ISOM"); // ISOM + assert_eq!(parsed.compatible_brands[1], b"MP42"); // MP42 +} + +#[test] +fn read_elst_v0() { + let mut stream = make_fullbox(BoxSize::Short(28), b"elst", 0, |s| { + s.B32(1) // list count + // first entry + .B32(1234) // duration + .B32(5678) // time + .B16(12) // rate integer + .B16(34) // rate fraction + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::EditListBox); + assert_eq!(stream.head.size, 28); + let parsed = super::read_elst(&mut stream).unwrap(); + assert_eq!(parsed.edits.len(), 1); + assert_eq!(parsed.edits[0].segment_duration, 1234); + assert_eq!(parsed.edits[0].media_time, 5678); + assert_eq!(parsed.edits[0].media_rate_integer, 12); + assert_eq!(parsed.edits[0].media_rate_fraction, 34); +} + +#[test] +fn read_elst_v1() { + let mut stream = make_fullbox(BoxSize::Short(56), b"elst", 1, |s| { + s.B32(2) // list count + // first entry + .B64(1234) // duration + .B64(5678) // time + .B16(12) // rate integer + .B16(34) // rate fraction + // second entry + .B64(1234) // duration + .B64(5678) // time + .B16(12) // rate integer + .B16(34) // rate fraction + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::EditListBox); + assert_eq!(stream.head.size, 56); + let parsed = super::read_elst(&mut stream).unwrap(); + assert_eq!(parsed.edits.len(), 2); + assert_eq!(parsed.edits[1].segment_duration, 1234); + assert_eq!(parsed.edits[1].media_time, 5678); + assert_eq!(parsed.edits[1].media_rate_integer, 12); + assert_eq!(parsed.edits[1].media_rate_fraction, 34); +} + +#[test] +fn read_mdhd_v0() { + let mut stream = make_fullbox(BoxSize::Short(32), b"mdhd", 0, |s| { + s.B32(0) + .B32(0) + .B32(1234) // timescale + .B32(5678) // duration + .B32(0) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::MediaHeaderBox); + assert_eq!(stream.head.size, 32); + let parsed = super::read_mdhd(&mut stream).unwrap(); + assert_eq!(parsed.timescale, 1234); + assert_eq!(parsed.duration, 5678); +} + +#[test] +fn read_mdhd_v1() { + let mut stream = make_fullbox(BoxSize::Short(44), b"mdhd", 1, |s| { + s.B64(0) + .B64(0) + .B32(1234) // timescale + .B64(5678) // duration + .B32(0) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::MediaHeaderBox); + assert_eq!(stream.head.size, 44); + let parsed = super::read_mdhd(&mut stream).unwrap(); + assert_eq!(parsed.timescale, 1234); + assert_eq!(parsed.duration, 5678); +} + +#[test] +fn read_mdhd_unknown_duration() { + let mut stream = make_fullbox(BoxSize::Short(32), b"mdhd", 0, |s| { + s.B32(0) + .B32(0) + .B32(1234) // timescale + .B32(::std::u32::MAX) // duration + .B32(0) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::MediaHeaderBox); + assert_eq!(stream.head.size, 32); + let parsed = super::read_mdhd(&mut stream).unwrap(); + assert_eq!(parsed.timescale, 1234); + assert_eq!(parsed.duration, ::std::u64::MAX); +} + +#[test] +fn read_mdhd_invalid_timescale() { + let mut stream = make_fullbox(BoxSize::Short(44), b"mdhd", 1, |s| { + s.B64(0) + .B64(0) + .B32(0) // timescale + .B64(5678) // duration + .B32(0) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::MediaHeaderBox); + assert_eq!(stream.head.size, 44); + let r = super::parse_mdhd(&mut stream, &super::Track::new(0)); + assert!(r.is_err()); +} + +#[test] +fn read_mvhd_v0() { + let mut stream = make_fullbox(BoxSize::Short(108), b"mvhd", 0, |s| { + s.B32(0).B32(0).B32(1234).B32(5678).append_repeated(0, 80) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::MovieHeaderBox); + assert_eq!(stream.head.size, 108); + let parsed = super::read_mvhd(&mut stream).unwrap(); + assert_eq!(parsed.timescale, 1234); + assert_eq!(parsed.duration, 5678); +} + +#[test] +fn read_mvhd_v1() { + let mut stream = make_fullbox(BoxSize::Short(120), b"mvhd", 1, |s| { + s.B64(0).B64(0).B32(1234).B64(5678).append_repeated(0, 80) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::MovieHeaderBox); + assert_eq!(stream.head.size, 120); + let parsed = super::read_mvhd(&mut stream).unwrap(); + assert_eq!(parsed.timescale, 1234); + assert_eq!(parsed.duration, 5678); +} + +#[test] +fn read_mvhd_invalid_timescale() { + let mut stream = make_fullbox(BoxSize::Short(120), b"mvhd", 1, |s| { + s.B64(0).B64(0).B32(0).B64(5678).append_repeated(0, 80) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::MovieHeaderBox); + assert_eq!(stream.head.size, 120); + let r = super::parse_mvhd(&mut stream); + assert!(r.is_err()); +} + +#[test] +fn read_mvhd_unknown_duration() { + let mut stream = make_fullbox(BoxSize::Short(108), b"mvhd", 0, |s| { + s.B32(0) + .B32(0) + .B32(1234) + .B32(::std::u32::MAX) + .append_repeated(0, 80) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::MovieHeaderBox); + assert_eq!(stream.head.size, 108); + let parsed = super::read_mvhd(&mut stream).unwrap(); + assert_eq!(parsed.timescale, 1234); + assert_eq!(parsed.duration, ::std::u64::MAX); +} + +#[test] +fn read_mvhd_v0_trailing_data() { + let mut stream = make_fullbox(BoxSize::Short(110), b"mvhd", 0, |s| { + s.B32(0) + .B32(0) + .B32(1234) + .B32(5678) + .append_repeated(0, 80) + .B16(0) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::MovieHeaderBox); + assert_eq!(stream.head.size, 110); + let parsed = super::read_mvhd(&mut stream).unwrap(); + assert_eq!(parsed.timescale, 1234); + assert_eq!(parsed.duration, 5678); +} + +#[test] +fn read_vpcc_version_0() { + let data_length = 12u16; + let mut stream = make_fullbox(BoxSize::Auto, b"vpcC", 0, |s| { + s.B8(2) + .B8(0) + .B8(0x82) + .B8(0) + .B16(data_length) + .append_repeated(42, data_length as usize) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::VPCodecConfigurationBox); + let r = super::read_vpcc(&mut stream); + assert!(r.is_ok()); +} + +// TODO: it'd be better to find a real sample here. +#[test] +#[allow(clippy::unusual_byte_groupings)] // Allow odd grouping for test readability. +fn read_vpcc_version_1() { + let data_length = 12u16; + let mut stream = make_fullbox(BoxSize::Auto, b"vpcC", 1, |s| { + s.B8(2) // profile + .B8(0) // level + .B8(0b1000_011_0) // bitdepth (4 bits), chroma (3 bits), video full range (1 bit) + .B8(1) // color primaries + .B8(1) // transfer characteristics + .B8(1) // matrix + .B16(data_length) + .append_repeated(42, data_length as usize) + }); + + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::VPCodecConfigurationBox); + let r = super::read_vpcc(&mut stream); + match r { + Ok(vpcc) => { + assert_eq!(vpcc.bit_depth, 8); + assert_eq!(vpcc.chroma_subsampling, 3); + assert!(!vpcc.video_full_range_flag); + assert_eq!(vpcc.matrix_coefficients.unwrap(), 1); + } + _ => panic!("vpcc parsing error"), + } +} + +#[test] +fn read_hdlr() { + let mut stream = make_fullbox(BoxSize::Short(45), b"hdlr", 0, |s| { + s.B32(0) + .append_bytes(b"vide") + .B32(0) + .B32(0) + .B32(0) + .append_bytes(b"VideoHandler") + .B8(0) // null-terminate string + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::HandlerBox); + assert_eq!(stream.head.size, 45); + let parsed = super::read_hdlr(&mut stream, ParseStrictness::Normal).unwrap(); + assert_eq!(parsed.handler_type, b"vide"); +} + +#[test] +fn read_hdlr_multiple_nul_in_name() { + let mut stream = make_fullbox(BoxSize::Short(45), b"hdlr", 0, |s| { + s.B32(0) + .append_bytes(b"vide") + .B32(0) + .B32(0) + .B32(0) + .append_bytes(b"Vide\0Handler") + .B8(0) // null-terminate string + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::HandlerBox); + assert_eq!(stream.head.size, 45); + assert_eq!( + super::Status::from(super::read_hdlr(&mut stream, ParseStrictness::Strict)), + super::Status::Ok, + ); +} + +#[test] +fn read_hdlr_short_name() { + let mut stream = make_fullbox(BoxSize::Short(33), b"hdlr", 0, |s| { + s.B32(0).append_bytes(b"vide").B32(0).B32(0).B32(0).B8(0) // null-terminate string + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::HandlerBox); + assert_eq!(stream.head.size, 33); + let parsed = super::read_hdlr(&mut stream, ParseStrictness::Normal).unwrap(); + assert_eq!(parsed.handler_type, b"vide"); +} + +#[test] +fn read_hdlr_unsupported_version() { + let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 1, |s| { + s.B32(0).append_bytes(b"vide").B32(0).B32(0).B32(0) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::HandlerBox); + assert_eq!(stream.head.size, 32); + assert_eq!( + super::Status::from(super::read_hdlr(&mut stream, ParseStrictness::Normal)), + super::Status::HdlrUnsupportedVersion, + ); +} + +#[test] +fn read_hdlr_invalid_pre_defined_field() { + let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 0, |s| { + s.B32(1).append_bytes(b"vide").B32(0).B32(0).B32(0) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::HandlerBox); + assert_eq!(stream.head.size, 32); + assert_eq!( + super::Status::from(super::read_hdlr(&mut stream, ParseStrictness::Strict)), + super::Status::HdlrPredefinedNonzero, + ); +} + +#[test] +fn read_hdlr_invalid_reserved_field() { + let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 0, |s| { + s.B32(0).append_bytes(b"vide").B32(0).B32(1).B32(0) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::HandlerBox); + assert_eq!(stream.head.size, 32); + assert_eq!( + super::Status::from(super::read_hdlr(&mut stream, ParseStrictness::Strict)), + super::Status::HdlrReservedNonzero, + ); +} + +#[test] +fn read_hdlr_zero_length_name() { + let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 0, |s| { + s.B32(0).append_bytes(b"vide").B32(0).B32(0).B32(0) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::HandlerBox); + assert_eq!(stream.head.size, 32); + assert_eq!( + super::Status::from(super::read_hdlr(&mut stream, ParseStrictness::Normal)), + super::Status::HdlrNameNoNul, + ); +} + +#[test] +fn read_hdlr_zero_length_name_permissive() { + let mut stream = make_fullbox(BoxSize::Short(32), b"hdlr", 0, |s| { + s.B32(0).append_bytes(b"vide").B32(0).B32(0).B32(0) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::HandlerBox); + assert_eq!(stream.head.size, 32); + let parsed = super::read_hdlr(&mut stream, ParseStrictness::Permissive).unwrap(); + assert_eq!(parsed.handler_type, b"vide"); +} + +fn flac_streaminfo() -> Vec<u8> { + vec![ + 0x10, 0x00, 0x10, 0x00, 0x00, 0x0a, 0x11, 0x00, 0x38, 0x32, 0x0a, 0xc4, 0x42, 0xf0, 0x00, + 0xc9, 0xdf, 0xae, 0xb5, 0x66, 0xfc, 0x02, 0x15, 0xa3, 0xb1, 0x54, 0x61, 0x47, 0x0f, 0xfb, + 0x05, 0x00, 0x33, 0xad, + ] +} + +#[test] +fn read_flac() { + let mut stream = make_box(BoxSize::Auto, b"fLaC", |s| { + s.append_repeated(0, 6) // reserved + .B16(1) // data reference index + .B32(0) // reserved + .B32(0) // reserved + .B16(2) // channel count + .B16(16) // bits per sample + .B16(0) // pre_defined + .B16(0) // reserved + .B32(44100 << 16) // Sample rate + .append_bytes( + &make_dfla( + FlacBlockType::StreamInfo, + true, + &flac_streaminfo(), + FlacBlockLength::Correct, + ) + .into_inner(), + ) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + let r = super::read_audio_sample_entry(&mut stream); + assert!(r.is_ok()); +} + +#[derive(Clone, Copy)] +enum FlacBlockType { + StreamInfo = 0, + _Padding = 1, + _Application = 2, + _Seektable = 3, + _Comment = 4, + _Cuesheet = 5, + _Picture = 6, + _Reserved, + _Invalid = 127, +} + +enum FlacBlockLength { + Correct, + Incorrect(usize), +} + +fn make_dfla( + block_type: FlacBlockType, + last: bool, + data: &[u8], + data_length: FlacBlockLength, +) -> Cursor<Vec<u8>> { + assert!(data.len() < 1 << 24); + make_fullbox(BoxSize::Auto, b"dfLa", 0, |s| { + let flag = u32::from(last); + let size = match data_length { + FlacBlockLength::Correct => (data.len() as u32) & 0x00ff_ffff, + FlacBlockLength::Incorrect(size) => { + assert!(size < 1 << 24); + (size as u32) & 0x00ff_ffff + } + }; + let block_type = (block_type as u32) & 0x7f; + s.B32(flag << 31 | block_type << 24 | size) + .append_bytes(data) + }) +} + +#[test] +fn read_dfla() { + let mut stream = make_dfla( + FlacBlockType::StreamInfo, + true, + &flac_streaminfo(), + FlacBlockLength::Correct, + ); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::FLACSpecificBox); + let dfla = super::read_dfla(&mut stream).unwrap(); + assert_eq!(dfla.version, 0); +} + +#[test] +fn long_flac_metadata() { + let streaminfo = flac_streaminfo(); + let mut stream = make_dfla( + FlacBlockType::StreamInfo, + true, + &streaminfo, + FlacBlockLength::Incorrect(streaminfo.len() + 4), + ); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::FLACSpecificBox); + let r = super::read_dfla(&mut stream); + assert!(r.is_err()); +} + +#[test] +fn read_opus() { + let mut stream = make_box(BoxSize::Auto, b"Opus", |s| { + s.append_repeated(0, 6) + .B16(1) // data reference index + .B32(0) + .B32(0) + .B16(2) // channel count + .B16(16) // bits per sample + .B16(0) + .B16(0) + .B32(48000 << 16) // Sample rate is always 48 kHz for Opus. + .append_bytes(&make_dops().into_inner()) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + let r = super::read_audio_sample_entry(&mut stream); + assert!(r.is_ok()); +} + +fn make_dops() -> Cursor<Vec<u8>> { + make_box(BoxSize::Auto, b"dOps", |s| { + s.B8(0) // version + .B8(2) // channel count + .B16(348) // pre-skip + .B32(44100) // original sample rate + .B16(0) // gain + .B8(0) // channel mapping + }) +} + +#[test] +fn read_dops() { + let mut stream = make_dops(); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + assert_eq!(stream.head.name, BoxType::OpusSpecificBox); + let r = super::read_dops(&mut stream); + assert!(r.is_ok()); +} + +#[test] +fn serialize_opus_header() { + let opus = super::OpusSpecificBox { + version: 0, + output_channel_count: 1, + pre_skip: 342, + input_sample_rate: 24000, + output_gain: 0, + channel_mapping_family: 0, + channel_mapping_table: None, + }; + let mut v = Vec::<u8>::new(); + super::serialize_opus_header(&opus, &mut v).unwrap(); + assert_eq!(v.len(), 19); + assert_eq!( + v, + vec![ + 0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x01, 0x56, 0x01, 0xc0, 0x5d, + 0x00, 0x00, 0x00, 0x00, 0x00, + ] + ); + let opus = super::OpusSpecificBox { + version: 0, + output_channel_count: 6, + pre_skip: 152, + input_sample_rate: 48000, + output_gain: 0, + channel_mapping_family: 1, + channel_mapping_table: Some(super::ChannelMappingTable { + stream_count: 4, + coupled_count: 2, + channel_mapping: vec![0, 4, 1, 2, 3, 5].into(), + }), + }; + let mut v = Vec::<u8>::new(); + super::serialize_opus_header(&opus, &mut v).unwrap(); + assert_eq!(v.len(), 27); + assert_eq!( + v, + vec![ + 0x4f, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x06, 0x98, 0x00, 0x80, 0xbb, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x04, 0x02, 0x00, 0x04, 0x01, 0x02, 0x03, 0x05, + ] + ); +} + +#[test] +fn read_alac() { + let mut stream = make_box(BoxSize::Auto, b"alac", |s| { + s.append_repeated(0, 6) // reserved + .B16(1) // data reference index + .B32(0) // reserved + .B32(0) // reserved + .B16(2) // channel count + .B16(16) // bits per sample + .B16(0) // pre_defined + .B16(0) // reserved + .B32(44100 << 16) // Sample rate + .append_bytes( + &make_fullbox(BoxSize::Auto, b"alac", 0, |s| s.append_bytes(&[0xfa; 24])) + .into_inner(), + ) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + let r = super::read_audio_sample_entry(&mut stream); + assert!(r.is_ok()); +} + +#[test] +fn esds_limit() { + let mut stream = make_box(BoxSize::Auto, b"mp4a", |s| { + s.append_repeated(0, 6) + .B16(1) + .B32(0) + .B32(0) + .B16(2) + .B16(16) + .B16(0) + .B16(0) + .B32(48000 << 16) + .B32(8) + .append_bytes(b"esds") + .append_repeated(0, 4) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + match super::read_audio_sample_entry(&mut stream) { + Err(Error::UnexpectedEOF) => (), + Ok(_) => panic!("expected an error result"), + _ => panic!("expected a different error result"), + } +} + +#[test] +fn read_elst_zero_entries() { + let mut stream = make_fullbox(BoxSize::Auto, b"elst", 0, |s| s.B32(0).B16(12).B16(34)); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + match super::read_elst(&mut stream) { + Ok(elst) => assert_eq!(elst.edits.len(), 0), + _ => panic!("expected no error"), + } +} + +fn make_elst() -> Cursor<Vec<u8>> { + make_fullbox(BoxSize::Auto, b"elst", 1, |s| { + s.B32(1) + // first entry + .B64(1234) // duration + .B64(0xffff_ffff_ffff_ffff) // time + .B16(12) // rate integer + .B16(34) // rate fraction + }) +} + +#[test] +fn read_edts_bogus() { + // First edit list entry has a media_time of -1, so we expect a second + // edit list entry to be present to provide a valid media_time. + // Bogus edts are ignored. + let mut stream = make_box(BoxSize::Auto, b"edts", |s| { + s.append_bytes(&make_elst().into_inner()) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + let mut track = super::Track::new(0); + match super::read_edts(&mut stream, &mut track) { + Ok(_) => { + assert_eq!(track.media_time, None); + assert_eq!(track.empty_duration, None); + } + _ => panic!("expected no error"), + } +} + +#[test] +fn skip_padding_in_boxes() { + // Padding data could be added in the end of these boxes. Parser needs to skip + // them instead of returning error. + let box_names = vec![b"stts", b"stsc", b"stsz", b"stco", b"co64", b"stss"]; + + for name in box_names { + let mut stream = make_fullbox(BoxSize::Auto, name, 1, |s| { + s.append_repeated(0, 100) // add padding data + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + match name { + b"stts" => { + super::read_stts(&mut stream).expect("fail to skip padding: stts"); + } + b"stsc" => { + super::read_stsc(&mut stream).expect("fail to skip padding: stsc"); + } + b"stsz" => { + super::read_stsz(&mut stream).expect("fail to skip padding: stsz"); + } + b"stco" => { + super::read_stco(&mut stream).expect("fail to skip padding: stco"); + } + b"co64" => { + super::read_co64(&mut stream).expect("fail to skip padding: co64"); + } + b"stss" => { + super::read_stss(&mut stream).expect("fail to skip padding: stss"); + } + _ => (), + } + } +} + +#[test] +fn skip_padding_in_stsd() { + // Padding data could be added in the end of stsd boxes. Parser needs to skip + // them instead of returning error. + let avc = make_box(BoxSize::Auto, b"avc1", |s| { + s.append_repeated(0, 6) + .B16(1) + .append_repeated(0, 16) + .B16(320) + .B16(240) + .append_repeated(0, 14) + .append_repeated(0, 32) + .append_repeated(0, 4) + .B32(0xffff_ffff) + .append_bytes(b"avcC") + .append_repeated(0, 100) + }) + .into_inner(); + let mut stream = make_fullbox(BoxSize::Auto, b"stsd", 0, |s| { + s.B32(1) + .append_bytes(avc.as_slice()) + .append_repeated(0, 100) // add padding data + }); + + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + super::read_stsd(&mut stream, &super::Track::new(0)).expect("fail to skip padding: stsd"); +} + +#[test] +fn read_qt_wave_atom() { + let esds = make_fullbox(BoxSize::Auto, b"esds", 0, |s| { + s.B8(0x03) // elementary stream descriptor tag + .B8(0x12) // esds length + .append_repeated(0, 2) + .B8(0x00) // flags + .B8(0x04) // decoder config descriptor tag + .B8(0x0d) // dcds length + .B8(0x6b) // mp3 + .append_repeated(0, 12) + }) + .into_inner(); + let chan = make_box(BoxSize::Auto, b"chan", |s| { + s.append_repeated(0, 10) // we don't care its data. + }) + .into_inner(); + let wave = make_box(BoxSize::Auto, b"wave", |s| s.append_bytes(esds.as_slice())).into_inner(); + let mut stream = make_box(BoxSize::Auto, b"mp4a", |s| { + s.append_repeated(0, 6) + .B16(1) // data_reference_count + .B16(1) // verion: qt -> 1 + .append_repeated(0, 6) + .B16(2) + .B16(16) + .append_repeated(0, 4) + .B32(48000 << 16) + .append_repeated(0, 16) + .append_bytes(wave.as_slice()) + .append_bytes(chan.as_slice()) + }); + + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + let sample_entry = + super::read_audio_sample_entry(&mut stream).expect("fail to read qt wave atom"); + match sample_entry { + super::SampleEntry::Audio(sample_entry) => { + assert_eq!(sample_entry.codec_type, super::CodecType::MP3) + } + _ => panic!("fail to read audio sample enctry"), + } +} + +#[test] +fn read_descriptor_80() { + let aac_esds = vec![ + 0x03, 0x80, 0x80, 0x80, 0x22, 0x00, 0x02, 0x00, 0x04, 0x80, 0x80, 0x80, 0x17, 0x40, 0x15, + 0x00, 0x00, 0x00, 0x00, 0x03, 0x22, 0xBC, 0x00, 0x01, 0xF5, 0x83, 0x05, 0x80, 0x80, 0x80, + 0x02, 0x11, 0x90, 0x06, 0x80, 0x80, 0x80, 0x01, 0x02, + ]; + let aac_dc_descriptor = &aac_esds[31..33]; + let mut stream = make_box(BoxSize::Auto, b"esds", |s| { + s.B32(0) // reserved + .append_bytes(aac_esds.as_slice()) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + + let es = super::read_esds(&mut stream).unwrap(); + + assert_eq!(es.audio_codec, super::CodecType::AAC); + assert_eq!(es.audio_object_type, Some(2)); + assert_eq!(es.extended_audio_object_type, None); + assert_eq!(es.audio_sample_rate, Some(48000)); + assert_eq!(es.audio_channel_count, Some(2)); + assert_eq!(es.codec_esds, aac_esds); + assert_eq!(es.decoder_specific_data, aac_dc_descriptor); +} + +#[test] +fn read_esds() { + let aac_esds = vec![ + 0x03, 0x24, 0x00, 0x00, 0x00, 0x04, 0x1c, 0x40, 0x15, 0x00, 0x12, 0x00, 0x00, 0x01, 0xf4, + 0x00, 0x00, 0x01, 0xf4, 0x00, 0x05, 0x0d, 0x13, 0x00, 0x05, 0x88, 0x05, 0x00, 0x48, 0x21, + 0x10, 0x00, 0x56, 0xe5, 0x98, 0x06, 0x01, 0x02, + ]; + let aac_dc_descriptor = &aac_esds[22..35]; + + let mut stream = make_box(BoxSize::Auto, b"esds", |s| { + s.B32(0) // reserved + .append_bytes(aac_esds.as_slice()) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + + let es = super::read_esds(&mut stream).unwrap(); + + assert_eq!(es.audio_codec, super::CodecType::AAC); + assert_eq!(es.audio_object_type, Some(2)); + assert_eq!(es.extended_audio_object_type, None); + assert_eq!(es.audio_sample_rate, Some(24000)); + assert_eq!(es.audio_channel_count, Some(6)); + assert_eq!(es.codec_esds, aac_esds); + assert_eq!(es.decoder_specific_data, aac_dc_descriptor); +} + +#[test] +fn read_esds_aac_type5() { + let aac_esds = vec![ + 0x03, 0x80, 0x80, 0x80, 0x2F, 0x00, 0x00, 0x00, 0x04, 0x80, 0x80, 0x80, 0x21, 0x40, 0x15, + 0x00, 0x15, 0x00, 0x00, 0x03, 0xED, 0xAA, 0x00, 0x03, 0x6B, 0x00, 0x05, 0x80, 0x80, 0x80, + 0x0F, 0x2B, 0x01, 0x88, 0x02, 0xC4, 0x04, 0x90, 0x2C, 0x10, 0x8C, 0x80, 0x00, 0x00, 0xED, + 0x40, 0x06, 0x80, 0x80, 0x80, 0x01, 0x02, + ]; + + let aac_dc_descriptor = &aac_esds[31..46]; + + let mut stream = make_box(BoxSize::Auto, b"esds", |s| { + s.B32(0) // reserved + .append_bytes(aac_esds.as_slice()) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + + let es = super::read_esds(&mut stream).unwrap(); + + assert_eq!(es.audio_codec, super::CodecType::AAC); + assert_eq!(es.audio_object_type, Some(2)); + assert_eq!(es.extended_audio_object_type, Some(5)); + assert_eq!(es.audio_sample_rate, Some(24000)); + assert_eq!(es.audio_channel_count, Some(8)); + assert_eq!(es.codec_esds, aac_esds); + assert_eq!(es.decoder_specific_data, aac_dc_descriptor); +} + +#[test] +fn read_esds_mpeg2_aac_lc() { + // Recognize MPEG-2 AAC LC (ISO 13818-7) object type as AAC. + // Extracted from BMO #1722497 sdasdasdasd_001.mp4 using Bento4. + // "mp4extract --payload-only moov/trak[1]/mdia/minf/stbl/stsd/mp4a/esds sdasdasdasd_001.mp4 /dev/stdout | xxd -i -c 15" + let aac_esds = vec![ + 0x03, 0x19, 0x00, 0x00, 0x00, 0x04, 0x11, 0x67, 0x15, 0x00, 0x02, 0x38, 0x00, 0x01, 0x0f, + 0xd0, 0x00, 0x00, 0xf5, 0x48, 0x05, 0x02, 0x13, 0x90, 0x06, 0x01, 0x02, + ]; + let aac_dc_descriptor = &aac_esds[22..24]; + + let mut stream = make_box(BoxSize::Auto, b"esds", |s| { + s.B32(0) // reserved + .append_bytes(aac_esds.as_slice()) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + + let es = super::read_esds(&mut stream).unwrap(); + + assert_eq!(es.audio_codec, super::CodecType::AAC); + assert_eq!(es.audio_object_type, Some(2)); + assert_eq!(es.extended_audio_object_type, None); + assert_eq!(es.audio_sample_rate, Some(22050)); + assert_eq!(es.audio_channel_count, Some(2)); + assert_eq!(es.codec_esds, aac_esds); + assert_eq!(es.decoder_specific_data, aac_dc_descriptor); +} + +#[test] +fn read_stsd_mp4v() { + let mp4v = vec![ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd0, 0x01, 0xe0, 0x00, 0x48, + 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, + 0x18, 0xff, 0xff, 0x00, 0x00, 0x00, 0x4c, 0x65, 0x73, 0x64, 0x73, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x3e, 0x00, 0x00, 0x1f, 0x04, 0x36, 0x20, 0x11, 0x01, 0x77, 0x00, 0x00, 0x03, 0xe8, + 0x00, 0x00, 0x03, 0xe8, 0x00, 0x05, 0x27, 0x00, 0x00, 0x01, 0xb0, 0x05, 0x00, 0x00, 0x01, + 0xb5, 0x0e, 0xcf, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x20, 0x00, 0x86, 0xe0, 0x00, + 0x2e, 0xa6, 0x60, 0x16, 0xf4, 0x01, 0xf4, 0x24, 0xc8, 0x01, 0xe5, 0x16, 0x84, 0x3c, 0x14, + 0x63, 0x06, 0x01, 0x02, + ]; + #[cfg(not(feature = "mp4v"))] + let esds_specific_data = &mp4v[90..]; + #[cfg(feature = "mp4v")] + let esds_specific_data = &mp4v[112..151]; + println!("esds_specific_data {esds_specific_data:?}"); + + let mut stream = make_box(BoxSize::Auto, b"mp4v", |s| s.append_bytes(mp4v.as_slice())); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + + let sample_entry = super::read_video_sample_entry(&mut stream).unwrap(); + + match sample_entry { + super::SampleEntry::Video(v) => { + assert_eq!(v.codec_type, super::CodecType::MP4V); + assert_eq!(v.width, 720); + assert_eq!(v.height, 480); + match v.codec_specific { + super::VideoCodecSpecific::ESDSConfig(esds_data) => { + assert_eq!(esds_data.as_slice(), esds_specific_data); + } + _ => panic!("it should be ESDSConfig!"), + } + } + _ => panic!("it should be a video sample entry!"), + } +} + +#[test] +fn read_esds_one_byte_extension_descriptor() { + let esds = vec![ + 0x00, 0x03, 0x80, 0x1b, 0x00, 0x00, 0x00, 0x04, 0x80, 0x12, 0x40, 0x15, 0x00, 0x06, 0x00, + 0x00, 0x01, 0xfe, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x05, 0x80, 0x02, 0x11, 0x90, 0x06, 0x01, + 0x02, + ]; + + let mut stream = make_box(BoxSize::Auto, b"esds", |s| { + s.B32(0) // reserved + .append_bytes(esds.as_slice()) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + + let es = super::read_esds(&mut stream).unwrap(); + + assert_eq!(es.audio_codec, super::CodecType::AAC); + assert_eq!(es.audio_object_type, Some(2)); + assert_eq!(es.extended_audio_object_type, None); + assert_eq!(es.audio_sample_rate, Some(48000)); + assert_eq!(es.audio_channel_count, Some(2)); +} + +#[test] +fn read_esds_byte_extension_descriptor() { + let mut stream = make_box(BoxSize::Auto, b"esds", |s| { + s.B32(0) // reserved + .B16(0x0003) + .B16(0x8181) // extension byte length 0x81 + .append_repeated(0, 0x81) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + + match super::read_esds(&mut stream) { + Ok(_) => (), + _ => panic!("fail to parse descriptor extension byte length"), + } +} + +#[test] +fn read_f4v_stsd() { + let mut stream = make_box(BoxSize::Auto, b".mp3", |s| { + s.append_repeated(0, 6) + .B16(1) + .B16(0) + .append_repeated(0, 6) + .B16(2) + .B16(16) + .append_repeated(0, 4) + .B32(48000 << 16) + }); + + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + let sample_entry = + super::read_audio_sample_entry(&mut stream).expect("failed to read f4v stsd atom"); + match sample_entry { + super::SampleEntry::Audio(sample_entry) => { + assert_eq!(sample_entry.codec_type, super::CodecType::MP3) + } + _ => panic!("fail to read audio sample enctry"), + } +} + +#[test] +fn unknown_video_sample_entry() { + let unknown_codec = make_box(BoxSize::Auto, b"yyyy", |s| s.append_repeated(0, 16)).into_inner(); + let mut stream = make_box(BoxSize::Auto, b"xxxx", |s| { + s.append_repeated(0, 6) + .B16(1) + .append_repeated(0, 16) + .B16(0) + .B16(0) + .append_repeated(0, 14) + .append_repeated(0, 32) + .append_repeated(0, 4) + .append_bytes(unknown_codec.as_slice()) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + match super::read_video_sample_entry(&mut stream) { + Ok(super::SampleEntry::Unknown) => (), + _ => panic!("expected a different error result"), + } +} + +#[test] +fn unknown_audio_sample_entry() { + let unknown_codec = make_box(BoxSize::Auto, b"yyyy", |s| s.append_repeated(0, 16)).into_inner(); + let mut stream = make_box(BoxSize::Auto, b"xxxx", |s| { + s.append_repeated(0, 6) + .B16(1) + .B32(0) + .B32(0) + .B16(2) + .B16(16) + .B16(0) + .B16(0) + .B32(48000 << 16) + .append_bytes(unknown_codec.as_slice()) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + match super::read_audio_sample_entry(&mut stream) { + Ok(super::SampleEntry::Unknown) => (), + _ => panic!("expected a different error result"), + } +} + +#[test] +fn read_esds_invalid_descriptor() { + // tag 0x06, 0xff, 0x7f is incorrect. + let esds = vec![ + 0x03, 0x80, 0x80, 0x80, 0x22, 0x00, 0x00, 0x00, 0x04, 0x80, 0x80, 0x80, 0x14, 0x40, 0x01, + 0x00, 0x04, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x05, 0x80, 0x80, 0x80, + 0x02, 0xe8, 0x35, 0x06, 0xff, 0x7f, 0x00, 0x00, + ]; + + let mut stream = make_box(BoxSize::Auto, b"esds", |s| { + s.B32(0) // reserved + .append_bytes(esds.as_slice()) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + + match super::read_esds(&mut stream) { + Err(Error::InvalidData(s)) => assert_eq!(s, Status::EsdsBadDescriptor), + _ => panic!("unexpected result with invalid descriptor"), + } +} + +#[test] +fn read_esds_redundant_descriptor() { + // the '2' at the end is redundant data. + let esds = vec![ + 3, 25, 0, 1, 0, 4, 19, 64, 21, 0, 0, 0, 0, 0, 0, 0, 0, 1, 119, 0, 5, 2, 18, 16, 6, 1, 2, + ]; + + let mut stream = make_box(BoxSize::Auto, b"esds", |s| { + s.B32(0) // reserved + .append_bytes(esds.as_slice()) + }); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + + match super::read_esds(&mut stream) { + Ok(esds) => assert_eq!(esds.audio_codec, super::CodecType::AAC), + _ => panic!("unexpected result with invalid descriptor"), + } +} + +#[test] +fn read_stsd_lpcm() { + // Extract from sample converted by ffmpeg. + // "ffmpeg -i ./gizmo-short.mp4 -acodec pcm_s16le -ar 96000 -vcodec copy -f mov gizmo-short.mov" + let lpcm = vec![ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x03, 0x00, 0x10, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x48, 0x40, 0xf7, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x7f, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x18, 0x63, 0x68, 0x61, 0x6e, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x64, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + let mut stream = make_box(BoxSize::Auto, b"lpcm", |s| s.append_bytes(lpcm.as_slice())); + let mut iter = super::BoxIter::new(&mut stream); + let mut stream = iter.next_box().unwrap().unwrap(); + + let sample_entry = super::read_audio_sample_entry(&mut stream).unwrap(); + + match sample_entry { + #[allow(clippy::float_cmp)] // The float comparison below is valid and intended. + super::SampleEntry::Audio(a) => { + assert_eq!(a.codec_type, super::CodecType::LPCM); + assert_eq!(a.samplerate, 96000.0); + assert_eq!(a.channelcount, 1); + match a.codec_specific { + super::AudioCodecSpecific::LPCM => (), + _ => panic!("it should be LPCM!"), + } + } + _ => panic!("it should be a audio sample entry!"), + } +} + +#[test] +fn read_to_end_() { + let mut src = b"1234567890".take(5); + let buf = src.read_into_try_vec().unwrap(); + assert_eq!(buf.len(), 5); + assert_eq!(buf, b"12345".as_ref()); +} + +#[test] +fn read_to_end_oom() { + let mut src = b"1234567890".take(std::isize::MAX.try_into().expect("isize < u64")); + assert!(src.read_into_try_vec().is_err()); +} diff --git a/third_party/rust/mp4parse/src/unstable.rs b/third_party/rust/mp4parse/src/unstable.rs new file mode 100644 index 0000000000..188dbbd731 --- /dev/null +++ b/third_party/rust/mp4parse/src/unstable.rs @@ -0,0 +1,541 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +use num_traits::{CheckedAdd, CheckedSub, PrimInt, Zero}; +use std::ops::{Add, Neg, Sub}; + +use super::*; + +/// A zero-overhead wrapper around integer types for the sake of always +/// requiring checked arithmetic +#[repr(transparent)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct CheckedInteger<T>(pub T); + +impl<T> From<T> for CheckedInteger<T> { + fn from(i: T) -> Self { + Self(i) + } +} + +// Orphan rules prevent a more general implementation, but this suffices +impl From<CheckedInteger<i64>> for i64 { + fn from(checked: CheckedInteger<i64>) -> i64 { + checked.0 + } +} + +impl<T, U: Into<T>> Add<U> for CheckedInteger<T> +where + T: CheckedAdd, +{ + type Output = Option<Self>; + + fn add(self, other: U) -> Self::Output { + self.0.checked_add(&other.into()).map(Into::into) + } +} + +impl<T, U: Into<T>> Sub<U> for CheckedInteger<T> +where + T: CheckedSub, +{ + type Output = Option<Self>; + + fn sub(self, other: U) -> Self::Output { + self.0.checked_sub(&other.into()).map(Into::into) + } +} + +/// Implement subtraction of checked `u64`s returning i64 +// This is necessary for handling Mp4parseTrackInfo::media_time gracefully +impl Sub for CheckedInteger<u64> { + type Output = Option<CheckedInteger<i64>>; + + fn sub(self, other: Self) -> Self::Output { + if self >= other { + self.0 + .checked_sub(other.0) + .and_then(|u| i64::try_from(u).ok()) + .map(CheckedInteger) + } else { + other + .0 + .checked_sub(self.0) + .and_then(|u| i64::try_from(u).ok()) + .map(i64::neg) + .map(CheckedInteger) + } + } +} + +#[test] +fn u64_subtraction_returning_i64() { + // self > other + assert_eq!( + CheckedInteger(2u64) - CheckedInteger(1u64), + Some(CheckedInteger(1i64)) + ); + + // self == other + assert_eq!( + CheckedInteger(1u64) - CheckedInteger(1u64), + Some(CheckedInteger(0i64)) + ); + + // difference too large to store in i64 + assert_eq!(CheckedInteger(u64::MAX) - CheckedInteger(1u64), None); + + // self < other + assert_eq!( + CheckedInteger(1u64) - CheckedInteger(2u64), + Some(CheckedInteger(-1i64)) + ); + + // difference not representable due to overflow + assert_eq!(CheckedInteger(1u64) - CheckedInteger(u64::MAX), None); +} + +impl<T: std::cmp::PartialEq> PartialEq<T> for CheckedInteger<T> { + fn eq(&self, other: &T) -> bool { + self.0 == *other + } +} + +/// Provides the following information about a sample in the source file: +/// sample data offset (start and end), composition time in microseconds +/// (start and end) and whether it is a sync sample +#[repr(C)] +#[derive(Default, Debug, PartialEq, Eq)] +pub struct Indice { + /// The byte offset in the file where the indexed sample begins. + pub start_offset: CheckedInteger<u64>, + /// The byte offset in the file where the indexed sample ends. This is + /// equivalent to `start_offset` + the length in bytes of the indexed + /// sample. Typically this will be the `start_offset` of the next sample + /// in the file. + pub end_offset: CheckedInteger<u64>, + /// The time in ticks when the indexed sample should be displayed. + /// Analogous to the concept of presentation time stamp (pts). + pub start_composition: CheckedInteger<i64>, + /// The time in ticks when the indexed sample should stop being + /// displayed. Typically this would be the `start_composition` time of the + /// next sample if samples were ordered by composition time. + pub end_composition: CheckedInteger<i64>, + /// The time in ticks that the indexed sample should be decoded at. + /// Analogous to the concept of decode time stamp (dts). + pub start_decode: CheckedInteger<i64>, + /// Set if the indexed sample is a sync sample. The meaning of sync is + /// somewhat codec specific, but essentially amounts to if the sample is a + /// key frame. + pub sync: bool, +} + +/// Create a vector of `Indice`s with the information about track samples. +/// It uses `stsc`, `stco`, `stsz` and `stts` boxes to construct a list of +/// every sample in the file and provides offsets which can be used to read +/// raw sample data from the file. +#[allow(clippy::reversed_empty_ranges)] +pub fn create_sample_table( + track: &Track, + track_offset_time: CheckedInteger<i64>, +) -> Option<TryVec<Indice>> { + let (stsc, stco, stsz, stts) = match (&track.stsc, &track.stco, &track.stsz, &track.stts) { + (Some(a), Some(b), Some(c), Some(d)) => (a, b, c, d), + _ => return None, + }; + + // According to spec, no sync table means every sample is sync sample. + let has_sync_table = track.stss.is_some(); + + let mut sample_size_iter = stsz.sample_sizes.iter(); + + // Get 'stsc' iterator for (chunk_id, chunk_sample_count) and calculate the sample + // offset address. + + // With large numbers of samples, the cost of many allocations dominates, + // so it's worth iterating twice to allocate sample_table just once. + let total_sample_count = sample_to_chunk_iter(&stsc.samples, &stco.offsets) + .map(|(_, sample_counts)| sample_counts.to_usize()) + .try_fold(0usize, usize::checked_add)?; + let mut sample_table = TryVec::with_capacity(total_sample_count).ok()?; + + for i in sample_to_chunk_iter(&stsc.samples, &stco.offsets) { + let chunk_id = i.0 as usize; + let sample_counts = i.1; + let mut cur_position = match stco.offsets.get(chunk_id) { + Some(&i) => i.into(), + _ => return None, + }; + for _ in 0..sample_counts { + let start_offset = cur_position; + let end_offset = match (stsz.sample_size, sample_size_iter.next()) { + (_, Some(t)) => (start_offset + *t)?, + (t, _) if t > 0 => (start_offset + t)?, + _ => 0.into(), + }; + if end_offset == 0 { + return None; + } + cur_position = end_offset; + + sample_table + .push(Indice { + start_offset, + end_offset, + sync: !has_sync_table, + ..Default::default() + }) + .ok()?; + } + } + + // Mark the sync sample in sample_table according to 'stss'. + if let Some(ref v) = track.stss { + for iter in &v.samples { + match iter + .checked_sub(&1) + .and_then(|idx| sample_table.get_mut(idx as usize)) + { + Some(elem) => elem.sync = true, + _ => return None, + } + } + } + + let ctts_iter = track.ctts.as_ref().map(|v| v.samples.as_slice().iter()); + + let mut ctts_offset_iter = TimeOffsetIterator { + cur_sample_range: (0..0), + cur_offset: 0, + ctts_iter, + track_id: track.id, + }; + + let mut stts_iter = TimeToSampleIterator { + cur_sample_count: (0..0), + cur_sample_delta: 0, + stts_iter: stts.samples.as_slice().iter(), + track_id: track.id, + }; + + // sum_delta is the sum of stts_iter delta. + // According to spec: + // decode time => DT(n) = DT(n-1) + STTS(n) + // composition time => CT(n) = DT(n) + CTTS(n) + // Note: + // composition time needs to add the track offset time from 'elst' table. + let mut sum_delta = TrackScaledTime::<i64>(0, track.id); + for sample in sample_table.as_mut_slice() { + let decode_time = sum_delta; + sum_delta = (sum_delta + stts_iter.next_delta())?; + + // ctts_offset is the current sample offset time. + let ctts_offset = ctts_offset_iter.next_offset_time(); + + let start_composition = decode_time + ctts_offset; + + let end_composition = sum_delta + ctts_offset; + + let start_decode = decode_time; + + sample.start_composition = CheckedInteger(track_offset_time.0 + start_composition?.0); + sample.end_composition = CheckedInteger(track_offset_time.0 + end_composition?.0); + sample.start_decode = CheckedInteger(start_decode.0); + } + + // Correct composition end time due to 'ctts' causes composition time re-ordering. + // + // Composition end time is not in specification. However, gecko needs it, so we need to + // calculate to correct the composition end time. + if !sample_table.is_empty() { + // Create an index table refers to sample_table and sorted by start_composisiton time. + let mut sort_table = TryVec::with_capacity(sample_table.len()).ok()?; + + for i in 0..sample_table.len() { + sort_table.push(i).ok()?; + } + + sort_table.sort_by_key(|i| match sample_table.get(*i) { + Some(v) => v.start_composition, + _ => 0.into(), + }); + + for indices in sort_table.windows(2) { + if let [current_index, peek_index] = *indices { + let next_start_composition_time = sample_table[peek_index].start_composition; + let sample = &mut sample_table[current_index]; + sample.end_composition = next_start_composition_time; + } + } + } + + Some(sample_table) +} + +// Convert a 'ctts' compact table to full table by iterator, +// (sample_with_the_same_offset_count, offset) => (offset), (offset), (offset) ... +// +// For example: +// (2, 10), (4, 9) into (10, 10, 9, 9, 9, 9) by calling next_offset_time(). +struct TimeOffsetIterator<'a> { + cur_sample_range: std::ops::Range<u32>, + cur_offset: i64, + ctts_iter: Option<std::slice::Iter<'a, TimeOffset>>, + track_id: usize, +} + +impl<'a> Iterator for TimeOffsetIterator<'a> { + type Item = i64; + + #[allow(clippy::reversed_empty_ranges)] + fn next(&mut self) -> Option<i64> { + let has_sample = self.cur_sample_range.next().or_else(|| { + // At end of current TimeOffset, find the next TimeOffset. + let iter = match self.ctts_iter { + Some(ref mut v) => v, + _ => return None, + }; + let offset_version; + self.cur_sample_range = match iter.next() { + Some(v) => { + offset_version = v.time_offset; + 0..v.sample_count + } + _ => { + offset_version = TimeOffsetVersion::Version0(0); + 0..0 + } + }; + + self.cur_offset = match offset_version { + TimeOffsetVersion::Version0(i) => i64::from(i), + TimeOffsetVersion::Version1(i) => i64::from(i), + }; + + self.cur_sample_range.next() + }); + + has_sample.and(Some(self.cur_offset)) + } +} + +impl<'a> TimeOffsetIterator<'a> { + fn next_offset_time(&mut self) -> TrackScaledTime<i64> { + match self.next() { + Some(v) => TrackScaledTime::<i64>(v, self.track_id), + _ => TrackScaledTime::<i64>(0, self.track_id), + } + } +} + +// Convert 'stts' compact table to full table by iterator, +// (sample_count_with_the_same_time, time) => (time, time, time) ... repeats +// sample_count_with_the_same_time. +// +// For example: +// (2, 3000), (1, 2999) to (3000, 3000, 2999). +struct TimeToSampleIterator<'a> { + cur_sample_count: std::ops::Range<u32>, + cur_sample_delta: u32, + stts_iter: std::slice::Iter<'a, Sample>, + track_id: usize, +} + +impl<'a> Iterator for TimeToSampleIterator<'a> { + type Item = u32; + + #[allow(clippy::reversed_empty_ranges)] + fn next(&mut self) -> Option<u32> { + let has_sample = self.cur_sample_count.next().or_else(|| { + self.cur_sample_count = match self.stts_iter.next() { + Some(v) => { + self.cur_sample_delta = v.sample_delta; + 0..v.sample_count + } + _ => 0..0, + }; + + self.cur_sample_count.next() + }); + + has_sample.and(Some(self.cur_sample_delta)) + } +} + +impl<'a> TimeToSampleIterator<'a> { + fn next_delta(&mut self) -> TrackScaledTime<i64> { + match self.next() { + Some(v) => TrackScaledTime::<i64>(i64::from(v), self.track_id), + _ => TrackScaledTime::<i64>(0, self.track_id), + } + } +} + +// Convert 'stco' compact table to full table by iterator. +// (start_chunk_num, sample_number) => (start_chunk_num, sample_number), +// (start_chunk_num + 1, sample_number), +// (start_chunk_num + 2, sample_number), +// ... +// (next start_chunk_num, next sample_number), +// ... +// +// For example: +// (1, 5), (5, 10), (9, 2) => (1, 5), (2, 5), (3, 5), (4, 5), (5, 10), (6, 10), +// (7, 10), (8, 10), (9, 2) +fn sample_to_chunk_iter<'a>( + stsc_samples: &'a TryVec<SampleToChunk>, + stco_offsets: &'a TryVec<u64>, +) -> SampleToChunkIterator<'a> { + SampleToChunkIterator { + chunks: (0..0), + sample_count: 0, + stsc_peek_iter: stsc_samples.as_slice().iter().peekable(), + remain_chunk_count: stco_offsets + .len() + .try_into() + .expect("stco.entry_count is u32"), + } +} + +struct SampleToChunkIterator<'a> { + chunks: std::ops::Range<u32>, + sample_count: u32, + stsc_peek_iter: std::iter::Peekable<std::slice::Iter<'a, SampleToChunk>>, + remain_chunk_count: u32, // total chunk number from 'stco'. +} + +impl<'a> Iterator for SampleToChunkIterator<'a> { + type Item = (u32, u32); + + fn next(&mut self) -> Option<(u32, u32)> { + let has_chunk = self.chunks.next().or_else(|| { + self.chunks = self.locate(); + self.remain_chunk_count + .checked_sub( + self.chunks + .len() + .try_into() + .expect("len() of a Range<u32> must fit in u32"), + ) + .and_then(|res| { + self.remain_chunk_count = res; + self.chunks.next() + }) + }); + + has_chunk.map(|id| (id, self.sample_count)) + } +} + +impl<'a> SampleToChunkIterator<'a> { + #[allow(clippy::reversed_empty_ranges)] + fn locate(&mut self) -> std::ops::Range<u32> { + loop { + return match (self.stsc_peek_iter.next(), self.stsc_peek_iter.peek()) { + (Some(next), Some(peek)) if next.first_chunk == peek.first_chunk => { + // Invalid entry, skip it and will continue searching at + // next loop iteration. + continue; + } + (Some(next), Some(peek)) if next.first_chunk > 0 && peek.first_chunk > 0 => { + self.sample_count = next.samples_per_chunk; + (next.first_chunk - 1)..(peek.first_chunk - 1) + } + (Some(next), None) if next.first_chunk > 0 => { + self.sample_count = next.samples_per_chunk; + // Total chunk number in 'stsc' could be different to 'stco', + // there could be more chunks at the last 'stsc' record. + match next.first_chunk.checked_add(self.remain_chunk_count) { + Some(r) => (next.first_chunk - 1)..r - 1, + _ => 0..0, + } + } + _ => 0..0, + }; + } + } +} + +/// Calculate numerator * scale / denominator, if possible. +/// +/// Applying the associativity of integer arithmetic, we divide first +/// and add the remainder after multiplying each term separately +/// to preserve precision while leaving more headroom. That is, +/// (n * s) / d is split into floor(n / d) * s + (n % d) * s / d. +/// +/// Return None on overflow or if the denominator is zero. +pub fn rational_scale<T, S>(numerator: T, denominator: T, scale2: S) -> Option<T> +where + T: PrimInt + Zero, + S: PrimInt, +{ + if denominator.is_zero() { + return None; + } + + let integer = numerator / denominator; + let remainder = numerator % denominator; + num_traits::cast(scale2).and_then(|s| match integer.checked_mul(&s) { + Some(integer) => remainder + .checked_mul(&s) + .and_then(|remainder| (remainder / denominator).checked_add(&integer)), + None => None, + }) +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Microseconds<T>(pub T); + +/// Convert `time` in media's global (mvhd) timescale to microseconds, +/// using provided `MediaTimeScale` +pub fn media_time_to_us(time: MediaScaledTime, scale: MediaTimeScale) -> Option<Microseconds<u64>> { + let microseconds_per_second = 1_000_000; + rational_scale(time.0, scale.0, microseconds_per_second).map(Microseconds) +} + +/// Convert `time` in track's local (mdhd) timescale to microseconds, +/// using provided `TrackTimeScale<T>` +pub fn track_time_to_us<T>( + time: TrackScaledTime<T>, + scale: TrackTimeScale<T>, +) -> Option<Microseconds<T>> +where + T: PrimInt + Zero, +{ + assert_eq!(time.1, scale.1); + let microseconds_per_second = 1_000_000; + rational_scale(time.0, scale.0, microseconds_per_second).map(Microseconds) +} + +#[test] +fn rational_scale_overflow() { + assert_eq!(rational_scale::<u64, u64>(17, 3, 1000), Some(5666)); + let large = 0x4000_0000_0000_0000; + assert_eq!(rational_scale::<u64, u64>(large, 2, 2), Some(large)); + assert_eq!(rational_scale::<u64, u64>(large, 4, 4), Some(large)); + assert_eq!(rational_scale::<u64, u64>(large, 2, 8), None); + assert_eq!(rational_scale::<u64, u64>(large, 8, 4), Some(large / 2)); + assert_eq!(rational_scale::<u64, u64>(large + 1, 4, 4), Some(large + 1)); + assert_eq!(rational_scale::<u64, u64>(large, 40, 1000), None); +} + +#[test] +fn media_time_overflow() { + let scale = MediaTimeScale(90000); + let duration = MediaScaledTime(9_007_199_254_710_000); + assert_eq!( + media_time_to_us(duration, scale), + Some(Microseconds(100_079_991_719_000_000u64)) + ); +} + +#[test] +fn track_time_overflow() { + let scale = TrackTimeScale(44100u64, 0); + let duration = TrackScaledTime(4_413_527_634_807_900u64, 0); + assert_eq!( + track_time_to_us(duration, scale), + Some(Microseconds(100_079_991_719_000_000u64)) + ); +} diff --git a/third_party/rust/mp4parse/tests/amr_nb_1f.3gp b/third_party/rust/mp4parse/tests/amr_nb_1f.3gp Binary files differnew file mode 100644 index 0000000000..5ef216c636 --- /dev/null +++ b/third_party/rust/mp4parse/tests/amr_nb_1f.3gp diff --git a/third_party/rust/mp4parse/tests/amr_wb_1f.3gp b/third_party/rust/mp4parse/tests/amr_wb_1f.3gp Binary files differnew file mode 100644 index 0000000000..a49f39438d --- /dev/null +++ b/third_party/rust/mp4parse/tests/amr_wb_1f.3gp diff --git a/third_party/rust/mp4parse/tests/bbb_sunflower_QCIF_30fps_h263_noaudio_1f.3gp b/third_party/rust/mp4parse/tests/bbb_sunflower_QCIF_30fps_h263_noaudio_1f.3gp Binary files differnew file mode 100644 index 0000000000..7e8ff7f814 --- /dev/null +++ b/third_party/rust/mp4parse/tests/bbb_sunflower_QCIF_30fps_h263_noaudio_1f.3gp diff --git a/third_party/rust/mp4parse/tests/clusterfuzz-testcase-minimized-mp4-6093954524250112 b/third_party/rust/mp4parse/tests/clusterfuzz-testcase-minimized-mp4-6093954524250112 Binary files differnew file mode 100644 index 0000000000..fb6335f563 --- /dev/null +++ b/third_party/rust/mp4parse/tests/clusterfuzz-testcase-minimized-mp4-6093954524250112 diff --git a/third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple.zip b/third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple.zip Binary files differnew file mode 100644 index 0000000000..14629882d0 --- /dev/null +++ b/third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple.zip diff --git a/third_party/rust/mp4parse/tests/overflow.rs b/third_party/rust/mp4parse/tests/overflow.rs new file mode 100644 index 0000000000..ce5dc52247 --- /dev/null +++ b/third_party/rust/mp4parse/tests/overflow.rs @@ -0,0 +1,15 @@ +/// Verify we're built with run-time integer overflow detection. + +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#[test] +#[should_panic(expected = "attempt to add with overflow")] +fn overflow_protection() { + let edge = u32::max_value(); + assert_eq!(0u32, edge + 1); + + let edge = u64::max_value(); + assert_eq!(0u64, edge + 1); +} diff --git a/third_party/rust/mp4parse/tests/public.rs b/third_party/rust/mp4parse/tests/public.rs new file mode 100644 index 0000000000..36f62f47cf --- /dev/null +++ b/third_party/rust/mp4parse/tests/public.rs @@ -0,0 +1,1546 @@ +/// Check if needed fields are still public. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. +use mp4parse as mp4; + +use crate::mp4::{ParseStrictness, Status}; +use std::convert::TryInto; +use std::fs::File; +use std::io::{Cursor, Read, Seek}; + +static MINI_MP4: &str = "tests/minimal.mp4"; +static MINI_MP4_WITH_METADATA: &str = "tests/metadata.mp4"; +static MINI_MP4_WITH_METADATA_STD_GENRE: &str = "tests/metadata_gnre.mp4"; + +static AUDIO_EME_CENC_MP4: &str = "tests/bipbop-cenc-audioinit.mp4"; +static VIDEO_EME_CENC_MP4: &str = "tests/bipbop_480wp_1001kbps-cenc-video-key1-init.mp4"; +// The cbcs files were created via shaka-packager from Firefox's test suite's bipbop.mp4 using: +// packager-win.exe +// in=bipbop.mp4,stream=audio,init_segment=bipbop_cbcs_audio_init.mp4,segment_template=bipbop_cbcs_audio_$Number$.m4s +// in=bipbop.mp4,stream=video,init_segment=bipbop_cbcs_video_init.mp4,segment_template=bipbop_cbcs_video_$Number$.m4s +// --protection_scheme cbcs --enable_raw_key_encryption +// --keys label=:key_id=7e571d047e571d047e571d047e571d21:key=7e5744447e5744447e5744447e574421 +// --iv 11223344556677889900112233445566 +// --generate_static_mpd --mpd_output bipbop_cbcs.mpd +// note: only the init files are needed for these tests +static AUDIO_EME_CBCS_MP4: &str = "tests/bipbop_cbcs_audio_init.mp4"; +static VIDEO_EME_CBCS_MP4: &str = "tests/bipbop_cbcs_video_init.mp4"; +static VIDEO_AV1_MP4: &str = "tests/tiny_av1.mp4"; +// This file contains invalid userdata in its copyright userdata. See +// https://bugzilla.mozilla.org/show_bug.cgi?id=1687357 for more information. +static VIDEO_INVALID_USERDATA: &str = "tests/invalid_userdata.mp4"; +static IMAGE_AVIF: &str = "tests/valid.avif"; +static IMAGE_AVIF_EXTENTS: &str = "tests/multiple-extents.avif"; +static IMAGE_AVIF_ALPHA: &str = "tests/valid-alpha.avif"; +static IMAGE_AVIF_ALPHA_PREMULTIPLIED: &str = "tests/1x1-black-alpha-50pct-premultiplied.avif"; +static IMAGE_AVIF_CORRUPT: &str = "tests/corrupt/bug-1655846.avif"; +static IMAGE_AVIF_CORRUPT_2: &str = "tests/corrupt/bug-1661347.avif"; +static IMAGE_AVIF_IPMA_BAD_VERSION: &str = "tests/bad-ipma-version.avif"; +static IMAGE_AVIF_IPMA_BAD_FLAGS: &str = "tests/bad-ipma-flags.avif"; +static IMAGE_AVIF_IPMA_DUPLICATE_VERSION_AND_FLAGS: &str = + "tests/corrupt/ipma-duplicate-version-and-flags.avif"; +static IMAGE_AVIF_IPMA_DUPLICATE_ITEM_ID: &str = "tests/corrupt/ipma-duplicate-item_id.avif"; +static IMAGE_AVIF_IPMA_INVALID_PROPERTY_INDEX: &str = + "tests/corrupt/ipma-invalid-property-index.avif"; +static IMAGE_AVIF_NO_HDLR: &str = "tests/corrupt/hdlr-not-first.avif"; +static IMAGE_AVIF_HDLR_NOT_FIRST: &str = "tests/corrupt/no-hdlr.avif"; +static IMAGE_AVIF_HDLR_NOT_PICT: &str = "tests/corrupt/hdlr-not-pict.avif"; +static IMAGE_AVIF_HDLR_NONZERO_RESERVED: &str = "tests/hdlr-nonzero-reserved.avif"; +static IMAGE_AVIF_HDLR_MULTIPLE_NUL: &str = "tests/invalid-avif-hdlr-name-multiple-nul.avif"; +static IMAGE_AVIF_NO_MIF1: &str = "tests/no-mif1.avif"; +static IMAGE_AVIF_NO_PITM: &str = "tests/corrupt/no-pitm.avif"; +static IMAGE_AVIF_NO_PIXI: &str = "tests/corrupt/no-pixi.avif"; +static IMAGE_AVIF_NO_AV1C: &str = "tests/corrupt/no-av1C.avif"; +static IMAGE_AVIF_NO_ISPE: &str = "tests/corrupt/no-ispe.avif"; +static IMAGE_AVIF_NO_ALPHA_ISPE: &str = "tests/corrupt/no-alpha-ispe.avif"; +static IMAGE_AVIF_TRANSFORM_ORDER: &str = "tests/corrupt/invalid-transformation-order.avif"; +static IMAGE_AVIF_TRANSFORM_BEFORE_ISPE: &str = "tests/corrupt/transformation-before-ispe.avif"; +static IMAGE_AVIF_NO_ALPHA_AV1C: &str = "tests/corrupt/no-alpha-av1C.avif"; +static IMAGE_AVIF_NO_ALPHA_PIXI: &str = "tests/corrupt/no-pixi-for-alpha.avif"; +static IMAGE_AVIF_AV1C_MISSING_ESSENTIAL: &str = "tests/av1C-missing-essential.avif"; +static IMAGE_AVIF_A1LX_MARKED_ESSENTIAL: &str = "tests/corrupt/a1lx-marked-essential.avif"; +static IMAGE_AVIF_A1OP_MISSING_ESSENTIAL: &str = "tests/corrupt/a1op-missing-essential.avif"; +static IMAGE_AVIF_IMIR_MISSING_ESSENTIAL: &str = "tests/imir-missing-essential.avif"; +static IMAGE_AVIF_IROT_MISSING_ESSENTIAL: &str = "tests/irot-missing-essential.avif"; +static IMAGE_AVIF_LSEL_MISSING_ESSENTIAL: &str = "tests/corrupt/lsel-missing-essential.avif"; +static IMAGE_AVIF_CLAP_MISSING_ESSENTIAL: &str = "tests/clap-missing-essential.avif"; +static IMAGE_AVIF_UNKNOWN_MDAT_SIZE: &str = "tests/unknown_mdat.avif"; +static IMAGE_AVIF_UNKNOWN_MDAT_SIZE_IN_OVERSIZED_META: &str = + "tests/unknown_mdat_in_oversized_meta.avif"; +static IMAGE_AVIF_VALID_WITH_GARBAGE_OVERREAD_AT_END: &str = + "tests/valid_with_garbage_overread.avif"; +static IMAGE_AVIF_VALID_WITH_GARBAGE_BYTE_AT_END: &str = "tests/valid_with_garbage_byte.avif"; +static IMAGE_AVIF_WIDE_BOX_SIZE_0: &str = "tests/wide_box_size_0.avif"; +static AVIF_TEST_DIRS: &[&str] = &["tests", "av1-avif/testFiles", "link-u-avif-sample-images"]; + +// These files are +// av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1op.avif +// av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1lx.avif +// av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_lsel.avif +// respectively, but with https://github.com/AOMediaCodec/av1-avif/issues/174 fixed +static AVIF_A1OP: &str = "tests/a1op.avif"; +static AVIF_A1LX: &str = "tests/a1lx.avif"; +static AVIF_LSEL: &str = "tests/lsel.avif"; + +static AVIF_CLAP: &str = "tests/clap-basic-1_3x3-to-1x1.avif"; +static AVIF_GRID: &str = "av1-avif/testFiles/Microsoft/Summer_in_Tomsk_720p_5x4_grid.avif"; +static AVIF_GRID_A1LX: &str = + "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_grid_a1lx.avif"; +static AVIF_AVIS_MAJOR_NO_PITM: &str = + "av1-avif/testFiles/Netflix/avis/Chimera-AV1-10bit-480x270.avif"; +/// This is av1-avif/testFiles/Netflix/avis/alpha_video.avif +/// but with https://github.com/AOMediaCodec/av1-avif/issues/177 fixed +static AVIF_AVIS_MAJOR_WITH_PITM_AND_ALPHA: &str = "tests/alpha_video_fixed.avif"; +static AVIF_AVIS_WITH_NO_PITM_NO_ILOC: &str = "tests/avis_with_no_ptim_no_iloc.avif"; +static AVIF_AVIS_WITH_PITM_NO_ILOC: &str = "tests/avis_with_pitm_no_iloc.avif"; +static AVIF_AVIS_MAJOR_NO_MOOV: &str = "tests/corrupt/alpha_video_moov_is_moop.avif"; +static AVIF_AVIS_NO_LOOP: &str = "tests/loop_none.avif"; +static AVIF_AVIS_LOOP_FOREVER: &str = "tests/loop_forever.avif"; +static AVIF_NO_PIXI_IMAGES: &[&str] = &[IMAGE_AVIF_NO_PIXI, IMAGE_AVIF_NO_ALPHA_PIXI]; +static AVIF_UNSUPPORTED_IMAGES: &[&str] = &[ + AVIF_A1LX, + AVIF_A1OP, + AVIF_CLAP, + IMAGE_AVIF_CLAP_MISSING_ESSENTIAL, + AVIF_GRID, + AVIF_GRID_A1LX, + AVIF_LSEL, + "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1lx.avif", + "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1op.avif", + "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_a1op_lsel.avif", + "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_lsel.avif", + "av1-avif/testFiles/Apple/multilayer_examples/animals_00_multilayer_grid_lsel.avif", + "av1-avif/testFiles/Link-U/kimono.crop.avif", + "av1-avif/testFiles/Link-U/kimono.mirror-vertical.rotate270.crop.avif", + "av1-avif/testFiles/Microsoft/Chimera_10bit_cropped_to_1920x1008.avif", + "av1-avif/testFiles/Microsoft/Chimera_10bit_cropped_to_1920x1008_with_HDR_metadata.avif", + "av1-avif/testFiles/Microsoft/Chimera_8bit_cropped_480x256.avif", + "av1-avif/testFiles/Xiph/abandoned_filmgrain.avif", + "av1-avif/testFiles/Xiph/fruits_2layer_thumbsize.avif", + "av1-avif/testFiles/Xiph/quebec_3layer_op2.avif", + "av1-avif/testFiles/Xiph/tiger_3layer_1res.avif", + "av1-avif/testFiles/Xiph/tiger_3layer_3res.avif", + "link-u-avif-sample-images/kimono.crop.avif", + "link-u-avif-sample-images/kimono.mirror-vertical.rotate270.crop.avif", +]; +/// See https://github.com/AOMediaCodec/av1-avif/issues/150 +/// https://github.com/AOMediaCodec/av1-avif/issues/174 +/// https://github.com/AOMediaCodec/av1-avif/issues/177 +/// and https://github.com/AOMediaCodec/av1-avif/issues/178 +// TODO: make this into a map of expected errors? +static AV1_AVIF_CORRUPT_IMAGES: &[&str] = &[ + IMAGE_AVIF_UNKNOWN_MDAT_SIZE_IN_OVERSIZED_META, + IMAGE_AVIF_WIDE_BOX_SIZE_0, + "av1-avif/testFiles/Link-U/kimono.crop.avif", + "av1-avif/testFiles/Link-U/kimono.mirror-horizontal.avif", + "av1-avif/testFiles/Link-U/kimono.mirror-vertical.avif", + "av1-avif/testFiles/Link-U/kimono.mirror-vertical.rotate270.avif", + "av1-avif/testFiles/Link-U/kimono.mirror-vertical.rotate270.crop.avif", + "av1-avif/testFiles/Link-U/kimono.rotate90.avif", + "av1-avif/testFiles/Link-U/kimono.rotate270.avif", + "link-u-avif-sample-images/kimono.crop.avif", + "link-u-avif-sample-images/kimono.mirror-horizontal.avif", + "link-u-avif-sample-images/kimono.mirror-vertical.avif", + "link-u-avif-sample-images/kimono.mirror-vertical.rotate270.avif", + "link-u-avif-sample-images/kimono.mirror-vertical.rotate270.crop.avif", + "link-u-avif-sample-images/kimono.rotate90.avif", + "link-u-avif-sample-images/kimono.rotate270.avif", + "link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-full.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-full.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-large.profile1.10bpc.yuv444.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-large.profile1.10bpc.yuv444.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-large.profile1.8bpc.yuv444.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-large.profile1.8bpc.yuv444.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-full.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-full.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-full.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-full.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-full.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-full.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-full.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-small.profile1.10bpc.yuv444.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-small.profile1.10bpc.yuv444.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-small.profile1.8bpc.yuv444.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-small.profile1.8bpc.yuv444.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-full.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-full.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-full.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-full.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-full.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-full.monochrome.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.avif", + "link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.monochrome.avif", +]; +static AVIF_CORRUPT_IMAGES_DIR: &str = "tests/corrupt"; +// The 1 frame h263 3gp file can be generated by ffmpeg with command +// "ffmpeg -i [input file] -f 3gp -vcodec h263 -vf scale=176x144 -frames:v 1 -an output.3gp" +static VIDEO_H263_3GP: &str = "tests/bbb_sunflower_QCIF_30fps_h263_noaudio_1f.3gp"; +// The 1 frame hevc mp4 file generated by ffmpeg with command +// "ffmpeg -f lavfi -i color=c=white:s=640x480 -c:v libx265 -frames:v 1 -pix_fmt yuv420p hevc_white_frame.mp4" +static VIDEO_HEVC_MP4: &str = "tests/hevc_white_frame.mp4"; +// The 1 frame AMR-NB 3gp file can be generated by ffmpeg with command +// "ffmpeg -i [input file] -f 3gp -acodec amr_nb -ar 8000 -ac 1 -frames:a 1 -vn output.3gp" +#[cfg(feature = "3gpp")] +static AUDIO_AMRNB_3GP: &str = "tests/amr_nb_1f.3gp"; +// The 1 frame AMR-WB 3gp file can be generated by ffmpeg with command +// "ffmpeg -i [input file] -f 3gp -acodec amr_wb -ar 16000 -ac 1 -frames:a 1 -vn output.3gp" +#[cfg(feature = "3gpp")] +static AUDIO_AMRWB_3GP: &str = "tests/amr_wb_1f.3gp"; +#[cfg(feature = "mp4v")] +// The 1 frame mp4v mp4 file can be generated by ffmpeg with command +// "ffmpeg -i [input file] -f mp4 -c:v mpeg4 -vf scale=176x144 -frames:v 1 -an output.mp4" +static VIDEO_MP4V_MP4: &str = "tests/bbb_sunflower_QCIF_30fps_mp4v_noaudio_1f.mp4"; + +// Adapted from https://github.com/GuillaumeGomez/audio-video-metadata/blob/9dff40f565af71d5502e03a2e78ae63df95cfd40/src/metadata.rs#L53 +#[test] +fn public_api() { + let mut fd = File::open(MINI_MP4).expect("Unknown file"); + let mut buf = Vec::new(); + fd.read_to_end(&mut buf).expect("File error"); + + let mut c = Cursor::new(&buf); + let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); + assert_eq!(context.timescale, Some(mp4::MediaTimeScale(1000))); + for track in context.tracks { + match track.track_type { + mp4::TrackType::Video => { + // track part + assert_eq!(track.duration, Some(mp4::TrackScaledTime(512, 0))); + assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0))); + assert_eq!(track.media_time, Some(mp4::TrackScaledTime(0, 0))); + assert_eq!(track.timescale, Some(mp4::TrackTimeScale(12800, 0))); + + // track.tkhd part + let tkhd = track.tkhd.unwrap(); + assert!(!tkhd.disabled); + assert_eq!(tkhd.duration, 40); + assert_eq!(tkhd.width, 20_971_520); + assert_eq!(tkhd.height, 15_728_640); + + // track.stsd part + let stsd = track.stsd.expect("expected an stsd"); + let v = match stsd.descriptions.first().expect("expected a SampleEntry") { + mp4::SampleEntry::Video(v) => v, + _ => panic!("expected a VideoSampleEntry"), + }; + assert_eq!(v.width, 320); + assert_eq!(v.height, 240); + assert_eq!( + match v.codec_specific { + mp4::VideoCodecSpecific::AVCConfig(ref avc) => { + assert!(!avc.is_empty()); + "AVC" + } + mp4::VideoCodecSpecific::VPxConfig(ref vpx) => { + // We don't enter in here, we just check if fields are public. + assert!(vpx.bit_depth > 0); + assert!(vpx.colour_primaries > 0); + assert!(vpx.chroma_subsampling > 0); + assert!(!vpx.codec_init.is_empty()); + "VPx" + } + mp4::VideoCodecSpecific::ESDSConfig(ref mp4v) => { + assert!(!mp4v.is_empty()); + "MP4V" + } + mp4::VideoCodecSpecific::AV1Config(ref _av1c) => { + "AV1" + } + mp4::VideoCodecSpecific::H263Config(ref _h263) => { + "H263" + } + mp4::VideoCodecSpecific::HEVCConfig(ref hevc) => { + assert!(!hevc.is_empty()); + "HEVC" + } + }, + "AVC" + ); + } + mp4::TrackType::Audio => { + // track part + assert_eq!(track.duration, Some(mp4::TrackScaledTime(2944, 1))); + assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0))); + assert_eq!(track.media_time, Some(mp4::TrackScaledTime(1024, 1))); + assert_eq!(track.timescale, Some(mp4::TrackTimeScale(48000, 1))); + + // track.tkhd part + let tkhd = track.tkhd.unwrap(); + assert!(!tkhd.disabled); + assert_eq!(tkhd.duration, 62); + assert_eq!(tkhd.width, 0); + assert_eq!(tkhd.height, 0); + + // track.stsd part + let stsd = track.stsd.expect("expected an stsd"); + let a = match stsd.descriptions.first().expect("expected a SampleEntry") { + mp4::SampleEntry::Audio(a) => a, + _ => panic!("expected a AudioSampleEntry"), + }; + assert_eq!( + match a.codec_specific { + mp4::AudioCodecSpecific::ES_Descriptor(ref esds) => { + assert_eq!(esds.audio_codec, mp4::CodecType::AAC); + assert_eq!(esds.audio_sample_rate.unwrap(), 48000); + assert_eq!(esds.audio_object_type.unwrap(), 2); + "ES" + } + mp4::AudioCodecSpecific::FLACSpecificBox(ref flac) => { + // STREAMINFO block must be present and first. + assert!(!flac.blocks.is_empty()); + assert_eq!(flac.blocks[0].block_type, 0); + assert_eq!(flac.blocks[0].data.len(), 34); + "FLAC" + } + mp4::AudioCodecSpecific::OpusSpecificBox(ref opus) => { + // We don't enter in here, we just check if fields are public. + assert!(opus.version > 0); + "Opus" + } + mp4::AudioCodecSpecific::ALACSpecificBox(ref alac) => { + assert!(alac.data.len() == 24 || alac.data.len() == 48); + "ALAC" + } + mp4::AudioCodecSpecific::MP3 => { + "MP3" + } + mp4::AudioCodecSpecific::LPCM => { + "LPCM" + } + #[cfg(feature = "3gpp")] + mp4::AudioCodecSpecific::AMRSpecificBox(_) => { + "AMR" + } + }, + "ES" + ); + assert!(a.samplesize > 0); + assert!(a.samplerate > 0.0); + } + _ => {} + } + } +} + +#[test] +fn public_metadata() { + let mut fd = File::open(MINI_MP4_WITH_METADATA).expect("Unknown file"); + let mut buf = Vec::new(); + fd.read_to_end(&mut buf).expect("File error"); + + let mut c = Cursor::new(&buf); + let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); + let udta = context + .userdata + .expect("didn't find udta") + .expect("failed to parse udta"); + let meta = udta.meta.expect("didn't find meta"); + assert_eq!(meta.title.unwrap(), "Title"); + assert_eq!(meta.artist.unwrap(), "Artist"); + assert_eq!(meta.album_artist.unwrap(), "Album Artist"); + assert_eq!(meta.comment.unwrap(), "Comments"); + assert_eq!(meta.year.unwrap(), "2019"); + assert_eq!( + meta.genre.unwrap(), + mp4::Genre::CustomGenre("Custom Genre".try_into().unwrap()) + ); + assert_eq!(meta.encoder.unwrap(), "Lavf56.40.101"); + assert_eq!(meta.encoded_by.unwrap(), "Encoded-by"); + assert_eq!(meta.copyright.unwrap(), "Copyright"); + assert_eq!(meta.track_number.unwrap(), 3); + assert_eq!(meta.total_tracks.unwrap(), 6); + assert_eq!(meta.disc_number.unwrap(), 5); + assert_eq!(meta.total_discs.unwrap(), 10); + assert_eq!(meta.beats_per_minute.unwrap(), 128); + assert_eq!(meta.composer.unwrap(), "Composer"); + assert!(meta.compilation.unwrap()); + assert!(!meta.gapless_playback.unwrap()); + assert!(!meta.podcast.unwrap()); + assert_eq!(meta.advisory.unwrap(), mp4::AdvisoryRating::Clean); + assert_eq!(meta.media_type.unwrap(), mp4::MediaType::Normal); + assert_eq!(meta.rating.unwrap(), "50"); + assert_eq!(meta.grouping.unwrap(), "Grouping"); + assert_eq!(meta.category.unwrap(), "Category"); + assert_eq!(meta.keyword.unwrap(), "Keyword"); + assert_eq!(meta.description.unwrap(), "Description"); + assert_eq!(meta.lyrics.unwrap(), "Lyrics"); + assert_eq!(meta.long_description.unwrap(), "Long Description"); + assert_eq!(meta.tv_episode_name.unwrap(), "Episode Name"); + assert_eq!(meta.tv_network_name.unwrap(), "Network Name"); + assert_eq!(meta.tv_episode_number.unwrap(), 15); + assert_eq!(meta.tv_season.unwrap(), 10); + assert_eq!(meta.tv_show_name.unwrap(), "Show Name"); + assert!(meta.hd_video.unwrap()); + assert_eq!(meta.owner.unwrap(), "Owner"); + assert_eq!(meta.sort_name.unwrap(), "Sort Name"); + assert_eq!(meta.sort_album.unwrap(), "Sort Album"); + assert_eq!(meta.sort_artist.unwrap(), "Sort Artist"); + assert_eq!(meta.sort_album_artist.unwrap(), "Sort Album Artist"); + assert_eq!(meta.sort_composer.unwrap(), "Sort Composer"); + + // Check for valid JPEG header + let covers = meta.cover_art.unwrap(); + let cover = &covers[0]; + let mut bytes = [0u8; 4]; + bytes[0] = cover[0]; + bytes[1] = cover[1]; + bytes[2] = cover[2]; + assert_eq!(u32::from_le_bytes(bytes), 0x00ff_d8ff); +} + +#[test] +fn public_metadata_gnre() { + let mut fd = File::open(MINI_MP4_WITH_METADATA_STD_GENRE).expect("Unknown file"); + let mut buf = Vec::new(); + fd.read_to_end(&mut buf).expect("File error"); + + let mut c = Cursor::new(&buf); + let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); + let udta = context + .userdata + .expect("didn't find udta") + .expect("failed to parse udta"); + let meta = udta.meta.expect("didn't find meta"); + assert_eq!(meta.title.unwrap(), "Title"); + assert_eq!(meta.artist.unwrap(), "Artist"); + assert_eq!(meta.album_artist.unwrap(), "Album Artist"); + assert_eq!(meta.comment.unwrap(), "Comments"); + assert_eq!(meta.year.unwrap(), "2019"); + assert_eq!(meta.genre.unwrap(), mp4::Genre::StandardGenre(3)); + assert_eq!(meta.encoder.unwrap(), "Lavf56.40.101"); + assert_eq!(meta.encoded_by.unwrap(), "Encoded-by"); + assert_eq!(meta.copyright.unwrap(), "Copyright"); + assert_eq!(meta.track_number.unwrap(), 3); + assert_eq!(meta.total_tracks.unwrap(), 6); + assert_eq!(meta.disc_number.unwrap(), 5); + assert_eq!(meta.total_discs.unwrap(), 10); + assert_eq!(meta.beats_per_minute.unwrap(), 128); + assert_eq!(meta.composer.unwrap(), "Composer"); + assert!(meta.compilation.unwrap()); + assert!(!meta.gapless_playback.unwrap()); + assert!(!meta.podcast.unwrap()); + assert_eq!(meta.advisory.unwrap(), mp4::AdvisoryRating::Clean); + assert_eq!(meta.media_type.unwrap(), mp4::MediaType::Normal); + assert_eq!(meta.rating.unwrap(), "50"); + assert_eq!(meta.grouping.unwrap(), "Grouping"); + assert_eq!(meta.category.unwrap(), "Category"); + assert_eq!(meta.keyword.unwrap(), "Keyword"); + assert_eq!(meta.description.unwrap(), "Description"); + assert_eq!(meta.lyrics.unwrap(), "Lyrics"); + assert_eq!(meta.long_description.unwrap(), "Long Description"); + assert_eq!(meta.tv_episode_name.unwrap(), "Episode Name"); + assert_eq!(meta.tv_network_name.unwrap(), "Network Name"); + assert_eq!(meta.tv_episode_number.unwrap(), 15); + assert_eq!(meta.tv_season.unwrap(), 10); + assert_eq!(meta.tv_show_name.unwrap(), "Show Name"); + assert!(meta.hd_video.unwrap()); + assert_eq!(meta.owner.unwrap(), "Owner"); + assert_eq!(meta.sort_name.unwrap(), "Sort Name"); + assert_eq!(meta.sort_album.unwrap(), "Sort Album"); + assert_eq!(meta.sort_artist.unwrap(), "Sort Artist"); + assert_eq!(meta.sort_album_artist.unwrap(), "Sort Album Artist"); + assert_eq!(meta.sort_composer.unwrap(), "Sort Composer"); + + // Check for valid JPEG header + let covers = meta.cover_art.unwrap(); + let cover = &covers[0]; + let mut bytes = [0u8; 4]; + bytes[0] = cover[0]; + bytes[1] = cover[1]; + bytes[2] = cover[2]; + assert_eq!(u32::from_le_bytes(bytes), 0x00ff_d8ff); +} + +#[test] +fn public_invalid_metadata() { + // Test that reading userdata containing invalid metadata is not fatal to parsing and that + // expected values are still found. + let mut fd = File::open(VIDEO_INVALID_USERDATA).expect("Unknown file"); + let mut buf = Vec::new(); + fd.read_to_end(&mut buf).expect("File error"); + + let mut c = Cursor::new(&buf); + let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); + // Should have userdata. + assert!(context.userdata.is_some()); + // But it should contain an error. + assert!(context.userdata.unwrap().is_err()); + // Smoke test that other data has been parsed. Don't check everything, just make sure some + // values are as expected. + assert_eq!(context.tracks.len(), 2); + for track in context.tracks { + match track.track_type { + mp4::TrackType::Video => { + // Check some of the values in the video tkhd. + let tkhd = track.tkhd.unwrap(); + assert!(!tkhd.disabled); + assert_eq!(tkhd.duration, 231232); + assert_eq!(tkhd.width, 83_886_080); + assert_eq!(tkhd.height, 47_185_920); + } + mp4::TrackType::Audio => { + // Check some of the values in the audio tkhd. + let tkhd = track.tkhd.unwrap(); + assert!(!tkhd.disabled); + assert_eq!(tkhd.duration, 231338); + assert_eq!(tkhd.width, 0); + assert_eq!(tkhd.height, 0); + } + _ => panic!("File should not contain other tracks."), + } + } +} + +#[test] +fn public_audio_tenc() { + let kid = vec![ + 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, + 0x04, + ]; + + let mut fd = File::open(AUDIO_EME_CENC_MP4).expect("Unknown file"); + let mut buf = Vec::new(); + fd.read_to_end(&mut buf).expect("File error"); + + let mut c = Cursor::new(&buf); + let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); + for track in context.tracks { + let stsd = track.stsd.expect("expected an stsd"); + let a = match stsd.descriptions.first().expect("expected a SampleEntry") { + mp4::SampleEntry::Audio(a) => a, + _ => panic!("expected a AudioSampleEntry"), + }; + assert_eq!(a.codec_type, mp4::CodecType::EncryptedAudio); + match a.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { + Some(p) => { + assert_eq!(p.original_format, b"mp4a"); + if let Some(ref schm) = p.scheme_type { + assert_eq!(schm.scheme_type, b"cenc"); + } else { + panic!("Expected scheme type info"); + } + if let Some(ref tenc) = p.tenc { + assert!(tenc.is_encrypted > 0); + assert_eq!(tenc.iv_size, 16); + assert_eq!(tenc.kid, kid); + assert_eq!(tenc.crypt_byte_block_count, None); + assert_eq!(tenc.skip_byte_block_count, None); + assert_eq!(tenc.constant_iv, None); + } else { + panic!("Invalid test condition"); + } + } + _ => { + panic!("Invalid test condition"); + } + } + } +} + +#[test] +fn public_video_cenc() { + let system_id = vec![ + 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, + 0x4b, + ]; + + let kid = vec![ + 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, + 0x11, + ]; + + let pssh_box = vec![ + 0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68, 0x01, 0x00, 0x00, 0x00, 0x10, 0x77, 0xef, + 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, 0x00, 0x00, + 0x00, 0x01, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, 0x57, 0x1d, 0x03, 0x7e, + 0x57, 0x1d, 0x11, 0x00, 0x00, 0x00, 0x00, + ]; + + let mut fd = File::open(VIDEO_EME_CENC_MP4).expect("Unknown file"); + let mut buf = Vec::new(); + fd.read_to_end(&mut buf).expect("File error"); + + let mut c = Cursor::new(&buf); + let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); + for track in context.tracks { + let stsd = track.stsd.expect("expected an stsd"); + let v = match stsd.descriptions.first().expect("expected a SampleEntry") { + mp4::SampleEntry::Video(ref v) => v, + _ => panic!("expected a VideoSampleEntry"), + }; + assert_eq!(v.codec_type, mp4::CodecType::EncryptedVideo); + match v.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { + Some(p) => { + assert_eq!(p.original_format, b"avc1"); + if let Some(ref schm) = p.scheme_type { + assert_eq!(schm.scheme_type, b"cenc"); + } else { + panic!("Expected scheme type info"); + } + if let Some(ref tenc) = p.tenc { + assert!(tenc.is_encrypted > 0); + assert_eq!(tenc.iv_size, 16); + assert_eq!(tenc.kid, kid); + assert_eq!(tenc.crypt_byte_block_count, None); + assert_eq!(tenc.skip_byte_block_count, None); + assert_eq!(tenc.constant_iv, None); + } else { + panic!("Invalid test condition"); + } + } + _ => { + panic!("Invalid test condition"); + } + } + } + + for pssh in context.psshs { + assert_eq!(pssh.system_id, system_id); + for kid_id in pssh.kid { + assert_eq!(kid_id, kid); + } + assert!(pssh.data.is_empty()); + assert_eq!(pssh.box_content, pssh_box); + } +} + +#[test] +fn public_audio_cbcs() { + let system_id = vec![ + 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, + 0x4b, + ]; + + let kid = vec![ + 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, + 0x21, + ]; + + let default_iv = vec![ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, + 0x66, + ]; + + let pssh_box = vec![ + 0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68, 0x01, 0x00, 0x00, 0x00, 0x10, 0x77, 0xef, + 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, 0x00, 0x00, + 0x00, 0x01, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, + 0x57, 0x1d, 0x21, 0x00, 0x00, 0x00, 0x00, + ]; + + let mut fd = File::open(AUDIO_EME_CBCS_MP4).expect("Unknown file"); + let mut buf = Vec::new(); + fd.read_to_end(&mut buf).expect("File error"); + + let mut c = Cursor::new(&buf); + let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); + for track in context.tracks { + let stsd = track.stsd.expect("expected an stsd"); + assert_eq!(stsd.descriptions.len(), 2); + let mut found_encrypted_sample_description = false; + for description in stsd.descriptions { + match description { + mp4::SampleEntry::Audio(ref a) => { + if let Some(p) = a.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { + found_encrypted_sample_description = true; + assert_eq!(p.original_format, b"mp4a"); + if let Some(ref schm) = p.scheme_type { + assert_eq!(schm.scheme_type, b"cbcs"); + } else { + panic!("Expected scheme type info"); + } + if let Some(ref tenc) = p.tenc { + assert!(tenc.is_encrypted > 0); + assert_eq!(tenc.iv_size, 0); + assert_eq!(tenc.kid, kid); + // Note: 0 for both crypt and skip seems odd but + // that's what shaka-packager produced. It appears + // to indicate full encryption. + assert_eq!(tenc.crypt_byte_block_count, Some(0)); + assert_eq!(tenc.skip_byte_block_count, Some(0)); + assert_eq!(tenc.constant_iv, Some(default_iv.clone().into())); + } else { + panic!("Invalid test condition"); + } + } + } + _ => { + panic!("expected a VideoSampleEntry"); + } + } + } + assert!( + found_encrypted_sample_description, + "Should have found an encrypted sample description" + ); + } + + for pssh in context.psshs { + assert_eq!(pssh.system_id, system_id); + for kid_id in pssh.kid { + assert_eq!(kid_id, kid); + } + assert!(pssh.data.is_empty()); + assert_eq!(pssh.box_content, pssh_box); + } +} + +#[test] +fn public_video_cbcs() { + let system_id = vec![ + 0x10, 0x77, 0xef, 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, + 0x4b, + ]; + + let kid = vec![ + 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, + 0x21, + ]; + + let default_iv = vec![ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, + 0x66, + ]; + + let pssh_box = vec![ + 0x00, 0x00, 0x00, 0x34, 0x70, 0x73, 0x73, 0x68, 0x01, 0x00, 0x00, 0x00, 0x10, 0x77, 0xef, + 0xec, 0xc0, 0xb2, 0x4d, 0x02, 0xac, 0xe3, 0x3c, 0x1e, 0x52, 0xe2, 0xfb, 0x4b, 0x00, 0x00, + 0x00, 0x01, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, + 0x57, 0x1d, 0x21, 0x00, 0x00, 0x00, 0x00, + ]; + + let mut fd = File::open(VIDEO_EME_CBCS_MP4).expect("Unknown file"); + let mut buf = Vec::new(); + fd.read_to_end(&mut buf).expect("File error"); + + let mut c = Cursor::new(&buf); + let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); + for track in context.tracks { + let stsd = track.stsd.expect("expected an stsd"); + assert_eq!(stsd.descriptions.len(), 2); + let mut found_encrypted_sample_description = false; + for description in stsd.descriptions { + match description { + mp4::SampleEntry::Video(ref v) => { + assert_eq!(v.width, 400); + assert_eq!(v.height, 300); + if let Some(p) = v.protection_info.iter().find(|sinf| sinf.tenc.is_some()) { + found_encrypted_sample_description = true; + assert_eq!(p.original_format, b"avc1"); + if let Some(ref schm) = p.scheme_type { + assert_eq!(schm.scheme_type, b"cbcs"); + } else { + panic!("Expected scheme type info"); + } + if let Some(ref tenc) = p.tenc { + assert!(tenc.is_encrypted > 0); + assert_eq!(tenc.iv_size, 0); + assert_eq!(tenc.kid, kid); + assert_eq!(tenc.crypt_byte_block_count, Some(1)); + assert_eq!(tenc.skip_byte_block_count, Some(9)); + assert_eq!(tenc.constant_iv, Some(default_iv.clone().into())); + } else { + panic!("Invalid test condition"); + } + } + } + _ => { + panic!("expected a VideoSampleEntry"); + } + } + } + assert!( + found_encrypted_sample_description, + "Should have found an encrypted sample description" + ); + } + + for pssh in context.psshs { + assert_eq!(pssh.system_id, system_id); + for kid_id in pssh.kid { + assert_eq!(kid_id, kid); + } + assert!(pssh.data.is_empty()); + assert_eq!(pssh.box_content, pssh_box); + } +} + +#[test] +fn public_video_av1() { + let mut fd = File::open(VIDEO_AV1_MP4).expect("Unknown file"); + let mut buf = Vec::new(); + fd.read_to_end(&mut buf).expect("File error"); + + let mut c = Cursor::new(&buf); + let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); + for track in context.tracks { + // track part + assert_eq!(track.duration, Some(mp4::TrackScaledTime(512, 0))); + assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0))); + assert_eq!(track.media_time, Some(mp4::TrackScaledTime(0, 0))); + assert_eq!(track.timescale, Some(mp4::TrackTimeScale(12288, 0))); + + // track.tkhd part + let tkhd = track.tkhd.unwrap(); + assert!(!tkhd.disabled); + assert_eq!(tkhd.duration, 42); + assert_eq!(tkhd.width, 4_194_304); + assert_eq!(tkhd.height, 4_194_304); + + // track.stsd part + let stsd = track.stsd.expect("expected an stsd"); + let v = match stsd.descriptions.first().expect("expected a SampleEntry") { + mp4::SampleEntry::Video(ref v) => v, + _ => panic!("expected a VideoSampleEntry"), + }; + assert_eq!(v.codec_type, mp4::CodecType::AV1); + assert_eq!(v.width, 64); + assert_eq!(v.height, 64); + + match v.codec_specific { + mp4::VideoCodecSpecific::AV1Config(ref av1c) => { + // TODO: test av1c fields once ffmpeg is updated + assert_eq!(av1c.profile, 0); + assert_eq!(av1c.level, 0); + assert_eq!(av1c.tier, 0); + assert_eq!(av1c.bit_depth, 8); + assert!(!av1c.monochrome); + assert_eq!(av1c.chroma_subsampling_x, 1); + assert_eq!(av1c.chroma_subsampling_y, 1); + assert_eq!(av1c.chroma_sample_position, 0); + assert!(!av1c.initial_presentation_delay_present); + assert_eq!(av1c.initial_presentation_delay_minus_one, 0); + } + _ => panic!("Invalid test condition"), + } + } +} + +#[test] +fn public_mp4_bug_1185230() { + let input = &mut File::open("tests/test_case_1185230.mp4").expect("Unknown file"); + let context = mp4::read_mp4(input).expect("read_mp4 failed"); + let number_video_tracks = context + .tracks + .iter() + .filter(|t| t.track_type == mp4::TrackType::Video) + .count(); + let number_audio_tracks = context + .tracks + .iter() + .filter(|t| t.track_type == mp4::TrackType::Audio) + .count(); + assert_eq!(number_video_tracks, 2); + assert_eq!(number_audio_tracks, 2); +} + +#[test] +fn public_mp4_ctts_overflow() { + let input = &mut File::open("tests/clusterfuzz-testcase-minimized-mp4-6093954524250112") + .expect("Unknown file"); + assert_eq!(Status::from(mp4::read_mp4(input)), Status::CttsBadSize); +} + +#[test] +fn public_avif_primary_item() { + let input = &mut File::open(IMAGE_AVIF).expect("Unknown file"); + let context = mp4::read_avif(input, ParseStrictness::Normal).expect("read_avif failed"); + assert_eq!( + context.primary_item_coded_data().unwrap(), + [ + 0x12, 0x00, 0x0A, 0x07, 0x38, 0x00, 0x06, 0x90, 0x20, 0x20, 0x69, 0x32, 0x0C, 0x16, + 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x79, 0x4C, 0xD2, 0x02 + ] + ); +} + +#[test] +fn public_avif_primary_item_split_extents() { + let input = &mut File::open(IMAGE_AVIF_EXTENTS).expect("Unknown file"); + let context = mp4::read_avif(input, ParseStrictness::Normal).expect("read_avif failed"); + assert_eq!(context.primary_item_coded_data().unwrap().len(), 52); +} + +#[test] +fn public_avif_alpha_item() { + for_strictness_result(IMAGE_AVIF_ALPHA, |_strictness, result| { + assert!(result.is_ok()); + }); +} + +#[test] +fn public_avif_alpha_non_premultiplied() { + for_strictness_result(IMAGE_AVIF_ALPHA, |_strictness, result| { + let context = result.expect("read_avif failed"); + assert!(context.primary_item_coded_data().is_some()); + assert!(context.alpha_item_coded_data().is_some()); + assert!(!context.premultiplied_alpha); + }); +} + +#[test] +fn public_avif_alpha_premultiplied() { + for_strictness_result(IMAGE_AVIF_ALPHA_PREMULTIPLIED, |_strictness, result| { + let context = result.expect("read_avif failed"); + assert!(context.primary_item_coded_data().is_some()); + assert!(context.alpha_item_coded_data().is_some()); + assert!(context.premultiplied_alpha); + }); +} + +#[test] +fn public_avif_unknown_mdat() { + let input = &mut File::open(IMAGE_AVIF_UNKNOWN_MDAT_SIZE).expect("Unknown file"); + let context = mp4::read_avif(input, ParseStrictness::Normal).expect("read_avif failed"); + assert_eq!( + context.primary_item_coded_data().unwrap(), + [ + 0x12, 0x00, 0x0A, 0x07, 0x38, 0x00, 0x06, 0x90, 0x20, 0x20, 0x69, 0x32, 0x0C, 0x16, + 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x79, 0x4C, 0xD2, 0x02 + ] + ); +} + +#[test] +fn public_avif_unknown_mdat_in_oversized_meta() { + let input = + &mut File::open(IMAGE_AVIF_UNKNOWN_MDAT_SIZE_IN_OVERSIZED_META).expect("Unknown file"); + assert_eq!( + Status::from(mp4::read_avif(input, ParseStrictness::Normal)), + Status::Unsupported + ); +} + +#[test] +fn public_avif_bug_1655846() { + let input = &mut File::open(IMAGE_AVIF_CORRUPT).expect("Unknown file"); + assert!(mp4::read_avif(input, ParseStrictness::Normal).is_err()); +} + +#[test] +fn public_avif_bug_1661347() { + let input = &mut File::open(IMAGE_AVIF_CORRUPT_2).expect("Unknown file"); + assert!(mp4::read_avif(input, ParseStrictness::Normal).is_err()); +} + +fn for_strictness_result( + path: &str, + check: impl Fn(ParseStrictness, mp4::Result<mp4::AvifContext>), +) { + let input = &mut File::open(path).expect("Unknown file"); + + for strictness in [ + ParseStrictness::Permissive, + ParseStrictness::Normal, + ParseStrictness::Strict, + ] { + input.rewind().expect("rewind failed"); + check(strictness, mp4::read_avif(input, strictness)); + } +} + +/// Check that input generates the expected error only in strict parsing mode +fn assert_avif_should(path: &str, expected: Status) { + for_strictness_result(path, |strictness, result| { + if strictness == ParseStrictness::Strict { + assert_eq!(expected, Status::from(result)); + } else { + assert!(result.is_ok()); + } + }) +} + +/// Check that input generates the expected error unless in permissive parsing mode +fn assert_avif_shall(path: &str, expected: Status) { + for_strictness_result(path, |strictness, result| { + if strictness == ParseStrictness::Permissive { + assert!(result.is_ok()); + } else { + assert_eq!(expected, Status::from(result)); + } + }) +} + +// Technically all transforms shall be essential, but this appears likely to change +// so we only enforce it in strict parsing +// See https://github.com/mozilla/mp4parse-rust/issues/284 + +#[test] +fn public_avif_av1c_missing_essential() { + assert_avif_should(IMAGE_AVIF_AV1C_MISSING_ESSENTIAL, Status::TxformNoEssential); +} + +#[test] +fn public_avif_clap_missing_essential() { + for_strictness_result(IMAGE_AVIF_CLAP_MISSING_ESSENTIAL, |strictness, result| { + if strictness == ParseStrictness::Strict { + assert_eq!(Status::TxformNoEssential, Status::from(result)); + } else { + assert_unsupported_nonfatal(&result, mp4::Feature::Clap); + } + }) +} + +#[test] +fn public_avif_imir_missing_essential() { + assert_avif_should(IMAGE_AVIF_IMIR_MISSING_ESSENTIAL, Status::TxformNoEssential); +} + +#[test] +fn public_avif_irot_missing_essential() { + assert_avif_should(IMAGE_AVIF_IROT_MISSING_ESSENTIAL, Status::TxformNoEssential); +} + +#[test] +fn public_avif_ipma_bad_version() { + assert_avif_should(IMAGE_AVIF_IPMA_BAD_VERSION, Status::IpmaBadVersion); +} + +#[test] +fn public_avif_ipma_bad_flags() { + assert_avif_should(IMAGE_AVIF_IPMA_BAD_FLAGS, Status::IpmaFlagsNonzero); +} + +#[test] +fn public_avif_ipma_duplicate_version_and_flags() { + assert_avif_shall( + IMAGE_AVIF_IPMA_DUPLICATE_VERSION_AND_FLAGS, + Status::IpmaBadQuantity, + ); +} + +#[test] +// TODO: convert this to a `assert_avif_shall` test to cover all `ParseStrictness` modes +// that would require crafting an input that validly uses multiple ipma boxes, +// which is kind of annoying to make pass the "should" requirements on flags and version +// as well as the "shall" requirement on duplicate version and flags +fn public_avif_ipma_duplicate_item_id() { + let input = &mut File::open(IMAGE_AVIF_IPMA_DUPLICATE_ITEM_ID).expect("Unknown file"); + assert_eq!( + Status::from(mp4::read_avif(input, ParseStrictness::Permissive)), + Status::IpmaDuplicateItemId + ) +} + +#[test] +fn public_avif_ipma_invalid_property_index() { + assert_avif_shall(IMAGE_AVIF_IPMA_INVALID_PROPERTY_INDEX, Status::IpmaBadIndex); +} + +#[test] +fn public_avif_hdlr_first_in_meta() { + assert_avif_shall(IMAGE_AVIF_NO_HDLR, Status::HdlrNotFirst); + assert_avif_shall(IMAGE_AVIF_HDLR_NOT_FIRST, Status::HdlrNotFirst); +} + +#[test] +fn public_avif_hdlr_is_pict() { + assert_avif_shall(IMAGE_AVIF_HDLR_NOT_PICT, Status::HdlrTypeNotPict); +} + +#[test] +fn public_avif_hdlr_nonzero_reserved() { + // This is a "should" despite the spec indicating a (somewhat ambiguous) + // requirement that this field is set to zero. + // See comments in read_hdlr + assert_avif_should( + IMAGE_AVIF_HDLR_NONZERO_RESERVED, + Status::HdlrReservedNonzero, + ); +} + +#[test] +fn public_avif_hdlr_multiple_nul() { + // This is a "should" despite the spec indicating a (somewhat ambiguous) + // requirement about extra data in boxes + // See comments in read_hdlr + assert_avif_should(IMAGE_AVIF_HDLR_MULTIPLE_NUL, Status::Ok); +} + +#[test] +fn public_avif_no_mif1() { + assert_avif_should(IMAGE_AVIF_NO_MIF1, Status::MissingMif1Brand); +} + +#[test] +fn public_avif_no_pitm() { + assert_avif_shall(IMAGE_AVIF_NO_PITM, Status::PitmMissing); +} + +#[test] +fn public_avif_pixi_present_for_displayable_images() { + let pixi_test = if cfg!(feature = "missing-pixi-permitted") { + assert_avif_should + } else { + assert_avif_shall + }; + + pixi_test(IMAGE_AVIF_NO_PIXI, Status::PixiMissing); + pixi_test(IMAGE_AVIF_NO_ALPHA_PIXI, Status::PixiMissing); +} + +#[test] +fn public_avif_av1c_present_for_av01() { + assert_avif_shall(IMAGE_AVIF_NO_AV1C, Status::Av1cMissing); + assert_avif_shall(IMAGE_AVIF_NO_ALPHA_AV1C, Status::Av1cMissing); +} + +#[test] +fn public_avif_ispe_present() { + assert_avif_shall(IMAGE_AVIF_NO_ISPE, Status::IspeMissing); + assert_avif_shall(IMAGE_AVIF_NO_ALPHA_ISPE, Status::IspeMissing); +} + +#[test] +fn public_avif_transform_before_ispe() { + assert_avif_shall(IMAGE_AVIF_TRANSFORM_BEFORE_ISPE, Status::TxformBeforeIspe); +} + +#[test] +fn public_avif_transform_order() { + assert_avif_shall(IMAGE_AVIF_TRANSFORM_ORDER, Status::TxformOrder); +} + +#[allow(clippy::uninlined_format_args)] +fn assert_unsupported_nonfatal(result: &mp4::Result<mp4::AvifContext>, feature: mp4::Feature) { + match result { + Ok(context) => { + assert!( + context.unsupported_features.contains(feature), + "context.unsupported_features missing expected {:?}", + feature + ); + } + r => panic!( + "Expected Ok with unsupported_features containing {:?}, found {:?}", + feature, r + ), + } +} + +// Assert that across all strictness levels the given feature is tracked as +// being used, but unsupported. Additionally, if the feature is essential, +// assert that the primary item is not processed unless using permissive mode. +// TODO: Add similar tests for alpha +fn assert_unsupported(path: &str, feature: mp4::Feature, essential: bool) { + for_strictness_result(path, |strictness, result| { + assert_unsupported_nonfatal(&result, feature); + match result { + Ok(context) if essential => assert_eq!( + context.primary_item_coded_data().is_some(), + strictness == ParseStrictness::Permissive + ), + Ok(context) if !essential => assert!(context.primary_item_coded_data().is_some()), + _ => panic!("Expected Ok, got {:?}", result), + } + }); +} + +fn assert_unsupported_nonessential(path: &str, feature: mp4::Feature) { + assert_unsupported(path, feature, false); +} + +fn assert_unsupported_essential(path: &str, feature: mp4::Feature) { + assert_unsupported(path, feature, true); +} + +#[test] +fn public_avif_a1lx() { + assert_unsupported_nonessential(AVIF_A1LX, mp4::Feature::A1lx); +} + +#[test] +fn public_avif_a1lx_marked_essential() { + assert_avif_shall(IMAGE_AVIF_A1LX_MARKED_ESSENTIAL, Status::A1lxEssential); +} + +#[test] +fn public_avif_a1op() { + assert_unsupported_essential(AVIF_A1OP, mp4::Feature::A1op); +} + +#[test] +fn public_avif_a1op_missing_essential() { + assert_avif_shall(IMAGE_AVIF_A1OP_MISSING_ESSENTIAL, Status::A1opNoEssential); +} + +#[test] +fn public_avif_lsel() { + assert_unsupported_essential(AVIF_LSEL, mp4::Feature::Lsel); +} + +#[test] +fn public_avif_lsel_missing_essential() { + assert_avif_shall(IMAGE_AVIF_LSEL_MISSING_ESSENTIAL, Status::LselNoEssential); +} + +#[test] +fn public_avif_clap() { + assert_unsupported_essential(AVIF_CLAP, mp4::Feature::Clap); +} + +#[test] +fn public_avif_grid() { + for file in &[AVIF_GRID, AVIF_GRID_A1LX] { + let input = &mut File::open(file).expect(file); + assert_unsupported_nonfatal( + &mp4::read_avif(input, ParseStrictness::Normal), + mp4::Feature::Grid, + ); + } +} + +#[test] +fn public_avis_major_no_pitm() { + let input = &mut File::open(AVIF_AVIS_MAJOR_NO_PITM).expect("Unknown file"); + match mp4::read_avif(input, ParseStrictness::Normal) { + Ok(context) => { + assert_eq!(context.major_brand, mp4::AVIS_BRAND); + assert!(context.primary_item_coded_data().is_none()); + assert!(context.sequence.is_some()); + } + Err(e) => panic!("Expected Ok(_), found {:?}", e), + } +} + +#[test] +fn public_avis_major_with_pitm_and_alpha() { + let input = &mut File::open(AVIF_AVIS_MAJOR_WITH_PITM_AND_ALPHA).expect("Unknown file"); + match mp4::read_avif(input, ParseStrictness::Normal) { + Ok(context) => { + assert_eq!(context.major_brand, mp4::AVIS_BRAND); + assert!(context.primary_item_coded_data().is_some()); + assert!(context.alpha_item_coded_data().is_some()); + match context.sequence { + Some(sequence) => { + assert!(!sequence.tracks.is_empty()); + assert_eq!(sequence.tracks[0].looped, None); + } + None => panic!("Expected sequence"), + } + } + Err(e) => panic!("Expected Ok(_), found {:?}", e), + } +} + +#[test] +fn public_avif_avis_major_no_moov() { + assert_avif_shall(AVIF_AVIS_MAJOR_NO_MOOV, Status::MoovMissing); +} + +#[test] +fn public_avif_avis_with_no_pitm_no_iloc() { + let input = &mut File::open(AVIF_AVIS_WITH_NO_PITM_NO_ILOC).expect("Unknown file"); + match mp4::read_avif(input, ParseStrictness::Normal) { + Ok(context) => { + assert_eq!(context.major_brand, mp4::AVIS_BRAND); + match context.sequence { + Some(sequence) => { + assert!(!sequence.tracks.is_empty()); + assert_eq!(sequence.tracks[0].looped, Some(false)); + } + None => panic!("Expected sequence"), + } + } + Err(e) => panic!("Expected Ok(_), found {:?}", e), + } +} + +#[test] +fn public_avif_avis_with_pitm_no_iloc() { + assert_avif_should(AVIF_AVIS_WITH_PITM_NO_ILOC, Status::PitmNotFound); +} + +#[test] +fn public_avif_valid_with_garbage_overread_at_end() { + assert_avif_should( + IMAGE_AVIF_VALID_WITH_GARBAGE_OVERREAD_AT_END, + Status::CheckParserStateErr, + ); +} + +#[test] +fn public_avif_valid_with_garbage_byte_at_end() { + assert_avif_should(IMAGE_AVIF_VALID_WITH_GARBAGE_BYTE_AT_END, Status::Ok); +} + +#[test] +fn public_avif_bad_video_sample_entry() { + let input = &mut File::open(IMAGE_AVIF_WIDE_BOX_SIZE_0).expect("Unknown file"); + assert_eq!( + Status::from(mp4::read_avif(input, ParseStrictness::Normal)), + Status::BoxBadWideSize + ); +} + +fn public_avis_loop_impl(path: &str, looped: bool) { + let input = &mut File::open(path).expect("Unknown file"); + match mp4::read_avif(input, ParseStrictness::Normal) { + Ok(context) => match context.sequence { + Some(sequence) => { + assert!(!sequence.tracks.is_empty()); + assert_eq!(sequence.tracks[0].looped, Some(looped)); + if looped { + assert!(sequence.tracks[0].edited_duration.is_some()); + } + } + None => panic!( + "Expected sequence in {}", + AVIF_AVIS_MAJOR_WITH_PITM_AND_ALPHA + ), + }, + Err(e) => panic!("Expected Ok(_), found {:?}", e), + } +} + +#[test] +fn public_avif_avis_no_loop() { + public_avis_loop_impl(AVIF_AVIS_NO_LOOP, false); +} + +#[test] +fn public_avif_avis_loop_forever() { + public_avis_loop_impl(AVIF_AVIS_LOOP_FOREVER, true); +} + +#[test] +fn public_avif_read_samples() { + public_avif_read_samples_impl(ParseStrictness::Normal); +} + +#[test] +#[ignore] // See https://github.com/AOMediaCodec/av1-avif/issues/146 +fn public_avif_read_samples_strict() { + public_avif_read_samples_impl(ParseStrictness::Strict); +} + +fn to_canonical_paths(strs: &[&str]) -> Vec<std::path::PathBuf> { + strs.iter() + .map(std::fs::canonicalize) + .map(Result::unwrap) + .collect() +} + +fn public_avif_read_samples_impl(strictness: ParseStrictness) { + let corrupt_images = to_canonical_paths(AV1_AVIF_CORRUPT_IMAGES); + let unsupported_images = to_canonical_paths(AVIF_UNSUPPORTED_IMAGES); + let legal_no_pixi_images = if cfg!(feature = "missing-pixi-permitted") { + to_canonical_paths(AVIF_NO_PIXI_IMAGES) + } else { + vec![] + }; + for dir in AVIF_TEST_DIRS { + for entry in walkdir::WalkDir::new(dir) { + let entry = entry.expect("AVIF entry"); + let path = entry.path(); + let extension = path.extension().unwrap_or_default(); + if !path.is_file() || (extension != "avif" && extension != "avifs") { + eprintln!("Skipping {path:?}"); + continue; // Skip directories, ReadMe.txt, etc. + } + let corrupt = (path.canonicalize().unwrap().parent().unwrap() + == std::fs::canonicalize(AVIF_CORRUPT_IMAGES_DIR).unwrap() + || corrupt_images.contains(&path.canonicalize().unwrap())) + && !legal_no_pixi_images.contains(&path.canonicalize().unwrap()); + + let unsupported = unsupported_images.contains(&path.canonicalize().unwrap()); + println!( + "parsing {}{}{:?}", + if corrupt { "(corrupt) " } else { "" }, + if unsupported { "(unsupported) " } else { "" }, + path, + ); + let input = &mut File::open(path).expect("Unknow file"); + match mp4::read_avif(input, strictness) { + Ok(c) if unsupported || corrupt => { + if unsupported { + assert!(!c.unsupported_features.is_empty()); + } else { + panic!("Expected error parsing {:?}, found:\n{:?}", path, c) + } + } + Ok(c) => { + assert!( + c.unsupported_features.is_empty(), + "{:?}", + c.unsupported_features + ); + eprintln!("Successfully parsed {path:?}") + } + Err(e) if corrupt => { + eprintln!("Expected error parsing corrupt input {path:?}: {e:?}") + } + Err(e) => panic!("Unexpected error parsing {:?}: {:?}", path, e), + } + } + } +} + +#[test] +fn public_video_h263() { + let mut fd = File::open(VIDEO_H263_3GP).expect("Unknown file"); + let mut buf = Vec::new(); + fd.read_to_end(&mut buf).expect("File error"); + + let mut c = Cursor::new(&buf); + let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); + for track in context.tracks { + let stsd = track.stsd.expect("expected an stsd"); + let v = match stsd.descriptions.first().expect("expected a SampleEntry") { + mp4::SampleEntry::Video(ref v) => v, + _ => panic!("expected a VideoSampleEntry"), + }; + assert_eq!(v.codec_type, mp4::CodecType::H263); + assert_eq!(v.width, 176); + assert_eq!(v.height, 144); + let _codec_specific = match v.codec_specific { + mp4::VideoCodecSpecific::H263Config(_) => true, + _ => { + panic!("expected a H263Config",); + } + }; + } +} + +#[test] +fn public_video_hevc() { + let mut fd = File::open(VIDEO_HEVC_MP4).expect("Unknown file"); + let mut buf = Vec::new(); + fd.read_to_end(&mut buf).expect("File error"); + + let mut c = Cursor::new(&buf); + let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); + for track in context.tracks { + let stsd = track.stsd.expect("expected an stsd"); + let v = match stsd.descriptions.first().expect("expected a SampleEntry") { + mp4::SampleEntry::Video(ref v) => v, + _ => panic!("expected a VideoSampleEntry"), + }; + assert_eq!(v.codec_type, mp4::CodecType::HEVC); + assert_eq!(v.width, 640); + assert_eq!(v.height, 480); + let _codec_specific = match &v.codec_specific { + mp4::VideoCodecSpecific::HEVCConfig(_) => true, + _ => { + panic!("expected a HEVCConfig",); + } + }; + } +} + +#[test] +#[cfg(feature = "3gpp")] +fn public_audio_amrnb() { + let mut fd = File::open(AUDIO_AMRNB_3GP).expect("Unknown file"); + let mut buf = Vec::new(); + fd.read_to_end(&mut buf).expect("File error"); + + let mut c = Cursor::new(&buf); + let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); + for track in context.tracks { + let stsd = track.stsd.expect("expected an stsd"); + let a = match stsd.descriptions.first().expect("expected a SampleEntry") { + mp4::SampleEntry::Audio(ref v) => v, + _ => panic!("expected a AudioSampleEntry"), + }; + assert!(a.codec_type == mp4::CodecType::AMRNB); + let _codec_specific = match a.codec_specific { + mp4::AudioCodecSpecific::AMRSpecificBox(_) => true, + _ => { + panic!("expected a AMRSpecificBox",); + } + }; + } +} + +#[test] +#[cfg(feature = "3gpp")] +fn public_audio_amrwb() { + let mut fd = File::open(AUDIO_AMRWB_3GP).expect("Unknown file"); + let mut buf = Vec::new(); + fd.read_to_end(&mut buf).expect("File error"); + + let mut c = Cursor::new(&buf); + let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); + for track in context.tracks { + let stsd = track.stsd.expect("expected an stsd"); + let a = match stsd.descriptions.first().expect("expected a SampleEntry") { + mp4::SampleEntry::Audio(ref v) => v, + _ => panic!("expected a AudioSampleEntry"), + }; + assert!(a.codec_type == mp4::CodecType::AMRWB); + let _codec_specific = match a.codec_specific { + mp4::AudioCodecSpecific::AMRSpecificBox(_) => true, + _ => { + panic!("expected a AMRSpecificBox",); + } + }; + } +} + +#[test] +#[cfg(feature = "mp4v")] +fn public_video_mp4v() { + let mut fd = File::open(VIDEO_MP4V_MP4).expect("Unknown file"); + let mut buf = Vec::new(); + fd.read_to_end(&mut buf).expect("File error"); + + let mut c = Cursor::new(&buf); + let context = mp4::read_mp4(&mut c).expect("read_mp4 failed"); + for track in context.tracks { + let stsd = track.stsd.expect("expected an stsd"); + let v = match stsd.descriptions.first().expect("expected a SampleEntry") { + mp4::SampleEntry::Video(ref v) => v, + _ => panic!("expected a VideoSampleEntry"), + }; + assert_eq!(v.codec_type, mp4::CodecType::MP4V); + assert_eq!(v.width, 176); + assert_eq!(v.height, 144); + let _codec_specific = match v.codec_specific { + mp4::VideoCodecSpecific::ESDSConfig(_) => true, + _ => { + panic!("expected a ESDSConfig",); + } + }; + } +} |