@osmo.inc/cli 0.1.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DEPLOY.md +87 -0
- package/README.md +243 -0
- package/RELEASE.md +164 -0
- package/SETUP.md +60 -0
- package/THIRD_PARTY_NOTICES.md +18 -0
- package/bin/osmo.mjs +12 -0
- package/dist/osmo.js +7817 -0
- package/node_modules/@esbuild/darwin-arm64/README.md +3 -0
- package/node_modules/@esbuild/darwin-arm64/bin/esbuild +0 -0
- package/node_modules/@esbuild/darwin-arm64/package.json +20 -0
- package/node_modules/esbuild/LICENSE.md +21 -0
- package/node_modules/esbuild/README.md +3 -0
- package/node_modules/esbuild/bin/esbuild +223 -0
- package/node_modules/esbuild/install.js +289 -0
- package/node_modules/esbuild/lib/main.d.ts +716 -0
- package/node_modules/esbuild/lib/main.js +2532 -0
- package/node_modules/esbuild/package.json +49 -0
- package/node_modules/fflate/LICENSE +21 -0
- package/node_modules/fflate/esm/index.mjs +2710 -0
- package/node_modules/fflate/package.json +128 -0
- package/node_modules/typescript/LICENSE.txt +55 -0
- package/node_modules/typescript/ThirdPartyNoticeText.txt +193 -0
- package/node_modules/typescript/lib/lib.d.ts +22 -0
- package/node_modules/typescript/lib/lib.decorators.d.ts +384 -0
- package/node_modules/typescript/lib/lib.decorators.legacy.d.ts +22 -0
- package/node_modules/typescript/lib/lib.dom.asynciterable.d.ts +41 -0
- package/node_modules/typescript/lib/lib.dom.d.ts +39429 -0
- package/node_modules/typescript/lib/lib.dom.iterable.d.ts +571 -0
- package/node_modules/typescript/lib/lib.es2015.collection.d.ts +147 -0
- package/node_modules/typescript/lib/lib.es2015.core.d.ts +597 -0
- package/node_modules/typescript/lib/lib.es2015.d.ts +28 -0
- package/node_modules/typescript/lib/lib.es2015.generator.d.ts +77 -0
- package/node_modules/typescript/lib/lib.es2015.iterable.d.ts +605 -0
- package/node_modules/typescript/lib/lib.es2015.promise.d.ts +81 -0
- package/node_modules/typescript/lib/lib.es2015.proxy.d.ts +128 -0
- package/node_modules/typescript/lib/lib.es2015.reflect.d.ts +144 -0
- package/node_modules/typescript/lib/lib.es2015.symbol.d.ts +46 -0
- package/node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts +326 -0
- package/node_modules/typescript/lib/lib.es2016.array.include.d.ts +116 -0
- package/node_modules/typescript/lib/lib.es2016.d.ts +21 -0
- package/node_modules/typescript/lib/lib.es2016.full.d.ts +23 -0
- package/node_modules/typescript/lib/lib.es2016.intl.d.ts +31 -0
- package/node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts +21 -0
- package/node_modules/typescript/lib/lib.es2017.d.ts +26 -0
- package/node_modules/typescript/lib/lib.es2017.date.d.ts +31 -0
- package/node_modules/typescript/lib/lib.es2017.full.d.ts +23 -0
- package/node_modules/typescript/lib/lib.es2017.intl.d.ts +44 -0
- package/node_modules/typescript/lib/lib.es2017.object.d.ts +49 -0
- package/node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts +135 -0
- package/node_modules/typescript/lib/lib.es2017.string.d.ts +45 -0
- package/node_modules/typescript/lib/lib.es2017.typedarrays.d.ts +53 -0
- package/node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts +77 -0
- package/node_modules/typescript/lib/lib.es2018.asynciterable.d.ts +53 -0
- package/node_modules/typescript/lib/lib.es2018.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2018.full.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2018.intl.d.ts +83 -0
- package/node_modules/typescript/lib/lib.es2018.promise.d.ts +30 -0
- package/node_modules/typescript/lib/lib.es2018.regexp.d.ts +37 -0
- package/node_modules/typescript/lib/lib.es2019.array.d.ts +79 -0
- package/node_modules/typescript/lib/lib.es2019.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2019.full.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2019.intl.d.ts +23 -0
- package/node_modules/typescript/lib/lib.es2019.object.d.ts +33 -0
- package/node_modules/typescript/lib/lib.es2019.string.d.ts +37 -0
- package/node_modules/typescript/lib/lib.es2019.symbol.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2020.bigint.d.ts +765 -0
- package/node_modules/typescript/lib/lib.es2020.d.ts +27 -0
- package/node_modules/typescript/lib/lib.es2020.date.d.ts +42 -0
- package/node_modules/typescript/lib/lib.es2020.full.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2020.intl.d.ts +474 -0
- package/node_modules/typescript/lib/lib.es2020.number.d.ts +28 -0
- package/node_modules/typescript/lib/lib.es2020.promise.d.ts +47 -0
- package/node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts +99 -0
- package/node_modules/typescript/lib/lib.es2020.string.d.ts +44 -0
- package/node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts +41 -0
- package/node_modules/typescript/lib/lib.es2021.d.ts +23 -0
- package/node_modules/typescript/lib/lib.es2021.full.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2021.intl.d.ts +166 -0
- package/node_modules/typescript/lib/lib.es2021.promise.d.ts +48 -0
- package/node_modules/typescript/lib/lib.es2021.string.d.ts +33 -0
- package/node_modules/typescript/lib/lib.es2021.weakref.d.ts +78 -0
- package/node_modules/typescript/lib/lib.es2022.array.d.ts +121 -0
- package/node_modules/typescript/lib/lib.es2022.d.ts +25 -0
- package/node_modules/typescript/lib/lib.es2022.error.d.ts +75 -0
- package/node_modules/typescript/lib/lib.es2022.full.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2022.intl.d.ts +145 -0
- package/node_modules/typescript/lib/lib.es2022.object.d.ts +26 -0
- package/node_modules/typescript/lib/lib.es2022.regexp.d.ts +39 -0
- package/node_modules/typescript/lib/lib.es2022.string.d.ts +25 -0
- package/node_modules/typescript/lib/lib.es2023.array.d.ts +924 -0
- package/node_modules/typescript/lib/lib.es2023.collection.d.ts +21 -0
- package/node_modules/typescript/lib/lib.es2023.d.ts +22 -0
- package/node_modules/typescript/lib/lib.es2023.full.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2023.intl.d.ts +56 -0
- package/node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts +65 -0
- package/node_modules/typescript/lib/lib.es2024.collection.d.ts +29 -0
- package/node_modules/typescript/lib/lib.es2024.d.ts +26 -0
- package/node_modules/typescript/lib/lib.es2024.full.d.ts +24 -0
- package/node_modules/typescript/lib/lib.es2024.object.d.ts +29 -0
- package/node_modules/typescript/lib/lib.es2024.promise.d.ts +35 -0
- package/node_modules/typescript/lib/lib.es2024.regexp.d.ts +25 -0
- package/node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts +68 -0
- package/node_modules/typescript/lib/lib.es2024.string.d.ts +29 -0
- package/node_modules/typescript/lib/lib.es5.d.ts +4601 -0
- package/node_modules/typescript/lib/lib.es6.d.ts +23 -0
- package/node_modules/typescript/lib/lib.esnext.array.d.ts +35 -0
- package/node_modules/typescript/lib/lib.esnext.collection.d.ts +96 -0
- package/node_modules/typescript/lib/lib.esnext.d.ts +29 -0
- package/node_modules/typescript/lib/lib.esnext.decorators.d.ts +28 -0
- package/node_modules/typescript/lib/lib.esnext.disposable.d.ts +193 -0
- package/node_modules/typescript/lib/lib.esnext.error.d.ts +24 -0
- package/node_modules/typescript/lib/lib.esnext.float16.d.ts +445 -0
- package/node_modules/typescript/lib/lib.esnext.full.d.ts +24 -0
- package/node_modules/typescript/lib/lib.esnext.intl.d.ts +21 -0
- package/node_modules/typescript/lib/lib.esnext.iterator.d.ts +148 -0
- package/node_modules/typescript/lib/lib.esnext.promise.d.ts +34 -0
- package/node_modules/typescript/lib/lib.esnext.sharedmemory.d.ts +25 -0
- package/node_modules/typescript/lib/lib.scripthost.d.ts +322 -0
- package/node_modules/typescript/lib/lib.webworker.asynciterable.d.ts +41 -0
- package/node_modules/typescript/lib/lib.webworker.d.ts +13150 -0
- package/node_modules/typescript/lib/lib.webworker.importscripts.d.ts +23 -0
- package/node_modules/typescript/lib/lib.webworker.iterable.d.ts +340 -0
- package/node_modules/typescript/lib/typescript.js +382 -0
- package/node_modules/typescript/package.json +57 -0
- package/package.json +54 -0
- package/release.json +136 -0
- package/runtime/apps/native/tools/compile.mjs +217 -0
- package/runtime/packages/scene/scripts/validate-strict.mjs +246 -0
- package/runtime/packages/scene/src/clip.d.mts +14 -0
- package/runtime/packages/scene/src/clip.mjs +15 -0
- package/runtime/packages/scene/src/color-value.d.mts +5 -0
- package/runtime/packages/scene/src/color-value.mjs +19 -0
- package/runtime/packages/scene/src/color.ts +1 -0
- package/runtime/packages/scene/src/effects.ts +213 -0
- package/runtime/packages/scene/src/elements.ts +491 -0
- package/runtime/packages/scene/src/evaluate.ts +919 -0
- package/runtime/packages/scene/src/index.ts +69 -0
- package/runtime/packages/scene/src/jsx-runtime.ts +73 -0
- package/runtime/packages/scene/src/knob-schema.ts +115 -0
- package/runtime/packages/scene/src/knobs.ts +118 -0
- package/runtime/packages/scene/src/mask.d.mts +6 -0
- package/runtime/packages/scene/src/mask.mjs +17 -0
- package/runtime/packages/scene/src/metadata.d.mts +23 -0
- package/runtime/packages/scene/src/metadata.mjs +44 -0
- package/runtime/packages/scene/src/motion-sampling.d.mts +2 -0
- package/runtime/packages/scene/src/motion-sampling.mjs +50 -0
- package/runtime/packages/scene/src/motion.ts +121 -0
- package/runtime/packages/scene/src/shader.ts +57 -0
- package/src/resource-examples/example-image/landscape.svg +7 -0
- package/src/resource-examples/example-image/resource.json +8 -0
- package/src/resource-examples/example-model/gem.glb +0 -0
- package/src/resource-examples/example-model/preview.svg +1 -0
- package/src/resource-examples/example-model/resource.json +7 -0
- package/src/resource-examples/example-scene/resource.json +1 -0
- package/src/resource-examples/example-scene/scene.tsx +8 -0
- package/src/resource-examples/example-shader/effect.wgsl +7 -0
- package/src/resource-examples/example-shader/preview.svg +1 -0
- package/src/resource-examples/example-shader/resource.json +27 -0
- package/src/resource-examples/example-shader/tint.wgsl +7 -0
- package/src/resource-examples/example-video/ambient.mp4 +0 -0
- package/src/resource-examples/example-video/resource.json +9 -0
- package/src/starter/scene.tsx +20 -0
- package/studio/index.html +2 -0
- package/studio/public/check-circle-filled.svg +3 -0
- package/studio/public/comment-cursor/active-outer.svg +3 -0
- package/studio/public/comment-cursor/default.svg +6 -0
- package/studio/public/osmo-studio-logo.svg +12 -0
- package/studio/public/pencil-simple.svg +5 -0
- package/studio/src/active-outer-TVYUEURE.svg +3 -0
- package/studio/src/app.css +4 -0
- package/studio/src/app.js +705 -0
- package/studio/src/default-6CTRH4V5.svg +6 -0
- package/studio/src/engine_bg.wasm +0 -0
- package/studio/src/inter-cyrillic-ext-wght-normal-IYF56FF6.woff2 +0 -0
- package/studio/src/inter-cyrillic-wght-normal-JEOLYBOO.woff2 +0 -0
- package/studio/src/inter-greek-ext-wght-normal-EOVOK2B5.woff2 +0 -0
- package/studio/src/inter-greek-wght-normal-IRE366VL.woff2 +0 -0
- package/studio/src/inter-latin-ext-wght-normal-HA22NDSG.woff2 +0 -0
- package/studio/src/inter-latin-wght-normal-NRMW37G5.woff2 +0 -0
- package/studio/src/inter-vietnamese-wght-normal-CE5GGD3W.woff2 +0 -0
- package/studio/src/osmo-studio-logo-D5R2V77B.svg +12 -0
- package/third-party/cargo/adler2@2.0.1/Cargo.toml +91 -0
- package/third-party/cargo/adler2@2.0.1/LICENSE-0BSD +12 -0
- package/third-party/cargo/adler2@2.0.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/adler2@2.0.1/LICENSE-MIT +23 -0
- package/third-party/cargo/allocator-api2@0.2.21/Cargo.toml +48 -0
- package/third-party/cargo/allocator-api2@0.2.21/LICENSE-APACHE +176 -0
- package/third-party/cargo/allocator-api2@0.2.21/LICENSE-MIT +23 -0
- package/third-party/cargo/arrayref@0.3.9/Cargo.toml +23 -0
- package/third-party/cargo/arrayref@0.3.9/LICENSE +26 -0
- package/third-party/cargo/arrayvec@0.7.8/Cargo.toml +112 -0
- package/third-party/cargo/arrayvec@0.7.8/LICENSE-APACHE +201 -0
- package/third-party/cargo/arrayvec@0.7.8/LICENSE-MIT +25 -0
- package/third-party/cargo/ascii@1.1.0/Cargo.toml +41 -0
- package/third-party/cargo/ascii@1.1.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/ascii@1.1.0/LICENSE-MIT +22 -0
- package/third-party/cargo/ash@0.38.0+1.3.281/Cargo.toml +66 -0
- package/third-party/cargo/ash@0.38.0+1.3.281/LICENSE-APACHE +67 -0
- package/third-party/cargo/ash@0.38.0+1.3.281/LICENSE-MIT +25 -0
- package/third-party/cargo/autocfg@1.5.1/Cargo.toml +75 -0
- package/third-party/cargo/autocfg@1.5.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/autocfg@1.5.1/LICENSE-MIT +25 -0
- package/third-party/cargo/base64@0.13.1/Cargo.toml +53 -0
- package/third-party/cargo/base64@0.13.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/base64@0.13.1/LICENSE-MIT +21 -0
- package/third-party/cargo/base64@0.22.1/Cargo.toml +85 -0
- package/third-party/cargo/base64@0.22.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/base64@0.22.1/LICENSE-MIT +21 -0
- package/third-party/cargo/bit-set@0.10.0/Cargo.toml +108 -0
- package/third-party/cargo/bit-set@0.10.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/bit-set@0.10.0/LICENSE-MIT +25 -0
- package/third-party/cargo/bit-vec@0.9.1/Cargo.toml +94 -0
- package/third-party/cargo/bit-vec@0.9.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/bit-vec@0.9.1/LICENSE-MIT +25 -0
- package/third-party/cargo/bitflags@2.13.0/Cargo.toml +120 -0
- package/third-party/cargo/bitflags@2.13.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/bitflags@2.13.0/LICENSE-MIT +25 -0
- package/third-party/cargo/block2@0.6.2/Cargo.toml +115 -0
- package/third-party/cargo/block2@0.6.2/MIT-terms.txt +22 -0
- package/third-party/cargo/block2@0.6.2/madsmtm-objc2-b4167b582b2f-LICENSE.md +34 -0
- package/third-party/cargo/bumpalo@3.20.3/Cargo.toml +99 -0
- package/third-party/cargo/bumpalo@3.20.3/LICENSE-APACHE +201 -0
- package/third-party/cargo/bumpalo@3.20.3/LICENSE-MIT +25 -0
- package/third-party/cargo/bytemuck@1.25.1/Cargo.toml +153 -0
- package/third-party/cargo/bytemuck@1.25.1/LICENSE-APACHE +61 -0
- package/third-party/cargo/bytemuck@1.25.1/LICENSE-MIT +9 -0
- package/third-party/cargo/bytemuck@1.25.1/LICENSE-ZLIB +11 -0
- package/third-party/cargo/bytemuck_derive@1.11.0/Cargo.toml +60 -0
- package/third-party/cargo/bytemuck_derive@1.11.0/LICENSE-APACHE +61 -0
- package/third-party/cargo/bytemuck_derive@1.11.0/LICENSE-MIT +9 -0
- package/third-party/cargo/bytemuck_derive@1.11.0/LICENSE-ZLIB +11 -0
- package/third-party/cargo/byteorder-lite@0.1.0/Cargo.toml +57 -0
- package/third-party/cargo/byteorder-lite@0.1.0/LICENSE-MIT +21 -0
- package/third-party/cargo/byteorder-lite@0.1.0/UNLICENSE +24 -0
- package/third-party/cargo/byteorder@1.5.0/COPYING +3 -0
- package/third-party/cargo/byteorder@1.5.0/Cargo.toml +54 -0
- package/third-party/cargo/byteorder@1.5.0/LICENSE-MIT +21 -0
- package/third-party/cargo/byteorder@1.5.0/UNLICENSE +24 -0
- package/third-party/cargo/cfg-if@1.0.4/Cargo.toml +47 -0
- package/third-party/cargo/cfg-if@1.0.4/LICENSE-APACHE +201 -0
- package/third-party/cargo/cfg-if@1.0.4/LICENSE-MIT +25 -0
- package/third-party/cargo/cfg_aliases@0.2.1/Cargo.toml +43 -0
- package/third-party/cargo/cfg_aliases@0.2.1/LICENSE +9 -0
- package/third-party/cargo/chunked_transfer@1.5.0/Cargo.toml +27 -0
- package/third-party/cargo/chunked_transfer@1.5.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/chunked_transfer@1.5.0/LICENSE-MIT +22 -0
- package/third-party/cargo/codespan-reporting@0.13.1/Cargo.toml +115 -0
- package/third-party/cargo/codespan-reporting@0.13.1/LICENSE +201 -0
- package/third-party/cargo/color_quant@1.1.0/Cargo.toml +20 -0
- package/third-party/cargo/color_quant@1.1.0/LICENSE +21 -0
- package/third-party/cargo/console_error_panic_hook@0.1.7/Cargo.toml +27 -0
- package/third-party/cargo/console_error_panic_hook@0.1.7/LICENSE-APACHE +201 -0
- package/third-party/cargo/console_error_panic_hook@0.1.7/LICENSE-MIT +25 -0
- package/third-party/cargo/core_maths@0.1.1/Cargo.toml +37 -0
- package/third-party/cargo/core_maths@0.1.1/LICENSE +21 -0
- package/third-party/cargo/cosmic-text@0.19.0/Cargo.toml +214 -0
- package/third-party/cargo/cosmic-text@0.19.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/cosmic-text@0.19.0/LICENSE-MIT +21 -0
- package/third-party/cargo/crc32fast@1.5.0/Cargo.toml +68 -0
- package/third-party/cargo/crc32fast@1.5.0/LICENSE-APACHE +202 -0
- package/third-party/cargo/crc32fast@1.5.0/LICENSE-MIT +21 -0
- package/third-party/cargo/data-url@0.3.2/Cargo.toml +63 -0
- package/third-party/cargo/data-url@0.3.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/data-url@0.3.2/LICENSE-MIT +25 -0
- package/third-party/cargo/dispatch2@0.3.1/Cargo.toml +122 -0
- package/third-party/cargo/dispatch2@0.3.1/MIT-terms.txt +22 -0
- package/third-party/cargo/dispatch2@0.3.1/madsmtm-objc2-8852b424193c-LICENSE.md +34 -0
- package/third-party/cargo/dlib@0.5.3/Cargo.toml +43 -0
- package/third-party/cargo/dlib@0.5.3/LICENSE.txt +19 -0
- package/third-party/cargo/document-features@0.2.12/Cargo.toml +51 -0
- package/third-party/cargo/document-features@0.2.12/LICENSE-APACHE +73 -0
- package/third-party/cargo/document-features@0.2.12/LICENSE-MIT +19 -0
- package/third-party/cargo/equivalent@1.0.2/Cargo.toml +37 -0
- package/third-party/cargo/equivalent@1.0.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/equivalent@1.0.2/LICENSE-MIT +25 -0
- package/third-party/cargo/errno@0.3.14/Cargo.toml +67 -0
- package/third-party/cargo/errno@0.3.14/LICENSE-APACHE +201 -0
- package/third-party/cargo/errno@0.3.14/LICENSE-MIT +25 -0
- package/third-party/cargo/etagere@0.3.0/Cargo.toml +57 -0
- package/third-party/cargo/etagere@0.3.0/LICENSE-APACHE +13 -0
- package/third-party/cargo/etagere@0.3.0/LICENSE-MIT +20 -0
- package/third-party/cargo/euclid@0.22.14/COPYRIGHT +5 -0
- package/third-party/cargo/euclid@0.22.14/Cargo.toml +66 -0
- package/third-party/cargo/euclid@0.22.14/LICENSE-APACHE +201 -0
- package/third-party/cargo/euclid@0.22.14/LICENSE-MIT +25 -0
- package/third-party/cargo/fastrand@2.5.0/Cargo.toml +89 -0
- package/third-party/cargo/fastrand@2.5.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/fastrand@2.5.0/LICENSE-MIT +23 -0
- package/third-party/cargo/fdeflate@0.3.7/Cargo.toml +55 -0
- package/third-party/cargo/fdeflate@0.3.7/LICENSE-APACHE +177 -0
- package/third-party/cargo/fdeflate@0.3.7/LICENSE-MIT +25 -0
- package/third-party/cargo/flate2@1.1.9/Cargo.toml +274 -0
- package/third-party/cargo/flate2@1.1.9/LICENSE-APACHE +201 -0
- package/third-party/cargo/flate2@1.1.9/LICENSE-MIT +25 -0
- package/third-party/cargo/float-cmp@0.9.0/Cargo.toml +39 -0
- package/third-party/cargo/float-cmp@0.9.0/LICENSE +19 -0
- package/third-party/cargo/float_next_after@1.0.0/Cargo.toml +43 -0
- package/third-party/cargo/float_next_after@1.0.0/LICENSE +23 -0
- package/third-party/cargo/foldhash@0.2.0/Cargo.toml +80 -0
- package/third-party/cargo/foldhash@0.2.0/LICENSE +19 -0
- package/third-party/cargo/font-types@0.11.3/Cargo.toml +55 -0
- package/third-party/cargo/font-types@0.11.3/LICENSE-APACHE +67 -0
- package/third-party/cargo/font-types@0.11.3/LICENSE-MIT +25 -0
- package/third-party/cargo/fontdb@0.23.0/Cargo.toml +83 -0
- package/third-party/cargo/fontdb@0.23.0/LICENSE +21 -0
- package/third-party/cargo/futures-core@0.3.32/Cargo.toml +66 -0
- package/third-party/cargo/futures-core@0.3.32/LICENSE-APACHE +202 -0
- package/third-party/cargo/futures-core@0.3.32/LICENSE-MIT +26 -0
- package/third-party/cargo/futures-task@0.3.32/Cargo.toml +58 -0
- package/third-party/cargo/futures-task@0.3.32/LICENSE-APACHE +202 -0
- package/third-party/cargo/futures-task@0.3.32/LICENSE-MIT +26 -0
- package/third-party/cargo/futures-util@0.3.32/Cargo.toml +181 -0
- package/third-party/cargo/futures-util@0.3.32/LICENSE-APACHE +202 -0
- package/third-party/cargo/futures-util@0.3.32/LICENSE-MIT +26 -0
- package/third-party/cargo/getrandom@0.4.3/Cargo.toml +165 -0
- package/third-party/cargo/getrandom@0.4.3/LICENSE-APACHE +201 -0
- package/third-party/cargo/getrandom@0.4.3/LICENSE-MIT +26 -0
- package/third-party/cargo/gif@0.14.2/Cargo.toml +84 -0
- package/third-party/cargo/gif@0.14.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/gif@0.14.2/LICENSE-MIT +21 -0
- package/third-party/cargo/glam@0.33.2/Cargo.toml +354 -0
- package/third-party/cargo/glam@0.33.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/glam@0.33.2/LICENSE-MIT +23 -0
- package/third-party/cargo/glow@0.17.0/Cargo.toml +122 -0
- package/third-party/cargo/glow@0.17.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/glow@0.17.0/LICENSE-MIT +23 -0
- package/third-party/cargo/glow@0.17.0/LICENSE-ZLIB +15 -0
- package/third-party/cargo/gltf-derive@1.4.1/Cargo.toml +34 -0
- package/third-party/cargo/gltf-derive@1.4.1/LICENSE-APACHE +202 -0
- package/third-party/cargo/gltf-derive@1.4.1/LICENSE-MIT +26 -0
- package/third-party/cargo/gltf-json@1.4.1/Cargo.toml +50 -0
- package/third-party/cargo/gltf-json@1.4.1/LICENSE-APACHE +202 -0
- package/third-party/cargo/gltf-json@1.4.1/LICENSE-MIT +26 -0
- package/third-party/cargo/gltf@1.4.1/Cargo.toml +122 -0
- package/third-party/cargo/gltf@1.4.1/LICENSE-APACHE +202 -0
- package/third-party/cargo/gltf@1.4.1/LICENSE-MIT +26 -0
- package/third-party/cargo/glyphon@0.12.0/Cargo.toml +35 -0
- package/third-party/cargo/glyphon@0.12.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/glyphon@0.12.0/LICENSE-MIT +23 -0
- package/third-party/cargo/glyphon@0.12.0/LICENSE-ZLIB +15 -0
- package/third-party/cargo/gpu-allocator@0.28.0/Cargo.toml +180 -0
- package/third-party/cargo/gpu-allocator@0.28.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/gpu-allocator@0.28.0/LICENSE-MIT +7 -0
- package/third-party/cargo/grid@1.0.1/Cargo.toml +77 -0
- package/third-party/cargo/grid@1.0.1/LICENSE +21 -0
- package/third-party/cargo/half@2.7.1/Cargo.toml +137 -0
- package/third-party/cargo/half@2.7.1/LICENSE-APACHE +176 -0
- package/third-party/cargo/half@2.7.1/LICENSE-MIT +19 -0
- package/third-party/cargo/harfrust@0.5.2/Cargo.toml +115 -0
- package/third-party/cargo/harfrust@0.5.2/harfbuzz-harfrust-efdae3142ab7-LICENSE +22 -0
- package/third-party/cargo/hashbrown@0.16.1/Cargo.toml +180 -0
- package/third-party/cargo/hashbrown@0.16.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/hashbrown@0.16.1/LICENSE-MIT +25 -0
- package/third-party/cargo/hashbrown@0.17.1/Cargo.toml +201 -0
- package/third-party/cargo/hashbrown@0.17.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/hashbrown@0.17.1/LICENSE-MIT +25 -0
- package/third-party/cargo/httpdate@1.0.3/Cargo.toml +35 -0
- package/third-party/cargo/httpdate@1.0.3/LICENSE-APACHE +201 -0
- package/third-party/cargo/httpdate@1.0.3/LICENSE-MIT +19 -0
- package/third-party/cargo/image-webp@0.2.4/Cargo.toml +63 -0
- package/third-party/cargo/image-webp@0.2.4/LICENSE-APACHE +177 -0
- package/third-party/cargo/image-webp@0.2.4/LICENSE-MIT +25 -0
- package/third-party/cargo/image@0.25.10/Cargo.toml +248 -0
- package/third-party/cargo/image@0.25.10/LICENSE-APACHE +177 -0
- package/third-party/cargo/image@0.25.10/LICENSE-MIT +25 -0
- package/third-party/cargo/imagesize@0.14.0/Cargo.toml +104 -0
- package/third-party/cargo/imagesize@0.14.0/LICENSE +21 -0
- package/third-party/cargo/indexmap@2.14.0/Cargo.toml +167 -0
- package/third-party/cargo/indexmap@2.14.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/indexmap@2.14.0/LICENSE-MIT +25 -0
- package/third-party/cargo/inflections@1.1.1/Cargo.toml +21 -0
- package/third-party/cargo/inflections@1.1.1/LICENSE +20 -0
- package/third-party/cargo/itoa@1.0.18/Cargo.toml +66 -0
- package/third-party/cargo/itoa@1.0.18/LICENSE-APACHE +176 -0
- package/third-party/cargo/itoa@1.0.18/LICENSE-MIT +23 -0
- package/third-party/cargo/js-sys@0.3.103/Cargo.toml +98 -0
- package/third-party/cargo/js-sys@0.3.103/LICENSE-APACHE +201 -0
- package/third-party/cargo/js-sys@0.3.103/LICENSE-MIT +25 -0
- package/third-party/cargo/khronos-egl@6.0.0/Cargo.toml +82 -0
- package/third-party/cargo/khronos-egl@6.0.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/khronos-egl@6.0.0/LICENSE-MIT +23 -0
- package/third-party/cargo/kurbo@0.13.1/Cargo.toml +230 -0
- package/third-party/cargo/kurbo@0.13.1/LICENSE-APACHE +202 -0
- package/third-party/cargo/kurbo@0.13.1/LICENSE-MIT +25 -0
- package/third-party/cargo/lazy_static@1.5.0/Cargo.toml +46 -0
- package/third-party/cargo/lazy_static@1.5.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/lazy_static@1.5.0/LICENSE-MIT +25 -0
- package/third-party/cargo/libc@0.2.186/Cargo.toml +229 -0
- package/third-party/cargo/libc@0.2.186/LICENSE-APACHE +176 -0
- package/third-party/cargo/libc@0.2.186/LICENSE-MIT +25 -0
- package/third-party/cargo/libloading@0.8.9/Cargo.toml +90 -0
- package/third-party/cargo/libloading@0.8.9/LICENSE +12 -0
- package/third-party/cargo/libm@0.2.16/Cargo.toml +61 -0
- package/third-party/cargo/libm@0.2.16/LICENSE.txt +258 -0
- package/third-party/cargo/linebender_resource_handle@0.1.1/Cargo.toml +107 -0
- package/third-party/cargo/linebender_resource_handle@0.1.1/LICENSE-APACHE +176 -0
- package/third-party/cargo/linebender_resource_handle@0.1.1/LICENSE-MIT +19 -0
- package/third-party/cargo/litrs@1.0.0/Cargo.toml +63 -0
- package/third-party/cargo/litrs@1.0.0/LICENSE-APACHE +176 -0
- package/third-party/cargo/litrs@1.0.0/LICENSE-MIT +25 -0
- package/third-party/cargo/lock_api@0.4.14/Cargo.toml +68 -0
- package/third-party/cargo/lock_api@0.4.14/LICENSE-APACHE +201 -0
- package/third-party/cargo/lock_api@0.4.14/LICENSE-MIT +25 -0
- package/third-party/cargo/log@0.4.33/Cargo.toml +152 -0
- package/third-party/cargo/log@0.4.33/LICENSE-APACHE +201 -0
- package/third-party/cargo/log@0.4.33/LICENSE-MIT +25 -0
- package/third-party/cargo/lru@0.16.4/Cargo.toml +47 -0
- package/third-party/cargo/lru@0.16.4/LICENSE +21 -0
- package/third-party/cargo/lyon_geom@1.0.19/Cargo.toml +69 -0
- package/third-party/cargo/lyon_geom@1.0.19/nical-lyon-56219c95c658-LICENSE-APACHE +13 -0
- package/third-party/cargo/lyon_geom@1.0.19/nical-lyon-56219c95c658-LICENSE-MIT +20 -0
- package/third-party/cargo/lyon_path@1.0.19/Cargo.toml +58 -0
- package/third-party/cargo/lyon_path@1.0.19/nical-lyon-56219c95c658-LICENSE-APACHE +13 -0
- package/third-party/cargo/lyon_path@1.0.19/nical-lyon-56219c95c658-LICENSE-MIT +20 -0
- package/third-party/cargo/lyon_tessellation@1.0.20/Cargo.toml +71 -0
- package/third-party/cargo/lyon_tessellation@1.0.20/nical-lyon-7fa75ea8eea3-LICENSE-APACHE +13 -0
- package/third-party/cargo/lyon_tessellation@1.0.20/nical-lyon-7fa75ea8eea3-LICENSE-MIT +20 -0
- package/third-party/cargo/memchr@2.8.3/COPYING +3 -0
- package/third-party/cargo/memchr@2.8.3/Cargo.toml +89 -0
- package/third-party/cargo/memchr@2.8.3/LICENSE-MIT +21 -0
- package/third-party/cargo/memchr@2.8.3/UNLICENSE +24 -0
- package/third-party/cargo/memmap2@0.9.11/Cargo.toml +59 -0
- package/third-party/cargo/memmap2@0.9.11/LICENSE-APACHE +201 -0
- package/third-party/cargo/memmap2@0.9.11/LICENSE-MIT +26 -0
- package/third-party/cargo/miniz_oxide@0.8.9/Cargo.toml +91 -0
- package/third-party/cargo/miniz_oxide@0.8.9/LICENSE +25 -0
- package/third-party/cargo/miniz_oxide@0.8.9/LICENSE-APACHE.md +177 -0
- package/third-party/cargo/miniz_oxide@0.8.9/LICENSE-MIT.md +24 -0
- package/third-party/cargo/miniz_oxide@0.8.9/LICENSE-ZLIB.md +14 -0
- package/third-party/cargo/moxcms@0.8.1/Cargo.toml +113 -0
- package/third-party/cargo/moxcms@0.8.1/LICENSE-APACHE.md +201 -0
- package/third-party/cargo/moxcms@0.8.1/LICENSE.md +26 -0
- package/third-party/cargo/naga-types@30.0.0/Cargo.toml +94 -0
- package/third-party/cargo/naga-types@30.0.0/LICENSE.APACHE +176 -0
- package/third-party/cargo/naga-types@30.0.0/LICENSE.MIT +21 -0
- package/third-party/cargo/naga@30.0.0/Cargo.toml +246 -0
- package/third-party/cargo/naga@30.0.0/LICENSE.APACHE +176 -0
- package/third-party/cargo/naga@30.0.0/LICENSE.MIT +21 -0
- package/third-party/cargo/num-traits@0.2.19/Cargo.toml +54 -0
- package/third-party/cargo/num-traits@0.2.19/LICENSE-APACHE +201 -0
- package/third-party/cargo/num-traits@0.2.19/LICENSE-MIT +25 -0
- package/third-party/cargo/objc2-core-foundation@0.3.2/Cargo.toml +221 -0
- package/third-party/cargo/objc2-core-foundation@0.3.2/MIT-terms.txt +22 -0
- package/third-party/cargo/objc2-core-foundation@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
- package/third-party/cargo/objc2-core-graphics@0.3.2/Cargo.toml +331 -0
- package/third-party/cargo/objc2-core-graphics@0.3.2/MIT-terms.txt +22 -0
- package/third-party/cargo/objc2-core-graphics@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
- package/third-party/cargo/objc2-encode@4.1.0/Cargo.toml +81 -0
- package/third-party/cargo/objc2-encode@4.1.0/MIT-terms.txt +22 -0
- package/third-party/cargo/objc2-encode@4.1.0/madsmtm-objc2-8d214f547736-LICENSE.md +34 -0
- package/third-party/cargo/objc2-foundation@0.3.2/Cargo.toml +529 -0
- package/third-party/cargo/objc2-foundation@0.3.2/MIT-terms.txt +22 -0
- package/third-party/cargo/objc2-foundation@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
- package/third-party/cargo/objc2-io-surface@0.3.2/Cargo.toml +139 -0
- package/third-party/cargo/objc2-io-surface@0.3.2/MIT-terms.txt +22 -0
- package/third-party/cargo/objc2-io-surface@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
- package/third-party/cargo/objc2-metal@0.3.2/Cargo.toml +601 -0
- package/third-party/cargo/objc2-metal@0.3.2/MIT-terms.txt +22 -0
- package/third-party/cargo/objc2-metal@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
- package/third-party/cargo/objc2-quartz-core@0.3.2/Cargo.toml +302 -0
- package/third-party/cargo/objc2-quartz-core@0.3.2/MIT-terms.txt +22 -0
- package/third-party/cargo/objc2-quartz-core@0.3.2/madsmtm-objc2-7b1abfd750a2-LICENSE.md +34 -0
- package/third-party/cargo/objc2@0.6.4/Cargo.toml +232 -0
- package/third-party/cargo/objc2@0.6.4/MIT-terms.txt +22 -0
- package/third-party/cargo/objc2@0.6.4/madsmtm-objc2-8852b424193c-LICENSE.md +34 -0
- package/third-party/cargo/once_cell@1.21.4/Cargo.toml +119 -0
- package/third-party/cargo/once_cell@1.21.4/LICENSE-APACHE +201 -0
- package/third-party/cargo/once_cell@1.21.4/LICENSE-MIT +23 -0
- package/third-party/cargo/ordered-float@5.3.0/Cargo.toml +162 -0
- package/third-party/cargo/ordered-float@5.3.0/LICENSE-MIT +25 -0
- package/third-party/cargo/parking_lot@0.12.5/Cargo.toml +87 -0
- package/third-party/cargo/parking_lot@0.12.5/LICENSE-APACHE +201 -0
- package/third-party/cargo/parking_lot@0.12.5/LICENSE-MIT +25 -0
- package/third-party/cargo/parking_lot_core@0.9.12/Cargo.toml +72 -0
- package/third-party/cargo/parking_lot_core@0.9.12/LICENSE-APACHE +201 -0
- package/third-party/cargo/parking_lot_core@0.9.12/LICENSE-MIT +25 -0
- package/third-party/cargo/pico-args@0.5.0/Cargo.toml +32 -0
- package/third-party/cargo/pico-args@0.5.0/LICENSE +20 -0
- package/third-party/cargo/pin-project-lite@0.2.17/Cargo.toml +198 -0
- package/third-party/cargo/pin-project-lite@0.2.17/LICENSE-APACHE +177 -0
- package/third-party/cargo/pin-project-lite@0.2.17/LICENSE-MIT +23 -0
- package/third-party/cargo/pkg-config@0.3.33/Cargo.toml +43 -0
- package/third-party/cargo/pkg-config@0.3.33/LICENSE-APACHE +201 -0
- package/third-party/cargo/pkg-config@0.3.33/LICENSE-MIT +25 -0
- package/third-party/cargo/png@0.18.1/Cargo.toml +130 -0
- package/third-party/cargo/png@0.18.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/png@0.18.1/LICENSE-MIT +25 -0
- package/third-party/cargo/pollster@0.4.0/Cargo.toml +53 -0
- package/third-party/cargo/pollster@0.4.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/pollster@0.4.0/LICENSE-MIT +25 -0
- package/third-party/cargo/polycool@0.4.0/Cargo.toml +83 -0
- package/third-party/cargo/polycool@0.4.0/LICENSE-APACHE +202 -0
- package/third-party/cargo/polycool@0.4.0/LICENSE-MIT +25 -0
- package/third-party/cargo/pp-rs@0.2.1/Cargo.toml +23 -0
- package/third-party/cargo/pp-rs@0.2.1/LICENSE +29 -0
- package/third-party/cargo/presser@0.3.1/Cargo.toml +58 -0
- package/third-party/cargo/presser@0.3.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/presser@0.3.1/LICENSE-MIT +25 -0
- package/third-party/cargo/proc-macro2@1.0.106/Cargo.toml +105 -0
- package/third-party/cargo/proc-macro2@1.0.106/LICENSE-APACHE +176 -0
- package/third-party/cargo/proc-macro2@1.0.106/LICENSE-MIT +23 -0
- package/third-party/cargo/profiling@1.0.18/Cargo.toml +96 -0
- package/third-party/cargo/profiling@1.0.18/aclysma-profiling-8271551172eb-LICENSE-APACHE +201 -0
- package/third-party/cargo/profiling@1.0.18/aclysma-profiling-8271551172eb-LICENSE-MIT +21 -0
- package/third-party/cargo/pxfm@0.1.30/Cargo.toml +53 -0
- package/third-party/cargo/pxfm@0.1.30/LICENSE-APACHE.md +201 -0
- package/third-party/cargo/pxfm@0.1.30/LICENSE.md +26 -0
- package/third-party/cargo/quick-error@2.0.1/Cargo.toml +24 -0
- package/third-party/cargo/quick-error@2.0.1/LICENSE-APACHE +202 -0
- package/third-party/cargo/quick-error@2.0.1/LICENSE-MIT +19 -0
- package/third-party/cargo/quote@1.0.46/Cargo.toml +70 -0
- package/third-party/cargo/quote@1.0.46/LICENSE-APACHE +176 -0
- package/third-party/cargo/quote@1.0.46/LICENSE-MIT +23 -0
- package/third-party/cargo/rangemap@1.7.1/Cargo.toml +91 -0
- package/third-party/cargo/rangemap@1.7.1/LICENSE-APACHE +191 -0
- package/third-party/cargo/rangemap@1.7.1/LICENSE-MIT +7 -0
- package/third-party/cargo/raw-window-handle@0.6.2/Cargo.toml +56 -0
- package/third-party/cargo/raw-window-handle@0.6.2/LICENSE-APACHE.md +177 -0
- package/third-party/cargo/raw-window-handle@0.6.2/LICENSE-MIT.md +21 -0
- package/third-party/cargo/raw-window-handle@0.6.2/LICENSE-ZLIB.md +11 -0
- package/third-party/cargo/raw-window-metal@1.1.0/Cargo.toml +111 -0
- package/third-party/cargo/raw-window-metal@1.1.0/LICENSE-APACHE +202 -0
- package/third-party/cargo/raw-window-metal@1.1.0/LICENSE-MIT +17 -0
- package/third-party/cargo/read-fonts@0.37.0/Cargo.toml +95 -0
- package/third-party/cargo/read-fonts@0.37.0/LICENSE-APACHE +67 -0
- package/third-party/cargo/read-fonts@0.37.0/LICENSE-MIT +25 -0
- package/third-party/cargo/read-fonts@0.39.2/Cargo.toml +96 -0
- package/third-party/cargo/read-fonts@0.39.2/LICENSE-APACHE +67 -0
- package/third-party/cargo/read-fonts@0.39.2/LICENSE-MIT +25 -0
- package/third-party/cargo/renderdoc-sys@1.1.0/Cargo.toml +39 -0
- package/third-party/cargo/renderdoc-sys@1.1.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/renderdoc-sys@1.1.0/LICENSE-MIT +25 -0
- package/third-party/cargo/resvg@0.47.0/Cargo.toml +112 -0
- package/third-party/cargo/resvg@0.47.0/LICENSE-APACHE +176 -0
- package/third-party/cargo/resvg@0.47.0/LICENSE-MIT +25 -0
- package/third-party/cargo/rgb@0.8.53/Cargo.toml +110 -0
- package/third-party/cargo/rgb@0.8.53/LICENSE +21 -0
- package/third-party/cargo/roxmltree@0.21.1/Cargo.toml +75 -0
- package/third-party/cargo/roxmltree@0.21.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/roxmltree@0.21.1/LICENSE-MIT +21 -0
- package/third-party/cargo/rustc-hash@1.1.0/Cargo.toml +25 -0
- package/third-party/cargo/rustc-hash@1.1.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/rustc-hash@1.1.0/LICENSE-MIT +23 -0
- package/third-party/cargo/rustc-hash@2.1.3/Cargo.toml +50 -0
- package/third-party/cargo/rustc-hash@2.1.3/LICENSE-APACHE +176 -0
- package/third-party/cargo/rustc-hash@2.1.3/LICENSE-MIT +23 -0
- package/third-party/cargo/rustix@1.1.4/COPYRIGHT +29 -0
- package/third-party/cargo/rustix@1.1.4/Cargo.toml +292 -0
- package/third-party/cargo/rustix@1.1.4/LICENSE-APACHE +201 -0
- package/third-party/cargo/rustix@1.1.4/LICENSE-Apache-2.0_WITH_LLVM-exception +220 -0
- package/third-party/cargo/rustix@1.1.4/LICENSE-MIT +23 -0
- package/third-party/cargo/rustversion@1.0.23/Cargo.toml +69 -0
- package/third-party/cargo/rustversion@1.0.23/LICENSE-APACHE +176 -0
- package/third-party/cargo/rustversion@1.0.23/LICENSE-MIT +23 -0
- package/third-party/cargo/rustybuzz@0.20.1/Cargo.toml +110 -0
- package/third-party/cargo/rustybuzz@0.20.1/LICENSE +22 -0
- package/third-party/cargo/scopeguard@1.2.0/Cargo.toml +43 -0
- package/third-party/cargo/scopeguard@1.2.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/scopeguard@1.2.0/LICENSE-MIT +25 -0
- package/third-party/cargo/self_cell@1.2.2/Cargo.toml +33 -0
- package/third-party/cargo/self_cell@1.2.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/self_cell@1.2.2/LICENSE-GPLv2 +339 -0
- package/third-party/cargo/self_cell@1.3.0/Cargo.toml +33 -0
- package/third-party/cargo/self_cell@1.3.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/self_cell@1.3.0/LICENSE-GPLv2 +339 -0
- package/third-party/cargo/serde@1.0.228/Cargo.toml +84 -0
- package/third-party/cargo/serde@1.0.228/LICENSE-APACHE +176 -0
- package/third-party/cargo/serde@1.0.228/LICENSE-MIT +23 -0
- package/third-party/cargo/serde_core@1.0.228/Cargo.toml +87 -0
- package/third-party/cargo/serde_core@1.0.228/LICENSE-APACHE +176 -0
- package/third-party/cargo/serde_core@1.0.228/LICENSE-MIT +23 -0
- package/third-party/cargo/serde_derive@1.0.228/Cargo.toml +87 -0
- package/third-party/cargo/serde_derive@1.0.228/LICENSE-APACHE +176 -0
- package/third-party/cargo/serde_derive@1.0.228/LICENSE-MIT +23 -0
- package/third-party/cargo/serde_json@1.0.150/Cargo.toml +162 -0
- package/third-party/cargo/serde_json@1.0.150/LICENSE-APACHE +176 -0
- package/third-party/cargo/serde_json@1.0.150/LICENSE-MIT +23 -0
- package/third-party/cargo/simd-adler32@0.3.10/Cargo.toml +68 -0
- package/third-party/cargo/simd-adler32@0.3.10/LICENSE.md +21 -0
- package/third-party/cargo/simplecss@0.2.2/Cargo.toml +146 -0
- package/third-party/cargo/simplecss@0.2.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/simplecss@0.2.2/LICENSE-MIT +21 -0
- package/third-party/cargo/siphasher@1.0.3/COPYING +7 -0
- package/third-party/cargo/siphasher@1.0.3/Cargo.toml +64 -0
- package/third-party/cargo/skrifa@0.40.0/Cargo.toml +86 -0
- package/third-party/cargo/skrifa@0.40.0/LICENSE-APACHE +67 -0
- package/third-party/cargo/skrifa@0.40.0/LICENSE-MIT +25 -0
- package/third-party/cargo/skrifa@0.42.1/Cargo.toml +86 -0
- package/third-party/cargo/skrifa@0.42.1/LICENSE-APACHE +67 -0
- package/third-party/cargo/skrifa@0.42.1/LICENSE-MIT +25 -0
- package/third-party/cargo/slab@0.4.12/Cargo.toml +67 -0
- package/third-party/cargo/slab@0.4.12/LICENSE +25 -0
- package/third-party/cargo/slotmap@1.1.1/Cargo.toml +83 -0
- package/third-party/cargo/slotmap@1.1.1/LICENSE +19 -0
- package/third-party/cargo/smallvec@1.15.2/Cargo.toml +103 -0
- package/third-party/cargo/smallvec@1.15.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/smallvec@1.15.2/LICENSE-MIT +25 -0
- package/third-party/cargo/smol_str@0.3.6/Cargo.toml +155 -0
- package/third-party/cargo/smol_str@0.3.6/LICENSE-APACHE +201 -0
- package/third-party/cargo/smol_str@0.3.6/LICENSE-MIT +23 -0
- package/third-party/cargo/spirv@0.4.0+sdk-1.4.341.0/Cargo.toml +56 -0
- package/third-party/cargo/spirv@0.4.0+sdk-1.4.341.0/gfx-rs-rspirv-8afc3d0ac8e1-LICENSE +202 -0
- package/third-party/cargo/static_assertions@1.1.0/Cargo.toml +39 -0
- package/third-party/cargo/static_assertions@1.1.0/LICENSE-APACHE +202 -0
- package/third-party/cargo/static_assertions@1.1.0/LICENSE-MIT +21 -0
- package/third-party/cargo/strict-num@0.1.1/Cargo.toml +35 -0
- package/third-party/cargo/strict-num@0.1.1/LICENSE +19 -0
- package/third-party/cargo/svgtypes@0.16.1/Cargo.toml +57 -0
- package/third-party/cargo/svgtypes@0.16.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/svgtypes@0.16.1/LICENSE-MIT +20 -0
- package/third-party/cargo/swash@0.2.9/Cargo.toml +93 -0
- package/third-party/cargo/swash@0.2.9/LICENSE-APACHE +201 -0
- package/third-party/cargo/swash@0.2.9/LICENSE-MIT +25 -0
- package/third-party/cargo/syn@2.0.118/Cargo.toml +272 -0
- package/third-party/cargo/syn@2.0.118/LICENSE-APACHE +176 -0
- package/third-party/cargo/syn@2.0.118/LICENSE-MIT +23 -0
- package/third-party/cargo/sys-locale@0.3.2/Cargo.toml +73 -0
- package/third-party/cargo/sys-locale@0.3.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/sys-locale@0.3.2/LICENSE-MIT +21 -0
- package/third-party/cargo/taffy@0.12.1/Cargo.toml +171 -0
- package/third-party/cargo/taffy@0.12.1/DioxusLabs-taffy-1c9e37a0a1cf-LICENSE.md +25 -0
- package/third-party/cargo/tempfile@3.27.0/Cargo.toml +105 -0
- package/third-party/cargo/tempfile@3.27.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/tempfile@3.27.0/LICENSE-MIT +25 -0
- package/third-party/cargo/termcolor@1.4.1/COPYING +3 -0
- package/third-party/cargo/termcolor@1.4.1/Cargo.toml +40 -0
- package/third-party/cargo/termcolor@1.4.1/LICENSE-MIT +21 -0
- package/third-party/cargo/termcolor@1.4.1/UNLICENSE +24 -0
- package/third-party/cargo/thiserror-impl@2.0.18/Cargo.toml +52 -0
- package/third-party/cargo/thiserror-impl@2.0.18/LICENSE-APACHE +176 -0
- package/third-party/cargo/thiserror-impl@2.0.18/LICENSE-MIT +23 -0
- package/third-party/cargo/thiserror@2.0.18/Cargo.toml +119 -0
- package/third-party/cargo/thiserror@2.0.18/LICENSE-APACHE +176 -0
- package/third-party/cargo/thiserror@2.0.18/LICENSE-MIT +23 -0
- package/third-party/cargo/tiny-skia-path@0.12.0/Cargo.toml +58 -0
- package/third-party/cargo/tiny-skia-path@0.12.0/LICENSE +30 -0
- package/third-party/cargo/tiny-skia@0.12.0/Cargo.toml +116 -0
- package/third-party/cargo/tiny-skia@0.12.0/LICENSE +30 -0
- package/third-party/cargo/tiny_http@0.12.0/Cargo.toml +82 -0
- package/third-party/cargo/tiny_http@0.12.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/tiny_http@0.12.0/LICENSE-MIT +25 -0
- package/third-party/cargo/tinyvec@1.12.0/Cargo.toml +184 -0
- package/third-party/cargo/tinyvec@1.12.0/LICENSE-APACHE.md +202 -0
- package/third-party/cargo/tinyvec@1.12.0/LICENSE-MIT.md +5 -0
- package/third-party/cargo/tinyvec@1.12.0/LICENSE-ZLIB.md +11 -0
- package/third-party/cargo/tinyvec_macros@0.1.1/Cargo.toml +19 -0
- package/third-party/cargo/tinyvec_macros@0.1.1/LICENSE-APACHE.md +202 -0
- package/third-party/cargo/tinyvec_macros@0.1.1/LICENSE-MIT.md +21 -0
- package/third-party/cargo/tinyvec_macros@0.1.1/LICENSE-ZLIB.md +20 -0
- package/third-party/cargo/ttf-parser@0.25.1/Cargo.toml +101 -0
- package/third-party/cargo/ttf-parser@0.25.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/ttf-parser@0.25.1/LICENSE-MIT +20 -0
- package/third-party/cargo/unicode-bidi-mirroring@0.4.0/Cargo.toml +22 -0
- package/third-party/cargo/unicode-bidi-mirroring@0.4.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-bidi-mirroring@0.4.0/LICENSE-MIT +21 -0
- package/third-party/cargo/unicode-bidi@0.3.18/COPYRIGHT +8 -0
- package/third-party/cargo/unicode-bidi@0.3.18/Cargo.toml +90 -0
- package/third-party/cargo/unicode-bidi@0.3.18/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-bidi@0.3.18/LICENSE-MIT +25 -0
- package/third-party/cargo/unicode-ccc@0.4.0/Cargo.toml +22 -0
- package/third-party/cargo/unicode-ccc@0.4.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-ccc@0.4.0/LICENSE-MIT +21 -0
- package/third-party/cargo/unicode-ident@1.0.24/Cargo.toml +84 -0
- package/third-party/cargo/unicode-ident@1.0.24/LICENSE-APACHE +176 -0
- package/third-party/cargo/unicode-ident@1.0.24/LICENSE-MIT +23 -0
- package/third-party/cargo/unicode-ident@1.0.24/LICENSE-UNICODE +39 -0
- package/third-party/cargo/unicode-linebreak@0.1.5/Cargo.toml +32 -0
- package/third-party/cargo/unicode-linebreak@0.1.5/LICENSE +201 -0
- package/third-party/cargo/unicode-properties@0.1.4/COPYRIGHT +7 -0
- package/third-party/cargo/unicode-properties@0.1.4/Cargo.toml +65 -0
- package/third-party/cargo/unicode-properties@0.1.4/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-properties@0.1.4/LICENSE-MIT +25 -0
- package/third-party/cargo/unicode-script@0.5.8/Cargo.toml +63 -0
- package/third-party/cargo/unicode-script@0.5.8/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-script@0.5.8/LICENSE-MIT +27 -0
- package/third-party/cargo/unicode-segmentation@1.13.3/COPYRIGHT +7 -0
- package/third-party/cargo/unicode-segmentation@1.13.3/Cargo.toml +91 -0
- package/third-party/cargo/unicode-segmentation@1.13.3/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-segmentation@1.13.3/LICENSE-MIT +25 -0
- package/third-party/cargo/unicode-vo@0.1.0/Cargo.toml +22 -0
- package/third-party/cargo/unicode-vo@0.1.0/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-vo@0.1.0/LICENSE-MIT +21 -0
- package/third-party/cargo/unicode-width@0.2.2/COPYRIGHT +7 -0
- package/third-party/cargo/unicode-width@0.2.2/Cargo.toml +77 -0
- package/third-party/cargo/unicode-width@0.2.2/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-width@0.2.2/LICENSE-MIT +25 -0
- package/third-party/cargo/unicode-xid@0.2.6/COPYRIGHT +7 -0
- package/third-party/cargo/unicode-xid@0.2.6/Cargo.toml +65 -0
- package/third-party/cargo/unicode-xid@0.2.6/LICENSE-APACHE +201 -0
- package/third-party/cargo/unicode-xid@0.2.6/LICENSE-MIT +25 -0
- package/third-party/cargo/urlencoding@2.1.3/Cargo.toml +41 -0
- package/third-party/cargo/urlencoding@2.1.3/LICENSE +20 -0
- package/third-party/cargo/usvg@0.47.0/Cargo.toml +138 -0
- package/third-party/cargo/usvg@0.47.0/LICENSE-APACHE +176 -0
- package/third-party/cargo/usvg@0.47.0/LICENSE-MIT +25 -0
- package/third-party/cargo/version_check@0.9.5/Cargo.toml +38 -0
- package/third-party/cargo/version_check@0.9.5/LICENSE-APACHE +201 -0
- package/third-party/cargo/version_check@0.9.5/LICENSE-MIT +19 -0
- package/third-party/cargo/wasm-bindgen-futures@0.4.76/Cargo.toml +88 -0
- package/third-party/cargo/wasm-bindgen-futures@0.4.76/LICENSE-APACHE +201 -0
- package/third-party/cargo/wasm-bindgen-futures@0.4.76/LICENSE-MIT +25 -0
- package/third-party/cargo/wasm-bindgen-macro-support@0.2.126/Cargo.toml +83 -0
- package/third-party/cargo/wasm-bindgen-macro-support@0.2.126/LICENSE-APACHE +201 -0
- package/third-party/cargo/wasm-bindgen-macro-support@0.2.126/LICENSE-MIT +25 -0
- package/third-party/cargo/wasm-bindgen-macro@0.2.126/Cargo.toml +73 -0
- package/third-party/cargo/wasm-bindgen-macro@0.2.126/LICENSE-APACHE +201 -0
- package/third-party/cargo/wasm-bindgen-macro@0.2.126/LICENSE-MIT +25 -0
- package/third-party/cargo/wasm-bindgen-shared@0.2.126/Cargo.toml +66 -0
- package/third-party/cargo/wasm-bindgen-shared@0.2.126/LICENSE-APACHE +201 -0
- package/third-party/cargo/wasm-bindgen-shared@0.2.126/LICENSE-MIT +25 -0
- package/third-party/cargo/wasm-bindgen@0.2.126/Cargo.toml +122 -0
- package/third-party/cargo/wasm-bindgen@0.2.126/LICENSE-APACHE +201 -0
- package/third-party/cargo/wasm-bindgen@0.2.126/LICENSE-MIT +25 -0
- package/third-party/cargo/wayland-sys@0.31.11/Cargo.toml +81 -0
- package/third-party/cargo/wayland-sys@0.31.11/LICENSE.txt +19 -0
- package/third-party/cargo/web-sys@0.3.103/Cargo.toml +2823 -0
- package/third-party/cargo/web-sys@0.3.103/LICENSE-APACHE +201 -0
- package/third-party/cargo/web-sys@0.3.103/LICENSE-MIT +25 -0
- package/third-party/cargo/weezl@0.1.12/Cargo.toml +120 -0
- package/third-party/cargo/weezl@0.1.12/LICENSE-APACHE +201 -0
- package/third-party/cargo/weezl@0.1.12/LICENSE-MIT +22 -0
- package/third-party/cargo/wgpu-core-deps-apple@30.0.0/Cargo.toml +53 -0
- package/third-party/cargo/wgpu-core-deps-apple@30.0.0/LICENSE.APACHE +176 -0
- package/third-party/cargo/wgpu-core-deps-apple@30.0.0/LICENSE.MIT +21 -0
- package/third-party/cargo/wgpu-core@30.0.0/Cargo.toml +237 -0
- package/third-party/cargo/wgpu-core@30.0.0/LICENSE.APACHE +176 -0
- package/third-party/cargo/wgpu-core@30.0.0/LICENSE.MIT +21 -0
- package/third-party/cargo/wgpu-hal@30.0.0/Cargo.toml +541 -0
- package/third-party/cargo/wgpu-hal@30.0.0/LICENSE.APACHE +176 -0
- package/third-party/cargo/wgpu-hal@30.0.0/LICENSE.MIT +21 -0
- package/third-party/cargo/wgpu-naga-bridge@30.0.0/Cargo.toml +46 -0
- package/third-party/cargo/wgpu-naga-bridge@30.0.0/LICENSE.APACHE +176 -0
- package/third-party/cargo/wgpu-naga-bridge@30.0.0/LICENSE.MIT +21 -0
- package/third-party/cargo/wgpu-types@30.0.0/Cargo.toml +161 -0
- package/third-party/cargo/wgpu-types@30.0.0/LICENSE.APACHE +176 -0
- package/third-party/cargo/wgpu-types@30.0.0/LICENSE.MIT +21 -0
- package/third-party/cargo/wgpu@30.0.0/Cargo.toml +278 -0
- package/third-party/cargo/wgpu@30.0.0/LICENSE.APACHE +176 -0
- package/third-party/cargo/wgpu@30.0.0/LICENSE.MIT +21 -0
- package/third-party/cargo/xmlwriter@0.1.0/Cargo.toml +23 -0
- package/third-party/cargo/xmlwriter@0.1.0/LICENSE +21 -0
- package/third-party/cargo/yazi@0.2.1/Cargo.toml +45 -0
- package/third-party/cargo/yazi@0.2.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/yazi@0.2.1/LICENSE-MIT +19 -0
- package/third-party/cargo/zeno@0.3.3/Cargo.toml +65 -0
- package/third-party/cargo/zeno@0.3.3/LICENSE-APACHE +201 -0
- package/third-party/cargo/zeno@0.3.3/LICENSE-MIT +25 -0
- package/third-party/cargo/zerocopy-derive@0.8.54/Cargo.toml +209 -0
- package/third-party/cargo/zerocopy-derive@0.8.54/LICENSE-APACHE +202 -0
- package/third-party/cargo/zerocopy-derive@0.8.54/LICENSE-BSD +24 -0
- package/third-party/cargo/zerocopy-derive@0.8.54/LICENSE-MIT +26 -0
- package/third-party/cargo/zerocopy@0.8.54/Cargo.toml +419 -0
- package/third-party/cargo/zerocopy@0.8.54/LICENSE-APACHE +202 -0
- package/third-party/cargo/zerocopy@0.8.54/LICENSE-BSD +24 -0
- package/third-party/cargo/zerocopy@0.8.54/LICENSE-MIT +26 -0
- package/third-party/cargo/zmij@1.0.23/Cargo.toml +90 -0
- package/third-party/cargo/zmij@1.0.23/LICENSE-MIT +23 -0
- package/third-party/cargo/zune-core@0.5.1/Cargo.toml +47 -0
- package/third-party/cargo/zune-core@0.5.1/LICENSE-APACHE +201 -0
- package/third-party/cargo/zune-core@0.5.1/LICENSE-MIT +21 -0
- package/third-party/cargo/zune-core@0.5.1/LICENSE-ZLIB +19 -0
- package/third-party/cargo/zune-jpeg@0.5.15/Cargo.toml +67 -0
- package/third-party/cargo/zune-jpeg@0.5.15/LICENSE-APACHE +201 -0
- package/third-party/cargo/zune-jpeg@0.5.15/LICENSE-MIT +21 -0
- package/third-party/cargo/zune-jpeg@0.5.15/LICENSE-ZLIB +19 -0
- package/third-party/inventory.json +3614 -0
- package/third-party/npm/@esbuild__darwin-arm64@0.27.4/esbuild-0.27.4-LICENSE.md +21 -0
- package/third-party/npm/@esbuild__darwin-arm64@0.27.4/go-LICENSE +27 -0
- package/third-party/npm/@esbuild__darwin-arm64@0.27.4/golang-x-sys-c0bba94af5f8-LICENSE +27 -0
- package/third-party/npm/@esbuild__darwin-arm64@0.27.4/package.json +20 -0
- package/third-party/npm/@floating-ui__core@1.7.5/LICENSE +20 -0
- package/third-party/npm/@floating-ui__core@1.7.5/package.json +63 -0
- package/third-party/npm/@floating-ui__dom@1.7.6/LICENSE +20 -0
- package/third-party/npm/@floating-ui__dom@1.7.6/package.json +71 -0
- package/third-party/npm/@floating-ui__react-dom@2.1.8/LICENSE +20 -0
- package/third-party/npm/@floating-ui__react-dom@2.1.8/package.json +76 -0
- package/third-party/npm/@floating-ui__utils@0.2.11/LICENSE +20 -0
- package/third-party/npm/@floating-ui__utils@0.2.11/package.json +70 -0
- package/third-party/npm/@radix-ui__primitive@1.1.7/LICENSE +21 -0
- package/third-party/npm/@radix-ui__primitive@1.1.7/package.json +79 -0
- package/third-party/npm/@radix-ui__react-arrow@1.1.15/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-arrow@1.1.15/package.json +68 -0
- package/third-party/npm/@radix-ui__react-collection@1.1.15/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-collection@1.1.15/package.json +71 -0
- package/third-party/npm/@radix-ui__react-compose-refs@1.1.5/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-compose-refs@1.1.5/package.json +60 -0
- package/third-party/npm/@radix-ui__react-context-menu@2.3.7/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-context-menu@2.3.7/package.json +73 -0
- package/third-party/npm/@radix-ui__react-context@1.2.2/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-context@1.2.2/package.json +60 -0
- package/third-party/npm/@radix-ui__react-direction@1.1.4/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-direction@1.1.4/package.json +60 -0
- package/third-party/npm/@radix-ui__react-dismissable-layer@1.1.19/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-dismissable-layer@1.1.19/package.json +74 -0
- package/third-party/npm/@radix-ui__react-focus-guards@1.1.6/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-focus-guards@1.1.6/package.json +60 -0
- package/third-party/npm/@radix-ui__react-focus-scope@1.1.16/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-focus-scope@1.1.16/package.json +71 -0
- package/third-party/npm/@radix-ui__react-id@1.1.4/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-id@1.1.4/package.json +63 -0
- package/third-party/npm/@radix-ui__react-menu@2.1.24/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-menu@2.1.24/package.json +86 -0
- package/third-party/npm/@radix-ui__react-popper@1.3.7/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-popper@1.3.7/package.json +78 -0
- package/third-party/npm/@radix-ui__react-portal@1.1.17/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-portal@1.1.17/package.json +69 -0
- package/third-party/npm/@radix-ui__react-presence@1.1.10/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-presence@1.1.10/package.json +68 -0
- package/third-party/npm/@radix-ui__react-primitive@2.1.10/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-primitive@2.1.10/package.json +68 -0
- package/third-party/npm/@radix-ui__react-roving-focus@1.1.19/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-roving-focus@1.1.19/package.json +78 -0
- package/third-party/npm/@radix-ui__react-slot@1.3.3/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-slot@1.3.3/package.json +63 -0
- package/third-party/npm/@radix-ui__react-use-callback-ref@1.1.4/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-callback-ref@1.1.4/package.json +60 -0
- package/third-party/npm/@radix-ui__react-use-controllable-state@1.2.6/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-controllable-state@1.2.6/package.json +65 -0
- package/third-party/npm/@radix-ui__react-use-effect-event@0.0.5/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-effect-event@0.0.5/package.json +65 -0
- package/third-party/npm/@radix-ui__react-use-is-hydrated@0.1.3/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-is-hydrated@0.1.3/package.json +61 -0
- package/third-party/npm/@radix-ui__react-use-layout-effect@1.1.4/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-layout-effect@1.1.4/package.json +60 -0
- package/third-party/npm/@radix-ui__react-use-size@1.1.4/LICENSE +21 -0
- package/third-party/npm/@radix-ui__react-use-size@1.1.4/package.json +63 -0
- package/third-party/npm/aria-hidden@1.2.6/LICENSE +21 -0
- package/third-party/npm/aria-hidden@1.2.6/package.json +71 -0
- package/third-party/npm/detect-node-es@1.1.0/LICENSE +21 -0
- package/third-party/npm/detect-node-es@1.1.0/package.json +29 -0
- package/third-party/npm/esbuild@0.27.4/LICENSE.md +21 -0
- package/third-party/npm/esbuild@0.27.4/package.json +49 -0
- package/third-party/npm/fflate@0.8.3/LICENSE +21 -0
- package/third-party/npm/fflate@0.8.3/package.json +128 -0
- package/third-party/npm/get-nonce@1.0.1/LICENSE +21 -0
- package/third-party/npm/get-nonce@1.0.1/package.json +68 -0
- package/third-party/npm/mediabunny@1.52.2/LICENSE +373 -0
- package/third-party/npm/mediabunny@1.52.2/package.json +136 -0
- package/third-party/npm/mediabunny@1.52.2/source/LICENSE +373 -0
- package/third-party/npm/mediabunny@1.52.2/source/package.json +136 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/adts/adts-demuxer.ts +400 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/adts/adts-muxer.ts +124 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/adts/adts-reader.ts +85 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/aes.ts +307 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/codec-data.ts +3178 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/codec.ts +1154 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/conversion.ts +2042 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/custom-coder.ts +212 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/decode.ts +263 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/demuxer.ts +43 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/encode.ts +1412 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/flac/flac-demuxer.ts +813 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/flac/flac-misc.ts +165 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/flac/flac-muxer.ts +350 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/hls/hls-demuxer.ts +995 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/hls/hls-misc.ts +81 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/hls/hls-muxer.ts +1529 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/hls/hls-segmented-input.ts +772 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/id3.ts +972 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/index.ts +319 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/input-format.ts +819 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/input-track.ts +1141 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/input.ts +571 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/isobmff/isobmff-boxes.ts +1911 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/isobmff/isobmff-demuxer.ts +3953 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/isobmff/isobmff-misc.ts +90 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/isobmff/isobmff-muxer.ts +1595 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/isobmff/isobmff-reader.ts +91 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/logging.ts +120 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/matroska/ebml.ts +770 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/matroska/matroska-demuxer.ts +2598 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/matroska/matroska-misc.ts +29 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/matroska/matroska-muxer.ts +1360 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/media-sink.ts +2589 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/media-source.ts +3041 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/metadata.ts +335 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/misc.ts +1336 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/mp3/mp3-demuxer.ts +469 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/mp3/mp3-muxer.ts +169 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/mp3/mp3-reader.ts +61 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/mp3/mp3-writer.ts +121 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/mpeg-ts/mpeg-ts-demuxer.ts +2519 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/mpeg-ts/mpeg-ts-misc.ts +32 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/mpeg-ts/mpeg-ts-muxer.ts +842 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/muxer.ts +80 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/node.ts +11 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/ogg/ogg-demuxer.ts +1087 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/ogg/ogg-misc.ts +116 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/ogg/ogg-muxer.ts +527 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/ogg/ogg-reader.ts +93 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/output-format.ts +1425 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/output.ts +940 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/packet.ts +287 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/pcm.ts +112 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/reader.ts +391 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/resample.ts +287 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/sample.ts +3231 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/segmented-input.ts +575 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/source.ts +2588 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/subtitles.ts +131 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/target.ts +871 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/tsconfig.json +16 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/wave/riff-writer.ts +36 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/wave/wave-demuxer.ts +583 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/wave/wave-muxer.ts +375 -0
- package/third-party/npm/mediabunny@1.52.2/source/src/writer.ts +128 -0
- package/third-party/npm/react-dom@19.3.0/LICENSE +21 -0
- package/third-party/npm/react-dom@19.3.0/package.json +117 -0
- package/third-party/npm/react-remove-scroll-bar@2.3.8/package.json +85 -0
- package/third-party/npm/react-remove-scroll-bar@2.3.8/react-remove-scroll-bar-LICENSE +21 -0
- package/third-party/npm/react-remove-scroll@2.7.2/LICENSE +21 -0
- package/third-party/npm/react-remove-scroll@2.7.2/package.json +98 -0
- package/third-party/npm/react-style-singleton@2.2.3/LICENSE +21 -0
- package/third-party/npm/react-style-singleton@2.2.3/package.json +90 -0
- package/third-party/npm/react@19.3.0/LICENSE +21 -0
- package/third-party/npm/react@19.3.0/package.json +51 -0
- package/third-party/npm/scheduler@0.28.0/LICENSE +21 -0
- package/third-party/npm/scheduler@0.28.0/package.json +27 -0
- package/third-party/npm/tslib@2.8.1/LICENSE.txt +12 -0
- package/third-party/npm/tslib@2.8.1/package.json +47 -0
- package/third-party/npm/typescript@5.9.3/LICENSE.txt +55 -0
- package/third-party/npm/typescript@5.9.3/ThirdPartyNoticeText.txt +193 -0
- package/third-party/npm/typescript@5.9.3/package.json +120 -0
- package/third-party/npm/use-callback-ref@1.3.3/LICENSE +21 -0
- package/third-party/npm/use-callback-ref@1.3.3/package.json +85 -0
- package/third-party/npm/use-sidecar@1.1.3/LICENSE +21 -0
- package/third-party/npm/use-sidecar@1.1.3/package.json +93 -0
- package/third-party/packages/render-engine/assets/OFL.txt +93 -0
- package/third-party/packages/studio/public/fonts/OFL.txt +93 -0
- package/third-party/packages/studio/vendor/osmo-icons/LICENSE +21 -0
- package/third-party/packages/studio/vendor/shadcn/LICENSE +21 -0
- package/third-party/repository-notices.md +41 -0
- package/third-party/rust-standard-library/COPYRIGHT-library.html +5556 -0
- package/third-party/rust-standard-library/licenses/Apache-2.0.txt +73 -0
- package/third-party/rust-standard-library/licenses/BSD-2-Clause.txt +9 -0
- package/third-party/rust-standard-library/licenses/CC-BY-SA-4.0.txt +427 -0
- package/third-party/rust-standard-library/licenses/GCC-exception-3.1.txt +30 -0
- package/third-party/rust-standard-library/licenses/GPL-2.0-only.txt +133 -0
- package/third-party/rust-standard-library/licenses/GPL-3.0-or-later.txt +202 -0
- package/third-party/rust-standard-library/licenses/ISC.txt +7 -0
- package/third-party/rust-standard-library/licenses/LLVM-exception.txt +15 -0
- package/third-party/rust-standard-library/licenses/MIT.txt +9 -0
- package/third-party/rust-standard-library/licenses/NCSA.txt +29 -0
- package/third-party/rust-standard-library/licenses/OFL-1.1.txt +43 -0
- package/third-party/rust-standard-library/licenses/Unicode-3.0.txt +39 -0
- package/third-party/rust-standard-library/version.txt +1 -0
- package/vendor/osmo-lite +0 -0
- package/vendor/osmo-scene-0.1.0.tgz +0 -0
- package/vendor/platform.json +1 -0
|
@@ -0,0 +1,3953 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026-present, Vanilagy and contributors
|
|
3
|
+
*
|
|
4
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { TrackType } from '../output';
|
|
10
|
+
import { parseAacAudioSpecificConfig } from '../../shared/aac-misc';
|
|
11
|
+
import {
|
|
12
|
+
AacCodecInfo,
|
|
13
|
+
AudioCodec,
|
|
14
|
+
extractAudioCodecString,
|
|
15
|
+
extractVideoCodecString,
|
|
16
|
+
MediaCodec,
|
|
17
|
+
OPUS_SAMPLE_RATE,
|
|
18
|
+
parsePcmCodec,
|
|
19
|
+
PCM_AUDIO_CODECS,
|
|
20
|
+
PcmAudioCodec,
|
|
21
|
+
PRORES_FOURCCS,
|
|
22
|
+
ProresFourCc,
|
|
23
|
+
VideoCodec,
|
|
24
|
+
} from '../codec';
|
|
25
|
+
import {
|
|
26
|
+
Av1CodecInfo,
|
|
27
|
+
AvcDecoderConfigurationRecord,
|
|
28
|
+
extractAv1CodecInfoFromPacket,
|
|
29
|
+
extractVp9CodecInfoFromPacket,
|
|
30
|
+
FlacBlockType,
|
|
31
|
+
HevcDecoderConfigurationRecord,
|
|
32
|
+
Vp9CodecInfo,
|
|
33
|
+
parseEac3Config,
|
|
34
|
+
getEac3SampleRate,
|
|
35
|
+
getEac3ChannelCount,
|
|
36
|
+
AC3_ACMOD_CHANNEL_COUNTS,
|
|
37
|
+
} from '../codec-data';
|
|
38
|
+
import { Demuxer } from '../demuxer';
|
|
39
|
+
import { Input } from '../input';
|
|
40
|
+
import {
|
|
41
|
+
InputAudioTrackBacking,
|
|
42
|
+
InputTrackBacking,
|
|
43
|
+
InputVideoTrackBacking,
|
|
44
|
+
} from '../input-track';
|
|
45
|
+
import { PacketRetrievalOptions } from '../media-sink';
|
|
46
|
+
import {
|
|
47
|
+
assert,
|
|
48
|
+
binarySearchExact,
|
|
49
|
+
binarySearchLessOrEqual,
|
|
50
|
+
bytesToHexString,
|
|
51
|
+
COLOR_PRIMARIES_MAP_INVERSE,
|
|
52
|
+
findLastIndex,
|
|
53
|
+
isIso639Dash2LanguageCode,
|
|
54
|
+
last,
|
|
55
|
+
MATRIX_COEFFICIENTS_MAP_INVERSE,
|
|
56
|
+
normalizeRotation,
|
|
57
|
+
roundToMultiple,
|
|
58
|
+
Rotation,
|
|
59
|
+
textDecoder,
|
|
60
|
+
TransformationMatrix,
|
|
61
|
+
TRANSFER_CHARACTERISTICS_MAP_INVERSE,
|
|
62
|
+
UNDETERMINED_LANGUAGE,
|
|
63
|
+
toDataView,
|
|
64
|
+
roundIfAlmostInteger,
|
|
65
|
+
hexStringToBytes,
|
|
66
|
+
HEX_STRING_REGEX,
|
|
67
|
+
} from '../misc';
|
|
68
|
+
import { EncodedPacket, PLACEHOLDER_DATA } from '../packet';
|
|
69
|
+
import { buildIsobmffMimeType, parsePsshBoxContents, psshBoxesAreEqual, PsshBox } from './isobmff-misc';
|
|
70
|
+
import {
|
|
71
|
+
MAX_BOX_HEADER_SIZE,
|
|
72
|
+
MIN_BOX_HEADER_SIZE,
|
|
73
|
+
readBoxHeader,
|
|
74
|
+
readDataBox,
|
|
75
|
+
readFixed_16_16,
|
|
76
|
+
readFixed_2_30,
|
|
77
|
+
readIsomVariableInteger,
|
|
78
|
+
readMetadataStringShort,
|
|
79
|
+
} from './isobmff-reader';
|
|
80
|
+
import {
|
|
81
|
+
FileSlice,
|
|
82
|
+
readBytes,
|
|
83
|
+
readF64Be,
|
|
84
|
+
readI16Be,
|
|
85
|
+
readI32Be,
|
|
86
|
+
readI64Be,
|
|
87
|
+
Reader,
|
|
88
|
+
readU16Be,
|
|
89
|
+
readU24Be,
|
|
90
|
+
readU32Be,
|
|
91
|
+
readU64Be,
|
|
92
|
+
readU8,
|
|
93
|
+
readAscii,
|
|
94
|
+
} from '../reader';
|
|
95
|
+
import { DEFAULT_TRACK_DISPOSITION, MetadataTags, RichImageData, TrackDisposition } from '../metadata';
|
|
96
|
+
import { AC3_SAMPLE_RATES } from '../../shared/ac3-misc';
|
|
97
|
+
import { Bitstream } from '../../shared/bitstream';
|
|
98
|
+
import { Aes128CbcContext } from '../aes';
|
|
99
|
+
import { Logging } from '../logging';
|
|
100
|
+
|
|
101
|
+
type InternalTrack = {
|
|
102
|
+
id: number;
|
|
103
|
+
demuxer: IsobmffDemuxer;
|
|
104
|
+
trackBacking: InputTrackBacking | null;
|
|
105
|
+
disposition: TrackDisposition;
|
|
106
|
+
timescale: number;
|
|
107
|
+
durationInMovieTimescale: number;
|
|
108
|
+
durationInMediaTimescale: number;
|
|
109
|
+
rotation: Rotation;
|
|
110
|
+
internalCodecId: string | null;
|
|
111
|
+
name: string | null;
|
|
112
|
+
languageCode: string;
|
|
113
|
+
sampleTableByteOffset: number | null; // null when the track's sample table is another file (ominous ik 👀)
|
|
114
|
+
sampleTable: SampleTable | null;
|
|
115
|
+
fragmentLookupTable: FragmentLookupTableEntry[];
|
|
116
|
+
currentFragmentState: FragmentTrackState | null;
|
|
117
|
+
/**
|
|
118
|
+
* List of all encountered fragment offsets alongside their timestamps. This list never gets truncated, but memory
|
|
119
|
+
* consumption should be negligible.
|
|
120
|
+
*/
|
|
121
|
+
fragmentPositionCache: {
|
|
122
|
+
moofOffset: number;
|
|
123
|
+
startTimestamp: number;
|
|
124
|
+
endTimestamp: number;
|
|
125
|
+
}[];
|
|
126
|
+
/** The segment durations of all edit list entries leading up to the main one (from which the offset is taken.) */
|
|
127
|
+
editListPreviousSegmentDurations: number;
|
|
128
|
+
/** The media time offset of the main edit list entry (with media time !== -1) */
|
|
129
|
+
editListOffset: number;
|
|
130
|
+
/** Set when the track's samples are encrypted using a supported scheme (cenc/cens/cbcs), parsed from sinf/tenc. */
|
|
131
|
+
encryptionInfo: TrackEncryptionInfo | null;
|
|
132
|
+
/** For non-fragmented encrypted tracks: parsed saiz+saio from stbl; aux info is fetched lazily on first use. */
|
|
133
|
+
encryptionAuxInfo: SampleEncryptionAuxInfo | null;
|
|
134
|
+
frmaCodecString: string | null;
|
|
135
|
+
} & ({
|
|
136
|
+
info: null;
|
|
137
|
+
} | {
|
|
138
|
+
info: {
|
|
139
|
+
type: 'video';
|
|
140
|
+
width: number;
|
|
141
|
+
height: number;
|
|
142
|
+
squarePixelWidth: number;
|
|
143
|
+
squarePixelHeight: number;
|
|
144
|
+
codec: VideoCodec | null;
|
|
145
|
+
codecDescription: Uint8Array | null;
|
|
146
|
+
colorSpace: VideoColorSpaceInit | null;
|
|
147
|
+
avcType: 1 | 3 | null;
|
|
148
|
+
avcCodecInfo: AvcDecoderConfigurationRecord | null;
|
|
149
|
+
hevcCodecInfo: HevcDecoderConfigurationRecord | null;
|
|
150
|
+
vp9CodecInfo: Vp9CodecInfo | null;
|
|
151
|
+
av1CodecInfo: Av1CodecInfo | null;
|
|
152
|
+
proresFormat: ProresFourCc | null;
|
|
153
|
+
};
|
|
154
|
+
} | {
|
|
155
|
+
info: {
|
|
156
|
+
type: 'audio';
|
|
157
|
+
numberOfChannels: number;
|
|
158
|
+
sampleRate: number;
|
|
159
|
+
codec: AudioCodec | null;
|
|
160
|
+
codecDescription: Uint8Array | null;
|
|
161
|
+
aacCodecInfo: AacCodecInfo | null;
|
|
162
|
+
pcmLittleEndian: boolean;
|
|
163
|
+
pcmSampleSize: number | null;
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
type InternalVideoTrack = InternalTrack & { info: { type: 'video' } };
|
|
168
|
+
type InternalAudioTrack = InternalTrack & { info: { type: 'audio' } };
|
|
169
|
+
|
|
170
|
+
type SampleTable = {
|
|
171
|
+
sampleTimingEntries: SampleTimingEntry[];
|
|
172
|
+
sampleCompositionTimeOffsets: SampleCompositionTimeOffsetEntry[];
|
|
173
|
+
sampleSizes: number[];
|
|
174
|
+
keySampleIndices: number[] | null; // Samples that are keyframes
|
|
175
|
+
chunkOffsets: number[];
|
|
176
|
+
sampleToChunk: SampleToChunkEntry[];
|
|
177
|
+
presentationTimestamps: {
|
|
178
|
+
presentationTimestamp: number;
|
|
179
|
+
sampleIndex: number;
|
|
180
|
+
}[] | null;
|
|
181
|
+
/**
|
|
182
|
+
* Provides a fast map from sample index to index in the sorted presentation timestamps array - so, a fast map from
|
|
183
|
+
* decode order to presentation order.
|
|
184
|
+
*/
|
|
185
|
+
presentationTimestampIndexMap: number[] | null;
|
|
186
|
+
};
|
|
187
|
+
type SampleTimingEntry = {
|
|
188
|
+
startIndex: number;
|
|
189
|
+
startDecodeTimestamp: number;
|
|
190
|
+
count: number;
|
|
191
|
+
delta: number;
|
|
192
|
+
};
|
|
193
|
+
type SampleCompositionTimeOffsetEntry = {
|
|
194
|
+
startIndex: number;
|
|
195
|
+
count: number;
|
|
196
|
+
offset: number;
|
|
197
|
+
};
|
|
198
|
+
type SampleToChunkEntry = {
|
|
199
|
+
startSampleIndex: number;
|
|
200
|
+
startChunkIndex: number;
|
|
201
|
+
samplesPerChunk: number;
|
|
202
|
+
sampleDescriptionIndex: number;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
type FragmentTrackDefaults = {
|
|
206
|
+
trackId: number;
|
|
207
|
+
defaultSampleDescriptionIndex: number;
|
|
208
|
+
defaultSampleDuration: number;
|
|
209
|
+
defaultSampleSize: number;
|
|
210
|
+
defaultSampleFlags: number;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
type FragmentLookupTableEntry = {
|
|
214
|
+
timestamp: number;
|
|
215
|
+
moofOffset: number;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
type FragmentTrackState = {
|
|
219
|
+
baseDataOffset: number;
|
|
220
|
+
sampleDescriptionIndex: number | null;
|
|
221
|
+
defaultSampleDuration: number | null;
|
|
222
|
+
defaultSampleSize: number | null;
|
|
223
|
+
defaultSampleFlags: number | null;
|
|
224
|
+
startTimestamp: number | null;
|
|
225
|
+
encryptionAuxInfo: SampleEncryptionAuxInfo | null;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
type FragmentTrackData = {
|
|
229
|
+
track: InternalTrack;
|
|
230
|
+
|
|
231
|
+
// Kept as state for the presence of multiple trun boxes
|
|
232
|
+
currentTimestamp: number;
|
|
233
|
+
currentOffset: number;
|
|
234
|
+
|
|
235
|
+
startTimestamp: number;
|
|
236
|
+
endTimestamp: number;
|
|
237
|
+
firstKeyFrameTimestamp: number | null;
|
|
238
|
+
samples: FragmentTrackSample[];
|
|
239
|
+
presentationTimestamps: {
|
|
240
|
+
presentationTimestamp: number;
|
|
241
|
+
sampleIndex: number;
|
|
242
|
+
}[];
|
|
243
|
+
startTimestampIsFinal: boolean;
|
|
244
|
+
encryptionAuxInfo: SampleEncryptionAuxInfo | null;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
type FragmentTrackSample = {
|
|
248
|
+
presentationTimestamp: number;
|
|
249
|
+
duration: number;
|
|
250
|
+
byteOffset: number;
|
|
251
|
+
byteSize: number;
|
|
252
|
+
isKeyFrame: boolean;
|
|
253
|
+
encryption: SampleEncryptionInfo | null;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
type Fragment = {
|
|
257
|
+
moofOffset: number;
|
|
258
|
+
moofSize: number;
|
|
259
|
+
implicitBaseDataOffset: number;
|
|
260
|
+
trackData: Map<InternalTrack['id'], FragmentTrackData>;
|
|
261
|
+
psshBoxes: PsshBox[];
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
type TrackEncryptionInfo = {
|
|
265
|
+
scheme: 'cenc' | 'cens' | 'cbcs';
|
|
266
|
+
defaultKid: string | null;
|
|
267
|
+
defaultIsProtected: boolean | null;
|
|
268
|
+
defaultPerSampleIvSize: number | null;
|
|
269
|
+
defaultConstantIv: Uint8Array | null;
|
|
270
|
+
defaultCryptByteBlock: number | null;
|
|
271
|
+
defaultSkipByteBlock: number | null;
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
type SampleEncryptionInfo = {
|
|
275
|
+
iv: Uint8Array;
|
|
276
|
+
subsamples: {
|
|
277
|
+
clearLen: number;
|
|
278
|
+
protectedLen: number;
|
|
279
|
+
}[] | null;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Holds parsed saiz+saio state. The encryption info itself lives at a file offset and is fetched lazily.
|
|
284
|
+
* For fragmented files this state is per-traf; for non-fragmented files it's per-track (on stbl).
|
|
285
|
+
*/
|
|
286
|
+
type SampleEncryptionAuxInfo = {
|
|
287
|
+
defaultSampleInfoSize: number;
|
|
288
|
+
sampleSizes: Uint8Array | null;
|
|
289
|
+
sampleCount: number;
|
|
290
|
+
offset: number | null; // Absolute file offset of the first sample's aux info
|
|
291
|
+
resolved: SampleEncryptionInfo[] | null;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
export class IsobmffDemuxer extends Demuxer {
|
|
295
|
+
reader: Reader;
|
|
296
|
+
moovSlice: FileSlice | null = null;
|
|
297
|
+
|
|
298
|
+
currentTrack: InternalTrack | null = null;
|
|
299
|
+
tracks: InternalTrack[] = [];
|
|
300
|
+
metadataPromise: Promise<void> | null = null;
|
|
301
|
+
movieTimescale = -1;
|
|
302
|
+
movieDurationInTimescale = -1;
|
|
303
|
+
isQuickTime = false;
|
|
304
|
+
metadataTags: MetadataTags = {};
|
|
305
|
+
currentMetadataKeys: Map<number, string> | null = null;
|
|
306
|
+
|
|
307
|
+
isFragmented = false;
|
|
308
|
+
fragmentTrackDefaults: FragmentTrackDefaults[] = [];
|
|
309
|
+
psshBoxes: PsshBox[] = [];
|
|
310
|
+
currentFragment: Fragment | null = null;
|
|
311
|
+
/**
|
|
312
|
+
* Caches the last fragment that was read. Based on the assumption that there will be multiple reads to the
|
|
313
|
+
* same fragment in quick succession.
|
|
314
|
+
*/
|
|
315
|
+
lastReadFragment: Fragment | null = null;
|
|
316
|
+
|
|
317
|
+
decryptionKeyCache = new Map<string, Promise<Uint8Array>>();
|
|
318
|
+
|
|
319
|
+
constructor(input: Input) {
|
|
320
|
+
super(input);
|
|
321
|
+
|
|
322
|
+
this.reader = input._reader;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
override async getTrackBackings() {
|
|
326
|
+
await this.readMetadata();
|
|
327
|
+
return this.tracks.map(track => track.trackBacking!);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
override async getMimeType() {
|
|
331
|
+
await this.readMetadata();
|
|
332
|
+
|
|
333
|
+
const backings = await this.getTrackBackings();
|
|
334
|
+
const codecStrings = await Promise.all(backings.map(
|
|
335
|
+
x => x.getDecoderConfig().then(c => c?.codec ?? null),
|
|
336
|
+
));
|
|
337
|
+
|
|
338
|
+
return buildIsobmffMimeType({
|
|
339
|
+
isQuickTime: this.isQuickTime,
|
|
340
|
+
hasVideo: this.tracks.some(x => x.info?.type === 'video'),
|
|
341
|
+
hasAudio: this.tracks.some(x => x.info?.type === 'audio'),
|
|
342
|
+
codecStrings: codecStrings.filter(Boolean) as string[],
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async getMetadataTags() {
|
|
347
|
+
await this.readMetadata();
|
|
348
|
+
return this.metadataTags;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
readMetadata() {
|
|
352
|
+
return this.metadataPromise ??= (async () => {
|
|
353
|
+
let currentPos = 0;
|
|
354
|
+
let lookForMfraBox = false;
|
|
355
|
+
|
|
356
|
+
while (true) {
|
|
357
|
+
let slice = this.reader.requestSliceRange(currentPos, MIN_BOX_HEADER_SIZE, MAX_BOX_HEADER_SIZE);
|
|
358
|
+
if (slice instanceof Promise) slice = await slice;
|
|
359
|
+
if (!slice) break;
|
|
360
|
+
|
|
361
|
+
const startPos = currentPos;
|
|
362
|
+
const boxInfo = readBoxHeader(slice);
|
|
363
|
+
if (!boxInfo) {
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (boxInfo.name === 'ftyp' || boxInfo.name === 'styp') {
|
|
368
|
+
const majorBrand = readAscii(slice, 4);
|
|
369
|
+
this.isQuickTime = majorBrand === 'qt ';
|
|
370
|
+
} else if (boxInfo.name === 'moov') {
|
|
371
|
+
// Found moov, load it
|
|
372
|
+
|
|
373
|
+
let moovSlice = this.reader.requestSlice(slice.filePos, boxInfo.contentSize);
|
|
374
|
+
if (moovSlice instanceof Promise) moovSlice = await moovSlice;
|
|
375
|
+
if (!moovSlice) break;
|
|
376
|
+
|
|
377
|
+
this.moovSlice = moovSlice;
|
|
378
|
+
this.readContiguousBoxes(this.moovSlice);
|
|
379
|
+
|
|
380
|
+
for (const track of this.tracks) {
|
|
381
|
+
// Modify the edit list offset based on the previous segment durations. They are in different
|
|
382
|
+
// timescales, so we first convert to seconds and then into the track timescale.
|
|
383
|
+
const previousSegmentDurationsInSeconds
|
|
384
|
+
= track.editListPreviousSegmentDurations / this.movieTimescale;
|
|
385
|
+
track.editListOffset -= Math.round(previousSegmentDurationsInSeconds * track.timescale);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
lookForMfraBox = this.isFragmented
|
|
389
|
+
&& this.reader.fileSize !== null
|
|
390
|
+
&& this.reader.fileSize > startPos + boxInfo.totalSize; // There's more after the moov box
|
|
391
|
+
|
|
392
|
+
break;
|
|
393
|
+
} else if (boxInfo.name === 'moof') {
|
|
394
|
+
if (!this.input._initInput) {
|
|
395
|
+
throw new Error(
|
|
396
|
+
'"moof" box encountered with no "moov" box present; this file is likely a Segment as'
|
|
397
|
+
+ ' described in ISO/IEC 14496-12 Section 8.16. A separate init file that contains a "moov"'
|
|
398
|
+
+ ' box is required to read this file, please provide it using InputOptions.initInput.',
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const initDemuxer = (await this.input._initInput._getDemuxer()) as IsobmffDemuxer;
|
|
403
|
+
if (initDemuxer.constructor !== IsobmffDemuxer) {
|
|
404
|
+
throw new Error('Init input must match the input\'s format.');
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
await initDemuxer.readMetadata();
|
|
408
|
+
|
|
409
|
+
this.movieTimescale = initDemuxer.movieTimescale;
|
|
410
|
+
this.movieDurationInTimescale = initDemuxer.movieDurationInTimescale;
|
|
411
|
+
this.metadataTags = initDemuxer.metadataTags;
|
|
412
|
+
this.isFragmented = true;
|
|
413
|
+
this.fragmentTrackDefaults = initDemuxer.fragmentTrackDefaults;
|
|
414
|
+
this.psshBoxes = initDemuxer.psshBoxes;
|
|
415
|
+
|
|
416
|
+
// Create tracks from the init input's tracks
|
|
417
|
+
for (const foreignTrack of initDemuxer.tracks) {
|
|
418
|
+
const track: InternalTrack = {
|
|
419
|
+
id: foreignTrack.id,
|
|
420
|
+
demuxer: this,
|
|
421
|
+
trackBacking: null,
|
|
422
|
+
disposition: foreignTrack.disposition,
|
|
423
|
+
timescale: foreignTrack.timescale,
|
|
424
|
+
durationInMediaTimescale: foreignTrack.durationInMediaTimescale,
|
|
425
|
+
durationInMovieTimescale: foreignTrack.durationInMovieTimescale,
|
|
426
|
+
rotation: foreignTrack.rotation,
|
|
427
|
+
internalCodecId: foreignTrack.internalCodecId,
|
|
428
|
+
name: foreignTrack.name,
|
|
429
|
+
languageCode: foreignTrack.languageCode,
|
|
430
|
+
sampleTableByteOffset: null,
|
|
431
|
+
sampleTable: null,
|
|
432
|
+
fragmentLookupTable: [],
|
|
433
|
+
currentFragmentState: null,
|
|
434
|
+
fragmentPositionCache: [],
|
|
435
|
+
editListPreviousSegmentDurations: foreignTrack.editListPreviousSegmentDurations,
|
|
436
|
+
editListOffset: foreignTrack.editListOffset,
|
|
437
|
+
encryptionInfo: foreignTrack.encryptionInfo,
|
|
438
|
+
encryptionAuxInfo: null,
|
|
439
|
+
frmaCodecString: null,
|
|
440
|
+
info: foreignTrack.info,
|
|
441
|
+
};
|
|
442
|
+
|
|
443
|
+
if (foreignTrack.trackBacking) {
|
|
444
|
+
assert(track.info);
|
|
445
|
+
|
|
446
|
+
if (track.info.type === 'video' && track.info.width !== -1) {
|
|
447
|
+
const videoTrack = track as InternalVideoTrack;
|
|
448
|
+
track.trackBacking = new IsobmffVideoTrackBacking(videoTrack);
|
|
449
|
+
this.tracks.push(track);
|
|
450
|
+
} else if (track.info.type === 'audio' && track.info.numberOfChannels !== -1) {
|
|
451
|
+
const audioTrack = track as InternalAudioTrack;
|
|
452
|
+
track.trackBacking = new IsobmffAudioTrackBacking(audioTrack);
|
|
453
|
+
this.tracks.push(track);
|
|
454
|
+
}
|
|
455
|
+
} else {
|
|
456
|
+
// The track didn't have enough info to warrant a backing
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
lookForMfraBox = false; // No point in doing it for segment files
|
|
461
|
+
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
currentPos = startPos + boxInfo.totalSize;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (lookForMfraBox) {
|
|
469
|
+
assert(this.reader.fileSize !== null);
|
|
470
|
+
|
|
471
|
+
// The last 4 bytes may contain the size of the mfra box at the end of the file
|
|
472
|
+
let lastWordSlice = this.reader.requestSlice(this.reader.fileSize - 4, 4);
|
|
473
|
+
if (lastWordSlice instanceof Promise) lastWordSlice = await lastWordSlice;
|
|
474
|
+
assert(lastWordSlice);
|
|
475
|
+
|
|
476
|
+
const lastWord = readU32Be(lastWordSlice);
|
|
477
|
+
const potentialMfraPos = this.reader.fileSize - lastWord;
|
|
478
|
+
|
|
479
|
+
if (potentialMfraPos >= 0 && potentialMfraPos <= this.reader.fileSize - MAX_BOX_HEADER_SIZE) {
|
|
480
|
+
let mfraHeaderSlice = this.reader.requestSliceRange(
|
|
481
|
+
potentialMfraPos,
|
|
482
|
+
MIN_BOX_HEADER_SIZE,
|
|
483
|
+
MAX_BOX_HEADER_SIZE,
|
|
484
|
+
);
|
|
485
|
+
if (mfraHeaderSlice instanceof Promise) mfraHeaderSlice = await mfraHeaderSlice;
|
|
486
|
+
|
|
487
|
+
if (mfraHeaderSlice) {
|
|
488
|
+
const boxInfo = readBoxHeader(mfraHeaderSlice);
|
|
489
|
+
|
|
490
|
+
if (boxInfo && boxInfo.name === 'mfra') {
|
|
491
|
+
// We found the mfra box, allowing for much better random access. Let's parse it.
|
|
492
|
+
let mfraSlice = this.reader.requestSlice(mfraHeaderSlice.filePos, boxInfo.contentSize);
|
|
493
|
+
if (mfraSlice instanceof Promise) mfraSlice = await mfraSlice;
|
|
494
|
+
|
|
495
|
+
if (mfraSlice) {
|
|
496
|
+
this.readContiguousBoxes(mfraSlice);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
})();
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
getSampleTableForTrack(internalTrack: InternalTrack) {
|
|
506
|
+
if (internalTrack.sampleTable) {
|
|
507
|
+
return internalTrack.sampleTable;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const sampleTable: SampleTable = {
|
|
511
|
+
sampleTimingEntries: [],
|
|
512
|
+
sampleCompositionTimeOffsets: [],
|
|
513
|
+
sampleSizes: [],
|
|
514
|
+
keySampleIndices: null,
|
|
515
|
+
chunkOffsets: [],
|
|
516
|
+
sampleToChunk: [],
|
|
517
|
+
presentationTimestamps: null,
|
|
518
|
+
presentationTimestampIndexMap: null,
|
|
519
|
+
};
|
|
520
|
+
internalTrack.sampleTable = sampleTable;
|
|
521
|
+
|
|
522
|
+
if (internalTrack.sampleTableByteOffset === null) {
|
|
523
|
+
// There's no sample table to read, it's in another file (happens with segments)
|
|
524
|
+
return sampleTable;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
assert(this.moovSlice);
|
|
528
|
+
|
|
529
|
+
const stblContainerSlice = this.moovSlice.slice(internalTrack.sampleTableByteOffset);
|
|
530
|
+
|
|
531
|
+
this.currentTrack = internalTrack;
|
|
532
|
+
this.traverseBox(stblContainerSlice);
|
|
533
|
+
this.currentTrack = null;
|
|
534
|
+
|
|
535
|
+
const isPcmCodec = internalTrack.info?.type === 'audio'
|
|
536
|
+
&& internalTrack.info.codec
|
|
537
|
+
&& (PCM_AUDIO_CODECS as readonly string[]).includes(internalTrack.info.codec);
|
|
538
|
+
|
|
539
|
+
if (isPcmCodec && sampleTable.sampleCompositionTimeOffsets.length === 0) {
|
|
540
|
+
// If the audio has PCM samples, the way the samples are defined in the sample table is somewhat
|
|
541
|
+
// suboptimal: Each individual audio sample is its own sample, meaning we can have 48000 samples per second.
|
|
542
|
+
// Because we treat each sample as its own atomic unit that can be decoded, this would lead to a huge
|
|
543
|
+
// amount of very short samples for PCM audio. So instead, we make a transformation: If the audio is in PCM,
|
|
544
|
+
// we say that each chunk (that normally holds many samples) now is one big sample. We can this because
|
|
545
|
+
// the samples in the chunk are contiguous and the format is PCM, so the entire chunk as one thing still
|
|
546
|
+
// encodes valid audio information.
|
|
547
|
+
|
|
548
|
+
assert(internalTrack.info?.type === 'audio');
|
|
549
|
+
const pcmInfo = parsePcmCodec(internalTrack.info.codec as PcmAudioCodec);
|
|
550
|
+
|
|
551
|
+
const newSampleTimingEntries: SampleTimingEntry[] = [];
|
|
552
|
+
const newSampleSizes: number[] = [];
|
|
553
|
+
|
|
554
|
+
for (let i = 0; i < sampleTable.sampleToChunk.length; i++) {
|
|
555
|
+
const chunkEntry = sampleTable.sampleToChunk[i]!;
|
|
556
|
+
const nextEntry = sampleTable.sampleToChunk[i + 1];
|
|
557
|
+
const chunkCount = (nextEntry ? nextEntry.startChunkIndex : sampleTable.chunkOffsets.length)
|
|
558
|
+
- chunkEntry.startChunkIndex;
|
|
559
|
+
|
|
560
|
+
for (let j = 0; j < chunkCount; j++) {
|
|
561
|
+
const startSampleIndex = chunkEntry.startSampleIndex + j * chunkEntry.samplesPerChunk;
|
|
562
|
+
const endSampleIndex = startSampleIndex + chunkEntry.samplesPerChunk; // Exclusive, outside of chunk
|
|
563
|
+
|
|
564
|
+
const startTimingEntryIndex = binarySearchLessOrEqual(
|
|
565
|
+
sampleTable.sampleTimingEntries,
|
|
566
|
+
startSampleIndex,
|
|
567
|
+
x => x.startIndex,
|
|
568
|
+
);
|
|
569
|
+
const startTimingEntry = sampleTable.sampleTimingEntries[startTimingEntryIndex]!;
|
|
570
|
+
const endTimingEntryIndex = binarySearchLessOrEqual(
|
|
571
|
+
sampleTable.sampleTimingEntries,
|
|
572
|
+
endSampleIndex,
|
|
573
|
+
x => x.startIndex,
|
|
574
|
+
);
|
|
575
|
+
const endTimingEntry = sampleTable.sampleTimingEntries[endTimingEntryIndex]!;
|
|
576
|
+
|
|
577
|
+
const firstSampleTimestamp = startTimingEntry.startDecodeTimestamp
|
|
578
|
+
+ (startSampleIndex - startTimingEntry.startIndex) * startTimingEntry.delta;
|
|
579
|
+
const lastSampleTimestamp = endTimingEntry.startDecodeTimestamp
|
|
580
|
+
+ (endSampleIndex - endTimingEntry.startIndex) * endTimingEntry.delta;
|
|
581
|
+
const delta = lastSampleTimestamp - firstSampleTimestamp;
|
|
582
|
+
|
|
583
|
+
const lastSampleTimingEntry = last(newSampleTimingEntries);
|
|
584
|
+
if (lastSampleTimingEntry && lastSampleTimingEntry.delta === delta) {
|
|
585
|
+
lastSampleTimingEntry.count++;
|
|
586
|
+
} else {
|
|
587
|
+
// One sample for the entire chunk
|
|
588
|
+
newSampleTimingEntries.push({
|
|
589
|
+
startIndex: chunkEntry.startChunkIndex + j,
|
|
590
|
+
startDecodeTimestamp: firstSampleTimestamp,
|
|
591
|
+
count: 1,
|
|
592
|
+
delta,
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// Instead of determining the chunk's size by looping over the samples sizes in the sample table, we
|
|
597
|
+
// can directly compute it as we know how many PCM frames are in this chunk, and the size of each
|
|
598
|
+
// PCM frame. This also improves compatibility with some files which fail to write proper sample
|
|
599
|
+
// size values into their sample tables in the PCM case.
|
|
600
|
+
const chunkSize = chunkEntry.samplesPerChunk
|
|
601
|
+
* pcmInfo.sampleSize
|
|
602
|
+
* internalTrack.info.numberOfChannels;
|
|
603
|
+
|
|
604
|
+
newSampleSizes.push(chunkSize);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
chunkEntry.startSampleIndex = chunkEntry.startChunkIndex;
|
|
608
|
+
chunkEntry.samplesPerChunk = 1;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
sampleTable.sampleTimingEntries = newSampleTimingEntries;
|
|
612
|
+
sampleTable.sampleSizes = newSampleSizes;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (sampleTable.sampleCompositionTimeOffsets.length > 0) {
|
|
616
|
+
// If composition time offsets are defined, we must build a list of all presentation timestamps and then
|
|
617
|
+
// sort them
|
|
618
|
+
sampleTable.presentationTimestamps = [];
|
|
619
|
+
|
|
620
|
+
for (const entry of sampleTable.sampleTimingEntries) {
|
|
621
|
+
for (let i = 0; i < entry.count; i++) {
|
|
622
|
+
sampleTable.presentationTimestamps.push({
|
|
623
|
+
presentationTimestamp: entry.startDecodeTimestamp + i * entry.delta,
|
|
624
|
+
sampleIndex: entry.startIndex + i,
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
for (const entry of sampleTable.sampleCompositionTimeOffsets) {
|
|
630
|
+
for (let i = 0; i < entry.count; i++) {
|
|
631
|
+
const sampleIndex = entry.startIndex + i;
|
|
632
|
+
const sample = sampleTable.presentationTimestamps[sampleIndex];
|
|
633
|
+
if (!sample) {
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
sample.presentationTimestamp += entry.offset;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
sampleTable.presentationTimestamps.sort((a, b) => a.presentationTimestamp - b.presentationTimestamp);
|
|
642
|
+
|
|
643
|
+
sampleTable.presentationTimestampIndexMap = Array(sampleTable.presentationTimestamps.length).fill(-1);
|
|
644
|
+
for (let i = 0; i < sampleTable.presentationTimestamps.length; i++) {
|
|
645
|
+
sampleTable.presentationTimestampIndexMap[sampleTable.presentationTimestamps[i]!.sampleIndex] = i;
|
|
646
|
+
}
|
|
647
|
+
} else {
|
|
648
|
+
// If they're not defined, we can simply use the decode timestamps as presentation timestamps
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return sampleTable;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
async readFragment(startPos: number): Promise<Fragment> {
|
|
655
|
+
if (this.lastReadFragment?.moofOffset === startPos) {
|
|
656
|
+
return this.lastReadFragment;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
let headerSlice = this.reader.requestSliceRange(startPos, MIN_BOX_HEADER_SIZE, MAX_BOX_HEADER_SIZE);
|
|
660
|
+
if (headerSlice instanceof Promise) headerSlice = await headerSlice;
|
|
661
|
+
assert(headerSlice);
|
|
662
|
+
|
|
663
|
+
const moofBoxInfo = readBoxHeader(headerSlice);
|
|
664
|
+
assert(moofBoxInfo?.name === 'moof');
|
|
665
|
+
|
|
666
|
+
let entireSlice = this.reader.requestSlice(startPos, moofBoxInfo.totalSize);
|
|
667
|
+
if (entireSlice instanceof Promise) entireSlice = await entireSlice;
|
|
668
|
+
assert(entireSlice);
|
|
669
|
+
|
|
670
|
+
this.traverseBox(entireSlice);
|
|
671
|
+
|
|
672
|
+
const fragment = this.lastReadFragment;
|
|
673
|
+
assert(fragment && fragment.moofOffset === startPos);
|
|
674
|
+
|
|
675
|
+
for (const [, trackData] of fragment.trackData) {
|
|
676
|
+
const track = trackData.track;
|
|
677
|
+
const { fragmentPositionCache } = track;
|
|
678
|
+
|
|
679
|
+
if (!trackData.startTimestampIsFinal) {
|
|
680
|
+
// It may be that some tracks don't define the base decode time, i.e. when the fragment begins. This
|
|
681
|
+
// we'll need to figure out the start timestamp another way. We'll compute the timestamp by accessing
|
|
682
|
+
// the lookup entries and fragment cache, which works out nicely with the lookup algorithm: If these
|
|
683
|
+
// exist, then the lookup will automatically start at the furthest possible point. If they don't, the
|
|
684
|
+
// lookup starts sequentially from the start, incrementally summing up all fragment durations. It's sort
|
|
685
|
+
// of implicit, but it ends up working nicely.
|
|
686
|
+
|
|
687
|
+
const lookupEntry = track.fragmentLookupTable.find(x => x.moofOffset === fragment.moofOffset);
|
|
688
|
+
if (lookupEntry) {
|
|
689
|
+
// There's a lookup entry, let's use its timestamp
|
|
690
|
+
offsetFragmentTrackDataByTimestamp(trackData, lookupEntry.timestamp);
|
|
691
|
+
} else {
|
|
692
|
+
const lastCacheIndex = binarySearchLessOrEqual(
|
|
693
|
+
fragmentPositionCache,
|
|
694
|
+
fragment.moofOffset - 1,
|
|
695
|
+
x => x.moofOffset,
|
|
696
|
+
);
|
|
697
|
+
if (lastCacheIndex !== -1) {
|
|
698
|
+
// Let's use the timestamp of the previous fragment in the cache
|
|
699
|
+
const lastCache = fragmentPositionCache[lastCacheIndex]!;
|
|
700
|
+
offsetFragmentTrackDataByTimestamp(trackData, lastCache.endTimestamp);
|
|
701
|
+
} else {
|
|
702
|
+
// We're the first fragment I guess, "offset by 0"
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
trackData.startTimestampIsFinal = true;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// Let's remember that a fragment with a given timestamp is here, speeding up future lookups if no
|
|
710
|
+
// lookup table exists
|
|
711
|
+
const insertionIndex = binarySearchLessOrEqual(
|
|
712
|
+
fragmentPositionCache,
|
|
713
|
+
trackData.startTimestamp,
|
|
714
|
+
x => x.startTimestamp,
|
|
715
|
+
);
|
|
716
|
+
if (
|
|
717
|
+
insertionIndex === -1
|
|
718
|
+
|| fragmentPositionCache[insertionIndex]!.moofOffset !== fragment.moofOffset
|
|
719
|
+
) {
|
|
720
|
+
fragmentPositionCache.splice(insertionIndex + 1, 0, {
|
|
721
|
+
moofOffset: fragment.moofOffset,
|
|
722
|
+
startTimestamp: trackData.startTimestamp,
|
|
723
|
+
endTimestamp: trackData.endTimestamp,
|
|
724
|
+
});
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// If senc wasn't parsed but saiz+saio were, fetch the aux info now and stamp each sample with it
|
|
728
|
+
if (trackData.encryptionAuxInfo && track.encryptionInfo) {
|
|
729
|
+
const entries = await resolveEncryptionAuxInfo(
|
|
730
|
+
this.reader,
|
|
731
|
+
track.encryptionInfo,
|
|
732
|
+
trackData.encryptionAuxInfo,
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
for (let i = 0; i < Math.min(trackData.samples.length, entries.length); i++) {
|
|
736
|
+
const entry = entries[i]!;
|
|
737
|
+
trackData.samples[i]!.encryption = entry;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return fragment;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
readContiguousBoxes(slice: FileSlice) {
|
|
746
|
+
const startIndex = slice.filePos;
|
|
747
|
+
|
|
748
|
+
while (slice.filePos - startIndex <= slice.length - MIN_BOX_HEADER_SIZE) {
|
|
749
|
+
const foundBox = this.traverseBox(slice);
|
|
750
|
+
|
|
751
|
+
if (!foundBox) {
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// eslint-disable-next-line @stylistic/generator-star-spacing
|
|
758
|
+
*iterateContiguousBoxes(slice: FileSlice) {
|
|
759
|
+
const startIndex = slice.filePos;
|
|
760
|
+
|
|
761
|
+
while (slice.filePos - startIndex <= slice.length - MIN_BOX_HEADER_SIZE) {
|
|
762
|
+
const startPos = slice.filePos;
|
|
763
|
+
const boxInfo = readBoxHeader(slice);
|
|
764
|
+
if (!boxInfo) {
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
yield { boxInfo, slice };
|
|
769
|
+
slice.filePos = startPos + boxInfo.totalSize;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
traverseBox(slice: FileSlice): boolean {
|
|
774
|
+
const startPos = slice.filePos;
|
|
775
|
+
const boxInfo = readBoxHeader(slice);
|
|
776
|
+
if (!boxInfo) {
|
|
777
|
+
return false;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const contentStartPos = slice.filePos;
|
|
781
|
+
const boxEndPos = startPos + boxInfo.totalSize;
|
|
782
|
+
|
|
783
|
+
switch (boxInfo.name) {
|
|
784
|
+
case 'mdia':
|
|
785
|
+
case 'minf':
|
|
786
|
+
case 'dinf':
|
|
787
|
+
case 'mfra':
|
|
788
|
+
case 'edts':
|
|
789
|
+
case 'sinf':
|
|
790
|
+
case 'schi': {
|
|
791
|
+
this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
|
|
792
|
+
}; break;
|
|
793
|
+
|
|
794
|
+
case 'mvhd': {
|
|
795
|
+
const version = readU8(slice);
|
|
796
|
+
slice.skip(3); // Flags
|
|
797
|
+
|
|
798
|
+
if (version === 1) {
|
|
799
|
+
slice.skip(8 + 8);
|
|
800
|
+
this.movieTimescale = readU32Be(slice);
|
|
801
|
+
this.movieDurationInTimescale = readU64Be(slice);
|
|
802
|
+
} else {
|
|
803
|
+
slice.skip(4 + 4);
|
|
804
|
+
this.movieTimescale = readU32Be(slice);
|
|
805
|
+
this.movieDurationInTimescale = readU32Be(slice);
|
|
806
|
+
}
|
|
807
|
+
}; break;
|
|
808
|
+
|
|
809
|
+
case 'trak': {
|
|
810
|
+
const track = {
|
|
811
|
+
id: -1,
|
|
812
|
+
demuxer: this,
|
|
813
|
+
trackBacking: null,
|
|
814
|
+
disposition: {
|
|
815
|
+
...DEFAULT_TRACK_DISPOSITION,
|
|
816
|
+
primary: false,
|
|
817
|
+
},
|
|
818
|
+
info: null,
|
|
819
|
+
timescale: -1,
|
|
820
|
+
durationInMovieTimescale: -1,
|
|
821
|
+
durationInMediaTimescale: -1,
|
|
822
|
+
rotation: 0,
|
|
823
|
+
internalCodecId: null,
|
|
824
|
+
name: null,
|
|
825
|
+
languageCode: UNDETERMINED_LANGUAGE,
|
|
826
|
+
sampleTableByteOffset: -1,
|
|
827
|
+
sampleTable: null,
|
|
828
|
+
fragmentLookupTable: [],
|
|
829
|
+
currentFragmentState: null,
|
|
830
|
+
fragmentPositionCache: [],
|
|
831
|
+
editListPreviousSegmentDurations: 0,
|
|
832
|
+
editListOffset: 0,
|
|
833
|
+
encryptionInfo: null,
|
|
834
|
+
encryptionAuxInfo: null,
|
|
835
|
+
frmaCodecString: null,
|
|
836
|
+
} satisfies InternalTrack as InternalTrack;
|
|
837
|
+
this.currentTrack = track;
|
|
838
|
+
|
|
839
|
+
this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
|
|
840
|
+
|
|
841
|
+
if (track.id !== -1 && track.timescale !== -1 && track.info !== null) {
|
|
842
|
+
if (track.info.type === 'video' && track.info.width !== -1) {
|
|
843
|
+
const videoTrack = track as InternalVideoTrack;
|
|
844
|
+
track.trackBacking = new IsobmffVideoTrackBacking(videoTrack);
|
|
845
|
+
this.tracks.push(track);
|
|
846
|
+
} else if (track.info.type === 'audio' && track.info.numberOfChannels !== -1) {
|
|
847
|
+
const audioTrack = track as InternalAudioTrack;
|
|
848
|
+
track.trackBacking = new IsobmffAudioTrackBacking(audioTrack);
|
|
849
|
+
this.tracks.push(track);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
this.currentTrack = null;
|
|
854
|
+
}; break;
|
|
855
|
+
|
|
856
|
+
case 'tkhd': {
|
|
857
|
+
const track = this.currentTrack;
|
|
858
|
+
if (!track) {
|
|
859
|
+
break;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const version = readU8(slice);
|
|
863
|
+
const flags = readU24Be(slice);
|
|
864
|
+
|
|
865
|
+
// Spec says disabled tracks are to be treated like they don't exist, but in practice, they are treated
|
|
866
|
+
// more like non-default tracks.
|
|
867
|
+
const trackEnabled = !!(flags & 0x1);
|
|
868
|
+
track.disposition.default = trackEnabled;
|
|
869
|
+
|
|
870
|
+
// Skip over creation & modification time to reach the track ID
|
|
871
|
+
if (version === 0) {
|
|
872
|
+
slice.skip(8);
|
|
873
|
+
track.id = readU32Be(slice);
|
|
874
|
+
slice.skip(4);
|
|
875
|
+
track.durationInMovieTimescale = readU32Be(slice);
|
|
876
|
+
} else if (version === 1) {
|
|
877
|
+
slice.skip(16);
|
|
878
|
+
track.id = readU32Be(slice);
|
|
879
|
+
slice.skip(4);
|
|
880
|
+
track.durationInMovieTimescale = readU64Be(slice);
|
|
881
|
+
} else {
|
|
882
|
+
throw new Error(`Incorrect track header version ${version}.`);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
slice.skip(2 * 4 + 2 + 2 + 2 + 2);
|
|
886
|
+
const matrix: TransformationMatrix = [
|
|
887
|
+
readFixed_16_16(slice),
|
|
888
|
+
readFixed_16_16(slice),
|
|
889
|
+
readFixed_2_30(slice),
|
|
890
|
+
readFixed_16_16(slice),
|
|
891
|
+
readFixed_16_16(slice),
|
|
892
|
+
readFixed_2_30(slice),
|
|
893
|
+
readFixed_16_16(slice),
|
|
894
|
+
readFixed_16_16(slice),
|
|
895
|
+
readFixed_2_30(slice),
|
|
896
|
+
];
|
|
897
|
+
|
|
898
|
+
const rotation = normalizeRotation(roundToMultiple(extractRotationFromMatrix(matrix), 90));
|
|
899
|
+
assert(rotation === 0 || rotation === 90 || rotation === 180 || rotation === 270);
|
|
900
|
+
|
|
901
|
+
track.rotation = rotation;
|
|
902
|
+
}; break;
|
|
903
|
+
|
|
904
|
+
case 'elst': {
|
|
905
|
+
const track = this.currentTrack;
|
|
906
|
+
if (!track) {
|
|
907
|
+
break;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
const version = readU8(slice);
|
|
911
|
+
slice.skip(3); // Flags
|
|
912
|
+
|
|
913
|
+
let relevantEntryFound = false;
|
|
914
|
+
let previousSegmentDurations = 0;
|
|
915
|
+
|
|
916
|
+
const entryCount = readU32Be(slice);
|
|
917
|
+
for (let i = 0; i < entryCount; i++) {
|
|
918
|
+
const segmentDuration = version === 1
|
|
919
|
+
? readU64Be(slice)
|
|
920
|
+
: readU32Be(slice);
|
|
921
|
+
const mediaTime = version === 1
|
|
922
|
+
? readI64Be(slice)
|
|
923
|
+
: readI32Be(slice);
|
|
924
|
+
const mediaRate = readFixed_16_16(slice);
|
|
925
|
+
|
|
926
|
+
if (segmentDuration === 0) {
|
|
927
|
+
// Don't care
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
if (relevantEntryFound) {
|
|
932
|
+
Logging._warn(
|
|
933
|
+
'Unsupported edit list: multiple edits are not currently supported. Only using first edit.',
|
|
934
|
+
);
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (mediaTime === -1) {
|
|
939
|
+
previousSegmentDurations += segmentDuration;
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
if (mediaRate !== 1) {
|
|
944
|
+
Logging._warn('Unsupported edit list entry: media rate must be 1.');
|
|
945
|
+
break;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
track.editListPreviousSegmentDurations = previousSegmentDurations;
|
|
949
|
+
track.editListOffset = mediaTime;
|
|
950
|
+
relevantEntryFound = true;
|
|
951
|
+
}
|
|
952
|
+
}; break;
|
|
953
|
+
|
|
954
|
+
case 'mdhd': {
|
|
955
|
+
const track = this.currentTrack;
|
|
956
|
+
if (!track) {
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
const version = readU8(slice);
|
|
961
|
+
slice.skip(3); // Flags
|
|
962
|
+
|
|
963
|
+
if (version === 0) {
|
|
964
|
+
slice.skip(8);
|
|
965
|
+
track.timescale = readU32Be(slice);
|
|
966
|
+
track.durationInMediaTimescale = readU32Be(slice);
|
|
967
|
+
} else if (version === 1) {
|
|
968
|
+
slice.skip(16);
|
|
969
|
+
track.timescale = readU32Be(slice);
|
|
970
|
+
track.durationInMediaTimescale = readU64Be(slice);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
let language = readU16Be(slice);
|
|
974
|
+
|
|
975
|
+
if (language > 0) {
|
|
976
|
+
track.languageCode = '';
|
|
977
|
+
|
|
978
|
+
for (let i = 0; i < 3; i++) {
|
|
979
|
+
track.languageCode = String.fromCharCode(0x60 + (language & 0b11111)) + track.languageCode;
|
|
980
|
+
language >>= 5;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
if (!isIso639Dash2LanguageCode(track.languageCode)) {
|
|
984
|
+
// Sometimes the bytes are garbage
|
|
985
|
+
track.languageCode = UNDETERMINED_LANGUAGE;
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}; break;
|
|
989
|
+
|
|
990
|
+
case 'hdlr': {
|
|
991
|
+
const track = this.currentTrack;
|
|
992
|
+
if (!track) {
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
slice.skip(8); // Version + flags + pre-defined
|
|
997
|
+
const handlerType = readAscii(slice, 4);
|
|
998
|
+
|
|
999
|
+
if (handlerType === 'vide') {
|
|
1000
|
+
track.info = {
|
|
1001
|
+
type: 'video',
|
|
1002
|
+
width: -1,
|
|
1003
|
+
height: -1,
|
|
1004
|
+
squarePixelWidth: -1,
|
|
1005
|
+
squarePixelHeight: -1,
|
|
1006
|
+
codec: null,
|
|
1007
|
+
codecDescription: null,
|
|
1008
|
+
colorSpace: null,
|
|
1009
|
+
avcType: null,
|
|
1010
|
+
avcCodecInfo: null,
|
|
1011
|
+
hevcCodecInfo: null,
|
|
1012
|
+
vp9CodecInfo: null,
|
|
1013
|
+
av1CodecInfo: null,
|
|
1014
|
+
proresFormat: null,
|
|
1015
|
+
};
|
|
1016
|
+
} else if (handlerType === 'soun') {
|
|
1017
|
+
track.info = {
|
|
1018
|
+
type: 'audio',
|
|
1019
|
+
numberOfChannels: -1,
|
|
1020
|
+
sampleRate: -1,
|
|
1021
|
+
codec: null,
|
|
1022
|
+
codecDescription: null,
|
|
1023
|
+
aacCodecInfo: null,
|
|
1024
|
+
pcmLittleEndian: false,
|
|
1025
|
+
pcmSampleSize: null,
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
}; break;
|
|
1029
|
+
|
|
1030
|
+
case 'stbl': {
|
|
1031
|
+
const track = this.currentTrack;
|
|
1032
|
+
if (!track) {
|
|
1033
|
+
break;
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
track.sampleTableByteOffset = startPos;
|
|
1037
|
+
|
|
1038
|
+
this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
|
|
1039
|
+
}; break;
|
|
1040
|
+
|
|
1041
|
+
case 'stsd': {
|
|
1042
|
+
const track = this.currentTrack;
|
|
1043
|
+
if (!track) {
|
|
1044
|
+
break;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
if (track.info === null || track.sampleTable) {
|
|
1048
|
+
break;
|
|
1049
|
+
}
|
|
1050
|
+
|
|
1051
|
+
const stsdVersion = readU8(slice);
|
|
1052
|
+
slice.skip(3); // Flags
|
|
1053
|
+
|
|
1054
|
+
const entries = readU32Be(slice);
|
|
1055
|
+
|
|
1056
|
+
for (let i = 0; i < entries; i++) {
|
|
1057
|
+
const sampleBoxStartPos = slice.filePos;
|
|
1058
|
+
const sampleBoxInfo = readBoxHeader(slice);
|
|
1059
|
+
if (!sampleBoxInfo) {
|
|
1060
|
+
break;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
track.internalCodecId = sampleBoxInfo.name;
|
|
1064
|
+
const lowercaseBoxName = sampleBoxInfo.name.toLowerCase();
|
|
1065
|
+
|
|
1066
|
+
if (track.info.type === 'video') {
|
|
1067
|
+
slice.skip(6 * 1 + 2 + 2 + 2 + 3 * 4);
|
|
1068
|
+
|
|
1069
|
+
track.info.width = readU16Be(slice);
|
|
1070
|
+
track.info.height = readU16Be(slice);
|
|
1071
|
+
track.info.squarePixelWidth = track.info.width;
|
|
1072
|
+
track.info.squarePixelHeight = track.info.height;
|
|
1073
|
+
|
|
1074
|
+
slice.skip(4 + 4 + 4 + 2 + 32 + 2 + 2);
|
|
1075
|
+
|
|
1076
|
+
track.frmaCodecString = null;
|
|
1077
|
+
this.readContiguousBoxes(
|
|
1078
|
+
slice.slice(
|
|
1079
|
+
slice.filePos,
|
|
1080
|
+
(sampleBoxStartPos + sampleBoxInfo.totalSize) - slice.filePos,
|
|
1081
|
+
),
|
|
1082
|
+
);
|
|
1083
|
+
|
|
1084
|
+
const codecName = lowercaseBoxName === 'encv'
|
|
1085
|
+
? track.frmaCodecString
|
|
1086
|
+
: lowercaseBoxName;
|
|
1087
|
+
track.frmaCodecString = null;
|
|
1088
|
+
|
|
1089
|
+
if (codecName === 'avc1' || codecName === 'avc3') {
|
|
1090
|
+
track.info.codec = 'avc';
|
|
1091
|
+
track.info.avcType = codecName === 'avc1' ? 1 : 3;
|
|
1092
|
+
} else if (codecName === 'hvc1' || codecName === 'hev1') {
|
|
1093
|
+
track.info.codec = 'hevc';
|
|
1094
|
+
} else if (codecName === 'vp08') {
|
|
1095
|
+
track.info.codec = 'vp8';
|
|
1096
|
+
} else if (codecName === 'vp09') {
|
|
1097
|
+
track.info.codec = 'vp9';
|
|
1098
|
+
} else if (codecName === 'av01') {
|
|
1099
|
+
track.info.codec = 'av1';
|
|
1100
|
+
} else if ((PRORES_FOURCCS as readonly string[]).includes(lowercaseBoxName)) {
|
|
1101
|
+
track.info.codec = 'prores';
|
|
1102
|
+
track.info.proresFormat = lowercaseBoxName as ProresFourCc;
|
|
1103
|
+
} else if (codecName === null) {
|
|
1104
|
+
Logging._warn(`Unknown encrypted video codec due to missing frma box.`);
|
|
1105
|
+
} else {
|
|
1106
|
+
Logging._warn(`Unsupported video codec (sample entry type '${sampleBoxInfo.name}').`);
|
|
1107
|
+
}
|
|
1108
|
+
} else {
|
|
1109
|
+
slice.skip(6 * 1 + 2);
|
|
1110
|
+
|
|
1111
|
+
const version = readU16Be(slice);
|
|
1112
|
+
slice.skip(3 * 2);
|
|
1113
|
+
|
|
1114
|
+
let channelCount = readU16Be(slice);
|
|
1115
|
+
let sampleSize = readU16Be(slice);
|
|
1116
|
+
|
|
1117
|
+
slice.skip(2 * 2);
|
|
1118
|
+
|
|
1119
|
+
// Can't use fixed16_16 as that's signed
|
|
1120
|
+
let sampleRate = readU32Be(slice) / 0x10000;
|
|
1121
|
+
let lpcmFlags: number | null = null;
|
|
1122
|
+
|
|
1123
|
+
if (stsdVersion === 0 && version > 0) {
|
|
1124
|
+
// Additional QuickTime fields
|
|
1125
|
+
if (version === 1) {
|
|
1126
|
+
slice.skip(4);
|
|
1127
|
+
sampleSize = 8 * readU32Be(slice);
|
|
1128
|
+
slice.skip(2 * 4);
|
|
1129
|
+
} else if (version === 2) {
|
|
1130
|
+
slice.skip(4);
|
|
1131
|
+
sampleRate = readF64Be(slice);
|
|
1132
|
+
channelCount = readU32Be(slice);
|
|
1133
|
+
slice.skip(4); // Always 0x7f000000
|
|
1134
|
+
|
|
1135
|
+
sampleSize = readU32Be(slice);
|
|
1136
|
+
|
|
1137
|
+
lpcmFlags = readU32Be(slice);
|
|
1138
|
+
|
|
1139
|
+
slice.skip(2 * 4);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
track.info.numberOfChannels = channelCount;
|
|
1144
|
+
track.info.sampleRate = sampleRate;
|
|
1145
|
+
|
|
1146
|
+
track.frmaCodecString = null;
|
|
1147
|
+
this.readContiguousBoxes(
|
|
1148
|
+
slice.slice(
|
|
1149
|
+
slice.filePos,
|
|
1150
|
+
(sampleBoxStartPos + sampleBoxInfo.totalSize) - slice.filePos,
|
|
1151
|
+
),
|
|
1152
|
+
);
|
|
1153
|
+
|
|
1154
|
+
const codecName = lowercaseBoxName === 'enca'
|
|
1155
|
+
? track.frmaCodecString
|
|
1156
|
+
: lowercaseBoxName;
|
|
1157
|
+
track.frmaCodecString = null;
|
|
1158
|
+
|
|
1159
|
+
// developer.apple.com/documentation/quicktime-file-format/sound_sample_descriptions/
|
|
1160
|
+
if (codecName === 'mp4a') {
|
|
1161
|
+
// The codec is set by the esds box
|
|
1162
|
+
} else if (codecName === 'opus') {
|
|
1163
|
+
track.info.codec = 'opus';
|
|
1164
|
+
track.info.sampleRate = OPUS_SAMPLE_RATE; // Always the same
|
|
1165
|
+
} else if (codecName === 'flac') {
|
|
1166
|
+
track.info.codec = 'flac';
|
|
1167
|
+
} else if (codecName === 'ulaw') {
|
|
1168
|
+
track.info.codec = 'ulaw';
|
|
1169
|
+
} else if (codecName === 'alaw') {
|
|
1170
|
+
track.info.codec = 'alaw';
|
|
1171
|
+
} else if (codecName === 'ac-3') {
|
|
1172
|
+
track.info.codec = 'ac3';
|
|
1173
|
+
} else if (codecName === 'ec-3') {
|
|
1174
|
+
track.info.codec = 'eac3';
|
|
1175
|
+
} else if (codecName === 'twos') {
|
|
1176
|
+
if (sampleSize === 8) {
|
|
1177
|
+
track.info.codec = 'pcm-s8';
|
|
1178
|
+
} else if (sampleSize === 16) {
|
|
1179
|
+
track.info.codec = track.info.pcmLittleEndian ? 'pcm-s16' : 'pcm-s16be';
|
|
1180
|
+
} else {
|
|
1181
|
+
Logging._warn(`Unsupported sample size ${sampleSize} for codec 'twos'.`);
|
|
1182
|
+
track.info.codec = null;
|
|
1183
|
+
}
|
|
1184
|
+
} else if (codecName === 'sowt') {
|
|
1185
|
+
if (sampleSize === 8) {
|
|
1186
|
+
track.info.codec = 'pcm-s8';
|
|
1187
|
+
} else if (sampleSize === 16) {
|
|
1188
|
+
track.info.codec = 'pcm-s16';
|
|
1189
|
+
} else {
|
|
1190
|
+
Logging._warn(`Unsupported sample size ${sampleSize} for codec 'sowt'.`);
|
|
1191
|
+
track.info.codec = null;
|
|
1192
|
+
}
|
|
1193
|
+
} else if (codecName === 'raw ') {
|
|
1194
|
+
track.info.codec = 'pcm-u8';
|
|
1195
|
+
} else if (codecName === 'in24') {
|
|
1196
|
+
track.info.codec = track.info.pcmLittleEndian ? 'pcm-s24' : 'pcm-s24be';
|
|
1197
|
+
} else if (codecName === 'in32') {
|
|
1198
|
+
track.info.codec = track.info.pcmLittleEndian ? 'pcm-s32' : 'pcm-s32be';
|
|
1199
|
+
} else if (codecName === 'fl32') {
|
|
1200
|
+
track.info.codec = track.info.pcmLittleEndian ? 'pcm-f32' : 'pcm-f32be';
|
|
1201
|
+
} else if (codecName === 'fl64') {
|
|
1202
|
+
track.info.codec = track.info.pcmLittleEndian ? 'pcm-f64' : 'pcm-f64be';
|
|
1203
|
+
} else if (codecName === 'ipcm') {
|
|
1204
|
+
const pcmSampleSize = track.info.pcmSampleSize;
|
|
1205
|
+
|
|
1206
|
+
if (track.info.pcmLittleEndian) {
|
|
1207
|
+
if (pcmSampleSize === 16) {
|
|
1208
|
+
track.info.codec = 'pcm-s16';
|
|
1209
|
+
} else if (pcmSampleSize === 24) {
|
|
1210
|
+
track.info.codec = 'pcm-s24';
|
|
1211
|
+
} else if (pcmSampleSize === 32) {
|
|
1212
|
+
track.info.codec = 'pcm-s32';
|
|
1213
|
+
} else {
|
|
1214
|
+
Logging._warn(`Invalid ipcm sample size ${pcmSampleSize}.`);
|
|
1215
|
+
track.info.codec = null;
|
|
1216
|
+
}
|
|
1217
|
+
} else {
|
|
1218
|
+
if (pcmSampleSize === 16) {
|
|
1219
|
+
track.info.codec = 'pcm-s16be';
|
|
1220
|
+
} else if (pcmSampleSize === 24) {
|
|
1221
|
+
track.info.codec = 'pcm-s24be';
|
|
1222
|
+
} else if (pcmSampleSize === 32) {
|
|
1223
|
+
track.info.codec = 'pcm-s32be';
|
|
1224
|
+
} else {
|
|
1225
|
+
Logging._warn(`Invalid ipcm sample size ${pcmSampleSize}.`);
|
|
1226
|
+
track.info.codec = null;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
} else if (codecName === 'fpcm') {
|
|
1230
|
+
const pcmSampleSize = track.info.pcmSampleSize;
|
|
1231
|
+
|
|
1232
|
+
if (track.info.pcmLittleEndian) {
|
|
1233
|
+
if (pcmSampleSize === 32) {
|
|
1234
|
+
track.info.codec = 'pcm-f32';
|
|
1235
|
+
} else if (pcmSampleSize === 64) {
|
|
1236
|
+
track.info.codec = 'pcm-f64';
|
|
1237
|
+
} else {
|
|
1238
|
+
Logging._warn(`Invalid fpcm sample size ${pcmSampleSize}.`);
|
|
1239
|
+
track.info.codec = null;
|
|
1240
|
+
}
|
|
1241
|
+
} else {
|
|
1242
|
+
if (pcmSampleSize === 32) {
|
|
1243
|
+
track.info.codec = 'pcm-f32be';
|
|
1244
|
+
} else if (pcmSampleSize === 64) {
|
|
1245
|
+
track.info.codec = 'pcm-f64be';
|
|
1246
|
+
} else {
|
|
1247
|
+
Logging._warn(`Invalid fpcm sample size ${pcmSampleSize}.`);
|
|
1248
|
+
track.info.codec = null;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
} else if (codecName === 'lpcm' && lpcmFlags !== null) {
|
|
1252
|
+
const bytesPerSample = (sampleSize + 7) >> 3;
|
|
1253
|
+
const isFloat = Boolean(lpcmFlags & 1);
|
|
1254
|
+
const isBigEndian = Boolean(lpcmFlags & 2);
|
|
1255
|
+
const sFlags = lpcmFlags & 4 ? -1 : 0; // I guess it means "signed flags" or something?
|
|
1256
|
+
|
|
1257
|
+
if (sampleSize > 0 && sampleSize <= 64) {
|
|
1258
|
+
if (isFloat) {
|
|
1259
|
+
if (sampleSize === 32) {
|
|
1260
|
+
track.info.codec = isBigEndian ? 'pcm-f32be' : 'pcm-f32';
|
|
1261
|
+
}
|
|
1262
|
+
} else {
|
|
1263
|
+
if (sFlags & (1 << (bytesPerSample - 1))) {
|
|
1264
|
+
if (bytesPerSample === 1) {
|
|
1265
|
+
track.info.codec = 'pcm-s8';
|
|
1266
|
+
} else if (bytesPerSample === 2) {
|
|
1267
|
+
track.info.codec = isBigEndian ? 'pcm-s16be' : 'pcm-s16';
|
|
1268
|
+
} else if (bytesPerSample === 3) {
|
|
1269
|
+
track.info.codec = isBigEndian ? 'pcm-s24be' : 'pcm-s24';
|
|
1270
|
+
} else if (bytesPerSample === 4) {
|
|
1271
|
+
track.info.codec = isBigEndian ? 'pcm-s32be' : 'pcm-s32';
|
|
1272
|
+
}
|
|
1273
|
+
} else {
|
|
1274
|
+
if (bytesPerSample === 1) {
|
|
1275
|
+
track.info.codec = 'pcm-u8';
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
if (track.info.codec === null) {
|
|
1282
|
+
Logging._warn('Unsupported PCM format.');
|
|
1283
|
+
}
|
|
1284
|
+
} else if (codecName === null) {
|
|
1285
|
+
Logging._warn(`Unknown encrypted audio codec due to missing frma box.`);
|
|
1286
|
+
} else {
|
|
1287
|
+
Logging._warn(`Unsupported audio codec (sample entry type '${sampleBoxInfo.name}').`);
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
slice.filePos = sampleBoxStartPos + sampleBoxInfo.totalSize;
|
|
1292
|
+
}
|
|
1293
|
+
}; break;
|
|
1294
|
+
|
|
1295
|
+
case 'frma': {
|
|
1296
|
+
const track = this.currentTrack;
|
|
1297
|
+
if (!track) {
|
|
1298
|
+
break;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
const format = readAscii(slice, 4);
|
|
1302
|
+
const lowercase = format.toLowerCase();
|
|
1303
|
+
|
|
1304
|
+
// Tells us what codec the encrypted track actually uses
|
|
1305
|
+
track.frmaCodecString = lowercase;
|
|
1306
|
+
}; break;
|
|
1307
|
+
|
|
1308
|
+
case 'schm': {
|
|
1309
|
+
const track = this.currentTrack;
|
|
1310
|
+
if (!track) {
|
|
1311
|
+
break;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
slice.skip(4); // Version + flags
|
|
1315
|
+
|
|
1316
|
+
const schemeType = readAscii(slice, 4);
|
|
1317
|
+
if (schemeType === 'cenc' || schemeType === 'cens' || schemeType === 'cbcs') {
|
|
1318
|
+
track.encryptionInfo = {
|
|
1319
|
+
scheme: schemeType,
|
|
1320
|
+
defaultKid: null,
|
|
1321
|
+
defaultIsProtected: null,
|
|
1322
|
+
defaultPerSampleIvSize: null,
|
|
1323
|
+
defaultConstantIv: null,
|
|
1324
|
+
defaultCryptByteBlock: null,
|
|
1325
|
+
defaultSkipByteBlock: null,
|
|
1326
|
+
};
|
|
1327
|
+
} else {
|
|
1328
|
+
Logging._warn(`Unsupported encryption scheme '${schemeType}'.`);
|
|
1329
|
+
}
|
|
1330
|
+
}; break;
|
|
1331
|
+
|
|
1332
|
+
case 'tenc': {
|
|
1333
|
+
const track = this.currentTrack;
|
|
1334
|
+
if (!track || !track.encryptionInfo) {
|
|
1335
|
+
break;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
const version = readU8(slice);
|
|
1339
|
+
slice.skip(3); // Flags
|
|
1340
|
+
slice.skip(1); // Reserved
|
|
1341
|
+
|
|
1342
|
+
const patternByte = readU8(slice);
|
|
1343
|
+
if (version > 0) {
|
|
1344
|
+
track.encryptionInfo.defaultCryptByteBlock = patternByte >> 4;
|
|
1345
|
+
track.encryptionInfo.defaultSkipByteBlock = patternByte & 0xf;
|
|
1346
|
+
} else {
|
|
1347
|
+
track.encryptionInfo.defaultCryptByteBlock = 0;
|
|
1348
|
+
track.encryptionInfo.defaultSkipByteBlock = 0;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
track.encryptionInfo.defaultIsProtected = readU8(slice) !== 0;
|
|
1352
|
+
track.encryptionInfo.defaultPerSampleIvSize = readU8(slice);
|
|
1353
|
+
track.encryptionInfo.defaultKid = bytesToHexString(readBytes(slice, 16));
|
|
1354
|
+
|
|
1355
|
+
if (track.encryptionInfo.defaultIsProtected && track.encryptionInfo.defaultPerSampleIvSize === 0) {
|
|
1356
|
+
const constantIvSize = readU8(slice);
|
|
1357
|
+
const constantIv = new Uint8Array(16);
|
|
1358
|
+
constantIv.set(readBytes(slice, constantIvSize), 0);
|
|
1359
|
+
track.encryptionInfo.defaultConstantIv = constantIv;
|
|
1360
|
+
}
|
|
1361
|
+
}; break;
|
|
1362
|
+
|
|
1363
|
+
case 'avcC': {
|
|
1364
|
+
const track = this.currentTrack;
|
|
1365
|
+
if (!track) {
|
|
1366
|
+
break;
|
|
1367
|
+
}
|
|
1368
|
+
assert(track.info);
|
|
1369
|
+
|
|
1370
|
+
track.info.codecDescription = readBytes(slice, boxInfo.contentSize);
|
|
1371
|
+
}; break;
|
|
1372
|
+
|
|
1373
|
+
case 'hvcC': {
|
|
1374
|
+
const track = this.currentTrack;
|
|
1375
|
+
if (!track) {
|
|
1376
|
+
break;
|
|
1377
|
+
}
|
|
1378
|
+
assert(track.info);
|
|
1379
|
+
|
|
1380
|
+
track.info.codecDescription = readBytes(slice, boxInfo.contentSize);
|
|
1381
|
+
}; break;
|
|
1382
|
+
|
|
1383
|
+
case 'vpcC': {
|
|
1384
|
+
const track = this.currentTrack;
|
|
1385
|
+
if (!track) {
|
|
1386
|
+
break;
|
|
1387
|
+
}
|
|
1388
|
+
assert(track.info?.type === 'video');
|
|
1389
|
+
|
|
1390
|
+
slice.skip(4); // Version + flags
|
|
1391
|
+
|
|
1392
|
+
const profile = readU8(slice);
|
|
1393
|
+
const level = readU8(slice);
|
|
1394
|
+
const thirdByte = readU8(slice);
|
|
1395
|
+
const bitDepth = thirdByte >> 4;
|
|
1396
|
+
const chromaSubsampling = (thirdByte >> 1) & 0b111;
|
|
1397
|
+
const videoFullRangeFlag = thirdByte & 1;
|
|
1398
|
+
const colourPrimaries = readU8(slice);
|
|
1399
|
+
const transferCharacteristics = readU8(slice);
|
|
1400
|
+
const matrixCoefficients = readU8(slice);
|
|
1401
|
+
|
|
1402
|
+
track.info.vp9CodecInfo = {
|
|
1403
|
+
profile,
|
|
1404
|
+
level,
|
|
1405
|
+
bitDepth,
|
|
1406
|
+
chromaSubsampling,
|
|
1407
|
+
videoFullRangeFlag,
|
|
1408
|
+
colourPrimaries,
|
|
1409
|
+
transferCharacteristics,
|
|
1410
|
+
matrixCoefficients,
|
|
1411
|
+
};
|
|
1412
|
+
}; break;
|
|
1413
|
+
|
|
1414
|
+
case 'av1C': {
|
|
1415
|
+
const track = this.currentTrack;
|
|
1416
|
+
if (!track) {
|
|
1417
|
+
break;
|
|
1418
|
+
}
|
|
1419
|
+
assert(track.info?.type === 'video');
|
|
1420
|
+
|
|
1421
|
+
slice.skip(1); // Marker + version
|
|
1422
|
+
|
|
1423
|
+
const secondByte = readU8(slice);
|
|
1424
|
+
const profile = secondByte >> 5;
|
|
1425
|
+
const level = secondByte & 0b11111;
|
|
1426
|
+
|
|
1427
|
+
const thirdByte = readU8(slice);
|
|
1428
|
+
const tier = thirdByte >> 7;
|
|
1429
|
+
const highBitDepth = (thirdByte >> 6) & 1;
|
|
1430
|
+
const twelveBit = (thirdByte >> 5) & 1;
|
|
1431
|
+
const monochrome = (thirdByte >> 4) & 1;
|
|
1432
|
+
const chromaSubsamplingX = (thirdByte >> 3) & 1;
|
|
1433
|
+
const chromaSubsamplingY = (thirdByte >> 2) & 1;
|
|
1434
|
+
const chromaSamplePosition = thirdByte & 0b11;
|
|
1435
|
+
|
|
1436
|
+
// Logic from https://aomediacodec.github.io/av1-spec/av1-spec.pdf
|
|
1437
|
+
const bitDepth = profile === 2 && highBitDepth ? (twelveBit ? 12 : 10) : (highBitDepth ? 10 : 8);
|
|
1438
|
+
|
|
1439
|
+
track.info.av1CodecInfo = {
|
|
1440
|
+
profile,
|
|
1441
|
+
level,
|
|
1442
|
+
tier,
|
|
1443
|
+
bitDepth,
|
|
1444
|
+
monochrome,
|
|
1445
|
+
chromaSubsamplingX,
|
|
1446
|
+
chromaSubsamplingY,
|
|
1447
|
+
chromaSamplePosition,
|
|
1448
|
+
};
|
|
1449
|
+
}; break;
|
|
1450
|
+
|
|
1451
|
+
case 'colr': {
|
|
1452
|
+
const track = this.currentTrack;
|
|
1453
|
+
if (!track) {
|
|
1454
|
+
break;
|
|
1455
|
+
}
|
|
1456
|
+
assert(track.info?.type === 'video');
|
|
1457
|
+
|
|
1458
|
+
const colourType = readAscii(slice, 4);
|
|
1459
|
+
if (colourType !== 'nclx' && colourType !== 'nclc') {
|
|
1460
|
+
break;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
const colourPrimaries = readU16Be(slice);
|
|
1464
|
+
const transferCharacteristics = readU16Be(slice);
|
|
1465
|
+
const matrixCoefficients = readU16Be(slice);
|
|
1466
|
+
|
|
1467
|
+
let fullRange: boolean | undefined = undefined;
|
|
1468
|
+
if (colourType === 'nclx') {
|
|
1469
|
+
fullRange = Boolean(readU8(slice) & 0x80);
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
track.info.colorSpace = {
|
|
1473
|
+
primaries: COLOR_PRIMARIES_MAP_INVERSE[colourPrimaries],
|
|
1474
|
+
transfer: TRANSFER_CHARACTERISTICS_MAP_INVERSE[transferCharacteristics],
|
|
1475
|
+
matrix: MATRIX_COEFFICIENTS_MAP_INVERSE[matrixCoefficients],
|
|
1476
|
+
fullRange,
|
|
1477
|
+
} as VideoColorSpaceInit;
|
|
1478
|
+
}; break;
|
|
1479
|
+
|
|
1480
|
+
case 'pasp': {
|
|
1481
|
+
const track = this.currentTrack;
|
|
1482
|
+
if (!track) {
|
|
1483
|
+
break;
|
|
1484
|
+
}
|
|
1485
|
+
assert(track.info?.type === 'video');
|
|
1486
|
+
|
|
1487
|
+
const num = readU32Be(slice);
|
|
1488
|
+
const den = readU32Be(slice);
|
|
1489
|
+
|
|
1490
|
+
// https://github.com/Vanilagy/mediabunny/issues/362
|
|
1491
|
+
if (num > 0 && den > 0) {
|
|
1492
|
+
if (num > den) {
|
|
1493
|
+
track.info.squarePixelWidth = Math.round(track.info.width * num / den);
|
|
1494
|
+
} else {
|
|
1495
|
+
track.info.squarePixelHeight = Math.round(track.info.height * den / num);
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
}; break;
|
|
1499
|
+
|
|
1500
|
+
case 'wave': {
|
|
1501
|
+
this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
|
|
1502
|
+
}; break;
|
|
1503
|
+
|
|
1504
|
+
case 'esds': {
|
|
1505
|
+
const track = this.currentTrack;
|
|
1506
|
+
if (!track) {
|
|
1507
|
+
break;
|
|
1508
|
+
}
|
|
1509
|
+
assert(track.info?.type === 'audio');
|
|
1510
|
+
|
|
1511
|
+
slice.skip(4); // Version + flags
|
|
1512
|
+
|
|
1513
|
+
const tag = readU8(slice);
|
|
1514
|
+
assert(tag === 0x03); // ES Descriptor
|
|
1515
|
+
|
|
1516
|
+
readIsomVariableInteger(slice); // Length
|
|
1517
|
+
|
|
1518
|
+
slice.skip(2); // ES ID
|
|
1519
|
+
const mixed = readU8(slice);
|
|
1520
|
+
|
|
1521
|
+
const streamDependenceFlag = (mixed & 0x80) !== 0;
|
|
1522
|
+
const urlFlag = (mixed & 0x40) !== 0;
|
|
1523
|
+
const ocrStreamFlag = (mixed & 0x20) !== 0;
|
|
1524
|
+
|
|
1525
|
+
if (streamDependenceFlag) {
|
|
1526
|
+
slice.skip(2);
|
|
1527
|
+
}
|
|
1528
|
+
if (urlFlag) {
|
|
1529
|
+
const urlLength = readU8(slice);
|
|
1530
|
+
slice.skip(urlLength);
|
|
1531
|
+
}
|
|
1532
|
+
if (ocrStreamFlag) {
|
|
1533
|
+
slice.skip(2);
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
const decoderConfigTag = readU8(slice);
|
|
1537
|
+
assert(decoderConfigTag === 0x04); // DecoderConfigDescriptor
|
|
1538
|
+
|
|
1539
|
+
const decoderConfigDescriptorLength = readIsomVariableInteger(slice); // Length
|
|
1540
|
+
|
|
1541
|
+
const payloadStart = slice.filePos;
|
|
1542
|
+
|
|
1543
|
+
const objectTypeIndication = readU8(slice);
|
|
1544
|
+
if (objectTypeIndication === 0x40 || objectTypeIndication === 0x67) {
|
|
1545
|
+
track.info.codec = 'aac';
|
|
1546
|
+
track.info.aacCodecInfo = {
|
|
1547
|
+
isMpeg2: objectTypeIndication === 0x67,
|
|
1548
|
+
objectType: null,
|
|
1549
|
+
};
|
|
1550
|
+
} else if (objectTypeIndication === 0x69 || objectTypeIndication === 0x6b) {
|
|
1551
|
+
track.info.codec = 'mp3';
|
|
1552
|
+
} else if (objectTypeIndication === 0xdd) {
|
|
1553
|
+
track.info.codec = 'vorbis'; // "nonstandard, gpac uses it" - FFmpeg
|
|
1554
|
+
} else {
|
|
1555
|
+
Logging._warn(
|
|
1556
|
+
`Unsupported audio codec (objectTypeIndication ${objectTypeIndication}) - discarding track.`,
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
slice.skip(1 + 3 + 4 + 4);
|
|
1561
|
+
|
|
1562
|
+
if (decoderConfigDescriptorLength > slice.filePos - payloadStart) {
|
|
1563
|
+
// There's a DecoderSpecificInfo at the end, let's read it
|
|
1564
|
+
|
|
1565
|
+
const decoderSpecificInfoTag = readU8(slice);
|
|
1566
|
+
assert(decoderSpecificInfoTag === 0x05); // DecoderSpecificInfo
|
|
1567
|
+
|
|
1568
|
+
const decoderSpecificInfoLength = readIsomVariableInteger(slice);
|
|
1569
|
+
track.info.codecDescription = readBytes(slice, decoderSpecificInfoLength);
|
|
1570
|
+
|
|
1571
|
+
if (track.info.codec === 'aac') {
|
|
1572
|
+
// Let's try to deduce more accurate values directly from the AudioSpecificConfig:
|
|
1573
|
+
const audioSpecificConfig = parseAacAudioSpecificConfig(track.info.codecDescription);
|
|
1574
|
+
if (audioSpecificConfig.numberOfChannels !== null) {
|
|
1575
|
+
track.info.numberOfChannels = audioSpecificConfig.numberOfChannels;
|
|
1576
|
+
}
|
|
1577
|
+
if (audioSpecificConfig.sampleRate !== null) {
|
|
1578
|
+
track.info.sampleRate = audioSpecificConfig.sampleRate;
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
}; break;
|
|
1583
|
+
|
|
1584
|
+
case 'enda': {
|
|
1585
|
+
const track = this.currentTrack;
|
|
1586
|
+
if (!track) {
|
|
1587
|
+
break;
|
|
1588
|
+
}
|
|
1589
|
+
assert(track.info?.type === 'audio');
|
|
1590
|
+
|
|
1591
|
+
track.info.pcmLittleEndian = !!(readU16Be(slice) & 0xff); // 0xff is from FFmpeg
|
|
1592
|
+
}; break;
|
|
1593
|
+
|
|
1594
|
+
case 'pcmC': {
|
|
1595
|
+
const track = this.currentTrack;
|
|
1596
|
+
if (!track) {
|
|
1597
|
+
break;
|
|
1598
|
+
}
|
|
1599
|
+
assert(track.info?.type === 'audio');
|
|
1600
|
+
|
|
1601
|
+
slice.skip(1 + 3); // Version + flags
|
|
1602
|
+
|
|
1603
|
+
// ISO/IEC 23003-5
|
|
1604
|
+
|
|
1605
|
+
const formatFlags = readU8(slice);
|
|
1606
|
+
track.info.pcmLittleEndian = Boolean(formatFlags & 0x01);
|
|
1607
|
+
track.info.pcmSampleSize = readU8(slice);
|
|
1608
|
+
}; break;
|
|
1609
|
+
|
|
1610
|
+
case 'dOps': { // Used for Opus audio
|
|
1611
|
+
const track = this.currentTrack;
|
|
1612
|
+
if (!track) {
|
|
1613
|
+
break;
|
|
1614
|
+
}
|
|
1615
|
+
assert(track.info?.type === 'audio');
|
|
1616
|
+
|
|
1617
|
+
slice.skip(1); // Version
|
|
1618
|
+
|
|
1619
|
+
// https://www.opus-codec.org/docs/opus_in_isobmff.html
|
|
1620
|
+
const outputChannelCount = readU8(slice);
|
|
1621
|
+
const preSkip = readU16Be(slice);
|
|
1622
|
+
const inputSampleRate = readU32Be(slice);
|
|
1623
|
+
const outputGain = readI16Be(slice);
|
|
1624
|
+
const channelMappingFamily = readU8(slice);
|
|
1625
|
+
|
|
1626
|
+
let channelMappingTable: Uint8Array;
|
|
1627
|
+
if (channelMappingFamily !== 0) {
|
|
1628
|
+
channelMappingTable = readBytes(slice, 2 + outputChannelCount);
|
|
1629
|
+
} else {
|
|
1630
|
+
channelMappingTable = new Uint8Array(0);
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// https://datatracker.ietf.org/doc/html/draft-ietf-codec-oggopus-06
|
|
1634
|
+
const description = new Uint8Array(8 + 1 + 1 + 2 + 4 + 2 + 1 + channelMappingTable.byteLength);
|
|
1635
|
+
const view = new DataView(description.buffer);
|
|
1636
|
+
view.setUint32(0, 0x4f707573, false); // 'Opus'
|
|
1637
|
+
view.setUint32(4, 0x48656164, false); // 'Head'
|
|
1638
|
+
view.setUint8(8, 1); // Version
|
|
1639
|
+
view.setUint8(9, outputChannelCount);
|
|
1640
|
+
view.setUint16(10, preSkip, true);
|
|
1641
|
+
view.setUint32(12, inputSampleRate, true);
|
|
1642
|
+
view.setInt16(16, outputGain, true);
|
|
1643
|
+
view.setUint8(18, channelMappingFamily);
|
|
1644
|
+
description.set(channelMappingTable, 19);
|
|
1645
|
+
|
|
1646
|
+
track.info.codecDescription = description;
|
|
1647
|
+
track.info.numberOfChannels = outputChannelCount;
|
|
1648
|
+
// Don't copy the input sample rate, irrelevant, and output sample rate is fixed
|
|
1649
|
+
}; break;
|
|
1650
|
+
|
|
1651
|
+
case 'dfLa': { // Used for FLAC audio
|
|
1652
|
+
const track = this.currentTrack;
|
|
1653
|
+
if (!track) {
|
|
1654
|
+
break;
|
|
1655
|
+
}
|
|
1656
|
+
assert(track.info?.type === 'audio');
|
|
1657
|
+
|
|
1658
|
+
slice.skip(4); // Version + flags
|
|
1659
|
+
|
|
1660
|
+
// https://datatracker.ietf.org/doc/rfc9639/
|
|
1661
|
+
|
|
1662
|
+
const BLOCK_TYPE_MASK = 0x7f;
|
|
1663
|
+
const LAST_METADATA_BLOCK_FLAG_MASK = 0x80;
|
|
1664
|
+
|
|
1665
|
+
const startPos = slice.filePos;
|
|
1666
|
+
|
|
1667
|
+
while (slice.filePos < boxEndPos) {
|
|
1668
|
+
const flagAndType = readU8(slice);
|
|
1669
|
+
const metadataBlockLength = readU24Be(slice);
|
|
1670
|
+
const type = flagAndType & BLOCK_TYPE_MASK;
|
|
1671
|
+
|
|
1672
|
+
// It's a STREAMINFO block; let's extract the actual sample rate and channel count
|
|
1673
|
+
if (type === FlacBlockType.STREAMINFO) {
|
|
1674
|
+
slice.skip(10);
|
|
1675
|
+
|
|
1676
|
+
// Extract sample rate and channel count
|
|
1677
|
+
const word = readU32Be(slice);
|
|
1678
|
+
const sampleRate = word >>> 12;
|
|
1679
|
+
const numberOfChannels = ((word >> 9) & 0b111) + 1;
|
|
1680
|
+
|
|
1681
|
+
track.info.sampleRate = sampleRate;
|
|
1682
|
+
track.info.numberOfChannels = numberOfChannels;
|
|
1683
|
+
|
|
1684
|
+
slice.skip(20);
|
|
1685
|
+
} else {
|
|
1686
|
+
// Simply skip ahead to the next block
|
|
1687
|
+
slice.skip(metadataBlockLength);
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
if (flagAndType & LAST_METADATA_BLOCK_FLAG_MASK) {
|
|
1691
|
+
break;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
const endPos = slice.filePos;
|
|
1696
|
+
slice.filePos = startPos;
|
|
1697
|
+
const bytes = readBytes(slice, endPos - startPos);
|
|
1698
|
+
|
|
1699
|
+
const description = new Uint8Array(4 + bytes.byteLength);
|
|
1700
|
+
const view = new DataView(description.buffer);
|
|
1701
|
+
view.setUint32(0, 0x664c6143, false); // 'fLaC'
|
|
1702
|
+
description.set(bytes, 4);
|
|
1703
|
+
|
|
1704
|
+
// Set the codec description to be 'fLaC' + all metadata blocks
|
|
1705
|
+
track.info.codecDescription = description;
|
|
1706
|
+
}; break;
|
|
1707
|
+
|
|
1708
|
+
case 'dac3': { // AC3SpecificBox
|
|
1709
|
+
const track = this.currentTrack;
|
|
1710
|
+
if (!track) {
|
|
1711
|
+
break;
|
|
1712
|
+
}
|
|
1713
|
+
assert(track.info?.type === 'audio');
|
|
1714
|
+
|
|
1715
|
+
const bytes = readBytes(slice, 3);
|
|
1716
|
+
const bitstream = new Bitstream(bytes);
|
|
1717
|
+
|
|
1718
|
+
const fscod = bitstream.readBits(2);
|
|
1719
|
+
bitstream.skipBits(5 + 3); // Skip bsid and bsmod
|
|
1720
|
+
const acmod = bitstream.readBits(3);
|
|
1721
|
+
const lfeon = bitstream.readBits(1);
|
|
1722
|
+
|
|
1723
|
+
if (fscod < 3) {
|
|
1724
|
+
track.info.sampleRate = AC3_SAMPLE_RATES[fscod]!;
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
track.info.numberOfChannels = AC3_ACMOD_CHANNEL_COUNTS[acmod]! + lfeon;
|
|
1728
|
+
}; break;
|
|
1729
|
+
|
|
1730
|
+
case 'dec3': { // EC3SpecificBox
|
|
1731
|
+
const track = this.currentTrack;
|
|
1732
|
+
if (!track) {
|
|
1733
|
+
break;
|
|
1734
|
+
}
|
|
1735
|
+
assert(track.info?.type === 'audio');
|
|
1736
|
+
|
|
1737
|
+
const bytes = readBytes(slice, boxInfo.contentSize);
|
|
1738
|
+
const config = parseEac3Config(bytes);
|
|
1739
|
+
|
|
1740
|
+
if (!config) {
|
|
1741
|
+
Logging._warn('Invalid dec3 box contents, ignoring.');
|
|
1742
|
+
break;
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
const sampleRate = getEac3SampleRate(config);
|
|
1746
|
+
if (sampleRate !== null) {
|
|
1747
|
+
track.info.sampleRate = sampleRate;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
track.info.numberOfChannels = getEac3ChannelCount(config);
|
|
1751
|
+
}; break;
|
|
1752
|
+
|
|
1753
|
+
case 'stts': {
|
|
1754
|
+
const track = this.currentTrack;
|
|
1755
|
+
if (!track) {
|
|
1756
|
+
break;
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
if (!track.sampleTable) {
|
|
1760
|
+
break;
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1763
|
+
slice.skip(4); // Version + flags
|
|
1764
|
+
|
|
1765
|
+
const entryCount = readU32Be(slice);
|
|
1766
|
+
|
|
1767
|
+
let currentIndex = 0;
|
|
1768
|
+
let currentTimestamp = 0;
|
|
1769
|
+
|
|
1770
|
+
for (let i = 0; i < entryCount; i++) {
|
|
1771
|
+
const sampleCount = readU32Be(slice);
|
|
1772
|
+
const sampleDelta = readU32Be(slice);
|
|
1773
|
+
|
|
1774
|
+
track.sampleTable.sampleTimingEntries.push({
|
|
1775
|
+
startIndex: currentIndex,
|
|
1776
|
+
startDecodeTimestamp: currentTimestamp,
|
|
1777
|
+
count: sampleCount,
|
|
1778
|
+
delta: sampleDelta,
|
|
1779
|
+
});
|
|
1780
|
+
|
|
1781
|
+
currentIndex += sampleCount;
|
|
1782
|
+
currentTimestamp += sampleCount * sampleDelta;
|
|
1783
|
+
}
|
|
1784
|
+
}; break;
|
|
1785
|
+
|
|
1786
|
+
case 'ctts': {
|
|
1787
|
+
const track = this.currentTrack;
|
|
1788
|
+
if (!track) {
|
|
1789
|
+
break;
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
if (!track.sampleTable) {
|
|
1793
|
+
break;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
slice.skip(1 + 3); // Version + flags
|
|
1797
|
+
|
|
1798
|
+
const entryCount = readU32Be(slice);
|
|
1799
|
+
|
|
1800
|
+
let sampleIndex = 0;
|
|
1801
|
+
for (let i = 0; i < entryCount; i++) {
|
|
1802
|
+
const sampleCount = readU32Be(slice);
|
|
1803
|
+
const sampleOffset = readI32Be(slice);
|
|
1804
|
+
|
|
1805
|
+
track.sampleTable.sampleCompositionTimeOffsets.push({
|
|
1806
|
+
startIndex: sampleIndex,
|
|
1807
|
+
count: sampleCount,
|
|
1808
|
+
offset: sampleOffset,
|
|
1809
|
+
});
|
|
1810
|
+
|
|
1811
|
+
sampleIndex += sampleCount;
|
|
1812
|
+
}
|
|
1813
|
+
}; break;
|
|
1814
|
+
|
|
1815
|
+
case 'stsz': {
|
|
1816
|
+
const track = this.currentTrack;
|
|
1817
|
+
if (!track) {
|
|
1818
|
+
break;
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
if (!track.sampleTable) {
|
|
1822
|
+
break;
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
slice.skip(4); // Version + flags
|
|
1826
|
+
|
|
1827
|
+
const sampleSize = readU32Be(slice);
|
|
1828
|
+
const sampleCount = readU32Be(slice);
|
|
1829
|
+
|
|
1830
|
+
if (sampleSize === 0) {
|
|
1831
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
1832
|
+
const sampleSize = readU32Be(slice);
|
|
1833
|
+
track.sampleTable.sampleSizes.push(sampleSize);
|
|
1834
|
+
}
|
|
1835
|
+
} else {
|
|
1836
|
+
track.sampleTable.sampleSizes.push(sampleSize);
|
|
1837
|
+
}
|
|
1838
|
+
}; break;
|
|
1839
|
+
|
|
1840
|
+
case 'stz2': {
|
|
1841
|
+
const track = this.currentTrack;
|
|
1842
|
+
if (!track) {
|
|
1843
|
+
break;
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
if (!track.sampleTable) {
|
|
1847
|
+
break;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
slice.skip(4); // Version + flags
|
|
1851
|
+
slice.skip(3); // Reserved
|
|
1852
|
+
|
|
1853
|
+
const fieldSize = readU8(slice); // in bits
|
|
1854
|
+
const sampleCount = readU32Be(slice);
|
|
1855
|
+
|
|
1856
|
+
const bytes = readBytes(slice, Math.ceil(sampleCount * fieldSize / 8));
|
|
1857
|
+
const bitstream = new Bitstream(bytes);
|
|
1858
|
+
|
|
1859
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
1860
|
+
const sampleSize = bitstream.readBits(fieldSize);
|
|
1861
|
+
track.sampleTable.sampleSizes.push(sampleSize);
|
|
1862
|
+
}
|
|
1863
|
+
}; break;
|
|
1864
|
+
|
|
1865
|
+
case 'stss': {
|
|
1866
|
+
const track = this.currentTrack;
|
|
1867
|
+
if (!track) {
|
|
1868
|
+
break;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
if (!track.sampleTable) {
|
|
1872
|
+
break;
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
slice.skip(4); // Version + flags
|
|
1876
|
+
|
|
1877
|
+
track.sampleTable.keySampleIndices = [];
|
|
1878
|
+
|
|
1879
|
+
const entryCount = readU32Be(slice);
|
|
1880
|
+
for (let i = 0; i < entryCount; i++) {
|
|
1881
|
+
const sampleIndex = readU32Be(slice) - 1; // Convert to 0-indexed
|
|
1882
|
+
track.sampleTable.keySampleIndices.push(sampleIndex);
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
if (track.sampleTable.keySampleIndices[0] !== 0) {
|
|
1886
|
+
// Some files don't mark the first sample a key sample, which is basically almost always incorrect.
|
|
1887
|
+
// Here, we correct for that mistake:
|
|
1888
|
+
track.sampleTable.keySampleIndices.unshift(0);
|
|
1889
|
+
}
|
|
1890
|
+
}; break;
|
|
1891
|
+
|
|
1892
|
+
case 'stsc': {
|
|
1893
|
+
const track = this.currentTrack;
|
|
1894
|
+
if (!track) {
|
|
1895
|
+
break;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
if (!track.sampleTable) {
|
|
1899
|
+
break;
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
slice.skip(4);
|
|
1903
|
+
|
|
1904
|
+
const entryCount = readU32Be(slice);
|
|
1905
|
+
|
|
1906
|
+
for (let i = 0; i < entryCount; i++) {
|
|
1907
|
+
const startChunkIndex = readU32Be(slice) - 1; // Convert to 0-indexed
|
|
1908
|
+
const samplesPerChunk = readU32Be(slice);
|
|
1909
|
+
const sampleDescriptionIndex = readU32Be(slice);
|
|
1910
|
+
|
|
1911
|
+
track.sampleTable.sampleToChunk.push({
|
|
1912
|
+
startSampleIndex: -1,
|
|
1913
|
+
startChunkIndex,
|
|
1914
|
+
samplesPerChunk,
|
|
1915
|
+
sampleDescriptionIndex,
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
let startSampleIndex = 0;
|
|
1920
|
+
for (let i = 0; i < track.sampleTable.sampleToChunk.length; i++) {
|
|
1921
|
+
track.sampleTable.sampleToChunk[i]!.startSampleIndex = startSampleIndex;
|
|
1922
|
+
|
|
1923
|
+
if (i < track.sampleTable.sampleToChunk.length - 1) {
|
|
1924
|
+
const nextChunk = track.sampleTable.sampleToChunk[i + 1]!;
|
|
1925
|
+
const chunkCount = nextChunk.startChunkIndex
|
|
1926
|
+
- track.sampleTable.sampleToChunk[i]!.startChunkIndex;
|
|
1927
|
+
startSampleIndex += chunkCount * track.sampleTable.sampleToChunk[i]!.samplesPerChunk;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
}; break;
|
|
1931
|
+
|
|
1932
|
+
case 'stco': {
|
|
1933
|
+
const track = this.currentTrack;
|
|
1934
|
+
if (!track) {
|
|
1935
|
+
break;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
if (!track.sampleTable) {
|
|
1939
|
+
break;
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
slice.skip(4); // Version + flags
|
|
1943
|
+
|
|
1944
|
+
const entryCount = readU32Be(slice);
|
|
1945
|
+
|
|
1946
|
+
for (let i = 0; i < entryCount; i++) {
|
|
1947
|
+
const chunkOffset = readU32Be(slice);
|
|
1948
|
+
track.sampleTable.chunkOffsets.push(chunkOffset);
|
|
1949
|
+
}
|
|
1950
|
+
}; break;
|
|
1951
|
+
|
|
1952
|
+
case 'co64': {
|
|
1953
|
+
const track = this.currentTrack;
|
|
1954
|
+
if (!track) {
|
|
1955
|
+
break;
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
if (!track.sampleTable) {
|
|
1959
|
+
break;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
slice.skip(4); // Version + flags
|
|
1963
|
+
|
|
1964
|
+
const entryCount = readU32Be(slice);
|
|
1965
|
+
|
|
1966
|
+
for (let i = 0; i < entryCount; i++) {
|
|
1967
|
+
const chunkOffset = readU64Be(slice);
|
|
1968
|
+
track.sampleTable.chunkOffsets.push(chunkOffset);
|
|
1969
|
+
}
|
|
1970
|
+
}; break;
|
|
1971
|
+
|
|
1972
|
+
case 'mvex': {
|
|
1973
|
+
this.isFragmented = true;
|
|
1974
|
+
this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
|
|
1975
|
+
}; break;
|
|
1976
|
+
|
|
1977
|
+
case 'mehd': {
|
|
1978
|
+
const version = readU8(slice);
|
|
1979
|
+
slice.skip(3); // Flags
|
|
1980
|
+
|
|
1981
|
+
const fragmentDuration = version === 1 ? readU64Be(slice) : readU32Be(slice);
|
|
1982
|
+
this.movieDurationInTimescale = fragmentDuration;
|
|
1983
|
+
}; break;
|
|
1984
|
+
|
|
1985
|
+
case 'trex': {
|
|
1986
|
+
slice.skip(4); // Version + flags
|
|
1987
|
+
|
|
1988
|
+
const trackId = readU32Be(slice);
|
|
1989
|
+
const defaultSampleDescriptionIndex = readU32Be(slice);
|
|
1990
|
+
const defaultSampleDuration = readU32Be(slice);
|
|
1991
|
+
const defaultSampleSize = readU32Be(slice);
|
|
1992
|
+
const defaultSampleFlags = readU32Be(slice);
|
|
1993
|
+
|
|
1994
|
+
// We store these separately rather than in the tracks since the tracks may not exist yet
|
|
1995
|
+
this.fragmentTrackDefaults.push({
|
|
1996
|
+
trackId,
|
|
1997
|
+
defaultSampleDescriptionIndex,
|
|
1998
|
+
defaultSampleDuration,
|
|
1999
|
+
defaultSampleSize,
|
|
2000
|
+
defaultSampleFlags,
|
|
2001
|
+
});
|
|
2002
|
+
}; break;
|
|
2003
|
+
|
|
2004
|
+
case 'tfra': {
|
|
2005
|
+
const version = readU8(slice);
|
|
2006
|
+
slice.skip(3); // Flags
|
|
2007
|
+
|
|
2008
|
+
const trackId = readU32Be(slice);
|
|
2009
|
+
const track = this.tracks.find(x => x.id === trackId);
|
|
2010
|
+
if (!track) {
|
|
2011
|
+
break;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
const word = readU32Be(slice);
|
|
2015
|
+
|
|
2016
|
+
const lengthSizeOfTrafNum = (word & 0b110000) >> 4;
|
|
2017
|
+
const lengthSizeOfTrunNum = (word & 0b001100) >> 2;
|
|
2018
|
+
const lengthSizeOfSampleNum = word & 0b000011;
|
|
2019
|
+
|
|
2020
|
+
const functions = [readU8, readU16Be, readU24Be, readU32Be];
|
|
2021
|
+
|
|
2022
|
+
const readTrafNum = functions[lengthSizeOfTrafNum]!;
|
|
2023
|
+
const readTrunNum = functions[lengthSizeOfTrunNum]!;
|
|
2024
|
+
const readSampleNum = functions[lengthSizeOfSampleNum]!;
|
|
2025
|
+
|
|
2026
|
+
const numberOfEntries = readU32Be(slice);
|
|
2027
|
+
for (let i = 0; i < numberOfEntries; i++) {
|
|
2028
|
+
const time = version === 1 ? readU64Be(slice) : readU32Be(slice);
|
|
2029
|
+
const moofOffset = version === 1 ? readU64Be(slice) : readU32Be(slice);
|
|
2030
|
+
|
|
2031
|
+
readTrafNum(slice);
|
|
2032
|
+
readTrunNum(slice);
|
|
2033
|
+
readSampleNum(slice);
|
|
2034
|
+
|
|
2035
|
+
track.fragmentLookupTable.push({
|
|
2036
|
+
timestamp: time,
|
|
2037
|
+
moofOffset,
|
|
2038
|
+
});
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
// Sort by timestamp in case it's not naturally sorted
|
|
2042
|
+
track.fragmentLookupTable.sort((a, b) => a.timestamp - b.timestamp);
|
|
2043
|
+
|
|
2044
|
+
// Remove multiple entries for the same time
|
|
2045
|
+
for (let i = 0; i < track.fragmentLookupTable.length - 1; i++) {
|
|
2046
|
+
const entry1 = track.fragmentLookupTable[i]!;
|
|
2047
|
+
const entry2 = track.fragmentLookupTable[i + 1]!;
|
|
2048
|
+
|
|
2049
|
+
if (entry1.timestamp === entry2.timestamp) {
|
|
2050
|
+
track.fragmentLookupTable.splice(i + 1, 1);
|
|
2051
|
+
i--;
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
}; break;
|
|
2055
|
+
|
|
2056
|
+
case 'moof': {
|
|
2057
|
+
this.currentFragment = {
|
|
2058
|
+
moofOffset: startPos,
|
|
2059
|
+
moofSize: boxInfo.totalSize,
|
|
2060
|
+
implicitBaseDataOffset: startPos,
|
|
2061
|
+
trackData: new Map(),
|
|
2062
|
+
psshBoxes: [],
|
|
2063
|
+
};
|
|
2064
|
+
|
|
2065
|
+
this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
|
|
2066
|
+
|
|
2067
|
+
this.lastReadFragment = this.currentFragment;
|
|
2068
|
+
this.currentFragment = null;
|
|
2069
|
+
}; break;
|
|
2070
|
+
|
|
2071
|
+
case 'traf': {
|
|
2072
|
+
assert(this.currentFragment);
|
|
2073
|
+
|
|
2074
|
+
this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
|
|
2075
|
+
|
|
2076
|
+
// It is possible that there is no current track, for example when we don't care about the track
|
|
2077
|
+
// referenced in the track fragment header.
|
|
2078
|
+
if (this.currentTrack) {
|
|
2079
|
+
const trackData = this.currentFragment.trackData.get(this.currentTrack.id);
|
|
2080
|
+
cond:
|
|
2081
|
+
if (trackData) {
|
|
2082
|
+
if (trackData.samples.length === 0) {
|
|
2083
|
+
// Don't associate the fragment with the track if it has no samples, this simplifies
|
|
2084
|
+
// other code
|
|
2085
|
+
this.currentFragment.trackData.delete(this.currentTrack.id);
|
|
2086
|
+
break cond;
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
trackData.presentationTimestamps = trackData.samples
|
|
2090
|
+
.map((x, i) => ({ presentationTimestamp: x.presentationTimestamp, sampleIndex: i }))
|
|
2091
|
+
.sort((a, b) => a.presentationTimestamp - b.presentationTimestamp);
|
|
2092
|
+
|
|
2093
|
+
for (let i = 0; i < trackData.presentationTimestamps.length; i++) {
|
|
2094
|
+
const currentEntry = trackData.presentationTimestamps[i]!;
|
|
2095
|
+
const currentSample = trackData.samples[currentEntry.sampleIndex]!;
|
|
2096
|
+
|
|
2097
|
+
if (trackData.firstKeyFrameTimestamp === null && currentSample.isKeyFrame) {
|
|
2098
|
+
trackData.firstKeyFrameTimestamp = currentSample.presentationTimestamp;
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
if (i < trackData.presentationTimestamps.length - 1) {
|
|
2102
|
+
// Update sample durations based on presentation order
|
|
2103
|
+
const nextEntry = trackData.presentationTimestamps[i + 1]!;
|
|
2104
|
+
const duration = nextEntry.presentationTimestamp - currentEntry.presentationTimestamp;
|
|
2105
|
+
|
|
2106
|
+
currentSample.duration = duration;
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
|
|
2110
|
+
const firstSample = trackData.samples[trackData.presentationTimestamps[0]!.sampleIndex]!;
|
|
2111
|
+
const lastSample = trackData.samples[last(trackData.presentationTimestamps)!.sampleIndex]!;
|
|
2112
|
+
|
|
2113
|
+
trackData.startTimestamp = firstSample.presentationTimestamp;
|
|
2114
|
+
trackData.endTimestamp = lastSample.presentationTimestamp + lastSample.duration;
|
|
2115
|
+
|
|
2116
|
+
const { currentFragmentState } = this.currentTrack;
|
|
2117
|
+
assert(currentFragmentState);
|
|
2118
|
+
|
|
2119
|
+
if (currentFragmentState.startTimestamp !== null) {
|
|
2120
|
+
offsetFragmentTrackDataByTimestamp(trackData, currentFragmentState.startTimestamp);
|
|
2121
|
+
trackData.startTimestampIsFinal = true;
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
// Transfer the buffered saiz+saio state onto the track data, so readFragment can resolve it
|
|
2125
|
+
// once all boxes are parsed. Only relevant if senc wasn't already used to populate samples.
|
|
2126
|
+
if (currentFragmentState.encryptionAuxInfo && !trackData.samples[0]!.encryption) {
|
|
2127
|
+
trackData.encryptionAuxInfo = currentFragmentState.encryptionAuxInfo;
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
|
|
2131
|
+
this.currentTrack.currentFragmentState = null;
|
|
2132
|
+
this.currentTrack = null;
|
|
2133
|
+
}
|
|
2134
|
+
}; break;
|
|
2135
|
+
|
|
2136
|
+
case 'pssh': {
|
|
2137
|
+
if (this.input._formatOptions.isobmff?._suppressPsshParsing) {
|
|
2138
|
+
break;
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
const psshBox = parsePsshBoxContents(readBytes(slice, boxInfo.contentSize));
|
|
2142
|
+
|
|
2143
|
+
if (this.currentFragment) {
|
|
2144
|
+
this.currentFragment.psshBoxes.push(psshBox);
|
|
2145
|
+
} else if (!this.currentTrack) {
|
|
2146
|
+
this.psshBoxes.push(psshBox);
|
|
2147
|
+
}
|
|
2148
|
+
}; break;
|
|
2149
|
+
|
|
2150
|
+
case 'tfhd': {
|
|
2151
|
+
assert(this.currentFragment);
|
|
2152
|
+
|
|
2153
|
+
slice.skip(1); // Version
|
|
2154
|
+
|
|
2155
|
+
const flags = readU24Be(slice);
|
|
2156
|
+
const baseDataOffsetPresent = Boolean(flags & 0x000001);
|
|
2157
|
+
const sampleDescriptionIndexPresent = Boolean(flags & 0x000002);
|
|
2158
|
+
const defaultSampleDurationPresent = Boolean(flags & 0x000008);
|
|
2159
|
+
const defaultSampleSizePresent = Boolean(flags & 0x000010);
|
|
2160
|
+
const defaultSampleFlagsPresent = Boolean(flags & 0x000020);
|
|
2161
|
+
const durationIsEmpty = Boolean(flags & 0x010000);
|
|
2162
|
+
const defaultBaseIsMoof = Boolean(flags & 0x020000);
|
|
2163
|
+
|
|
2164
|
+
const trackId = readU32Be(slice);
|
|
2165
|
+
const track = this.tracks.find(x => x.id === trackId);
|
|
2166
|
+
if (!track) {
|
|
2167
|
+
// We don't care about this track
|
|
2168
|
+
break;
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
const defaults = this.fragmentTrackDefaults.find(x => x.trackId === trackId);
|
|
2172
|
+
|
|
2173
|
+
this.currentTrack = track;
|
|
2174
|
+
track.currentFragmentState = {
|
|
2175
|
+
baseDataOffset: this.currentFragment.implicitBaseDataOffset,
|
|
2176
|
+
sampleDescriptionIndex: defaults?.defaultSampleDescriptionIndex ?? null,
|
|
2177
|
+
defaultSampleDuration: defaults?.defaultSampleDuration ?? null,
|
|
2178
|
+
defaultSampleSize: defaults?.defaultSampleSize ?? null,
|
|
2179
|
+
defaultSampleFlags: defaults?.defaultSampleFlags ?? null,
|
|
2180
|
+
startTimestamp: null,
|
|
2181
|
+
encryptionAuxInfo: null,
|
|
2182
|
+
};
|
|
2183
|
+
|
|
2184
|
+
if (baseDataOffsetPresent) {
|
|
2185
|
+
track.currentFragmentState.baseDataOffset = readU64Be(slice);
|
|
2186
|
+
} else if (defaultBaseIsMoof) {
|
|
2187
|
+
track.currentFragmentState.baseDataOffset = this.currentFragment.moofOffset;
|
|
2188
|
+
}
|
|
2189
|
+
if (sampleDescriptionIndexPresent) {
|
|
2190
|
+
track.currentFragmentState.sampleDescriptionIndex = readU32Be(slice);
|
|
2191
|
+
}
|
|
2192
|
+
if (defaultSampleDurationPresent) {
|
|
2193
|
+
track.currentFragmentState.defaultSampleDuration = readU32Be(slice);
|
|
2194
|
+
}
|
|
2195
|
+
if (defaultSampleSizePresent) {
|
|
2196
|
+
track.currentFragmentState.defaultSampleSize = readU32Be(slice);
|
|
2197
|
+
}
|
|
2198
|
+
if (defaultSampleFlagsPresent) {
|
|
2199
|
+
track.currentFragmentState.defaultSampleFlags = readU32Be(slice);
|
|
2200
|
+
}
|
|
2201
|
+
if (durationIsEmpty) {
|
|
2202
|
+
track.currentFragmentState.defaultSampleDuration = 0;
|
|
2203
|
+
}
|
|
2204
|
+
}; break;
|
|
2205
|
+
|
|
2206
|
+
case 'tfdt': {
|
|
2207
|
+
const track = this.currentTrack;
|
|
2208
|
+
if (!track) {
|
|
2209
|
+
break;
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
assert(track.currentFragmentState);
|
|
2213
|
+
|
|
2214
|
+
const version = readU8(slice);
|
|
2215
|
+
slice.skip(3); // Flags
|
|
2216
|
+
|
|
2217
|
+
const baseMediaDecodeTime = version === 0 ? readU32Be(slice) : readU64Be(slice);
|
|
2218
|
+
track.currentFragmentState.startTimestamp = baseMediaDecodeTime;
|
|
2219
|
+
}; break;
|
|
2220
|
+
|
|
2221
|
+
case 'trun': {
|
|
2222
|
+
const track = this.currentTrack;
|
|
2223
|
+
if (!track) {
|
|
2224
|
+
break;
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
assert(this.currentFragment);
|
|
2228
|
+
assert(track.currentFragmentState);
|
|
2229
|
+
|
|
2230
|
+
const version = readU8(slice);
|
|
2231
|
+
const flags = readU24Be(slice);
|
|
2232
|
+
const dataOffsetPresent = Boolean(flags & 0x000001);
|
|
2233
|
+
const firstSampleFlagsPresent = Boolean(flags & 0x000004);
|
|
2234
|
+
const sampleDurationPresent = Boolean(flags & 0x000100);
|
|
2235
|
+
const sampleSizePresent = Boolean(flags & 0x000200);
|
|
2236
|
+
const sampleFlagsPresent = Boolean(flags & 0x000400);
|
|
2237
|
+
const sampleCompositionTimeOffsetsPresent = Boolean(flags & 0x000800);
|
|
2238
|
+
|
|
2239
|
+
const sampleCount = readU32Be(slice);
|
|
2240
|
+
|
|
2241
|
+
let dataOffset: number | null = null;
|
|
2242
|
+
if (dataOffsetPresent) {
|
|
2243
|
+
dataOffset = readI32Be(slice);
|
|
2244
|
+
}
|
|
2245
|
+
let firstSampleFlags: number | null = null;
|
|
2246
|
+
if (firstSampleFlagsPresent) {
|
|
2247
|
+
firstSampleFlags = readU32Be(slice);
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
let trackData: FragmentTrackData;
|
|
2251
|
+
|
|
2252
|
+
if (this.currentFragment.trackData.has(track.id)) {
|
|
2253
|
+
trackData = this.currentFragment.trackData.get(track.id)!;
|
|
2254
|
+
|
|
2255
|
+
if (dataOffset !== null) {
|
|
2256
|
+
trackData.currentOffset = track.currentFragmentState.baseDataOffset + dataOffset;
|
|
2257
|
+
} else {
|
|
2258
|
+
// "If the data-offset is not present, then the data for this run starts immediately after the
|
|
2259
|
+
// data of the previous run"
|
|
2260
|
+
}
|
|
2261
|
+
} else {
|
|
2262
|
+
trackData = {
|
|
2263
|
+
track,
|
|
2264
|
+
currentTimestamp: 0,
|
|
2265
|
+
currentOffset: track.currentFragmentState.baseDataOffset + (dataOffset ?? 0),
|
|
2266
|
+
startTimestamp: 0,
|
|
2267
|
+
endTimestamp: 0,
|
|
2268
|
+
firstKeyFrameTimestamp: null,
|
|
2269
|
+
samples: [],
|
|
2270
|
+
presentationTimestamps: [],
|
|
2271
|
+
startTimestampIsFinal: false,
|
|
2272
|
+
encryptionAuxInfo: null,
|
|
2273
|
+
};
|
|
2274
|
+
this.currentFragment.trackData.set(track.id, trackData);
|
|
2275
|
+
}
|
|
2276
|
+
|
|
2277
|
+
for (let i = 0; i < sampleCount; i++) {
|
|
2278
|
+
let sampleDuration: number;
|
|
2279
|
+
if (sampleDurationPresent) {
|
|
2280
|
+
sampleDuration = readU32Be(slice);
|
|
2281
|
+
} else {
|
|
2282
|
+
assert(track.currentFragmentState.defaultSampleDuration !== null);
|
|
2283
|
+
sampleDuration = track.currentFragmentState.defaultSampleDuration;
|
|
2284
|
+
}
|
|
2285
|
+
|
|
2286
|
+
let sampleSize: number;
|
|
2287
|
+
if (sampleSizePresent) {
|
|
2288
|
+
sampleSize = readU32Be(slice);
|
|
2289
|
+
} else {
|
|
2290
|
+
assert(track.currentFragmentState.defaultSampleSize !== null);
|
|
2291
|
+
sampleSize = track.currentFragmentState.defaultSampleSize;
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
let sampleFlags: number;
|
|
2295
|
+
if (sampleFlagsPresent) {
|
|
2296
|
+
sampleFlags = readU32Be(slice);
|
|
2297
|
+
} else {
|
|
2298
|
+
assert(track.currentFragmentState.defaultSampleFlags !== null);
|
|
2299
|
+
sampleFlags = track.currentFragmentState.defaultSampleFlags;
|
|
2300
|
+
}
|
|
2301
|
+
if (i === 0 && firstSampleFlags !== null) {
|
|
2302
|
+
sampleFlags = firstSampleFlags;
|
|
2303
|
+
}
|
|
2304
|
+
|
|
2305
|
+
let sampleCompositionTimeOffset = 0;
|
|
2306
|
+
if (sampleCompositionTimeOffsetsPresent) {
|
|
2307
|
+
if (version === 0) {
|
|
2308
|
+
sampleCompositionTimeOffset = readU32Be(slice);
|
|
2309
|
+
} else {
|
|
2310
|
+
sampleCompositionTimeOffset = readI32Be(slice);
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
const isKeyFrame = !(sampleFlags & 0x00010000);
|
|
2315
|
+
|
|
2316
|
+
trackData.samples.push({
|
|
2317
|
+
presentationTimestamp: trackData.currentTimestamp + sampleCompositionTimeOffset,
|
|
2318
|
+
duration: sampleDuration,
|
|
2319
|
+
byteOffset: trackData.currentOffset,
|
|
2320
|
+
byteSize: sampleSize,
|
|
2321
|
+
isKeyFrame,
|
|
2322
|
+
encryption: null,
|
|
2323
|
+
});
|
|
2324
|
+
|
|
2325
|
+
trackData.currentOffset += sampleSize;
|
|
2326
|
+
trackData.currentTimestamp += sampleDuration;
|
|
2327
|
+
}
|
|
2328
|
+
|
|
2329
|
+
this.currentFragment.implicitBaseDataOffset = trackData.currentOffset;
|
|
2330
|
+
}; break;
|
|
2331
|
+
|
|
2332
|
+
case 'saiz': {
|
|
2333
|
+
// Sample Auxiliary Information Sizes - per-sample sizes of (typically) the encryption aux info.
|
|
2334
|
+
const track = this.currentTrack;
|
|
2335
|
+
if (!track || !track.encryptionInfo) {
|
|
2336
|
+
break;
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
slice.skip(1); // Version
|
|
2340
|
+
const flags = readU24Be(slice);
|
|
2341
|
+
|
|
2342
|
+
if (flags & 0x01) {
|
|
2343
|
+
const auxInfoType = readAscii(slice, 4);
|
|
2344
|
+
const auxInfoTypeParam = readU32Be(slice);
|
|
2345
|
+
if (auxInfoType !== track.encryptionInfo.scheme || auxInfoTypeParam !== 0) {
|
|
2346
|
+
// Not the encryption aux info
|
|
2347
|
+
break;
|
|
2348
|
+
}
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
const defaultSampleInfoSize = readU8(slice);
|
|
2352
|
+
const sampleCount = readU32Be(slice);
|
|
2353
|
+
|
|
2354
|
+
let sampleSizes: Uint8Array | null = null;
|
|
2355
|
+
if (defaultSampleInfoSize === 0 && sampleCount > 0) {
|
|
2356
|
+
sampleSizes = readBytes(slice, sampleCount);
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
const aux = getOrCreateEncryptionAuxInfo(track);
|
|
2360
|
+
aux.defaultSampleInfoSize = defaultSampleInfoSize;
|
|
2361
|
+
aux.sampleSizes = sampleSizes;
|
|
2362
|
+
aux.sampleCount = sampleCount;
|
|
2363
|
+
}; break;
|
|
2364
|
+
|
|
2365
|
+
case 'saio': {
|
|
2366
|
+
// Sample Auxiliary Information Offsets - file offset(s) where the aux info lives.
|
|
2367
|
+
const track = this.currentTrack;
|
|
2368
|
+
if (!track || !track.encryptionInfo) {
|
|
2369
|
+
break;
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
const version = readU8(slice);
|
|
2373
|
+
const flags = readU24Be(slice);
|
|
2374
|
+
|
|
2375
|
+
if (flags & 0x01) {
|
|
2376
|
+
const auxInfoType = readAscii(slice, 4);
|
|
2377
|
+
const auxInfoTypeParam = readU32Be(slice);
|
|
2378
|
+
if (auxInfoType !== track.encryptionInfo.scheme || auxInfoTypeParam !== 0) {
|
|
2379
|
+
break;
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
|
|
2383
|
+
const entryCount = readU32Be(slice);
|
|
2384
|
+
if (entryCount === 0) {
|
|
2385
|
+
break;
|
|
2386
|
+
}
|
|
2387
|
+
if (entryCount > 1) {
|
|
2388
|
+
Logging._warn('Multiple saio entries are not supported; using the first offset only.');
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
let offset = version === 0 ? readU32Be(slice) : Number(readU64Be(slice));
|
|
2392
|
+
|
|
2393
|
+
// Per ISO/IEC 23001-7: when saio is inside a moof, offsets are relative to the start of the moof box.
|
|
2394
|
+
if (this.currentFragment) {
|
|
2395
|
+
offset += this.currentFragment.moofOffset;
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
const aux = getOrCreateEncryptionAuxInfo(track);
|
|
2399
|
+
aux.offset = offset;
|
|
2400
|
+
}; break;
|
|
2401
|
+
|
|
2402
|
+
case 'senc': {
|
|
2403
|
+
// Per-sample encryption info inside a 'traf'. Holds per-sample IV and optional subsample breakdown
|
|
2404
|
+
// for CENC-protected samples
|
|
2405
|
+
const track = this.currentTrack;
|
|
2406
|
+
if (!track || !track.encryptionInfo) {
|
|
2407
|
+
break;
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
assert(this.currentFragment);
|
|
2411
|
+
const trackData = this.currentFragment.trackData.get(track.id);
|
|
2412
|
+
if (!trackData) {
|
|
2413
|
+
break;
|
|
2414
|
+
}
|
|
2415
|
+
|
|
2416
|
+
slice.skip(1); // Version
|
|
2417
|
+
const flags = readU24Be(slice);
|
|
2418
|
+
const useSubsamples = Boolean(flags & 0x000002);
|
|
2419
|
+
|
|
2420
|
+
const sampleCount = readU32Be(slice);
|
|
2421
|
+
const ivSize = track.encryptionInfo.defaultPerSampleIvSize;
|
|
2422
|
+
assert(ivSize !== null);
|
|
2423
|
+
|
|
2424
|
+
for (let i = 0; i < Math.min(sampleCount, trackData.samples.length); i++) {
|
|
2425
|
+
// Normalize the IV to 16 bytes so downstream code can assume a full-length buffer. For CTR with
|
|
2426
|
+
// an 8-byte per-sample IV the lower 8 bytes are zero (that's the CENC spec's block counter start);
|
|
2427
|
+
// for CBC/cbcs the IV is always 16 bytes by spec.
|
|
2428
|
+
const iv = new Uint8Array(16);
|
|
2429
|
+
if (ivSize > 0) {
|
|
2430
|
+
iv.set(readBytes(slice, ivSize), 0);
|
|
2431
|
+
} else {
|
|
2432
|
+
iv.set(track.encryptionInfo.defaultConstantIv!, 0);
|
|
2433
|
+
}
|
|
2434
|
+
|
|
2435
|
+
let subsamples: SampleEncryptionInfo['subsamples'] = null;
|
|
2436
|
+
if (useSubsamples) {
|
|
2437
|
+
const subsampleCount = readU16Be(slice);
|
|
2438
|
+
subsamples = [];
|
|
2439
|
+
for (let j = 0; j < subsampleCount; j++) {
|
|
2440
|
+
const clearLen = readU16Be(slice);
|
|
2441
|
+
const protectedLen = readU32Be(slice);
|
|
2442
|
+
subsamples.push({ clearLen, protectedLen });
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
const sample = trackData.samples[i]!;
|
|
2447
|
+
sample.encryption = { iv, subsamples };
|
|
2448
|
+
}
|
|
2449
|
+
}; break;
|
|
2450
|
+
|
|
2451
|
+
// Metadata section
|
|
2452
|
+
// https://exiftool.org/TagNames/QuickTime.html
|
|
2453
|
+
// https://mp4workshop.com/about
|
|
2454
|
+
|
|
2455
|
+
case 'udta': { // Contains either movie metadata or track metadata
|
|
2456
|
+
const iterator = this.iterateContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
|
|
2457
|
+
|
|
2458
|
+
for (const { boxInfo, slice } of iterator) {
|
|
2459
|
+
if (boxInfo.name !== 'meta' && !this.currentTrack) {
|
|
2460
|
+
const startPos = slice.filePos;
|
|
2461
|
+
this.metadataTags.raw ??= {};
|
|
2462
|
+
|
|
2463
|
+
if (boxInfo.name[0] === '©') {
|
|
2464
|
+
// https://mp4workshop.com/about
|
|
2465
|
+
// Box name starting with © indicates "international text"
|
|
2466
|
+
this.metadataTags.raw[boxInfo.name] ??= readMetadataStringShort(slice);
|
|
2467
|
+
} else {
|
|
2468
|
+
this.metadataTags.raw[boxInfo.name] ??= readBytes(slice, boxInfo.contentSize);
|
|
2469
|
+
}
|
|
2470
|
+
|
|
2471
|
+
slice.filePos = startPos;
|
|
2472
|
+
}
|
|
2473
|
+
|
|
2474
|
+
switch (boxInfo.name) {
|
|
2475
|
+
case 'meta': {
|
|
2476
|
+
slice.skip(-boxInfo.headerSize);
|
|
2477
|
+
this.traverseBox(slice);
|
|
2478
|
+
}; break;
|
|
2479
|
+
|
|
2480
|
+
case '©nam':
|
|
2481
|
+
case 'name': {
|
|
2482
|
+
if (this.currentTrack) {
|
|
2483
|
+
this.currentTrack.name = textDecoder.decode(readBytes(slice, boxInfo.contentSize));
|
|
2484
|
+
} else {
|
|
2485
|
+
this.metadataTags.title ??= readMetadataStringShort(slice);
|
|
2486
|
+
}
|
|
2487
|
+
}; break;
|
|
2488
|
+
|
|
2489
|
+
case '©des': {
|
|
2490
|
+
if (!this.currentTrack) {
|
|
2491
|
+
this.metadataTags.description ??= readMetadataStringShort(slice);
|
|
2492
|
+
}
|
|
2493
|
+
}; break;
|
|
2494
|
+
|
|
2495
|
+
case '©ART': {
|
|
2496
|
+
if (!this.currentTrack) {
|
|
2497
|
+
this.metadataTags.artist ??= readMetadataStringShort(slice);
|
|
2498
|
+
}
|
|
2499
|
+
}; break;
|
|
2500
|
+
|
|
2501
|
+
case '©alb': {
|
|
2502
|
+
if (!this.currentTrack) {
|
|
2503
|
+
this.metadataTags.album ??= readMetadataStringShort(slice);
|
|
2504
|
+
}
|
|
2505
|
+
}; break;
|
|
2506
|
+
|
|
2507
|
+
case 'albr': {
|
|
2508
|
+
if (!this.currentTrack) {
|
|
2509
|
+
this.metadataTags.albumArtist ??= readMetadataStringShort(slice);
|
|
2510
|
+
}
|
|
2511
|
+
}; break;
|
|
2512
|
+
|
|
2513
|
+
case '©gen': {
|
|
2514
|
+
if (!this.currentTrack) {
|
|
2515
|
+
this.metadataTags.genre ??= readMetadataStringShort(slice);
|
|
2516
|
+
}
|
|
2517
|
+
}; break;
|
|
2518
|
+
|
|
2519
|
+
case '©day': {
|
|
2520
|
+
if (!this.currentTrack) {
|
|
2521
|
+
const date = new Date(readMetadataStringShort(slice));
|
|
2522
|
+
if (!Number.isNaN(date.getTime())) {
|
|
2523
|
+
this.metadataTags.date ??= date;
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
}; break;
|
|
2527
|
+
|
|
2528
|
+
case '©cmt': {
|
|
2529
|
+
if (!this.currentTrack) {
|
|
2530
|
+
this.metadataTags.comment ??= readMetadataStringShort(slice);
|
|
2531
|
+
}
|
|
2532
|
+
}; break;
|
|
2533
|
+
|
|
2534
|
+
case '©lyr': {
|
|
2535
|
+
if (!this.currentTrack) {
|
|
2536
|
+
this.metadataTags.lyrics ??= readMetadataStringShort(slice);
|
|
2537
|
+
}
|
|
2538
|
+
}; break;
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
}; break;
|
|
2542
|
+
|
|
2543
|
+
case 'meta': {
|
|
2544
|
+
if (this.currentTrack) {
|
|
2545
|
+
break; // Only care about movie-level metadata for now
|
|
2546
|
+
}
|
|
2547
|
+
|
|
2548
|
+
// The 'meta' box comes in two flavors, one with flags/version and one without. To know which is which,
|
|
2549
|
+
// let's read the next 4 bytes, which are either the version or the size of the first subbox.
|
|
2550
|
+
const word = readU32Be(slice);
|
|
2551
|
+
const isQuickTime = word !== 0;
|
|
2552
|
+
|
|
2553
|
+
this.currentMetadataKeys = new Map();
|
|
2554
|
+
|
|
2555
|
+
if (isQuickTime) {
|
|
2556
|
+
this.readContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
|
|
2557
|
+
} else {
|
|
2558
|
+
this.readContiguousBoxes(slice.slice(contentStartPos + 4, boxInfo.contentSize - 4));
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
this.currentMetadataKeys = null;
|
|
2562
|
+
}; break;
|
|
2563
|
+
|
|
2564
|
+
case 'keys': {
|
|
2565
|
+
if (!this.currentMetadataKeys) {
|
|
2566
|
+
break;
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
slice.skip(4); // Version + flags
|
|
2570
|
+
|
|
2571
|
+
const entryCount = readU32Be(slice);
|
|
2572
|
+
|
|
2573
|
+
for (let i = 0; i < entryCount; i++) {
|
|
2574
|
+
const keySize = readU32Be(slice);
|
|
2575
|
+
slice.skip(4); // Key namespace
|
|
2576
|
+
const keyName = textDecoder.decode(readBytes(slice, keySize - 8));
|
|
2577
|
+
|
|
2578
|
+
this.currentMetadataKeys.set(i + 1, keyName);
|
|
2579
|
+
}
|
|
2580
|
+
}; break;
|
|
2581
|
+
|
|
2582
|
+
case 'ilst': {
|
|
2583
|
+
if (!this.currentMetadataKeys) {
|
|
2584
|
+
break;
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
const iterator = this.iterateContiguousBoxes(slice.slice(contentStartPos, boxInfo.contentSize));
|
|
2588
|
+
|
|
2589
|
+
for (const { boxInfo, slice } of iterator) {
|
|
2590
|
+
let metadataKey = boxInfo.name;
|
|
2591
|
+
|
|
2592
|
+
// Interpret the box name as a u32be
|
|
2593
|
+
const nameAsNumber = (metadataKey.charCodeAt(0) << 24)
|
|
2594
|
+
+ (metadataKey.charCodeAt(1) << 16)
|
|
2595
|
+
+ (metadataKey.charCodeAt(2) << 8)
|
|
2596
|
+
+ metadataKey.charCodeAt(3);
|
|
2597
|
+
|
|
2598
|
+
if (this.currentMetadataKeys.has(nameAsNumber)) {
|
|
2599
|
+
// An entry exists for this number
|
|
2600
|
+
metadataKey = this.currentMetadataKeys.get(nameAsNumber)!;
|
|
2601
|
+
}
|
|
2602
|
+
|
|
2603
|
+
const data = readDataBox(slice);
|
|
2604
|
+
|
|
2605
|
+
this.metadataTags.raw ??= {};
|
|
2606
|
+
this.metadataTags.raw[metadataKey] ??= data;
|
|
2607
|
+
|
|
2608
|
+
switch (metadataKey) {
|
|
2609
|
+
case '©nam':
|
|
2610
|
+
case 'titl':
|
|
2611
|
+
case 'com.apple.quicktime.title':
|
|
2612
|
+
case 'title': {
|
|
2613
|
+
if (typeof data === 'string') {
|
|
2614
|
+
this.metadataTags.title ??= data;
|
|
2615
|
+
}
|
|
2616
|
+
}; break;
|
|
2617
|
+
|
|
2618
|
+
case '©des':
|
|
2619
|
+
case 'desc':
|
|
2620
|
+
case 'dscp':
|
|
2621
|
+
case 'com.apple.quicktime.description':
|
|
2622
|
+
case 'description': {
|
|
2623
|
+
if (typeof data === 'string') {
|
|
2624
|
+
this.metadataTags.description ??= data;
|
|
2625
|
+
}
|
|
2626
|
+
}; break;
|
|
2627
|
+
|
|
2628
|
+
case '©ART':
|
|
2629
|
+
case 'com.apple.quicktime.artist':
|
|
2630
|
+
case 'artist': {
|
|
2631
|
+
if (typeof data === 'string') {
|
|
2632
|
+
this.metadataTags.artist ??= data;
|
|
2633
|
+
}
|
|
2634
|
+
}; break;
|
|
2635
|
+
|
|
2636
|
+
case '©alb':
|
|
2637
|
+
case 'albm':
|
|
2638
|
+
case 'com.apple.quicktime.album':
|
|
2639
|
+
case 'album': {
|
|
2640
|
+
if (typeof data === 'string') {
|
|
2641
|
+
this.metadataTags.album ??= data;
|
|
2642
|
+
}
|
|
2643
|
+
}; break;
|
|
2644
|
+
|
|
2645
|
+
case 'aART':
|
|
2646
|
+
case 'album_artist': {
|
|
2647
|
+
if (typeof data === 'string') {
|
|
2648
|
+
this.metadataTags.albumArtist ??= data;
|
|
2649
|
+
}
|
|
2650
|
+
}; break;
|
|
2651
|
+
|
|
2652
|
+
case '©cmt':
|
|
2653
|
+
case 'com.apple.quicktime.comment':
|
|
2654
|
+
case 'comment': {
|
|
2655
|
+
if (typeof data === 'string') {
|
|
2656
|
+
this.metadataTags.comment ??= data;
|
|
2657
|
+
}
|
|
2658
|
+
}; break;
|
|
2659
|
+
|
|
2660
|
+
case '©gen':
|
|
2661
|
+
case 'gnre':
|
|
2662
|
+
case 'com.apple.quicktime.genre':
|
|
2663
|
+
case 'genre': {
|
|
2664
|
+
if (typeof data === 'string') {
|
|
2665
|
+
this.metadataTags.genre ??= data;
|
|
2666
|
+
}
|
|
2667
|
+
}; break;
|
|
2668
|
+
|
|
2669
|
+
case '©lyr':
|
|
2670
|
+
case 'lyrics': {
|
|
2671
|
+
if (typeof data === 'string') {
|
|
2672
|
+
this.metadataTags.lyrics ??= data;
|
|
2673
|
+
}
|
|
2674
|
+
}; break;
|
|
2675
|
+
|
|
2676
|
+
case '©day':
|
|
2677
|
+
case 'rldt':
|
|
2678
|
+
case 'com.apple.quicktime.creationdate':
|
|
2679
|
+
case 'date': {
|
|
2680
|
+
if (typeof data === 'string') {
|
|
2681
|
+
const date = new Date(data);
|
|
2682
|
+
if (!Number.isNaN(date.getTime())) {
|
|
2683
|
+
this.metadataTags.date ??= date;
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
}; break;
|
|
2687
|
+
|
|
2688
|
+
case 'covr':
|
|
2689
|
+
case 'com.apple.quicktime.artwork': {
|
|
2690
|
+
if (data instanceof RichImageData) {
|
|
2691
|
+
this.metadataTags.images ??= [];
|
|
2692
|
+
this.metadataTags.images.push({
|
|
2693
|
+
data: data.data,
|
|
2694
|
+
kind: 'coverFront',
|
|
2695
|
+
mimeType: data.mimeType,
|
|
2696
|
+
});
|
|
2697
|
+
} else if (data instanceof Uint8Array) {
|
|
2698
|
+
this.metadataTags.images ??= [];
|
|
2699
|
+
this.metadataTags.images.push({
|
|
2700
|
+
data,
|
|
2701
|
+
kind: 'coverFront',
|
|
2702
|
+
mimeType: 'image/*',
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
2705
|
+
}; break;
|
|
2706
|
+
|
|
2707
|
+
case 'track': {
|
|
2708
|
+
if (typeof data === 'string') {
|
|
2709
|
+
const parts = data.split('/');
|
|
2710
|
+
const trackNum = Number.parseInt(parts[0]!, 10);
|
|
2711
|
+
const tracksTotal = parts[1] && Number.parseInt(parts[1], 10);
|
|
2712
|
+
|
|
2713
|
+
if (Number.isInteger(trackNum) && trackNum > 0) {
|
|
2714
|
+
this.metadataTags.trackNumber ??= trackNum;
|
|
2715
|
+
}
|
|
2716
|
+
if (tracksTotal && Number.isInteger(tracksTotal) && tracksTotal > 0) {
|
|
2717
|
+
this.metadataTags.tracksTotal ??= tracksTotal;
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
}; break;
|
|
2721
|
+
|
|
2722
|
+
case 'trkn': {
|
|
2723
|
+
if (data instanceof Uint8Array && data.length >= 6) {
|
|
2724
|
+
const view = toDataView(data);
|
|
2725
|
+
|
|
2726
|
+
const trackNumber = view.getUint16(2, false);
|
|
2727
|
+
const tracksTotal = view.getUint16(4, false);
|
|
2728
|
+
|
|
2729
|
+
if (trackNumber > 0) {
|
|
2730
|
+
this.metadataTags.trackNumber ??= trackNumber;
|
|
2731
|
+
}
|
|
2732
|
+
if (tracksTotal > 0) {
|
|
2733
|
+
this.metadataTags.tracksTotal ??= tracksTotal;
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
}; break;
|
|
2737
|
+
|
|
2738
|
+
case 'disc':
|
|
2739
|
+
case 'disk': {
|
|
2740
|
+
if (data instanceof Uint8Array && data.length >= 6) {
|
|
2741
|
+
const view = toDataView(data);
|
|
2742
|
+
|
|
2743
|
+
const discNumber = view.getUint16(2, false);
|
|
2744
|
+
const discNumberMax = view.getUint16(4, false);
|
|
2745
|
+
|
|
2746
|
+
if (discNumber > 0) {
|
|
2747
|
+
this.metadataTags.discNumber ??= discNumber;
|
|
2748
|
+
}
|
|
2749
|
+
if (discNumberMax > 0) {
|
|
2750
|
+
this.metadataTags.discsTotal ??= discNumberMax;
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
}; break;
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
}; break;
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
slice.filePos = boxEndPos;
|
|
2760
|
+
return true;
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
|
|
2764
|
+
abstract class IsobmffTrackBacking implements InputTrackBacking {
|
|
2765
|
+
packetToSampleIndex = new WeakMap<EncodedPacket, number>();
|
|
2766
|
+
packetToFragmentLocation = new WeakMap<EncodedPacket, {
|
|
2767
|
+
fragment: Fragment;
|
|
2768
|
+
sampleIndex: number;
|
|
2769
|
+
}>();
|
|
2770
|
+
|
|
2771
|
+
constructor(public internalTrack: InternalTrack) {}
|
|
2772
|
+
|
|
2773
|
+
abstract getType(): TrackType;
|
|
2774
|
+
abstract getDecoderConfig(): Promise<VideoDecoderConfig | AudioDecoderConfig | null>;
|
|
2775
|
+
|
|
2776
|
+
getId() {
|
|
2777
|
+
return this.internalTrack.id;
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
getNumber() {
|
|
2781
|
+
const demuxer = this.internalTrack.demuxer;
|
|
2782
|
+
const trackType = this.internalTrack.trackBacking!.getType();
|
|
2783
|
+
|
|
2784
|
+
let number = 0;
|
|
2785
|
+
for (const track of demuxer.tracks) {
|
|
2786
|
+
if (track.trackBacking!.getType() === trackType) {
|
|
2787
|
+
number++;
|
|
2788
|
+
}
|
|
2789
|
+
|
|
2790
|
+
if (track === this.internalTrack) {
|
|
2791
|
+
break;
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
return number;
|
|
2796
|
+
}
|
|
2797
|
+
|
|
2798
|
+
getCodec(): MediaCodec | null {
|
|
2799
|
+
throw new Error('Not implemented on base class.');
|
|
2800
|
+
}
|
|
2801
|
+
|
|
2802
|
+
getInternalCodecId() {
|
|
2803
|
+
return this.internalTrack.internalCodecId;
|
|
2804
|
+
}
|
|
2805
|
+
|
|
2806
|
+
getName() {
|
|
2807
|
+
return this.internalTrack.name;
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
getLanguageCode() {
|
|
2811
|
+
return this.internalTrack.languageCode;
|
|
2812
|
+
}
|
|
2813
|
+
|
|
2814
|
+
getTimeResolution() {
|
|
2815
|
+
return this.internalTrack.timescale;
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2818
|
+
isRelativeToUnixEpoch() {
|
|
2819
|
+
return false;
|
|
2820
|
+
}
|
|
2821
|
+
|
|
2822
|
+
getUnixTimeForTimestamp() {
|
|
2823
|
+
return null;
|
|
2824
|
+
}
|
|
2825
|
+
|
|
2826
|
+
getDisposition() {
|
|
2827
|
+
return this.internalTrack.disposition;
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
getPairingMask() {
|
|
2831
|
+
return 1n;
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
getBitrate() {
|
|
2835
|
+
return null;
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
getAverageBitrate() {
|
|
2839
|
+
return null;
|
|
2840
|
+
}
|
|
2841
|
+
|
|
2842
|
+
async getDurationFromMetadata() {
|
|
2843
|
+
const track = this.internalTrack;
|
|
2844
|
+
if (track.durationInMediaTimescale <= 0) {
|
|
2845
|
+
// The duration is often zero for fragmented files for example; return `null` to signal that the duration
|
|
2846
|
+
// must be computed instead.
|
|
2847
|
+
return null;
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
assert(track.trackBacking);
|
|
2851
|
+
|
|
2852
|
+
const firstPacket = await track.trackBacking.getFirstPacket({ metadataOnly: true });
|
|
2853
|
+
return (firstPacket?.timestamp ?? 0) + track.durationInMediaTimescale / track.timescale;
|
|
2854
|
+
}
|
|
2855
|
+
|
|
2856
|
+
async getLiveRefreshInterval() {
|
|
2857
|
+
return null;
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
async getFirstPacket(options: PacketRetrievalOptions) {
|
|
2861
|
+
const regularPacket = await this.fetchPacketForSampleIndex(0, options);
|
|
2862
|
+
if (regularPacket || !this.internalTrack.demuxer.isFragmented) {
|
|
2863
|
+
// If there's a non-fragmented packet, always prefer that
|
|
2864
|
+
return regularPacket;
|
|
2865
|
+
}
|
|
2866
|
+
|
|
2867
|
+
return this.performFragmentedLookup(
|
|
2868
|
+
null,
|
|
2869
|
+
(fragment) => {
|
|
2870
|
+
const trackData = fragment.trackData.get(this.internalTrack.id);
|
|
2871
|
+
if (trackData) {
|
|
2872
|
+
return {
|
|
2873
|
+
sampleIndex: 0,
|
|
2874
|
+
correctSampleFound: true,
|
|
2875
|
+
};
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2878
|
+
return {
|
|
2879
|
+
sampleIndex: -1,
|
|
2880
|
+
correctSampleFound: false,
|
|
2881
|
+
};
|
|
2882
|
+
},
|
|
2883
|
+
-Infinity, // Use -Infinity as a search timestamp to avoid using the lookup entries
|
|
2884
|
+
Infinity,
|
|
2885
|
+
options,
|
|
2886
|
+
);
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
private mapTimestampIntoTimescale(timestamp: number) {
|
|
2890
|
+
// Do a little rounding to catch cases where the result is very close to an integer. If it is, it's likely
|
|
2891
|
+
// that the number was originally an integer divided by the timescale. For stability, it's best
|
|
2892
|
+
// to return the integer in this case.
|
|
2893
|
+
return roundIfAlmostInteger(timestamp * this.internalTrack.timescale) + this.internalTrack.editListOffset;
|
|
2894
|
+
}
|
|
2895
|
+
|
|
2896
|
+
async getPacket(timestamp: number, options: PacketRetrievalOptions) {
|
|
2897
|
+
const timestampInTimescale = this.mapTimestampIntoTimescale(timestamp);
|
|
2898
|
+
|
|
2899
|
+
const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
|
|
2900
|
+
const sampleIndex = getSampleIndexForTimestamp(sampleTable, timestampInTimescale);
|
|
2901
|
+
const regularPacket = await this.fetchPacketForSampleIndex(sampleIndex, options);
|
|
2902
|
+
|
|
2903
|
+
if (!sampleTableIsEmpty(sampleTable) || !this.internalTrack.demuxer.isFragmented) {
|
|
2904
|
+
// Prefer the non-fragmented packet
|
|
2905
|
+
return regularPacket;
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
return this.performFragmentedLookup(
|
|
2909
|
+
null,
|
|
2910
|
+
(fragment) => {
|
|
2911
|
+
const trackData = fragment.trackData.get(this.internalTrack.id);
|
|
2912
|
+
if (!trackData) {
|
|
2913
|
+
return { sampleIndex: -1, correctSampleFound: false };
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2916
|
+
const index = binarySearchLessOrEqual(
|
|
2917
|
+
trackData.presentationTimestamps,
|
|
2918
|
+
timestampInTimescale,
|
|
2919
|
+
x => x.presentationTimestamp,
|
|
2920
|
+
);
|
|
2921
|
+
|
|
2922
|
+
const sampleIndex = index !== -1 ? trackData.presentationTimestamps[index]!.sampleIndex : -1;
|
|
2923
|
+
const correctSampleFound = index !== -1 && timestampInTimescale < trackData.endTimestamp;
|
|
2924
|
+
|
|
2925
|
+
return { sampleIndex, correctSampleFound };
|
|
2926
|
+
},
|
|
2927
|
+
timestampInTimescale,
|
|
2928
|
+
timestampInTimescale,
|
|
2929
|
+
options,
|
|
2930
|
+
);
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
async getNextPacket(packet: EncodedPacket, options: PacketRetrievalOptions) {
|
|
2934
|
+
const regularSampleIndex = this.packetToSampleIndex.get(packet);
|
|
2935
|
+
|
|
2936
|
+
if (regularSampleIndex !== undefined) {
|
|
2937
|
+
// Prefer the non-fragmented packet
|
|
2938
|
+
return this.fetchPacketForSampleIndex(regularSampleIndex + 1, options);
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
const locationInFragment = this.packetToFragmentLocation.get(packet);
|
|
2942
|
+
if (locationInFragment === undefined) {
|
|
2943
|
+
throw new Error('Packet was not created from this track.');
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2946
|
+
return this.performFragmentedLookup(
|
|
2947
|
+
locationInFragment.fragment,
|
|
2948
|
+
(fragment) => {
|
|
2949
|
+
if (fragment === locationInFragment.fragment) {
|
|
2950
|
+
const trackData = fragment.trackData.get(this.internalTrack.id)!;
|
|
2951
|
+
if (locationInFragment.sampleIndex + 1 < trackData.samples.length) {
|
|
2952
|
+
// We can simply take the next sample in the fragment
|
|
2953
|
+
return {
|
|
2954
|
+
sampleIndex: locationInFragment.sampleIndex + 1,
|
|
2955
|
+
correctSampleFound: true,
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
} else {
|
|
2959
|
+
const trackData = fragment.trackData.get(this.internalTrack.id);
|
|
2960
|
+
if (trackData) {
|
|
2961
|
+
return {
|
|
2962
|
+
sampleIndex: 0,
|
|
2963
|
+
correctSampleFound: true,
|
|
2964
|
+
};
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
return {
|
|
2969
|
+
sampleIndex: -1,
|
|
2970
|
+
correctSampleFound: false,
|
|
2971
|
+
};
|
|
2972
|
+
},
|
|
2973
|
+
-Infinity, // Use -Infinity as a search timestamp to avoid using the lookup entries
|
|
2974
|
+
Infinity,
|
|
2975
|
+
options,
|
|
2976
|
+
);
|
|
2977
|
+
}
|
|
2978
|
+
|
|
2979
|
+
async getKeyPacket(timestamp: number, options: PacketRetrievalOptions) {
|
|
2980
|
+
const timestampInTimescale = this.mapTimestampIntoTimescale(timestamp);
|
|
2981
|
+
|
|
2982
|
+
const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
|
|
2983
|
+
const sampleIndex = getKeyframeSampleIndexForTimestamp(sampleTable, timestampInTimescale);
|
|
2984
|
+
const regularPacket = await this.fetchPacketForSampleIndex(sampleIndex, options);
|
|
2985
|
+
|
|
2986
|
+
if (!sampleTableIsEmpty(sampleTable) || !this.internalTrack.demuxer.isFragmented) {
|
|
2987
|
+
// Prefer the non-fragmented packet
|
|
2988
|
+
return regularPacket;
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
return this.performFragmentedLookup(
|
|
2992
|
+
null,
|
|
2993
|
+
(fragment) => {
|
|
2994
|
+
const trackData = fragment.trackData.get(this.internalTrack.id);
|
|
2995
|
+
if (!trackData) {
|
|
2996
|
+
return { sampleIndex: -1, correctSampleFound: false };
|
|
2997
|
+
}
|
|
2998
|
+
|
|
2999
|
+
const index = findLastIndex(trackData.presentationTimestamps, (x) => {
|
|
3000
|
+
const sample = trackData.samples[x.sampleIndex]!;
|
|
3001
|
+
return sample.isKeyFrame && x.presentationTimestamp <= timestampInTimescale;
|
|
3002
|
+
});
|
|
3003
|
+
|
|
3004
|
+
const sampleIndex = index !== -1 ? trackData.presentationTimestamps[index]!.sampleIndex : -1;
|
|
3005
|
+
const correctSampleFound = index !== -1 && timestampInTimescale < trackData.endTimestamp;
|
|
3006
|
+
|
|
3007
|
+
return { sampleIndex, correctSampleFound };
|
|
3008
|
+
},
|
|
3009
|
+
timestampInTimescale,
|
|
3010
|
+
timestampInTimescale,
|
|
3011
|
+
options,
|
|
3012
|
+
);
|
|
3013
|
+
}
|
|
3014
|
+
|
|
3015
|
+
async getNextKeyPacket(packet: EncodedPacket, options: PacketRetrievalOptions) {
|
|
3016
|
+
const regularSampleIndex = this.packetToSampleIndex.get(packet);
|
|
3017
|
+
if (regularSampleIndex !== undefined) {
|
|
3018
|
+
// Prefer the non-fragmented packet
|
|
3019
|
+
const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
|
|
3020
|
+
const nextKeyFrameSampleIndex = getNextKeyframeIndexForSample(sampleTable, regularSampleIndex);
|
|
3021
|
+
return this.fetchPacketForSampleIndex(nextKeyFrameSampleIndex, options);
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
const locationInFragment = this.packetToFragmentLocation.get(packet);
|
|
3025
|
+
if (locationInFragment === undefined) {
|
|
3026
|
+
throw new Error('Packet was not created from this track.');
|
|
3027
|
+
}
|
|
3028
|
+
|
|
3029
|
+
return this.performFragmentedLookup(
|
|
3030
|
+
locationInFragment.fragment,
|
|
3031
|
+
(fragment) => {
|
|
3032
|
+
if (fragment === locationInFragment.fragment) {
|
|
3033
|
+
const trackData = fragment.trackData.get(this.internalTrack.id)!;
|
|
3034
|
+
const nextKeyFrameIndex = trackData.samples.findIndex(
|
|
3035
|
+
(x, i) => x.isKeyFrame && i > locationInFragment.sampleIndex,
|
|
3036
|
+
);
|
|
3037
|
+
|
|
3038
|
+
if (nextKeyFrameIndex !== -1) {
|
|
3039
|
+
// We can simply take the next key frame in the fragment
|
|
3040
|
+
return {
|
|
3041
|
+
sampleIndex: nextKeyFrameIndex,
|
|
3042
|
+
correctSampleFound: true,
|
|
3043
|
+
};
|
|
3044
|
+
}
|
|
3045
|
+
} else {
|
|
3046
|
+
const trackData = fragment.trackData.get(this.internalTrack.id);
|
|
3047
|
+
if (trackData && trackData.firstKeyFrameTimestamp !== null) {
|
|
3048
|
+
const keyFrameIndex = trackData.samples.findIndex(x => x.isKeyFrame);
|
|
3049
|
+
assert(keyFrameIndex !== -1); // There must be one
|
|
3050
|
+
|
|
3051
|
+
return {
|
|
3052
|
+
sampleIndex: keyFrameIndex,
|
|
3053
|
+
correctSampleFound: true,
|
|
3054
|
+
};
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
return {
|
|
3059
|
+
sampleIndex: -1,
|
|
3060
|
+
correctSampleFound: false,
|
|
3061
|
+
};
|
|
3062
|
+
},
|
|
3063
|
+
-Infinity, // Use -Infinity as a search timestamp to avoid using the lookup entries
|
|
3064
|
+
Infinity,
|
|
3065
|
+
options,
|
|
3066
|
+
);
|
|
3067
|
+
}
|
|
3068
|
+
|
|
3069
|
+
private async fetchPacketForSampleIndex(sampleIndex: number, options: PacketRetrievalOptions) {
|
|
3070
|
+
if (sampleIndex === -1) {
|
|
3071
|
+
return null;
|
|
3072
|
+
}
|
|
3073
|
+
|
|
3074
|
+
const sampleTable = this.internalTrack.demuxer.getSampleTableForTrack(this.internalTrack);
|
|
3075
|
+
const sampleInfo = getSampleInfo(sampleTable, sampleIndex);
|
|
3076
|
+
if (!sampleInfo) {
|
|
3077
|
+
return null;
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
let data: Uint8Array;
|
|
3081
|
+
if (options.metadataOnly) {
|
|
3082
|
+
data = PLACEHOLDER_DATA;
|
|
3083
|
+
} else {
|
|
3084
|
+
let slice = this.internalTrack.demuxer.reader.requestSlice(
|
|
3085
|
+
sampleInfo.sampleOffset,
|
|
3086
|
+
sampleInfo.sampleSize,
|
|
3087
|
+
);
|
|
3088
|
+
if (slice instanceof Promise) slice = await slice;
|
|
3089
|
+
if (!slice) {
|
|
3090
|
+
return null; // Data is outside
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
data = readBytes(slice, sampleInfo.sampleSize);
|
|
3094
|
+
|
|
3095
|
+
if (this.internalTrack.encryptionAuxInfo) {
|
|
3096
|
+
assert(this.internalTrack.encryptionInfo);
|
|
3097
|
+
|
|
3098
|
+
const entries = await resolveEncryptionAuxInfo(
|
|
3099
|
+
this.internalTrack.demuxer.reader,
|
|
3100
|
+
this.internalTrack.encryptionInfo,
|
|
3101
|
+
this.internalTrack.encryptionAuxInfo,
|
|
3102
|
+
);
|
|
3103
|
+
|
|
3104
|
+
if (sampleIndex < entries.length) {
|
|
3105
|
+
data = await decryptSample(this.internalTrack, entries[sampleIndex]!, data, null);
|
|
3106
|
+
}
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
const timestamp = (sampleInfo.presentationTimestamp - this.internalTrack.editListOffset)
|
|
3111
|
+
/ this.internalTrack.timescale;
|
|
3112
|
+
const duration = sampleInfo.duration / this.internalTrack.timescale;
|
|
3113
|
+
const packet = new EncodedPacket(
|
|
3114
|
+
data,
|
|
3115
|
+
sampleInfo.isKeyFrame ? 'key' : 'delta',
|
|
3116
|
+
timestamp,
|
|
3117
|
+
duration,
|
|
3118
|
+
sampleIndex,
|
|
3119
|
+
sampleInfo.sampleSize,
|
|
3120
|
+
);
|
|
3121
|
+
|
|
3122
|
+
this.packetToSampleIndex.set(packet, sampleIndex);
|
|
3123
|
+
|
|
3124
|
+
return packet;
|
|
3125
|
+
}
|
|
3126
|
+
|
|
3127
|
+
private async fetchPacketInFragment(fragment: Fragment, sampleIndex: number, options: PacketRetrievalOptions) {
|
|
3128
|
+
if (sampleIndex === -1) {
|
|
3129
|
+
return null;
|
|
3130
|
+
}
|
|
3131
|
+
|
|
3132
|
+
const trackData = fragment.trackData.get(this.internalTrack.id)!;
|
|
3133
|
+
const fragmentSample = trackData.samples[sampleIndex];
|
|
3134
|
+
assert(fragmentSample);
|
|
3135
|
+
|
|
3136
|
+
let data: Uint8Array;
|
|
3137
|
+
if (options.metadataOnly) {
|
|
3138
|
+
data = PLACEHOLDER_DATA;
|
|
3139
|
+
} else {
|
|
3140
|
+
let slice = this.internalTrack.demuxer.reader.requestSlice(
|
|
3141
|
+
fragmentSample.byteOffset,
|
|
3142
|
+
fragmentSample.byteSize,
|
|
3143
|
+
);
|
|
3144
|
+
if (slice instanceof Promise) slice = await slice;
|
|
3145
|
+
if (!slice) {
|
|
3146
|
+
return null; // Data is outside
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
data = readBytes(slice, fragmentSample.byteSize);
|
|
3150
|
+
|
|
3151
|
+
if (fragmentSample.encryption) {
|
|
3152
|
+
data = await decryptSample(this.internalTrack, fragmentSample.encryption, data, fragment);
|
|
3153
|
+
}
|
|
3154
|
+
}
|
|
3155
|
+
|
|
3156
|
+
const timestamp = (fragmentSample.presentationTimestamp - this.internalTrack.editListOffset)
|
|
3157
|
+
/ this.internalTrack.timescale;
|
|
3158
|
+
const duration = fragmentSample.duration / this.internalTrack.timescale;
|
|
3159
|
+
const packet = new EncodedPacket(
|
|
3160
|
+
data,
|
|
3161
|
+
fragmentSample.isKeyFrame ? 'key' : 'delta',
|
|
3162
|
+
timestamp,
|
|
3163
|
+
duration,
|
|
3164
|
+
fragment.moofOffset + sampleIndex,
|
|
3165
|
+
fragmentSample.byteSize,
|
|
3166
|
+
);
|
|
3167
|
+
|
|
3168
|
+
this.packetToFragmentLocation.set(packet, { fragment, sampleIndex });
|
|
3169
|
+
|
|
3170
|
+
return packet;
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
/** Looks for a packet in the fragments while trying to load as few fragments as possible to retrieve it. */
|
|
3174
|
+
private async performFragmentedLookup(
|
|
3175
|
+
// The fragment where we start looking
|
|
3176
|
+
startFragment: Fragment | null,
|
|
3177
|
+
// This function returns the best-matching sample in a given fragment
|
|
3178
|
+
getMatchInFragment: (fragment: Fragment) => { sampleIndex: number; correctSampleFound: boolean },
|
|
3179
|
+
// The timestamp with which we can search the lookup table
|
|
3180
|
+
searchTimestamp: number,
|
|
3181
|
+
// The timestamp for which we know the correct sample will not come after it
|
|
3182
|
+
latestTimestamp: number,
|
|
3183
|
+
options: PacketRetrievalOptions,
|
|
3184
|
+
): Promise<EncodedPacket | null> {
|
|
3185
|
+
const demuxer = this.internalTrack.demuxer;
|
|
3186
|
+
|
|
3187
|
+
let currentFragment: Fragment | null = null;
|
|
3188
|
+
let bestFragment: Fragment | null = null;
|
|
3189
|
+
let bestSampleIndex = -1;
|
|
3190
|
+
|
|
3191
|
+
if (startFragment) {
|
|
3192
|
+
const { sampleIndex, correctSampleFound } = getMatchInFragment(startFragment);
|
|
3193
|
+
|
|
3194
|
+
if (correctSampleFound) {
|
|
3195
|
+
return this.fetchPacketInFragment(startFragment, sampleIndex, options);
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
if (sampleIndex !== -1) {
|
|
3199
|
+
bestFragment = startFragment;
|
|
3200
|
+
bestSampleIndex = sampleIndex;
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
// Search for a lookup entry; this way, we won't need to start searching from the start of the file
|
|
3205
|
+
// but can jump right into the correct fragment (or at least nearby).
|
|
3206
|
+
const lookupEntryIndex = binarySearchLessOrEqual(
|
|
3207
|
+
this.internalTrack.fragmentLookupTable,
|
|
3208
|
+
searchTimestamp,
|
|
3209
|
+
x => x.timestamp,
|
|
3210
|
+
);
|
|
3211
|
+
const lookupEntry = lookupEntryIndex !== -1
|
|
3212
|
+
? this.internalTrack.fragmentLookupTable[lookupEntryIndex]!
|
|
3213
|
+
: null;
|
|
3214
|
+
|
|
3215
|
+
const positionCacheIndex = binarySearchLessOrEqual(
|
|
3216
|
+
this.internalTrack.fragmentPositionCache,
|
|
3217
|
+
searchTimestamp,
|
|
3218
|
+
x => x.startTimestamp,
|
|
3219
|
+
);
|
|
3220
|
+
const positionCacheEntry = positionCacheIndex !== -1
|
|
3221
|
+
? this.internalTrack.fragmentPositionCache[positionCacheIndex]!
|
|
3222
|
+
: null;
|
|
3223
|
+
|
|
3224
|
+
const lookupEntryPosition = Math.max(
|
|
3225
|
+
lookupEntry?.moofOffset ?? 0,
|
|
3226
|
+
positionCacheEntry?.moofOffset ?? 0,
|
|
3227
|
+
) || null;
|
|
3228
|
+
|
|
3229
|
+
let currentPos: number;
|
|
3230
|
+
|
|
3231
|
+
if (!startFragment) {
|
|
3232
|
+
currentPos = lookupEntryPosition ?? 0;
|
|
3233
|
+
} else {
|
|
3234
|
+
if (lookupEntryPosition === null || startFragment.moofOffset >= lookupEntryPosition) {
|
|
3235
|
+
currentPos = startFragment.moofOffset + startFragment.moofSize;
|
|
3236
|
+
currentFragment = startFragment;
|
|
3237
|
+
} else {
|
|
3238
|
+
// Use the lookup entry
|
|
3239
|
+
currentPos = lookupEntryPosition;
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
|
|
3243
|
+
while (true) {
|
|
3244
|
+
if (currentFragment) {
|
|
3245
|
+
const trackData = currentFragment.trackData.get(this.internalTrack.id);
|
|
3246
|
+
if (trackData && trackData.startTimestamp > latestTimestamp) {
|
|
3247
|
+
// We're already past the upper bound, no need to keep searching
|
|
3248
|
+
break;
|
|
3249
|
+
}
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
// Load the header
|
|
3253
|
+
let slice = demuxer.reader.requestSliceRange(currentPos, MIN_BOX_HEADER_SIZE, MAX_BOX_HEADER_SIZE);
|
|
3254
|
+
if (slice instanceof Promise) slice = await slice;
|
|
3255
|
+
if (!slice) break;
|
|
3256
|
+
|
|
3257
|
+
const boxStartPos = currentPos;
|
|
3258
|
+
const boxInfo = readBoxHeader(slice);
|
|
3259
|
+
if (!boxInfo) {
|
|
3260
|
+
break;
|
|
3261
|
+
}
|
|
3262
|
+
|
|
3263
|
+
if (boxInfo.name === 'moof') {
|
|
3264
|
+
currentFragment = await demuxer.readFragment(boxStartPos);
|
|
3265
|
+
const { sampleIndex, correctSampleFound } = getMatchInFragment(currentFragment);
|
|
3266
|
+
if (correctSampleFound) {
|
|
3267
|
+
return this.fetchPacketInFragment(currentFragment, sampleIndex, options);
|
|
3268
|
+
}
|
|
3269
|
+
if (sampleIndex !== -1) {
|
|
3270
|
+
bestFragment = currentFragment;
|
|
3271
|
+
bestSampleIndex = sampleIndex;
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
currentPos = boxStartPos + boxInfo.totalSize;
|
|
3276
|
+
}
|
|
3277
|
+
|
|
3278
|
+
// Catch faulty lookup table entries
|
|
3279
|
+
if (lookupEntry && (!bestFragment || bestFragment.moofOffset < lookupEntry.moofOffset)) {
|
|
3280
|
+
// The lookup table entry lied to us! We found a lookup entry but no fragment there that satisfied
|
|
3281
|
+
// the match. In this case, let's search again but using the lookup entry before that.
|
|
3282
|
+
const previousLookupEntry = this.internalTrack.fragmentLookupTable[lookupEntryIndex - 1];
|
|
3283
|
+
assert(!previousLookupEntry || previousLookupEntry.timestamp < lookupEntry.timestamp);
|
|
3284
|
+
|
|
3285
|
+
const newSearchTimestamp = previousLookupEntry?.timestamp ?? -Infinity;
|
|
3286
|
+
return this.performFragmentedLookup(
|
|
3287
|
+
null,
|
|
3288
|
+
getMatchInFragment,
|
|
3289
|
+
newSearchTimestamp,
|
|
3290
|
+
latestTimestamp,
|
|
3291
|
+
options,
|
|
3292
|
+
);
|
|
3293
|
+
}
|
|
3294
|
+
|
|
3295
|
+
if (bestFragment) {
|
|
3296
|
+
// If we finished looping but didn't find a perfect match, still return the best match we found
|
|
3297
|
+
return this.fetchPacketInFragment(bestFragment, bestSampleIndex, options);
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
return null;
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
class IsobmffVideoTrackBacking extends IsobmffTrackBacking implements InputVideoTrackBacking {
|
|
3305
|
+
override internalTrack: InternalVideoTrack;
|
|
3306
|
+
decoderConfigPromise: Promise<VideoDecoderConfig> | null = null;
|
|
3307
|
+
|
|
3308
|
+
constructor(internalTrack: InternalVideoTrack) {
|
|
3309
|
+
super(internalTrack);
|
|
3310
|
+
this.internalTrack = internalTrack;
|
|
3311
|
+
}
|
|
3312
|
+
|
|
3313
|
+
getType() {
|
|
3314
|
+
return 'video' as const;
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3317
|
+
override getCodec(): VideoCodec | null {
|
|
3318
|
+
return this.internalTrack.info.codec;
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
getCodedWidth() {
|
|
3322
|
+
return this.internalTrack.info.width;
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
getCodedHeight() {
|
|
3326
|
+
return this.internalTrack.info.height;
|
|
3327
|
+
}
|
|
3328
|
+
|
|
3329
|
+
getSquarePixelWidth() {
|
|
3330
|
+
return this.internalTrack.info.squarePixelWidth;
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3333
|
+
getSquarePixelHeight() {
|
|
3334
|
+
return this.internalTrack.info.squarePixelHeight;
|
|
3335
|
+
}
|
|
3336
|
+
|
|
3337
|
+
getRotation() {
|
|
3338
|
+
return this.internalTrack.rotation;
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
async getColorSpace(): Promise<VideoColorSpaceInit> {
|
|
3342
|
+
return {
|
|
3343
|
+
primaries: this.internalTrack.info.colorSpace?.primaries,
|
|
3344
|
+
transfer: this.internalTrack.info.colorSpace?.transfer,
|
|
3345
|
+
matrix: this.internalTrack.info.colorSpace?.matrix,
|
|
3346
|
+
fullRange: this.internalTrack.info.colorSpace?.fullRange,
|
|
3347
|
+
};
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
async canBeTransparent() {
|
|
3351
|
+
return this.internalTrack.info.codec === 'prores' && (
|
|
3352
|
+
this.internalTrack.info.proresFormat === 'ap4h'
|
|
3353
|
+
|| this.internalTrack.info.proresFormat === 'ap4x'
|
|
3354
|
+
);
|
|
3355
|
+
}
|
|
3356
|
+
|
|
3357
|
+
async getDecoderConfig(): Promise<VideoDecoderConfig | null> {
|
|
3358
|
+
if (!this.internalTrack.info.codec) {
|
|
3359
|
+
return null;
|
|
3360
|
+
}
|
|
3361
|
+
|
|
3362
|
+
return this.decoderConfigPromise ??= (async (): Promise<VideoDecoderConfig> => {
|
|
3363
|
+
if (this.internalTrack.info.codec === 'vp9' && !this.internalTrack.info.vp9CodecInfo) {
|
|
3364
|
+
const firstPacket = await this.getFirstPacket({});
|
|
3365
|
+
this.internalTrack.info.vp9CodecInfo = firstPacket && extractVp9CodecInfoFromPacket(firstPacket.data);
|
|
3366
|
+
} else if (this.internalTrack.info.codec === 'av1' && !this.internalTrack.info.av1CodecInfo) {
|
|
3367
|
+
const firstPacket = await this.getFirstPacket({});
|
|
3368
|
+
this.internalTrack.info.av1CodecInfo = firstPacket && extractAv1CodecInfoFromPacket(firstPacket.data);
|
|
3369
|
+
}
|
|
3370
|
+
|
|
3371
|
+
const config: VideoDecoderConfig = {
|
|
3372
|
+
codec: extractVideoCodecString(this.internalTrack.info),
|
|
3373
|
+
codedWidth: this.internalTrack.info.width,
|
|
3374
|
+
codedHeight: this.internalTrack.info.height,
|
|
3375
|
+
description: this.internalTrack.info.codecDescription ?? undefined,
|
|
3376
|
+
colorSpace: this.internalTrack.info.colorSpace ?? undefined,
|
|
3377
|
+
};
|
|
3378
|
+
|
|
3379
|
+
if (
|
|
3380
|
+
this.internalTrack.info.width !== this.internalTrack.info.squarePixelWidth
|
|
3381
|
+
|| this.internalTrack.info.height !== this.internalTrack.info.squarePixelHeight
|
|
3382
|
+
) {
|
|
3383
|
+
config.displayAspectWidth = this.internalTrack.info.squarePixelWidth;
|
|
3384
|
+
config.displayAspectHeight = this.internalTrack.info.squarePixelHeight;
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
return config;
|
|
3388
|
+
})();
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
|
|
3392
|
+
class IsobmffAudioTrackBacking extends IsobmffTrackBacking implements InputAudioTrackBacking {
|
|
3393
|
+
override internalTrack: InternalAudioTrack;
|
|
3394
|
+
decoderConfig: AudioDecoderConfig | null = null;
|
|
3395
|
+
|
|
3396
|
+
constructor(internalTrack: InternalAudioTrack) {
|
|
3397
|
+
super(internalTrack);
|
|
3398
|
+
this.internalTrack = internalTrack;
|
|
3399
|
+
}
|
|
3400
|
+
|
|
3401
|
+
getType() {
|
|
3402
|
+
return 'audio' as const;
|
|
3403
|
+
}
|
|
3404
|
+
|
|
3405
|
+
override getCodec(): AudioCodec | null {
|
|
3406
|
+
return this.internalTrack.info.codec;
|
|
3407
|
+
}
|
|
3408
|
+
|
|
3409
|
+
getNumberOfChannels() {
|
|
3410
|
+
return this.internalTrack.info.numberOfChannels;
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3413
|
+
getSampleRate() {
|
|
3414
|
+
return this.internalTrack.info.sampleRate;
|
|
3415
|
+
}
|
|
3416
|
+
|
|
3417
|
+
async getDecoderConfig(): Promise<AudioDecoderConfig | null> {
|
|
3418
|
+
if (!this.internalTrack.info.codec) {
|
|
3419
|
+
return null;
|
|
3420
|
+
}
|
|
3421
|
+
|
|
3422
|
+
return this.decoderConfig ??= {
|
|
3423
|
+
codec: extractAudioCodecString(this.internalTrack.info),
|
|
3424
|
+
numberOfChannels: this.internalTrack.info.numberOfChannels,
|
|
3425
|
+
sampleRate: this.internalTrack.info.sampleRate,
|
|
3426
|
+
description: this.internalTrack.info.codecDescription ?? undefined,
|
|
3427
|
+
};
|
|
3428
|
+
}
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
const getSampleIndexForTimestamp = (sampleTable: SampleTable, timescaleUnits: number) => {
|
|
3432
|
+
if (sampleTable.presentationTimestamps) {
|
|
3433
|
+
const index = binarySearchLessOrEqual(
|
|
3434
|
+
sampleTable.presentationTimestamps,
|
|
3435
|
+
timescaleUnits,
|
|
3436
|
+
x => x.presentationTimestamp,
|
|
3437
|
+
);
|
|
3438
|
+
if (index === -1) {
|
|
3439
|
+
return -1;
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
return sampleTable.presentationTimestamps[index]!.sampleIndex;
|
|
3443
|
+
} else {
|
|
3444
|
+
const index = binarySearchLessOrEqual(
|
|
3445
|
+
sampleTable.sampleTimingEntries,
|
|
3446
|
+
timescaleUnits,
|
|
3447
|
+
x => x.startDecodeTimestamp,
|
|
3448
|
+
);
|
|
3449
|
+
if (index === -1) {
|
|
3450
|
+
return -1;
|
|
3451
|
+
}
|
|
3452
|
+
|
|
3453
|
+
const entry = sampleTable.sampleTimingEntries[index]!;
|
|
3454
|
+
return entry.startIndex
|
|
3455
|
+
+ Math.min(
|
|
3456
|
+
Math.floor((timescaleUnits - entry.startDecodeTimestamp) / entry.delta),
|
|
3457
|
+
entry.count - 1,
|
|
3458
|
+
);
|
|
3459
|
+
}
|
|
3460
|
+
};
|
|
3461
|
+
|
|
3462
|
+
const getKeyframeSampleIndexForTimestamp = (sampleTable: SampleTable, timescaleUnits: number) => {
|
|
3463
|
+
if (!sampleTable.keySampleIndices) {
|
|
3464
|
+
// Every sample is a keyframe
|
|
3465
|
+
return getSampleIndexForTimestamp(sampleTable, timescaleUnits);
|
|
3466
|
+
}
|
|
3467
|
+
|
|
3468
|
+
if (sampleTable.presentationTimestamps) {
|
|
3469
|
+
const index = binarySearchLessOrEqual(
|
|
3470
|
+
sampleTable.presentationTimestamps,
|
|
3471
|
+
timescaleUnits,
|
|
3472
|
+
x => x.presentationTimestamp,
|
|
3473
|
+
);
|
|
3474
|
+
if (index === -1) {
|
|
3475
|
+
return -1;
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
// Walk the samples in presentation order until we find one that's a keyframe
|
|
3479
|
+
for (let i = index; i >= 0; i--) {
|
|
3480
|
+
const sampleIndex = sampleTable.presentationTimestamps[i]!.sampleIndex;
|
|
3481
|
+
const isKeyFrame = binarySearchExact(sampleTable.keySampleIndices, sampleIndex, x => x) !== -1;
|
|
3482
|
+
|
|
3483
|
+
if (isKeyFrame) {
|
|
3484
|
+
return sampleIndex;
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
|
|
3488
|
+
return -1;
|
|
3489
|
+
} else {
|
|
3490
|
+
const sampleIndex = getSampleIndexForTimestamp(sampleTable, timescaleUnits);
|
|
3491
|
+
|
|
3492
|
+
const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, x => x);
|
|
3493
|
+
return sampleTable.keySampleIndices[index] ?? -1;
|
|
3494
|
+
}
|
|
3495
|
+
};
|
|
3496
|
+
|
|
3497
|
+
type SampleInfo = {
|
|
3498
|
+
presentationTimestamp: number;
|
|
3499
|
+
duration: number;
|
|
3500
|
+
sampleOffset: number;
|
|
3501
|
+
sampleSize: number;
|
|
3502
|
+
chunkOffset: number;
|
|
3503
|
+
chunkSize: number;
|
|
3504
|
+
isKeyFrame: boolean;
|
|
3505
|
+
};
|
|
3506
|
+
|
|
3507
|
+
const getSampleInfo = (sampleTable: SampleTable, sampleIndex: number): SampleInfo | null => {
|
|
3508
|
+
const timingEntryIndex = binarySearchLessOrEqual(sampleTable.sampleTimingEntries, sampleIndex, x => x.startIndex);
|
|
3509
|
+
const timingEntry = sampleTable.sampleTimingEntries[timingEntryIndex];
|
|
3510
|
+
if (!timingEntry || timingEntry.startIndex + timingEntry.count <= sampleIndex) {
|
|
3511
|
+
return null;
|
|
3512
|
+
}
|
|
3513
|
+
|
|
3514
|
+
const decodeTimestamp = timingEntry.startDecodeTimestamp
|
|
3515
|
+
+ (sampleIndex - timingEntry.startIndex) * timingEntry.delta;
|
|
3516
|
+
let presentationTimestamp = decodeTimestamp;
|
|
3517
|
+
const offsetEntryIndex = binarySearchLessOrEqual(
|
|
3518
|
+
sampleTable.sampleCompositionTimeOffsets,
|
|
3519
|
+
sampleIndex,
|
|
3520
|
+
x => x.startIndex,
|
|
3521
|
+
);
|
|
3522
|
+
const offsetEntry = sampleTable.sampleCompositionTimeOffsets[offsetEntryIndex];
|
|
3523
|
+
if (offsetEntry && sampleIndex - offsetEntry.startIndex < offsetEntry.count) {
|
|
3524
|
+
presentationTimestamp += offsetEntry.offset;
|
|
3525
|
+
}
|
|
3526
|
+
|
|
3527
|
+
const sampleSize = sampleTable.sampleSizes[Math.min(sampleIndex, sampleTable.sampleSizes.length - 1)]!;
|
|
3528
|
+
const chunkEntryIndex = binarySearchLessOrEqual(sampleTable.sampleToChunk, sampleIndex, x => x.startSampleIndex);
|
|
3529
|
+
const chunkEntry = sampleTable.sampleToChunk[chunkEntryIndex];
|
|
3530
|
+
assert(chunkEntry);
|
|
3531
|
+
|
|
3532
|
+
const chunkIndex = chunkEntry.startChunkIndex
|
|
3533
|
+
+ Math.floor((sampleIndex - chunkEntry.startSampleIndex) / chunkEntry.samplesPerChunk);
|
|
3534
|
+
const chunkOffset = sampleTable.chunkOffsets[chunkIndex]!;
|
|
3535
|
+
|
|
3536
|
+
const startSampleIndexOfChunk = chunkEntry.startSampleIndex
|
|
3537
|
+
+ (chunkIndex - chunkEntry.startChunkIndex) * chunkEntry.samplesPerChunk;
|
|
3538
|
+
let chunkSize = 0;
|
|
3539
|
+
let sampleOffset = chunkOffset;
|
|
3540
|
+
|
|
3541
|
+
if (sampleTable.sampleSizes.length === 1) {
|
|
3542
|
+
sampleOffset += sampleSize * (sampleIndex - startSampleIndexOfChunk);
|
|
3543
|
+
chunkSize += sampleSize * chunkEntry.samplesPerChunk;
|
|
3544
|
+
} else {
|
|
3545
|
+
for (let i = startSampleIndexOfChunk; i < startSampleIndexOfChunk + chunkEntry.samplesPerChunk; i++) {
|
|
3546
|
+
const sampleSize = sampleTable.sampleSizes[i]!;
|
|
3547
|
+
|
|
3548
|
+
if (i < sampleIndex) {
|
|
3549
|
+
sampleOffset += sampleSize;
|
|
3550
|
+
}
|
|
3551
|
+
chunkSize += sampleSize;
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
|
|
3555
|
+
let duration = timingEntry.delta;
|
|
3556
|
+
if (sampleTable.presentationTimestamps) {
|
|
3557
|
+
// In order to accurately compute the duration, we need to take the duration to the next sample in presentation
|
|
3558
|
+
// order, not in decode order
|
|
3559
|
+
const presentationIndex = sampleTable.presentationTimestampIndexMap![sampleIndex];
|
|
3560
|
+
assert(presentationIndex !== undefined);
|
|
3561
|
+
|
|
3562
|
+
if (presentationIndex < sampleTable.presentationTimestamps.length - 1) {
|
|
3563
|
+
const nextEntry = sampleTable.presentationTimestamps[presentationIndex + 1]!;
|
|
3564
|
+
const nextPresentationTimestamp = nextEntry.presentationTimestamp;
|
|
3565
|
+
duration = nextPresentationTimestamp - presentationTimestamp;
|
|
3566
|
+
}
|
|
3567
|
+
}
|
|
3568
|
+
|
|
3569
|
+
return {
|
|
3570
|
+
presentationTimestamp,
|
|
3571
|
+
duration,
|
|
3572
|
+
sampleOffset,
|
|
3573
|
+
sampleSize,
|
|
3574
|
+
chunkOffset,
|
|
3575
|
+
chunkSize,
|
|
3576
|
+
isKeyFrame: sampleTable.keySampleIndices
|
|
3577
|
+
? binarySearchExact(sampleTable.keySampleIndices, sampleIndex, x => x) !== -1
|
|
3578
|
+
: true,
|
|
3579
|
+
};
|
|
3580
|
+
};
|
|
3581
|
+
|
|
3582
|
+
const getNextKeyframeIndexForSample = (sampleTable: SampleTable, sampleIndex: number) => {
|
|
3583
|
+
if (!sampleTable.keySampleIndices) {
|
|
3584
|
+
return sampleIndex + 1;
|
|
3585
|
+
}
|
|
3586
|
+
|
|
3587
|
+
const index = binarySearchLessOrEqual(sampleTable.keySampleIndices, sampleIndex, x => x);
|
|
3588
|
+
return sampleTable.keySampleIndices[index + 1] ?? -1;
|
|
3589
|
+
};
|
|
3590
|
+
|
|
3591
|
+
const offsetFragmentTrackDataByTimestamp = (trackData: FragmentTrackData, timestamp: number) => {
|
|
3592
|
+
trackData.startTimestamp += timestamp;
|
|
3593
|
+
trackData.endTimestamp += timestamp;
|
|
3594
|
+
|
|
3595
|
+
for (const sample of trackData.samples) {
|
|
3596
|
+
sample.presentationTimestamp += timestamp;
|
|
3597
|
+
}
|
|
3598
|
+
for (const entry of trackData.presentationTimestamps) {
|
|
3599
|
+
entry.presentationTimestamp += timestamp;
|
|
3600
|
+
}
|
|
3601
|
+
};
|
|
3602
|
+
|
|
3603
|
+
/** Extracts the rotation component from a transformation matrix, in degrees. */
|
|
3604
|
+
const extractRotationFromMatrix = (matrix: TransformationMatrix) => {
|
|
3605
|
+
const [a, b] = matrix; // (1, 0) projects onto (a, b), so that's all we need
|
|
3606
|
+
|
|
3607
|
+
const radians = Math.atan2(b, a);
|
|
3608
|
+
|
|
3609
|
+
if (!Number.isFinite(radians)) {
|
|
3610
|
+
// Can happen if the entire matrix is 0, for example
|
|
3611
|
+
return 0;
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
return radians * (180 / Math.PI);
|
|
3615
|
+
};
|
|
3616
|
+
|
|
3617
|
+
const sampleTableIsEmpty = (sampleTable: SampleTable) => {
|
|
3618
|
+
return sampleTable.sampleSizes.length === 0;
|
|
3619
|
+
};
|
|
3620
|
+
|
|
3621
|
+
const getOrCreateEncryptionAuxInfo = (track: InternalTrack) => {
|
|
3622
|
+
if (track.currentFragmentState) {
|
|
3623
|
+
return track.currentFragmentState.encryptionAuxInfo ??= {
|
|
3624
|
+
defaultSampleInfoSize: 0,
|
|
3625
|
+
sampleSizes: null,
|
|
3626
|
+
sampleCount: 0,
|
|
3627
|
+
offset: null,
|
|
3628
|
+
resolved: null,
|
|
3629
|
+
};
|
|
3630
|
+
} else {
|
|
3631
|
+
return track.encryptionAuxInfo ??= {
|
|
3632
|
+
defaultSampleInfoSize: 0,
|
|
3633
|
+
sampleSizes: null,
|
|
3634
|
+
sampleCount: 0,
|
|
3635
|
+
offset: null,
|
|
3636
|
+
resolved: null,
|
|
3637
|
+
};
|
|
3638
|
+
}
|
|
3639
|
+
};
|
|
3640
|
+
|
|
3641
|
+
const resolveEncryptionAuxInfo = async (
|
|
3642
|
+
reader: Reader,
|
|
3643
|
+
encryptionInfo: TrackEncryptionInfo,
|
|
3644
|
+
aux: SampleEncryptionAuxInfo,
|
|
3645
|
+
) => {
|
|
3646
|
+
if (aux.resolved) {
|
|
3647
|
+
return aux.resolved;
|
|
3648
|
+
}
|
|
3649
|
+
|
|
3650
|
+
if (aux.offset === null || aux.sampleCount === 0) {
|
|
3651
|
+
throw new Error('Incomplete saiz/saio info; cannot resolve encryption data.');
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3654
|
+
let totalSize = 0;
|
|
3655
|
+
if (aux.defaultSampleInfoSize > 0) {
|
|
3656
|
+
totalSize = aux.defaultSampleInfoSize * aux.sampleCount;
|
|
3657
|
+
} else {
|
|
3658
|
+
assert(aux.sampleSizes);
|
|
3659
|
+
for (let i = 0; i < aux.sampleCount; i++) {
|
|
3660
|
+
totalSize += aux.sampleSizes[i]!;
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
let slice = reader.requestSlice(aux.offset, totalSize);
|
|
3665
|
+
if (slice instanceof Promise) slice = await slice;
|
|
3666
|
+
if (!slice) {
|
|
3667
|
+
throw new Error('Failed to read auxiliary encryption info.');
|
|
3668
|
+
}
|
|
3669
|
+
|
|
3670
|
+
const ivSize = encryptionInfo.defaultPerSampleIvSize;
|
|
3671
|
+
assert(ivSize !== null);
|
|
3672
|
+
|
|
3673
|
+
// Each aux entry has the same byte layout as a senc entry: IV (of size ivSize, or the constant IV from tenc
|
|
3674
|
+
// when ivSize is 0), then optionally subsample count + [clearLen, protectedLen] pairs. Subsamples are present
|
|
3675
|
+
// iff the entry is larger than the IV.
|
|
3676
|
+
const entries: SampleEncryptionInfo[] = [];
|
|
3677
|
+
for (let i = 0; i < aux.sampleCount; i++) {
|
|
3678
|
+
const entrySize = aux.defaultSampleInfoSize > 0
|
|
3679
|
+
? aux.defaultSampleInfoSize
|
|
3680
|
+
: aux.sampleSizes![i]!;
|
|
3681
|
+
|
|
3682
|
+
const iv = new Uint8Array(16);
|
|
3683
|
+
if (ivSize > 0) {
|
|
3684
|
+
iv.set(readBytes(slice, ivSize), 0);
|
|
3685
|
+
} else {
|
|
3686
|
+
iv.set(encryptionInfo.defaultConstantIv!, 0);
|
|
3687
|
+
}
|
|
3688
|
+
|
|
3689
|
+
let subsamples: { clearLen: number; protectedLen: number }[] | null = null;
|
|
3690
|
+
if (entrySize > ivSize) {
|
|
3691
|
+
const subsampleCount = readU16Be(slice);
|
|
3692
|
+
subsamples = [];
|
|
3693
|
+
for (let j = 0; j < subsampleCount; j++) {
|
|
3694
|
+
const clearLen = readU16Be(slice);
|
|
3695
|
+
const protectedLen = readU32Be(slice);
|
|
3696
|
+
subsamples.push({ clearLen, protectedLen });
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
entries.push({ iv, subsamples });
|
|
3701
|
+
}
|
|
3702
|
+
|
|
3703
|
+
aux.resolved = entries;
|
|
3704
|
+
return entries;
|
|
3705
|
+
};
|
|
3706
|
+
|
|
3707
|
+
const decryptSample = async (
|
|
3708
|
+
track: InternalTrack,
|
|
3709
|
+
sampleEncryption: SampleEncryptionInfo,
|
|
3710
|
+
data: Uint8Array,
|
|
3711
|
+
fragment: Fragment | null,
|
|
3712
|
+
): Promise<Uint8Array> => {
|
|
3713
|
+
assert(track.encryptionInfo);
|
|
3714
|
+
const encryptionInfo = track.encryptionInfo;
|
|
3715
|
+
assert(encryptionInfo.defaultKid !== null);
|
|
3716
|
+
|
|
3717
|
+
const keyId = encryptionInfo.defaultKid;
|
|
3718
|
+
let keyBytes: Uint8Array;
|
|
3719
|
+
|
|
3720
|
+
const cacheEntry = track.demuxer.decryptionKeyCache.get(keyId);
|
|
3721
|
+
if (cacheEntry) {
|
|
3722
|
+
keyBytes = await cacheEntry;
|
|
3723
|
+
} else {
|
|
3724
|
+
if (!track.demuxer.input._formatOptions.isobmff?.resolveKeyId) {
|
|
3725
|
+
throw new Error(
|
|
3726
|
+
'Encrypted media samples encountered. To decrypt them, please provide a callback for'
|
|
3727
|
+
+ ' InputOptions.formatOptions.isobmff.resolveKeyId.',
|
|
3728
|
+
);
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3731
|
+
const promise = (async () => {
|
|
3732
|
+
let psshBoxes = track.demuxer.psshBoxes;
|
|
3733
|
+
if (fragment) {
|
|
3734
|
+
psshBoxes = [
|
|
3735
|
+
...psshBoxes,
|
|
3736
|
+
...fragment.psshBoxes,
|
|
3737
|
+
].filter(x => x.keyIds === null || x.keyIds.includes(keyId));
|
|
3738
|
+
|
|
3739
|
+
// Filter out duplicates
|
|
3740
|
+
for (let i = 0; i < psshBoxes.length - 1; i++) {
|
|
3741
|
+
for (let j = i + 1; j < psshBoxes.length; j++) {
|
|
3742
|
+
if (psshBoxesAreEqual(psshBoxes[i]!, psshBoxes[j]!)) {
|
|
3743
|
+
psshBoxes.splice(j, 1);
|
|
3744
|
+
j--;
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
|
|
3750
|
+
const keyResult = await track.demuxer.input._formatOptions.isobmff!.resolveKeyId!({ keyId, psshBoxes });
|
|
3751
|
+
|
|
3752
|
+
if (!(
|
|
3753
|
+
(typeof keyResult === 'string' && keyResult.length === 32 && HEX_STRING_REGEX.test(keyResult))
|
|
3754
|
+
|| (keyResult instanceof Uint8Array && keyResult.byteLength === 16)
|
|
3755
|
+
)) {
|
|
3756
|
+
throw new TypeError(
|
|
3757
|
+
'resolveKeyId must return a 32-character hex string or a 16-byte Uint8Array containing the'
|
|
3758
|
+
+ ' decryption key.',
|
|
3759
|
+
);
|
|
3760
|
+
}
|
|
3761
|
+
|
|
3762
|
+
return keyResult instanceof Uint8Array
|
|
3763
|
+
? keyResult
|
|
3764
|
+
: hexStringToBytes(keyResult);
|
|
3765
|
+
})();
|
|
3766
|
+
|
|
3767
|
+
track.demuxer.decryptionKeyCache.set(keyId, promise);
|
|
3768
|
+
keyBytes = await promise;
|
|
3769
|
+
}
|
|
3770
|
+
|
|
3771
|
+
if (encryptionInfo.scheme === 'cenc' || encryptionInfo.scheme === 'cens') {
|
|
3772
|
+
return decryptCtr(keyBytes, encryptionInfo, sampleEncryption, data);
|
|
3773
|
+
} else {
|
|
3774
|
+
return decryptCbcs(keyBytes, encryptionInfo, sampleEncryption, data);
|
|
3775
|
+
}
|
|
3776
|
+
};
|
|
3777
|
+
|
|
3778
|
+
const decryptCtr = async (
|
|
3779
|
+
key: Uint8Array,
|
|
3780
|
+
encryptionInfo: TrackEncryptionInfo,
|
|
3781
|
+
sampleEncryption: SampleEncryptionInfo,
|
|
3782
|
+
data: Uint8Array,
|
|
3783
|
+
) => {
|
|
3784
|
+
const counter = new Uint8Array(16);
|
|
3785
|
+
counter.set(sampleEncryption.iv, 0);
|
|
3786
|
+
|
|
3787
|
+
const cryptoKey = await crypto.subtle.importKey(
|
|
3788
|
+
'raw',
|
|
3789
|
+
key as BufferSource,
|
|
3790
|
+
{ name: 'AES-CTR' },
|
|
3791
|
+
false,
|
|
3792
|
+
['decrypt'],
|
|
3793
|
+
);
|
|
3794
|
+
|
|
3795
|
+
const cryptApply = async (input: Uint8Array) => {
|
|
3796
|
+
const plaintext = await crypto.subtle.decrypt(
|
|
3797
|
+
{ name: 'AES-CTR', counter, length: 64 },
|
|
3798
|
+
cryptoKey,
|
|
3799
|
+
input as BufferSource,
|
|
3800
|
+
);
|
|
3801
|
+
|
|
3802
|
+
return new Uint8Array(plaintext);
|
|
3803
|
+
};
|
|
3804
|
+
|
|
3805
|
+
if (!sampleEncryption.subsamples) {
|
|
3806
|
+
// Whole sample is protected, no pattern
|
|
3807
|
+
return cryptApply(data);
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3810
|
+
assert(encryptionInfo.defaultCryptByteBlock !== null && encryptionInfo.defaultSkipByteBlock !== null);
|
|
3811
|
+
const cryptRanges = collectCryptRanges(
|
|
3812
|
+
sampleEncryption.subsamples,
|
|
3813
|
+
encryptionInfo.defaultCryptByteBlock,
|
|
3814
|
+
encryptionInfo.defaultSkipByteBlock,
|
|
3815
|
+
);
|
|
3816
|
+
|
|
3817
|
+
// Concatenate all crypt ranges into a single buffer so the continuous CTR counter behavior is preserved
|
|
3818
|
+
let totalCryptLen = 0;
|
|
3819
|
+
for (const range of cryptRanges) {
|
|
3820
|
+
for (const seg of range.perSubsample) {
|
|
3821
|
+
totalCryptLen += seg.length;
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
const cryptBuffer = new Uint8Array(totalCryptLen);
|
|
3825
|
+
let writePos = 0;
|
|
3826
|
+
for (const range of cryptRanges) {
|
|
3827
|
+
for (const seg of range.perSubsample) {
|
|
3828
|
+
cryptBuffer.set(data.subarray(seg.offset, seg.offset + seg.length), writePos);
|
|
3829
|
+
writePos += seg.length;
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
const plain = await cryptApply(cryptBuffer);
|
|
3834
|
+
|
|
3835
|
+
// Now let's build the output
|
|
3836
|
+
const output = new Uint8Array(data);
|
|
3837
|
+
let readPos = 0;
|
|
3838
|
+
for (const range of cryptRanges) {
|
|
3839
|
+
for (const seg of range.perSubsample) {
|
|
3840
|
+
output.set(plain.subarray(readPos, readPos + seg.length), seg.offset);
|
|
3841
|
+
readPos += seg.length;
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
|
|
3845
|
+
return output;
|
|
3846
|
+
};
|
|
3847
|
+
|
|
3848
|
+
const decryptCbcs = (
|
|
3849
|
+
key: Uint8Array,
|
|
3850
|
+
encryptionInfo: TrackEncryptionInfo,
|
|
3851
|
+
sampleEncryption: SampleEncryptionInfo,
|
|
3852
|
+
data: Uint8Array,
|
|
3853
|
+
) => {
|
|
3854
|
+
const ctx = new Aes128CbcContext();
|
|
3855
|
+
ctx.init({ key, iv: sampleEncryption.iv });
|
|
3856
|
+
|
|
3857
|
+
const cryptByteBlock = encryptionInfo.defaultCryptByteBlock;
|
|
3858
|
+
const skipByteBlock = encryptionInfo.defaultSkipByteBlock;
|
|
3859
|
+
assert(cryptByteBlock !== null && skipByteBlock !== null);
|
|
3860
|
+
|
|
3861
|
+
if (!sampleEncryption.subsamples) {
|
|
3862
|
+
// Whole-sample encryption: straightforward CBC over floor(size / 16) blocks, any trailing bytes stay clear
|
|
3863
|
+
const output = new Uint8Array(data);
|
|
3864
|
+
const numBlocks = Math.floor(data.length / 16);
|
|
3865
|
+
|
|
3866
|
+
for (let b = 0; b < numBlocks; b++) {
|
|
3867
|
+
const off = b * 16;
|
|
3868
|
+
ctx.in.set(data.subarray(off, off + 16));
|
|
3869
|
+
ctx.decrypt();
|
|
3870
|
+
output.set(ctx.out, off);
|
|
3871
|
+
}
|
|
3872
|
+
|
|
3873
|
+
return output;
|
|
3874
|
+
}
|
|
3875
|
+
|
|
3876
|
+
if (cryptByteBlock === 0 && skipByteBlock === 0) {
|
|
3877
|
+
throw new Error('cbcs with subsamples requires pattern encryption.');
|
|
3878
|
+
}
|
|
3879
|
+
|
|
3880
|
+
const output = new Uint8Array(data);
|
|
3881
|
+
|
|
3882
|
+
// Pattern decryption: IV is reset at the start of each subsample. Within a subsample, the CBC chain continues
|
|
3883
|
+
// across skipped blocks (the IV after a crypt group carries over to the next crypt group's first block).
|
|
3884
|
+
const cryptRanges = collectCryptRanges(sampleEncryption.subsamples, cryptByteBlock, skipByteBlock);
|
|
3885
|
+
const ivView = new DataView(sampleEncryption.iv.buffer, sampleEncryption.iv.byteOffset, 16);
|
|
3886
|
+
|
|
3887
|
+
for (const range of cryptRanges) {
|
|
3888
|
+
// Reset IV per subsample
|
|
3889
|
+
ctx.iv[0] = ivView.getUint32(0, false);
|
|
3890
|
+
ctx.iv[1] = ivView.getUint32(4, false);
|
|
3891
|
+
ctx.iv[2] = ivView.getUint32(8, false);
|
|
3892
|
+
ctx.iv[3] = ivView.getUint32(12, false);
|
|
3893
|
+
|
|
3894
|
+
for (const seg of range.perSubsample) {
|
|
3895
|
+
// Decrypt length / 16 blocks at this offset
|
|
3896
|
+
const numBlocks = seg.length / 16;
|
|
3897
|
+
|
|
3898
|
+
for (let b = 0; b < numBlocks; b++) {
|
|
3899
|
+
const offset = seg.offset + b * 16;
|
|
3900
|
+
ctx.in.set(data.subarray(offset, offset + 16));
|
|
3901
|
+
ctx.decrypt();
|
|
3902
|
+
output.set(ctx.out, offset);
|
|
3903
|
+
}
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
return output;
|
|
3908
|
+
};
|
|
3909
|
+
|
|
3910
|
+
const collectCryptRanges = (
|
|
3911
|
+
subsamples: { clearLen: number; protectedLen: number }[],
|
|
3912
|
+
cryptByteBlock: number,
|
|
3913
|
+
skipByteBlock: number,
|
|
3914
|
+
) => {
|
|
3915
|
+
const ranges: { perSubsample: { offset: number; length: number }[] }[] = [];
|
|
3916
|
+
const hasPattern = cryptByteBlock !== 0 || skipByteBlock !== 0;
|
|
3917
|
+
|
|
3918
|
+
let cursor = 0;
|
|
3919
|
+
for (const subsample of subsamples) {
|
|
3920
|
+
cursor += subsample.clearLen;
|
|
3921
|
+
|
|
3922
|
+
const perSubsample: { offset: number; length: number }[] = [];
|
|
3923
|
+
|
|
3924
|
+
if (!hasPattern) {
|
|
3925
|
+
if (subsample.protectedLen > 0) {
|
|
3926
|
+
perSubsample.push({ offset: cursor, length: subsample.protectedLen });
|
|
3927
|
+
}
|
|
3928
|
+
cursor += subsample.protectedLen;
|
|
3929
|
+
} else {
|
|
3930
|
+
let remaining = subsample.protectedLen;
|
|
3931
|
+
let pos = cursor;
|
|
3932
|
+
while (remaining > 0) {
|
|
3933
|
+
if (remaining < 16 * cryptByteBlock) {
|
|
3934
|
+
break; // Partial final crypt group stays clear
|
|
3935
|
+
}
|
|
3936
|
+
|
|
3937
|
+
const cryptBytes = 16 * cryptByteBlock;
|
|
3938
|
+
perSubsample.push({ offset: pos, length: cryptBytes });
|
|
3939
|
+
pos += cryptBytes;
|
|
3940
|
+
remaining -= cryptBytes;
|
|
3941
|
+
|
|
3942
|
+
const skipBytes = Math.min(16 * skipByteBlock, remaining);
|
|
3943
|
+
pos += skipBytes;
|
|
3944
|
+
remaining -= skipBytes;
|
|
3945
|
+
}
|
|
3946
|
+
cursor += subsample.protectedLen;
|
|
3947
|
+
}
|
|
3948
|
+
|
|
3949
|
+
ranges.push({ perSubsample });
|
|
3950
|
+
}
|
|
3951
|
+
|
|
3952
|
+
return ranges;
|
|
3953
|
+
};
|