summaryrefslogtreecommitdiffstats
path: root/third_party/rust/mp4parse
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--third_party/rust/mp4parse/.cargo-checksum.json1
-rw-r--r--third_party/rust/mp4parse/Cargo.toml67
-rw-r--r--third_party/rust/mp4parse/LICENSE373
-rw-r--r--third_party/rust/mp4parse/README.md2
-rw-r--r--third_party/rust/mp4parse/benches/avif_benchmark.rs21
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/.github/workflows/encode-and-decode-daily.yml146
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/.gitignore4
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/LICENSE.txt427
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/Makefile911
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/README.md582
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.jpgbin0 -> 272383 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.odd-height.pngbin0 -> 1464986 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.odd-width.odd-height.pngbin0 -> 1463878 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.odd-width.pngbin0 -> 1466473 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.pngbin0 -> 1300068 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.avifbin0 -> 83040 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.avifbin0 -> 71882 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.odd-height.avifbin0 -> 69373 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.odd-width.avifbin0 -> 69321 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.odd-width.odd-height.avifbin0 -> 68303 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.odd-height.avifbin0 -> 80540 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.odd-width.avifbin0 -> 80552 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.odd-width.odd-height.avifbin0 -> 79515 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.avifbin0 -> 80743 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.avifbin0 -> 69856 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.odd-height.avifbin0 -> 67570 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.odd-width.avifbin0 -> 67417 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.odd-width.odd-height.avifbin0 -> 66487 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.odd-height.avifbin0 -> 78504 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.odd-width.avifbin0 -> 78348 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.odd-width.odd-height.avifbin0 -> 77645 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.10bpc.yuv444.avifbin0 -> 97436 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.10bpc.yuv444.odd-height.avifbin0 -> 93201 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.10bpc.yuv444.odd-width.avifbin0 -> 93382 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.10bpc.yuv444.odd-width.odd-height.avifbin0 -> 91311 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.8bpc.yuv444.avifbin0 -> 95375 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.8bpc.yuv444.odd-height.avifbin0 -> 91421 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.8bpc.yuv444.odd-width.avifbin0 -> 91492 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.8bpc.yuv444.odd-width.odd-height.avifbin0 -> 89658 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.avifbin0 -> 89097 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.avifbin0 -> 71878 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.odd-height.avifbin0 -> 69369 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.odd-width.avifbin0 -> 69317 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.odd-width.odd-height.avifbin0 -> 68299 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.odd-height.avifbin0 -> 86052 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.odd-width.avifbin0 -> 86327 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.odd-width.odd-height.avifbin0 -> 85008 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.avifbin0 -> 84078 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.avifbin0 -> 73014 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.odd-height.avifbin0 -> 70251 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.odd-width.avifbin0 -> 70199 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.odd-width.odd-height.avifbin0 -> 69124 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.odd-height.avifbin0 -> 81694 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.odd-width.avifbin0 -> 81695 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.odd-width.odd-height.avifbin0 -> 80631 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.avifbin0 -> 90198 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.avifbin0 -> 73014 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.odd-height.avifbin0 -> 70251 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.odd-width.avifbin0 -> 70199 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.odd-width.odd-height.avifbin0 -> 69124 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.odd-height.avifbin0 -> 86996 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.odd-width.avifbin0 -> 87525 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.odd-width.odd-height.avifbin0 -> 85910 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.avifbin0 -> 98800 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.avifbin0 -> 73014 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.odd-height.avifbin0 -> 70251 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.odd-width.avifbin0 -> 70199 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.odd-width.odd-height.avifbin0 -> 69124 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.odd-height.avifbin0 -> 94557 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.odd-width.avifbin0 -> 94341 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.odd-width.odd-height.avifbin0 -> 92506 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.avifbin0 -> 86782 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.avifbin0 -> 69852 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.odd-height.avifbin0 -> 67566 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.odd-width.avifbin0 -> 67413 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.odd-width.odd-height.avifbin0 -> 66483 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.odd-height.avifbin0 -> 83919 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.odd-width.avifbin0 -> 84128 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.odd-width.odd-height.avifbin0 -> 82819 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/hato.16bpc.pngbin0 -> 32049710 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/hato.jpgbin0 -> 848035 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/hato.pngbin0 -> 6665475 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile0.10bpc.yuv420.avifbin0 -> 328366 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile0.10bpc.yuv420.monochrome.avifbin0 -> 269194 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile0.8bpc.yuv420.avifbin0 -> 259104 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile0.8bpc.yuv420.monochrome.avifbin0 -> 220755 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.10bpc.yuv422.avifbin0 -> 370041 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.10bpc.yuv422.monochrome.avifbin0 -> 269190 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.12bpc.yuv422.avifbin0 -> 380045 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.12bpc.yuv422.monochrome.avifbin0 -> 277716 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.8bpc.yuv422.avifbin0 -> 282146 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.8bpc.yuv422.monochrome.avifbin0 -> 220751 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/images.html745
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/kimono.avifbin0 -> 85445 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/kimono.crop.avifbin0 -> 85486 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/kimono.crop.pngbin0 -> 157486 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/kimono.jpgbin0 -> 259121 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-horizontal.avifbin0 -> 84996 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-horizontal.pngbin0 -> 1267932 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.avifbin0 -> 84632 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.pngbin0 -> 1268141 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.rotate270.avifbin0 -> 85529 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.rotate270.crop.avifbin0 -> 85570 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.rotate270.pngbin0 -> 1277948 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/kimono.pngbin0 -> 1278247 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate270.avifbin0 -> 84886 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate270.pngbin0 -> 1269687 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate90.avifbin0 -> 84837 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate90.pngbin0 -> 1269605 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.pngbin0 -> 87931 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-full.avifbin0 -> 49032 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-full.monochrome.avifbin0 -> 46838 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.avifbin0 -> 48394 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.monochrome.avifbin0 -> 46200 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-full.avifbin0 -> 36191 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-full.monochrome.avifbin0 -> 34060 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.avifbin0 -> 35946 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.monochrome.avifbin0 -> 33815 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile1.10bpc.yuv444.alpha-full.avifbin0 -> 51332 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile1.10bpc.yuv444.alpha-limited.avifbin0 -> 50694 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile1.8bpc.yuv444.alpha-full.avifbin0 -> 38385 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile1.8bpc.yuv444.alpha-limited.avifbin0 -> 38140 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-full.avifbin0 -> 50169 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-full.monochrome.avifbin0 -> 46834 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.avifbin0 -> 49531 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.monochrome.avifbin0 -> 46196 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-full.avifbin0 -> 64569 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-full.monochrome.avifbin0 -> 62152 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.avifbin0 -> 63618 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.monochrome.avifbin0 -> 61201 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-full.avifbin0 -> 65714 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-full.monochrome.avifbin0 -> 62152 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.avifbin0 -> 64763 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.monochrome.avifbin0 -> 61201 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-full.avifbin0 -> 66795 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-full.monochrome.avifbin0 -> 62152 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.avifbin0 -> 65844 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.monochrome.avifbin0 -> 61201 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-full.avifbin0 -> 37161 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-full.monochrome.avifbin0 -> 34056 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.avifbin0 -> 36916 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.monochrome.avifbin0 -> 33811 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.pngbin0 -> 4121 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-full.avifbin0 -> 4217 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-full.monochrome.avifbin0 -> 3978 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.avifbin0 -> 4125 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.monochrome.avifbin0 -> 3886 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-full.avifbin0 -> 3284 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-full.monochrome.avifbin0 -> 3044 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.avifbin0 -> 3236 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.monochrome.avifbin0 -> 2996 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile1.10bpc.yuv444.alpha-full.avifbin0 -> 4415 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile1.10bpc.yuv444.alpha-limited.avifbin0 -> 4323 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile1.8bpc.yuv444.alpha-full.avifbin0 -> 3463 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile1.8bpc.yuv444.alpha-limited.avifbin0 -> 3415 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-full.avifbin0 -> 4292 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-full.monochrome.avifbin0 -> 3974 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.avifbin0 -> 4200 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.monochrome.avifbin0 -> 3882 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-full.avifbin0 -> 5202 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-full.monochrome.avifbin0 -> 4945 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.avifbin0 -> 5110 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.monochrome.avifbin0 -> 4853 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-full.avifbin0 -> 5270 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-full.monochrome.avifbin0 -> 4945 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.avifbin0 -> 5178 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.monochrome.avifbin0 -> 4853 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-full.avifbin0 -> 5378 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-full.monochrome.avifbin0 -> 4945 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.avifbin0 -> 5286 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.monochrome.avifbin0 -> 4853 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-full.avifbin0 -> 3369 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-full.monochrome.avifbin0 -> 3040 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.avifbin0 -> 3321 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.monochrome.avifbin0 -> 2992 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom.svg176
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile-10bpc.avifbin0 -> 198819 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile-12bpc.avifbin0 -> 315064 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile-8bpc.avifbin0 -> 106983 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile-lossy.avifbin0 -> 2840 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile.jpgbin0 -> 231513 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile.pngbin0 -> 214470 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/scripts/compare.sh23
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/star-10bpc-with-alpha.avifsbin0 -> 50564 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/star-10bpc.avifsbin0 -> 31451 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/star-12bpc-with-alpha.avifsbin0 -> 76383 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/star-12bpc.avifsbin0 -> 49583 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/star-8bpc-with-alpha.avifsbin0 -> 29724 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/star-8bpc.avifsbin0 -> 15679 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/star.gifbin0 -> 2900 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/star.input.txt9
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/star.pngbin0 -> 3844 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/star.svg83
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/star180.pngbin0 -> 9211 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/star270.pngbin0 -> 9395 bytes
-rw-r--r--third_party/rust/mp4parse/link-u-avif-sample-images/star90.pngbin0 -> 9272 bytes
-rw-r--r--third_party/rust/mp4parse/src/boxes.rs238
-rw-r--r--third_party/rust/mp4parse/src/lib.rs6291
-rw-r--r--third_party/rust/mp4parse/src/macros.rs12
-rw-r--r--third_party/rust/mp4parse/src/tests.rs1347
-rw-r--r--third_party/rust/mp4parse/src/unstable.rs541
-rw-r--r--third_party/rust/mp4parse/tests/1x1-black-alpha-50pct-premultiplied.avifbin0 -> 1106 bytes
-rw-r--r--third_party/rust/mp4parse/tests/a1lx.avifbin0 -> 1999844 bytes
-rw-r--r--third_party/rust/mp4parse/tests/a1op.avifbin0 -> 1999912 bytes
-rw-r--r--third_party/rust/mp4parse/tests/alpha_video_avif_major_avis_compatible.avifbin0 -> 10755 bytes
-rw-r--r--third_party/rust/mp4parse/tests/alpha_video_fixed.avifbin0 -> 10755 bytes
-rw-r--r--third_party/rust/mp4parse/tests/alpha_video_no_avis.avifbin0 -> 10755 bytes
-rw-r--r--third_party/rust/mp4parse/tests/amr_nb_1f.3gpbin0 -> 701 bytes
-rw-r--r--third_party/rust/mp4parse/tests/amr_wb_1f.3gpbin0 -> 713 bytes
-rw-r--r--third_party/rust/mp4parse/tests/av1C-missing-essential.avifbin0 -> 334 bytes
-rw-r--r--third_party/rust/mp4parse/tests/avis_with_no_ptim_no_iloc.avifbin0 -> 1611 bytes
-rw-r--r--third_party/rust/mp4parse/tests/avis_with_pitm_no_iloc.avifbin0 -> 1625 bytes
-rw-r--r--third_party/rust/mp4parse/tests/bad-ipma-flags.avifbin0 -> 437 bytes
-rw-r--r--third_party/rust/mp4parse/tests/bad-ipma-version.avifbin0 -> 435 bytes
-rw-r--r--third_party/rust/mp4parse/tests/bbb_sunflower_QCIF_30fps_h263_noaudio_1f.3gpbin0 -> 1578 bytes
-rw-r--r--third_party/rust/mp4parse/tests/clap-basic-1_3x3-to-1x1.avifbin0 -> 355 bytes
-rw-r--r--third_party/rust/mp4parse/tests/clap-missing-essential.avifbin0 -> 355 bytes
-rw-r--r--third_party/rust/mp4parse/tests/clusterfuzz-testcase-minimized-mp4-6093954524250112bin0 -> 56 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/a1lx-marked-essential.avifbin0 -> 1999844 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/a1op-missing-essential.avifbin0 -> 1999912 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/alpha_video_moov_is_moop.avifbin0 -> 10755 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/bug-1655846.avifbin0 -> 256 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/bug-1661347.avifbin0 -> 8468 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/clusterfuzz-testcase-minimized-avif-4914209301856256.avifbin0 -> 342 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/hdlr-not-first.avifbin0 -> 334 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/hdlr-not-pict.avifbin0 -> 334 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/imir-before-clap.avifbin0 -> 345 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple-nclx.avifbin0 -> 315 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple-prof.avifbin0 -> 864 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple-rICC.avifbin0 -> 864 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple.zipbin0 -> 1963 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/invalid-transformation-order.avifbin0 -> 345 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/ipma-duplicate-item_id.avifbin0 -> 49032 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/ipma-duplicate-version-and-flags.avifbin0 -> 49032 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/ipma-invalid-property-index.avifbin0 -> 294 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/lsel-missing-essential.avifbin0 -> 1999914 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/no-alpha-av1C.avifbin0 -> 437 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/no-alpha-ispe.avifbin0 -> 449 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/no-av1C.avifbin0 -> 281 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/no-ftyp.avifbin0 -> 262 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/no-hdlr.avifbin0 -> 294 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/no-ispe.avifbin0 -> 273 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/no-pitm.avifbin0 -> 280 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/no-pixi-for-alpha.avifbin0 -> 522 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/no-pixi.avifbin0 -> 317 bytes
-rw-r--r--third_party/rust/mp4parse/tests/corrupt/transformation-before-ispe.avifbin0 -> 338 bytes
-rw-r--r--third_party/rust/mp4parse/tests/hdlr-nonzero-reserved.avifbin0 -> 294 bytes
-rw-r--r--third_party/rust/mp4parse/tests/imir-missing-essential.avifbin0 -> 337 bytes
-rw-r--r--third_party/rust/mp4parse/tests/invalid-avif-hdlr-name-multiple-nul.avifbin0 -> 295 bytes
-rw-r--r--third_party/rust/mp4parse/tests/irot-missing-essential.avifbin0 -> 337 bytes
-rw-r--r--third_party/rust/mp4parse/tests/loop_forever.avifbin0 -> 2967 bytes
-rw-r--r--third_party/rust/mp4parse/tests/loop_none.avifbin0 -> 2967 bytes
-rw-r--r--third_party/rust/mp4parse/tests/lsel.avifbin0 -> 1999914 bytes
-rw-r--r--third_party/rust/mp4parse/tests/multiple-extents.avifbin0 -> 342 bytes
-rw-r--r--third_party/rust/mp4parse/tests/no-mif1.avifbin0 -> 334 bytes
-rw-r--r--third_party/rust/mp4parse/tests/overflow.rs15
-rw-r--r--third_party/rust/mp4parse/tests/public.rs1513
-rw-r--r--third_party/rust/mp4parse/tests/unknown_mdat.avifbin0 -> 294 bytes
-rw-r--r--third_party/rust/mp4parse/tests/unknown_mdat_in_oversized_meta.avifbin0 -> 2104 bytes
-rw-r--r--third_party/rust/mp4parse/tests/valid-alpha.avifbin0 -> 450 bytes
-rw-r--r--third_party/rust/mp4parse/tests/valid-avif-colr-nclx-and-prof-and-rICC.avifbin0 -> 1452 bytes
-rw-r--r--third_party/rust/mp4parse/tests/valid-avif-colr-nclx-and-prof.avifbin0 -> 883 bytes
-rw-r--r--third_party/rust/mp4parse/tests/valid-avif-colr-nclx-and-rICC.avifbin0 -> 883 bytes
-rw-r--r--third_party/rust/mp4parse/tests/valid-avif-colr-nclx.avifbin0 -> 314 bytes
-rw-r--r--third_party/rust/mp4parse/tests/valid-avif-colr-prof-and-rICC.avifbin0 -> 1432 bytes
-rw-r--r--third_party/rust/mp4parse/tests/valid-avif-colr-prof.avifbin0 -> 863 bytes
-rw-r--r--third_party/rust/mp4parse/tests/valid-avif-colr-rICC.avifbin0 -> 863 bytes
-rw-r--r--third_party/rust/mp4parse/tests/valid.avifbin0 -> 294 bytes
-rw-r--r--third_party/rust/mp4parse/tests/valid_with_garbage_byte.avifbin0 -> 374 bytes
-rw-r--r--third_party/rust/mp4parse/tests/valid_with_garbage_overread.avifbin0 -> 390 bytes
-rw-r--r--third_party/rust/mp4parse/tests/wide_box_size_0.avifbin0 -> 345 bytes
-rw-r--r--third_party/rust/mp4parse_capi/.cargo-checksum.json1
-rw-r--r--third_party/rust/mp4parse_capi/Cargo.toml54
-rw-r--r--third_party/rust/mp4parse_capi/LICENSE373
-rw-r--r--third_party/rust/mp4parse_capi/README.md2
-rw-r--r--third_party/rust/mp4parse_capi/cbindgen.toml45
-rw-r--r--third_party/rust/mp4parse_capi/examples/dump.rs200
-rw-r--r--third_party/rust/mp4parse_capi/src/lib.rs1863
-rw-r--r--third_party/rust/mp4parse_capi/tests/loop_1.avifbin0 -> 2967 bytes
-rw-r--r--third_party/rust/mp4parse_capi/tests/loop_2.avifbin0 -> 2967 bytes
-rw-r--r--third_party/rust/mp4parse_capi/tests/loop_ceiled_4.avifbin0 -> 2967 bytes
-rw-r--r--third_party/rust/mp4parse_capi/tests/loop_forever.avifbin0 -> 2967 bytes
-rw-r--r--third_party/rust/mp4parse_capi/tests/no_edts.avifbin0 -> 2052 bytes
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_avis.rs113
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs44
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_encryption.rs295
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_fragment.rs119
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_rotation.rs40
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_sample_table.rs278
-rw-r--r--third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs45
290 files changed, 16999 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..37e9633665
--- /dev/null
+++ b/third_party/rust/mp4parse/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"1e99c107a75c4579ea2099cad81f82fe9b942d3494fe8f2a6e1838d307f7c22a","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/fox.profile0.10bpc.yuv420.avif":"811af5e96631309030a14cbc30c3bacfaa667f2e36e16a4f30434b8f5a23310c","link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.avif":"e35713343e9ee04c51ab9cfdc99a0c7d126a1917cb83f5b9a23c71ed59269be2","link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.odd-height.avif":"485623f7838d8f442c47348c6492765e6aafbc3d5ccd8e90efc9c812ff15e265","link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.odd-width.avif":"8e75b9a8975267359d827e4cbc6877b1674aa31b87f88f222dde03263f9254df","link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.odd-width.odd-height.avif":"1fff5915a332d6aaa85d668f3f338bda6fad9c6ec1f380f2c46737d536cea5de","link-u-avif-sample-images/fox.profile0.10bpc.yuv420.odd-height.avif":"cca9785f14c74d865453518c0962dabd6d1b92d2c6c5f5ac67efdbf5606acb83","link-u-avif-sample-images/fox.profile0.10bpc.yuv420.odd-width.avif":"de3399d8b56431f0ed34e2f14200f31dd54544fddbb12f39b4d55449d5660c56","link-u-avif-sample-images/fox.profile0.10bpc.yuv420.odd-width.odd-height.avif":"07507744cb76b74a3586b93fc3b273a98f998d75f7687db3e9cb3725d7b1fb9d","link-u-avif-sample-images/fox.profile0.8bpc.yuv420.avif":"cb884c82ac7b6d4fa03b1f687e9e20abc346107095473e9c1d422aaf0de14eaf","link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.avif":"15d84077066c47fdbe2a7b8ed583a17017d09a033144ac1b31486d6c8f6f5c82","link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.odd-height.avif":"365439d2ee2fe5229e066362c03a73a182c7f6626772ecf5345b22752d32e681","link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.odd-width.avif":"c623b9943123d6c47d3300444f7255cbdfebfe2b47a670287f2baeb717fac42b","link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.odd-width.odd-height.avif":"dd069f3c3c4f7589f5f7ef1d7b6a91b8cb975d32663a4a92b6d75219edd72954","link-u-avif-sample-images/fox.profile0.8bpc.yuv420.odd-height.avif":"75628450288ace3386651725411c8f0ffff7eb95f82c5307b0faa3350f09f50e","link-u-avif-sample-images/fox.profile0.8bpc.yuv420.odd-width.avif":"f91b6f455412adabc5094011362eaaa1f6a9d5740de0b8a1be42a96c16e7617f","link-u-avif-sample-images/fox.profile0.8bpc.yuv420.odd-width.odd-height.avif":"bb8695cacacaf8f2e13a739de75e5e8a9d970d68c3acdfb7d82171a9bac2f01e","link-u-avif-sample-images/fox.profile1.10bpc.yuv444.avif":"a10de8204aee73ba1786daca6390546bd7aa6b069aaa644012219a1c11246a43","link-u-avif-sample-images/fox.profile1.10bpc.yuv444.odd-height.avif":"734a5badc59a8bed19e8eb476911048ddb011ca7c9eac31b7a7ed20e0135ac2c","link-u-avif-sample-images/fox.profile1.10bpc.yuv444.odd-width.avif":"68d15d76d95f268e810dfd87bbf96d8c3197580afce2d30b50bb1f07865077f3","link-u-avif-sample-images/fox.profile1.10bpc.yuv444.odd-width.odd-height.avif":"01e469b33bacb7e07e15e23b2997907bbcf02d8fcc99885a027494d31c45547c","link-u-avif-sample-images/fox.profile1.8bpc.yuv444.avif":"a0cdc981a6b056c8af2d177a1438c332d630040dacbfd1c89bb5e3e381ba5822","link-u-avif-sample-images/fox.profile1.8bpc.yuv444.odd-height.avif":"77e0510def73213c00ebcf051cf45fa63cf27d7c69340cc145ab6d44ec77bb07","link-u-avif-sample-images/fox.profile1.8bpc.yuv444.odd-width.avif":"12787042364bd13be01830f988cb714220bde340a3329baa808df27a269b83f8","link-u-avif-sample-images/fox.profile1.8bpc.yuv444.odd-width.odd-height.avif":"fad3b6dd9cb99e6925858f69aafae3f68c861845f2c3d4a6d1c51c6161490134","link-u-avif-sample-images/fox.profile2.10bpc.yuv422.avif":"e34f3bab5df802be2d422c685824464eb5f7e182b235ca99bde11c4c34ff3ac2","link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.avif":"05804752621e2fd7a9d85b01e2995a50e3efd30693efd934152560622d66e841","link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.odd-height.avif":"ef041f2efa0424d3a6dcfc5f0c3f619aea0e197aaeb5a050a2d877a457513921","link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.odd-width.avif":"b711b321b8a4d3a41e08f80a17c2203b839458cb6346aff6fff3f8d7699032e6","link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.odd-width.odd-height.avif":"e3f40aad0ddd6c04ffacf4a754a0525986c89d519119d37655282551e3ea3a59","link-u-avif-sample-images/fox.profile2.10bpc.yuv422.odd-height.avif":"4ef74c37712b41d6e5269d939f9d52031bc10746f7c8c029db618c5935aff9a6","link-u-avif-sample-images/fox.profile2.10bpc.yuv422.odd-width.avif":"4cf6c849f4a48a639217ad9a1e0d027f4fc804d524c03e6707ec9714a252532f","link-u-avif-sample-images/fox.profile2.10bpc.yuv422.odd-width.odd-height.avif":"3d3978e8e8d07270f82a877d3535f9483b88fce93a1c4accb658b494452b1d36","link-u-avif-sample-images/fox.profile2.12bpc.yuv420.avif":"626d73272e59722084c528b27b72a0920bec462a10749b3e87036e42bf8a24e5","link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.avif":"caf50c467242b3fa0b895a2af131fa0d03dd7b34989f2b1a51d25b2246c98fab","link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.odd-height.avif":"1e88b92df592c02af53ff5258fa898ac8fe1d71954e9a440500ef7879b8a1c46","link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.odd-width.avif":"466efdd269f3be3b1c88afc093c839e50ba5eaf6bd0c2425e157000d02a3c21a","link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.odd-width.odd-height.avif":"36014509d0688cb307e0ffcdb5b4c16ff6e6c79772c7fbbeea33b4d483a1566f","link-u-avif-sample-images/fox.profile2.12bpc.yuv420.odd-height.avif":"d1786723a5d6ea1e1764b0045dc3f669fe3905ffc2913011a2b1b424d2393397","link-u-avif-sample-images/fox.profile2.12bpc.yuv420.odd-width.avif":"a2d9178cc5622e28472a735650d9c4deaec6d27d69830110911f4158f9d4a04e","link-u-avif-sample-images/fox.profile2.12bpc.yuv420.odd-width.odd-height.avif":"89a9fc7598d72251b903e0c7088ab3e7a7f316b5e02b85f8c69e51f8eb0c7e69","link-u-avif-sample-images/fox.profile2.12bpc.yuv422.avif":"51476b8471e1c0a5ebbd1e7545709495bef619cc96c02d277aad32b1deff8ea9","link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.avif":"caf50c467242b3fa0b895a2af131fa0d03dd7b34989f2b1a51d25b2246c98fab","link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.odd-height.avif":"1e88b92df592c02af53ff5258fa898ac8fe1d71954e9a440500ef7879b8a1c46","link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.odd-width.avif":"466efdd269f3be3b1c88afc093c839e50ba5eaf6bd0c2425e157000d02a3c21a","link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.odd-width.odd-height.avif":"36014509d0688cb307e0ffcdb5b4c16ff6e6c79772c7fbbeea33b4d483a1566f","link-u-avif-sample-images/fox.profile2.12bpc.yuv422.odd-height.avif":"4808960cdcd1207b29eb6ef5b4db81ae5e63c5d40ef92f7a1e8f4430e765bde4","link-u-avif-sample-images/fox.profile2.12bpc.yuv422.odd-width.avif":"71f3c73737bc04c23d93ea2c61a8a07408f604192fe53fe12c65050927dacb4e","link-u-avif-sample-images/fox.profile2.12bpc.yuv422.odd-width.odd-height.avif":"8f9649384fa09c70af0c2e9e0d94ecd17492c702e015d9d6290d3aefc57c548c","link-u-avif-sample-images/fox.profile2.12bpc.yuv444.avif":"ed96eca6ed79863eaf91e4d666e4e220b5fa4e5a6cb1696477ba901ac12f5dde","link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.avif":"caf50c467242b3fa0b895a2af131fa0d03dd7b34989f2b1a51d25b2246c98fab","link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.odd-height.avif":"1e88b92df592c02af53ff5258fa898ac8fe1d71954e9a440500ef7879b8a1c46","link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.odd-width.avif":"466efdd269f3be3b1c88afc093c839e50ba5eaf6bd0c2425e157000d02a3c21a","link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.odd-width.odd-height.avif":"36014509d0688cb307e0ffcdb5b4c16ff6e6c79772c7fbbeea33b4d483a1566f","link-u-avif-sample-images/fox.profile2.12bpc.yuv444.odd-height.avif":"c43b4c607e301e34eb953770133d89d15e77d63be8d6421a80d6212fbbbf3453","link-u-avif-sample-images/fox.profile2.12bpc.yuv444.odd-width.avif":"0e6218ce250ee7f84a621f3af73286fd4dabea19e9898d3575c20c5955aa427d","link-u-avif-sample-images/fox.profile2.12bpc.yuv444.odd-width.odd-height.avif":"9f19846d884ac7d161ac2ca15811bb22ff7f3847bc1ad5d7713971ea024631aa","link-u-avif-sample-images/fox.profile2.8bpc.yuv422.avif":"2cb363d30f83bff58ee049874b1808b37cb1d35342edf16b3ce25cb243c9ea55","link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.avif":"8217b88f350c5d3812d7f863fe9d710c4c1b846b0be8df29ce6b3eb30d2b8d8f","link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.odd-height.avif":"51eccd8c1368ddec9bc1f3a7631dadb00682e985fbfea66c0a2f533f6a73b67e","link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.odd-width.avif":"83fe4593ab839cf296a2ccb8a146daf826d1d602f4f239cb63ee020f2cc326f6","link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.odd-width.odd-height.avif":"4e63b84a980a81adde586314a94d8f834ec763749d8797c286471415b6b75647","link-u-avif-sample-images/fox.profile2.8bpc.yuv422.odd-height.avif":"efc70882aacbb533c0e833a4401949d152dceb364846442cdccca5048ad17a60","link-u-avif-sample-images/fox.profile2.8bpc.yuv422.odd-width.avif":"c07575c88ef400c1725c9260a19439e0e784da41c7db3867059019ddbdb3bebe","link-u-avif-sample-images/fox.profile2.8bpc.yuv422.odd-width.odd-height.avif":"86aee64fd7b11b9834537ea14b2eff234c062c98d32fab51ff14aba262d5b106","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/hato.profile0.10bpc.yuv420.avif":"1e84598c94bc795c55a551bdf6f283b4eed4997bda15e5c4bb2e29d0dc70897b","link-u-avif-sample-images/hato.profile0.10bpc.yuv420.monochrome.avif":"25c19a187d10eac963bb51b3fae1ffe50f3ccbc7ad02e51948dc7467b397ce61","link-u-avif-sample-images/hato.profile0.8bpc.yuv420.avif":"07cd454de19dd638354f75d9e97aab08bc8a04dd45c4f7531cb62a1a5656c8c9","link-u-avif-sample-images/hato.profile0.8bpc.yuv420.monochrome.avif":"120a3f26f3d6cde80869fb52bdb8fdd55b443882c98070ea938575e4790982ff","link-u-avif-sample-images/hato.profile2.10bpc.yuv422.avif":"d54251d4bc023f2e53624a46cb18e56cbc45768bd1bd50394de191c2a42106e8","link-u-avif-sample-images/hato.profile2.10bpc.yuv422.monochrome.avif":"ed33dfdb5e663b3394298b6921f9b19b129e14788dead776430eee2f14780a73","link-u-avif-sample-images/hato.profile2.12bpc.yuv422.avif":"ad361ac7d94fbc6af7ef30cbd3601ff366bc360c304480387a58a4c6fecee9b6","link-u-avif-sample-images/hato.profile2.12bpc.yuv422.monochrome.avif":"e28b4cacda95750e465e205fbfcba6a6af1d8418dac649838730c555ff7d828f","link-u-avif-sample-images/hato.profile2.8bpc.yuv422.avif":"69c353c482c2eb3d3671bb55f7ccfc932e4d781c714a72116e9138ffc6f6c720","link-u-avif-sample-images/hato.profile2.8bpc.yuv422.monochrome.avif":"e7d6f7d42a8519e1482f225fe447b5642d19a54ee830529223eac114a0dec189","link-u-avif-sample-images/images.html":"9e18453dfe5b205600f158282c6896265281e3b04b2fbc332804fab1dbdb3faf","link-u-avif-sample-images/kimono.avif":"63412e0f67f37c8b6fcf0e8269a2afae0a017fa6a3a99d37d055c590b0be52d3","link-u-avif-sample-images/kimono.crop.avif":"f175dcd9c64813b759da185fa67076fb772b76059845b2aad3ddcfab257f75ad","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.avif":"2bbc004d91145488610158a5acdb4d706495a2b15511db20ff57bb9efd80885c","link-u-avif-sample-images/kimono.mirror-horizontal.png":"9af9e839fe6bf6342831970c20291f619570d2fc687951ae00cd81ea766f53fe","link-u-avif-sample-images/kimono.mirror-vertical.avif":"f10eb04791fcca3409868b367128649f32e6b6fffcf02484cdefa57909f6bb74","link-u-avif-sample-images/kimono.mirror-vertical.png":"4ed003c5868fd2e78c7b2dcbd54a67a0e7593dabb3ac82b1c9e5e2dbdf09b8ec","link-u-avif-sample-images/kimono.mirror-vertical.rotate270.avif":"33c36ec2274b00ac6f81c9f61e55c20cbfce1649ad27520afe635310f516ead1","link-u-avif-sample-images/kimono.mirror-vertical.rotate270.crop.avif":"90ca48f657455b8f8e4a4e1769a05af90a3e34dca11a8b3c32552daf378ee956","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.avif":"79a99a0415276cc11f2e871d070a9df84df3385888a2f2fa3534320f6bed98ed","link-u-avif-sample-images/kimono.rotate270.png":"1918a47c02b378945a705301abd4250ddc65bb95afce9424572ffd0fdd1f45ef","link-u-avif-sample-images/kimono.rotate90.avif":"bd1157d8c840713c82b907b9d3ae80bc3817849e11c323d875f8016e035bd3cc","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-large.profile0.10bpc.yuv420.alpha-full.avif":"cf8e15ec4b210235f3d68332a1adeb64e35c41b8d8e1e7586ae38b6d9cd8926c","link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-full.monochrome.avif":"b413440309d5669a7aefa06f291f72de0d0c09972e3da9a385e6016d6a1c5562","link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.avif":"64f6f2b1fda594af5c9e2d6e4bc752b55e8121000cdcedc0066018e53f76de40","link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.monochrome.avif":"2b175a600aed64c1c1de4b2d661fb405437b1ff000b964d4be6ac437ce73136f","link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-full.avif":"21ebb3732186bf7c6c13cf7197155b64201e674b9c79cf613b6e5718bde14c2a","link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-full.monochrome.avif":"65917797e511c9033b3e225eb5d84f3c0440b7a496d4b8ab5674e123ad68aab7","link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.avif":"f4809df9188fa46ed100f63c78c4cf42559d90a98351a8f69e177385920672b4","link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.monochrome.avif":"dffc213dc36f0b5ff3d86254f5185c2d1fb03380918a8fa39d186fb59cbba7cc","link-u-avif-sample-images/plum-blossom-large.profile1.10bpc.yuv444.alpha-full.avif":"0a615cfb673ab45e37da3582c17dd36f86d5da3d81246a32951d1db4ed90149d","link-u-avif-sample-images/plum-blossom-large.profile1.10bpc.yuv444.alpha-limited.avif":"d1cc6a0db116a68e229676b289e8ced84d42a9f0e8d3ccd760fc92dbeb932547","link-u-avif-sample-images/plum-blossom-large.profile1.8bpc.yuv444.alpha-full.avif":"8626cf0b2c60fee51cf1df7fe4d8a18081b72e3604f7d3ff14cc5a35581927d9","link-u-avif-sample-images/plum-blossom-large.profile1.8bpc.yuv444.alpha-limited.avif":"56bf18e4bcbfbea460c306ab900fc506169854d2b91690e1a48156a86fff3264","link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-full.avif":"b56f3c33797e4c5bb80418ae25f6f2c6fc88ddc1f6965d82abc44059f052f36c","link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-full.monochrome.avif":"c3c6db4ce801c68e2afd7bfb72abacc13287cc2e247917a817ff5632bd4b18fb","link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.avif":"095f57fff1dc037a6414c60263415dcfa86b10d81f58e8e4c3aef17cffae0e5f","link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.monochrome.avif":"ba3035a69296d12f47d1c450f2de8cc0c9059ebbf27bb8b0996ff6d1ab1132a1","link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-full.avif":"a9551e1963049b874216b40aaf9be45bc44dc11887770712b77240862c6c08a9","link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-full.monochrome.avif":"a8a0ba881cd0aef45a5874f412e97812ca457eaf12e56b34bac2ba051e142828","link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.avif":"6891b5bdbed7f541c673c66cec4584b191bb737a49b37cca16ae069830890b42","link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.monochrome.avif":"67e66826637e349e1c3c46316c7048d152aed8453d8cdd00c6b786f87fe176d8","link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-full.avif":"a466503110f60dcadaafe78c2cb0a8ca58334c22868efe428c7ac12b69407c95","link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-full.monochrome.avif":"a8a0ba881cd0aef45a5874f412e97812ca457eaf12e56b34bac2ba051e142828","link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.avif":"061b4c70707e7fc8866580656cca7ca2da2263645829a564dbf8f205d639f058","link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.monochrome.avif":"67e66826637e349e1c3c46316c7048d152aed8453d8cdd00c6b786f87fe176d8","link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-full.avif":"4a03c8bcfcaadccdeb07432ed80680b06aba5ae5e616fc370b376acb478f6cd1","link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-full.monochrome.avif":"a8a0ba881cd0aef45a5874f412e97812ca457eaf12e56b34bac2ba051e142828","link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.avif":"376625ce4e7ec3b57344a9f2185a34975fe380774c9f8e0dd4ef61c46da94fca","link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.monochrome.avif":"67e66826637e349e1c3c46316c7048d152aed8453d8cdd00c6b786f87fe176d8","link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-full.avif":"1e17b7dd8e351cb667bc6ec0b81fdebca9fd30ad5e588b3eac57bf9f90a9b58b","link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-full.monochrome.avif":"f77588d2ff39e4461869a95ca236efdf4a3fdc17b8a6e3907e63b417e56c81b5","link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.avif":"f2eabd3fc2db07b361d55b5af63023d28b5bdd0047a665e9d0685651ce9db94b","link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.monochrome.avif":"dd88c377b253d689bfd4600d7c3007448df0ed92f679b661d5d51e051a6916d8","link-u-avif-sample-images/plum-blossom-small.png":"c859fd97b647e494461f65835b9c1c3476807aee77076599adf18a832b3617a4","link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-full.avif":"fe3379620f983e5da4210b0f7c5d164e86208b34106d6b11d56e2b52beee5dfe","link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-full.monochrome.avif":"9e6234686053dba2e44bb32192c641074095a61e45aa1bede85bb83751e6e157","link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.avif":"60ffa59dd007aea3bb596cfb03853b3dad490b1ffd10471ad4fe1901ec819907","link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.monochrome.avif":"58abfa027525aca7339a705f311bc30b000aef9822c7c50b232894ece1ba9cb1","link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-full.avif":"541ca66a11b1430d11f77b74a13f859239a3bd9f44976b8dfe163c5d7595a69f","link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-full.monochrome.avif":"6d7852c63c27a77bef94443e178ecf6b6a3da8cc846f9f0c1adc17f7fc35e1f3","link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.avif":"3712216ffc7edb95a976b68469ead533fda6fe9f3d2e8cb09cbdb0d01d9c094b","link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.monochrome.avif":"59672af536b169ebf7e48129167dc91cb253925517c37db28b64539f7afba5d3","link-u-avif-sample-images/plum-blossom-small.profile1.10bpc.yuv444.alpha-full.avif":"561d5158425ad39425916a3be048b0202e82efef44cf1a9f711db57192d2ff01","link-u-avif-sample-images/plum-blossom-small.profile1.10bpc.yuv444.alpha-limited.avif":"88336f29457c1dc4ae0b754ef14b791de4d8c337f2b44d7960aafcca08a5586e","link-u-avif-sample-images/plum-blossom-small.profile1.8bpc.yuv444.alpha-full.avif":"245a3dad6371dc702f29eb7e9735f843b63c525da871859728bedbe5bb274985","link-u-avif-sample-images/plum-blossom-small.profile1.8bpc.yuv444.alpha-limited.avif":"490c216b5850c670f6088ee72c7fd906102b2dd0c8432c01a517b3328db27c75","link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-full.avif":"0d0bbbcb0c9ade1c827317a6409ce6ba25027b36da1b6752379a5f5d4c8af056","link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-full.monochrome.avif":"418a9d4b7dcbc1f67fc4b95ccb2e7946c446fddd35a79f0d587b3f2165cb3f14","link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.avif":"a41dcfabb3795ed93a05881a91bf4d1c5d1b9b0e5d0db728f12684b12613e0c9","link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.monochrome.avif":"4b6a18f4f608d1b00598e67c1c91a4e8f4943ae5acf0166ae0121cc21f3b7437","link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-full.avif":"5e6b83adee3aeac4bba3f38e854e2ed7585aa1557a74b5fa66596098c53e26f2","link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-full.monochrome.avif":"fb0937564c08f4e1c22ebbde426a0eff6a1d388434c30148bc7e5582dc378caa","link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.avif":"f42e780bff644167e27525fee65b23e70814f7665ea19b6d693eb127c5eeebd8","link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.monochrome.avif":"d41ead1d69d33d0cf5f6fe575beb5f60d9565300c535f9042113dd8a748c1d31","link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-full.avif":"c6ee9234ee7783d0203f9512e3cc8fe76a694675bcf03c320ff8bab4684bd4e9","link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-full.monochrome.avif":"fb0937564c08f4e1c22ebbde426a0eff6a1d388434c30148bc7e5582dc378caa","link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.avif":"21e1e51fc6da66e090e12e1df5aeabf1f478df64bcb8151caaa687ad262bdd9d","link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.monochrome.avif":"d41ead1d69d33d0cf5f6fe575beb5f60d9565300c535f9042113dd8a748c1d31","link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-full.avif":"dfed73d646d7159d3ee4f9ce95ea628fdd33bf8bf1bb02a83d88302d088f4d97","link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-full.monochrome.avif":"fb0937564c08f4e1c22ebbde426a0eff6a1d388434c30148bc7e5582dc378caa","link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.avif":"5539cd575256772ba31d6e87cb0136d5f465f84b041a4677b1023cfd6bee9956","link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.monochrome.avif":"d41ead1d69d33d0cf5f6fe575beb5f60d9565300c535f9042113dd8a748c1d31","link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-full.avif":"f205cf2c05265b84e45ceb723e9a3707fd8749130166d4797e64c8c62b497f8c","link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-full.monochrome.avif":"ad2244cb7b2ac487a5104d6e5a07270f9ceb2550e4d56d3662fcc1868910ced5","link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.avif":"7ef9468effdc970e4e6e8cc7533d66dc967686e357dda3dda186c021f9e6156b","link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.monochrome.avif":"3dd34d854ff83b6e27a2bf98301380a6cac272084ed863735285ab5dc8acda00","link-u-avif-sample-images/plum-blossom.svg":"be1f03dd05f63292c85a96b1c48fb06727283610cc69b1e116d547bab27b171d","link-u-avif-sample-images/red-at-12-oclock-with-color-profile-10bpc.avif":"5842951d81118d256962384e08a986816e8ade6b05530269f0208c6b69cedb3b","link-u-avif-sample-images/red-at-12-oclock-with-color-profile-12bpc.avif":"1f0c9f36d69b9aa13eff3897ada3e78b81099c613b329a402c27e09453e7e261","link-u-avif-sample-images/red-at-12-oclock-with-color-profile-8bpc.avif":"3e6f2f4016e66e3c94707eaa8373e6f582321e005964cd35b64bc183e1bf10ea","link-u-avif-sample-images/red-at-12-oclock-with-color-profile-lossy.avif":"79483242f2dca12c4ec18bd33ff8099216b3094fb55a26a909f046b2f9b4ce58","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":"39e979bd0b08fe6a9f43e092984879fc19185a48284e9ff868e5d47cf8df4363","src/lib.rs":"b45d3e3c3e2ede2f5d55c4625994dd09dca8f06f618344d478a6739181ef2e45","src/macros.rs":"498bef25c8ea468d8ae0cd89593807d9e9253385cb9456d04f8cb00b721a54cb","src/tests.rs":"13c9c7082473aa8f2267b2d01fb3dfb6a36fe8d03b8f47536922e47553ce683e","src/unstable.rs":"05866e5c79df5d27dd48226dd4558089f9b853ca66245cf429fed8cd0e7244ad","tests/1x1-black-alpha-50pct-premultiplied.avif":"31a8c235bf2cf601a593a7bc33f7f2779f2d5b2e0cd145897b931fce94b0c0b8","tests/a1lx.avif":"81a784c568e7619bd47c4e7153181e9a86c8a03221097aa63979f97affd7efbb","tests/a1op.avif":"184e50efd3ad226800c610da7bc470d29b548d9fa942fd1fd1848ed02e2e5f0a","tests/alpha_video_avif_major_avis_compatible.avif":"81771981248c4e0d708f0828eadb20e9e37e7c21280efe5726666958f899c237","tests/alpha_video_fixed.avif":"67051d1a4ccf9f3e38d4514d8a6b3a2e07f1beb979369cabdc60881a698a36ac","tests/alpha_video_no_avis.avif":"a95f0e7c818bc5bf646143cf43474b70722e17669d4f2a32874c125936207d6a","tests/amr_nb_1f.3gp":"d1423e3414ad06b69f8b58d5c916ec353ba2d0402d99dec9f1c88acc33b6a127","tests/amr_wb_1f.3gp":"be635b24097e8757b0c04d70ab28e00417ca113e86108b6c269b79b64b89bcd5","tests/av1C-missing-essential.avif":"a1501254c4071847b2269fe40b81409c389ff14e91cf7c0005a47e6ea97a6803","tests/avis_with_no_ptim_no_iloc.avif":"e1b564ea254139bbdedd164f4a0037b335627fdf4a5df1e0b08290048b87b597","tests/avis_with_pitm_no_iloc.avif":"11030c89147b8066db167c71f0122d04ce3d6c56fa8a7d204a11d8ea3d961937","tests/bad-ipma-flags.avif":"ecde7997b97db1910b9dcc7ca8e3c8957da0e83681ea9008c66dc9f12b78ad19","tests/bad-ipma-version.avif":"7f9a1a0b4ebbf8d800d22eaae5ff78970cc6b811317db6c1467c6883952b7c9b","tests/bbb_sunflower_QCIF_30fps_h263_noaudio_1f.3gp":"03e5b1264d0a188d77b9e676ba3ce23a801b17aaa11c0343dfd851d6ea4e3a40","tests/clap-basic-1_3x3-to-1x1.avif":"83af9c8196fa93b2475163585a23d0eb5a8f8015d0db8da7a5d6de61adfb1876","tests/clap-missing-essential.avif":"4d61aacd2327661a456abc76d3d490e5ddd2b6f8cbfa52922a6c541c9d983193","tests/clusterfuzz-testcase-minimized-mp4-6093954524250112":"af7044a470732d4e7e34ac7ab5ff038c58b66f09702cbcd774931d7766bbfd35","tests/corrupt/a1lx-marked-essential.avif":"0d481240222450827ea335ae9a1a300777a0db4f9b0f4c17ed77c758c5133fa3","tests/corrupt/a1op-missing-essential.avif":"33c24d54f43f1a7be7a8334718881c8a0de24730c997c7842b9d7140e75017ea","tests/corrupt/alpha_video_moov_is_moop.avif":"89e0091edd6efc2c5b163525553c5abc56263fe1b3b885184bb07b9ea4bf346d","tests/corrupt/bug-1655846.avif":"e0a5a06225800fadf05f5352503a4cec11af73eef705c43b4acab5f4a99dea50","tests/corrupt/bug-1661347.avif":"31c26561e1d9eafb60f7c5968b82a0859d203d73f17f26b29276256acee12966","tests/corrupt/clusterfuzz-testcase-minimized-avif-4914209301856256.avif":"34a142c7916e314881f3fb6394add1c543fac0e5b45109e3a425eeb4c68998d0","tests/corrupt/hdlr-not-first.avif":"2c29308af077209b9c984921b7e36f8fb7ca7cf379cf8eba4c7a91f65bc7a304","tests/corrupt/hdlr-not-pict.avif":"9fe37619606645a95725300a9e34fada9190d1e0b3919881db84353941ca9291","tests/corrupt/imir-before-clap.avif":"22d6b5dacf0ef0be59053beba7564b08037fed859ada2885e3476e0ff0d19c95","tests/corrupt/invalid-avif-colr-multiple-nclx.avif":"7990a995855120dc4f724a6098816595becc35077fcd9e0de8c68300b49c4f1f","tests/corrupt/invalid-avif-colr-multiple-prof.avif":"b077a6b58e3a13ad743ee3f19fbae53b521eab8727606e0dba9bf06384f3121c","tests/corrupt/invalid-avif-colr-multiple-rICC.avif":"88b24d4d588744b9f2cdc03944f28283e9315eb3de7d7d57773a0541137f6529","tests/corrupt/invalid-avif-colr-multiple.zip":"9abddcbc47fde6da20263a29b770c6a9e76c8ab8dc785ef8512f35d9cb3206ed","tests/corrupt/invalid-transformation-order.avif":"22d6b5dacf0ef0be59053beba7564b08037fed859ada2885e3476e0ff0d19c95","tests/corrupt/ipma-duplicate-item_id.avif":"ca8c5275b0b8b79c1068489a52d0a5c8f0b4453463971e72b694189f11c10745","tests/corrupt/ipma-duplicate-version-and-flags.avif":"cf8e15ec4b210235f3d68332a1adeb64e35c41b8d8e1e7586ae38b6d9cd8926c","tests/corrupt/ipma-invalid-property-index.avif":"2480e773fa716d22883032d05fd4cf2c6b00fba8796cf4ff286a5d1ba26adff6","tests/corrupt/lsel-missing-essential.avif":"43c3b1e4c4acecd7559a9a7197a7befd43c71705748f0f8c063bca3be1c6d074","tests/corrupt/no-alpha-av1C.avif":"ad3d34d6331db7d9bea0c5f37efb88923520e33e08e7c636a5df435a4575eae7","tests/corrupt/no-alpha-ispe.avif":"94805fe40e0f5a3d9e5e98050020d4ffe62e5c703ece2867bc72bb1cdd775f77","tests/corrupt/no-av1C.avif":"eeb4fc50930c91465999f787c4a2a3b12de20556da0857be72da5a1a9eaa3f01","tests/corrupt/no-ftyp.avif":"74b431f32b2e2761e77df7fdb260f623b4e8f7e3f4c8af8a42d6826911706d7b","tests/corrupt/no-hdlr.avif":"91a1eb70c7b6adf2104e471d7deeeb98084a591d64ce09ba106c27edfbc3a409","tests/corrupt/no-ispe.avif":"4b6edfd8c9b40c25dc40305a6057e32b5e65f40da4a9d810c58dbff53254113f","tests/corrupt/no-pitm.avif":"7960eeb9e6e5140fbe5eb6d281e6974efd6c3c0147562f3dcf06f6b009dc540a","tests/corrupt/no-pixi-for-alpha.avif":"f8adc3573c79ee25bf6d4dd2693c61661469b28f86a5c7b1d9e41b0e8d2d53bb","tests/corrupt/no-pixi.avif":"4b1776def440dc8b913c170e4479772ee6bbb299b8679f7c564704bd03c9597e","tests/corrupt/transformation-before-ispe.avif":"7ac59dd68a4caa54c0cde9af0d57ff7832baada74d39174b50fbe0275a59a049","tests/hdlr-nonzero-reserved.avif":"b872dcd7b4f49c6808d6da109cf4fedc26a237c42e8529c5aa8f7130abaf40a9","tests/imir-missing-essential.avif":"6a5dfd4a9cc6dac96fbcaa759b976ec7246120455ab59f970a97606737bf31e9","tests/invalid-avif-hdlr-name-multiple-nul.avif":"0d3e4a6ce42154ee288c18339c2b59ff2104fd890cd2d616e5dbf26375a90e98","tests/irot-missing-essential.avif":"494589024dd277788b4de60b76352947b4fe7fe62bbe7ea6a47885ab126a8a2f","tests/loop_forever.avif":"aa425b4da99646d3b71d63f6b1741b60a2331ee2c9ab3bcf2fbc05b0cc832565","tests/loop_none.avif":"6c9ccfa16bff19ebb95c94b6948ea2c4fdeb4784408b961f541998a55edbf402","tests/lsel.avif":"ef8ba6827709f48cd45f749efb580129162d9599ea98f3363d2140957502d806","tests/multiple-extents.avif":"b5549ac68793e155a726d754e565cea0da03fa17833d3545f45c79e13f4c9360","tests/no-mif1.avif":"1442aa6ffaeb9512724287768bfd1850d3aa29a651ef05abb33e5dec2b3ee5c2","tests/overflow.rs":"16b591d8def1a155b3b997622f6ea255536870d99c3d8f97c51755b77a50de3c","tests/public.rs":"dc98879099963fd04ba00b1639000bcf3a9cc70d719f1a0ec0071cf3e5fb56b7","tests/unknown_mdat.avif":"d3e7be873bb342b4cf2046201dcc4fa6aa649dfead6306dcb55949d5ee00cc8e","tests/unknown_mdat_in_oversized_meta.avif":"e66fa5d6bd5f685cd6fa4fbe3c9b3308b4b5dc83e3fbf2adb3bc63f67cc9b7d6","tests/valid-alpha.avif":"9d417a35b9b62ad3ff66ffbc55f16552aacf821a092aa5ef4adff7e746bd4c2f","tests/valid-avif-colr-nclx-and-prof-and-rICC.avif":"ab6f5e786d26f8bcade5993f8b9cca3cd004a3d7fcec76e829f5d0f98cb18e7b","tests/valid-avif-colr-nclx-and-prof.avif":"0e982818de61869fcb85a2a4c2b7b8aeecb3053cbfdc6276987f91204998eefb","tests/valid-avif-colr-nclx-and-rICC.avif":"8530ef1305ff956a0c2912d0b3d1e0fc3a68cf3103e70b04cc2574530389b030","tests/valid-avif-colr-nclx.avif":"345ab58b7b1cb48aba2e21eb8dc5ab0a751a78a752ce1896c59b4bf361992f38","tests/valid-avif-colr-prof-and-rICC.avif":"1f0f085141106885bda78b0879c768818420d8196b39440a36578456a7d50a6c","tests/valid-avif-colr-prof.avif":"5d7aaefb5204ebe1cc296456866b8e46e023748b921a38ee56fd6c776a9733ff","tests/valid-avif-colr-rICC.avif":"e1c7b49bfad5904b484bd5118e6b33b78e2dc708a31a10fcbb0e4a373ed8dbb7","tests/valid.avif":"f0b33e09bf01232e0877df325f47986c0bee7764f2a81c9c908ae109e7dc63c4","tests/valid_with_garbage_byte.avif":"6cb72d8fa8837d2ac38c62aa6c8d57d8d2a571fda5bccc84ce522631dbe3f8ec","tests/valid_with_garbage_overread.avif":"022c1d67a9b4db4fd757d06c0f7de0a29abc78aafba799021bb4f98fd879eeda","tests/wide_box_size_0.avif":"0f7875d5ade1ae9293cbfeb2b98f2076808c8038d7c69bbb539081fe5e1e4dfa"},"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..f7a5c959cc
--- /dev/null
+++ b/third_party/rust/mp4parse/Cargo.toml
@@ -0,0 +1,67 @@
+# 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",
+ "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
new file mode 100644
index 0000000000..748f2786b5
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.jpg
Binary files differ
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
new file mode 100644
index 0000000000..831ab7529a
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.odd-height.png
Binary files differ
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
new file mode 100644
index 0000000000..c858205691
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.odd-width.odd-height.png
Binary files differ
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
new file mode 100644
index 0000000000..f5fe9fce9b
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.odd-width.png
Binary files differ
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
new file mode 100644
index 0000000000..a1e59a2a11
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.png
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.avif
new file mode 100644
index 0000000000..a1c0abd7aa
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.avif
new file mode 100644
index 0000000000..d5febaa151
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.odd-height.avif
new file mode 100644
index 0000000000..1ab92318e3
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.odd-width.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.odd-width.avif
new file mode 100644
index 0000000000..5f3828cb52
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.odd-width.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.odd-width.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.odd-width.odd-height.avif
new file mode 100644
index 0000000000..cc48e0bed3
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.monochrome.odd-width.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.odd-height.avif
new file mode 100644
index 0000000000..3ab37403e9
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.odd-width.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.odd-width.avif
new file mode 100644
index 0000000000..d1b2555542
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.odd-width.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.odd-width.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.odd-width.odd-height.avif
new file mode 100644
index 0000000000..94cca20074
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.10bpc.yuv420.odd-width.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.avif
new file mode 100644
index 0000000000..2bae4c7131
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.avif
new file mode 100644
index 0000000000..a9b7fe9d1a
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.odd-height.avif
new file mode 100644
index 0000000000..b69c3231e1
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.odd-width.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.odd-width.avif
new file mode 100644
index 0000000000..3a8c04b844
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.odd-width.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.odd-width.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.odd-width.odd-height.avif
new file mode 100644
index 0000000000..5bfc8e7ce8
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.monochrome.odd-width.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.odd-height.avif
new file mode 100644
index 0000000000..fc2d1f5749
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.odd-width.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.odd-width.avif
new file mode 100644
index 0000000000..927d7f677b
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.odd-width.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.odd-width.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.odd-width.odd-height.avif
new file mode 100644
index 0000000000..5edb77e51d
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile0.8bpc.yuv420.odd-width.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.10bpc.yuv444.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.10bpc.yuv444.avif
new file mode 100644
index 0000000000..755463c6d4
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.10bpc.yuv444.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.10bpc.yuv444.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.10bpc.yuv444.odd-height.avif
new file mode 100644
index 0000000000..888d2ce227
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.10bpc.yuv444.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.10bpc.yuv444.odd-width.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.10bpc.yuv444.odd-width.avif
new file mode 100644
index 0000000000..27c3aa3397
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.10bpc.yuv444.odd-width.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.10bpc.yuv444.odd-width.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.10bpc.yuv444.odd-width.odd-height.avif
new file mode 100644
index 0000000000..a2826556e8
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.10bpc.yuv444.odd-width.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.8bpc.yuv444.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.8bpc.yuv444.avif
new file mode 100644
index 0000000000..b6cec528e1
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.8bpc.yuv444.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.8bpc.yuv444.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.8bpc.yuv444.odd-height.avif
new file mode 100644
index 0000000000..22e25a25bd
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.8bpc.yuv444.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.8bpc.yuv444.odd-width.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.8bpc.yuv444.odd-width.avif
new file mode 100644
index 0000000000..bb6c184be2
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.8bpc.yuv444.odd-width.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.8bpc.yuv444.odd-width.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.8bpc.yuv444.odd-width.odd-height.avif
new file mode 100644
index 0000000000..353fed4712
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile1.8bpc.yuv444.odd-width.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.avif
new file mode 100644
index 0000000000..04d460ca7f
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.avif
new file mode 100644
index 0000000000..85e565af18
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.odd-height.avif
new file mode 100644
index 0000000000..639be9af0a
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.odd-width.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.odd-width.avif
new file mode 100644
index 0000000000..6536a7c4e7
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.odd-width.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.odd-width.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.odd-width.odd-height.avif
new file mode 100644
index 0000000000..b4439bbbc1
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.monochrome.odd-width.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.odd-height.avif
new file mode 100644
index 0000000000..476162e8f9
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.odd-width.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.odd-width.avif
new file mode 100644
index 0000000000..1818973475
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.odd-width.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.odd-width.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.odd-width.odd-height.avif
new file mode 100644
index 0000000000..92c9cc93fe
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.10bpc.yuv422.odd-width.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.avif
new file mode 100644
index 0000000000..c1d4f7171e
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.avif
new file mode 100644
index 0000000000..2c8975019b
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.odd-height.avif
new file mode 100644
index 0000000000..fbca49f58b
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.odd-width.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.odd-width.avif
new file mode 100644
index 0000000000..7d9407e397
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.odd-width.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.odd-width.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.odd-width.odd-height.avif
new file mode 100644
index 0000000000..bc6497683a
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.monochrome.odd-width.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.odd-height.avif
new file mode 100644
index 0000000000..476346ba3e
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.odd-width.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.odd-width.avif
new file mode 100644
index 0000000000..2ebd4e881b
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.odd-width.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.odd-width.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.odd-width.odd-height.avif
new file mode 100644
index 0000000000..72606405e1
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv420.odd-width.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.avif
new file mode 100644
index 0000000000..de1d6d6c21
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.avif
new file mode 100644
index 0000000000..2c8975019b
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.odd-height.avif
new file mode 100644
index 0000000000..fbca49f58b
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.odd-width.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.odd-width.avif
new file mode 100644
index 0000000000..7d9407e397
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.odd-width.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.odd-width.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.odd-width.odd-height.avif
new file mode 100644
index 0000000000..bc6497683a
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.monochrome.odd-width.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.odd-height.avif
new file mode 100644
index 0000000000..99536832cc
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.odd-width.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.odd-width.avif
new file mode 100644
index 0000000000..cda3cc7f63
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.odd-width.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.odd-width.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.odd-width.odd-height.avif
new file mode 100644
index 0000000000..36a585969b
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv422.odd-width.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.avif
new file mode 100644
index 0000000000..134a3b8ae5
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.avif
new file mode 100644
index 0000000000..2c8975019b
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.odd-height.avif
new file mode 100644
index 0000000000..fbca49f58b
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.odd-width.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.odd-width.avif
new file mode 100644
index 0000000000..7d9407e397
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.odd-width.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.odd-width.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.odd-width.odd-height.avif
new file mode 100644
index 0000000000..bc6497683a
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.monochrome.odd-width.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.odd-height.avif
new file mode 100644
index 0000000000..8289a38e4a
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.odd-width.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.odd-width.avif
new file mode 100644
index 0000000000..31fd3f8d5e
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.odd-width.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.odd-width.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.odd-width.odd-height.avif
new file mode 100644
index 0000000000..bfc513dcfb
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.12bpc.yuv444.odd-width.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.avif
new file mode 100644
index 0000000000..f5ea954d84
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.avif
new file mode 100644
index 0000000000..43d9292b05
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.odd-height.avif
new file mode 100644
index 0000000000..c1e5e41ada
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.odd-width.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.odd-width.avif
new file mode 100644
index 0000000000..669a400204
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.odd-width.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.odd-width.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.odd-width.odd-height.avif
new file mode 100644
index 0000000000..e4953714da
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.monochrome.odd-width.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.odd-height.avif
new file mode 100644
index 0000000000..26a0b2a43c
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.odd-height.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.odd-width.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.odd-width.avif
new file mode 100644
index 0000000000..27d6d6ef03
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.odd-width.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.odd-width.odd-height.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.odd-width.odd-height.avif
new file mode 100644
index 0000000000..7668b3515e
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/fox.profile2.8bpc.yuv422.odd-width.odd-height.avif
Binary files differ
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
new file mode 100644
index 0000000000..2ce57305bd
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.16bpc.png
Binary files differ
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
new file mode 100644
index 0000000000..f760cb9a8e
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.jpg
Binary files differ
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
new file mode 100644
index 0000000000..3667c72467
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.png
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile0.10bpc.yuv420.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile0.10bpc.yuv420.avif
new file mode 100644
index 0000000000..70186fa2c6
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile0.10bpc.yuv420.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile0.10bpc.yuv420.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile0.10bpc.yuv420.monochrome.avif
new file mode 100644
index 0000000000..afda40dada
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile0.10bpc.yuv420.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile0.8bpc.yuv420.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile0.8bpc.yuv420.avif
new file mode 100644
index 0000000000..d37c2734be
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile0.8bpc.yuv420.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile0.8bpc.yuv420.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile0.8bpc.yuv420.monochrome.avif
new file mode 100644
index 0000000000..fb08dd4f92
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile0.8bpc.yuv420.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.10bpc.yuv422.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.10bpc.yuv422.avif
new file mode 100644
index 0000000000..da8fe75a1c
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.10bpc.yuv422.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.10bpc.yuv422.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.10bpc.yuv422.monochrome.avif
new file mode 100644
index 0000000000..7f90e43eba
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.10bpc.yuv422.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.12bpc.yuv422.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.12bpc.yuv422.avif
new file mode 100644
index 0000000000..7d703d474c
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.12bpc.yuv422.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.12bpc.yuv422.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.12bpc.yuv422.monochrome.avif
new file mode 100644
index 0000000000..16c0558666
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.12bpc.yuv422.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.8bpc.yuv422.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.8bpc.yuv422.avif
new file mode 100644
index 0000000000..cd58a6f8a3
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.8bpc.yuv422.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.8bpc.yuv422.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.8bpc.yuv422.monochrome.avif
new file mode 100644
index 0000000000..b6b033a617
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/hato.profile2.8bpc.yuv422.monochrome.avif
Binary files differ
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.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.avif
new file mode 100644
index 0000000000..e91fe56e72
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.crop.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.crop.avif
new file mode 100644
index 0000000000..777813e690
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.crop.avif
Binary files differ
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
new file mode 100644
index 0000000000..ea47f4c9a2
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.crop.png
Binary files differ
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
new file mode 100644
index 0000000000..6c4894a43d
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.jpg
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-horizontal.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-horizontal.avif
new file mode 100644
index 0000000000..3447d4ad89
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-horizontal.avif
Binary files differ
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
new file mode 100644
index 0000000000..de61ec89f0
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-horizontal.png
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.avif
new file mode 100644
index 0000000000..41d2bd2091
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.avif
Binary files differ
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
new file mode 100644
index 0000000000..2eeb69d89b
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.png
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.rotate270.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.rotate270.avif
new file mode 100644
index 0000000000..164c66bc7d
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.rotate270.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.rotate270.crop.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.rotate270.crop.avif
new file mode 100644
index 0000000000..47ab00b1e4
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.rotate270.crop.avif
Binary files differ
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
new file mode 100644
index 0000000000..bbd9dc766e
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.mirror-vertical.rotate270.png
Binary files differ
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
new file mode 100644
index 0000000000..2bddce177a
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.png
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate270.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate270.avif
new file mode 100644
index 0000000000..aed93e55a0
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate270.avif
Binary files differ
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
new file mode 100644
index 0000000000..e9916b527c
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate270.png
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate90.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate90.avif
new file mode 100644
index 0000000000..ee7c5246e1
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate90.avif
Binary files differ
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
new file mode 100644
index 0000000000..02ede290b4
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/kimono.rotate90.png
Binary files differ
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
new file mode 100644
index 0000000000..22f0c8627a
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.png
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-full.avif
new file mode 100644
index 0000000000..9bc5e76309
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-full.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-full.monochrome.avif
new file mode 100644
index 0000000000..77ffb204a1
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-full.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.avif
new file mode 100644
index 0000000000..3befc33670
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.monochrome.avif
new file mode 100644
index 0000000000..dc9f644b4d
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.10bpc.yuv420.alpha-limited.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-full.avif
new file mode 100644
index 0000000000..6ef118d519
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-full.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-full.monochrome.avif
new file mode 100644
index 0000000000..e71f4e5452
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-full.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.avif
new file mode 100644
index 0000000000..73c6a32236
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.monochrome.avif
new file mode 100644
index 0000000000..8e9d45d0bf
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile0.8bpc.yuv420.alpha-limited.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile1.10bpc.yuv444.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile1.10bpc.yuv444.alpha-full.avif
new file mode 100644
index 0000000000..029512f75f
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile1.10bpc.yuv444.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile1.10bpc.yuv444.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile1.10bpc.yuv444.alpha-limited.avif
new file mode 100644
index 0000000000..fc9ef8bf19
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile1.10bpc.yuv444.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile1.8bpc.yuv444.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile1.8bpc.yuv444.alpha-full.avif
new file mode 100644
index 0000000000..bcca0a5584
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile1.8bpc.yuv444.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile1.8bpc.yuv444.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile1.8bpc.yuv444.alpha-limited.avif
new file mode 100644
index 0000000000..2ac003523a
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile1.8bpc.yuv444.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-full.avif
new file mode 100644
index 0000000000..cb5256097b
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-full.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-full.monochrome.avif
new file mode 100644
index 0000000000..9e91137240
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-full.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.avif
new file mode 100644
index 0000000000..2906c39fea
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.monochrome.avif
new file mode 100644
index 0000000000..1d07bfb056
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.10bpc.yuv422.alpha-limited.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-full.avif
new file mode 100644
index 0000000000..51fce45c0d
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-full.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-full.monochrome.avif
new file mode 100644
index 0000000000..ec30fcc3e1
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-full.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.avif
new file mode 100644
index 0000000000..370c33dd37
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.monochrome.avif
new file mode 100644
index 0000000000..515f247f61
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv420.alpha-limited.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-full.avif
new file mode 100644
index 0000000000..9a99028b8e
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-full.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-full.monochrome.avif
new file mode 100644
index 0000000000..ec30fcc3e1
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-full.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.avif
new file mode 100644
index 0000000000..43ede8cbc8
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.monochrome.avif
new file mode 100644
index 0000000000..515f247f61
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv422.alpha-limited.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-full.avif
new file mode 100644
index 0000000000..bfed8fb528
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-full.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-full.monochrome.avif
new file mode 100644
index 0000000000..ec30fcc3e1
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-full.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.avif
new file mode 100644
index 0000000000..28caa27a37
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.monochrome.avif
new file mode 100644
index 0000000000..515f247f61
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.12bpc.yuv444.alpha-limited.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-full.avif
new file mode 100644
index 0000000000..4952b91fa1
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-full.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-full.monochrome.avif
new file mode 100644
index 0000000000..509ff84b8a
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-full.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.avif
new file mode 100644
index 0000000000..d0a177adf3
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.monochrome.avif
new file mode 100644
index 0000000000..f14276c5fd
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-large.profile2.8bpc.yuv422.alpha-limited.monochrome.avif
Binary files differ
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
new file mode 100644
index 0000000000..901b3edd4a
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.png
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-full.avif
new file mode 100644
index 0000000000..77a9c12d64
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-full.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-full.monochrome.avif
new file mode 100644
index 0000000000..9c3a0d84cc
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-full.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.avif
new file mode 100644
index 0000000000..09e25a8f4f
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.monochrome.avif
new file mode 100644
index 0000000000..ebfc2d1c84
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.10bpc.yuv420.alpha-limited.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-full.avif
new file mode 100644
index 0000000000..0669fcebe1
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-full.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-full.monochrome.avif
new file mode 100644
index 0000000000..b1aadc8a61
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-full.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.avif
new file mode 100644
index 0000000000..4418b2c814
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.monochrome.avif
new file mode 100644
index 0000000000..b4bd661c4a
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile0.8bpc.yuv420.alpha-limited.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile1.10bpc.yuv444.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile1.10bpc.yuv444.alpha-full.avif
new file mode 100644
index 0000000000..30a3542c7a
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile1.10bpc.yuv444.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile1.10bpc.yuv444.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile1.10bpc.yuv444.alpha-limited.avif
new file mode 100644
index 0000000000..0561b592cb
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile1.10bpc.yuv444.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile1.8bpc.yuv444.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile1.8bpc.yuv444.alpha-full.avif
new file mode 100644
index 0000000000..db4f7a5c50
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile1.8bpc.yuv444.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile1.8bpc.yuv444.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile1.8bpc.yuv444.alpha-limited.avif
new file mode 100644
index 0000000000..ad07e3453c
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile1.8bpc.yuv444.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-full.avif
new file mode 100644
index 0000000000..ca18319163
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-full.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-full.monochrome.avif
new file mode 100644
index 0000000000..47e732a2ef
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-full.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.avif
new file mode 100644
index 0000000000..ecb78e9e22
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.monochrome.avif
new file mode 100644
index 0000000000..c32f19489d
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.10bpc.yuv422.alpha-limited.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-full.avif
new file mode 100644
index 0000000000..7959d15b92
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-full.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-full.monochrome.avif
new file mode 100644
index 0000000000..1ef86bcae0
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-full.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.avif
new file mode 100644
index 0000000000..9d8b7116f2
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.monochrome.avif
new file mode 100644
index 0000000000..a240078446
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv420.alpha-limited.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-full.avif
new file mode 100644
index 0000000000..faf043db63
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-full.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-full.monochrome.avif
new file mode 100644
index 0000000000..1ef86bcae0
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-full.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.avif
new file mode 100644
index 0000000000..7f840da29c
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.monochrome.avif
new file mode 100644
index 0000000000..a240078446
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv422.alpha-limited.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-full.avif
new file mode 100644
index 0000000000..db34cd3f74
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-full.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-full.monochrome.avif
new file mode 100644
index 0000000000..1ef86bcae0
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-full.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.avif
new file mode 100644
index 0000000000..f1879a5550
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.monochrome.avif
new file mode 100644
index 0000000000..a240078446
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.12bpc.yuv444.alpha-limited.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-full.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-full.avif
new file mode 100644
index 0000000000..ebb5e34c43
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-full.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-full.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-full.monochrome.avif
new file mode 100644
index 0000000000..3e6021b716
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-full.monochrome.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.avif
new file mode 100644
index 0000000000..e7eb2a33d3
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.monochrome.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.monochrome.avif
new file mode 100644
index 0000000000..50522d68af
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/plum-blossom-small.profile2.8bpc.yuv422.alpha-limited.monochrome.avif
Binary files differ
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-10bpc.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile-10bpc.avif
new file mode 100644
index 0000000000..8c964b1448
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile-10bpc.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile-12bpc.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile-12bpc.avif
new file mode 100644
index 0000000000..27fd2932e9
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile-12bpc.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile-8bpc.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile-8bpc.avif
new file mode 100644
index 0000000000..913e6b6eb5
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile-8bpc.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile-lossy.avif b/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile-lossy.avif
new file mode 100644
index 0000000000..4f2d48cba0
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/red-at-12-oclock-with-color-profile-lossy.avif
Binary files differ
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
new 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
Binary files differ
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
new 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
Binary files differ
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
new file mode 100644
index 0000000000..05c5a45c49
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star-10bpc-with-alpha.avifs
Binary files differ
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
new file mode 100644
index 0000000000..2ddcf5047f
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star-10bpc.avifs
Binary files differ
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
new file mode 100644
index 0000000000..c6fe1a53cd
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star-12bpc-with-alpha.avifs
Binary files differ
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
new file mode 100644
index 0000000000..86dfc2ee80
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star-12bpc.avifs
Binary files differ
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
new file mode 100644
index 0000000000..bb9dfa5c33
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star-8bpc-with-alpha.avifs
Binary files differ
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
new file mode 100644
index 0000000000..6c0b0e3dc0
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star-8bpc.avifs
Binary files differ
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
new file mode 100644
index 0000000000..52076cafdd
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star.gif
Binary files differ
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
new file mode 100644
index 0000000000..468dcde005
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star.png
Binary files differ
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
new file mode 100644
index 0000000000..2c5f522211
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star180.png
Binary files differ
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
new file mode 100644
index 0000000000..8812b9bdeb
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star270.png
Binary files differ
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
new file mode 100644
index 0000000000..93526260ba
--- /dev/null
+++ b/third_party/rust/mp4parse/link-u-avif-sample-images/star90.png
Binary files differ
diff --git a/third_party/rust/mp4parse/src/boxes.rs b/third_party/rust/mp4parse/src/boxes.rs
new file mode 100644
index 0000000000..838df5db19
--- /dev/null
+++ b/third_party/rust/mp4parse/src/boxes.rs
@@ -0,0 +1,238 @@
+// 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"
+ 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..1fb2ef4d4c
--- /dev/null
+++ b/third_party/rust/mp4parse/src/lib.rs
@@ -0,0 +1,6291 @@
+//! 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>),
+}
+
+#[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..)
+ }
+ }
+ }
+}
+
+#[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][..]));
+ }
+}
+
+#[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,
+ #[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: &mut Track,
+) -> Result<(
+ MediaHeaderBox,
+ Option<TrackScaledTime<u64>>,
+ Option<TrackTimeScale<u64>>,
+)> {
+ let mdhd = read_mdhd(f)?;
+ let duration = match mdhd.duration {
+ std::u64::MAX => None,
+ duration => Some(TrackScaledTime::<u64>(duration, track.id)),
+ };
+ if mdhd.timescale == 0 {
+ return 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 fields.
+ skip(src, 80)?;
+ Ok(MovieHeaderBox {
+ timescale,
+ duration,
+ })
+}
+
+/// Parse a tkhd box.
+fn read_tkhd<T: Read>(src: &mut BMFFBox<T>) -> Result<TrackHeaderBox> {
+ let (version, flags) = read_fullbox_extra(src)?;
+ let disabled = flags & 0x1u32 == 0 || flags & 0x2u32 == 0;
+ match version {
+ // 64 bit creation and modification times.
+ 1 => {
+ skip(src, 16)?;
+ }
+ // 32 bit creation and modification times.
+ 0 => {
+ skip(src, 8)?;
+ }
+ _ => return 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 = vec![
+ (0x0, 96000),
+ (0x1, 88200),
+ (0x2, 64000),
+ (0x3, 48000),
+ (0x4, 44100),
+ (0x5, 32000),
+ (0x6, 24000),
+ (0x7, 22050),
+ (0x8, 16000),
+ (0x9, 12000),
+ (0xa, 11025),
+ (0xb, 8000),
+ (0xc, 7350),
+ ];
+
+ let bit_reader = &mut BitReader::new(data);
+
+ let mut audio_object_type = get_audio_object_type(bit_reader)?;
+
+ let sample_index: u32 = ReadInto::read(bit_reader, 4)?;
+
+ // Sample frequency could be from table, or retrieved from stream directly
+ // if index is 0x0f.
+ let sample_frequency = match sample_index {
+ 0x0F => Some(ReadInto::read(bit_reader, 24)?),
+ _ => frequency_table
+ .iter()
+ .find(|item| item.0 == sample_index)
+ .map(|x| x.1),
+ };
+
+ let channel_configuration: u16 = ReadInto::read(bit_reader, 4)?;
+
+ let extended_audio_object_type = match audio_object_type {
+ 5 | 29 => Some(5),
+ _ => None,
+ };
+
+ if audio_object_type == 5 || audio_object_type == 29 {
+ // We have an explicit signaling for BSAC extension, should the decoder
+ // decode the BSAC extension (all Gecko's AAC decoders do), then this is
+ // what the stream will actually look like once decoded.
+ let _extended_sample_index = ReadInto::read(bit_reader, 4)?;
+ let _extended_sample_frequency: Option<u32> = match _extended_sample_index {
+ 0x0F => Some(ReadInto::read(bit_reader, 24)?),
+ _ => frequency_table
+ .iter()
+ .find(|item| item.0 == sample_index)
+ .map(|x| x.1),
+ };
+ audio_object_type = get_audio_object_type(bit_reader)?;
+ let _extended_channel_configuration = match audio_object_type {
+ 22 => ReadInto::read(bit_reader, 4)?,
+ _ => channel_configuration,
+ };
+ };
+
+ match audio_object_type {
+ 1..=4 | 6 | 7 | 17 | 19..=23 => {
+ if sample_frequency.is_none() {
+ return Err(Error::Unsupported("unknown frequency"));
+ }
+
+ // parsing GASpecificConfig
+
+ // If the sampling rate is not one of the rates listed in the right
+ // column in Table 4.82, the sampling frequency dependent tables
+ // (code tables, scale factor band tables etc.) must be deduced in
+ // order for the bitstream payload to be parsed. Since a given
+ // sampling frequency is associated with only one sampling frequency
+ // table, and since maximum flexibility is desired in the range of
+ // possible sampling frequencies, the following table shall be used
+ // to associate an implied sampling frequency with the desired
+ // sampling frequency dependent tables.
+ let sample_frequency_value = match sample_frequency.unwrap() {
+ 0..=9390 => 8000,
+ 9391..=11501 => 11025,
+ 11502..=13855 => 12000,
+ 13856..=18782 => 16000,
+ 18783..=23003 => 22050,
+ 23004..=27712 => 24000,
+ 27713..=37565 => 32000,
+ 37566..=46008 => 44100,
+ 46009..=55425 => 48000,
+ 55426..=75131 => 64000,
+ 75132..=92016 => 88200,
+ _ => 96000,
+ };
+
+ bit_reader.skip(1)?; // frameLengthFlag
+ let depend_on_core_order: u8 = ReadInto::read(bit_reader, 1)?;
+ if depend_on_core_order > 0 {
+ bit_reader.skip(14)?; // codeCoderDelay
+ }
+ bit_reader.skip(1)?; // extensionFlag
+
+ let channel_counts = match channel_configuration {
+ 0 => {
+ debug!("Parsing program_config_element for channel counts");
+
+ bit_reader.skip(4)?; // element_instance_tag
+ bit_reader.skip(2)?; // object_type
+ bit_reader.skip(4)?; // sampling_frequency_index
+ let num_front_channel: u8 = ReadInto::read(bit_reader, 4)?;
+ let num_side_channel: u8 = ReadInto::read(bit_reader, 4)?;
+ let num_back_channel: u8 = ReadInto::read(bit_reader, 4)?;
+ let num_lfe_channel: u8 = ReadInto::read(bit_reader, 2)?;
+ bit_reader.skip(3)?; // num_assoc_data
+ bit_reader.skip(4)?; // num_valid_cc
+
+ let mono_mixdown_present: bool = ReadInto::read(bit_reader, 1)?;
+ if mono_mixdown_present {
+ bit_reader.skip(4)?; // mono_mixdown_element_number
+ }
+
+ let stereo_mixdown_present: bool = ReadInto::read(bit_reader, 1)?;
+ if stereo_mixdown_present {
+ bit_reader.skip(4)?; // stereo_mixdown_element_number
+ }
+
+ let matrix_mixdown_idx_present: bool = ReadInto::read(bit_reader, 1)?;
+ if matrix_mixdown_idx_present {
+ bit_reader.skip(2)?; // matrix_mixdown_idx
+ bit_reader.skip(1)?; // pseudo_surround_enable
+ }
+ let mut _channel_counts = 0;
+ _channel_counts += read_surround_channel_count(bit_reader, num_front_channel)?;
+ _channel_counts += read_surround_channel_count(bit_reader, num_side_channel)?;
+ _channel_counts += read_surround_channel_count(bit_reader, num_back_channel)?;
+ _channel_counts += read_surround_channel_count(bit_reader, num_lfe_channel)?;
+ _channel_counts
+ }
+ 1..=7 => channel_configuration,
+ // Amendment 4 of the AAC standard in 2013 below
+ 11 => 7, // 6.1 Amendment 4 of the AAC standard in 2013
+ 12 | 14 => 8, // 7.1 (a/d) of ITU BS.2159
+ _ => {
+ return Err(Error::Unsupported("invalid channel configuration"));
+ }
+ };
+
+ esds.audio_object_type = Some(audio_object_type);
+ esds.extended_audio_object_type = extended_audio_object_type;
+ esds.audio_sample_rate = Some(sample_frequency_value);
+ esds.audio_channel_count = Some(channel_counts);
+ if !esds.decoder_specific_data.is_empty() {
+ return 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,
+ _ => {
+ 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)?;
+ }
+ _ => {
+ 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: &mut 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)
+}
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..a628e2675e
--- /dev/null
+++ b/third_party/rust/mp4parse/src/tests.rs
@@ -0,0 +1,1347 @@
+//! 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, &mut super::Track::new(0));
+ assert!(r.is_err());
+}
+
+#[test]
+fn read_mvhd_v0() {
+ let mut stream = make_fullbox(BoxSize::Short(108), b"mvhd", 0, |s| {
+ s.B32(0).B32(0).B32(1234).B32(5678).append_repeated(0, 80)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
+ assert_eq!(stream.head.size, 108);
+ let parsed = super::read_mvhd(&mut stream).unwrap();
+ assert_eq!(parsed.timescale, 1234);
+ assert_eq!(parsed.duration, 5678);
+}
+
+#[test]
+fn read_mvhd_v1() {
+ let mut stream = make_fullbox(BoxSize::Short(120), b"mvhd", 1, |s| {
+ s.B64(0).B64(0).B32(1234).B64(5678).append_repeated(0, 80)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
+ assert_eq!(stream.head.size, 120);
+ let parsed = super::read_mvhd(&mut stream).unwrap();
+ assert_eq!(parsed.timescale, 1234);
+ assert_eq!(parsed.duration, 5678);
+}
+
+#[test]
+fn read_mvhd_invalid_timescale() {
+ let mut stream = make_fullbox(BoxSize::Short(120), b"mvhd", 1, |s| {
+ s.B64(0).B64(0).B32(0).B64(5678).append_repeated(0, 80)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
+ assert_eq!(stream.head.size, 120);
+ let r = super::parse_mvhd(&mut stream);
+ assert!(r.is_err());
+}
+
+#[test]
+fn read_mvhd_unknown_duration() {
+ let mut stream = make_fullbox(BoxSize::Short(108), b"mvhd", 0, |s| {
+ s.B32(0)
+ .B32(0)
+ .B32(1234)
+ .B32(::std::u32::MAX)
+ .append_repeated(0, 80)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::MovieHeaderBox);
+ assert_eq!(stream.head.size, 108);
+ let parsed = super::read_mvhd(&mut stream).unwrap();
+ assert_eq!(parsed.timescale, 1234);
+ assert_eq!(parsed.duration, ::std::u64::MAX);
+}
+
+#[test]
+fn read_vpcc_version_0() {
+ let data_length = 12u16;
+ let mut stream = make_fullbox(BoxSize::Auto, b"vpcC", 0, |s| {
+ s.B8(2)
+ .B8(0)
+ .B8(0x82)
+ .B8(0)
+ .B16(data_length)
+ .append_repeated(42, data_length as usize)
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::VPCodecConfigurationBox);
+ let r = super::read_vpcc(&mut stream);
+ assert!(r.is_ok());
+}
+
+// TODO: it'd be better to find a real sample here.
+#[test]
+#[allow(clippy::unusual_byte_groupings)] // Allow odd grouping for test readability.
+fn read_vpcc_version_1() {
+ let data_length = 12u16;
+ let mut stream = make_fullbox(BoxSize::Auto, b"vpcC", 1, |s| {
+ s.B8(2) // profile
+ .B8(0) // level
+ .B8(0b1000_011_0) // bitdepth (4 bits), chroma (3 bits), video full range (1 bit)
+ .B8(1) // color primaries
+ .B8(1) // transfer characteristics
+ .B8(1) // matrix
+ .B16(data_length)
+ .append_repeated(42, data_length as usize)
+ });
+
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::VPCodecConfigurationBox);
+ let r = super::read_vpcc(&mut stream);
+ match r {
+ Ok(vpcc) => {
+ assert_eq!(vpcc.bit_depth, 8);
+ assert_eq!(vpcc.chroma_subsampling, 3);
+ assert!(!vpcc.video_full_range_flag);
+ assert_eq!(vpcc.matrix_coefficients.unwrap(), 1);
+ }
+ _ => panic!("vpcc parsing error"),
+ }
+}
+
+#[test]
+fn read_hdlr() {
+ let mut stream = make_fullbox(BoxSize::Short(45), b"hdlr", 0, |s| {
+ s.B32(0)
+ .append_bytes(b"vide")
+ .B32(0)
+ .B32(0)
+ .B32(0)
+ .append_bytes(b"VideoHandler")
+ .B8(0) // null-terminate string
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::HandlerBox);
+ assert_eq!(stream.head.size, 45);
+ let parsed = super::read_hdlr(&mut stream, ParseStrictness::Normal).unwrap();
+ assert_eq!(parsed.handler_type, b"vide");
+}
+
+#[test]
+fn read_hdlr_multiple_nul_in_name() {
+ let mut stream = make_fullbox(BoxSize::Short(45), b"hdlr", 0, |s| {
+ s.B32(0)
+ .append_bytes(b"vide")
+ .B32(0)
+ .B32(0)
+ .B32(0)
+ .append_bytes(b"Vide\0Handler")
+ .B8(0) // null-terminate string
+ });
+ let mut iter = super::BoxIter::new(&mut stream);
+ let mut stream = iter.next_box().unwrap().unwrap();
+ assert_eq!(stream.head.name, BoxType::HandlerBox);
+ assert_eq!(stream.head.size, 45);
+ assert_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, &mut 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..9b3e5b407b
--- /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 = matches!(track.stss, Some(_));
+
+ let mut sample_size_iter = stsz.sample_sizes.iter();
+
+ // Get 'stsc' iterator for (chunk_id, chunk_sample_count) and calculate the sample
+ // offset address.
+
+ // With large numbers of samples, the cost of many allocations dominates,
+ // so it's worth iterating twice to allocate sample_table just once.
+ let total_sample_count = sample_to_chunk_iter(&stsc.samples, &stco.offsets)
+ .map(|(_, sample_counts)| sample_counts.to_usize())
+ .try_fold(0usize, usize::checked_add)?;
+ let mut sample_table = TryVec::with_capacity(total_sample_count).ok()?;
+
+ for i in sample_to_chunk_iter(&stsc.samples, &stco.offsets) {
+ let chunk_id = i.0 as usize;
+ let sample_counts = i.1;
+ let mut cur_position = match stco.offsets.get(chunk_id) {
+ Some(&i) => i.into(),
+ _ => return None,
+ };
+ for _ in 0..sample_counts {
+ let start_offset = cur_position;
+ let end_offset = match (stsz.sample_size, sample_size_iter.next()) {
+ (_, Some(t)) => (start_offset + *t)?,
+ (t, _) if t > 0 => (start_offset + t)?,
+ _ => 0.into(),
+ };
+ if end_offset == 0 {
+ return None;
+ }
+ cur_position = end_offset;
+
+ sample_table
+ .push(Indice {
+ start_offset,
+ end_offset,
+ sync: !has_sync_table,
+ ..Default::default()
+ })
+ .ok()?;
+ }
+ }
+
+ // Mark the sync sample in sample_table according to 'stss'.
+ if let Some(ref v) = track.stss {
+ for iter in &v.samples {
+ match iter
+ .checked_sub(&1)
+ .and_then(|idx| sample_table.get_mut(idx as usize))
+ {
+ Some(elem) => elem.sync = true,
+ _ => return None,
+ }
+ }
+ }
+
+ let ctts_iter = track.ctts.as_ref().map(|v| v.samples.as_slice().iter());
+
+ let mut ctts_offset_iter = TimeOffsetIterator {
+ cur_sample_range: (0..0),
+ cur_offset: 0,
+ ctts_iter,
+ track_id: track.id,
+ };
+
+ let mut stts_iter = TimeToSampleIterator {
+ cur_sample_count: (0..0),
+ cur_sample_delta: 0,
+ stts_iter: stts.samples.as_slice().iter(),
+ track_id: track.id,
+ };
+
+ // sum_delta is the sum of stts_iter delta.
+ // According to spec:
+ // decode time => DT(n) = DT(n-1) + STTS(n)
+ // composition time => CT(n) = DT(n) + CTTS(n)
+ // Note:
+ // composition time needs to add the track offset time from 'elst' table.
+ let mut sum_delta = TrackScaledTime::<i64>(0, track.id);
+ for sample in sample_table.as_mut_slice() {
+ let decode_time = sum_delta;
+ sum_delta = (sum_delta + stts_iter.next_delta())?;
+
+ // ctts_offset is the current sample offset time.
+ let ctts_offset = ctts_offset_iter.next_offset_time();
+
+ let start_composition = 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.
+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/1x1-black-alpha-50pct-premultiplied.avif b/third_party/rust/mp4parse/tests/1x1-black-alpha-50pct-premultiplied.avif
new file mode 100644
index 0000000000..40652dfd49
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/1x1-black-alpha-50pct-premultiplied.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/a1lx.avif b/third_party/rust/mp4parse/tests/a1lx.avif
new file mode 100644
index 0000000000..42d77f2336
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/a1lx.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/a1op.avif b/third_party/rust/mp4parse/tests/a1op.avif
new file mode 100644
index 0000000000..a98f0ffffd
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/a1op.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/alpha_video_avif_major_avis_compatible.avif b/third_party/rust/mp4parse/tests/alpha_video_avif_major_avis_compatible.avif
new file mode 100644
index 0000000000..f2782a5206
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/alpha_video_avif_major_avis_compatible.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/alpha_video_fixed.avif b/third_party/rust/mp4parse/tests/alpha_video_fixed.avif
new file mode 100644
index 0000000000..a600f849c0
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/alpha_video_fixed.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/alpha_video_no_avis.avif b/third_party/rust/mp4parse/tests/alpha_video_no_avis.avif
new file mode 100644
index 0000000000..b1794c9d6e
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/alpha_video_no_avis.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/amr_nb_1f.3gp b/third_party/rust/mp4parse/tests/amr_nb_1f.3gp
new file mode 100644
index 0000000000..5ef216c636
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/amr_nb_1f.3gp
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/amr_wb_1f.3gp b/third_party/rust/mp4parse/tests/amr_wb_1f.3gp
new file mode 100644
index 0000000000..a49f39438d
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/amr_wb_1f.3gp
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/av1C-missing-essential.avif b/third_party/rust/mp4parse/tests/av1C-missing-essential.avif
new file mode 100644
index 0000000000..c5bf6ee045
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/av1C-missing-essential.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/avis_with_no_ptim_no_iloc.avif b/third_party/rust/mp4parse/tests/avis_with_no_ptim_no_iloc.avif
new file mode 100644
index 0000000000..0498b8d1c3
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/avis_with_no_ptim_no_iloc.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/avis_with_pitm_no_iloc.avif b/third_party/rust/mp4parse/tests/avis_with_pitm_no_iloc.avif
new file mode 100644
index 0000000000..40ce57ac5c
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/avis_with_pitm_no_iloc.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/bad-ipma-flags.avif b/third_party/rust/mp4parse/tests/bad-ipma-flags.avif
new file mode 100644
index 0000000000..40012f2114
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/bad-ipma-flags.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/bad-ipma-version.avif b/third_party/rust/mp4parse/tests/bad-ipma-version.avif
new file mode 100644
index 0000000000..e8442c166c
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/bad-ipma-version.avif
Binary files differ
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
new file mode 100644
index 0000000000..7e8ff7f814
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/bbb_sunflower_QCIF_30fps_h263_noaudio_1f.3gp
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/clap-basic-1_3x3-to-1x1.avif b/third_party/rust/mp4parse/tests/clap-basic-1_3x3-to-1x1.avif
new file mode 100644
index 0000000000..2d59986a28
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/clap-basic-1_3x3-to-1x1.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/clap-missing-essential.avif b/third_party/rust/mp4parse/tests/clap-missing-essential.avif
new file mode 100644
index 0000000000..4dfcfd2bb9
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/clap-missing-essential.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/clusterfuzz-testcase-minimized-mp4-6093954524250112 b/third_party/rust/mp4parse/tests/clusterfuzz-testcase-minimized-mp4-6093954524250112
new file mode 100644
index 0000000000..fb6335f563
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/clusterfuzz-testcase-minimized-mp4-6093954524250112
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/a1lx-marked-essential.avif b/third_party/rust/mp4parse/tests/corrupt/a1lx-marked-essential.avif
new file mode 100644
index 0000000000..c24f2c1c67
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/a1lx-marked-essential.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/a1op-missing-essential.avif b/third_party/rust/mp4parse/tests/corrupt/a1op-missing-essential.avif
new file mode 100644
index 0000000000..828dbbc52c
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/a1op-missing-essential.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/alpha_video_moov_is_moop.avif b/third_party/rust/mp4parse/tests/corrupt/alpha_video_moov_is_moop.avif
new file mode 100644
index 0000000000..c6aa254722
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/alpha_video_moov_is_moop.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/bug-1655846.avif b/third_party/rust/mp4parse/tests/corrupt/bug-1655846.avif
new file mode 100644
index 0000000000..31c7e42454
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/bug-1655846.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/bug-1661347.avif b/third_party/rust/mp4parse/tests/corrupt/bug-1661347.avif
new file mode 100644
index 0000000000..11b539230b
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/bug-1661347.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/clusterfuzz-testcase-minimized-avif-4914209301856256.avif b/third_party/rust/mp4parse/tests/corrupt/clusterfuzz-testcase-minimized-avif-4914209301856256.avif
new file mode 100644
index 0000000000..e5cb2fe70e
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/clusterfuzz-testcase-minimized-avif-4914209301856256.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/hdlr-not-first.avif b/third_party/rust/mp4parse/tests/corrupt/hdlr-not-first.avif
new file mode 100644
index 0000000000..2011e7bb7d
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/hdlr-not-first.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/hdlr-not-pict.avif b/third_party/rust/mp4parse/tests/corrupt/hdlr-not-pict.avif
new file mode 100644
index 0000000000..34137ce3c9
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/hdlr-not-pict.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/imir-before-clap.avif b/third_party/rust/mp4parse/tests/corrupt/imir-before-clap.avif
new file mode 100644
index 0000000000..d02ef72424
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/imir-before-clap.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple-nclx.avif b/third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple-nclx.avif
new file mode 100644
index 0000000000..7d2ea0dc52
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple-nclx.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple-prof.avif b/third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple-prof.avif
new file mode 100644
index 0000000000..671b8b6585
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple-prof.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple-rICC.avif b/third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple-rICC.avif
new file mode 100644
index 0000000000..e8ced19127
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple-rICC.avif
Binary files differ
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
new file mode 100644
index 0000000000..14629882d0
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/invalid-avif-colr-multiple.zip
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/invalid-transformation-order.avif b/third_party/rust/mp4parse/tests/corrupt/invalid-transformation-order.avif
new file mode 100644
index 0000000000..d02ef72424
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/invalid-transformation-order.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/ipma-duplicate-item_id.avif b/third_party/rust/mp4parse/tests/corrupt/ipma-duplicate-item_id.avif
new file mode 100644
index 0000000000..ed47ebfe6f
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/ipma-duplicate-item_id.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/ipma-duplicate-version-and-flags.avif b/third_party/rust/mp4parse/tests/corrupt/ipma-duplicate-version-and-flags.avif
new file mode 100644
index 0000000000..9bc5e76309
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/ipma-duplicate-version-and-flags.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/ipma-invalid-property-index.avif b/third_party/rust/mp4parse/tests/corrupt/ipma-invalid-property-index.avif
new file mode 100644
index 0000000000..e5ecbb993b
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/ipma-invalid-property-index.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/lsel-missing-essential.avif b/third_party/rust/mp4parse/tests/corrupt/lsel-missing-essential.avif
new file mode 100644
index 0000000000..a83300c6a9
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/lsel-missing-essential.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/no-alpha-av1C.avif b/third_party/rust/mp4parse/tests/corrupt/no-alpha-av1C.avif
new file mode 100644
index 0000000000..6d674ebbbf
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/no-alpha-av1C.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/no-alpha-ispe.avif b/third_party/rust/mp4parse/tests/corrupt/no-alpha-ispe.avif
new file mode 100644
index 0000000000..0183a60831
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/no-alpha-ispe.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/no-av1C.avif b/third_party/rust/mp4parse/tests/corrupt/no-av1C.avif
new file mode 100644
index 0000000000..f46fcb6523
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/no-av1C.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/no-ftyp.avif b/third_party/rust/mp4parse/tests/corrupt/no-ftyp.avif
new file mode 100644
index 0000000000..ff666bb3f8
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/no-ftyp.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/no-hdlr.avif b/third_party/rust/mp4parse/tests/corrupt/no-hdlr.avif
new file mode 100644
index 0000000000..d75e869589
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/no-hdlr.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/no-ispe.avif b/third_party/rust/mp4parse/tests/corrupt/no-ispe.avif
new file mode 100644
index 0000000000..69f97caeae
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/no-ispe.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/no-pitm.avif b/third_party/rust/mp4parse/tests/corrupt/no-pitm.avif
new file mode 100644
index 0000000000..074788b398
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/no-pitm.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/no-pixi-for-alpha.avif b/third_party/rust/mp4parse/tests/corrupt/no-pixi-for-alpha.avif
new file mode 100644
index 0000000000..caf1b7fe60
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/no-pixi-for-alpha.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/no-pixi.avif b/third_party/rust/mp4parse/tests/corrupt/no-pixi.avif
new file mode 100644
index 0000000000..9e04a6817a
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/no-pixi.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/corrupt/transformation-before-ispe.avif b/third_party/rust/mp4parse/tests/corrupt/transformation-before-ispe.avif
new file mode 100644
index 0000000000..4ff8652734
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/corrupt/transformation-before-ispe.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/hdlr-nonzero-reserved.avif b/third_party/rust/mp4parse/tests/hdlr-nonzero-reserved.avif
new file mode 100644
index 0000000000..e84ba63e2d
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/hdlr-nonzero-reserved.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/imir-missing-essential.avif b/third_party/rust/mp4parse/tests/imir-missing-essential.avif
new file mode 100644
index 0000000000..760a7b9390
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/imir-missing-essential.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/invalid-avif-hdlr-name-multiple-nul.avif b/third_party/rust/mp4parse/tests/invalid-avif-hdlr-name-multiple-nul.avif
new file mode 100644
index 0000000000..fe7a64ee02
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/invalid-avif-hdlr-name-multiple-nul.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/irot-missing-essential.avif b/third_party/rust/mp4parse/tests/irot-missing-essential.avif
new file mode 100644
index 0000000000..07c32e3604
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/irot-missing-essential.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/loop_forever.avif b/third_party/rust/mp4parse/tests/loop_forever.avif
new file mode 100644
index 0000000000..b24b4f29d3
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/loop_forever.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/loop_none.avif b/third_party/rust/mp4parse/tests/loop_none.avif
new file mode 100644
index 0000000000..8cc0dfb427
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/loop_none.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/lsel.avif b/third_party/rust/mp4parse/tests/lsel.avif
new file mode 100644
index 0000000000..ad85f72b84
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/lsel.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/multiple-extents.avif b/third_party/rust/mp4parse/tests/multiple-extents.avif
new file mode 100644
index 0000000000..dd306992cb
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/multiple-extents.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/no-mif1.avif b/third_party/rust/mp4parse/tests/no-mif1.avif
new file mode 100644
index 0000000000..1ccbcc43fc
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/no-mif1.avif
Binary files differ
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..0789946b52
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/public.rs
@@ -0,0 +1,1513 @@
+/// 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 AMR-NB 3gp file can be generated by ffmpeg with command
+// "ffmpeg -i [input file] -f 3gp -acodec amr_nb -ar 8000 -ac 1 -frames:a 1 -vn output.3gp"
+#[cfg(feature = "3gpp")]
+static AUDIO_AMRNB_3GP: &str = "tests/amr_nb_1f.3gp";
+// The 1 frame AMR-WB 3gp file can be generated by ffmpeg with command
+// "ffmpeg -i [input file] -f 3gp -acodec amr_wb -ar 16000 -ac 1 -frames:a 1 -vn output.3gp"
+#[cfg(feature = "3gpp")]
+static AUDIO_AMRWB_3GP: &str = "tests/amr_wb_1f.3gp";
+#[cfg(feature = "mp4v")]
+// The 1 frame mp4v mp4 file can be generated by ffmpeg with command
+// "ffmpeg -i [input file] -f mp4 -c:v mpeg4 -vf scale=176x144 -frames:v 1 -an output.mp4"
+static VIDEO_MP4V_MP4: &str = "tests/bbb_sunflower_QCIF_30fps_mp4v_noaudio_1f.mp4";
+
+// Adapted from https://github.com/GuillaumeGomez/audio-video-metadata/blob/9dff40f565af71d5502e03a2e78ae63df95cfd40/src/metadata.rs#L53
+#[test]
+fn public_api() {
+ let mut fd = File::open(MINI_MP4).expect("Unknown file");
+ let mut buf = Vec::new();
+ fd.read_to_end(&mut buf).expect("File error");
+
+ let mut c = Cursor::new(&buf);
+ let context = mp4::read_mp4(&mut c).expect("read_mp4 failed");
+ assert_eq!(context.timescale, Some(mp4::MediaTimeScale(1000)));
+ for track in context.tracks {
+ match track.track_type {
+ mp4::TrackType::Video => {
+ // track part
+ assert_eq!(track.duration, Some(mp4::TrackScaledTime(512, 0)));
+ assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0)));
+ assert_eq!(track.media_time, Some(mp4::TrackScaledTime(0, 0)));
+ assert_eq!(track.timescale, Some(mp4::TrackTimeScale(12800, 0)));
+
+ // track.tkhd part
+ let tkhd = track.tkhd.unwrap();
+ assert!(!tkhd.disabled);
+ assert_eq!(tkhd.duration, 40);
+ assert_eq!(tkhd.width, 20_971_520);
+ assert_eq!(tkhd.height, 15_728_640);
+
+ // track.stsd part
+ let stsd = track.stsd.expect("expected an stsd");
+ let v = match stsd.descriptions.first().expect("expected a SampleEntry") {
+ mp4::SampleEntry::Video(v) => v,
+ _ => panic!("expected a VideoSampleEntry"),
+ };
+ assert_eq!(v.width, 320);
+ assert_eq!(v.height, 240);
+ assert_eq!(
+ match v.codec_specific {
+ mp4::VideoCodecSpecific::AVCConfig(ref avc) => {
+ assert!(!avc.is_empty());
+ "AVC"
+ }
+ mp4::VideoCodecSpecific::VPxConfig(ref vpx) => {
+ // We don't enter in here, we just check if fields are public.
+ assert!(vpx.bit_depth > 0);
+ assert!(vpx.colour_primaries > 0);
+ assert!(vpx.chroma_subsampling > 0);
+ assert!(!vpx.codec_init.is_empty());
+ "VPx"
+ }
+ mp4::VideoCodecSpecific::ESDSConfig(ref mp4v) => {
+ assert!(!mp4v.is_empty());
+ "MP4V"
+ }
+ mp4::VideoCodecSpecific::AV1Config(ref _av1c) => {
+ "AV1"
+ }
+ mp4::VideoCodecSpecific::H263Config(ref _h263) => {
+ "H263"
+ }
+ },
+ "AVC"
+ );
+ }
+ mp4::TrackType::Audio => {
+ // track part
+ assert_eq!(track.duration, Some(mp4::TrackScaledTime(2944, 1)));
+ assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0)));
+ assert_eq!(track.media_time, Some(mp4::TrackScaledTime(1024, 1)));
+ assert_eq!(track.timescale, Some(mp4::TrackTimeScale(48000, 1)));
+
+ // track.tkhd part
+ let tkhd = track.tkhd.unwrap();
+ assert!(!tkhd.disabled);
+ assert_eq!(tkhd.duration, 62);
+ assert_eq!(tkhd.width, 0);
+ assert_eq!(tkhd.height, 0);
+
+ // track.stsd part
+ let stsd = track.stsd.expect("expected an stsd");
+ let a = match stsd.descriptions.first().expect("expected a SampleEntry") {
+ mp4::SampleEntry::Audio(a) => a,
+ _ => panic!("expected a AudioSampleEntry"),
+ };
+ assert_eq!(
+ match a.codec_specific {
+ mp4::AudioCodecSpecific::ES_Descriptor(ref esds) => {
+ assert_eq!(esds.audio_codec, mp4::CodecType::AAC);
+ assert_eq!(esds.audio_sample_rate.unwrap(), 48000);
+ assert_eq!(esds.audio_object_type.unwrap(), 2);
+ "ES"
+ }
+ mp4::AudioCodecSpecific::FLACSpecificBox(ref flac) => {
+ // STREAMINFO block must be present and first.
+ assert!(!flac.blocks.is_empty());
+ assert_eq!(flac.blocks[0].block_type, 0);
+ assert_eq!(flac.blocks[0].data.len(), 34);
+ "FLAC"
+ }
+ mp4::AudioCodecSpecific::OpusSpecificBox(ref opus) => {
+ // We don't enter in here, we just check if fields are public.
+ assert!(opus.version > 0);
+ "Opus"
+ }
+ mp4::AudioCodecSpecific::ALACSpecificBox(ref alac) => {
+ assert!(alac.data.len() == 24 || alac.data.len() == 48);
+ "ALAC"
+ }
+ mp4::AudioCodecSpecific::MP3 => {
+ "MP3"
+ }
+ mp4::AudioCodecSpecific::LPCM => {
+ "LPCM"
+ }
+ #[cfg(feature = "3gpp")]
+ mp4::AudioCodecSpecific::AMRSpecificBox(_) => {
+ "AMR"
+ }
+ },
+ "ES"
+ );
+ assert!(a.samplesize > 0);
+ assert!(a.samplerate > 0.0);
+ }
+ _ => {}
+ }
+ }
+}
+
+#[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]
+#[cfg(feature = "3gpp")]
+fn public_audio_amrnb() {
+ let mut fd = File::open(AUDIO_AMRNB_3GP).expect("Unknown file");
+ let mut buf = Vec::new();
+ fd.read_to_end(&mut buf).expect("File error");
+
+ let mut c = Cursor::new(&buf);
+ let context = mp4::read_mp4(&mut c).expect("read_mp4 failed");
+ for track in context.tracks {
+ let stsd = track.stsd.expect("expected an stsd");
+ let a = match stsd.descriptions.first().expect("expected a SampleEntry") {
+ mp4::SampleEntry::Audio(ref v) => v,
+ _ => panic!("expected a AudioSampleEntry"),
+ };
+ assert!(a.codec_type == mp4::CodecType::AMRNB);
+ let _codec_specific = match a.codec_specific {
+ mp4::AudioCodecSpecific::AMRSpecificBox(_) => true,
+ _ => {
+ panic!("expected a AMRSpecificBox",);
+ }
+ };
+ }
+}
+
+#[test]
+#[cfg(feature = "3gpp")]
+fn public_audio_amrwb() {
+ let mut fd = File::open(AUDIO_AMRWB_3GP).expect("Unknown file");
+ let mut buf = Vec::new();
+ fd.read_to_end(&mut buf).expect("File error");
+
+ let mut c = Cursor::new(&buf);
+ let context = mp4::read_mp4(&mut c).expect("read_mp4 failed");
+ for track in context.tracks {
+ let stsd = track.stsd.expect("expected an stsd");
+ let a = match stsd.descriptions.first().expect("expected a SampleEntry") {
+ mp4::SampleEntry::Audio(ref v) => v,
+ _ => panic!("expected a AudioSampleEntry"),
+ };
+ assert!(a.codec_type == mp4::CodecType::AMRWB);
+ let _codec_specific = match a.codec_specific {
+ mp4::AudioCodecSpecific::AMRSpecificBox(_) => true,
+ _ => {
+ panic!("expected a AMRSpecificBox",);
+ }
+ };
+ }
+}
+
+#[test]
+#[cfg(feature = "mp4v")]
+fn public_video_mp4v() {
+ let mut fd = File::open(VIDEO_MP4V_MP4).expect("Unknown file");
+ let mut buf = Vec::new();
+ fd.read_to_end(&mut buf).expect("File error");
+
+ let mut c = Cursor::new(&buf);
+ let context = mp4::read_mp4(&mut c).expect("read_mp4 failed");
+ for track in context.tracks {
+ let stsd = track.stsd.expect("expected an stsd");
+ let v = match stsd.descriptions.first().expect("expected a SampleEntry") {
+ mp4::SampleEntry::Video(ref v) => v,
+ _ => panic!("expected a VideoSampleEntry"),
+ };
+ assert_eq!(v.codec_type, mp4::CodecType::MP4V);
+ assert_eq!(v.width, 176);
+ assert_eq!(v.height, 144);
+ let _codec_specific = match v.codec_specific {
+ mp4::VideoCodecSpecific::ESDSConfig(_) => true,
+ _ => {
+ panic!("expected a ESDSConfig",);
+ }
+ };
+ }
+}
diff --git a/third_party/rust/mp4parse/tests/unknown_mdat.avif b/third_party/rust/mp4parse/tests/unknown_mdat.avif
new file mode 100644
index 0000000000..d10c7511d9
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/unknown_mdat.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/unknown_mdat_in_oversized_meta.avif b/third_party/rust/mp4parse/tests/unknown_mdat_in_oversized_meta.avif
new file mode 100644
index 0000000000..a291bda7b3
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/unknown_mdat_in_oversized_meta.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/valid-alpha.avif b/third_party/rust/mp4parse/tests/valid-alpha.avif
new file mode 100644
index 0000000000..b192607495
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/valid-alpha.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/valid-avif-colr-nclx-and-prof-and-rICC.avif b/third_party/rust/mp4parse/tests/valid-avif-colr-nclx-and-prof-and-rICC.avif
new file mode 100644
index 0000000000..b9fe1853ff
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/valid-avif-colr-nclx-and-prof-and-rICC.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/valid-avif-colr-nclx-and-prof.avif b/third_party/rust/mp4parse/tests/valid-avif-colr-nclx-and-prof.avif
new file mode 100644
index 0000000000..683baa7f54
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/valid-avif-colr-nclx-and-prof.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/valid-avif-colr-nclx-and-rICC.avif b/third_party/rust/mp4parse/tests/valid-avif-colr-nclx-and-rICC.avif
new file mode 100644
index 0000000000..eea6c788a7
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/valid-avif-colr-nclx-and-rICC.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/valid-avif-colr-nclx.avif b/third_party/rust/mp4parse/tests/valid-avif-colr-nclx.avif
new file mode 100644
index 0000000000..3aa4b8f5bb
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/valid-avif-colr-nclx.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/valid-avif-colr-prof-and-rICC.avif b/third_party/rust/mp4parse/tests/valid-avif-colr-prof-and-rICC.avif
new file mode 100644
index 0000000000..a4609aa93e
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/valid-avif-colr-prof-and-rICC.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/valid-avif-colr-prof.avif b/third_party/rust/mp4parse/tests/valid-avif-colr-prof.avif
new file mode 100644
index 0000000000..2ee8fa86b3
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/valid-avif-colr-prof.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/valid-avif-colr-rICC.avif b/third_party/rust/mp4parse/tests/valid-avif-colr-rICC.avif
new file mode 100644
index 0000000000..1da111942e
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/valid-avif-colr-rICC.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/valid.avif b/third_party/rust/mp4parse/tests/valid.avif
new file mode 100644
index 0000000000..d576365b6e
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/valid.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/valid_with_garbage_byte.avif b/third_party/rust/mp4parse/tests/valid_with_garbage_byte.avif
new file mode 100644
index 0000000000..d56ae6e41e
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/valid_with_garbage_byte.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/valid_with_garbage_overread.avif b/third_party/rust/mp4parse/tests/valid_with_garbage_overread.avif
new file mode 100644
index 0000000000..35958fac30
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/valid_with_garbage_overread.avif
Binary files differ
diff --git a/third_party/rust/mp4parse/tests/wide_box_size_0.avif b/third_party/rust/mp4parse/tests/wide_box_size_0.avif
new file mode 100644
index 0000000000..3f01f6d7f5
--- /dev/null
+++ b/third_party/rust/mp4parse/tests/wide_box_size_0.avif
Binary files differ
diff --git a/third_party/rust/mp4parse_capi/.cargo-checksum.json b/third_party/rust/mp4parse_capi/.cargo-checksum.json
new file mode 100644
index 0000000000..b140363588
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/.cargo-checksum.json
@@ -0,0 +1 @@
+{"files":{"Cargo.toml":"6b2b4576bf1bca3cf6b019f297a84189a3dd595353c451a4af38246ec14bcbef","LICENSE":"fab3dd6bdab226f1c08630b1dd917e11fcb4ec5e1e020e2c16f83a0a13863e85","README.md":"f776ed4bbb7b58a5684402a9c5c28dfe1fa02b6b184139b2c2c49384cc1e3723","cbindgen.toml":"62066cd34285ab9e7f1cc5db8950a51e9e080f5a85bd55ad43d7022e4eae2758","examples/dump.rs":"2a3cdebc5ed6f0f6b640e6722cd13fc7f4534774eb057b369a791c2eddb8132d","src/lib.rs":"4761968b061a78ea849ce3e9fdf3009dbddd4c6b0026a765f0e959b30233017a","tests/loop_1.avif":"bf8777d103e023a0da94f16dbe89b7081d13fe8b50e6418c69381a23561a5a84","tests/loop_2.avif":"be5f53417f8432342c0e0cbace60e33a791f2e98a03a44c4a9f5af3ec0bf6906","tests/loop_ceiled_4.avif":"136c36b7d2f5839c9107e56b6e3ce327d8b97bea291756f0ce377e6011716712","tests/loop_forever.avif":"aa425b4da99646d3b71d63f6b1741b60a2331ee2c9ab3bcf2fbc05b0cc832565","tests/no_edts.avif":"8940e97673d7d67c283a0f8f52fdbc45a9b74d5fb6c430507d406b70e4bbfe5f","tests/test_avis.rs":"d480b104ab2dfde7a25afd6705532caf7988aea21fc955dcf2f86fc8a5e85151","tests/test_chunk_out_of_range.rs":"4039d0db0ee5973787e4ca14cea510fd958ae5d21856a79240a5e7b826caa18d","tests/test_encryption.rs":"f62131a36b0516caf9e2c48f8aea060d300b0f5c8a32bc54d31cbc97aa25b4e6","tests/test_fragment.rs":"d3f805cc2107481ee9a989818af3addbb3ea1faf7422ea7f4416591d03031318","tests/test_rotation.rs":"23fa4898eca2e17255bc1ba2f538707a6554fb4644bb75f80548ae56a7cd2d44","tests/test_sample_table.rs":"53ed6a5e8db463ad8dc0300116f470c2aadd39896e6ba4cabcd01c6b9a7b5c59","tests/test_workaround_stsc.rs":"1d17a394f55e1524c30888bfe1e57e2b0457444b79c23eb91b02d2edf859c9ad"},"package":null} \ No newline at end of file
diff --git a/third_party/rust/mp4parse_capi/Cargo.toml b/third_party/rust/mp4parse_capi/Cargo.toml
new file mode 100644
index 0000000000..192097dbed
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/Cargo.toml
@@ -0,0 +1,54 @@
+# 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_capi"
+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"]
+description = "Parser for ISO base media file format (mp4)"
+documentation = "https://docs.rs/mp4parse_capi/"
+readme = "README.md"
+license = "MPL-2.0"
+repository = "https://github.com/mozilla/mp4parse-rust"
+
+[dependencies]
+byteorder = "1.2.1"
+log = "0.4"
+num-traits = "0.2.14"
+
+[dependencies.fallible_collections]
+version = "0.4"
+features = ["std_io"]
+
+[dependencies.mp4parse]
+version = "0.17.0"
+path = "../mp4parse"
+features = ["unstable-api"]
+
+[dev-dependencies]
+env_logger = "0.9"
+
+[features]
+3gpp = ["mp4parse/3gpp"]
+meta-xml = ["mp4parse/meta-xml"]
+missing-pixi-permitted = ["mp4parse/missing-pixi-permitted"]
+mp4v = ["mp4parse/mp4v"]
+
+[badges.travis-ci]
+repository = "https://github.com/mozilla/mp4parse-rust"
diff --git a/third_party/rust/mp4parse_capi/LICENSE b/third_party/rust/mp4parse_capi/LICENSE
new file mode 100644
index 0000000000..14e2f777f6
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/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_capi/README.md b/third_party/rust/mp4parse_capi/README.md
new file mode 100644
index 0000000000..de01503734
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/README.md
@@ -0,0 +1,2 @@
+`mp4parse-capi` is a C API that exposes the functionality of `mp4parse`.
+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_capi/cbindgen.toml b/third_party/rust/mp4parse_capi/cbindgen.toml
new file mode 100644
index 0000000000..858d09921a
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/cbindgen.toml
@@ -0,0 +1,45 @@
+header = """/* 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/. */"""
+autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. */
+#ifndef mp4parse_rust_mp4parse_h
+#error "Don't include this file directly, instead include mp4parse.h"
+#endif
+"""
+include_version = true
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+language = "C"
+cpp_compat = true
+
+[enum]
+rename_variants = "QualifiedScreamingSnakeCase"
+
+[defines]
+"feature = 3gpp" = "MP4PARSE_FEATURE_3GPP"
+"feature = meta-xml" = "MP4PARSE_FEATURE_META_XML"
+"feature = unstable-api" = "MP4PARSE_UNSTABLE_API"
+
+[parse]
+parse_deps = true
+include = ["mp4parse"]
+
+[export]
+# `mp4parse::Status` was previously defined as `mp4parse_capi::Mp4parseStatus`,
+# but now is referenced from `mp4parse_capi` via `pub use mp4parse::Status as Mp4parseStatus`,
+# the name `Status` does not appear in the public C API, so we must force its inclusion.
+# Similarly, we must force the inclusion of `mp4parse::Feature` as well.
+include = ["Status", "Feature"]
+
+[export.rename]
+# We need to declare these types in mp4parse, but we rename them in the generated
+# header to match mp4parse_capi naming conventions
+"Status" = "Mp4parseStatus"
+"Feature" = "Mp4parseFeature"
+"ParseStrictness" = "Mp4parseStrictness"
+"ImageSpatialExtentsProperty" = "Mp4parseImageSpatialExtents"
+"ImageRotation" = "Mp4parseIrot"
+"ImageMirror" = "Mp4parseImir"
+"Indice" = "Mp4parseIndice"
+"NclxColourInformation" = "Mp4parseNclxColourInformation"
diff --git a/third_party/rust/mp4parse_capi/examples/dump.rs b/third_party/rust/mp4parse_capi/examples/dump.rs
new file mode 100644
index 0000000000..86cf9acdb5
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/examples/dump.rs
@@ -0,0 +1,200 @@
+use log::info;
+use mp4parse::ParseStrictness;
+use mp4parse_capi::*;
+use std::env;
+use std::fs::File;
+use std::io::Read;
+
+extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
+ let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
+ let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+fn dump_avif(filename: &str, strictness: ParseStrictness) {
+ let mut file = File::open(filename).expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let rv = mp4parse_avif_new(&io, strictness, &mut parser);
+ println!("mp4parse_avif_new -> {rv:?}");
+ if rv == Mp4parseStatus::Ok {
+ println!(
+ "mp4parse_avif_get_image_safe -> {:?}",
+ mp4parse_avif_get_image_safe(&*parser)
+ );
+ }
+ }
+}
+
+fn dump_file(filename: &str, strictness: ParseStrictness) {
+ let mut file = File::open(filename).expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let rv = mp4parse_new(&io, &mut parser);
+
+ match rv {
+ Mp4parseStatus::Ok => (),
+ Mp4parseStatus::Invalid => {
+ println!("-- failed to parse as mp4 video, trying AVIF");
+ dump_avif(filename, strictness);
+ }
+ _ => {
+ println!("-- fail to parse: {rv:?}, '-v' for more info");
+ return;
+ }
+ }
+
+ let mut frag_info = Mp4parseFragmentInfo::default();
+ match mp4parse_get_fragment_info(parser, &mut frag_info) {
+ Mp4parseStatus::Ok => {
+ println!("-- mp4parse_fragment_info {frag_info:?}");
+ }
+ rv => {
+ println!("-- mp4parse_fragment_info failed with {rv:?}");
+ return;
+ }
+ }
+
+ let mut counts: u32 = 0;
+ match mp4parse_get_track_count(parser, &mut counts) {
+ Mp4parseStatus::Ok => (),
+ _ => {
+ println!("-- mp4parse_get_track_count failed");
+ return;
+ }
+ }
+
+ for i in 0..counts {
+ let mut track_info = Mp4parseTrackInfo {
+ track_type: Mp4parseTrackType::Audio,
+ ..Default::default()
+ };
+ match mp4parse_get_track_info(parser, i, &mut track_info) {
+ Mp4parseStatus::Ok => {
+ println!("-- mp4parse_get_track_info {track_info:?}");
+ }
+ _ => {
+ println!("-- mp4parse_get_track_info failed, track id: {i}");
+ return;
+ }
+ }
+
+ match track_info.track_type {
+ Mp4parseTrackType::Audio => {
+ let mut audio_info = Mp4parseTrackAudioInfo::default();
+ match mp4parse_get_track_audio_info(parser, i, &mut audio_info) {
+ Mp4parseStatus::Ok => {
+ println!("-- mp4parse_get_track_audio_info {audio_info:?}");
+ for i in 0..audio_info.sample_info_count as isize {
+ let sample_info = audio_info.sample_info.offset(i);
+ println!(
+ " -- mp4parse_get_track_audio_info sample_info[{:?}] {:?}",
+ i, *sample_info
+ );
+ }
+ }
+ _ => {
+ println!("-- mp4parse_get_track_audio_info failed, track id: {i}");
+ return;
+ }
+ }
+ }
+ Mp4parseTrackType::Video
+ | Mp4parseTrackType::Picture
+ | Mp4parseTrackType::AuxiliaryVideo => {
+ let mut video_info = Mp4parseTrackVideoInfo::default();
+ match mp4parse_get_track_video_info(parser, i, &mut video_info) {
+ Mp4parseStatus::Ok => {
+ println!("-- mp4parse_get_track_video_info {video_info:?}");
+ for i in 0..video_info.sample_info_count as isize {
+ let sample_info = video_info.sample_info.offset(i);
+ println!(
+ " -- mp4parse_get_track_video_info sample_info[{:?}] {:?}",
+ i, *sample_info
+ );
+ }
+ }
+ _ => {
+ println!("-- mp4parse_get_track_video_info failed, track id: {i}");
+ return;
+ }
+ }
+ }
+ Mp4parseTrackType::Metadata => {
+ println!("TODO metadata track");
+ }
+ }
+
+ let mut indices = Mp4parseByteData::default();
+ match mp4parse_get_indice_table(parser, track_info.track_id, &mut indices) {
+ Mp4parseStatus::Ok => {
+ println!(
+ "-- mp4parse_get_indice_table track_id {} indices {:?}",
+ track_info.track_id, indices
+ );
+ }
+ _ => {
+ println!(
+ "-- mp4parse_get_indice_table failed, track_info.track_id: {}",
+ track_info.track_id
+ );
+ return;
+ }
+ }
+ }
+ mp4parse_free(parser);
+ }
+}
+
+fn main() {
+ let mut dump_func: fn(&str, ParseStrictness) = dump_file;
+ let mut verbose = false;
+ let mut strictness = ParseStrictness::Normal;
+ let mut filenames = Vec::new();
+ for arg in env::args().skip(1) {
+ match arg.as_str() {
+ "--avif" => dump_func = dump_avif,
+ "--strict" => strictness = ParseStrictness::Strict,
+ "--permissive" => strictness = ParseStrictness::Permissive,
+ "-v" | "--verbose" => verbose = true,
+ _ => {
+ if let Some("-") = arg.get(0..1) {
+ eprintln!("Ignoring unknown switch {arg:?}");
+ } else {
+ filenames.push(arg)
+ }
+ }
+ }
+ }
+
+ if filenames.is_empty() {
+ eprintln!("No files to dump, exiting...");
+ return;
+ }
+
+ let env = env_logger::Env::default();
+ let mut logger = env_logger::Builder::from_env(env);
+ if verbose {
+ logger.filter(None, log::LevelFilter::Debug);
+ }
+ logger.init();
+
+ for filename in filenames {
+ info!("-- dump of '{}' --", filename);
+ dump_func(filename.as_str(), strictness);
+ info!("-- end of '{}' --", filename);
+ }
+}
diff --git a/third_party/rust/mp4parse_capi/src/lib.rs b/third_party/rust/mp4parse_capi/src/lib.rs
new file mode 100644
index 0000000000..5b362f303f
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/src/lib.rs
@@ -0,0 +1,1863 @@
+//! C API for mp4parse module.
+//!
+//! Parses ISO Base Media Format aka video/mp4 streams.
+//!
+//! # Examples
+//!
+//! ```rust
+//! use std::io::Read;
+//!
+//! extern fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
+//! let mut input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
+//! let mut buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+//! match input.read(&mut buf) {
+//! Ok(n) => n as isize,
+//! Err(_) => -1,
+//! }
+//! }
+//! let capi_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
+//! let mut file = std::fs::File::open(capi_dir + "/../mp4parse/tests/minimal.mp4").unwrap();
+//! let io = mp4parse_capi::Mp4parseIo {
+//! read: Some(buf_read),
+//! userdata: &mut file as *mut _ as *mut std::os::raw::c_void
+//! };
+//! let mut parser = std::ptr::null_mut();
+//! unsafe {
+//! let rv = mp4parse_capi::mp4parse_new(&io, &mut parser);
+//! assert_eq!(rv, mp4parse_capi::Mp4parseStatus::Ok);
+//! assert!(!parser.is_null());
+//! mp4parse_capi::mp4parse_free(parser);
+//! }
+//! ```
+
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+use byteorder::WriteBytesExt;
+use std::convert::TryFrom;
+use std::convert::TryInto;
+
+use std::io::Read;
+
+// Symbols we need from our rust api.
+use mp4parse::serialize_opus_header;
+use mp4parse::unstable::{create_sample_table, CheckedInteger, Indice};
+use mp4parse::AV1ConfigBox;
+use mp4parse::AudioCodecSpecific;
+use mp4parse::AvifContext;
+use mp4parse::CodecType;
+use mp4parse::MediaContext;
+// Re-exported so consumers don't have to depend on mp4parse as well
+pub use mp4parse::ParseStrictness;
+use mp4parse::SampleEntry;
+pub use mp4parse::Status as Mp4parseStatus;
+use mp4parse::Track;
+use mp4parse::TrackType;
+use mp4parse::TryBox;
+use mp4parse::TryHashMap;
+use mp4parse::TryVec;
+use mp4parse::VideoCodecSpecific;
+
+// To ensure we don't use stdlib allocating types by accident
+#[allow(dead_code)]
+struct Vec;
+#[allow(dead_code)]
+struct Box;
+#[allow(dead_code)]
+struct HashMap;
+#[allow(dead_code)]
+struct String;
+
+#[repr(C)]
+#[derive(PartialEq, Eq, Debug, Default)]
+pub enum Mp4parseTrackType {
+ #[default]
+ Video = 0,
+ Picture = 1,
+ AuxiliaryVideo = 2,
+ Audio = 3,
+ Metadata = 4,
+}
+
+#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
+#[repr(C)]
+#[derive(PartialEq, Eq, Debug, Default)]
+pub enum Mp4parseCodec {
+ #[default]
+ Unknown,
+ Aac,
+ Flac,
+ Opus,
+ Avc,
+ Vp9,
+ Av1,
+ Mp3,
+ Mp4v,
+ Jpeg, // for QT JPEG atom in video track
+ Ac3,
+ Ec3,
+ Alac,
+ H263,
+ #[cfg(feature = "3gpp")]
+ AMRNB,
+ #[cfg(feature = "3gpp")]
+ AMRWB,
+}
+
+#[repr(C)]
+#[derive(PartialEq, Eq, Debug, Default)]
+pub enum Mp4ParseEncryptionSchemeType {
+ #[default]
+ None,
+ Cenc,
+ Cbc1,
+ Cens,
+ Cbcs,
+ // Schemes also have a version component. At the time of writing, this does
+ // not impact handling, so we do not expose it. Note that this may need to
+ // be exposed in future, should the spec change.
+}
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub struct Mp4parseTrackInfo {
+ pub track_type: Mp4parseTrackType,
+ pub track_id: u32,
+ pub duration: u64,
+ pub media_time: CheckedInteger<i64>,
+ pub time_scale: u32,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct Mp4parseByteData {
+ pub length: usize,
+ // cheddar can't handle generic type, so it needs to be multiple data types here.
+ pub data: *const u8,
+ pub indices: *const Indice,
+}
+
+impl Mp4parseByteData {
+ fn with_data(slice: &[u8]) -> Self {
+ Self {
+ length: slice.len(),
+ data: if !slice.is_empty() {
+ slice.as_ptr()
+ } else {
+ std::ptr::null()
+ },
+ indices: std::ptr::null(),
+ }
+ }
+}
+
+impl Default for Mp4parseByteData {
+ fn default() -> Self {
+ Self {
+ length: 0,
+ data: std::ptr::null(),
+ indices: std::ptr::null(),
+ }
+ }
+}
+
+impl Mp4parseByteData {
+ fn set_data(&mut self, data: &[u8]) {
+ self.length = data.len();
+ self.data = data.as_ptr();
+ }
+
+ fn set_indices(&mut self, data: &[Indice]) {
+ self.length = data.len();
+ self.indices = data.as_ptr();
+ }
+}
+
+#[repr(C)]
+#[derive(Default)]
+pub struct Mp4parsePsshInfo {
+ pub data: Mp4parseByteData,
+}
+
+#[repr(u8)]
+#[derive(Debug, PartialEq, Eq)]
+pub enum OptionalFourCc {
+ None,
+ Some([u8; 4]),
+}
+
+impl Default for OptionalFourCc {
+ fn default() -> Self {
+ Self::None
+ }
+}
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub struct Mp4parseSinfInfo {
+ pub original_format: OptionalFourCc,
+ pub scheme_type: Mp4ParseEncryptionSchemeType,
+ pub is_encrypted: u8,
+ pub iv_size: u8,
+ pub kid: Mp4parseByteData,
+ // Members for pattern encryption schemes, may be 0 (u8) or empty
+ // (Mp4parseByteData) if pattern encryption is not in use
+ pub crypt_byte_block: u8,
+ pub skip_byte_block: u8,
+ pub constant_iv: Mp4parseByteData,
+ // End pattern encryption scheme members
+}
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub struct Mp4parseTrackAudioSampleInfo {
+ pub codec_type: Mp4parseCodec,
+ pub channels: u16,
+ pub bit_depth: u16,
+ pub sample_rate: u32,
+ pub profile: u16,
+ pub extended_profile: u16,
+ pub codec_specific_config: Mp4parseByteData,
+ pub extra_data: Mp4parseByteData,
+ pub protected_data: Mp4parseSinfInfo,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct Mp4parseTrackAudioInfo {
+ pub sample_info_count: u32,
+ pub sample_info: *const Mp4parseTrackAudioSampleInfo,
+}
+
+impl Default for Mp4parseTrackAudioInfo {
+ fn default() -> Self {
+ Self {
+ sample_info_count: 0,
+ sample_info: std::ptr::null(),
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub struct Mp4parseTrackVideoSampleInfo {
+ pub codec_type: Mp4parseCodec,
+ pub image_width: u16,
+ pub image_height: u16,
+ pub extra_data: Mp4parseByteData,
+ pub protected_data: Mp4parseSinfInfo,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct Mp4parseTrackVideoInfo {
+ pub display_width: u32,
+ pub display_height: u32,
+ pub rotation: u16,
+ pub sample_info_count: u32,
+ pub sample_info: *const Mp4parseTrackVideoSampleInfo,
+}
+
+impl Default for Mp4parseTrackVideoInfo {
+ fn default() -> Self {
+ Self {
+ display_width: 0,
+ display_height: 0,
+ rotation: 0,
+ sample_info_count: 0,
+ sample_info: std::ptr::null(),
+ }
+ }
+}
+
+#[repr(C)]
+#[derive(Default, Debug)]
+pub struct Mp4parseFragmentInfo {
+ pub fragment_duration: u64, // in ticks
+ pub time_scale: u64,
+ // TODO:
+ // info in trex box.
+}
+
+#[derive(Default)]
+pub struct Mp4parseParser {
+ context: MediaContext,
+ opus_header: TryHashMap<u32, TryVec<u8>>,
+ pssh_data: TryVec<u8>,
+ sample_table: TryHashMap<u32, TryVec<Indice>>,
+ // Store a mapping from track index (not id) to associated sample
+ // descriptions. Because each track has a variable number of sample
+ // descriptions, and because we need the data to live long enough to be
+ // copied out by callers, we store these on the parser struct.
+ audio_track_sample_descriptions: TryHashMap<u32, TryVec<Mp4parseTrackAudioSampleInfo>>,
+ video_track_sample_descriptions: TryHashMap<u32, TryVec<Mp4parseTrackVideoSampleInfo>>,
+}
+
+#[repr(C)]
+#[derive(Debug, Default)]
+pub enum Mp4parseAvifLoopMode {
+ #[default]
+ NoEdits,
+ LoopByCount,
+ LoopInfinitely,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct Mp4parseAvifInfo {
+ pub premultiplied_alpha: bool,
+ pub major_brand: [u8; 4],
+ pub unsupported_features_bitfield: u32,
+ /// The size of the image; should never be null unless using permissive parsing
+ pub spatial_extents: *const mp4parse::ImageSpatialExtentsProperty,
+ pub nclx_colour_information: *const mp4parse::NclxColourInformation,
+ pub icc_colour_information: Mp4parseByteData,
+ pub image_rotation: mp4parse::ImageRotation,
+ pub image_mirror: *const mp4parse::ImageMirror,
+ pub pixel_aspect_ratio: *const mp4parse::PixelAspectRatio,
+
+ /// Whether there is a `pitm` reference to the color image present.
+ pub has_primary_item: bool,
+ /// Bit depth for the item referenced by `pitm`, or 0 if values are inconsistent.
+ pub primary_item_bit_depth: u8,
+ /// Whether there is an `auxl` reference to the `pitm`-accompanying
+ /// alpha image present.
+ pub has_alpha_item: bool,
+ /// Bit depth for the alpha item used by the `pitm`, or 0 if values are inconsistent.
+ pub alpha_item_bit_depth: u8,
+
+ /// Whether there is a sequence. Can be true with no primary image.
+ pub has_sequence: bool,
+ /// Indicates whether the EditListBox requests that the image be looped.
+ pub loop_mode: Mp4parseAvifLoopMode,
+ /// Number of times to loop the animation during playback.
+ ///
+ /// The duration of the animation specified in `elst` must be looped to fill the
+ /// duration of the color track. If the resulting loop count is not an integer,
+ /// then it will be ceiled to play past and fill the entire track's duration.
+ pub loop_count: u64,
+ /// The color track's ID, which must be valid if has_sequence is true.
+ pub color_track_id: u32,
+ pub color_track_bit_depth: u8,
+ /// The track ID of the alpha track, will be 0 if no alpha track is present.
+ pub alpha_track_id: u32,
+ pub alpha_track_bit_depth: u8,
+}
+
+#[repr(C)]
+#[derive(Debug)]
+pub struct Mp4parseAvifImage {
+ pub primary_image: Mp4parseByteData,
+ /// If no alpha item exists, members' `.length` will be 0 and `.data` will be null
+ pub alpha_image: Mp4parseByteData,
+}
+
+/// A unified interface for the parsers which have different contexts, but
+/// share the same pattern of construction. This allows unification of
+/// argument validation from C and minimizes the surface of unsafe code.
+trait ContextParser
+where
+ Self: Sized,
+{
+ type Context;
+
+ fn with_context(context: Self::Context) -> Self;
+
+ fn read<T: Read>(io: &mut T, strictness: ParseStrictness) -> mp4parse::Result<Self::Context>;
+}
+
+impl Mp4parseParser {
+ fn context(&self) -> &MediaContext {
+ &self.context
+ }
+
+ fn context_mut(&mut self) -> &mut MediaContext {
+ &mut self.context
+ }
+}
+
+impl ContextParser for Mp4parseParser {
+ type Context = MediaContext;
+
+ fn with_context(context: Self::Context) -> Self {
+ Self {
+ context,
+ ..Default::default()
+ }
+ }
+
+ fn read<T: Read>(io: &mut T, _strictness: ParseStrictness) -> mp4parse::Result<Self::Context> {
+ let r = mp4parse::read_mp4(io);
+ log::debug!("mp4parse::read_mp4 -> {:?}", r);
+ r
+ }
+}
+
+#[derive(Default)]
+pub struct Mp4parseAvifParser {
+ context: AvifContext,
+ sample_table: TryHashMap<u32, TryVec<Indice>>,
+}
+
+impl Mp4parseAvifParser {
+ fn context(&self) -> &AvifContext {
+ &self.context
+ }
+}
+
+impl ContextParser for Mp4parseAvifParser {
+ type Context = AvifContext;
+
+ fn with_context(context: Self::Context) -> Self {
+ Self {
+ context,
+ ..Default::default()
+ }
+ }
+
+ fn read<T: Read>(io: &mut T, strictness: ParseStrictness) -> mp4parse::Result<Self::Context> {
+ let r = mp4parse::read_avif(io, strictness);
+ if r.is_err() {
+ log::debug!("{:?}", r);
+ }
+ log::trace!("mp4parse::read_avif -> {:?}", r);
+ r
+ }
+}
+
+#[repr(C)]
+#[derive(Clone)]
+pub struct Mp4parseIo {
+ pub read: Option<
+ extern "C" fn(buffer: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize,
+ >,
+ pub userdata: *mut std::os::raw::c_void,
+}
+
+impl Read for Mp4parseIo {
+ fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
+ if buf.len() > isize::max_value() as usize {
+ return Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "buf length overflow in Mp4parseIo Read impl",
+ ));
+ }
+ let rv = self.read.unwrap()(buf.as_mut_ptr(), buf.len(), self.userdata);
+ if rv >= 0 {
+ Ok(rv as usize)
+ } else {
+ Err(std::io::Error::new(
+ std::io::ErrorKind::Other,
+ "I/O error in Mp4parseIo Read impl",
+ ))
+ }
+ }
+}
+
+// C API wrapper functions.
+
+/// Allocate an `Mp4parseParser*` to read from the supplied `Mp4parseIo` and
+/// parse the content from the `Mp4parseIo` argument until EOF or error.
+///
+/// # Safety
+///
+/// This function is unsafe because it dereferences the `io` and `parser_out`
+/// pointers given to it. The caller should ensure that the `Mp4ParseIo`
+/// struct passed in is a valid pointer. The caller should also ensure the
+/// members of io are valid: the `read` function should be sanely implemented,
+/// and the `userdata` pointer should be valid. The `parser_out` should be a
+/// valid pointer to a location containing a null pointer. Upon successful
+/// return (`Mp4parseStatus::Ok`), that location will contain the address of
+/// an `Mp4parseParser` allocated by this function.
+///
+/// To avoid leaking memory, any successful return of this function must be
+/// paired with a call to `mp4parse_free`. In the event of error, no memory
+/// will be allocated and `mp4parse_free` must *not* be called.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_new(
+ io: *const Mp4parseIo,
+ parser_out: *mut *mut Mp4parseParser,
+) -> Mp4parseStatus {
+ mp4parse_new_common(io, ParseStrictness::Normal, parser_out)
+}
+
+/// Allocate an `Mp4parseAvifParser*` to read from the supplied `Mp4parseIo`.
+///
+/// See mp4parse_new; this function is identical except that it allocates an
+/// `Mp4parseAvifParser`, which (when successful) must be paired with a call
+/// to mp4parse_avif_free.
+///
+/// # Safety
+///
+/// Same as mp4parse_new.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_avif_new(
+ io: *const Mp4parseIo,
+ strictness: ParseStrictness,
+ parser_out: *mut *mut Mp4parseAvifParser,
+) -> Mp4parseStatus {
+ mp4parse_new_common(io, strictness, parser_out)
+}
+
+unsafe fn mp4parse_new_common<P: ContextParser>(
+ io: *const Mp4parseIo,
+ strictness: ParseStrictness,
+ parser_out: *mut *mut P,
+) -> Mp4parseStatus {
+ // Validate arguments from C.
+ if io.is_null()
+ || (*io).userdata.is_null()
+ || (*io).read.is_none()
+ || parser_out.is_null()
+ || !(*parser_out).is_null()
+ {
+ Mp4parseStatus::BadArg
+ } else {
+ match mp4parse_new_common_safe(&mut (*io).clone(), strictness) {
+ Ok(parser) => {
+ *parser_out = parser;
+ Mp4parseStatus::Ok
+ }
+ Err(status) => status,
+ }
+ }
+}
+
+fn mp4parse_new_common_safe<T: Read, P: ContextParser>(
+ io: &mut T,
+ strictness: ParseStrictness,
+) -> Result<*mut P, Mp4parseStatus> {
+ P::read(io, strictness)
+ .map(P::with_context)
+ .and_then(|x| TryBox::try_new(x).map_err(mp4parse::Error::from))
+ .map(TryBox::into_raw)
+ .map_err(Mp4parseStatus::from)
+}
+
+/// Free an `Mp4parseParser*` allocated by `mp4parse_new()`.
+///
+/// # Safety
+///
+/// This function is unsafe because it creates a box from a raw pointer.
+/// Callers should ensure that the parser pointer points to a valid
+/// `Mp4parseParser` created by `mp4parse_new`.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_free(parser: *mut Mp4parseParser) {
+ assert!(!parser.is_null());
+ let _ = TryBox::from_raw(parser);
+}
+
+/// Free an `Mp4parseAvifParser*` allocated by `mp4parse_avif_new()`.
+///
+/// # Safety
+///
+/// This function is unsafe because it creates a box from a raw pointer.
+/// Callers should ensure that the parser pointer points to a valid
+/// `Mp4parseAvifParser` created by `mp4parse_avif_new`.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_avif_free(parser: *mut Mp4parseAvifParser) {
+ assert!(!parser.is_null());
+ let _ = TryBox::from_raw(parser);
+}
+
+/// Return the number of tracks parsed by previous `mp4parse_read()` call.
+///
+/// # Safety
+///
+/// This function is unsafe because it dereferences both the parser and count
+/// raw pointers passed into it. Callers should ensure the parser pointer
+/// points to a valid `Mp4parseParser`, and that the count pointer points an
+/// appropriate memory location to have a `u32` written to.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_get_track_count(
+ parser: *const Mp4parseParser,
+ count: *mut u32,
+) -> Mp4parseStatus {
+ // Validate arguments from C.
+ if parser.is_null() || count.is_null() {
+ return Mp4parseStatus::BadArg;
+ }
+ let context = (*parser).context();
+
+ // Make sure the track count fits in a u32.
+ if context.tracks.len() > u32::max_value() as usize {
+ return Mp4parseStatus::Invalid;
+ }
+ *count = context.tracks.len() as u32;
+ Mp4parseStatus::Ok
+}
+
+/// Fill the supplied `Mp4parseTrackInfo` with metadata for `track`.
+///
+/// # Safety
+///
+/// This function is unsafe because it dereferences the the parser and info raw
+/// pointers passed to it. Callers should ensure the parser pointer points to a
+/// valid `Mp4parseParser` and that the info pointer points to a valid
+/// `Mp4parseTrackInfo`.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_get_track_info(
+ parser: *mut Mp4parseParser,
+ track_index: u32,
+ info: *mut Mp4parseTrackInfo,
+) -> Mp4parseStatus {
+ if parser.is_null() || info.is_null() {
+ return Mp4parseStatus::BadArg;
+ }
+
+ // Initialize fields to default values to ensure all fields are always valid.
+ *info = Default::default();
+
+ let context = (*parser).context_mut();
+ let track_index: usize = track_index as usize;
+ let info: &mut Mp4parseTrackInfo = &mut *info;
+
+ if track_index >= context.tracks.len() {
+ return Mp4parseStatus::BadArg;
+ }
+
+ info.track_type = match context.tracks[track_index].track_type {
+ TrackType::Video => Mp4parseTrackType::Video,
+ TrackType::Picture => Mp4parseTrackType::Picture,
+ TrackType::AuxiliaryVideo => Mp4parseTrackType::AuxiliaryVideo,
+ TrackType::Audio => Mp4parseTrackType::Audio,
+ TrackType::Metadata => Mp4parseTrackType::Metadata,
+ TrackType::Unknown => return Mp4parseStatus::Unsupported,
+ };
+
+ let track = &context.tracks[track_index];
+
+ if let (Some(timescale), Some(_)) = (track.timescale, context.timescale) {
+ info.time_scale = timescale.0 as u32;
+ let media_time: CheckedInteger<u64> = track
+ .media_time
+ .map_or(0.into(), |media_time| media_time.0.into());
+
+ let empty_duration: CheckedInteger<u64> = track
+ .empty_duration
+ .map_or(0.into(), |empty_duration| empty_duration.0.into());
+
+ info.media_time = match media_time - empty_duration {
+ Some(difference) => difference,
+ None => return Mp4parseStatus::Invalid,
+ };
+
+ match track.duration {
+ Some(duration) => info.duration = duration.0,
+ None => {
+ // Duration unknown; stagefright returns 0 for this.
+ info.duration = 0
+ }
+ }
+ } else {
+ return Mp4parseStatus::Invalid;
+ }
+
+ info.track_id = match track.track_id {
+ Some(track_id) => track_id,
+ None => return Mp4parseStatus::Invalid,
+ };
+ Mp4parseStatus::Ok
+}
+
+/// Fill the supplied `Mp4parseTrackAudioInfo` with metadata for `track`.
+///
+/// # Safety
+///
+/// This function is unsafe because it dereferences the the parser and info raw
+/// pointers passed to it. Callers should ensure the parser pointer points to a
+/// valid `Mp4parseParser` and that the info pointer points to a valid
+/// `Mp4parseTrackAudioInfo`.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_get_track_audio_info(
+ parser: *mut Mp4parseParser,
+ track_index: u32,
+ info: *mut Mp4parseTrackAudioInfo,
+) -> Mp4parseStatus {
+ if parser.is_null() || info.is_null() {
+ return Mp4parseStatus::BadArg;
+ }
+
+ // Initialize fields to default values to ensure all fields are always valid.
+ *info = Default::default();
+
+ get_track_audio_info(&mut *parser, track_index, &mut *info).into()
+}
+
+fn get_track_audio_info(
+ parser: &mut Mp4parseParser,
+ track_index: u32,
+ info: &mut Mp4parseTrackAudioInfo,
+) -> Result<(), Mp4parseStatus> {
+ let Mp4parseParser {
+ context,
+ opus_header,
+ ..
+ } = parser;
+
+ if track_index as usize >= context.tracks.len() {
+ return Err(Mp4parseStatus::BadArg);
+ }
+
+ let track = &context.tracks[track_index as usize];
+
+ if track.track_type != TrackType::Audio {
+ return Err(Mp4parseStatus::Invalid);
+ }
+
+ // Handle track.stsd
+ let stsd = match track.stsd {
+ Some(ref stsd) => stsd,
+ None => return Err(Mp4parseStatus::Invalid), // Stsd should be present
+ };
+
+ if stsd.descriptions.is_empty() {
+ return Err(Mp4parseStatus::Invalid); // Should have at least 1 description
+ }
+
+ let mut audio_sample_infos = TryVec::with_capacity(stsd.descriptions.len())?;
+ for description in stsd.descriptions.iter() {
+ let mut sample_info = Mp4parseTrackAudioSampleInfo::default();
+ let audio = match description {
+ SampleEntry::Audio(a) => a,
+ _ => return Err(Mp4parseStatus::Invalid),
+ };
+
+ // UNKNOWN for unsupported format.
+ sample_info.codec_type = match audio.codec_specific {
+ AudioCodecSpecific::OpusSpecificBox(_) => Mp4parseCodec::Opus,
+ AudioCodecSpecific::FLACSpecificBox(_) => Mp4parseCodec::Flac,
+ AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC => {
+ Mp4parseCodec::Aac
+ }
+ AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 => {
+ Mp4parseCodec::Mp3
+ }
+ AudioCodecSpecific::ES_Descriptor(_) | AudioCodecSpecific::LPCM => {
+ Mp4parseCodec::Unknown
+ }
+ AudioCodecSpecific::MP3 => Mp4parseCodec::Mp3,
+ AudioCodecSpecific::ALACSpecificBox(_) => Mp4parseCodec::Alac,
+ #[cfg(feature = "3gpp")]
+ AudioCodecSpecific::AMRSpecificBox(_) => {
+ if audio.codec_type == CodecType::AMRNB {
+ Mp4parseCodec::AMRNB
+ } else {
+ Mp4parseCodec::AMRWB
+ }
+ }
+ };
+ sample_info.channels = audio.channelcount as u16;
+ sample_info.bit_depth = audio.samplesize;
+ sample_info.sample_rate = audio.samplerate as u32;
+ // sample_info.profile is handled below on a per case basis
+
+ match audio.codec_specific {
+ AudioCodecSpecific::ES_Descriptor(ref esds) => {
+ if esds.codec_esds.len() > std::u32::MAX as usize {
+ return Err(Mp4parseStatus::Invalid);
+ }
+ sample_info.extra_data.length = esds.codec_esds.len();
+ sample_info.extra_data.data = esds.codec_esds.as_ptr();
+ sample_info.codec_specific_config.length = esds.decoder_specific_data.len();
+ sample_info.codec_specific_config.data = esds.decoder_specific_data.as_ptr();
+ if let Some(rate) = esds.audio_sample_rate {
+ sample_info.sample_rate = rate;
+ }
+ if let Some(channels) = esds.audio_channel_count {
+ sample_info.channels = channels;
+ }
+ if let Some(profile) = esds.audio_object_type {
+ sample_info.profile = profile;
+ }
+ sample_info.extended_profile = match esds.extended_audio_object_type {
+ Some(extended_profile) => extended_profile,
+ _ => sample_info.profile,
+ };
+ }
+ AudioCodecSpecific::FLACSpecificBox(ref flac) => {
+ // Return the STREAMINFO metadata block in the codec_specific.
+ let streaminfo = &flac.blocks[0];
+ if streaminfo.block_type != 0 || streaminfo.data.len() != 34 {
+ return Err(Mp4parseStatus::Invalid);
+ }
+ sample_info.codec_specific_config.length = streaminfo.data.len();
+ sample_info.codec_specific_config.data = streaminfo.data.as_ptr();
+ }
+ AudioCodecSpecific::OpusSpecificBox(ref opus) => {
+ let mut v = TryVec::new();
+ match serialize_opus_header(opus, &mut v) {
+ Err(_) => {
+ return Err(Mp4parseStatus::Invalid);
+ }
+ Ok(_) => {
+ opus_header.insert(track_index, v)?;
+ if let Some(v) = opus_header.get(&track_index) {
+ if v.len() > std::u32::MAX as usize {
+ return Err(Mp4parseStatus::Invalid);
+ }
+ sample_info.codec_specific_config.length = v.len();
+ sample_info.codec_specific_config.data = v.as_ptr();
+ }
+ }
+ }
+ }
+ AudioCodecSpecific::ALACSpecificBox(ref alac) => {
+ sample_info.codec_specific_config.length = alac.data.len();
+ sample_info.codec_specific_config.data = alac.data.as_ptr();
+ }
+ AudioCodecSpecific::MP3 | AudioCodecSpecific::LPCM => (),
+ #[cfg(feature = "3gpp")]
+ AudioCodecSpecific::AMRSpecificBox(_) => (),
+ }
+
+ if let Some(p) = audio
+ .protection_info
+ .iter()
+ .find(|sinf| sinf.tenc.is_some())
+ {
+ sample_info.protected_data.original_format =
+ OptionalFourCc::Some(p.original_format.value);
+ sample_info.protected_data.scheme_type = match p.scheme_type {
+ Some(ref scheme_type_box) => {
+ match scheme_type_box.scheme_type.value.as_ref() {
+ b"cenc" => Mp4ParseEncryptionSchemeType::Cenc,
+ b"cbcs" => Mp4ParseEncryptionSchemeType::Cbcs,
+ // We don't support other schemes, and shouldn't reach
+ // this case. Try to gracefully handle by treating as
+ // no encryption case.
+ _ => Mp4ParseEncryptionSchemeType::None,
+ }
+ }
+ None => Mp4ParseEncryptionSchemeType::None,
+ };
+ if let Some(ref tenc) = p.tenc {
+ sample_info.protected_data.is_encrypted = tenc.is_encrypted;
+ sample_info.protected_data.iv_size = tenc.iv_size;
+ sample_info.protected_data.kid.set_data(&(tenc.kid));
+ sample_info.protected_data.crypt_byte_block =
+ tenc.crypt_byte_block_count.unwrap_or(0);
+ sample_info.protected_data.skip_byte_block =
+ tenc.skip_byte_block_count.unwrap_or(0);
+ if let Some(ref iv_vec) = tenc.constant_iv {
+ if iv_vec.len() > std::u32::MAX as usize {
+ return Err(Mp4parseStatus::Invalid);
+ }
+ sample_info.protected_data.constant_iv.set_data(iv_vec);
+ };
+ }
+ }
+ audio_sample_infos.push(sample_info)?;
+ }
+
+ parser
+ .audio_track_sample_descriptions
+ .insert(track_index, audio_sample_infos)?;
+ match parser.audio_track_sample_descriptions.get(&track_index) {
+ Some(sample_info) => {
+ if sample_info.len() > std::u32::MAX as usize {
+ // Should never happen due to upper limits on number of sample
+ // descriptions a track can have, but lets be safe.
+ return Err(Mp4parseStatus::Invalid);
+ }
+ info.sample_info_count = sample_info.len() as u32;
+ info.sample_info = sample_info.as_ptr();
+ }
+ None => return Err(Mp4parseStatus::Invalid), // Shouldn't happen, we just inserted the info!
+ }
+
+ Ok(())
+}
+
+/// Fill the supplied `Mp4parseTrackVideoInfo` with metadata for `track`.
+///
+/// # Safety
+///
+/// This function is unsafe because it dereferences the the parser and info raw
+/// pointers passed to it. Callers should ensure the parser pointer points to a
+/// valid `Mp4parseParser` and that the info pointer points to a valid
+/// `Mp4parseTrackVideoInfo`.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_get_track_video_info(
+ parser: *mut Mp4parseParser,
+ track_index: u32,
+ info: *mut Mp4parseTrackVideoInfo,
+) -> Mp4parseStatus {
+ if parser.is_null() || info.is_null() {
+ return Mp4parseStatus::BadArg;
+ }
+
+ // Initialize fields to default values to ensure all fields are always valid.
+ *info = Default::default();
+
+ mp4parse_get_track_video_info_safe(&mut *parser, track_index, &mut *info).into()
+}
+
+fn mp4parse_get_track_video_info_safe(
+ parser: &mut Mp4parseParser,
+ track_index: u32,
+ info: &mut Mp4parseTrackVideoInfo,
+) -> Result<(), Mp4parseStatus> {
+ let context = parser.context();
+
+ if track_index as usize >= context.tracks.len() {
+ return Err(Mp4parseStatus::BadArg);
+ }
+
+ let track = &context.tracks[track_index as usize];
+
+ if track.track_type != TrackType::Video {
+ return Err(Mp4parseStatus::Invalid);
+ }
+
+ // Handle track.tkhd
+ if let Some(ref tkhd) = track.tkhd {
+ info.display_width = tkhd.width >> 16; // 16.16 fixed point
+ info.display_height = tkhd.height >> 16; // 16.16 fixed point
+ let matrix = (
+ tkhd.matrix.a >> 16,
+ tkhd.matrix.b >> 16,
+ tkhd.matrix.c >> 16,
+ tkhd.matrix.d >> 16,
+ );
+ info.rotation = match matrix {
+ (0, 1, -1, 0) => 90, // rotate 90 degrees
+ (-1, 0, 0, -1) => 180, // rotate 180 degrees
+ (0, -1, 1, 0) => 270, // rotate 270 degrees
+ _ => 0,
+ };
+ } else {
+ return Err(Mp4parseStatus::Invalid);
+ }
+
+ // Handle track.stsd
+ let stsd = match track.stsd {
+ Some(ref stsd) => stsd,
+ None => return Err(Mp4parseStatus::Invalid), // Stsd should be present
+ };
+
+ if stsd.descriptions.is_empty() {
+ return Err(Mp4parseStatus::Invalid); // Should have at least 1 description
+ }
+
+ let mut video_sample_infos = TryVec::with_capacity(stsd.descriptions.len())?;
+ for description in stsd.descriptions.iter() {
+ let mut sample_info = Mp4parseTrackVideoSampleInfo::default();
+ let video = match description {
+ SampleEntry::Video(v) => v,
+ _ => return Err(Mp4parseStatus::Invalid),
+ };
+
+ // UNKNOWN for unsupported format.
+ sample_info.codec_type = match video.codec_specific {
+ VideoCodecSpecific::VPxConfig(_) => Mp4parseCodec::Vp9,
+ VideoCodecSpecific::AV1Config(_) => Mp4parseCodec::Av1,
+ VideoCodecSpecific::AVCConfig(_) => Mp4parseCodec::Avc,
+ VideoCodecSpecific::H263Config(_) => Mp4parseCodec::H263,
+ #[cfg(feature = "mp4v")]
+ VideoCodecSpecific::ESDSConfig(_) => Mp4parseCodec::Mp4v,
+ #[cfg(not(feature = "mp4v"))]
+ VideoCodecSpecific::ESDSConfig(_) =>
+ // MP4V (14496-2) video is unsupported.
+ {
+ Mp4parseCodec::Unknown
+ }
+ };
+ sample_info.image_width = video.width;
+ sample_info.image_height = video.height;
+
+ match video.codec_specific {
+ VideoCodecSpecific::AV1Config(ref config) => {
+ sample_info.extra_data.set_data(&config.raw_config);
+ }
+ VideoCodecSpecific::AVCConfig(ref data) | VideoCodecSpecific::ESDSConfig(ref data) => {
+ sample_info.extra_data.set_data(data);
+ }
+ _ => {}
+ }
+
+ if let Some(p) = video
+ .protection_info
+ .iter()
+ .find(|sinf| sinf.tenc.is_some())
+ {
+ sample_info.protected_data.original_format =
+ OptionalFourCc::Some(p.original_format.value);
+ sample_info.protected_data.scheme_type = match p.scheme_type {
+ Some(ref scheme_type_box) => {
+ match scheme_type_box.scheme_type.value.as_ref() {
+ b"cenc" => Mp4ParseEncryptionSchemeType::Cenc,
+ b"cbcs" => Mp4ParseEncryptionSchemeType::Cbcs,
+ // We don't support other schemes, and shouldn't reach
+ // this case. Try to gracefully handle by treating as
+ // no encryption case.
+ _ => Mp4ParseEncryptionSchemeType::None,
+ }
+ }
+ None => Mp4ParseEncryptionSchemeType::None,
+ };
+ if let Some(ref tenc) = p.tenc {
+ sample_info.protected_data.is_encrypted = tenc.is_encrypted;
+ sample_info.protected_data.iv_size = tenc.iv_size;
+ sample_info.protected_data.kid.set_data(&(tenc.kid));
+ sample_info.protected_data.crypt_byte_block =
+ tenc.crypt_byte_block_count.unwrap_or(0);
+ sample_info.protected_data.skip_byte_block =
+ tenc.skip_byte_block_count.unwrap_or(0);
+ if let Some(ref iv_vec) = tenc.constant_iv {
+ if iv_vec.len() > std::u32::MAX as usize {
+ return Err(Mp4parseStatus::Invalid);
+ }
+ sample_info.protected_data.constant_iv.set_data(iv_vec);
+ };
+ }
+ }
+ video_sample_infos.push(sample_info)?;
+ }
+
+ parser
+ .video_track_sample_descriptions
+ .insert(track_index, video_sample_infos)?;
+ match parser.video_track_sample_descriptions.get(&track_index) {
+ Some(sample_info) => {
+ if sample_info.len() > std::u32::MAX as usize {
+ // Should never happen due to upper limits on number of sample
+ // descriptions a track can have, but lets be safe.
+ return Err(Mp4parseStatus::Invalid);
+ }
+ info.sample_info_count = sample_info.len() as u32;
+ info.sample_info = sample_info.as_ptr();
+ }
+ None => return Err(Mp4parseStatus::Invalid), // Shouldn't happen, we just inserted the info!
+ }
+ Ok(())
+}
+
+/// Return a struct containing meta information read by previous
+/// `mp4parse_avif_new()` call.
+///
+/// `color_track_id`and `alpha_track_id` will be 0 if has_sequence is false.
+/// `alpha_track_id` will be 0 if no alpha aux track is present.
+///
+/// # Safety
+///
+/// This function is unsafe because it dereferences both the parser and
+/// avif_info raw pointers passed into it. Callers should ensure the parser
+/// pointer points to a valid `Mp4parseAvifParser`, and that the avif_info
+/// pointer points to a valid `Mp4parseAvifInfo`.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_avif_get_info(
+ parser: *const Mp4parseAvifParser,
+ avif_info: *mut Mp4parseAvifInfo,
+) -> Mp4parseStatus {
+ if parser.is_null() || avif_info.is_null() {
+ return Mp4parseStatus::BadArg;
+ }
+
+ if let Ok(info) = mp4parse_avif_get_info_safe((*parser).context()) {
+ *avif_info = info;
+ Mp4parseStatus::Ok
+ } else {
+ Mp4parseStatus::Invalid
+ }
+}
+
+fn mp4parse_avif_get_info_safe(context: &AvifContext) -> mp4parse::Result<Mp4parseAvifInfo> {
+ let info = Mp4parseAvifInfo {
+ premultiplied_alpha: context.premultiplied_alpha,
+ major_brand: context.major_brand.value,
+ unsupported_features_bitfield: context.unsupported_features.into_bitfield(),
+ spatial_extents: context.spatial_extents_ptr()?,
+ nclx_colour_information: context
+ .nclx_colour_information_ptr()
+ .unwrap_or(Ok(std::ptr::null()))?,
+ icc_colour_information: Mp4parseByteData::with_data(
+ context.icc_colour_information().unwrap_or(Ok(&[]))?,
+ ),
+ image_rotation: context.image_rotation()?,
+ image_mirror: context.image_mirror_ptr()?,
+ pixel_aspect_ratio: context.pixel_aspect_ratio_ptr()?,
+
+ has_primary_item: context.primary_item_is_present(),
+ primary_item_bit_depth: 0,
+ has_alpha_item: context.alpha_item_is_present(),
+ alpha_item_bit_depth: 0,
+
+ has_sequence: false,
+ loop_mode: Mp4parseAvifLoopMode::NoEdits,
+ loop_count: 0,
+ color_track_id: 0,
+ color_track_bit_depth: 0,
+ alpha_track_id: 0,
+ alpha_track_bit_depth: 0,
+ };
+
+ fn get_bit_depth(data: &[u8]) -> u8 {
+ if !data.is_empty() && data.iter().all(|v| *v == data[0]) {
+ data[0]
+ } else {
+ 0
+ }
+ }
+ let primary_item_bit_depth =
+ get_bit_depth(context.primary_item_bits_per_channel().unwrap_or(Ok(&[]))?);
+ let alpha_item_bit_depth =
+ get_bit_depth(context.alpha_item_bits_per_channel().unwrap_or(Ok(&[]))?);
+
+ if let Some(sequence) = &context.sequence {
+ // Tracks must have track_id and samples
+ fn get_track<T>(tracks: &TryVec<Track>, pred: T) -> Option<&Track>
+ where
+ T: Fn(&Track) -> bool,
+ {
+ tracks.iter().find(|track| {
+ if track.track_id.is_none() {
+ return false;
+ }
+ match &track.stsc {
+ Some(stsc) => {
+ if stsc.samples.is_empty() {
+ return false;
+ }
+ if !pred(track) {
+ return false;
+ }
+ stsc.samples.iter().any(|chunk| chunk.samples_per_chunk > 0)
+ }
+ _ => false,
+ }
+ })
+ }
+
+ // Color track will be the first track found
+ let color_track = match get_track(&sequence.tracks, |_| true) {
+ Some(v) => v,
+ _ => return Ok(info),
+ };
+
+ // Alpha track will be the first track found with auxl.aux_for_track_id set to color_track's id
+ let alpha_track = get_track(&sequence.tracks, |track| match &track.tref {
+ Some(tref) => tref.has_auxl_reference(color_track.track_id.unwrap()),
+ _ => false,
+ });
+
+ fn get_av1c(track: &Track) -> Option<&AV1ConfigBox> {
+ if let Some(stsd) = &track.stsd {
+ for entry in &stsd.descriptions {
+ if let SampleEntry::Video(video_entry) = entry {
+ if let VideoCodecSpecific::AV1Config(av1c) = &video_entry.codec_specific {
+ return Some(av1c);
+ }
+ }
+ }
+ }
+
+ None
+ }
+
+ let color_track_id = color_track.track_id.unwrap();
+ let color_track_bit_depth = match get_av1c(color_track) {
+ Some(av1c) => av1c.bit_depth,
+ _ => return Ok(info),
+ };
+
+ let (alpha_track_id, alpha_track_bit_depth) = match alpha_track {
+ Some(track) => (
+ track.track_id.unwrap(),
+ match get_av1c(track) {
+ Some(av1c) => av1c.bit_depth,
+ _ => return Ok(info),
+ },
+ ),
+ _ => (0, 0),
+ };
+
+ let (loop_mode, loop_count) = match color_track.tkhd.as_ref().map(|tkhd| tkhd.duration) {
+ Some(movie_duration) if movie_duration == std::u64::MAX => {
+ (Mp4parseAvifLoopMode::LoopInfinitely, 0)
+ }
+ Some(movie_duration) => match color_track.looped {
+ Some(true) => match color_track.edited_duration.map(|v| v.0) {
+ Some(segment_duration) => {
+ match movie_duration.checked_div(segment_duration).and_then(|n| {
+ match movie_duration.checked_rem(segment_duration) {
+ Some(0) => Some(n.saturating_sub(1)),
+ Some(_) => Some(n),
+ None => None,
+ }
+ }) {
+ Some(n) => (Mp4parseAvifLoopMode::LoopByCount, n),
+ None => (Mp4parseAvifLoopMode::LoopInfinitely, 0),
+ }
+ }
+ None => (Mp4parseAvifLoopMode::NoEdits, 0),
+ },
+ Some(false) => (Mp4parseAvifLoopMode::LoopByCount, 0),
+ None => (Mp4parseAvifLoopMode::NoEdits, 0),
+ },
+ None => (Mp4parseAvifLoopMode::LoopInfinitely, 0),
+ };
+
+ return Ok(Mp4parseAvifInfo {
+ primary_item_bit_depth,
+ alpha_item_bit_depth,
+ has_sequence: true,
+ loop_mode,
+ loop_count,
+ color_track_id,
+ color_track_bit_depth,
+ alpha_track_id,
+ alpha_track_bit_depth,
+ ..info
+ });
+ }
+
+ Ok(info)
+}
+
+/// Return a pointer to the primary item parsed by previous `mp4parse_avif_new()` call.
+///
+/// # Safety
+///
+/// This function is unsafe because it dereferences both the parser and
+/// avif_image raw pointers passed into it. Callers should ensure the parser
+/// pointer points to a valid `Mp4parseAvifParser`, and that the avif_image
+/// pointer points to a valid `Mp4parseAvifImage`. If there was not a previous
+/// successful call to `mp4parse_avif_read()`, no guarantees are made as to
+/// the state of `avif_image`. If `avif_image.alpha_image.coded_data` is set to
+/// a positive `length` and non-null `data`, then the `avif_image` contains a
+/// valid alpha channel data. Otherwise, the image is opaque.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_avif_get_image(
+ parser: *const Mp4parseAvifParser,
+ avif_image: *mut Mp4parseAvifImage,
+) -> Mp4parseStatus {
+ if parser.is_null() || avif_image.is_null() {
+ return Mp4parseStatus::BadArg;
+ }
+
+ if let Ok(image) = mp4parse_avif_get_image_safe(&*parser) {
+ *avif_image = image;
+ Mp4parseStatus::Ok
+ } else {
+ Mp4parseStatus::Invalid
+ }
+}
+
+pub fn mp4parse_avif_get_image_safe(
+ parser: &Mp4parseAvifParser,
+) -> mp4parse::Result<Mp4parseAvifImage> {
+ let context = parser.context();
+ Ok(Mp4parseAvifImage {
+ primary_image: Mp4parseByteData::with_data(
+ context.primary_item_coded_data().unwrap_or(&[]),
+ ),
+ alpha_image: Mp4parseByteData::with_data(context.alpha_item_coded_data().unwrap_or(&[])),
+ })
+}
+
+/// Fill the supplied `Mp4parseByteData` with index information from `track`.
+///
+/// # Safety
+///
+/// This function is unsafe because it dereferences the the parser and indices
+/// raw pointers passed to it. Callers should ensure the parser pointer points
+/// to a valid `Mp4parseParser` and that the indices pointer points to a valid
+/// `Mp4parseByteData`.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_get_indice_table(
+ parser: *mut Mp4parseParser,
+ track_id: u32,
+ indices: *mut Mp4parseByteData,
+) -> Mp4parseStatus {
+ if parser.is_null() {
+ return Mp4parseStatus::BadArg;
+ }
+
+ // Initialize fields to default values to ensure all fields are always valid.
+ *indices = Default::default();
+
+ get_indice_table(
+ &(*parser).context,
+ &mut (*parser).sample_table,
+ track_id,
+ &mut *indices,
+ )
+ .into()
+}
+
+/// Fill the supplied `Mp4parseByteData` with index information from `track`.
+///
+/// # Safety
+///
+/// This function is unsafe because it dereferences both the parser and
+/// indices raw pointers passed to it. Callers should ensure the parser
+/// points to a valid `Mp4parseAvifParser` and indices points to a valid
+/// `Mp4parseByteData`.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_avif_get_indice_table(
+ parser: *mut Mp4parseAvifParser,
+ track_id: u32,
+ indices: *mut Mp4parseByteData,
+ timescale: *mut u64,
+) -> Mp4parseStatus {
+ if parser.is_null() {
+ return Mp4parseStatus::BadArg;
+ }
+
+ if indices.is_null() {
+ return Mp4parseStatus::BadArg;
+ }
+
+ if timescale.is_null() {
+ return Mp4parseStatus::BadArg;
+ }
+
+ // Initialize fields to default values to ensure all fields are always valid.
+ *indices = Default::default();
+
+ if let Some(sequence) = &(*parser).context.sequence {
+ // Use the top level timescale, and the track timescale if present.
+ let mut found_timescale = false;
+ if let Some(context_timescale) = sequence.timescale {
+ *timescale = context_timescale.0;
+ found_timescale = true;
+ }
+ let maybe_track_timescale = match sequence
+ .tracks
+ .iter()
+ .find(|track| track.track_id == Some(track_id))
+ {
+ Some(track) => track.timescale,
+ _ => None,
+ };
+ if let Some(track_timescale) = maybe_track_timescale {
+ found_timescale = true;
+ *timescale = track_timescale.0;
+ }
+ if !found_timescale {
+ return Mp4parseStatus::Invalid;
+ }
+ return get_indice_table(
+ sequence,
+ &mut (*parser).sample_table,
+ track_id,
+ &mut *indices,
+ )
+ .into();
+ }
+
+ Mp4parseStatus::BadArg
+}
+
+fn get_indice_table(
+ context: &MediaContext,
+ sample_table_cache: &mut TryHashMap<u32, TryVec<Indice>>,
+ track_id: u32,
+ indices: &mut Mp4parseByteData,
+) -> Result<(), Mp4parseStatus> {
+ let tracks = &context.tracks;
+ let track = match tracks.iter().find(|track| track.track_id == Some(track_id)) {
+ Some(t) => t,
+ _ => return Err(Mp4parseStatus::Invalid),
+ };
+
+ if let Some(v) = sample_table_cache.get(&track_id) {
+ indices.set_indices(v);
+ return Ok(());
+ }
+
+ let media_time = match &track.media_time {
+ &Some(t) => i64::try_from(t.0).ok().map(Into::into),
+ _ => None,
+ };
+
+ let empty_duration: Option<CheckedInteger<_>> = match &track.empty_duration {
+ &Some(e) => i64::try_from(e.0).ok().map(Into::into),
+ _ => None,
+ };
+
+ // Find the track start offset time from 'elst'.
+ // 'media_time' maps start time onward, 'empty_duration' adds time offset
+ // before first frame is displayed.
+ let offset_time = match (empty_duration, media_time) {
+ (Some(e), Some(m)) => (e - m).ok_or(Err(Mp4parseStatus::Invalid))?,
+ (Some(e), None) => e,
+ (None, Some(m)) => m,
+ _ => 0.into(),
+ };
+
+ if let Some(v) = create_sample_table(track, offset_time) {
+ indices.set_indices(&v);
+ sample_table_cache.insert(track_id, v)?;
+ return Ok(());
+ }
+
+ Err(Mp4parseStatus::Invalid)
+}
+
+/// Fill the supplied `Mp4parseFragmentInfo` with metadata from fragmented file.
+///
+/// # Safety
+///
+/// This function is unsafe because it dereferences the the parser and
+/// info raw pointers passed to it. Callers should ensure the parser
+/// pointer points to a valid `Mp4parseParser` and that the info pointer points
+/// to a valid `Mp4parseFragmentInfo`.
+
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_get_fragment_info(
+ parser: *mut Mp4parseParser,
+ info: *mut Mp4parseFragmentInfo,
+) -> Mp4parseStatus {
+ if parser.is_null() || info.is_null() {
+ return Mp4parseStatus::BadArg;
+ }
+
+ // Initialize fields to default values to ensure all fields are always valid.
+ *info = Default::default();
+
+ let context = (*parser).context();
+ let info: &mut Mp4parseFragmentInfo = &mut *info;
+
+ info.fragment_duration = 0;
+
+ let duration = match context.mvex {
+ Some(ref mvex) => mvex.fragment_duration,
+ None => return Mp4parseStatus::Invalid,
+ };
+
+ if let (Some(time), Some(scale)) = (duration, context.timescale) {
+ info.fragment_duration = time.0;
+ info.time_scale = scale.0;
+ return Mp4parseStatus::Ok;
+ };
+ Mp4parseStatus::Invalid
+}
+
+/// Determine if an mp4 file is fragmented. A fragmented file needs mvex table
+/// and contains no data in stts, stsc, and stco boxes.
+///
+/// # Safety
+///
+/// This function is unsafe because it dereferences the the parser and
+/// fragmented raw pointers passed to it. Callers should ensure the parser
+/// pointer points to a valid `Mp4parseParser` and that the fragmented pointer
+/// points to an appropriate memory location to have a `u8` written to.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_is_fragmented(
+ parser: *mut Mp4parseParser,
+ track_id: u32,
+ fragmented: *mut u8,
+) -> Mp4parseStatus {
+ if parser.is_null() {
+ return Mp4parseStatus::BadArg;
+ }
+
+ let context = (*parser).context_mut();
+ let tracks = &context.tracks;
+ (*fragmented) = false as u8;
+
+ if context.mvex.is_none() {
+ return Mp4parseStatus::Ok;
+ }
+
+ // check sample tables.
+ let mut iter = tracks.iter();
+ iter.find(|track| track.track_id == Some(track_id))
+ .map_or(Mp4parseStatus::BadArg, |track| {
+ match (&track.stsc, &track.stco, &track.stts) {
+ (Some(stsc), Some(stco), Some(stts))
+ if stsc.samples.is_empty()
+ && stco.offsets.is_empty()
+ && stts.samples.is_empty() =>
+ {
+ (*fragmented) = true as u8
+ }
+ _ => {}
+ };
+ Mp4parseStatus::Ok
+ })
+}
+
+/// Get 'pssh' system id and 'pssh' box content for eme playback.
+///
+/// The data format of the `info` struct passed to gecko is:
+///
+/// - system id (16 byte uuid)
+/// - pssh box size (32-bit native endian)
+/// - pssh box content (including header)
+///
+/// # Safety
+///
+/// This function is unsafe because it dereferences the the parser and
+/// info raw pointers passed to it. Callers should ensure the parser
+/// pointer points to a valid `Mp4parseParser` and that the fragmented pointer
+/// points to a valid `Mp4parsePsshInfo`.
+#[no_mangle]
+pub unsafe extern "C" fn mp4parse_get_pssh_info(
+ parser: *mut Mp4parseParser,
+ info: *mut Mp4parsePsshInfo,
+) -> Mp4parseStatus {
+ if parser.is_null() || info.is_null() {
+ return Mp4parseStatus::BadArg;
+ }
+
+ // Initialize fields to default values to ensure all fields are always valid.
+ *info = Default::default();
+
+ get_pssh_info(&mut *parser, &mut *info).into()
+}
+
+fn get_pssh_info(
+ parser: &mut Mp4parseParser,
+ info: &mut Mp4parsePsshInfo,
+) -> Result<(), Mp4parseStatus> {
+ let Mp4parseParser {
+ context, pssh_data, ..
+ } = parser;
+
+ pssh_data.clear();
+ for pssh in &context.psshs {
+ let content_len = pssh
+ .box_content
+ .len()
+ .try_into()
+ .map_err(|_| Mp4parseStatus::Invalid)?;
+ let mut data_len = TryVec::new();
+ data_len.write_u32::<byteorder::NativeEndian>(content_len)?;
+ pssh_data.extend_from_slice(pssh.system_id.as_slice())?;
+ pssh_data.extend_from_slice(data_len.as_slice())?;
+ pssh_data.extend_from_slice(pssh.box_content.as_slice())?;
+ }
+
+ info.data.set_data(pssh_data);
+
+ Ok(())
+}
+
+#[cfg(test)]
+extern "C" fn error_read(_: *mut u8, _: usize, _: *mut std::os::raw::c_void) -> isize {
+ -1
+}
+
+#[cfg(test)]
+extern "C" fn valid_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
+ let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
+
+ let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+#[test]
+fn get_track_count_null_parser() {
+ unsafe {
+ let mut count: u32 = 0;
+ let rv = mp4parse_get_track_count(std::ptr::null(), std::ptr::null_mut());
+ assert_eq!(rv, Mp4parseStatus::BadArg);
+ let rv = mp4parse_get_track_count(std::ptr::null(), &mut count);
+ assert_eq!(rv, Mp4parseStatus::BadArg);
+ }
+}
+
+#[test]
+fn arg_validation() {
+ unsafe {
+ let rv = mp4parse_new(std::ptr::null(), std::ptr::null_mut());
+ assert_eq!(rv, Mp4parseStatus::BadArg);
+
+ // Passing a null Mp4parseIo is an error.
+ let mut parser = std::ptr::null_mut();
+ let rv = mp4parse_new(std::ptr::null(), &mut parser);
+ assert_eq!(rv, Mp4parseStatus::BadArg);
+ assert!(parser.is_null());
+
+ let null_mut: *mut std::os::raw::c_void = std::ptr::null_mut();
+
+ // Passing an Mp4parseIo with null members is an error.
+ let io = Mp4parseIo {
+ read: None,
+ userdata: null_mut,
+ };
+ let mut parser = std::ptr::null_mut();
+ let rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::BadArg);
+ assert!(parser.is_null());
+
+ let mut dummy_value = 42;
+ let io = Mp4parseIo {
+ read: None,
+ userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
+ };
+ let mut parser = std::ptr::null_mut();
+ let rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::BadArg);
+ assert!(parser.is_null());
+
+ let mut dummy_info = Mp4parseTrackInfo {
+ track_type: Mp4parseTrackType::Video,
+ ..Default::default()
+ };
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_info(std::ptr::null_mut(), 0, &mut dummy_info)
+ );
+
+ let mut dummy_video = Mp4parseTrackVideoInfo {
+ display_width: 0,
+ display_height: 0,
+ rotation: 0,
+ sample_info_count: 0,
+ sample_info: std::ptr::null(),
+ };
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_video_info(std::ptr::null_mut(), 0, &mut dummy_video)
+ );
+
+ let mut dummy_audio = Default::default();
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_audio_info(std::ptr::null_mut(), 0, &mut dummy_audio)
+ );
+ }
+}
+
+#[test]
+fn parser_input_must_be_null() {
+ let mut dummy_value = 42;
+ let io = Mp4parseIo {
+ read: Some(error_read),
+ userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
+ };
+ let mut parser = 0xDEAD_BEEF as *mut _;
+ let rv = unsafe { mp4parse_new(&io, &mut parser) };
+ assert_eq!(rv, Mp4parseStatus::BadArg);
+}
+
+#[test]
+fn arg_validation_with_parser() {
+ unsafe {
+ let mut dummy_value = 42;
+ let io = Mp4parseIo {
+ read: Some(error_read),
+ userdata: &mut dummy_value as *mut _ as *mut std::os::raw::c_void,
+ };
+ let mut parser = std::ptr::null_mut();
+ let rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::Io);
+ assert!(parser.is_null());
+
+ // Null info pointers are an error.
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_info(parser, 0, std::ptr::null_mut())
+ );
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_video_info(parser, 0, std::ptr::null_mut())
+ );
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_audio_info(parser, 0, std::ptr::null_mut())
+ );
+
+ let mut dummy_info = Mp4parseTrackInfo {
+ track_type: Mp4parseTrackType::Video,
+ ..Default::default()
+ };
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_info(parser, 0, &mut dummy_info)
+ );
+
+ let mut dummy_video = Mp4parseTrackVideoInfo {
+ display_width: 0,
+ display_height: 0,
+ rotation: 0,
+ sample_info_count: 0,
+ sample_info: std::ptr::null(),
+ };
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_video_info(parser, 0, &mut dummy_video)
+ );
+
+ let mut dummy_audio = Default::default();
+ assert_eq!(
+ Mp4parseStatus::BadArg,
+ mp4parse_get_track_audio_info(parser, 0, &mut dummy_audio)
+ );
+ }
+}
+
+#[cfg(test)]
+fn parse_minimal_mp4() -> *mut Mp4parseParser {
+ let mut file = std::fs::File::open("../mp4parse/tests/minimal.mp4").unwrap();
+ let io = Mp4parseIo {
+ read: Some(valid_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+ let mut parser = std::ptr::null_mut();
+ let rv = unsafe { mp4parse_new(&io, &mut parser) };
+ assert_eq!(Mp4parseStatus::Ok, rv);
+ parser
+}
+
+#[test]
+fn minimal_mp4_parse_ok() {
+ let parser = parse_minimal_mp4();
+
+ assert!(!parser.is_null());
+
+ unsafe {
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn minimal_mp4_get_track_cout() {
+ let parser = parse_minimal_mp4();
+
+ let mut count: u32 = 0;
+ assert_eq!(Mp4parseStatus::Ok, unsafe {
+ mp4parse_get_track_count(parser, &mut count)
+ });
+ assert_eq!(2, count);
+
+ unsafe {
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn minimal_mp4_get_track_info() {
+ let parser = parse_minimal_mp4();
+
+ let mut info = Mp4parseTrackInfo {
+ track_type: Mp4parseTrackType::Video,
+ ..Default::default()
+ };
+ assert_eq!(Mp4parseStatus::Ok, unsafe {
+ mp4parse_get_track_info(parser, 0, &mut info)
+ });
+ assert_eq!(info.track_type, Mp4parseTrackType::Video);
+ assert_eq!(info.track_id, 1);
+ assert_eq!(info.duration, 512);
+ assert_eq!(info.media_time, 0);
+
+ assert_eq!(Mp4parseStatus::Ok, unsafe {
+ mp4parse_get_track_info(parser, 1, &mut info)
+ });
+ assert_eq!(info.track_type, Mp4parseTrackType::Audio);
+ assert_eq!(info.track_id, 2);
+ assert_eq!(info.duration, 2944);
+ assert_eq!(info.media_time, 1024);
+
+ unsafe {
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn minimal_mp4_get_track_video_info() {
+ let parser = parse_minimal_mp4();
+
+ let mut video = Mp4parseTrackVideoInfo::default();
+ assert_eq!(Mp4parseStatus::Ok, unsafe {
+ mp4parse_get_track_video_info(parser, 0, &mut video)
+ });
+ assert_eq!(video.display_width, 320);
+ assert_eq!(video.display_height, 240);
+ assert_eq!(video.sample_info_count, 1);
+
+ unsafe {
+ assert_eq!((*video.sample_info).image_width, 320);
+ assert_eq!((*video.sample_info).image_height, 240);
+ }
+
+ unsafe {
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn minimal_mp4_get_track_audio_info() {
+ let parser = parse_minimal_mp4();
+
+ let mut audio = Mp4parseTrackAudioInfo::default();
+ assert_eq!(Mp4parseStatus::Ok, unsafe {
+ mp4parse_get_track_audio_info(parser, 1, &mut audio)
+ });
+ assert_eq!(audio.sample_info_count, 1);
+
+ unsafe {
+ assert_eq!((*audio.sample_info).channels, 1);
+ assert_eq!((*audio.sample_info).bit_depth, 16);
+ assert_eq!((*audio.sample_info).sample_rate, 48000);
+ }
+
+ unsafe {
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn minimal_mp4_get_track_info_invalid_track_number() {
+ let parser = parse_minimal_mp4();
+
+ let mut info = Mp4parseTrackInfo {
+ track_type: Mp4parseTrackType::Video,
+ ..Default::default()
+ };
+ assert_eq!(Mp4parseStatus::BadArg, unsafe {
+ mp4parse_get_track_info(parser, 3, &mut info)
+ });
+ assert_eq!(info.track_type, Mp4parseTrackType::Video);
+ assert_eq!(info.track_id, 0);
+ assert_eq!(info.duration, 0);
+ assert_eq!(info.media_time, 0);
+
+ let mut video = Mp4parseTrackVideoInfo::default();
+ assert_eq!(Mp4parseStatus::BadArg, unsafe {
+ mp4parse_get_track_video_info(parser, 3, &mut video)
+ });
+ assert_eq!(video.display_width, 0);
+ assert_eq!(video.display_height, 0);
+ assert_eq!(video.sample_info_count, 0);
+
+ let mut audio = Default::default();
+ assert_eq!(Mp4parseStatus::BadArg, unsafe {
+ mp4parse_get_track_audio_info(parser, 3, &mut audio)
+ });
+ assert_eq!(audio.sample_info_count, 0);
+
+ unsafe {
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn parse_no_timescale() {
+ let mut file = std::fs::File::open("tests/no_timescale.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(valid_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let mut rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert!(!parser.is_null());
+
+ // The file has a video track, but the track has a timescale of 0, so.
+ let mut track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 0, &mut track_info);
+ assert_eq!(rv, Mp4parseStatus::Invalid);
+ };
+}
diff --git a/third_party/rust/mp4parse_capi/tests/loop_1.avif b/third_party/rust/mp4parse_capi/tests/loop_1.avif
new file mode 100644
index 0000000000..0402755eff
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/loop_1.avif
Binary files differ
diff --git a/third_party/rust/mp4parse_capi/tests/loop_2.avif b/third_party/rust/mp4parse_capi/tests/loop_2.avif
new file mode 100644
index 0000000000..4ed7426588
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/loop_2.avif
Binary files differ
diff --git a/third_party/rust/mp4parse_capi/tests/loop_ceiled_4.avif b/third_party/rust/mp4parse_capi/tests/loop_ceiled_4.avif
new file mode 100644
index 0000000000..1274547dff
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/loop_ceiled_4.avif
Binary files differ
diff --git a/third_party/rust/mp4parse_capi/tests/loop_forever.avif b/third_party/rust/mp4parse_capi/tests/loop_forever.avif
new file mode 100644
index 0000000000..b24b4f29d3
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/loop_forever.avif
Binary files differ
diff --git a/third_party/rust/mp4parse_capi/tests/no_edts.avif b/third_party/rust/mp4parse_capi/tests/no_edts.avif
new file mode 100644
index 0000000000..9dc21fb010
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/no_edts.avif
Binary files differ
diff --git a/third_party/rust/mp4parse_capi/tests/test_avis.rs b/third_party/rust/mp4parse_capi/tests/test_avis.rs
new file mode 100644
index 0000000000..a6e5a1c64e
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/test_avis.rs
@@ -0,0 +1,113 @@
+use mp4parse_capi::*;
+use num_traits::ToPrimitive;
+use std::io::Read;
+
+extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
+ let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
+ let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+unsafe fn parse_file_and_get_info(path: &str) -> (*mut Mp4parseAvifParser, Mp4parseAvifInfo) {
+ let mut file = std::fs::File::open(path).expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ let mut parser = std::ptr::null_mut();
+ let mut rv = mp4parse_avif_new(&io, ParseStrictness::Normal, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert!(!parser.is_null());
+
+ let mut info = Mp4parseAvifInfo {
+ premultiplied_alpha: Default::default(),
+ major_brand: Default::default(),
+ unsupported_features_bitfield: Default::default(),
+ spatial_extents: std::ptr::null(),
+ nclx_colour_information: std::ptr::null(),
+ icc_colour_information: Default::default(),
+ image_rotation: mp4parse::ImageRotation::D0,
+ image_mirror: std::ptr::null(),
+ pixel_aspect_ratio: std::ptr::null(),
+ has_primary_item: Default::default(),
+ primary_item_bit_depth: Default::default(),
+ has_alpha_item: Default::default(),
+ alpha_item_bit_depth: Default::default(),
+ has_sequence: Default::default(),
+ loop_mode: Default::default(),
+ loop_count: Default::default(),
+ color_track_id: Default::default(),
+ color_track_bit_depth: Default::default(),
+ alpha_track_id: Default::default(),
+ alpha_track_bit_depth: Default::default(),
+ };
+ rv = mp4parse_avif_get_info(parser, &mut info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ (parser, info)
+}
+
+fn check_loop_count(path: &str, expected_loop_count: i64) {
+ let (parser, info) = unsafe { parse_file_and_get_info(path) };
+ match info.loop_mode {
+ Mp4parseAvifLoopMode::NoEdits => assert_eq!(expected_loop_count, -1),
+ Mp4parseAvifLoopMode::LoopByCount => {
+ assert_eq!(info.loop_count.to_i64(), Some(expected_loop_count))
+ }
+ Mp4parseAvifLoopMode::LoopInfinitely => assert_eq!(expected_loop_count, std::i64::MIN),
+ }
+
+ unsafe { mp4parse_avif_free(parser) };
+}
+
+fn check_timescale(path: &str, expected_timescale: u64) {
+ let (parser, info) = unsafe { parse_file_and_get_info(path) };
+
+ let mut indices: Mp4parseByteData = Mp4parseByteData::default();
+ let mut timescale: u64 = 0;
+ let rv = unsafe {
+ mp4parse_avif_get_indice_table(parser, info.color_track_id, &mut indices, &mut timescale)
+ };
+
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(timescale, expected_timescale);
+
+ unsafe { mp4parse_avif_free(parser) };
+}
+
+#[test]
+fn loop_once() {
+ check_loop_count("tests/loop_1.avif", 1);
+}
+
+#[test]
+fn loop_twice() {
+ check_loop_count("tests/loop_2.avif", 2);
+}
+
+#[test]
+fn loop_four_times_due_to_ceiling() {
+ check_loop_count("tests/loop_ceiled_4.avif", 4);
+}
+
+#[test]
+fn loop_forever() {
+ check_loop_count("tests/loop_forever.avif", std::i64::MIN);
+}
+
+#[test]
+fn no_edts() {
+ check_loop_count("tests/no_edts.avif", -1);
+}
+
+#[test]
+fn check_timescales() {
+ check_timescale("tests/loop_1.avif", 2);
+ check_timescale("tests/loop_2.avif", 2);
+ check_timescale("tests/loop_ceiled_4.avif", 2);
+ check_timescale("tests/loop_forever.avif", 2);
+ check_timescale("tests/no_edts.avif", 16384);
+}
diff --git a/third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs b/third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs
new file mode 100644
index 0000000000..26c2f506e1
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/test_chunk_out_of_range.rs
@@ -0,0 +1,44 @@
+use mp4parse_capi::*;
+use std::io::Read;
+
+extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
+ let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
+ let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+#[test]
+fn parse_out_of_chunk_range() {
+ let mut file = std::fs::File::open("tests/chunk_out_of_range.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let mut rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert!(!parser.is_null());
+
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 1);
+
+ // its first chunk is out of range.
+ // <SampleToChunkBox EntryCount="1">
+ // <BoxInfo Size="28" Type="stsc"/>
+ // <FullBoxInfo Version="0" Flags="0x0"/>
+ // <SampleToChunkEntry FirstChunk="16777217" SamplesPerChunk="17" SampleDescriptionIndex="1"/>
+ //
+ let mut indice = Mp4parseByteData::default();
+ let rv = mp4parse_get_indice_table(parser, 1, &mut indice);
+ assert_eq!(rv, Mp4parseStatus::Invalid);
+
+ mp4parse_free(parser);
+ }
+}
diff --git a/third_party/rust/mp4parse_capi/tests/test_encryption.rs b/third_party/rust/mp4parse_capi/tests/test_encryption.rs
new file mode 100644
index 0000000000..03f5963044
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/test_encryption.rs
@@ -0,0 +1,295 @@
+use mp4parse_capi::*;
+use std::io::Read;
+
+extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
+ let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
+ let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+#[test]
+fn parse_cenc() {
+ let mut file = std::fs::File::open("tests/short-cenc.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let mut rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert!(!parser.is_null());
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 2);
+
+ // Make sure we have a video track and it's at index 0
+ let mut video_track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 0, &mut video_track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(video_track_info.track_type, Mp4parseTrackType::Video);
+
+ // Make sure we have a audio track and it's at index 1
+ let mut audio_track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 1, &mut audio_track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(audio_track_info.track_type, Mp4parseTrackType::Audio);
+
+ // Verify video track and crypto information
+ let mut video = Mp4parseTrackVideoInfo::default();
+ rv = mp4parse_get_track_video_info(parser, 0, &mut video);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(video.sample_info_count, 1);
+ assert_eq!((*video.sample_info).codec_type, Mp4parseCodec::Avc);
+ assert_eq!((*video.sample_info).image_width, 320);
+ assert_eq!((*video.sample_info).image_height, 240);
+ let protected_data = &(*video.sample_info).protected_data;
+ assert_eq!(
+ protected_data.original_format,
+ OptionalFourCc::Some(*b"avc1")
+ );
+ assert_eq!(
+ protected_data.scheme_type,
+ Mp4ParseEncryptionSchemeType::Cenc
+ );
+ assert_eq!(protected_data.is_encrypted, 0x01);
+ assert_eq!(protected_data.iv_size, 16);
+ assert_eq!(protected_data.kid.length, 16);
+ let expected_kid = [
+ 0x7e, 0x57, 0x1d, 0x01, 0x7e, 0x57, 0x1d, 0x01, 0x7e, 0x57, 0x1d, 0x01, 0x7e, 0x57,
+ 0x1d, 0x01,
+ ];
+ for (i, expected_byte) in expected_kid.iter().enumerate() {
+ assert_eq!(&(*protected_data.kid.data.add(i)), expected_byte);
+ }
+ assert_eq!(protected_data.crypt_byte_block, 0);
+ assert_eq!(protected_data.skip_byte_block, 0);
+ assert_eq!(protected_data.constant_iv.length, 0);
+
+ // Verify audio track and crypto information
+ let mut audio = Mp4parseTrackAudioInfo::default();
+ rv = mp4parse_get_track_audio_info(parser, 1, &mut audio);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(audio.sample_info_count, 1);
+ assert_eq!((*audio.sample_info).codec_type, Mp4parseCodec::Aac);
+ assert_eq!((*audio.sample_info).channels, 2);
+ assert_eq!((*audio.sample_info).bit_depth, 16);
+ assert_eq!((*audio.sample_info).sample_rate, 44100);
+ let protected_data = &(*audio.sample_info).protected_data;
+ assert_eq!(
+ protected_data.original_format,
+ OptionalFourCc::Some(*b"mp4a")
+ );
+ assert_eq!(protected_data.is_encrypted, 0x01);
+ assert_eq!(protected_data.iv_size, 16);
+ assert_eq!(protected_data.kid.length, 16);
+ let expected_kid = [
+ 0x7e, 0x57, 0x1d, 0x02, 0x7e, 0x57, 0x1d, 0x02, 0x7e, 0x57, 0x1d, 0x02, 0x7e, 0x57,
+ 0x1d, 0x02,
+ ];
+ for (i, expected_byte) in expected_kid.iter().enumerate() {
+ assert_eq!(&(*protected_data.kid.data.add(i)), expected_byte);
+ }
+ assert_eq!(protected_data.crypt_byte_block, 0);
+ assert_eq!(protected_data.skip_byte_block, 0);
+ assert_eq!(protected_data.constant_iv.length, 0);
+ }
+}
+
+#[test]
+fn parse_cbcs() {
+ let mut file = std::fs::File::open("tests/bipbop_cbcs_video_init.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let mut rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert!(!parser.is_null());
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 1);
+
+ // Make sure we have a video track
+ let mut video_track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 0, &mut video_track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(video_track_info.track_type, Mp4parseTrackType::Video);
+
+ // Verify video track and crypto information
+ let mut video = Mp4parseTrackVideoInfo::default();
+ rv = mp4parse_get_track_video_info(parser, 0, &mut video);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(video.sample_info_count, 2);
+ assert_eq!((*video.sample_info).codec_type, Mp4parseCodec::Avc);
+ assert_eq!((*video.sample_info).image_width, 400);
+ assert_eq!((*video.sample_info).image_height, 300);
+ let protected_data = &(*video.sample_info).protected_data;
+ assert_eq!(
+ protected_data.original_format,
+ OptionalFourCc::Some(*b"avc1")
+ );
+ assert_eq!(
+ protected_data.scheme_type,
+ Mp4ParseEncryptionSchemeType::Cbcs
+ );
+ assert_eq!(protected_data.is_encrypted, 0x01);
+ assert_eq!(protected_data.iv_size, 0);
+ assert_eq!(protected_data.kid.length, 16);
+ let expected_kid = [
+ 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57, 0x1d, 0x04, 0x7e, 0x57,
+ 0x1d, 0x21,
+ ];
+ for (i, expected_byte) in expected_kid.iter().enumerate() {
+ assert_eq!(&(*protected_data.kid.data.add(i)), expected_byte);
+ }
+ assert_eq!(protected_data.crypt_byte_block, 1);
+ assert_eq!(protected_data.skip_byte_block, 9);
+ assert_eq!(protected_data.constant_iv.length, 16);
+ let expected_iv = [
+ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44,
+ 0x55, 0x66,
+ ];
+ for (i, expected_byte) in expected_iv.iter().enumerate() {
+ assert_eq!(&(*protected_data.constant_iv.data.add(i)), expected_byte);
+ }
+ }
+}
+
+#[test]
+fn parse_unencrypted() {
+ // Ensure the encryption related data is not populated for files without
+ // encryption metadata.
+ let mut file = std::fs::File::open("tests/opus_audioinit.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let mut rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert!(!parser.is_null());
+
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 1);
+
+ let mut track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 0, &mut track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(track_info.track_type, Mp4parseTrackType::Audio);
+
+ let mut audio = Mp4parseTrackAudioInfo::default();
+ rv = mp4parse_get_track_audio_info(parser, 0, &mut audio);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(audio.sample_info_count, 1);
+ let protected_data = &(*audio.sample_info).protected_data;
+ assert_eq!(protected_data.original_format, OptionalFourCc::None);
+ assert_eq!(
+ protected_data.scheme_type,
+ Mp4ParseEncryptionSchemeType::None
+ );
+ assert_eq!(protected_data.is_encrypted, 0x00);
+ assert_eq!(protected_data.iv_size, 0);
+ assert_eq!(protected_data.kid.length, 0);
+ assert_eq!(protected_data.crypt_byte_block, 0);
+ assert_eq!(protected_data.skip_byte_block, 0);
+ assert_eq!(protected_data.constant_iv.length, 0);
+ }
+}
+
+#[test]
+fn parse_encrypted_av1() {
+ // For reference, this file was created from the av1.mp4 in mozilla's media tests using
+ // shaka-packager. The following command was used to produce the file:
+ // ```
+ // packager-win.exe in=av1.mp4,stream=video,output=av1-clearkey-cbcs-video.mp4
+ // --protection_scheme cbcs --enable_raw_key_encryption
+ // --keys label=:key_id=00112233445566778899AABBCCDDEEFF:key=00112233445566778899AABBCCDDEEFF
+ // --iv 11223344556677889900112233445566
+ // ```
+ let mut file = std::fs::File::open("tests/av1-clearkey-cbcs-video.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let mut rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert!(!parser.is_null());
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 1);
+
+ // Make sure we have a video track
+ let mut video_track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 0, &mut video_track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(video_track_info.track_type, Mp4parseTrackType::Video);
+
+ // Verify video track and crypto information
+ let mut video = Mp4parseTrackVideoInfo::default();
+ rv = mp4parse_get_track_video_info(parser, 0, &mut video);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(video.sample_info_count, 2);
+ assert_eq!((*video.sample_info).codec_type, Mp4parseCodec::Av1);
+ assert_eq!((*video.sample_info).image_width, 160);
+ assert_eq!((*video.sample_info).image_height, 90);
+
+ // Check that extra data binary blob.
+ let expected_extra_data = [
+ 0x81, 0x00, 0x0c, 0x00, 0x0a, 0x0a, 0x00, 0x00, 0x00, 0x03, 0xb4, 0xfd, 0x97, 0xff,
+ 0xe6, 0x01,
+ ];
+ let extra_data = &(*video.sample_info).extra_data;
+ assert_eq!(extra_data.length, 16);
+ for (i, expected_byte) in expected_extra_data.iter().enumerate() {
+ assert_eq!(&(*extra_data.data.add(i)), expected_byte);
+ }
+
+ let protected_data = &(*video.sample_info).protected_data;
+ assert_eq!(
+ protected_data.original_format,
+ OptionalFourCc::Some(*b"av01")
+ );
+ assert_eq!(
+ protected_data.scheme_type,
+ Mp4ParseEncryptionSchemeType::Cbcs
+ );
+ assert_eq!(protected_data.is_encrypted, 0x01);
+ assert_eq!(protected_data.iv_size, 0);
+ assert_eq!(protected_data.kid.length, 16);
+ let expected_kid = [
+ 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd,
+ 0xee, 0xff,
+ ];
+ for (i, expected_byte) in expected_kid.iter().enumerate() {
+ assert_eq!(&(*protected_data.kid.data.add(i)), expected_byte);
+ }
+ assert_eq!(protected_data.crypt_byte_block, 1);
+ assert_eq!(protected_data.skip_byte_block, 9);
+ assert_eq!(protected_data.constant_iv.length, 16);
+ let expected_iv = [
+ 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44,
+ 0x55, 0x66,
+ ];
+ for (i, expected_byte) in expected_iv.iter().enumerate() {
+ assert_eq!(&(*protected_data.constant_iv.data.add(i)), expected_byte);
+ }
+ }
+}
diff --git a/third_party/rust/mp4parse_capi/tests/test_fragment.rs b/third_party/rust/mp4parse_capi/tests/test_fragment.rs
new file mode 100644
index 0000000000..38bc569fa3
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/test_fragment.rs
@@ -0,0 +1,119 @@
+use mp4parse_capi::*;
+use std::io::Read;
+
+extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
+ let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
+ let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+#[test]
+fn parse_fragment() {
+ let mut file = std::fs::File::open("tests/bipbop_audioinit.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let mut rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert!(!parser.is_null());
+
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 1);
+
+ let mut track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 0, &mut track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(track_info.track_type, Mp4parseTrackType::Audio);
+ assert_eq!(track_info.track_id, 1);
+ assert_eq!(track_info.duration, 0);
+ assert_eq!(track_info.media_time, 0);
+ assert_eq!(track_info.time_scale, 22050);
+
+ let mut audio = Default::default();
+ rv = mp4parse_get_track_audio_info(parser, 0, &mut audio);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(audio.sample_info_count, 1);
+
+ assert_eq!((*audio.sample_info).codec_type, Mp4parseCodec::Aac);
+ assert_eq!((*audio.sample_info).channels, 2);
+ assert_eq!((*audio.sample_info).bit_depth, 16);
+ assert_eq!((*audio.sample_info).sample_rate, 22050);
+ assert_eq!((*audio.sample_info).extra_data.length, 27);
+ assert_eq!((*audio.sample_info).codec_specific_config.length, 2);
+
+ let mut is_fragmented_file: u8 = 0;
+ rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(is_fragmented_file, 1);
+
+ let mut fragment_info = Mp4parseFragmentInfo::default();
+ rv = mp4parse_get_fragment_info(parser, &mut fragment_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(fragment_info.fragment_duration, 10_032);
+ assert_eq!(fragment_info.time_scale, 1000);
+
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn parse_opus_fragment() {
+ let mut file = std::fs::File::open("tests/opus_audioinit.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let mut rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert!(!parser.is_null());
+
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 1);
+
+ let mut track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 0, &mut track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(track_info.track_type, Mp4parseTrackType::Audio);
+ assert_eq!(track_info.track_id, 1);
+ assert_eq!(track_info.duration, 0);
+ assert_eq!(track_info.media_time, 0);
+ assert_eq!(track_info.time_scale, 48000);
+
+ let mut audio = Default::default();
+ rv = mp4parse_get_track_audio_info(parser, 0, &mut audio);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(audio.sample_info_count, 1);
+
+ assert_eq!((*audio.sample_info).codec_type, Mp4parseCodec::Opus);
+ assert_eq!((*audio.sample_info).channels, 1);
+ assert_eq!((*audio.sample_info).bit_depth, 16);
+ assert_eq!((*audio.sample_info).sample_rate, 48000);
+ assert_eq!((*audio.sample_info).extra_data.length, 0);
+ assert_eq!((*audio.sample_info).codec_specific_config.length, 19);
+
+ let mut is_fragmented_file: u8 = 0;
+ rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(is_fragmented_file, 1);
+
+ let mut fragment_info = Mp4parseFragmentInfo::default();
+ rv = mp4parse_get_fragment_info(parser, &mut fragment_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+
+ mp4parse_free(parser);
+ }
+}
diff --git a/third_party/rust/mp4parse_capi/tests/test_rotation.rs b/third_party/rust/mp4parse_capi/tests/test_rotation.rs
new file mode 100644
index 0000000000..2ea47a5d52
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/test_rotation.rs
@@ -0,0 +1,40 @@
+use mp4parse_capi::*;
+use std::io::Read;
+
+extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
+ let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
+ let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+#[test]
+fn parse_rotation() {
+ let mut file = std::fs::File::open("tests/video_rotation_90.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let mut rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert!(!parser.is_null());
+
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 1);
+
+ let mut video = Mp4parseTrackVideoInfo::default();
+
+ let rv = mp4parse_get_track_video_info(parser, 0, &mut video);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(video.rotation, 90);
+
+ mp4parse_free(parser);
+ }
+}
diff --git a/third_party/rust/mp4parse_capi/tests/test_sample_table.rs b/third_party/rust/mp4parse_capi/tests/test_sample_table.rs
new file mode 100644
index 0000000000..820eed64f3
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/test_sample_table.rs
@@ -0,0 +1,278 @@
+use mp4parse::unstable::Indice;
+use mp4parse_capi::*;
+use std::io::Read;
+
+extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
+ let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
+ let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+#[test]
+fn parse_sample_table() {
+ let mut file =
+ std::fs::File::open("tests/bipbop_nonfragment_header.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let mut rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert!(!parser.is_null());
+
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 2);
+
+ let mut track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 1, &mut track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(track_info.track_type, Mp4parseTrackType::Audio);
+
+ // Check audio smaple table
+ let mut is_fragmented_file: u8 = 0;
+ rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(is_fragmented_file, 0);
+
+ let mut indice = Mp4parseByteData::default();
+ rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+
+ // Compare the value from stagefright.
+ let audio_indice_0 = Indice {
+ start_offset: 27_046.into(),
+ end_offset: 27_052.into(),
+ start_composition: 0.into(),
+ end_composition: 1024.into(),
+ start_decode: 0.into(),
+ sync: true,
+ };
+ let audio_indice_215 = Indice {
+ start_offset: 283_550.into(),
+ end_offset: 283_556.into(),
+ start_composition: 220160.into(),
+ end_composition: 221184.into(),
+ start_decode: 220160.into(),
+ sync: true,
+ };
+ assert_eq!(indice.length, 216);
+ assert_eq!(*indice.indices.offset(0), audio_indice_0);
+ assert_eq!(*indice.indices.offset(215), audio_indice_215);
+
+ // Check video smaple table
+ rv = mp4parse_get_track_info(parser, 0, &mut track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(track_info.track_type, Mp4parseTrackType::Video);
+
+ let mut is_fragmented_file: u8 = 0;
+ rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(is_fragmented_file, 0);
+
+ let mut indice = Mp4parseByteData::default();
+ rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+
+ // Compare the last few data from stagefright.
+ let video_indice_291 = Indice {
+ start_offset: 280_226.into(),
+ end_offset: 280_855.into(),
+ start_composition: 876995.into(),
+ end_composition: 879996.into(),
+ start_decode: 873900.into(),
+ sync: false,
+ };
+ let video_indice_292 = Indice {
+ start_offset: 280_855.into(),
+ end_offset: 281_297.into(),
+ start_composition: 873996.into(),
+ end_composition: 876995.into(),
+ start_decode: 873901.into(),
+ sync: false,
+ };
+ // TODO: start_composition time in stagefright is 9905000, but it is 9904999 in parser, it
+ // could be rounding error.
+ //let video_indice_293 = Indice { start_offset: 281_297, end_offset: 281_919, start_composition: 9_905_000, end_composition: 9_938_344, start_decode: 9_776_666, sync: false };
+ //let video_indice_294 = Indice { start_offset: 281_919, end_offset: 282_391, start_composition: 9_871_677, end_composition: 9_905_000, start_decode: 9_776_677, sync: false };
+ let video_indice_295 = Indice {
+ start_offset: 282_391.into(),
+ end_offset: 283_032.into(),
+ start_composition: 888995.into(),
+ end_composition: 888996.into(),
+ start_decode: 885900.into(),
+ sync: false,
+ };
+ let video_indice_296 = Indice {
+ start_offset: 283_092.into(),
+ end_offset: 283_526.into(),
+ start_composition: 885996.into(),
+ end_composition: 888995.into(),
+ start_decode: 885901.into(),
+ sync: false,
+ };
+
+ assert_eq!(indice.length, 297);
+ assert_eq!(*indice.indices.offset(291), video_indice_291);
+ assert_eq!(*indice.indices.offset(292), video_indice_292);
+ //assert_eq!(*indice.indices.offset(293), video_indice_293);
+ //assert_eq!(*indice.indices.offset(294), video_indice_294);
+ assert_eq!(*indice.indices.offset(295), video_indice_295);
+ assert_eq!(*indice.indices.offset(296), video_indice_296);
+
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn parse_sample_table_with_elst() {
+ let mut file = std::fs::File::open("tests/short-cenc.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let mut rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert!(!parser.is_null());
+
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 2);
+
+ let mut track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 1, &mut track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(track_info.track_type, Mp4parseTrackType::Audio);
+
+ // Check audio sample table
+ let mut is_fragmented_file: u8 = std::u8::MAX;
+ rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(is_fragmented_file, 0);
+
+ let mut indice = Mp4parseByteData::default();
+ rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+
+ // Compare the value from stagefright.
+ // Due to 'elst', the start_composition and end_composition are negative
+ // at first two samples.
+ let audio_indice_0 = Indice {
+ start_offset: 6992.into(),
+ end_offset: 7363.into(),
+ start_composition: (-1600).into(),
+ end_composition: (-576).into(),
+ start_decode: 0.into(),
+ sync: true,
+ };
+ let audio_indice_1 = Indice {
+ start_offset: 7363.into(),
+ end_offset: 7735.into(),
+ start_composition: (-576).into(),
+ end_composition: 448.into(),
+ start_decode: 1024.into(),
+ sync: true,
+ };
+ let audio_indice_2 = Indice {
+ start_offset: 7735.into(),
+ end_offset: 8106.into(),
+ start_composition: 448.into(),
+ end_composition: 1472.into(),
+ start_decode: 2048.into(),
+ sync: true,
+ };
+ assert_eq!(indice.length, 21);
+ assert_eq!(*indice.indices.offset(0), audio_indice_0);
+ assert_eq!(*indice.indices.offset(1), audio_indice_1);
+ assert_eq!(*indice.indices.offset(2), audio_indice_2);
+
+ mp4parse_free(parser);
+ }
+}
+
+#[test]
+fn parse_sample_table_with_negative_ctts() {
+ let mut file = std::fs::File::open("tests/white.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let mut rv = mp4parse_new(&io, &mut parser);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert!(!parser.is_null());
+
+ let mut counts: u32 = 0;
+ rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 1);
+
+ let mut track_info = Mp4parseTrackInfo::default();
+ rv = mp4parse_get_track_info(parser, 0, &mut track_info);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(track_info.track_type, Mp4parseTrackType::Video);
+
+ let mut is_fragmented_file: u8 = std::u8::MAX;
+ rv = mp4parse_is_fragmented(parser, track_info.track_id, &mut is_fragmented_file);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(is_fragmented_file, 0);
+
+ let mut indice = Mp4parseByteData::default();
+ rv = mp4parse_get_indice_table(parser, track_info.track_id, &mut indice);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+
+ // There are negative value in 'ctts' table.
+ let video_indice_0 = Indice {
+ start_offset: 48.into(),
+ end_offset: 890.into(),
+ start_composition: 0.into(),
+ end_composition: 100.into(),
+ start_decode: 0.into(),
+ sync: true,
+ };
+ let video_indice_1 = Indice {
+ start_offset: 890.into(),
+ end_offset: 913.into(),
+ start_composition: 400.into(),
+ end_composition: 500.into(),
+ start_decode: 100.into(),
+ sync: false,
+ };
+ let video_indice_2 = Indice {
+ start_offset: 913.into(),
+ end_offset: 934.into(),
+ start_composition: 200.into(),
+ end_composition: 300.into(),
+ start_decode: 200.into(),
+ sync: false,
+ };
+ let video_indice_3 = Indice {
+ start_offset: 934.into(),
+ end_offset: 955.into(),
+ start_composition: 100.into(),
+ end_composition: 200.into(),
+ start_decode: 300.into(),
+ sync: false,
+ };
+ assert_eq!(indice.length, 300);
+ assert_eq!(*indice.indices.offset(0), video_indice_0);
+ assert_eq!(*indice.indices.offset(1), video_indice_1);
+ assert_eq!(*indice.indices.offset(2), video_indice_2);
+ assert_eq!(*indice.indices.offset(3), video_indice_3);
+
+ mp4parse_free(parser);
+ }
+}
diff --git a/third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs b/third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs
new file mode 100644
index 0000000000..197024086f
--- /dev/null
+++ b/third_party/rust/mp4parse_capi/tests/test_workaround_stsc.rs
@@ -0,0 +1,45 @@
+use mp4parse_capi::*;
+use std::io::Read;
+
+extern "C" fn buf_read(buf: *mut u8, size: usize, userdata: *mut std::os::raw::c_void) -> isize {
+ let input: &mut std::fs::File = unsafe { &mut *(userdata as *mut _) };
+ let buf = unsafe { std::slice::from_raw_parts_mut(buf, size) };
+ match input.read(buf) {
+ Ok(n) => n as isize,
+ Err(_) => -1,
+ }
+}
+
+#[test]
+fn parse_invalid_stsc_table() {
+ let mut file = std::fs::File::open("tests/zero_empty_stsc.mp4").expect("Unknown file");
+ let io = Mp4parseIo {
+ read: Some(buf_read),
+ userdata: &mut file as *mut _ as *mut std::os::raw::c_void,
+ };
+
+ unsafe {
+ let mut parser = std::ptr::null_mut();
+ let rv = mp4parse_new(&io, &mut parser);
+
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert!(!parser.is_null());
+
+ let mut counts: u32 = 0;
+ let rv = mp4parse_get_track_count(parser, &mut counts);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(counts, 2);
+
+ let mut indice_video = Mp4parseByteData::default();
+ let rv = mp4parse_get_indice_table(parser, 1, &mut indice_video);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(indice_video.length, 1040);
+
+ let mut indice_audio = Mp4parseByteData::default();
+ let rv = mp4parse_get_indice_table(parser, 2, &mut indice_audio);
+ assert_eq!(rv, Mp4parseStatus::Ok);
+ assert_eq!(indice_audio.length, 1952);
+
+ mp4parse_free(parser);
+ }
+}