video-fps-probe 0.3.0 → 0.4.0-beta.1
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/CHANGELOG.md +43 -0
- package/README.md +18 -12
- package/dist/api/codes.d.ts +6 -0
- package/dist/api/codes.js +88 -0
- package/dist/cjs/analyze.d.cts +9 -0
- package/dist/cjs/api/agreement.d.cts +18 -0
- package/dist/cjs/api/codes.d.cts +6 -0
- package/dist/cjs/api/errors.d.cts +12 -0
- package/dist/cjs/api/guards.d.cts +4 -0
- package/dist/cjs/api/summary.d.cts +22 -0
- package/dist/cjs/check/index.d.cts +60 -0
- package/dist/cjs/check.cjs +1 -0
- package/dist/cjs/chunks/build-CT5g6_bs.cjs +1 -0
- package/dist/cjs/chunks/compare-D7RVqlqf.cjs +1 -0
- package/dist/cjs/chunks/errors-0oBg1Ai6.cjs +1 -0
- package/dist/cjs/chunks/fallback-D9Ak2zFv.cjs +1 -0
- package/dist/cjs/chunks/guards-Dqpjtkdn.cjs +1 -0
- package/dist/cjs/chunks/inline-Bx-TQg8f.cjs +1 -0
- package/dist/cjs/chunks/isobmff-B_bb5rS5.cjs +1 -0
- package/dist/cjs/chunks/matroska-CLoGzfKA.cjs +1 -0
- package/dist/cjs/chunks/probe-DDLUfr23.cjs +1 -0
- package/dist/cjs/chunks/rational-Dq5Vza-Q.cjs +1 -0
- package/dist/cjs/chunks/reader-DNLR-_tm.cjs +1 -0
- package/dist/cjs/chunks/rfps-C-pyKnu8.cjs +1 -0
- package/dist/cjs/chunks/runtime-DtgT0xzU.cjs +1 -0
- package/dist/cjs/chunks/segments-i-hPN7H3.cjs +1 -0
- package/dist/cjs/chunks/sniff-CXwqOGwh.cjs +1 -0
- package/dist/cjs/chunks/stats-D68frkk2.cjs +1 -0
- package/dist/cjs/client/types.d.cts +96 -0
- package/dist/cjs/core/reader.d.cts +47 -0
- package/dist/cjs/core/types.d.cts +149 -0
- package/dist/cjs/engines/types.d.cts +69 -0
- package/dist/cjs/header/index.d.cts +25 -0
- package/dist/cjs/header/limits.d.cts +25 -0
- package/dist/cjs/header.cjs +1 -0
- package/dist/cjs/index.cjs +1 -0
- package/dist/cjs/index.d.cts +13 -0
- package/dist/cjs/plugins/types.d.cts +17 -0
- package/dist/cjs/presets/index.d.cts +19 -0
- package/dist/cjs/presets.cjs +1 -0
- package/dist/cjs/probe.d.cts +3 -0
- package/dist/cjs/url/index.d.cts +47 -0
- package/dist/cjs/url.cjs +1 -0
- package/dist/cjs/worker/protocol.d.cts +47 -0
- package/dist/core/dispatch.js +18 -29
- package/dist/core/isobmff/boxes.js +6 -5
- package/dist/core/isobmff/build.js +398 -0
- package/dist/core/isobmff/header.js +654 -0
- package/dist/core/isobmff/index.js +9 -381
- package/dist/core/isobmff/moof.js +2 -2
- package/dist/core/isobmff/moov.js +2 -2
- package/dist/core/matroska/header.js +9 -4
- package/dist/core/matroska/index.js +7 -3
- package/dist/core/reader.d.ts +3 -0
- package/dist/core/reader.js +13 -7
- package/dist/core/result.js +1 -0
- package/dist/core/stats.js +3 -0
- package/dist/core/types.d.ts +15 -1
- package/dist/engines/common.js +2 -0
- package/dist/header/index.d.ts +25 -0
- package/dist/header/index.js +146 -0
- package/dist/header/limits.d.ts +25 -0
- package/dist/header/limits.js +140 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/standalone/own-isobmff.worker.js +1 -1
- package/dist/standalone/own-matroska.worker.js +1 -1
- package/dist/standalone/own.worker.js +1 -1
- package/dist/url/index.d.ts +47 -0
- package/dist/url/index.js +335 -0
- package/dist/worker/single.js +2 -1
- package/package.json +79 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,49 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions follo
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.4.0-beta.1 - 2026-10-31
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `errorCode` on every own result: a code for `error` from a closed list, such as `no-header`, `truncated` or `read-failed`, null exactly when `error` is null. Every `error` text is unchanged ([error codes](docs/api.md#error-codes)).
|
|
12
|
+
- `errorCodeOf`, `errorClass`, `warningCodeOf` and `warningCodesOf` in the root entry, for results of any engine or version, with stable warning codes: `invalid-timescale`, `truncated-mdat`, `truncated-box` and `trailing-data` from the parsers, `fragment-index-unread`, `duration-unresolved` and `fragments-ignored` from the header entry, and `structure-limit` for `too many top-level boxes` ([error codes](docs/api.md#error-codes)).
|
|
13
|
+
- `layout` on ISO BMFF results: `faststart`, `moov-at-end` or `fragmented`, the moov offset and size, a sidx before the first moof, and an empty moov.
|
|
14
|
+
- `medianFrameInterval` on video tracks of own, mp4box and mediabunny, and `enabled` (tkhd or FlagEnabled) on own tracks. `TrackField` gains `'medianFrameInterval'`, so an exhaustive `switch` needs the case.
|
|
15
|
+
- `video-fps-probe/header`: `analyzeHeader(source, options)` reads only the header of an MP4 or MOV file, in the calling thread, under limits on the bytes requested, the reads, the wall time, the tracks and the top-level boxes (`HEADER_DEFAULT_LIMITS`: 16 MiB, 8 reads, 10 s, 64 tracks and 1024 boxes). A fragmented file gets its duration from a covering sidx, the mfra index or mehd, and new codes name what stopped it: `no-duration`, `byte-limit`, `read-limit`, `time-limit` and `structure-limit`. It rejects only on the caller's abort and on an invalid option, with a `TypeError`, and adds at most 1 byte to the other entries ([header-only analysis](docs/api.md#header-only-analysis)). `readAhead` takes only `head` and `tail`, so the `fragment` or `matroskaScan` of `analyze` there is a `TypeError` rather than a silent no-op, and the options and `limits` ignore keys they do not know, so an option or a limit of a later version does not fail here. The entry also exports `isAbortError` and `isTimeoutError`. The signature, the members of `HeaderOptions` and `HeaderLimits`, these rules for keys, the error and warning codes and these exports are frozen from 0.4.0-beta.1.
|
|
16
|
+
- `video-fps-probe/url`: `rangeSource(fetcher, { size })`, `openRangeSource(fetcher)`, `httpRange(url)`, `readCapped(body, expectedLength)` and `RangeSourceError` turn any function that fetches a byte range, such as `fetch` with a presigned URL or the client of an object-storage SDK, into a source for `analyzeHeader` and the in-thread `analyze`. No SDK dependency, one request per read and no retries. Every reply is checked before its body is read, its status, its `Content-Range` with the object size and its `Content-Length`, and the body is capped while it streams, so a server that ignores the range, an object whose size changed or an oversized body fails the read as `read-failed` instead of giving wrong bytes. An object overwritten at the same size is caught only by an `If-Match` with its ETag, which the caller sends. A failed or aborted body is discarded, and a Node stream body is destroyed, so its socket closes. `openRangeSource` learns the size from one request for the first 64 KiB, accepts the clamped 206 of a smaller object and serves the first read of `analyzeHeader` from those bytes, so the requests equal `io.reads`. `RangeSourceError` carries a `code`, such as `unexpected-status` or `size-changed`, and the `status` ([Range source](docs/api.md#range-source)).
|
|
17
|
+
- A CommonJS copy of the five server entries, `video-fps-probe`, `video-fps-probe/header`, `video-fps-probe/url`, `video-fps-probe/check` and `video-fps-probe/presets`, minified with the names of classes and functions kept, so `error.name`, `error.constructor.name` and the export names equal the ESM build, with `.d.cts` types, under the conditions `module-sync`, `module` and `require`, and `main`. Node 22.12 and newer still load one ESM instance from `require()` and bundlers that match the `module` condition, as Vite, webpack, Rspack and esbuild do by default, still resolve the ESM build, so the copy serves only `require()` where `require(esm)` is off. Jest 30 runs these entries with no configuration and `jest` as the only dependency, and with `--experimental-vm-modules` on Node 24.9 and newer it loads the ESM build ([CommonJS](docs/api.md#commonjs), [Jest](docs/api.md#jest)).
|
|
18
|
+
- The types of `video-fps-probe/header`, `video-fps-probe/url`, `video-fps-probe/check` and `video-fps-probe/presets` name no DOM type, so a server that imports only those compiles with `"lib": ["ES2022"]` and the node types, without `skipLibCheck`.
|
|
19
|
+
|
|
20
|
+
### Frozen from 0.4.0-beta.1
|
|
21
|
+
|
|
22
|
+
These are API from 0.4.0-beta.1 on, and 0.4.0-beta.2, 0.4.0 and every later release only add to them ([versioning](docs/api.md#versioning)):
|
|
23
|
+
|
|
24
|
+
- `FileErrorCode`: `empty-file`, `unrecognised`, `unsupported-container`, `unsupported`, `too-large`, `no-header`, `truncated`, `corrupt`, `no-duration`, `byte-limit`, `read-limit`, `time-limit`, `structure-limit`, `read-failed`, `load-failed`, `engine-failed` and `internal`, with the `ErrorClass` of each.
|
|
25
|
+
- `WarningCode`: `invalid-timescale`, `truncated-mdat`, `truncated-box`, `trailing-data`, `fragment-index-unread`, `duration-unresolved`, `fragments-ignored` and `structure-limit`, with the warning prefixes they classify. The four parser prefixes are `invalid mdhd timescale ` or `invalid timescale `, `'mdat' at <offset> is truncated`, `'<type>' at <offset> is truncated` and `ignored trailing data at `, and the header entry adds `fragment index not read: `, `format duration unresolved: `, `fragments ignored: ` and `too many top-level boxes`.
|
|
26
|
+
- `LayoutKind`, `faststart`, `moov-at-end` and `fragmented`, and the members of `ContainerLayout`: `kind`, `moovOffset`, `moovSize`, `sidx` and `emptyMoov`.
|
|
27
|
+
- The members of `HeaderLimits`, `maxBytes`, `maxReads`, `maxMs`, `maxTracks` and `maxTopLevelBoxes`, and of `HeaderOptions`, `name`, `signal`, `limits`, `readAhead` with `head` and `tail` only and `fragmentedDuration`, with the export `HEADER_DEFAULT_LIMITS` and its keys, the signature `analyzeHeader(source: Blob | BlobLike, options?: HeaderOptions): Promise<HeaderResult>`, and the exports `isAbortError` and `isTimeoutError` of `video-fps-probe/header`. A default value of `HEADER_DEFAULT_LIMITS` may change later, as a behaviour change that the changelog names.
|
|
28
|
+
- How `analyzeHeader` treats keys: `readAhead` refuses any key but `head` and `tail` with a `TypeError`, and the options and `limits` ignore keys they do not know.
|
|
29
|
+
- The whole `video-fps-probe/url` API: `rangeSource`, `openRangeSource`, `httpRange`, `readCapped`, `RangeSourceError` with its `code` and `status`, the `RangeSourceErrorCode` values `unverifiable-reply`, `unexpected-status`, `range-mismatch`, `size-changed`, `length-mismatch`, `body-too-long`, `body-too-short` and `invalid-reply`, and the types `RangeRequest`, `RangeReply`, `RangeFetcher`, `RangeSource`, `RangeSourceInit`, `OpenRangeSourceInit`, `HttpRangeInit` and `RangeSourceErrorOptions`, with `BlobLike` and `BlobSlice`, the types of the root entry that it re-exports unchanged.
|
|
30
|
+
|
|
31
|
+
`ColorInfo` and `DolbyVisionConfig` join this list at 0.4.0-beta.2.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- The installed package may take up to 867 KB unpacked (580 KB in 0.3.0), measured at 842 KB: about 80 KB more for the error codes, layout, median frame interval, the header-only entry, the Range source and the longer README and CHANGELOG, and about 190 KB for the CommonJS copy of the server entries, with the names of its classes and functions kept, and its declarations, which took the cap from 632 KB to 867 KB, 3 percent above the measurement. A bundler still takes only the modules a host imports, and no browser bundle holds the CommonJS copy.
|
|
36
|
+
- Node 22.12 is the lowest supported version, as in 0.3.0, and now the only line the docs name: Node 20 is past its end of life.
|
|
37
|
+
- The first MP4 loads 23.07 KB gzip (22.73 KB in 0.3.0), 24.85 KB with Vite's default worker format (24.47 KB), and the first WebM or MKV 25.73 KB (25.54 KB) ([size matrix](report/size-matrix.md)).
|
|
38
|
+
- Still 0 mismatches against ffprobe 8.1.1, now on 168 committed fixtures, with 2 known deviations on a new `+cmaf+global_sidx` fixture with B-frames: ffprobe adds the largest negative composition offset of the fragments it reads to every video timestamp, and own does not yet, so its video starts at 0 instead of 0.066667 s and the file ends 0.045 s earlier ([accuracy](docs/benchmarks.md#accuracy-vs-ffprobe)).
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
- A `readAhead.tail` that is NaN no longer spins the Matroska probe forever: it scans the smallest tail, as every value below 4096 does.
|
|
43
|
+
|
|
44
|
+
### Known limitations
|
|
45
|
+
|
|
46
|
+
- In a browser `httpRange` checks the `Content-Range` of a cross-origin reply only when the server lists it in `Access-Control-Expose-Headers`, and the status and length checks remain. `openRangeSource` needs that header, or a known `size`, because discovery learns the size from it, and rejects with `unverifiable-reply` without it.
|
|
47
|
+
- The Range source checks the size of the object on every read, so an overwrite at the same size between two reads is caught only by an `If-Match` that the caller sends. `httpRange` does not keep the ETag of its first reply.
|
|
48
|
+
- Full mode reads every fragment, so on a `+cmaf+global_sidx` output of several fragments where every track has a sidx that covers the file and the last AAC fragment runs past its sidx, it reports that end, 1024 samples later than ffprobe 8.1.1, for example 20.521333 s against 20.5 s, as in 0.3.0. `analyzeHeader` gives the ffprobe value there. On ffmpeg's `+cmaf+global_sidx` output with a `mov_text` or `-timecode` track, whose sidx is written last and ends early, ffprobe reads every fragment too, and both modes give its value. On `+cmaf+global_sidx` files patched so that the one sidx left before the first moof covers the file and another track has none, ffprobe 8.1.1 gave the sidx end for every stream in the cases measured, 6.0 s where full mode gives 6.021333 s. Whether that holds as a rule is a hypothesis with counter-cases, so `analyzeHeader` gives the full-mode value there through the mfra, or without one keeps the video duration and leaves `formatDurationSec` null with a warning ([notes](docs/implementation-notes.md#what-the-header-walk-reads)).
|
|
49
|
+
|
|
7
50
|
## 0.3.0 - 2026-10-30
|
|
8
51
|
|
|
9
52
|
### Added
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# video-fps-probe
|
|
2
2
|
|
|
3
|
-
Frame rate, frame count, VFR detection and a rate profile for MP4, MOV, WebM and MKV files, read from the container headers in a Web Worker. Nothing is decoded and nothing leaves the browser, and the values match ffprobe 8.1.1 on the test corpus (0 mismatches). ESM
|
|
3
|
+
Frame rate, frame count, VFR detection and a rate profile for MP4, MOV, WebM and MKV files, read from the container headers in a Web Worker. Nothing is decoded and nothing leaves the browser, and the values match ffprobe 8.1.1 on the test corpus (0 mismatches). ESM with a CommonJS copy of the server entries, no runtime dependencies, types included.
|
|
4
4
|
|
|
5
5
|
**[Live demo](https://vbondarets.github.io/video-fps-probe/)** · [API](docs/api.md) · [Bundlers](docs/bundlers.md) · [Benchmarks](docs/benchmarks.md) · [Architecture](docs/architecture.md) · [Changelog](CHANGELOG.md)
|
|
6
6
|
|
|
@@ -13,7 +13,7 @@ pnpm add video-fps-probe
|
|
|
13
13
|
bun add video-fps-probe
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
The first npm release is 0.2.0
|
|
16
|
+
The first npm release is 0.2.0, and one publish serves every package manager ([publishing](docs/publishing.md)). npm is the tested package manager: CI installs the packed tarball with npm. Yarn, pnpm and bun are expected to work but are not tested in CI yet (pnpm, Yarn PnP and bun installs join the bundler matrix in 0.4.1). The package has no dependencies. Its peers are optional and no package manager installs them for you: install one yourself, only for the entry you import: `mp4box` for `video-fps-probe/mp4box`, `mediabunny` for `video-fps-probe/mediabunny`, React 18 or 19 for `video-fps-probe/react`.
|
|
17
17
|
|
|
18
18
|
For a commit that is not released yet, install a packed tarball. A git dependency gets no code with npm (Yarn, pnpm and bun are not tested): `prepack` builds `dist/`, and npm does not run it for a git install.
|
|
19
19
|
|
|
@@ -98,9 +98,15 @@ import { analyze } from 'video-fps-probe';
|
|
|
98
98
|
const result = await analyze(await openAsBlob(path), { name: path }); // in the calling thread, no worker
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
In-thread `analyze()` is tested on Node 24, the version in `.nvmrc`, and on Node 22.12, the lowest supported version: CI runs the unit, fixture and package tests on both. The example needs `fs.openAsBlob`, available since Node 19.8 and stable in Node 24. Files above 4 GiB need a `BlobLike` in Node 24 ([in-thread analyze](docs/api.md#in-thread-analyze)). The type declarations name DOM types such as `Worker` and `HTMLElement`, so a TypeScript project for Node needs `"DOM"` in `lib` or `skipLibCheck: true`.
|
|
101
|
+
In-thread `analyze()` is tested on Node 24, the version in `.nvmrc`, and on Node 22.12, the lowest supported version: CI runs the unit, fixture and package tests on both. The example needs `fs.openAsBlob`, available since Node 19.8 and stable in Node 24. Files above 4 GiB need a `BlobLike` in Node 24 ([in-thread analyze](docs/api.md#in-thread-analyze)). The type declarations of the root entry name DOM types such as `Worker` and `HTMLElement`, so a TypeScript project for Node that imports it needs `"DOM"` in `lib` or `skipLibCheck: true`. The server entries `video-fps-probe/header`, `video-fps-probe/url`, `video-fps-probe/check` and `video-fps-probe/presets` need neither.
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
A server can branch on `result.errorCode`, a machine-readable code for `error` such as `unrecognised`, `no-header`, `truncated` or `read-failed`, and on `result.layout`, which tells a faststart MP4 from one with its moov at the end or a fragmented one. `errorCodeOf(result)` also codes results of other engines and stored 0.3.0 JSON from their text, and `errorClass(code)` tells a file problem, which can be cached, from a read failure worth a retry ([error codes](docs/api.md#error-codes)).
|
|
104
|
+
|
|
105
|
+
A server that checks uploads in object storage can read only the header: `analyzeHeader(source, { limits })` from `video-fps-probe/header` reads the moov of an MP4 or MOV file under limits on the bytes, the reads, the time, the tracks and the top-level boxes, in one to three reads for most files, and gives the codec, size, rotation, duration and layout, and the frame count of a file that is not fragmented, without walking the samples. A fragmented file gets its duration from a sidx, the mfra index or mehd, and the error code `no-duration` when it has none of them ([header-only analysis](docs/api.md#header-only-analysis)).
|
|
106
|
+
|
|
107
|
+
`video-fps-probe/url` reads such an object over HTTP Range requests: `analyzeHeader(await openRangeSource(httpRange(presignedUrl)), { limits })`, or `rangeSource(fetcher, { size })` over the client of any object-storage SDK. It needs no SDK and makes one request per read, with no retries, and it checks the status, `Content-Range` and `Content-Length` of every reply before the body is read and caps the body while it streams, so a server that ignores the range or an object whose size changed fails the read as `read-failed` instead of returning wrong bytes. An `If-Match` with the object's ETag, which the caller sends, also catches an overwrite at the same size ([Range source](docs/api.md#range-source)).
|
|
108
|
+
|
|
109
|
+
CommonJS works on Node 22.12 and newer, the lowest supported version, which load ES modules from `require()`: `const { analyze } = require('video-fps-probe')`, the subpaths included, and a TypeScript project with `"module": "commonjs"` and `moduleResolution` `node10`, such as a NestJS 11 app. The five server entries, `video-fps-probe`, `video-fps-probe/header`, `video-fps-probe/url`, `video-fps-probe/check` and `video-fps-probe/presets`, also ship a minified CommonJS copy with `.d.cts` types for loaders that do not run `require(esm)`, so Jest 30 runs them with no configuration and `jest` as the only dependency, and with `--experimental-vm-modules` on Node 24.9 it loads the ESM build instead. The package tests check all of this from the packed tarball on Node 22.12 and 24. The other entries still need babel-jest in Jest: the tested configuration and a stub for unit tests are in [Jest](docs/api.md#jest).
|
|
104
110
|
|
|
105
111
|
## Cancellation, timeouts and failing open
|
|
106
112
|
|
|
@@ -131,12 +137,12 @@ Rules are JSON, such as `{ fps: { min: 24, max: 60 }, duration: { max: 10 }, vfr
|
|
|
131
137
|
|
|
132
138
|
<!-- tables:size:start -->
|
|
133
139
|
|
|
134
|
-
**0.
|
|
140
|
+
**0.66 KB gzip on the critical path**: the package code in the entry chunk of a Vite 8.3.5 app that imports `video-fps-probe/lazy` (1.40 KB with Vite's preload helper). The rest loads lazily, on the first file:
|
|
135
141
|
|
|
136
142
|
| Host Vite config | Critical path | First MP4 | First MKV | All own paths | Worker files |
|
|
137
143
|
|---|---:|---:|---:|---:|---:|
|
|
138
|
-
| `worker: { format: 'es' }` (recommended) | **0.
|
|
139
|
-
| none (Vite's default, 'iife') | **0.66 KB** | 24.
|
|
144
|
+
| `worker: { format: 'es' }` (recommended) | **0.66 KB** | 23.07 KB | 25.73 KB | 49.14 KB | 2 |
|
|
145
|
+
| none (Vite's default, 'iife') | **0.66 KB** | 24.85 KB | 27.19 KB | 48.75 KB | 2 |
|
|
140
146
|
|
|
141
147
|
Own runs on one worker per container family, picked by the first four bytes of the file, so a first file loads only the worker of its family. An iife worker cannot split, so without the option each own worker is one file with its lazy parts inlined:
|
|
142
148
|
|
|
@@ -146,9 +152,9 @@ import { defineConfig } from 'vite';
|
|
|
146
152
|
export default defineConfig({ worker: { format: 'es' } });
|
|
147
153
|
```
|
|
148
154
|
|
|
149
|
-
Engines cost nothing until imported. Importing a descriptor grows the critical path a little, from 0.
|
|
155
|
+
Engines cost nothing until imported. Importing a descriptor grows the critical path a little, from 0.66 KB to 0.89 KB with mp4box or mediabunny, 0.76 KB with rVFC and 1.01 KB with all three, and the engine itself loads lazily: mp4box 44.94 KB, mediabunny 54.17 KB, rVFC 2.19 KB. Gzip -9 of production builds from the packed tarball, budgeted in CI. Every configuration is in [docs/benchmarks.md](docs/benchmarks.md#package-size).
|
|
150
156
|
|
|
151
|
-
`video-fps-probe/react` adds 1.20 KB to the critical path for the provider, `useVideoFps` and its store (React itself not counted)
|
|
157
|
+
`video-fps-probe/react` adds 1.20 KB to the critical path for the provider, `useVideoFps` and its store (React itself not counted), and its first analysis also fetches a 0.43 KB summary module, in parallel with the probe client. `video-fps-probe/check` adds 3.99 KB when imported statically, and nothing lazily. One preset from `video-fps-probe/presets` adds 0.46 KB.
|
|
152
158
|
|
|
153
159
|
<!-- tables:size:end -->
|
|
154
160
|
|
|
@@ -156,9 +162,9 @@ Engines cost nothing until imported. Importing a descriptor grows the critical p
|
|
|
156
162
|
|
|
157
163
|
<!-- tables:accuracy-summary:start -->
|
|
158
164
|
|
|
159
|
-
**0 mismatches against ffprobe 8.1.1** on the
|
|
165
|
+
**0 mismatches against ffprobe 8.1.1** on the 168 committed fixtures: 3318 field checks, 3229 exact, 13 within tolerance (9 of them a definition difference by design: the measured average of a Matroska file that stores no duration for its last frame, which avgFps gives the median interval and ffprobe its own estimate), 2 known deviations with a recorded reason, 74 not applicable. The same comparison runs mp4box.js 2.4.1 and mediabunny 1.62.1 in 5 arms (their idiomatic recipes, mediabunny with a full scan, and this package's tuned adapters) and sorts each difference into wrong, definition differs, unverified, not provided or unsupported container. The [accuracy section](docs/benchmarks.md#accuracy-vs-ffprobe) has the outcomes and where the libraries win.
|
|
160
166
|
|
|
161
|
-
Of the
|
|
167
|
+
Of the 168 committed fixtures, 156 are synthetic files made with ffmpeg and 12 are real MediaRecorder recordings from headless Chromium, committed as recorded. Per-engine tables, speed and method: [docs/benchmarks.md](docs/benchmarks.md).
|
|
162
168
|
|
|
163
169
|
<!-- tables:accuracy-summary:end -->
|
|
164
170
|
|
|
@@ -170,8 +176,8 @@ Known limitations: [changelog](CHANGELOG.md#known-limitations), [benchmarks](doc
|
|
|
170
176
|
|
|
171
177
|
## Roadmap
|
|
172
178
|
|
|
173
|
-
- **0.3.x**: a bundler matrix in CI: webpack 5, Rspack, Next.js, Angular CLI, SvelteKit, Nuxt, Parcel, Bun and Deno, with pnpm, Yarn PnP and bun installs.
|
|
174
179
|
- **0.4.0**: a header-only ISO BMFF mode with byte, read and time limits, machine-readable error codes and a container layout field, a Range source for S3 and R2 style object storage (a fetcher function, no SDK dependency), minimal color, HDR and Dolby Vision metadata, the pixel aspect ratio and display size, a median frame interval that `check` uses, and a CommonJS build of the server entries only, while the browser entries stay ESM.
|
|
180
|
+
- **0.4.1**: a bundler matrix in CI: webpack 5, Rspack, Next.js, Angular CLI, SvelteKit, Nuxt, Parcel, Bun and Deno, with pnpm, Yarn PnP and bun installs.
|
|
175
181
|
- **0.5.0**: a Node entry (`fromPath`, DOM-free types, a `worker_threads` pool), NestJS validators, a CLI, bitrate, GOP and audio details, cadence detection, framework adapters for Vue, Svelte and Angular, and later MPEG-TS and HLS.
|
|
176
182
|
|
|
177
183
|
## Feedback
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FileErrorCode, FileResult, WarningCode } from '../core/types.js';
|
|
2
|
+
export type ErrorClass = 'file' | 'limit' | 'io' | 'environment' | 'internal';
|
|
3
|
+
export declare function errorCodeOf(result: Pick<FileResult, 'error'> & Partial<Pick<FileResult, 'errorCode' | 'engine'>>): FileErrorCode | null;
|
|
4
|
+
export declare function errorClass(code: FileErrorCode): ErrorClass;
|
|
5
|
+
export declare function warningCodeOf(warning: string): WarningCode | null;
|
|
6
|
+
export declare function warningCodesOf(result: Pick<FileResult, 'warnings' | 'videoTracks'>): WarningCode[];
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const ERROR_PREFIXES = [
|
|
2
|
+
['not a recognised container: empty file', 'empty-file'],
|
|
3
|
+
['not a recognised container', 'unrecognised'],
|
|
4
|
+
['unsupported: moov larger', 'too-large'],
|
|
5
|
+
['corrupt file: moof larger', 'too-large'],
|
|
6
|
+
['unsupported:', 'unsupported'],
|
|
7
|
+
['incomplete file: moov is truncated', 'truncated'],
|
|
8
|
+
['incomplete file: no moov', 'no-header'],
|
|
9
|
+
['incomplete header:', 'no-duration'],
|
|
10
|
+
['limit reached: maxBytes', 'byte-limit'],
|
|
11
|
+
['limit reached: maxReads', 'read-limit'],
|
|
12
|
+
['limit reached: maxMs', 'time-limit'],
|
|
13
|
+
['limit reached: maxTracks', 'structure-limit'],
|
|
14
|
+
['limit reached: maxTopLevelBoxes', 'structure-limit'],
|
|
15
|
+
['corrupt file:', 'corrupt'],
|
|
16
|
+
['file read failed', 'read-failed'],
|
|
17
|
+
['the own ', 'unsupported-container'],
|
|
18
|
+
['unsupported by ', 'unsupported-container'],
|
|
19
|
+
['container not read by', 'unsupported-container'],
|
|
20
|
+
['invalid EBML header', 'corrupt'],
|
|
21
|
+
['no Segment element', 'no-header'],
|
|
22
|
+
['no complete TrackEntry in Tracks', 'truncated'],
|
|
23
|
+
['no Tracks element found', 'no-header'],
|
|
24
|
+
];
|
|
25
|
+
const CLASS_OF = {
|
|
26
|
+
'empty-file': 'file',
|
|
27
|
+
unrecognised: 'file',
|
|
28
|
+
'unsupported-container': 'file',
|
|
29
|
+
unsupported: 'file',
|
|
30
|
+
'too-large': 'file',
|
|
31
|
+
'no-header': 'file',
|
|
32
|
+
truncated: 'file',
|
|
33
|
+
corrupt: 'file',
|
|
34
|
+
'no-duration': 'file',
|
|
35
|
+
'engine-failed': 'file',
|
|
36
|
+
'byte-limit': 'limit',
|
|
37
|
+
'read-limit': 'limit',
|
|
38
|
+
'time-limit': 'limit',
|
|
39
|
+
'structure-limit': 'limit',
|
|
40
|
+
'read-failed': 'io',
|
|
41
|
+
'load-failed': 'environment',
|
|
42
|
+
internal: 'internal',
|
|
43
|
+
};
|
|
44
|
+
const WARNING_PREFIXES = [
|
|
45
|
+
['invalid mdhd timescale ', 'invalid-timescale'],
|
|
46
|
+
['invalid timescale ', 'invalid-timescale'],
|
|
47
|
+
['ignored trailing data at ', 'trailing-data'],
|
|
48
|
+
['fragment index not read: ', 'fragment-index-unread'],
|
|
49
|
+
['format duration unresolved: ', 'duration-unresolved'],
|
|
50
|
+
['fragments ignored: ', 'fragments-ignored'],
|
|
51
|
+
['too many top-level boxes', 'structure-limit'],
|
|
52
|
+
];
|
|
53
|
+
const TRUNCATED_BOX = /^'(.{4})' at \d+ is truncated/;
|
|
54
|
+
export function errorCodeOf(result) {
|
|
55
|
+
if (result?.errorCode !== undefined)
|
|
56
|
+
return result.errorCode;
|
|
57
|
+
const error = result?.error;
|
|
58
|
+
if (error === null)
|
|
59
|
+
return null;
|
|
60
|
+
if (typeof error !== 'string')
|
|
61
|
+
return 'internal';
|
|
62
|
+
for (const [prefix, code] of ERROR_PREFIXES)
|
|
63
|
+
if (error.startsWith(prefix))
|
|
64
|
+
return code;
|
|
65
|
+
return result.engine !== undefined && result.engine !== 'own' ? 'engine-failed' : 'internal';
|
|
66
|
+
}
|
|
67
|
+
export function errorClass(code) {
|
|
68
|
+
return Object.hasOwn(CLASS_OF, code) ? CLASS_OF[code] : 'internal';
|
|
69
|
+
}
|
|
70
|
+
export function warningCodeOf(warning) {
|
|
71
|
+
if (typeof warning !== 'string')
|
|
72
|
+
return null;
|
|
73
|
+
for (const [prefix, code] of WARNING_PREFIXES)
|
|
74
|
+
if (warning.startsWith(prefix))
|
|
75
|
+
return code;
|
|
76
|
+
const box = TRUNCATED_BOX.exec(warning);
|
|
77
|
+
return box ? (box[1] === 'mdat' ? 'truncated-mdat' : 'truncated-box') : null;
|
|
78
|
+
}
|
|
79
|
+
export function warningCodesOf(result) {
|
|
80
|
+
const codes = new Set();
|
|
81
|
+
const listOf = (value) => (Array.isArray(value) ? value : []);
|
|
82
|
+
for (const w of [...listOf(result?.warnings), ...listOf(result?.videoTracks).flatMap((t) => listOf(t?.warnings))]) {
|
|
83
|
+
const code = warningCodeOf(w);
|
|
84
|
+
if (code)
|
|
85
|
+
codes.add(code);
|
|
86
|
+
}
|
|
87
|
+
return [...codes];
|
|
88
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ProbeResult } from './client/types.cjs';
|
|
2
|
+
import type { BlobLike } from './core/reader.cjs';
|
|
3
|
+
import type { AnalyzeOptions } from './core/types.cjs';
|
|
4
|
+
import type { EngineDescriptor } from './plugins/types.cjs';
|
|
5
|
+
export interface InThreadOptions extends AnalyzeOptions {
|
|
6
|
+
name?: string;
|
|
7
|
+
engine?: EngineDescriptor;
|
|
8
|
+
}
|
|
9
|
+
export declare function analyze(input: Blob | BlobLike, options?: InThreadOptions): Promise<ProbeResult>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { EngineId, FileResult, Rational } from '../core/types.cjs';
|
|
2
|
+
export type ComparedFieldName = 'avgFps' | 'nominalFps' | 'minFps' | 'maxFps' | 'frames' | 'durationSec';
|
|
3
|
+
export type ComparedValue = Rational | number | null;
|
|
4
|
+
export interface ComparedField<K extends EngineId> {
|
|
5
|
+
field: ComparedFieldName;
|
|
6
|
+
values: Partial<Record<K, ComparedValue>>;
|
|
7
|
+
agree: boolean | null;
|
|
8
|
+
differ: K[];
|
|
9
|
+
}
|
|
10
|
+
export interface Agreement<K extends EngineId> {
|
|
11
|
+
reference: K | null;
|
|
12
|
+
engines: K[];
|
|
13
|
+
fields: ComparedField<K>[];
|
|
14
|
+
agree: boolean | null;
|
|
15
|
+
}
|
|
16
|
+
export declare const RELATIVE_TOLERANCE = 0.003;
|
|
17
|
+
export declare const DURATION_TOLERANCE_SEC = 0.000001;
|
|
18
|
+
export declare function compareResults<K extends EngineId>(results: Partial<Record<K, FileResult>>, reference?: NoInfer<K>): Agreement<K>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FileErrorCode, FileResult, WarningCode } from '../core/types.cjs';
|
|
2
|
+
export type ErrorClass = 'file' | 'limit' | 'io' | 'environment' | 'internal';
|
|
3
|
+
export declare function errorCodeOf(result: Pick<FileResult, 'error'> & Partial<Pick<FileResult, 'errorCode' | 'engine'>>): FileErrorCode | null;
|
|
4
|
+
export declare function errorClass(code: FileErrorCode): ErrorClass;
|
|
5
|
+
export declare function warningCodeOf(warning: string): WarningCode | null;
|
|
6
|
+
export declare function warningCodesOf(result: Pick<FileResult, 'warnings' | 'videoTracks'>): WarningCode[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { EngineId } from '../core/types.cjs';
|
|
2
|
+
export type ProbeErrorCode = 'aborted' | 'timeout' | 'worker-failed' | 'engine-not-registered' | 'unknown-engine' | 'unsupported-environment' | 'protocol';
|
|
3
|
+
export interface ProbeErrorOptions {
|
|
4
|
+
engine?: EngineId | null;
|
|
5
|
+
cause?: unknown;
|
|
6
|
+
}
|
|
7
|
+
export declare class ProbeError extends Error {
|
|
8
|
+
readonly code: ProbeErrorCode;
|
|
9
|
+
readonly engine: EngineId | null;
|
|
10
|
+
readonly reason?: 'load' | 'crash';
|
|
11
|
+
constructor(code: ProbeErrorCode, message: string, options?: ProbeErrorOptions);
|
|
12
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ContainerKind, EngineId, FileResult } from '../core/types.cjs';
|
|
2
|
+
export interface VideoFpsSummary {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
name: string;
|
|
5
|
+
container: ContainerKind;
|
|
6
|
+
engine: EngineId;
|
|
7
|
+
fps: number | null;
|
|
8
|
+
fpsRational: string | null;
|
|
9
|
+
nominalFps: number | null;
|
|
10
|
+
frames: number | null;
|
|
11
|
+
durationSec: number | null;
|
|
12
|
+
width: number | null;
|
|
13
|
+
height: number | null;
|
|
14
|
+
rotation: number | null;
|
|
15
|
+
isVfr: boolean | null;
|
|
16
|
+
minFps: number | null;
|
|
17
|
+
maxFps: number | null;
|
|
18
|
+
approximate: boolean;
|
|
19
|
+
error: string | null;
|
|
20
|
+
warnings: string[];
|
|
21
|
+
}
|
|
22
|
+
export declare function summarize(r: FileResult): VideoFpsSummary;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ContainerKind, FileResult } from '../core/types.cjs';
|
|
2
|
+
export type RatioValue = number | `${number}/${number}` | `${number}:${number}`;
|
|
3
|
+
export type RatePolicy = 'allow' | 'warn' | 'reject';
|
|
4
|
+
export type FpsSource = 'avg' | 'ffprobe' | 'nominal' | 'dominant';
|
|
5
|
+
export type CheckVerdict = 'pass' | 'warn' | 'fail' | 'unknown';
|
|
6
|
+
export type CheckSeverity = 'fail' | 'warn' | 'unknown';
|
|
7
|
+
export interface RangeRule<T = number> {
|
|
8
|
+
min?: T;
|
|
9
|
+
max?: T;
|
|
10
|
+
}
|
|
11
|
+
export interface FpsRule extends RangeRule<RatioValue> {
|
|
12
|
+
exact?: RatioValue;
|
|
13
|
+
oneOf?: readonly RatioValue[];
|
|
14
|
+
source?: FpsSource;
|
|
15
|
+
}
|
|
16
|
+
export interface AspectRatioRule extends RangeRule<RatioValue> {
|
|
17
|
+
oneOf?: readonly RatioValue[];
|
|
18
|
+
}
|
|
19
|
+
export interface FrameSizeRule {
|
|
20
|
+
minWidth?: number;
|
|
21
|
+
maxWidth?: number;
|
|
22
|
+
minHeight?: number;
|
|
23
|
+
maxHeight?: number;
|
|
24
|
+
minShortSide?: number;
|
|
25
|
+
maxShortSide?: number;
|
|
26
|
+
minLongSide?: number;
|
|
27
|
+
maxLongSide?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface CheckRules {
|
|
30
|
+
fps?: FpsRule;
|
|
31
|
+
duration?: RangeRule;
|
|
32
|
+
frameSize?: FrameSizeRule;
|
|
33
|
+
aspectRatio?: AspectRatioRule;
|
|
34
|
+
pixels?: RangeRule;
|
|
35
|
+
fileSize?: RangeRule;
|
|
36
|
+
containers?: readonly ContainerKind[];
|
|
37
|
+
codecs?: readonly string[];
|
|
38
|
+
audio?: 'required' | 'forbidden';
|
|
39
|
+
vfr?: RatePolicy;
|
|
40
|
+
uneven?: RatePolicy;
|
|
41
|
+
severity?: 'fail' | 'warn';
|
|
42
|
+
}
|
|
43
|
+
export type CheckExpected = number | string | readonly (number | string)[] | null;
|
|
44
|
+
export interface CheckViolation {
|
|
45
|
+
rule: string;
|
|
46
|
+
severity: CheckSeverity;
|
|
47
|
+
expected: CheckExpected;
|
|
48
|
+
actual: number | string | boolean | null;
|
|
49
|
+
message: string;
|
|
50
|
+
}
|
|
51
|
+
export interface SuggestedFps {
|
|
52
|
+
value: number | null;
|
|
53
|
+
reason: string;
|
|
54
|
+
}
|
|
55
|
+
export interface CheckReport {
|
|
56
|
+
verdict: CheckVerdict;
|
|
57
|
+
violations: CheckViolation[];
|
|
58
|
+
suggestedFps?: SuggestedFps;
|
|
59
|
+
}
|
|
60
|
+
export declare function check(result: FileResult | null | undefined, rules: CheckRules): CheckReport;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=[3n,2n],t=3n,n=[24n,30n,48n,60n,120n],r=[...[24n,25n,30n,48n,50n,60n,100n,120n].map(e=>[e,1n]),...n.map(e=>[e*1000n,1001n])],i={min:`below the minimum`,max:`above the maximum`,exact:`not`,oneOf:`not one of`},a=Object.keys(i),o=[`fps`,`duration`,`frameSize`,`aspectRatio`,`pixels`,`fileSize`],s=[`allow`,`warn`,`reject`],c=[`avg`,`ffprobe`,`nominal`,`dominant`],l=[`own`,`mp4box`,`mediabunny`],u=[`min`,`max`],d=[`fileSize`,`containers`,`audio`],f={fps:[...u,`exact`,`oneOf`,`source`],duration:u,frameSize:[`Width`,`Height`,`ShortSide`,`LongSide`].flatMap(e=>u.map(t=>t+e)),aspectRatio:[...u,`oneOf`],pixels:u,fileSize:u},p=[...o,`containers`,`codecs`,`audio`,`vfr`,`uneven`,`severity`],invalid=(e,t,n)=>{throw TypeError(`${e}: ${typeof t==`number`?t:JSON.stringify(t)} is not ${n}`)};function decimal(e){let t=/^(\d+)(?:\.(\d+))?(?:e([+-]?\d+))?$/i.exec(e.trim());if(!t||!Number.isFinite(Number(t[0])))return null;let n=Number(t[3]??0)-(t[2]?.length??0);if(Math.abs(n)>400)return null;let r=BigInt(t[1]+(t[2]??``));return n>=0?[r*10n**BigInt(n),1n]:[r,10n**BigInt(-n)]}function ratio(e,t,n=!1){let r=typeof e==`number`||typeof e==`string`?String(e).split(/[/:]/):[],[i,a]=r.map(decimal),o=i&&r.length===1?i:i&&a&&r.length===2&&!n?[i[0]*a[1],i[1]*a[0]]:null,s=r.length>1?`; 'num/den' and 'num:den' ratios are for fps and aspectRatio`:``;return o&&o[1]!==0n?o:invalid(t,e,n?`a non-negative number${s}`:`a non-negative number or 'num/den'`)}const cmp=(e,t)=>e[0]*t[1]-t[0]*e[1],ntsc=(e,t)=>/[/:]/.test(String(e))?[]:n.filter(e=>t[0]*1000n==t[1]*e*999n).map(e=>[e*1000n,1001n]),readings=(e,t)=>{let n=ratio(e,t);return[n,...ntsc(e,n)]};function close(e,t,n,r,i){let a=e*(t[1]*n[0]-n[1]*t[0]);return(a<0n?-a:a)*i<=r*t[0]*n[0]}const whole=e=>Number.isSafeInteger(e)&&e>0,record=e=>!!e&&typeof e==`object`&&!Array.isArray(e);function primaryTrack(e){let t=e.videoTracks,n=e.primaryTrackId;return Array.isArray(t)?t.length===0?`no video track`:t.find(e=>record(e)&&typeof n==`number`&&e.trackId===n)||`no primary track: primaryTrackId ${n===void 0?`is missing`:`${JSON.stringify(n)} matches no video track`}`:`the result has no videoTracks list`}const errorKind=e=>typeof e==`string`?`blank`:Array.isArray(e)?`a list`:`of type ${typeof e}`,failedWith=e=>`the analysis failed${typeof e==`string`&&e.trim()?`: ${e}`:`, and its error is ${errorKind(e)}`}`,fromRational=e=>e&&whole(e.num)&&whole(e.den)?[BigInt(e.num),BigInt(e.den)]:null,gcd=(e,t)=>t===0n?e:gcd(t,e%t),value=e=>Number(e[0])/Number(e[1]),round=e=>+e.toFixed(3),micros=e=>(e[0]*2000000n+e[1])/(2n*e[1]);function digits(e,t){let n=3;for(;n<12&&t.some(t=>value(t).toFixed(n)===value(e).toFixed(n));)n++;return+value(e).toFixed(n)}function fraction(e){let t=gcd(e[0],e[1])||1n;return`${e[0]/t}/${e[1]/t}`}const fpsText=(e,t=[])=>e[0]%e[1]===0n?`${e[0]/e[1]}`:`${digits(e,t)} (${fraction(e)})`;function steady(e){let n=fromRational(e.avgFps),r=fromRational(e.nominalFps),i=e.presentedFrames??e.frames;if(!n||!r||!e.timestampsQuantized||e.isVfr!==!1||!whole(i))return null;let a=BigInt(i);return close(a,n,r,t,1000n)?[r,[a*r[1],r[0]]]:null}function resolution(e,n){let r=fromRational(e.avgFps),i=e.presentedFrames??e.frames;if(r&&e.timestampsQuantized&&e.isVfr===!1&&whole(i))return n?e=>close(1n,n[0],e,1n,10000000n):e=>close(BigInt(i),r,e,t,1000n)}function printedDuration(e,t){let n=e.durationSec,r=t.formatDurationSec,i=fromRational(e.dominantFps?.rate??(e.isVfr===!1?e.nominalFps:null)??e.avgFps),a=t.container===`webm`||t.container===`matroska`,[o,s]=[n,r].map(e=>typeof e==`number`?decimal(String(e)):null),c=o&&s&&abs(micros(o)-micros(s));return a&&i&&c!=null&&c*i[0]<=1000000n*i[1]?r:n??r}function judgedFps(e,t,n){let r=/^(\d+)\/(\d+)$/.exec(e.avgFpsFfprobe??``);return t===`ffprobe`?r&&fromRational({num:+r[1],den:+r[2]}):t===`nominal`?fromRational(e.nominalFps):t===`dominant`?fromRational(e.dominantFps?.rate??(e.isVfr===!1?e.nominalFps:null)):n?.[0]??fromRational(e.avgFps)}function unevenOf(e){let t=e.rateProfile;return t==null?e.isVfr===!1||e.minFps&&e.maxFps&&e.maxFps.value<=e.minFps.value*1.075?!1:null:record(t)?t.uneven:null}const abs=e=>e<0n?-e:e;function fit(t,n={}){let list=e=>e==null?[]:[e].flat().flatMap(e=>readings(e,`fps`)),[i]=list(n.min),a=list(n.max).reduce((e,t)=>e&&cmp(e,t)>0?e:t,void 0),inside=e=>(!i||cmp(e,i)>=0)&&(!a||cmp(e,a)<=0),nearer=(e,n)=>abs(cmp(e,t))*n[1]-abs(cmp(n,t))*e[1]||cmp(e,n),nearest=e=>e.filter(inside).reduce((e,t)=>e&&nearer(e,t)<=0n?e:t,null),divided=e=>{for(let n=2n;n<=8n;n++){let r=[t[0],t[1]*n];if(e(r))return[r,Number(n)]}},o=n.oneOf==null?null:list(n.oneOf),s=n.exact==null?o:list(n.exact).filter(e=>!o||o.some(t=>cmp(t,e)===0n));if(i&&a&&cmp(i,a)>0)return null;if(!s&&inside(t))return[t,0];let c=(s??r).filter(inside),l=!s&&c.length===0,u=l?i&&cmp(t,i)<0?i:a:nearest(c);if(!u)return null;let d=l?inside:e=>c.some(t=>cmp(t,e)===0n);return cmp(t,[e[0]*u[0],e[1]*u[1]])>=0&÷d(d)||[u,0]}function check(e,t){let known=(e,t,n)=>{(!t||typeof t!=`object`||Array.isArray(t))&&invalid(e,t,`an object`);let r=e===`rules`?``:`${e}.`;for(let e of Object.keys(t))if(!n.includes(e))throw TypeError(`${r}${e}: not a rule; expected one of ${n.join(`, `)}`)};known(`rules`,t,p);for(let e of o)t[e]!=null&&known(e,t[e],f[e]);let n=[[`fps.source`,t.fps?.source,c],[`audio`,t.audio,[`required`,`forbidden`]],[`vfr`,t.vfr,s],[`uneven`,t.uneven,s],[`severity`,t.severity,[`fail`,`warn`]]];for(let[e,t,r]of n)t!=null&&!r.includes(t)&&invalid(e,t,`one of ${r.join(`, `)}`);let r=[],u=t.severity===`warn`?`warn`:`fail`,m=e==null?`the analysis failed: no result`:e.error?failedWith(e.error):null,h=m??primaryTrack(e),g=typeof h==`string`?null:h,add=(e,t,n,i,a)=>r.push({rule:e,severity:t,expected:n,actual:i,message:`${e}: ${a}`}),unknown=(e,t)=>r.some(t=>t.rule===e)||add(e,`unknown`,null,null,`unknown, ${g||d.includes(e)?`no ${t}${m?`; ${m}`:``}`:h}`),limits=(e,t,n,r,o,s,c,l=``,d)=>{let f=a.filter(e=>t?.[e]!=null).map(n=>{let r=`${e}.${n}${l}`,i=(n===`oneOf`?[t[n]].flat():[t[n]]).map(t=>e===`fps`?readings(t,r):[ratio(t,r,e!==`aspectRatio`)]);return[n,t[n],i]});if(f.length!==0){if(!n)return void unknown(e,c);for(let[t,a,c]of f){let meets=e=>d?.(e)||(t===`min`?cmp(n,e)>=0n:t===`max`?cmp(n,e)<=0n:cmp(n,e)===0n),f=[a].flat(),p=f.length?`${f.join(`, `)}${s}`:`an empty list`,m=typeof o==`string`?o:o(c.flat());c.flat().some(meets)||add(`${e}.${t}${l}`,u,a,r,`${m}${s} is ${i[t]} ${p}`)}}},quantity=e=>typeof e==`number`?decimal(String(e)):null,allow=(e,t,n,r)=>{t!=null&&((!Array.isArray(t)||t.some(e=>typeof e!=`string`))&&invalid(e,t,`a list of strings`),typeof n!=`string`||!n||n===`unknown`?unknown(e,r):t.some(e=>e.toLowerCase()===n.toLowerCase())||add(e,u,t,n,`${n} is not one of ${t.length?t.join(`, `):`an empty list`}`))},policy=(e,n,r,i)=>{let a=t[e];return!a||a===`allow`?!1:(typeof n==`boolean`?n&&add(e,a===`warn`?`warn`:u,a,!0,i):unknown(e,r),n===!0)},_=g&&steady(g),v=g&&judgedFps(g,t.fps?.source,_),y=g&&(t.fps?.source??`avg`)===`avg`?resolution(g,_):void 0;limits(`fps`,t.fps,v,v?fraction(v):``,e=>fpsText(v,e),` fps`,`${t.fps?.source??`avg`} frame rate`,``,y);let b=_?.[1]??quantity(g&&printedDuration(g,e)),x=b&&[micros(b),1000000n],S=x?value(x):0;limits(`duration`,t.duration,x,S,e=>`${digits(x,e)}`,` s`,`duration`);let C=g?Math.abs(g.rotation)%180==90?[g.height,g.width]:[g.width,g.height]:[0,0],w=C.every(whole),[T,E]=w?C:[0,0],D={Width:T,Height:E,ShortSide:Math.min(T,E),LongSide:Math.max(T,E)},O=t.frameSize;for(let[e,t]of Object.entries(D))limits(`frameSize`,{min:O?.[`min${e}`],max:O?.[`max${e}`]},w?[BigInt(t),1n]:null,t,`${t}`,` px`,`frame size`,e);let k=[BigInt(T),BigInt(E)];if(limits(`aspectRatio`,t.aspectRatio,w?k:null,fraction(k),`${T}x${E} (${round(T/E)})`,``,`frame size`),limits(`pixels`,t.pixels,w?[BigInt(T)*BigInt(E),1n]:null,T*E,`${T*E}`,` px`,`frame size`),limits(`fileSize`,t.fileSize,quantity(e?.size),e?.size??``,`${e?.size}`,` bytes`,`file size`),allow(`containers`,t.containers,e?.container,`container`),allow(`codecs`,t.codecs,g?.codecName,`codec name`),t.audio){let n=e?.engine,r=l.includes(n),i=e?.audioTrackCount,a=!m&&r&&Number.isSafeInteger(i)&&i>=0?i>0:null,o=t.audio===`required`;a===null?unknown(`audio`,`audio track count${!r&&typeof n==`string`?` (the ${n} engine reports none)`:``}`):a!==o&&add(`audio`,u,t.audio,a,`${t.audio}, the file has ${o?`no`:`an`} audio track`)}let A=policy(`vfr`,g?.isVfr,`VFR flag`,`the frame rate is variable`),j=g?.rateProfile,M=record(j)&&typeof j.summary==`string`?`: ${j.summary}`:``,N=policy(`uneven`,g&&unevenOf(g),`${j==null?``:`uneven flag in the `}rate profile`,`the frame rate is uneven${M}`),P={verdict:`pass`,violations:r},F=r.some(e=>e.rule.startsWith(`fps.`));if(g&&(F||A||N)){let e=F?v:fromRational(g.dominantFps?.rate)??fromRational(g.nominalFps),n=Math.round(g.avgFps?.value??0),r=fit(e??[whole(n)?BigInt(n):0n,1n],t.fps),i=F?`${fpsText(v,r?[r[0]]:[])} fps is not allowed`:`the frame rate is ${A?`variable`:`uneven`}`;if(!r)P.suggestedFps={value:null,reason:`${i}, and no frame rate meets every bound of the fps rule`};else if(r[0][0]>0n){let[e,t]=r,n=t>1?`, which keeps 1 of every ${t} frames`:``;P.suggestedFps={value:value(e),reason:`${i}: ${F?`convert to`:`re-encode at a constant`} ${fpsText(e)} fps${n}`}}}return P.verdict=[`fail`,`unknown`,`warn`].find(e=>r.some(t=>t.severity===e))??`pass`,P}exports.check=check;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=require("./guards-Dqpjtkdn.cjs"),t=require("./reader-DNLR-_tm.cjs"),n=require("./rational-Dq5Vza-Q.cjs");var IsoBmffError=class extends Error{constructor(e,t){super(t),this.name=`IsoBmffError`,this.errorCode=e}};function readBoxHeader(e,n,r,i=0){let a=Math.min(e.byteLength,r);if(n+8>a)return null;let o=e.getUint32(n),s=t.n(e,n+4),c=8,l=o;if(o===1){if(n+16>a)return null;let t=e.getBigUint64(n+8);if(t>BigInt(2**53-1))throw new IsoBmffError(`corrupt`,`corrupt file: box '${s}' at ${i+n} has a size above 2^53-1`);l=Number(t),c=16}else o===0&&(l=r-n);if(s===`uuid`&&(c+=16),l<c)throw new IsoBmffError(`corrupt`,`corrupt file: box '${s}' at ${i+n} is smaller than its header`);let u=n+l;return{type:s,start:n,size:l,headerSize:c,end:Math.min(u,r),truncated:u>r}}function*childBoxes(e,t,n,r){let i=t;for(;i+8<=n;){let t;try{t=readBoxHeader(e,i,n)}catch(e){r?.push(e.message.replace(/^corrupt file: /,`ignored invalid child: `));return}if(!t)return;yield t,i=t.start+t.size}}function boxU64(e,t,n,r){let i=e.getBigUint64(t);if(i>BigInt(2**53-1))throw new IsoBmffError(`unsupported`,`unsupported: box '${n}' at ${r} holds a value above 2^53-1`);return Number(i)}function boxI64(e,t,n,r){let i=e.getBigInt64(t);if(i>BigInt(2**53-1)||i<-BigInt(2**53-1))throw new IsoBmffError(`unsupported`,`unsupported: box '${n}' at ${r} holds a value beyond 2^53-1`);return Number(i)}function boundEntries(e,t,n,r,i){let a=Math.max(0,Math.floor(t/n));return e>a?(i.push(`${r} declares ${e} entries but its box holds only ${a}`),a):e}let r=null;function videoTags(){if(r)return r;r=new Map;for(let e of`rawvideo:raw yuv22vuyyuvs|v210:v210|mjpeg:jpegmjpaAVDJAVRndmb1|mpeg4:mp4vDIVXXVID3IV2|h263:h263s263H263|dvvideo:dvcpdvc dvppdv5pdv5nAVdvAVd1dvhqdvhpdvh1dvh2dvh3dvh4dvh5dvh6|qtrle:rle |cinepak:cvid|svq1:SVQ1svq1svqi|svq3:SVQ3|vvc:vvc1vvi1|hevc:hev1hvc1dvhe|h264:avc1avc2avc3avc4ai5pai5qai52ai53ai55ai56ai1pai1qai12ai13ai15ai16AVinaivxdva1dvav|evc:evc1|vp8:vp08|vp9:vp09|av1:av01|mpeg1video:m1v m1v1mpegmp1v|mpeg2video:m2v1hdv1hdv2hdv3hdv4hdv5hdv6hdv7hdv8hdv9hdvamx5nmx5pmx4nmx4pmx3nmx3pxd51xd54xd55xd59xd5axd5bxd5cxd5dxd5exd5fxdv1xdv2xdv3xdv4xdv5xdv6xdv7xdv8xdv9xdvaxdvbxdvcxdvdxdvexdvfxdhdxdh2AVmpmp2v|jpeg2000:mjp2|png:png MNG |gif:gif |tiff:tiff|vc1:vc-1|cavs:avs2|avs3:avs3|dirac:drac|dnxhd:AVdnAVdh|prores:apchapcnapcsapcoap4hap4x|prores_raw:aprnaprh|hap:Hap1Hap5HapYHapAHapM|cfhd:CFHD|lcevc:lvc1|apv:apv1`.split(`|`)){let t=e.indexOf(`:`),n=e.slice(0,t);for(let i=t+1;i+4<=e.length;i+=4)r.set(e.slice(i,i+4),n)}return r}const i={32:`mpeg4`,33:`h264`,35:`hevc`,51:`vvc`,96:`mpeg2video`,97:`mpeg2video`,98:`mpeg2video`,99:`mpeg2video`,100:`mpeg2video`,101:`mpeg2video`,106:`mpeg1video`,108:`mjpeg`,109:`png`,110:`jpeg2000`,163:`vc1`,164:`dirac`,177:`vp9`};function isVideoFourcc(e){return videoTags().has(e)}function codecNameFor(e,t=null,n=!1){return t!==null&&i[t]?i[t]:e===`dvh1`&&n?`hevc`:videoTags().get(e)??`unknown`}const a=`no trex: sample defaults taken from tfhd`;function trunRecordSize(e){let t=0;for(let n=256;n<=2048;n<<=1)e&n&&(t+=4);return t}function parseTfhd(e,n,r,i,a,o,s=a+n-8){if(n+8>r)return null;let c=t.r(e,n+1),l=e.getUint32(n+4),u=i.trex.get(l),d=n+8,need=e=>d+e<=r,f=c&131072?a:o;if(c&1){if(!need(8))return null;f=boxU64(e,d,`tfhd`,s),d+=8}c&2&&(d+=4);let p=u?u.duration:null;if(c&8){if(!need(4))return null;p=e.getUint32(d),d+=4}let m=u?u.size:0;if(c&16){if(!need(4))return null;m=e.getUint32(d)}return{flags:c,trackId:l,baseDataOffset:f,defaultDuration:p,defaultSize:m,trexMissing:!u}}function parseTrun(e,n,r,i,a,o,s,c){let l=t.r(e,n+1),u=e.getUint32(n+4),d=n+8,f=a;l&1&&(f=i.baseDataOffset+e.getInt32(d),d+=4),l&4&&(d+=4);let p=trunRecordSize(l),m;p>0?m=boundEntries(u,r-d,p,`trun`,s):(m=u,m>c&&(s.push(`trun count ${u} limited to ${c}`),m=c));let h=p>0&&m<u,g=!!(l&256);if(!g&&i.defaultDuration===null&&m>0)throw new IsoBmffError(`corrupt`,`corrupt file: no sample duration for track ${i.trackId}`);let _=i.defaultDuration??0,v=g?new Uint32Array(m):null,y=l&2048?new Int32Array(m):null,b=!!(l&512),x=g?0:_*m,S=f,C=0,w=0;if(p>0)for(let t=0;t<m;t++){if(g){let n=e.getUint32(d);v[t]=n,x+=n,d+=4}let n=i.defaultSize;b&&(n=e.getUint32(d),d+=4),l&1024&&(d+=4),y&&(y[t]=e.getInt32(d),d+=4),S<o&&C++,w===t&&S+n<=o&&w++,S+=n}if(!b){let e=i.defaultSize,t=Math.max(0,o-f);C=e>0?Math.min(m,Math.ceil(t/e)):Math.min(m,t),w=e>0?Math.min(m,Math.floor(t/e)):C,S=f+e*m}return{fragment:{moofIndex:0,baseDts:null,count:m,durations:v,defaultDuration:_,cts:y,durationSum:x,present:C,complete:w,trexMissing:i.trexMissing,overrun:h},dataEnd:S}}function parseMoof(e,t,n,r,i,o){let s=n,c=!1,reject=e=>{i.ffprobeReject||={moof:r,reason:e}},l=new Map;for(let e of i.tracks)l.has(e.id)||l.set(e.id,e);for(let u of childBoxes(e,t.start+t.headerSize,t.end,i.warnings)){if(u.type!==`traf`)continue;let t=null,d,f=null,p=0;for(let m of childBoxes(e,u.start+u.headerSize,u.end,i.warnings)){let u=m.start+m.headerSize;if(m.type===`tfhd`){if(t=parseTfhd(e,u,m.end,i,n,s,n+m.start),!t)continue;t.trackId===0?reject(`rejects this file: tfhd with track_ID 0`):t.trexMissing||(c=!0),p=t.baseDataOffset,d=l.get(t.trackId),d||i.warnings.push(`fragment for unknown track ${t.trackId}`)}else if(m.type===`tfdt`&&u+8<=m.end)f=e.getUint8(u)===1&&u+12<=m.end?boxU64(e,u+4,`tfdt`,n+m.start):e.getUint32(u+4);else if(m.type===`trun`&&u+8<=m.end){if(c?(!t||t.trexMissing)&&reject(`misreads this file: a traf without trex is read as another track`):reject(`rejects this file: trun without a tfhd that has a trex`),!t||!d)continue;let n=d,{fragment:l,dataEnd:h}=parseTrun(e,u,m.end,t,p,i.fileSize,n.warnings,o);l.moofIndex=r,l.baseDts=f,f=null,p=h,s=h,l.trexMissing&&!n.warnings.includes(a)&&n.warnings.push(a),n.fragments.push(l)}}}}function parseSidx(e,t,n){let r=t.start+t.headerSize,i=r<t.end&&e.getUint8(r)===1;if(r+(i?32:24)>t.end)throw new IsoBmffError(`corrupt`,`corrupt file: box 'sidx' at ${n} is truncated`);let a=r+12,o=i?boxU64(e,a,`sidx`,n):e.getUint32(a),s=i?boxU64(e,a+8,`sidx`,n):e.getUint32(a+4);a+=i?16:8;let c=e.getUint16(a+2);a+=4;let l=n+(t.end-t.start),u=l+s,d=0,f=!1,p=[];for(let n=0;n<c&&a+12<=t.end;n++,a+=12){let t=e.getUint32(a);t&2147483648&&(f=!0);let n=t&2147483647;p.push([u,n]),u+=n,d+=e.getUint32(a+4)}return{start:n,end:l,referenceId:e.getUint32(r+4),timescale:e.getUint32(r+8),earliestPts:o,firstOffset:s,coverageEnd:u,durationSum:d,subsegments:p,hierarchical:f}}const o=2**53-1,s=[65536,0,0,0,65536,0,0,0,1<<30];function newMovie(e){return{fileSize:e,brand:null,compatibleBrands:[],hasFtyp:!1,hasMvhd:!1,timescale:1,duration:0,matrix:Int32Array.from(s),tracks:[],chapterTrackIds:new Set,hasMvex:!1,trex:new Map,moofCount:0,topLevel:[],truncated:!1,ffprobeMoofLimit:1/0,ffprobeReject:null,warnings:[]}}function newTrack(){return{id:0,enabled:!1,handler:``,timescale:0,hasMdhd:!1,mdhdDuration:0,matrix:Int32Array.from(s),sampleEntry:``,originalFormat:null,stsdCount:0,width:0,height:0,esdsObjectType:null,hasHvcc:!1,sttsCounts:new Uint32Array,sttsDeltas:new Uint32Array,cttsCounts:null,cttsOffsets:null,dtsShift:0,sampleCount:null,sampleSize:0,overruns:[],edits:[],fragments:[],warnings:[],dv:null,stss:null,stsz:null,stz2:null,stsc:null,stco:null,co64:null}}function children(e,t,n,r=0){return guard(childBoxes(e,t.start+t.headerSize+r,t.end,n.warnings),n,t.type)}function*guard(e,t,n){for(let r of e){if(r.truncated){if(t.strict)throw new IsoBmffError(`truncated`,`incomplete file: moov is truncated`);t.warnings.push(`'${r.type}' overruns '${n}'`)}yield r}}function payload(e){return e.start+e.headerSize}function readMatrix(e,t){let n=new Int32Array(9);for(let r=0;r<9;r++)n[r]=e.getInt32(t+r*4);return n}function normalizeTimescale(e){return e>0&&e<=2147483647?e:1}function tableRef(e){return{start:payload(e),end:e.end}}function parseStts(e,t,n){let r=payload(t);if(r+8>t.end)return;let i=e.getUint32(r+4),a=boundEntries(i,t.end-r-8,8,`stts`,n.warnings);a<i&&n.overruns.push(`stts`);let o=new Uint32Array(a),s=new Uint32Array(a),c=!1;for(let t=0;t<a;t++)o[t]=e.getUint32(r+8+t*8),s[t]=e.getUint32(r+12+t*8),s[t]>4294487295&&(c=!0);c&&correctLargeDeltas(o,s,n.warnings),n.sttsCounts=o,n.sttsDeltas=s}function correctLargeDeltas(e,t,n){let r=0n,i=0n;for(let a=0;a<e.length;a++){let o=BigInt(e[a]),s=t[a];if(s>4294487295){n.push(`stts delta ${s} clipped to 1`);let e=s|0;s=1,i+=BigInt(e<0?e:1)*o}else i+=BigInt(s)*o;if(r+=BigInt(s)*o,r>i){let e=(r-i)/(o>0n?o:1n),t=BigInt(s)>e?e:BigInt(s-1>>>0);r-=t*o,s=Number(BigInt(s)-t&4294967295n)}t[a]=s}}function parseCtts(e,t,n){let r=payload(t);if(r+8>t.end)return;let i=e.getUint32(r+4),a=boundEntries(i,t.end-r-8,8,`ctts`,n.warnings);if(a<i&&n.overruns.push(`ctts`),a===0)return;let o=[],s=[],c=0;for(let t=0;t<a;t++){let n=e.getInt32(r+8+t*8),a=e.getInt32(r+12+t*8);n<=0||(o.push(n),s.push(a),t+2<i&&a<0&&(c=Math.max(c,a===-2147483648?2147483647:-a)))}n.cttsCounts=Uint32Array.from(o),n.cttsOffsets=Int32Array.from(s),n.dtsShift=c}function parseElst(e,t,n,r,i){let a=payload(t);if(a+8>t.end)return;let s=e.getUint8(a),c=s===1?20:12,l=e.getUint32(a+4),u=t.end-a-8,d=l*c===u?l:Math.floor(u/c);if(d!==l&&n.warnings.push(`elst declares ${l} entries but its box holds ${d}`),d===0)return;n.edits.length&&(n.warnings.push(`duplicated elst: the last one is used`),n.edits=[]);let f=r.base+t.start;for(let t=0;t<d;t++){let r=a+8+t*c;if(s===1){let t=boxI64(e,r+8,`elst`,f),a=e.getBigInt64(r)===-1n;a&&(n.warnings.push(`unknown elst duration: ${t===-1?`the empty edit is ignored`:`the edit lasts to the end`}`),i.ffprobeReject??={moof:-1,reason:`rejects this file: an elst duration of -1`}),n.edits.push({duration:a?t===-1?0:o:boxU64(e,r,`elst`,f),mediaTime:t,rate:e.getInt32(r+16)/65536})}else n.edits.push({duration:e.getUint32(r),mediaTime:e.getInt32(r+4),rate:e.getInt32(r+8)/65536})}}function parseEsds(e,t,n){let r=t+4,descriptor=()=>{if(r+2>n)return null;let t=e.getUint8(r++),i=0;for(let t=0;t<4&&r<n;t++){let t=e.getUint8(r++);if(i=i*128+(t&127),!(t&128))break}return[t,i]},i=descriptor();if(i&&i[0]===3){if(r+3>n)return null;let t=e.getUint8(r+2);r+=3,t&128&&(r+=2),t&64&&r<n&&(r+=1+e.getUint8(r)),t&32&&(r+=2),i=descriptor()}return i&&i[0]===4&&r<n?e.getUint8(r):null}function parseStsd(e,n,r,i){let a=payload(n);if(a+8>n.end||(r.stsdCount=e.getUint32(a+4),r.stsdCount===0))return;let o;try{o=readBoxHeader(e,a+8,n.end)}catch(e){r.warnings.push(e.message);return}if(o&&(r.sampleEntry=o.type,!(r.handler===`soun`||o.start+86>o.end))){r.width=e.getUint16(o.start+32),r.height=e.getUint16(o.start+34);for(let n of childBoxes(e,o.start+86,o.end,i.warnings))if(n.type===`esds`)r.esdsObjectType=parseEsds(e,payload(n),n.end);else if(n.type===`hvcC`)r.hasHvcc=!0;else if(n.type===`sinf`)for(let a of childBoxes(e,payload(n),n.end,i.warnings))a.type===`frma`&&payload(a)+4<=a.end&&(r.originalFormat=t.n(e,payload(a)))}}function parseStbl(e,t,n,r){n.dv=e;for(let i of children(e,t,r)){let t=payload(i);switch(i.type){case`stsd`:parseStsd(e,i,n,r);break;case`stts`:parseStts(e,i,n);break;case`ctts`:parseCtts(e,i,n);break;case`stsz`:case`stz2`:t+12<=i.end&&(n.sampleCount=e.getUint32(t+8),n.sampleSize=i.type===`stsz`?e.getUint32(t+4):0),i.type===`stsz`?n.stsz=tableRef(i):n.stz2=tableRef(i);break;case`stss`:n.stss=tableRef(i),t+8<=i.end&&e.getUint32(t+4)>Math.floor((i.end-t-8)/4)&&n.overruns.push(`stss`);break;case`stsc`:n.stsc=tableRef(i);break;case`stco`:n.stco=tableRef(i);break;case`co64`:n.co64=tableRef(i)}}}function parseMdia(e,n,r,i){for(let a of children(e,n,i)){let n=payload(a);if(a.type===`mdhd`&&n+24<=a.end){let t=e.getUint8(n)===1;if(t&&n+32>a.end)continue;r.hasMdhd=!0;let i=e.getUint32(n+(t?20:12));if(r.timescale=normalizeTimescale(i),r.timescale!==i&&r.warnings.push(`invalid mdhd timescale ${i}, using 1`),t){let t=e.getBigUint64(n+24);r.mdhdDuration=t===18446744073709551615n?0:Number(t)}else{let t=e.getUint32(n+16);r.mdhdDuration=t===4294967295?0:t}}else if(a.type===`hdlr`&&n+12<=a.end)r.handler=t.n(e,n+8);else if(a.type===`minf`)for(let t of children(e,a,i))t.type===`stbl`&&parseStbl(e,t,r,i)}}function parseTrak(e,t,n,r){let i=newTrack();for(let a of children(e,t,r)){let t=payload(a);if(a.type===`tkhd`&&t+84<=a.end){let n=e.getUint8(t)===1;if(n&&t+96>a.end)continue;i.enabled=(e.getUint8(t+3)&1)==1,i.id=e.getUint32(t+(n?20:12)),i.matrix=readMatrix(e,t+(n?52:40))}else if(a.type===`edts`)for(let t of children(e,a,r))t.type===`elst`&&parseElst(e,t,i,r,n);else if(a.type===`mdia`)parseMdia(e,a,i,r);else if(a.type===`tref`){for(let t of children(e,a,r))if(t.type===`chap`)for(let r=payload(t);r+4<=t.end;r+=4)n.chapterTrackIds.add(e.getUint32(r))}}return i}function parseMoov(e,t,n,r,i=0){let a={strict:r,warnings:n.warnings,base:i};for(let r of children(e,t,a)){let t=payload(r);switch(r.type){case`mvhd`:{if(t+24>r.end)break;let i=e.getUint8(t)===1;if(i&&t+84>r.end)break;n.hasMvhd=!0,n.timescale=normalizeTimescale(e.getUint32(t+(i?20:12))),n.duration=i?Number(e.getBigUint64(t+24)):e.getUint32(t+16),t+(i?84:72)<=r.end&&(n.matrix=readMatrix(e,t+(i?48:36)));break}case`trak`:n.tracks.push(parseTrak(e,r,n,a));break;case`mvex`:n.hasMvex=!0;for(let t of children(e,r,a)){let r=payload(t);t.type===`trex`&&r+24<=t.end&&n.trex.set(e.getUint32(r+4),{duration:e.getUint32(r+12),size:e.getUint32(r+16)})}break;case`cmov`:throw new IsoBmffError(`unsupported`,`unsupported: compressed movie header (cmov)`)}}}const c=new Set([`moof`,`styp`,`sidx`,`ssix`,`emsg`,`prft`,`free`,`skip`,`uuid`]);function newPrefetch(){return{windows:[],hints:[],next:0,mfraTried:!1}}function prefetched(e,t,n){for(let[r,i]of e.windows)if(t>=r&&t+n<=r+i.byteLength)return new DataView(i.buffer,i.byteOffset+t-r,n);return null}async function prefetchNext(e,t,n,r){for(;t.next<t.hints.length&&t.hints[t.next]<=n;)t.next++;let i=[];for(let n=t.next;n<t.hints.length&&i.length<8;n++)t.hints[n]<e.size&&i.push([t.hints[n],Math.min(r,e.size-t.hints[n])]);if(t.next+=i.length,!i.length)return;let a=await e.readMany(i,4);t.windows=i.map(([e],t)=>[e,new DataView(a[t].buffer,a[t].byteOffset,a[t].byteLength)]),t.windows.every(([,e])=>startsWithFragmentBox(e))||(t.hints=[],t.next=0)}function startsWithFragmentBox(e){return e.byteLength>=8&&e.getUint32(0)>=8&&c.has(t.n(e,4))}function parseTfraOffsets(e,t,n,r,i=2**53-1){for(let a of childBoxes(e,t,n)){let t=a.start+a.headerSize;if(a.type!==`tfra`||t+16>a.end)continue;let n=e.getUint8(t)===1,o=e.getUint32(t+8),s=(n?16:8)+(o>>4&3)+(o>>2&3)+(o&3)+3,c=Math.min(e.getUint32(t+12),Math.floor((a.end-t-16)/s));for(let a=0;a<c;a++){let o=t+16+a*s,c=n?e.getBigUint64(o+8):BigInt(e.getUint32(o+4));c>0n&&c<BigInt(i)&&r.add(Number(c))}}}async function mfraMoofOffsets(e){try{return await readMfraOffsets(e)}catch(t){if(e.signal?.aborted||t?.name===`AbortError`)throw t;return[]}}async function readMfraOffsets(e){let n=e.size,r=Math.min(n,65536);if(r<16)return[];let i=await e.view(n-r,r,0);if(t.n(i,r-12)!==`mfro`)return[];let a=i.getUint32(r-4);if(a<24||a>Math.min(n,16777216))return[];let o=a<=r?new DataView(i.buffer,i.byteOffset+r-a,a):await e.view(n-a,a,0),s;try{s=readBoxHeader(o,0,a)}catch{return[]}if(!s||s.type!==`mfra`)return[];let c=new Set;return parseTfraOffsets(o,s.headerSize,s.end,c,n),[...c].sort((e,t)=>e-t)}function rescaleRound(e,t,n){if(!(n>0))return 0;let r=BigInt(Math.abs(e))*BigInt(t),i=BigInt(n),a=Number((r+i/2n)/i);return e<0?-a:a}function sampleTotals(e){let t=0,n=0;for(let r=0;r<e.sttsCounts.length;r++)t+=e.sttsCounts[r],n+=e.sttsCounts[r]*e.sttsDeltas[r];let r=0;for(let t of e.fragments)r+=t.count;return{moovSamples:t,moovDuration:n,fragmentSamples:r}}function sumBig(e,t,n){let r=0n,i=0n;if(n){for(let t=0;t<e.sttsCounts.length;t++)r+=BigInt(e.sttsCounts[t]),i+=BigInt(e.sttsCounts[t])*BigInt(e.sttsDeltas[t]);i<=0n&&(r=0n,i=0n)}for(let n of e.fragments)t(n.moofIndex,n.trexMissing)&&(r+=BigInt(n.count),i+=BigInt(n.durationSum));return[r,i]}function rateOf(e,t,r){return t<=0n||r<=0n?null:n.a(BigInt(e)*t,r,n.t)}function averageRates(e,t){let r=e.timescale,visible=(e,n)=>e<t.ffprobeMoofLimit&&!n,[i,a]=sumBig(e,()=>!0,!0),[o,s]=sumBig(e,visible,!0),c=e.overruns.includes(`stts`)||e.fragments.some(e=>e.overrun&&visible(e.moofIndex,e.trexMissing))?null:rateOf(r,o,s);return{avg:rateOf(r,i,a),ffprobe:c?n.o(c):null}}function missingMandatoryAtoms(e){let t=e.dv,declared=e=>t&&e&&e.start+8<=e.end?t.getUint32(e.start+4):0,n=declared(e.co64??e.stco),r=e.sttsCounts.length>0||e.overruns.includes(`stts`),i=e.sampleCount??0;return n>0&&(!r||declared(e.stsc)===0||e.sampleSize===0&&i===0)||i>0&&n===0}function editInfo(e,t,n){let r=0,i=0,a=0,s=null,c=(e.cttsOffsets?.[0]??0)+n.moovDuration;for(let n of e.edits){let l=n.duration===o?Math.max(0,c-n.mediaTime):rescaleRound(n.duration,e.timescale,t);i+=l,n.mediaTime===-1?s||(r+=l):(a++,s||={mediaTime:n.mediaTime,duration:l,rate:n.rate})}return{emptyBefore:r,firstMedia:s,editSum:i,mediaEdits:a}}function trackTiming(e,t,n,r){let i=e.mdhdDuration;n.moovDuration>0&&(i=Math.min(i,n.moovDuration));let a=e.edits.length>0&&n.moovSamples>0;a&&(i=Math.min(i,r.editSum));let o=n.moovDuration,s=null,c=timeOffsetOf(e,t);for(let t of e.fragments)t.baseDts!==null&&(o=t.baseDts),s===null&&t.count>0&&(s=o-c+(t.cts?t.cts[0]:0)),o+=t.durationSum,o>i&&(i=o);let l=completeSidxEnd(e,t);l!==null&&l>i&&(i=l);let u=null;return u=a?r.emptyBefore:n.moovSamples>0?e.cttsOffsets&&e.cttsOffsets.length?e.cttsOffsets[0]:0:s,{durationTs:i,startPts:u}}function completeSidxEnd(e,t){if(t.ffprobeMoofLimit===1/0)return null;let n=0,r=null;for(let i of t.topLevel){if(i.type===`moof`&&++n>t.ffprobeMoofLimit)break;let a=i.sidx;a&&!a.hierarchical&&a.referenceId===e.id&&a.timescale===e.timescale&&(r=a.earliestPts+a.durationSum)}return r}function timeOffsetOf(e,t){let n=0,r=0,i=0;return e.edits.forEach((e,t)=>{t===0&&e.mediaTime===-1?(n=e.duration,i=1):t===i&&e.mediaTime>=0&&(r=e.mediaTime)}),!n&&!r?0:r-rescaleRound(n,e.timescale,t.timescale)}function runsOf(e,t,n){let r=new Float64Array(e.length+1),i=new Float64Array(e.length),a=0,o=0;for(let s=0;s<e.length&&o<n;s++)e[s]<=0||(r[a]=o,i[a]=t[s],o+=Math.min(e[s],n-o),a++);return r[a]=o,{count:a,starts:r,values:i,total:o}}function runFinder(e){let t=e.starts,n=0;return r=>{if(r>=t[n]&&r<t[n+1])return n;let i=0,a=e.count-1;for(;i<a;){let e=Math.ceil((i+a)/2);t[e]<=r?i=e:a=e-1}return n=i,i}}function keyIndex(e,t){let n=e.stss;if(!n||!e.dv)return{all:!0,list:new Float64Array};let r=e.dv,i=n.start+8<=n.end?Math.min(r.getUint32(n.start+4),Math.floor((n.end-n.start-8)/4)):0;if(i===0)return{all:!1,list:Float64Array.of(0)};let a=+(r.getUint32(n.start+8)>0),o=new Float64Array(i),s=0,c=-1;for(let e=0;e<i;e++){let i=r.getUint32(n.start+8+e*4)-a;if(i<=c||i>=t)break;o[s++]=i,c=i}return{all:!1,list:o.subarray(0,s)}}function nextKeyOf(e,t,n){if(n>=t)return-1;if(e.all)return n;let r=e.list,i=0,a=r.length;for(;i<a;){let e=Math.floor((i+a)/2);r[e]<n?i=e+1:a=e}return i<r.length?r[i]:-1}function prevKeyOf(e,t){if(t<0)return-1;if(e.all)return t;let n=e.list,r=0,i=n.length;for(;r<i;){let e=Math.floor((r+i)/2);n[e]<=t?r=e+1:i=e}return r>0?n[r-1]:-1}function keep(e,t,n){let r=n;r<=0||e.len===0&&(e.first=t,e.last=t,e.len=1,r--,r===0)||((e.len>=2&&e.last!==e.first||r>=2&&t!==e.first)&&(e.bad=!0),e.last=t,e.len+=r)}function keepRange(e,t,n,r,i){let a=r,o=Math.min(i,t.total-r),s=0;for(;o>0&&!e.bad;){let r=n(a),i=Math.min(t.starts[r+1]-a,o);keep(e,t.values[r],i),a+=i,o-=i,s++}return s}function keptRate(e,t){return e.bad||e.len===0||!(e.first>0)?null:n.a(t,e.first,n.t)}function ffprobeStage1Rate(e,t,r,i,a){let o=Math.min(r,e.sampleCount??0);if(o<=0||e.sttsCounts.length===0||e.overruns.length>0)return null;let s=e.timescale,c=runsOf(e.sttsCounts,e.sttsDeltas,o),l=runFinder(c),u={len:0,first:0,last:0,bad:!1};if(e.edits.length===0)return keepRange(u,c,l,0,o),keptRate(u,s);if(!e.edits.some(e=>e.mediaTime!==-1))return null;let d=!0;for(let e=1;e<c.count&&d;e++)d=c.values[e]===c.values[0];if(d)return c.values[0]>0?n.a(s,c.values[0],n.t):null;let f=Math.min(i,2e6),p=0,m=!1,spend=e=>(p+=e,p>f&&(m=!0),m),h=new Float64Array(c.count+1);for(let e=0;e<c.count;e++)h[e+1]=h[e]+(c.starts[e+1]-c.starts[e])*c.values[e];let dtsAt=e=>{let t=l(e);return h[t]+(e-c.starts[t])*c.values[t]},durAt=e=>c.values[l(e)],g=!!e.cttsCounts&&e.cttsCounts.length>0,_=g?runsOf(e.cttsCounts,e.cttsOffsets,o):null,v=_?runFinder(_):null,offAt=e=>_&&e<_.total?_.values[v(e)]:0,y=keyIndex(e,o),nextKey=e=>nextKeyOf(y,o,e),prevKey=e=>prevKeyOf(y,e),b=e.dtsShift,firstAtOrAbove=(e,t)=>{let n=0,r=t;for(;n<r;){let t=Math.floor((n+r)/2);dtsAt(t)>=e?r=t:n=t+1}return n},search=(e,t)=>{let n=b>0?e-b:e,r=-1,i=o;for(dtsAt(o-1)-b<n&&(r=o-1);i-r>1;){let e=Math.floor((r+i)/2),t=dtsAt(e)-b;t>=n&&(i=e),t<=n&&(r=e)}let a=t?r:prevKey(r);if(a>0){let e=firstAtOrAbove(dtsAt(a),a);if(e<a){if(t)a=e;else{let t=nextKey(e);t>=0&&t<a&&(a=t)}}}if(g&&a>=0){let e=prevKey(a);for(;e>=0&&!spend(1)&&dtsAt(e)-b+offAt(e)>n;)e=prevKey(e-1);a=e}return a},x=0,S=!1;for(let n of e.edits){let r=n.mediaTime;if(!S&&r===-1)continue;if(S=!0,spend(64))break;let i=rescaleRound(n.duration,e.timescale,t.timescale),a=search(r,!1);if(a<0&&!m&&(a=search(r,!0)),m)break;a<0&&(a=0,x=0),g&&(x=a);let s=-1;if(g){let e=!1;for(let t=nextKey(a);t>=0&&!spend(1);t=nextKey(t+1)){let n=t+1<o?durAt(t):i;if(dtsAt(t)+offAt(t)+n>=i+r){if(e){s=t;break}e=!0}}if(m)break}else{let e=a,t=o;for(;e<t;){let n=Math.floor((e+t)/2);(n+1<o?dtsAt(n+1):dtsAt(o-1)+i)>=i+r?t=n:e=n+1}e<o&&(s=nextKey(e))}let d=s<0?o-a:s-a+1,f=Math.min(d,o-x);if(f>0&&(spend(keepRange(u,c,l,x,f)),x+=f),u.bad)return null;if(m)break}return m?(a?.push(`r_frame_rate not reproduced: edit list walk limit reached`),null):keptRate(u,s)}function cutRuns(e,t,n){let r=[],i=[],a=n;for(let n=0;n<e.length&&a>0;n++){let o=Math.min(e[n],a);o<=0||(r.push(o),i.push(t[n]),a-=o)}return{counts:r,values:i}}function timelineSegments(e,t){let n=[];if(t>0){let r=cutRuns(e.sttsCounts,e.sttsDeltas,t),i=e.cttsCounts&&e.cttsOffsets?cutRuns(e.cttsCounts,e.cttsOffsets,t):null;n.push({durations:{counts:r.counts,deltas:r.values},cts:i?{counts:i.counts,offsets:i.values}:null})}for(let t of e.fragments){let e=t.complete;n.push({baseDts:t.baseDts,durations:t.durations?t.durations.subarray(0,e):{counts:[e],deltas:[t.defaultDuration]},cts:t.cts?t.cts.subarray(0,e):null})}return n}function presentationWindow(e,t,n){let r=t.firstMedia;if(!r)return e.edits.length&&n.push(`edit list has no media edit`),null;t.mediaEdits>1&&n.push(`edit list has ${t.mediaEdits} media edits, only the first is applied`),r.rate!==1&&n.push(`edit rate ${r.rate} is not applied`);let i=e.fragments.length>0||r.duration<=0?r.mediaTime:r.mediaTime+r.duration;return{start:r.mediaTime,end:i}}function moovSamplesInFile(e,t,n){let r=e.dv,i=e.co64??e.stco,a=e.stsz??e.stz2;if(!r||!i||!a||!e.stsc||i.start+8>i.end||e.stsc.start+8>e.stsc.end||a.start+12>a.end)return null;let o=!!e.co64,s=Math.min(r.getUint32(i.start+4),Math.floor((i.end-i.start-8)/(o?8:4))),c=Math.min(r.getUint32(e.stsc.start+4),Math.floor((e.stsc.end-e.stsc.start-8)/12)),l=e.stsz?r.getUint32(a.start+4):0,u=e.stz2?r.getUint8(a.start+7):32,d=Math.min(r.getUint32(a.start+8),n),f=a.end-a.start-12,sizeOf=e=>{if(l)return l;let t=a.start+12;if(u===32)return(e+1)*4<=f?r.getUint32(t+e*4):0;if(u===16)return(e+1)*2<=f?r.getUint16(t+e*2):0;if(u===8)return e<f?r.getUint8(t+e):0;let n=e>>1<f?r.getUint8(t+(e>>1)):0;return e&1?n&15:n>>4},p=0,m=0,h=0,g=0;for(let n=0;n<s&&h<d;n++){for(;g+1<c&&r.getUint32(e.stsc.start+8+(g+1)*12)<=n+1;)g++;let a=c?r.getUint32(e.stsc.start+12+g*12):0,s=o?Number(r.getBigUint64(i.start+8+n*8)):r.getUint32(i.start+8+n*4);for(let e=0;e<a&&h<d;e++,h++){let e=sizeOf(h);s<t&&p++,s+e<=t&&m===h&&m++,s+=e}}return{present:p,complete:m}}function displayRotation(e,t){let n=[16n,16n,30n],r=new Int32Array(9);for(let i=0;i<3;i++)for(let a=0;a<3;a++){let o=0n;for(let r=0;r<3;r++)o+=BigInt(e[i*3+r])*BigInt(t[r*3+a])>>n[r];r[i*3+a]=Number(BigInt.asIntN(32,o))}if(r[0]===65536&&r[4]===65536&&r[8]===1<<30&&!r[1]&&!r[2]&&!r[3]&&!r[5]&&!r[6]&&!r[7])return 0;let i=r[0]/65536,a=r[1]/65536,o=Math.hypot(i,r[3]/65536),s=Math.hypot(a,r[4]/65536);return o===0||s===0?0:Math.trunc(-(Math.atan2(a/s,i/o)*180/Math.PI))||0}const l=16777216,u=65536;function isAbort(e,t){return t.signal?.aborted===!0||e?.name===`AbortError`}function containerOf(e){return e.brand===`qt `||!e.hasFtyp&&e.topLevel.length>0?`mov`:`mp4`}async function readMoov(e,t,n){let r=e.size,i=t.start+t.size>r;if(!i&&t.size>268435456)throw new IsoBmffError(`too-large`,`unsupported: moov larger than 256 MiB`);let a=i?Math.min(r-t.start,l):t.size,o=await e.view(t.start,a,0),s=readBoxHeader(o,0,o.byteLength);if(!s)throw new IsoBmffError(`truncated`,`incomplete file: moov is truncated`);parseMoov(o,s,n,i,t.start),i&&n.warnings.push(`moov size exceeds the file; parsed what is there`)}async function walk(e,t,n){let r=e.size,i=0,a=t.readAhead.head,o=!1,s=0,c=!1,l=newPrefetch(),look=async(t,n,r)=>prefetched(l,t,n)??e.view(t,n,r);for(;i+8<=r;){if(e.checkAbort(),++s>4e6){n.warnings.push(`too many top-level boxes`);break}let d=c&&!o&&r-i<=16777216?r-i:a,f=await look(i,Math.min(16,r-i),d),p;try{p=readBoxHeader(f,0,r-i,i)}catch(e){if(!o)throw e;n.warnings.push(`ignored trailing data at ${i}: ${e.message}`);break}if(!p)break;let m={...p,start:i,end:Math.min(i+p.size,r)},h=i+p.size,g=p.truncated&&o&&m.type!==`mdat`&&m.type!==`moof`;p.truncated&&(n.truncated=!0,n.warnings.push(g?`ignored trailing data at ${i}: not a box`:`'${m.type}' at ${i} is truncated`));let _={type:m.type,start:i,end:m.end,sidx:null};if(n.topLevel.push(_),g)break;if(m.type===`ftyp`){let t=await e.view(i,Math.min(m.size,1024)),r=m.headerSize;if(r+4<=t.byteLength){n.hasFtyp=!0,n.brand=String.fromCharCode(t.getUint8(r),t.getUint8(r+1),t.getUint8(r+2),t.getUint8(r+3));for(let e=r+8;e+4<=t.byteLength;e+=4)n.compatibleBrands.push(String.fromCharCode(t.getUint8(e),t.getUint8(e+1),t.getUint8(e+2),t.getUint8(e+3)))}}else if(m.type===`moov`)o?n.warnings.push(`ignored a second moov at ${i}`):(n.moov=[i,p.size],await readMoov(e,m,n),o=!0);else if(m.type===`moof`){if(!o)throw new IsoBmffError(`corrupt`,`corrupt file: movie fragment (moof) before moov`);if(m.size>67108864)throw new IsoBmffError(`too-large`,`corrupt file: moof larger than 64 MiB at ${i}`);let s=Math.min(m.size+16,r-i),c=await look(i,s,0),d=readBoxHeader(c,0,Math.min(m.size,c.byteLength));d&&parseMoof(c,d,i,n.moofCount,n,t.maxExpandedSamples),n.moofCount++;let f=0;try{let e=readBoxHeader(c,m.size,c.byteLength);e&&e.type===`mdat`&&(f=e.size)}catch{f=0}a=f<=u?t.readAhead.fragment:Math.min(65536,Math.max(1024,2*m.size+64)),f>u&&(!l.hints.length&&!l.mfraTried&&(r-i)/(m.size+f)>16&&(l.mfraTried=!0,l.hints=await mfraMoofOffsets(e)),l.next<l.hints.length&&!prefetched(l,i+m.size+f,16)&&await prefetchNext(e,l,i,a)),t.onProgress&&n.moofCount%64==0&&t.onProgress({phase:`fragments`,done:i,total:r})}else if(m.type===`sidx`&&!p.truncated){let e=await look(i,m.size,0),t=readBoxHeader(e,0,e.byteLength);t&&(_.sidx=parseSidx(e,t,i)),_.sidx&&_.sidx.subsegments.length>1&&!l.hints.length&&(l.hints=_.sidx.subsegments.map(([e])=>e))}else if(m.type===`mfra`)break;c=m.type===`mdat`,i=h}if(!o)throw new IsoBmffError(`no-header`,`incomplete file: no moov`)}async function emulateFfprobeExtent(e,t){let n=e.size,r=new Set(t.tracks.map(e=>e.id)),i=!1,a=!1,o=!1,s=null,c=0;for(let l of t.topLevel){if(l.type===`moof`)c++;else if(l.type===`moov`)i=!0;else if(l.type===`mdat`&&l.end-l.start>8)a=!0;else if(l.type===`sidx`&&i&&l.sidx&&!l.sidx.hierarchical&&r.has(l.sidx.referenceId)){let t=l.sidx.coverageEnd===n;!t&&n>=4&&(s===null&&(s=(await e.view(n-4,4,0)).getUint32(0)),t=l.sidx.coverageEnd===n-s),t&&l.sidx.firstOffset===0&&(o=!0)}if(i&&a&&(o||l.end===n)){t.ffprobeMoofLimit=c;return}}}function layoutOf(e){let t=1/0,n=!1,r=!1;for(let i of e.topLevel)i.type===`mdat`&&(t=Math.min(t,i.start)),n||=i.type===`moof`,r||=!n&&i.type===`sidx`;let i=e.moov,a=i?i[0]:1/0;if(a===t)return null;let o=e.hasMvex?`fragmented`:t<a?`moov-at-end`:`faststart`;return{kind:o,moovOffset:i?a:null,moovSize:i?i[1]:null,sidx:r,emptyMoov:o===`fragmented`&&e.tracks.every(t=>!isVideoTrack(t,e)||!t.sampleCount)}}function isVideoTrack(e,t){return t.chapterTrackIds.has(e.id)?!1:e.handler===`vide`||e.handler!==`soun`&&isVideoFourcc(e.originalFormat??e.sampleEntry)}const d=[`presentedFrames`,`minFps`,`maxFps`,`medianFrameInterval`,`isVfr`,`vfrRatio`,`distinctDeltas`,`duplicateTimestamps`,`nominalFps`,`dominantFps`,`timestampsQuantized`];function buildVideoTrack(t,r,i,a,o){let s=e.l(a.reportedId),c=s.warnings;a.reportedId!==t.id&&c.push(`duplicate track_ID ${t.id}: reported as ${a.reportedId}`),c.push(...t.warnings);let l=t.handler===`auxv`;l&&c.push(`auxiliary video track (handler 'auxv')`);let u=missingMandatoryAtoms(t);u&&c.push(`missing mandatory sample tables: ffprobe ignores the samples and timing of this track`);let f=t.hasMdhd?t.timescale:r.timescale;t.hasMdhd||(c.push(`no mdhd: using the movie timescale`),t.timescale=f),s.fourccOrCodecId=t.sampleEntry,s.codecName=codecNameFor(t.originalFormat??t.sampleEntry,t.esdsObjectType,t.hasHvcc),t.originalFormat&&c.push(`encrypted: ${t.sampleEntry} of ${t.originalFormat}`),t.stsdCount>1&&c.push(`${t.stsdCount} sample descriptions, first used`),s.width=t.width,s.height=t.height,s.rotation=displayRotation(t.matrix,r.matrix),s.enabled=t.enabled,s.timescale=f;let p=sampleTotals(t),m=r.truncated,h=0;for(let e of t.fragments)h+=e.present;let g=p.moovSamples+p.fragmentSamples,_=p.moovSamples+h;s.frames=_,s.isStill=_<=1,p.moovSamples>0&&t.fragments.length>0&&c.push(`hybrid fragmented file: moov holds ${p.moovSamples} of ${g} samples`),_<g&&c.push(`truncated: ${g-_} of ${g} samples missing`);let v=averageRates(t,r);s.avgFps=v.avg,s.avgFpsFfprobe=a.ffprobeFails?null:v.ffprobe,s.avgFpsFfprobe!==null&&s.avgFpsFfprobe!==n.o(v.avg)&&c.push(`ffprobe reads part of the fragments: avg_frame_rate ${s.avgFpsFfprobe}`),v.ffprobe===null&&!a.ffprobeFails&&g>0&&c.push(`avg_frame_rate of ffprobe is not reproduced: its header values are incomplete or overrun a box`);let y=!o||a.ffprobeFails||u?null:ffprobeStage1Rate(t,r,p.moovSamples,i.maxExpandedSamples,c);s.rFpsFfprobe=y?n.o(y):null;let b=editInfo(t,r.timescale,p);if(!u){let e=trackTiming(t,r,p,b);s.durationSec=e.durationTs/f,s.startSec=e.startPts===null?null:e.startPts/f}let x=p.moovSamples;if(t.sampleCount!==null&&t.sampleCount<x&&(c.push(`stts describes ${x} samples but the sample size table holds ${t.sampleCount}`),x=t.sampleCount),u||!o)return s.presentedFrames=0,finishProvenance(s),{result:s,enabled:t.enabled,auxiliary:l};if(m&&x>0){let e=moovSamplesInFile(t,r.fileSize,p.moovSamples);e&&e.complete<x&&(c.push(`truncated: ${e.complete} of ${x} samples complete`),x=e.complete)}let S=presentationWindow(t,b,c);S&&!t.cttsCounts&&!t.fragments.length&&S.start<=0&&(S.end<=S.start||S.end>=p.moovDuration)&&(S=null);let C=o({segments:timelineSegments(t,x),timescale:f,editWindow:S,maxExpandedSamples:i.maxExpandedSamples});c.push(...C.warnings);for(let e of d)s[e]=C[e];return C.presentedAvgFps&&(s.avgFps=C.presentedAvgFps),s.duplicateTimestamps>0&&c.push(`${s.duplicateTimestamps} duplicate timestamps`),finishProvenance(s),C.presentedAvgFps&&(s.provenance.avgFps=`exact`),{result:s,enabled:t.enabled,auxiliary:l,profile:C.profile}}function finishProvenance(e){let t=e.provenance;for(let e of[`codec`,`dimensions`,`rotation`,`frames`,`avgFps`])t[e]=`header`;e.durationSec!==null&&(t.durationSec=`header`),e.startSec!==null&&(t.startSec=`header`),e.presentedFrames!==null&&(t.presentedFrames=`exact`),e.isVfr!==null&&(t.minFps=`exact`,t.maxFps=`exact`,t.isVfr=`exact`,t.duplicateTimestamps=`exact`,t.medianFrameInterval=`exact`),e.nominalFps&&(t.nominalFps=`exact`),e.dominantFps&&(t.dominantFps=`exact`)}function pickPrimary(e){let t=null,score=e=>(e.result.frames??0)>1?e.auxiliary?.5:e.enabled?2:1:0;for(let n of e)(!t||score(n)>score(t)||score(n)===score(t)&&(n.result.frames??0)>(t.result.frames??0))&&(t=n);return t?t.result.trackId:null}function formatDuration(e,t){let n=2**53-1,r=n,i=n,a=-n,o=-n,s=-n,c=-n;for(let n of e.tracks){if(!n.hasMdhd)continue;let l=t.has(n)||n.handler===`soun`;if(missingMandatoryAtoms(n)){let e=rescaleRound(n.mdhdDuration,1e6,9e4);l?s=Math.max(s,e):c=Math.max(c,e);continue}let u=sampleTotals(n),d=trackTiming(n,e,u,editInfo(n,e.timescale,u)),f=rescaleRound(d.durationTs,1e6,n.timescale);if(d.startPts!==null){let e=rescaleRound(d.startPts,1e6,n.timescale);l?(r=Math.min(r,e),a=Math.max(a,e+f)):(i=Math.min(i,e),o=Math.max(o,e+f))}l?s=Math.max(s,f):c=Math.max(c,f)}return(r===n||r>i&&r-i<1e6)&&(r=i),(a===-n||a<o&&o-a<1e6)&&(a=o),(s===-n||s<c&&c-s<1e6)&&(s=c),r!==n&&a!==-n&&a>=r&&(s=Math.max(s,a-r)),s>0?s/1e6:null}function reportedIds(e){let t=new Map,n=new Set,r=e.reduce((e,t)=>Math.max(e,t.id),0)+1;for(let i of e)n.has(i.id)?t.set(i,r++):(n.add(i.id),t.set(i,i.id));return t}Object.defineProperty(exports,"C",{enumerable:!0,get:function(){return IsoBmffError}}),Object.defineProperty(exports,"S",{enumerable:!0,get:function(){return parseSidx}}),Object.defineProperty(exports,"T",{enumerable:!0,get:function(){return readBoxHeader}}),Object.defineProperty(exports,"_",{enumerable:!0,get:function(){return sampleTotals}}),Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return containerOf}}),Object.defineProperty(exports,"b",{enumerable:!0,get:function(){return newMovie}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return isAbort}}),Object.defineProperty(exports,"d",{enumerable:!0,get:function(){return pickPrimary}}),Object.defineProperty(exports,"f",{enumerable:!0,get:function(){return readMoov}}),Object.defineProperty(exports,"g",{enumerable:!0,get:function(){return rescaleRound}}),Object.defineProperty(exports,"h",{enumerable:!0,get:function(){return editInfo}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return buildVideoTrack}}),Object.defineProperty(exports,"l",{enumerable:!0,get:function(){return isVideoTrack}}),Object.defineProperty(exports,"m",{enumerable:!0,get:function(){return walk}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return 268435456}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return emulateFfprobeExtent}}),Object.defineProperty(exports,"p",{enumerable:!0,get:function(){return reportedIds}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return l}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return formatDuration}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return 67108864}}),Object.defineProperty(exports,"u",{enumerable:!0,get:function(){return layoutOf}}),Object.defineProperty(exports,"v",{enumerable:!0,get:function(){return trackTiming}}),Object.defineProperty(exports,"w",{enumerable:!0,get:function(){return childBoxes}}),Object.defineProperty(exports,"x",{enumerable:!0,get:function(){return parseMoof}}),Object.defineProperty(exports,"y",{enumerable:!0,get:function(){return parseTfraOffsets}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=require("./guards-Dqpjtkdn.cjs"),t=require("./probe-DDLUfr23.cjs"),n=require("./errors-0oBg1Ai6.cjs"),r=require("./fallback-D9Ak2zFv.cjs");async function compare(i,a,o={}){let s=t.n(a),c=[...new Set(o.engines??i.engines.filter(e=>e!==`rvfc`))],l=c.find(e=>!i.routes.has(e));if(l)throw new n.t(`engine-not-registered`,`engine ${l} is not registered`,{engine:l});let u={...i.defaults},{signal:d,timeoutMs:f,onProgress:p}=o,outcomeOf=async t=>{let o=performance.now();try{let e=await i.run(t,a,s,u,{signal:d,timeoutMs:f,onProgress:p&&(e=>p({...e,engine:t}))});if(e.type===`skipped`)return{status:`skipped`,reason:e.reason};let n=performance.now()-o,c=e.result,l=r.failureOf(c);return l===null?{status:`ok`,result:c,ms:n}:{status:`failed`,result:c,error:l,ms:n}}catch(r){if(e.t(r))throw r;let i=e.n(r)?r:Object.assign(new n.t(`worker-failed`,String(r),{engine:t,cause:r}),{reason:`crash`});return i.code===`unsupported-environment`?{status:`skipped`,reason:`unsupported-environment`}:{status:i.code===`timeout`?`timeout`:`crashed`,error:i}}},m=new Map;for(let e of c){let t=i.routes.get(e),n=t&&`kind`in t?t.kind:e;m.set(n,[...m.get(n)??[],e])}let h=new Map;await Promise.all([...m.values()].map(async e=>{for(let t of e)h.set(t,await outcomeOf(t))}));let g={},_={};for(let e of c){let t=h.get(e);t&&(g[e]=t,`result`in t&&(_[e]=t.result))}let v=t.r(_,o.reference);return{name:s,size:a.size,reference:v.reference,outcomes:g,agreement:v}}exports.compare=compare;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var ProbeError=class extends Error{code;engine;constructor(e,t,n={}){super(t,n.cause===void 0?void 0:n),this.name=`ProbeError`,this.code=e,this.engine=n.engine??null}};Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return ProbeError}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=require("./guards-Dqpjtkdn.cjs"),t=require("./errors-0oBg1Ai6.cjs"),failureOf=t=>{let n=e.u(t);return t.error??(n?n.avgFps?null:`no frame rate`:`no video track`)},skippedResult=(t,n,r,i)=>({name:r,size:n.size,...e.c(i===`isobmff`?`mp4`:i??`unknown`,i?`unsupported by ${t}`:`not a recognised container`),io:{reads:0,bytesRead:0,ms:0},engine:t}),withAttempts=(e,t)=>t.length>0?{...e,attempts:t.map(({engine:e,error:t})=>({engine:e,error:t}))}:e;async function runChain(n,r,i,a,o,s,c){let l=[],u=null;for(let[d,f]of r.entries()){let p;try{let e=d===0&&c?c:await n(f,i,a,o,s);p=e.type===`result`?e.result:skippedResult(f,i,a,e.family??null)}catch(n){if(e.t(n))throw n;u=e.n(n)?n:Object.assign(new t.t(`worker-failed`,String(n),{engine:f,cause:n}),{reason:`crash`}),l.push({engine:f,error:u.message});continue}let m=failureOf(p);if(m===null||r.length===1)return withAttempts(p,l);l.push({engine:f,error:m,result:p})}for(let e=l.length-1;e>=0;e--){let{result:t}=l[e];if(t)return withAttempts(t,l.slice(0,e).concat(l.slice(e+1)))}throw u}exports.failureOf=failureOf,exports.runChain=runChain;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
function emptyAnalysis(e,t=null){return{container:e,brand:null,formatDurationSec:null,scan:null,videoTracks:[],audioTrackCount:0,primaryTrackId:null,error:t,warnings:[]}}function emptyTrack(e){return{trackId:e,fourccOrCodecId:``,codecName:``,width:0,height:0,rotation:0,timescale:0,frames:null,presentedFrames:null,durationSec:null,startSec:null,avgFps:null,avgFpsFfprobe:null,rFpsFfprobe:null,nominalFps:null,dominantFps:null,minFps:null,maxFps:null,isVfr:null,vfrRatio:0,distinctDeltas:0,duplicateTimestamps:0,timestampsQuantized:!1,isStill:!1,rateProfile:null,medianFrameInterval:null,provenance:{},warnings:[]}}function primaryTrack(e){return e.primaryTrackId===null?null:e.videoTracks.find(t=>t.trackId===e.primaryTrackId)??null}const e=[[`not a recognised container: empty file`,`empty-file`],[`not a recognised container`,`unrecognised`],[`unsupported: moov larger`,`too-large`],[`corrupt file: moof larger`,`too-large`],[`unsupported:`,`unsupported`],[`incomplete file: moov is truncated`,`truncated`],[`incomplete file: no moov`,`no-header`],[`incomplete header:`,`no-duration`],[`limit reached: maxBytes`,`byte-limit`],[`limit reached: maxReads`,`read-limit`],[`limit reached: maxMs`,`time-limit`],[`limit reached: maxTracks`,`structure-limit`],[`limit reached: maxTopLevelBoxes`,`structure-limit`],[`corrupt file:`,`corrupt`],[`file read failed`,`read-failed`],[`the own `,`unsupported-container`],[`unsupported by `,`unsupported-container`],[`container not read by`,`unsupported-container`],[`invalid EBML header`,`corrupt`],[`no Segment element`,`no-header`],[`no complete TrackEntry in Tracks`,`truncated`],[`no Tracks element found`,`no-header`]],t={"empty-file":`file`,unrecognised:`file`,"unsupported-container":`file`,unsupported:`file`,"too-large":`file`,"no-header":`file`,truncated:`file`,corrupt:`file`,"no-duration":`file`,"engine-failed":`file`,"byte-limit":`limit`,"read-limit":`limit`,"time-limit":`limit`,"structure-limit":`limit`,"read-failed":`io`,"load-failed":`environment`,internal:`internal`},n=[[`invalid mdhd timescale `,`invalid-timescale`],[`invalid timescale `,`invalid-timescale`],[`ignored trailing data at `,`trailing-data`],[`fragment index not read: `,`fragment-index-unread`],[`format duration unresolved: `,`duration-unresolved`],[`fragments ignored: `,`fragments-ignored`],[`too many top-level boxes`,`structure-limit`]],r=/^'(.{4})' at \d+ is truncated/;function errorCodeOf(t){if(t?.errorCode!==void 0)return t.errorCode;let n=t?.error;if(n===null)return null;if(typeof n!=`string`)return`internal`;for(let[t,r]of e)if(n.startsWith(t))return r;return t.engine!==void 0&&t.engine!==`own`?`engine-failed`:`internal`}function errorClass(e){return Object.hasOwn(t,e)?t[e]:`internal`}function warningCodeOf(e){if(typeof e!=`string`)return null;for(let[t,r]of n)if(e.startsWith(t))return r;let t=r.exec(e);return t?t[1]===`mdat`?`truncated-mdat`:`truncated-box`:null}function warningCodesOf(e){let t=new Set,listOf=e=>Array.isArray(e)?e:[];for(let n of[...listOf(e?.warnings),...listOf(e?.videoTracks).flatMap(e=>listOf(e?.warnings))]){let e=warningCodeOf(n);e&&t.add(e)}return[...t]}const field=(e,t)=>typeof e==`object`&&e&&t in e?e[t]:void 0,isProbeError=e=>field(e,`name`)===`ProbeError`&&typeof field(e,`code`)==`string`,probeCode=(e,t,n)=>isProbeError(e)?e.code===t:field(e,`name`)===n;function isAbortError(e){return probeCode(e,`aborted`,`AbortError`)}function isTimeoutError(e){return probeCode(e,`timeout`,`TimeoutError`)}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return errorCodeOf}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return emptyAnalysis}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return errorClass}}),Object.defineProperty(exports,"l",{enumerable:!0,get:function(){return emptyTrack}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return isProbeError}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return warningCodeOf}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return isTimeoutError}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return warningCodesOf}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return isAbortError}}),Object.defineProperty(exports,"u",{enumerable:!0,get:function(){return primaryTrack}});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=require("./errors-0oBg1Ai6.cjs"),t=require("./sniff-CXwqOGwh.cjs");function runInline(n,r,i,a,o){let{engine:s,signals:c,onProgress:l,host:u}=o,d=o.timeoutMs??0,f=new AbortController;return new Promise((o,p)=>{let m=!1,h,finish=e=>{if(!m){m=!0,clearTimeout(h);for(let e of c)e?.removeEventListener(`abort`,onAbort);e()}},fail=e=>{f.abort(),finish(()=>p(e))},onAbort=()=>fail(new e.t(`aborted`,`aborted`,{engine:s,cause:c.find(e=>e?.aborted)?.reason}));if(c.some(e=>e?.aborted))return onAbort();for(let e of c)e?.addEventListener(`abort`,onAbort);d>0&&d<=2**31-1&&(h=setTimeout(()=>fail(new e.t(`timeout`,`timed out after ${d} ms`,{engine:s})),d));let g=!0;(async()=>{let e=await n();if(g=!1,e.containers!==`any`){let n=await t.t(r);if(!n||!e.containers.includes(n))return{type:`skipped`,reason:`unsupported-container`,family:n}}let o={...a,signal:f.signal,onProgress:l,host:u};return{type:`result`,result:await e.run(r,i,o)}})().then(e=>finish(()=>o(e)),t=>t?.name===`ProbeError`&&typeof t.code==`string`?finish(()=>p(t)):fail(Object.assign(new e.t(`worker-failed`,t instanceof Error?t.message||t.name:String(t),{engine:s,cause:t}),{reason:g?`load`:`crash`})))})}exports.runInline=runInline;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e=require("./guards-Dqpjtkdn.cjs"),t=require("./reader-DNLR-_tm.cjs"),n=require("./build-CT5g6_bs.cjs"),r=require("./stats-D68frkk2.cjs");async function analyzeIsoBmffDetailed(i,a){let o=n.b(i.size),fail=r=>{if(n.c(r,i))throw r;let a=r instanceof Error?r.message:String(r),s=e.c(n.a(o),r?.errorCode?a:`corrupt file: ${a}`);return s.errorCode=t.i(r),s.layout=n.u(o),s.brand=o.brand,s.warnings=o.warnings,{analysis:s,movie:o}};try{await n.m(i,a,o),await n.o(i,o);let t=e.c(n.a(o));t.brand=o.brand,t.scan=`exact`,t.warnings=o.warnings;let s=o.ffprobeReject,c=s!==null&&s.moof<o.ffprobeMoofLimit;c&&o.warnings.push(`ffprobe 8.1.1 ${s.reason}`);let l=n.p(o.tracks);[...l].some(([e,t])=>e.id!==t)&&o.warnings.push(`duplicate track_ID values`);let u=o.tracks.filter(e=>n.l(e,o)),d=u.map(e=>n.i(e,o,a,{reportedId:l.get(e)??e.id,ffprobeFails:c},r.analyzeTimeline));for(let e of d)e.result.rateProfile=await r.rateProfileOf(e.profile??null);return t.videoTracks=d.map(e=>e.result),t.audioTrackCount=o.tracks.filter(e=>e.handler===`soun`).length,t.primaryTrackId=n.d(d),t.formatDurationSec=n.s(o,new Set(u)),t.layout=n.u(o),{analysis:t,movie:o}}catch(e){return fail(e)}}async function analyzeIsoBmff(e,t){return(await analyzeIsoBmffDetailed(e,t)).analysis}exports.IsoBmffError=n.C,exports.MOOF_HARD_CAP=n.t,exports.MOOV_HARD_CAP=n.n,exports.MOOV_SUSPECT_CAP=n.r,exports.analyzeIsoBmff=analyzeIsoBmff,exports.analyzeIsoBmffDetailed=analyzeIsoBmffDetailed;
|