video-fps-probe 0.3.0 → 0.4.0-beta.2

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.
Files changed (94) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/README.md +19 -11
  3. package/dist/analyze.js +10 -1
  4. package/dist/api/codes.d.ts +6 -0
  5. package/dist/api/codes.js +90 -0
  6. package/dist/aspect/index.d.ts +13 -0
  7. package/dist/aspect/index.js +180 -0
  8. package/dist/aspect/rational.js +61 -0
  9. package/dist/cjs/analyze.d.cts +9 -0
  10. package/dist/cjs/api/agreement.d.cts +18 -0
  11. package/dist/cjs/api/codes.d.cts +6 -0
  12. package/dist/cjs/api/errors.d.cts +12 -0
  13. package/dist/cjs/api/guards.d.cts +4 -0
  14. package/dist/cjs/api/summary.d.cts +22 -0
  15. package/dist/cjs/aspect/index.d.cts +13 -0
  16. package/dist/cjs/aspect.cjs +1 -0
  17. package/dist/cjs/check/index.d.cts +60 -0
  18. package/dist/cjs/check.cjs +1 -0
  19. package/dist/cjs/chunks/boxes-B0T2nt3e.cjs +1 -0
  20. package/dist/cjs/chunks/build-DhETwJLO.cjs +1 -0
  21. package/dist/cjs/chunks/color-xqJHSfhZ.cjs +1 -0
  22. package/dist/cjs/chunks/compare-B_HKNwqg.cjs +1 -0
  23. package/dist/cjs/chunks/config-CSbXzEqr.cjs +1 -0
  24. package/dist/cjs/chunks/errors-0oBg1Ai6.cjs +1 -0
  25. package/dist/cjs/chunks/fallback-CyPIR3RM.cjs +1 -0
  26. package/dist/cjs/chunks/guards-DbQNcAZS.cjs +1 -0
  27. package/dist/cjs/chunks/inline-Bx-TQg8f.cjs +1 -0
  28. package/dist/cjs/chunks/isobmff-4RrRf9N-.cjs +1 -0
  29. package/dist/cjs/chunks/matroska-BhgWtuPU.cjs +1 -0
  30. package/dist/cjs/chunks/metadata-ECdzNLkE.cjs +1 -0
  31. package/dist/cjs/chunks/options-B_vLHxHd.cjs +1 -0
  32. package/dist/cjs/chunks/probe-Cn1DtL-C.cjs +1 -0
  33. package/dist/cjs/chunks/rational-Dq5Vza-Q.cjs +1 -0
  34. package/dist/cjs/chunks/reader-Dw11ozVU.cjs +1 -0
  35. package/dist/cjs/chunks/rfps-C-pyKnu8.cjs +1 -0
  36. package/dist/cjs/chunks/runtime-BgAlQQee.cjs +1 -0
  37. package/dist/cjs/chunks/segments-B1H6bDz-.cjs +1 -0
  38. package/dist/cjs/chunks/sniff-CXwqOGwh.cjs +1 -0
  39. package/dist/cjs/chunks/stats-DW7dVdFa.cjs +1 -0
  40. package/dist/cjs/client/types.d.cts +96 -0
  41. package/dist/cjs/color/index.d.cts +7 -0
  42. package/dist/cjs/color.cjs +1 -0
  43. package/dist/cjs/core/reader.d.cts +47 -0
  44. package/dist/cjs/core/types.d.cts +194 -0
  45. package/dist/cjs/engines/types.d.cts +69 -0
  46. package/dist/cjs/header/index.d.cts +28 -0
  47. package/dist/cjs/header/limits.d.cts +26 -0
  48. package/dist/cjs/header.cjs +1 -0
  49. package/dist/cjs/index.cjs +1 -0
  50. package/dist/cjs/index.d.cts +13 -0
  51. package/dist/cjs/plugins/types.d.cts +17 -0
  52. package/dist/cjs/presets/index.d.cts +19 -0
  53. package/dist/cjs/presets.cjs +1 -0
  54. package/dist/cjs/probe.d.cts +3 -0
  55. package/dist/cjs/url/index.d.cts +47 -0
  56. package/dist/cjs/url.cjs +1 -0
  57. package/dist/cjs/worker/protocol.d.cts +47 -0
  58. package/dist/color/codec-string.js +121 -0
  59. package/dist/color/config.js +199 -0
  60. package/dist/color/index.d.ts +7 -0
  61. package/dist/color/index.js +139 -0
  62. package/dist/color/names.js +33 -0
  63. package/dist/color/sps.js +275 -0
  64. package/dist/core/dispatch.js +18 -29
  65. package/dist/core/isobmff/boxes.js +6 -5
  66. package/dist/core/isobmff/build.js +401 -0
  67. package/dist/core/isobmff/config.js +34 -0
  68. package/dist/core/isobmff/header.js +677 -0
  69. package/dist/core/isobmff/index.js +9 -381
  70. package/dist/core/isobmff/moof.js +2 -2
  71. package/dist/core/isobmff/moov.js +3 -3
  72. package/dist/core/matroska/header.js +10 -4
  73. package/dist/core/matroska/index.js +11 -5
  74. package/dist/core/options.js +1 -0
  75. package/dist/core/reader.d.ts +3 -0
  76. package/dist/core/reader.js +13 -7
  77. package/dist/core/result.js +1 -0
  78. package/dist/core/stats.js +3 -0
  79. package/dist/core/types.d.ts +60 -1
  80. package/dist/engines/common.js +2 -0
  81. package/dist/header/index.d.ts +28 -0
  82. package/dist/header/index.js +182 -0
  83. package/dist/header/limits.d.ts +26 -0
  84. package/dist/header/limits.js +144 -0
  85. package/dist/header/metadata.js +2 -0
  86. package/dist/index.d.ts +1 -0
  87. package/dist/index.js +1 -0
  88. package/dist/standalone/own-isobmff.worker.js +1 -1
  89. package/dist/standalone/own-matroska.worker.js +1 -1
  90. package/dist/standalone/own.worker.js +1 -1
  91. package/dist/url/index.d.ts +47 -0
  92. package/dist/url/index.js +335 -0
  93. package/dist/worker/single.js +2 -1
  94. package/package.json +117 -2
@@ -0,0 +1,96 @@
1
+ import type { Agreement } from '../api/agreement.cjs';
2
+ import type { ProbeError } from '../api/errors.cjs';
3
+ import type { AnalyzeProgress, EngineId, FileResult } from '../core/types.cjs';
4
+ import type { MediabunnyNative, Mp4boxNative, RvfcDetails } from '../engines/types.cjs';
5
+ import type { ContainerFamily, EngineDescriptor, WorkerEngineId } from '../plugins/types.cjs';
6
+ import type { WireOptions } from '../worker/protocol.cjs';
7
+ export type IdOf<E> = E extends EngineDescriptor<infer I> ? I : never;
8
+ export interface BaseOptions<Id extends EngineId> {
9
+ engine?: Id;
10
+ fallback?: readonly Id[];
11
+ workers?: number;
12
+ idleTimeoutMs?: number;
13
+ timeoutMs?: number;
14
+ defaults?: WireOptions;
15
+ }
16
+ export interface ProbeOptions<E extends readonly EngineDescriptor[] = []> extends BaseOptions<NoInfer<'own' | IdOf<E[number]>>> {
17
+ engines?: E;
18
+ worker?: 'auto' | 'inline';
19
+ }
20
+ export interface CustomWorker<W extends WorkerEngineId> {
21
+ create: () => Worker;
22
+ engines: readonly W[];
23
+ byContainer?: Readonly<Partial<Record<ContainerFamily, () => Worker>>>;
24
+ }
25
+ export interface ClientOptions<E extends readonly EngineDescriptor[] = [], W extends WorkerEngineId = never> extends BaseOptions<NoInfer<W | IdOf<E[number]> | 'own'>> {
26
+ worker: CustomWorker<W> | 'inline';
27
+ engines?: E;
28
+ }
29
+ export interface CallOptions<Id extends EngineId> extends WireOptions {
30
+ engine?: Id;
31
+ fallback?: readonly Id[];
32
+ name?: string;
33
+ host?: HTMLElement;
34
+ signal?: AbortSignal;
35
+ timeoutMs?: number;
36
+ onProgress?: (progress: AnalyzeProgress) => void;
37
+ }
38
+ export interface Attempt {
39
+ engine: EngineId;
40
+ error: string;
41
+ }
42
+ export type ProbeResult = FileResult & {
43
+ mp4box?: Mp4boxNative;
44
+ mediabunny?: MediabunnyNative;
45
+ rvfc?: RvfcDetails;
46
+ attempts?: readonly Attempt[];
47
+ };
48
+ export interface PrewarmOptions<Id extends EngineId> {
49
+ engines?: readonly Id[];
50
+ containers?: readonly ContainerFamily[];
51
+ }
52
+ export interface CompareOptions<K extends EngineId> {
53
+ engines?: readonly K[];
54
+ reference?: K;
55
+ signal?: AbortSignal;
56
+ timeoutMs?: number;
57
+ onProgress?: (progress: AnalyzeProgress & {
58
+ engine: K;
59
+ }) => void;
60
+ }
61
+ export type EngineOutcome = {
62
+ status: 'ok';
63
+ result: ProbeResult;
64
+ ms: number;
65
+ } | {
66
+ status: 'failed';
67
+ result: ProbeResult;
68
+ error: string;
69
+ ms: number;
70
+ } | {
71
+ status: 'skipped';
72
+ reason: 'unsupported-container' | 'unsupported-environment';
73
+ } | {
74
+ status: 'timeout' | 'crashed';
75
+ error: ProbeError;
76
+ };
77
+ export interface CompareResult<K extends EngineId> {
78
+ name: string;
79
+ size: number;
80
+ reference: K | null;
81
+ outcomes: Partial<Record<K, EngineOutcome>>;
82
+ agreement: Agreement<K>;
83
+ }
84
+ export interface Probe<Id extends EngineId = 'own'> {
85
+ readonly engines: readonly Id[];
86
+ analyze(file: Blob, options?: CallOptions<Id>): Promise<ProbeResult>;
87
+ tryAnalyze(file: Blob, options?: CallOptions<Id>): Promise<ProbeResult | null>;
88
+ compare<K extends Id>(file: Blob, options?: CompareOptions<K>): Promise<CompareResult<K>>;
89
+ prewarm(options?: PrewarmOptions<Id>): Promise<void>;
90
+ dispose(): void;
91
+ destroy(): void;
92
+ }
93
+ export type LazyProbe<Id extends EngineId> = Probe<Id> & {
94
+ readonly loaded: boolean;
95
+ load(): Promise<Probe<Id>>;
96
+ };
@@ -0,0 +1,7 @@
1
+ import type { ColorInfo, FileResult, VideoTrackResult } from '../core/types.cjs';
2
+ export type { CodecConfig, ColorInfo, ColorSource, DolbyVisionConfig } from '../core/types.cjs';
3
+ export type ColorTrack = Pick<VideoTrackResult, 'codecName' | 'codecConfig'>;
4
+ export declare function colorOf(track: ColorTrack): ColorInfo | null | undefined;
5
+ export declare function withColor<R extends Pick<FileResult, 'videoTracks'>>(result: R, options?: {
6
+ keepCodecConfig?: boolean;
7
+ }): R;
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./chunks/color-xqJHSfhZ.cjs");exports.colorOf=e.r,exports.withColor=e.i;
@@ -0,0 +1,47 @@
1
+ import type { FileErrorCode } from './types.cjs';
2
+ export interface BlobSlice {
3
+ arrayBuffer(): Promise<ArrayBuffer>;
4
+ }
5
+ export interface BlobLike {
6
+ readonly size: number;
7
+ slice(start?: number, end?: number): BlobSlice;
8
+ }
9
+ export interface BlobReaderOptions {
10
+ headWindow?: number;
11
+ maxWindows?: number;
12
+ signal?: AbortSignal | null;
13
+ }
14
+ export declare const DEFAULT_HEAD_WINDOW: number;
15
+ export declare const DEFAULT_MAX_WINDOWS = 4;
16
+ export declare const MAX_SAFE_OFFSET: number;
17
+ export declare function toSafeNumber(value: bigint, what?: string): number;
18
+ export declare class BlobReader {
19
+ #private;
20
+ readonly size: number;
21
+ readonly headWindow: number;
22
+ readonly maxWindows: number;
23
+ readonly signal: AbortSignal | null;
24
+ constructor(source: BlobLike, options?: BlobReaderOptions);
25
+ get reads(): number;
26
+ get bytesFetched(): number;
27
+ checkAbort(): void;
28
+ read(offset: number, length: number): Promise<Uint8Array>;
29
+ readMany(ranges: ReadonlyArray<readonly [offset: number, length: number]>, concurrency?: number): Promise<Uint8Array[]>;
30
+ peek(offset: number, length: number): DataView | null;
31
+ view(offset: number, length: number, readAhead?: number): Promise<DataView>;
32
+ bytes(offset: number, length: number, readAhead?: number): Promise<Uint8Array>;
33
+ }
34
+ export declare function u8(dv: DataView, p: number): number;
35
+ export declare function u16(dv: DataView, p: number): number;
36
+ export declare function i16(dv: DataView, p: number): number;
37
+ export declare function u24(dv: DataView, p: number): number;
38
+ export declare function u32(dv: DataView, p: number): number;
39
+ export declare function i32(dv: DataView, p: number): number;
40
+ export declare function u64(dv: DataView, p: number): number;
41
+ export declare function i64(dv: DataView, p: number): number;
42
+ export declare function u64Big(dv: DataView, p: number): bigint;
43
+ export declare function fixed16_16(dv: DataView, p: number): number;
44
+ export declare function ascii(dv: DataView, p: number, length: number): string;
45
+ export declare function fourcc(dv: DataView, p: number): string;
46
+ export declare function withErrorCode(code: FileErrorCode, message: string): Error;
47
+ export declare function whichErrorCode(e: unknown, fallback?: FileErrorCode): FileErrorCode;
@@ -0,0 +1,194 @@
1
+ import type { BlobReader } from './reader.cjs';
2
+ export type Provenance = 'header' | 'exact' | 'sampled' | 'estimated' | 'computed';
3
+ export interface Rational {
4
+ num: number;
5
+ den: number;
6
+ value: number;
7
+ }
8
+ export type NominalKind = 'exact' | 'snapped' | 'declared';
9
+ export interface NominalFps extends Rational {
10
+ kind: NominalKind;
11
+ }
12
+ export interface DominantFps {
13
+ rate: Rational;
14
+ share: number;
15
+ }
16
+ export type ContainerKind = 'mp4' | 'mov' | 'webm' | 'matroska' | 'unknown';
17
+ export type EngineId = 'own' | 'mediabunny' | 'mp4box' | 'rvfc';
18
+ export type MatroskaMode = 'header' | 'probe' | 'exact' | 'auto';
19
+ export type ScanDepth = 'header' | 'probe' | 'exact';
20
+ export type TrackField = 'codec' | 'dimensions' | 'rotation' | 'frames' | 'presentedFrames' | 'durationSec' | 'startSec' | 'avgFps' | 'nominalFps' | 'dominantFps' | 'minFps' | 'maxFps' | 'isVfr' | 'duplicateTimestamps' | 'medianFrameInterval';
21
+ export type RateSegmentKind = 'steady' | 'variable';
22
+ export interface RateSegment {
23
+ startSec: number;
24
+ endSec: number;
25
+ frames: number;
26
+ fps: number;
27
+ kind: RateSegmentKind;
28
+ }
29
+ export type RateAnomalyKind = 'rate-change' | 'freeze' | 'burst' | 'gap';
30
+ export interface RateAnomaly {
31
+ kind: RateAnomalyKind;
32
+ atSec: number;
33
+ durationSec: number;
34
+ fromFps: number;
35
+ toFps: number;
36
+ }
37
+ export interface RateBuckets {
38
+ bucketSec: number;
39
+ fps: number[];
40
+ }
41
+ export interface RateProfile {
42
+ segments: RateSegment[];
43
+ moreSegments: number;
44
+ anomalies: RateAnomaly[];
45
+ moreAnomalies: number;
46
+ buckets: RateBuckets;
47
+ uneven: boolean;
48
+ dominantShare: number;
49
+ summary: string;
50
+ }
51
+ export interface VideoTrackResult {
52
+ trackId: number;
53
+ fourccOrCodecId: string;
54
+ codecName: string;
55
+ width: number;
56
+ height: number;
57
+ rotation: number;
58
+ timescale: number;
59
+ frames: number | null;
60
+ presentedFrames: number | null;
61
+ durationSec: number | null;
62
+ startSec: number | null;
63
+ avgFps: Rational | null;
64
+ avgFpsFfprobe: string | null;
65
+ rFpsFfprobe: string | null;
66
+ nominalFps: NominalFps | null;
67
+ dominantFps: DominantFps | null;
68
+ minFps: Rational | null;
69
+ maxFps: Rational | null;
70
+ isVfr: boolean | null;
71
+ vfrRatio: number;
72
+ distinctDeltas: number;
73
+ duplicateTimestamps: number;
74
+ timestampsQuantized: boolean;
75
+ isStill: boolean;
76
+ rateProfile?: RateProfile | null;
77
+ medianFrameInterval?: Rational | null;
78
+ enabled?: boolean;
79
+ codecConfig?: CodecConfig;
80
+ color?: ColorInfo | null;
81
+ sampleAspectRatio?: Rational | null;
82
+ displayAspectRatio?: Rational | null;
83
+ displayWidth?: number | null;
84
+ displayHeight?: number | null;
85
+ provenance: Partial<Record<TrackField, Provenance>>;
86
+ warnings: string[];
87
+ }
88
+ export type CodecConfig = {
89
+ family: 'isobmff';
90
+ stsd: Uint8Array | null;
91
+ tkhd: Uint8Array | null;
92
+ mvhd: Uint8Array | null;
93
+ } | {
94
+ family: 'isobmff-moov';
95
+ moov: DataView;
96
+ trak: number;
97
+ } | {
98
+ family: 'matroska';
99
+ trackEntry: Uint8Array | null;
100
+ };
101
+ export type ColorSource = 'sps' | 'colr' | 'vpcC' | 'Colour';
102
+ export interface DolbyVisionConfig {
103
+ versionMajor: number;
104
+ versionMinor: number;
105
+ profile: number;
106
+ level: number;
107
+ rpu: boolean;
108
+ el: boolean;
109
+ bl: boolean;
110
+ compatibilityId: number;
111
+ mdCompression: number;
112
+ }
113
+ export interface ColorInfo {
114
+ range: 'tv' | 'pc' | null;
115
+ primaries: string | null;
116
+ transfer: string | null;
117
+ space: string | null;
118
+ source: ColorSource | null;
119
+ bitDepth: number | null;
120
+ profile: number | null;
121
+ codecString: string | null;
122
+ hdr: 'pq' | 'hlg' | null;
123
+ dolbyVision: DolbyVisionConfig | null;
124
+ }
125
+ export interface IoStats {
126
+ reads: number;
127
+ bytesRead: number;
128
+ ms: number;
129
+ }
130
+ export type 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' | 'internal';
131
+ export type LayoutKind = 'faststart' | 'moov-at-end' | 'fragmented';
132
+ export interface ContainerLayout {
133
+ kind: LayoutKind;
134
+ moovOffset: number | null;
135
+ moovSize: number | null;
136
+ sidx: boolean;
137
+ emptyMoov: boolean;
138
+ }
139
+ export type WarningCode = 'invalid-timescale' | 'truncated-mdat' | 'truncated-box' | 'trailing-data' | 'fragment-index-unread' | 'duration-unresolved' | 'fragments-ignored' | 'structure-limit' | 'codec-config-unavailable' | 'metadata-unavailable';
140
+ export interface FileResult {
141
+ name: string;
142
+ size: number;
143
+ container: ContainerKind;
144
+ brand: string | null;
145
+ formatDurationSec: number | null;
146
+ scan: ScanDepth | null;
147
+ videoTracks: VideoTrackResult[];
148
+ audioTrackCount: number;
149
+ primaryTrackId: number | null;
150
+ error: string | null;
151
+ errorCode?: FileErrorCode | null;
152
+ layout?: ContainerLayout | null;
153
+ warnings: string[];
154
+ io: IoStats;
155
+ engine: EngineId;
156
+ }
157
+ export type ContainerAnalysis = Omit<FileResult, 'name' | 'size' | 'io' | 'engine'>;
158
+ export interface ReadAheadSizes {
159
+ head: number;
160
+ fragment: number;
161
+ matroskaScan: number;
162
+ tail: number;
163
+ }
164
+ export interface CostModel {
165
+ perReadMs: number;
166
+ bytesPerMs: number;
167
+ perBlockMs: number;
168
+ }
169
+ export interface AnalyzeProgress {
170
+ phase: string;
171
+ done: number;
172
+ total: number;
173
+ }
174
+ export interface AnalyzeOptions {
175
+ signal?: AbortSignal | null;
176
+ matroskaMode?: MatroskaMode;
177
+ exactBudgetMs?: number;
178
+ readAhead?: Partial<ReadAheadSizes>;
179
+ costModel?: Partial<CostModel>;
180
+ maxExpandedSamples?: number;
181
+ onProgress?: ((progress: AnalyzeProgress) => void) | null;
182
+ codecConfig?: boolean;
183
+ }
184
+ export interface ResolvedAnalyzeOptions {
185
+ signal: AbortSignal | null;
186
+ matroskaMode: MatroskaMode;
187
+ exactBudgetMs: number;
188
+ readAhead: ReadAheadSizes;
189
+ costModel: CostModel;
190
+ maxExpandedSamples: number;
191
+ onProgress: ((progress: AnalyzeProgress) => void) | null;
192
+ codecConfig?: boolean;
193
+ }
194
+ export type ContainerAnalyzer = (reader: BlobReader, opts: ResolvedAnalyzeOptions) => Promise<ContainerAnalysis>;
@@ -0,0 +1,69 @@
1
+ import type { FileResult } from '../core/types.cjs';
2
+ export interface Mp4boxTrackNative {
3
+ trackId: number;
4
+ sampleCount: number;
5
+ sttsLastDelta: number | null;
6
+ lastSampleDuration: number | null;
7
+ lastDurationOverridden: boolean;
8
+ fpsFromSampleDurations: number | null;
9
+ fpsFromMdhdDuration: number | null;
10
+ fpsFromMovieDuration: number | null;
11
+ }
12
+ export interface Mp4boxNative {
13
+ version: string;
14
+ fedBuffers: number;
15
+ fedBytes: number;
16
+ moofsFed: number;
17
+ tracks: Mp4boxTrackNative[];
18
+ }
19
+ export type Mp4boxResult = FileResult & {
20
+ mp4box?: Mp4boxNative;
21
+ };
22
+ export interface MediabunnyTrackNative {
23
+ trackId: number;
24
+ packetCount: number;
25
+ averagePacketRate: number | null;
26
+ nativeCallSkipped: boolean;
27
+ timeResolution: number;
28
+ fpsFromTimestamps: number | null;
29
+ }
30
+ export interface MediabunnyNative {
31
+ version: string;
32
+ formatName: string;
33
+ tracks: MediabunnyTrackNative[];
34
+ nativeStatsMs: number;
35
+ }
36
+ export type MediabunnyResult = FileResult & {
37
+ mediabunny?: MediabunnyNative;
38
+ };
39
+ export interface RvfcPass {
40
+ rate: number;
41
+ callbacks: number;
42
+ fps: number;
43
+ avg: number;
44
+ pairs: number;
45
+ spread: number;
46
+ lo: number;
47
+ hi: number;
48
+ }
49
+ export type RvfcConfidence = 'high' | 'medium' | 'low';
50
+ export interface RvfcDetails {
51
+ displayHz: number;
52
+ rate: number;
53
+ passes: RvfcPass[];
54
+ confidence: RvfcConfidence;
55
+ flags: string[];
56
+ }
57
+ export interface RvfcOptions {
58
+ host: HTMLElement;
59
+ name?: string;
60
+ signal?: AbortSignal;
61
+ onProgress?: (p: {
62
+ phase: string;
63
+ done: number;
64
+ total: number;
65
+ }) => void;
66
+ }
67
+ export type RvfcResult = FileResult & {
68
+ rvfc?: RvfcDetails;
69
+ };
@@ -0,0 +1,28 @@
1
+ import { type BlobLike } from '../core/reader.cjs';
2
+ import type { FileResult, IoStats } from '../core/types.cjs';
3
+ import { type HeaderLimits } from './limits.cjs';
4
+ export { errorClass, errorCodeOf, warningCodeOf, warningCodesOf, type ErrorClass } from '../api/codes.cjs';
5
+ export { isAbortError, isTimeoutError } from '../api/guards.cjs';
6
+ export { HEADER_DEFAULT_LIMITS, type HeaderLimits } from './limits.cjs';
7
+ export type { BlobLike, BlobSlice } from '../core/reader.cjs';
8
+ export type * from '../core/types.cjs';
9
+ export type FragmentedDuration = 'header' | 'index';
10
+ export interface HeaderOptions {
11
+ name?: string;
12
+ signal?: AbortSignal | null;
13
+ limits?: HeaderLimits;
14
+ readAhead?: {
15
+ head?: number;
16
+ tail?: number;
17
+ };
18
+ fragmentedDuration?: FragmentedDuration;
19
+ codecConfig?: boolean;
20
+ color?: boolean;
21
+ aspect?: boolean;
22
+ }
23
+ export type HeaderResult = FileResult & {
24
+ io: IoStats & {
25
+ bytesRequested: number;
26
+ };
27
+ };
28
+ export declare function analyzeHeader(source: Blob | BlobLike, options?: HeaderOptions): Promise<HeaderResult>;
@@ -0,0 +1,26 @@
1
+ import type { BlobLike, BlobSlice } from '../core/reader.cjs';
2
+ export interface HeaderLimits {
3
+ maxBytes?: number;
4
+ maxReads?: number;
5
+ maxMs?: number;
6
+ maxTracks?: number;
7
+ maxTopLevelBoxes?: number;
8
+ }
9
+ export declare const HEADER_DEFAULT_LIMITS: Readonly<Required<HeaderLimits>>;
10
+ export type LimitCode = 'byte-limit' | 'read-limit' | 'time-limit' | 'structure-limit';
11
+ export declare class LimitError extends Error {
12
+ readonly errorCode: LimitCode;
13
+ constructor(code: LimitCode, message: string);
14
+ }
15
+ export declare class LimitedSource implements BlobLike {
16
+ #private;
17
+ readonly size: number;
18
+ constructor(inner: BlobLike, size: number, limits: Required<Pick<HeaderLimits, 'maxBytes' | 'maxReads' | 'maxMs'>>, caller: AbortSignal | null);
19
+ get bytesLeft(): number;
20
+ get bytesRequested(): number;
21
+ checkTime(): void;
22
+ take(): LimitError | null;
23
+ race<T>(pending: Promise<T>): Promise<T>;
24
+ slice(start?: number, end?: number): BlobSlice;
25
+ dispose(): void;
26
+ }
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./chunks/guards-DbQNcAZS.cjs"),t=require("./chunks/reader-Dw11ozVU.cjs"),n=require("./chunks/options-B_vLHxHd.cjs"),r=require("./chunks/boxes-B0T2nt3e.cjs"),i=require("./chunks/build-DhETwJLO.cjs"),a=require("./chunks/sniff-CXwqOGwh.cjs"),o=67108864,s=new Set([`sidx`,`styp`,`prft`,`emsg`]);function count$1(e){if(++e.boxes>e.plan.maxTopLevelBoxes)throw new r.t(`structure-limit`,`limit reached: maxTopLevelBoxes ${e.plan.maxTopLevelBoxes}`)}function visit(e){e.budget.checkTime(),count$1(e)}function ahead(e,t,n){return Math.max(0,Math.min(n,e.budget.bytesLeft,e.reader.size-t))}async function peekAt(e,t,n){let r=Math.min(16,e.reader.size-t);return e.reader.peek(t,r)??e.reader.view(t,r,ahead(e,t,n))}function countTracks(e,t){let n;try{n=r.o(e,0,e.byteLength)}catch{return}if(!n)return;let i=0;for(let a=n.start+n.headerSize;a+8<=n.end;){let o;try{o=r.o(e,a,n.end)}catch{return}if(!o)return;if(o.type===`trak`&&++i>t)throw new r.t(`structure-limit`,`limit reached: maxTracks ${t}`);a=o.start+o.size}}function mehdDuration(e){let t;try{t=r.o(e,0,e.byteLength)}catch{return null}if(!t)return null;for(let n of r.a(e,t.start+t.headerSize,t.end))if(n.type===`mvex`)for(let t of r.a(e,n.start+n.headerSize,n.end)){let n=t.start+t.headerSize;if(t.type!==`mehd`||n+8>t.end)continue;if(e.getUint8(n)===1){if(n+12>t.end)return null;let r=e.getBigUint64(n+4);return r===0n||r>BigInt(2**53-1)?null:Number(r)}let r=e.getUint32(n+4);return r===0||r===4294967295?null:r}return null}async function readHeaderMoov(e,t){let n=e.reader.size,a=t.start+t.size>n;if(e.movie.moov=[t.start,t.size],!a&&t.size>268435456)throw new r.t(`too-large`,`unsupported: moov larger than 256 MiB`);let o=await e.reader.view(t.start,a?Math.min(n-t.start,i.r):t.size,a?0:ahead(e,t.start,t.size+32));return countTracks(o,e.plan.maxTracks),await i.f(e.reader,t,e.movie),o}async function walkTop(e){let{reader:n,movie:a}=e,o=n.size,c=0,l=null,u=!1,d=0,f=!1,walked=(e,t=null)=>({moov:l,fragments:a.hasMvex&&(e!==null||t!==null),firstMoof:e,run:e?e.start-d:0,limit:t});try{for(;c+8<=o;){let p=l!==null&&!a.hasMvex,m;if(p){if(m=n.peek(c,Math.min(16,o-c)),!m){if(f)break;f=!0;try{m=await peekAt(e,c,e.plan.head)}catch(t){if(i.c(t,n))throw t;let r=e.budget.take();if(r)throw r;a.warnings.push(`fragments ignored: read at ${c} after a moov without an mvex failed: ${t.message}`);break}}if(++e.boxes>e.plan.maxTopLevelBoxes){a.warnings.push(`too many top-level boxes`);break}}else visit(e),m=await peekAt(e,c,u&&!l?e.plan.tail:e.plan.head);let h;try{h=r.o(m,0,o-c,c)}catch(e){if(!l)throw e;a.warnings.push(`ignored trailing data at ${c}: ${e.message}`);break}if(!h)break;let g={...h,start:c,end:Math.min(c+h.size,o)},_=h.truncated&&l!==null&&g.type!==`mdat`&&g.type!==`moof`;h.truncated&&(a.truncated=!0,a.warnings.push(_?`ignored trailing data at ${c}: not a box`:`'${g.type}' at ${c} is truncated`));let v={type:g.type,start:c,end:g.end,sidx:null};if(a.topLevel.push(v),p&&g.type===`moof`&&a.warnings.push(`fragments ignored: moof at ${c} after a moov without an mvex`),_||p&&g.type===`moof`||l&&g.type===`mfra`)break;if(l&&g.type===`moof`)return walked(g);if(g.type===`ftyp`&&!p){let r=await n.view(c,Math.min(g.size,1024),ahead(e,c,e.plan.head)),i=g.headerSize;if(i+4<=r.byteLength){a.hasFtyp=!0,a.brand=t.n(r,i);for(let e=i+8;e+4<=r.byteLength;e+=4)a.compatibleBrands.push(t.n(r,e))}}else if(g.type===`moov`)l?a.warnings.push(`ignored a second moov at ${c}`):l=await readHeaderMoov(e,g);else if(g.type===`moof`)throw new r.t(`corrupt`,`corrupt file: movie fragment (moof) before moov`);else if(g.type===`sidx`&&!h.truncated){let t=p?n.peek(c,g.size):await n.view(c,g.size,ahead(e,c,e.plan.head)),a=t&&r.o(t,0,t.byteLength);a&&(v.sidx=i.S(t,a,c))}s.has(g.type)||(d=c+h.size),u=g.type===`mdat`,c+=h.size}}catch(t){if(!l||i.c(t,n))throw t;let r=e.budget.take()??(isLimit(t)?t:null);if(!r)throw t;return walked(null,r)}if(!l)throw new r.t(`no-header`,`incomplete file: no moov`);return walked(null)}async function readTailIndex(e){let{reader:n}=e,a=n.size,o=Math.min(a,65536);if(o<16)return null;let s=n.peek(a-o,o)??await n.view(a-o,o,0);if(t.n(s,o-12)!==`mfro`)return null;let c=s.getUint32(o-4);if(c<24||c>Math.min(a,16777216))return null;let l=c<=o?new DataView(s.buffer,s.byteOffset+o-c,c):await n.view(a-c,c,0),u;try{u=r.o(l,0,c)}catch{return null}if(!u||u.type!==`mfra`)return null;let d=new Map;for(let e of r.a(l,u.headerSize,u.end)){let t=e.start+e.headerSize;if(e.type!==`tfra`||t+8>e.end)continue;let n=l.getUint32(t+4),r=d.get(n)??new Set;i.y(l,e.start,e.end,r,a-c),r.size>0&&d.set(n,r)}return{mfraSize:c,offsets:d}}function parseFragment(e,t,a,o){let s=r.o(t,0,t.byteLength);s&&i.x(t,s,a,e.movie.moofCount,e.movie,n.r),e.movie.moofCount++,o.add(a)}async function walkFragments(e,t,n,a){let s=t;for(;s+8<=n;){visit(e);let t=await peekAt(e,s,e.plan.head),c;try{c=r.o(t,0,n-s,s)}catch{return!1}if(!c||c.truncated)return!1;if(c.type===`mfra`)return!0;if(c.type===`moof`&&!a.has(s)){if(c.size>o)throw new r.t(`too-large`,`corrupt file: moof larger than 64 MiB at ${s}`);parseFragment(e,await e.reader.view(s,c.size,ahead(e,s,e.plan.head)),s,a)}else c.type===`sidx`&&e.movie.topLevel.push({type:`sidx`,start:s,end:s+c.size,sidx:i.S(await e.reader.view(s,c.size,ahead(e,s,e.plan.head)),c,s)});s+=c.size}return!0}function runTo(e,t,n){let a=[];try{for(let o=t;o<e.byteLength;){let t=r.o(e,o,e.byteLength);if(!t||t.truncated||!s.has(t.type))return null;a.push({type:t.type,start:n+o,end:n+t.end,sidx:t.type===`sidx`?i.S(e,t,n+o):null}),o=t.end}}catch{return null}return a}async function preRun(e,t,n,r){let i=Math.max(r,t-Math.max(n,4096));if(t-i<8)return t;let a=await e.reader.view(i,t-i,ahead(e,i,e.plan.head));for(let t=0;t+8<=a.byteLength;t++){let n=a.getUint32(t+4)===1936286840?runTo(a,t,i):null;if(n){for(let t of n)count$1(e),e.movie.topLevel.push(t);return i+t}}return t}function lastSidx(e,t,n){let r=null;for(let i of e.topLevel){let e=i.sidx;e&&(!r||i.start>r.start)&&!e.hierarchical&&e.referenceId===t.id&&e.timescale===t.timescale&&(!n||n.includes(e.coverageEnd))&&(r=i)}return r}function timingOf(e,t,n){let r={...e,fragments:n},a=i._(r);return i.v(r,t,a,i.h(r,t.timescale,a))}function hasFragmentData(e,t,n){return t.trex.has(e.id)||(n?.offsets.has(e.id)??!1)||t.topLevel.some(t=>t.sidx?.referenceId===e.id)}function stillFragmented(e,t,n,r,a){if(!a)return!1;let o=i._({...e,fragments:[]}).moovSamples;for(let t of e.fragments)o+=t.count;if(o>1)return!1;for(let t of n?.offsets.get(e.id)??[])if(!r.has(t))return!1;for(let n of t.topLevel)if(n.sidx?.referenceId===e.id&&n.sidx.subsegments.some(([e])=>!r.has(e)))return!1;let s=e.fragments[0],c=t.trex.get(e.id)?.duration||(s?s.durations?.[0]??s.defaultDuration:0);return c>0&&a.ticks*e.timescale<=c*a.timescale}function fragmentedPrimary(e){let t=null,rank=e=>e.auxiliary?.5:e.enabled?2:1,area=e=>e.result.width*e.result.height;for(let n of e)t?rank(n)===rank(t)?n.result.isStill===t.result.isStill?area(n)===area(t)?n.result.trackId<t.result.trackId&&(t=n):t=area(n)>area(t)?n:t:t=t.result.isStill?n:t:t=rank(n)>rank(t)?n:t:t=n;return t?t.result.trackId:null}function headerTrack(e){e.presentedFrames=null,delete e.provenance.presentedFrames}function isLimit(e){let t=e?.errorCode;return typeof t==`string`&&t.endsWith(`-limit`)}function cachedBox(e,t){let n=e.size,i=t+8<=n?e.peek(t,Math.min(16,n-t)):null;try{let e=i&&r.o(i,0,n-t,t);return e&&!e.truncated?e:null}catch{return null}}function onlyFragment(e,t){let n=t.start+t.size,r=cachedBox(e,n);if(n===e.size)return!0;if(r?.type!==`mdat`)return!1;let i=n+r.size,a=cachedBox(e,i);return i===e.size||a?.type===`mfra`&&i+a.size===e.size}function lastOffset(e){let t=null;for(let n of e??[])(t===null||n>t)&&(t=n);return t}async function resolveFragmented(e,n,a,s,c){let{reader:l,plan:u,movie:d}=e,f=l.size,isMain=e=>a.includes(e)||e.handler===`soun`,p=d.tracks.filter(e=>isMain(e)||e.hasMdhd),m=new Map,h=new Map,g=new Set,_=new Set,v=new Map,y=n.firstMoof,b=null,x=n.limit,S=!1,C=f,open=()=>p.filter(e=>!m.has(e)&&(a.includes(e)||hasFragmentData(e,d,b))),settle=(e,t,n,r=m)=>{let i=lastSidx(d,e,t)?.sidx,a=i?i.earliestPts+i.durationSum:0,o=timingOf(e,d,n).durationTs;(t?!i:!S&&i&&i.start<C&&i.coverageEnd<=C&&a<o)||r.set(e,{ticks:Math.max(o,a),timescale:e.timescale,provenance:`header`})},bySidx=e=>{let t=open(),n=t.filter(t=>lastSidx(d,t,e)!==null);g.clear();for(let r of t)n.includes(r)?settle(r,e,r.fragments,n.length===t.length?m:h):g.add(r)};try{let t=y?l.peek(y.start,y.size)===null:!1;if(y&&!x&&(u.index||!t||open().some(e=>lastSidx(d,e,[f])!==null))){if(y.size>o)throw new r.t(`too-large`,`corrupt file: moof larger than 64 MiB at ${y.start}`);if(parseFragment(e,l.peek(y.start,y.size)??await l.view(y.start,y.size,ahead(e,y.start,u.head)),y.start,_),S=onlyFragment(l,y),S)for(let e of open())settle(e,null,e.fragments);else bySidx([f])}for(let e of p)v.set(e,e.fragments.length);if(u.index&&!x&&open().length>0&&(b=await readTailIndex(e),b)){let t=f-b.mfraSize;bySidx([f,t]),C=t;let r=!0;for(let t of[!0,!1]){let i=null;for(let e of open()){let n=isMain(e)===t?lastOffset(b.offsets.get(e.id)):null;n!==null&&n<C&&(i===null||n<i)&&(i=n)}if(i===null)continue;let a=p.map(e=>e.fragments.length),o=t&&y?await preRun(e,i,n.run,y.start+y.size):i;r=await walkFragments(e,i,C,_)&&r,p.forEach((e,t)=>{let n=e.fragments,r=v.get(e)??0;e.fragments=[...n.slice(0,r),...n.slice(a[t]),...n.slice(r,a[t])]}),C=o,S=r&&y!==null&&i<=y.start;for(let e of open()){let t=e.fragments.slice(v.get(e));(S||t.length>0&&t[0].baseDts!==null)&&settle(e,null,e.fragments)}}}}catch(t){if(i.c(t,l)||(x=e.budget.take()??(isLimit(t)?t:null),!x))throw t;for(let e of p)e.fragments.length=v.get(e)??0}let w=n.moov?mehdDuration(n.moov):null,T=open().length>0?x:null,E=[...h.keys()].filter(e=>!m.has(e));for(let e of E)m.set(e,h.get(e));for(let e of open())w!==null&&m.set(e,{ticks:w,timescale:d.timescale,provenance:`estimated`});for(let e of p)!m.has(e)&&!a.includes(e)&&!hasFragmentData(e,d,b)&&m.set(e,{ticks:timingOf(e,d,[]).durationTs,timescale:e.timescale,provenance:`header`});a.forEach((e,t)=>{let n=s[t].result,r=m.get(e)??null,i=n.provenance;n.frames=null,n.avgFps=null,n.avgFpsFfprobe=null,n.startSec=null,n.durationSec=r?r.ticks/r.timescale:null,n.isStill=stillFragmented(e,d,b,_,r),delete i.frames,delete i.avgFps,delete i.startSec,r?i.durationSec=r.provenance:delete i.durationSec});let D=open(),O=a.length>0?!a.some(e=>m.has(e)):![...m.keys()].some(isMain)&&D.some(isMain);T&&!O&&c.warnings.push(`fragment index not read: ${T.message}`);let k=[...m].some(([e,t])=>isMain(e)&&e.hasMdhd&&t.provenance===`estimated`),loose=e=>!k&&!isMain(e)&&p.some(e=>isMain(e)&&e.hasMdhd),A=D.filter(loose),j=D.filter(e=>!loose(e)),M=[],N=2**53-1,P={start:N,end:-N,dur:-N},F={...P},I=!0,L=0;for(let[e,t]of m){if(!e.hasMdhd)continue;let n=timingOf(e,d,S?e.fragments:e.fragments.slice(0,v.get(e)??0)).startPts,r=t.provenance!==`estimated`,a=n!==null||S||!hasFragmentData(e,d,b);if(loose(e)&&!(r&&a)){A.push(e);continue}L++;let o=isMain(e)?P:F,s=i.g(t.ticks,1e6,t.timescale);if(o.dur=Math.max(o.dur,s),!r)I=!1;else if(n!==null){let t=i.g(n,1e6,e.timescale);o.start=Math.min(o.start,t),o.end=Math.max(o.end,t+s)}else a||M.push(e)}(P.start===N||P.start>F.start&&P.start-F.start<1e6)&&(P.start=F.start),(P.end===-N||P.end<F.end&&F.end-P.end<1e6)&&(P.end=F.end);let R=P.dur===-N||P.dur<F.dur&&F.dur-P.dur<1e6?F.dur:P.dur;I&&P.start!==N&&P.end!==-N&&P.end>=P.start&&(R=Math.max(R,P.end-P.start)),(!I||L<2)&&(M.length=0);let z=E.length>0?[...g].filter(e=>m.get(e)?.provenance!==`header`):[];E.length>0&&z.length===0&&z.push(...E);let B=new Set([...j,...M,...A,...z]);if(!O)for(let e of p)B.has(e)&&c.warnings.push(`format duration unresolved: track ${e.id}`);if(c.formatDurationSec=j.length+M.length+z.length===0&&R>0?R/1e6:null,c.primaryTrackId=fragmentedPrimary(s),O){let e=x??new r.t(`no-duration`,`incomplete header: fragmented file without a sidx, mfra or mehd duration`);c.error=e.message,c.errorCode=t.i(e)}}function collapse(e){let t=new Map;for(let n of e)t.set(n,(t.get(n)??0)+1);if(t.size!==e.length){e.length=0;for(let[n,r]of t)e.push(r>1?`${n} (${r} times)`:n)}}let c,l;function loadCapture(){return l??=Promise.resolve().then(()=>require("./chunks/config-CSbXzEqr.cjs")).then(e=>c=e,e=>{throw l=void 0,e}),l}async function analyzeIsoBmffHeader(n,r,a){let o=i.b(n.size),s={reader:n,budget:r,plan:a,movie:o,boxes:0},l=e.c(`mp4`);l.scan=`header`,l.warnings=o.warnings,l.errorCode=null;try{let e=await walkTop(s);l.container=i.a(o),l.brand=o.brand;let u=o.ffprobeReject,d=u!==null&&u.moof<o.ffprobeMoofLimit;d&&o.warnings.push(`ffprobe 8.1.1 ${u.reason}`);let f=i.p(o.tracks);[...f].some(([e,t])=>e.id!==t)&&o.warnings.push(`duplicate track_ID values`);let p=o.tracks.filter(e=>i.l(e,o)),m=p.map(e=>i.i(e,o,{maxExpandedSamples:0},{reportedId:f.get(e)??e.id,ffprobeFails:d}));for(let e of m)headerTrack(e.result);let h=null;if(a.codecConfig)try{let{isoCodecConfig:e}=c??await r.race(loadCapture());p.forEach((t,n)=>m[n].result.codecConfig=e(o,t))}catch(e){if(i.c(e,n))throw e;h=e}l.videoTracks=m.map(e=>e.result),l.audioTrackCount=o.tracks.filter(e=>e.handler===`soun`).length,e.fragments?await resolveFragmented(s,e,p,m,l):(l.primaryTrackId=i.d(m),l.formatDurationSec=i.s(o,new Set(p)),e.limit&&(l.error=e.limit.message,l.errorCode=t.i(e.limit))),h!==null&&l.warnings.push(`codec configuration unavailable: ${h instanceof Error?h.message:String(h)}`)}catch(e){if(i.c(e,n))throw e;let a=r.take(),s=a?a.message:e instanceof Error?e.message:String(e);l.container=i.a(o),l.brand=o.brand,l.error=a||e?.errorCode?s:`corrupt file: ${s}`,l.errorCode=t.i(a??e),l.videoTracks=[],l.audioTrackCount=0,l.primaryTrackId=null,l.formatDurationSec=null}l.layout=i.u(o),collapse(l.warnings);for(let e of l.videoTracks)collapse(e.warnings);return l}const u=Object.freeze({maxBytes:16777216,maxReads:8,maxMs:1e4,maxTracks:64,maxTopLevelBoxes:1024});var LimitError=class extends Error{constructor(e,t){super(t),this.name=`LimitError`,this.errorCode=e}};const noop=()=>{};function sourceError(e){return Object.assign(Error(e),{name:``})}function bufferOf(e){if(ArrayBuffer.isView(e)){let t=new Uint8Array(e.buffer,e.byteOffset,e.byteLength);return t.byteOffset===0&&t.byteLength===t.buffer.byteLength&&t.buffer instanceof ArrayBuffer?t.buffer:t.slice().buffer}if(typeof e?.byteLength!=`number`)throw sourceError(`the source returned ${e===null?`null`:typeof e} instead of bytes`);return e}var LimitedSource=class{size;#e;#t;#n;#r;#i;#a=new AbortController;#o;#s;#c=()=>this.#a.abort(this.#s?.reason);#l=null;#u=!1;#d=0;#f=0;#p=null;constructor(e,t,n,r){this.size=t,this.#e=e,this.#t=n.maxBytes,this.#n=n.maxReads,this.#r=n.maxMs,this.#i=performance.now()+n.maxMs,this.#s=r;let i=this.#a.signal;this.#o=new Promise((e,t)=>i.addEventListener(`abort`,()=>t(i.reason),{once:!0})),this.#o.catch(noop),r&&(r.aborted?this.#c():r.addEventListener(`abort`,this.#c,{once:!0})),Number.isFinite(n.maxMs)&&!i.aborted&&this.#m(n.maxMs)}#m(e){let t=Math.min(e,2147483647);this.#l=setTimeout(()=>{if(e>t)return this.#m(e-t);this.#l=null,this.#u=!0,this.#a.abort(new LimitError(`time-limit`,`limit reached: maxMs ${this.#r}`))},t)}get bytesLeft(){return this.#t-this.#f}get bytesRequested(){return this.#f}checkTime(){if(this.#u||performance.now()>=this.#i)throw this.#h(`time-limit`,`limit reached: maxMs ${this.#r}`)}take(){let e=this.#p;return this.#p=null,e}race(e){return e.catch(noop),Promise.race([e,this.#o])}slice(e=0,t=this.size){return{arrayBuffer:()=>this.#_(e,t)}}dispose(){this.#l!==null&&clearTimeout(this.#l),this.#l=null,this.#s?.removeEventListener(`abort`,this.#c)}#h(e,t){return this.#p=new LimitError(e,t),this.#p}#g(e,t){if(this.#d+1>this.#n)return this.#h(`read-limit`,`limit reached: maxReads ${this.#n}`);if(this.#f+e>this.#t)return this.#h(`byte-limit`,`limit reached: maxBytes ${this.#t} (${this.#f+e-this.#t} more bytes needed at offset ${t})`);try{this.checkTime()}catch(e){return e}return null}#_(e,t){let n=Math.max(0,t-e),r=this.#g(n,e);if(r)return Promise.reject(r);this.#d++,this.#f+=n;let i;try{let n=this.#e;i=Promise.resolve(typeof n.read==`function`?n.read(e,t,this.#a.signal):n.slice(e,t).arrayBuffer()).then(bufferOf)}catch(e){i=Promise.reject(e)}return i.catch(noop),Promise.race([i,this.#o]).catch(e=>{if(e instanceof LimitError)this.#p=e;else if(!this.#a.signal.aborted&&e?.name===`AbortError`)throw sourceError(String(e));throw e})}};function shown(e){return typeof e==`string`?JSON.stringify(e):typeof e==`object`&&e?Array.isArray(e)?`an array`:`an object`:String(e)}function count(e,t,n=0,r=!1){if(r&&e===1/0)return e;if(typeof e!=`number`||!Number.isSafeInteger(e)||e<n)throw TypeError(`${t} must be a non-negative safe integer${n>0?` of at least ${n}`:``}${r?` or Infinity`:``}, got ${shown(e)}`);return e}function record(e,t){if(e===void 0)return{};if(typeof e!=`object`||!e||Array.isArray(e))throw TypeError(`${t} must be an object, got ${shown(e)}`);return e}function flag(e,t){if(e!==void 0&&typeof e!=`boolean`)throw TypeError(`${t} must be a boolean, got ${shown(e)}`);return e===!0}function resolveHeaderOptions(e,t){let r=e;if(typeof r!=`object`||!r||typeof r.slice!=`function`)throw TypeError(`source must be a Blob or a BlobLike with a size and a slice method, got ${shown(e)}`);let i=count(r.size,`source.size`),a=record(t,`options`);if(a.name!==void 0&&typeof a.name!=`string`)throw TypeError(`name must be a string, got ${shown(a.name)}`);let o=a.signal??null;if(o!==null){let e=o;if(typeof e!=`object`||typeof e.aborted!=`boolean`||typeof e.addEventListener!=`function`||typeof e.removeEventListener!=`function`)throw TypeError(`signal must be an AbortSignal, got ${shown(o)}`)}let s=record(a.limits,`limits`),c={...u};for(let e of Object.keys(u))s[e]!==void 0&&(c[e]=count(s[e],`limits.${e}`,0,!0));let l=record(a.readAhead,`readAhead`);for(let e of Object.keys(l))if(e!==`head`&&e!==`tail`&&l[e]!==void 0)throw TypeError(`readAhead takes only head and tail, got ${e}`);let d=l.head===void 0?n.n.head:count(l.head,`readAhead.head`,1024),f=l.tail===void 0?n.n.tail:count(l.tail,`readAhead.tail`,16),p=a.fragmentedDuration??`index`;if(p!==`index`&&p!==`header`)throw TypeError(`fragmentedDuration must be 'header' or 'index', got ${shown(p)}`);return{name:a.name??(typeof File<`u`&&e instanceof File?e.name:``),size:i,signal:o,limits:c,head:d,tail:f,index:p===`index`,codecConfig:flag(a.codecConfig,`codecConfig`),color:flag(a.color,`color`),aspect:flag(a.aspect,`aspect`)}}function docTypeOf(e){let t=4,vint=n=>{if(t>=e.length)return null;let r=e[t],i=1;for(;i<=8&&!(r&128>>i-1);)i++;if(i>8||t+i>e.length)return null;let a=n?r:r&255>>i;for(let n=1;n<i;n++)a=a*256+e[t+n];return t+=i,a},n=vint(!1);if(n===null)return null;let r=Math.min(e.length,t+n);for(;t<r;){let n=vint(!0),i=vint(!1);if(n===null||i===null||t+i>r)return null;if(n===17026)return String.fromCharCode(...e.subarray(t,t+i)).replace(/\0+$/,``);t+=i}return null}function failed(e,t,n){return e.error=t,e.errorCode=n,e}async function analyzeSource(t,n,r){let i={...e.c(`unknown`),scan:`header`,errorCode:null,layout:null},o=t.size;if(o===0)return failed(i,`not a recognised container: empty file`,`empty-file`);let s=await t.bytes(0,Math.min(1024,o),Math.min(o,r.head,n.bytesLeft)),c=a.n(s);if(c.kind===`unknown`)return failed(i,`not a recognised container: ${c.reason}`,`unrecognised`);if(c.kind===`matroska`)return i.container=docTypeOf(s)===`webm`?`webm`:`matroska`,failed(i,`container not read by the header entry: ${i.container}`,`unsupported-container`);let{maxTracks:l,maxTopLevelBoxes:u}=r.limits;return analyzeIsoBmffHeader(t,n,{head:r.head,tail:r.tail,maxTracks:l,maxTopLevelBoxes:u,index:r.index,codecConfig:r.codecConfig||r.color||r.aspect})}const messageOf=e=>e instanceof Error?e.message||e.name:String(e);let d,f;function loadMetadata(){return f??=Promise.resolve().then(()=>require("./chunks/metadata-ECdzNLkE.cjs")).then(e=>d=e,e=>{throw f=void 0,e}),f}async function withMetadata(e,n,r){try{let{withAspect:i,withColor:a}=d??await r.race(loadMetadata());t.o(n.signal);let o={keepCodecConfig:n.codecConfig},s=n.color?a(e,n.aspect?{keepCodecConfig:!0}:o):e;return n.aspect?i(s,o):s}catch(r){t.o(n.signal);let i=e.videoTracks.map(e=>{let t={...e};return n.codecConfig||delete t.codecConfig,t});return{...e,videoTracks:i,warnings:[...e.warnings,`color and aspect unavailable: ${messageOf(r)}`]}}}async function analyzeHeader(n,r={}){let i=performance.now(),a=resolveHeaderOptions(n,r);t.o(a.signal);let o=new LimitedSource(n,a.size,a.limits,a.signal),s=new t.t(o,{headWindow:a.head,signal:a.signal}),c;try{c=await analyzeSource(s,o,a),(a.color||a.aspect)&&c.videoTracks.some(e=>e.codecConfig)&&(c=await withMetadata(c,a,o))}catch(n){t.o(a.signal);let r=o.take();c={...e.c(`unknown`),scan:`header`,errorCode:null,layout:null},failed(c,r?r.message:messageOf(n),t.i(r??n))}finally{o.dispose()}return{name:a.name,size:a.size,...c,io:{reads:s.reads,bytesRead:s.bytesFetched,ms:performance.now()-i,bytesRequested:o.bytesRequested},engine:`own`}}exports.HEADER_DEFAULT_LIMITS=u,exports.analyzeHeader=analyzeHeader,exports.errorClass=e.i,exports.errorCodeOf=e.a,exports.isAbortError=e.t,exports.isTimeoutError=e.r,exports.warningCodeOf=e.o,exports.warningCodesOf=e.s;
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./chunks/guards-DbQNcAZS.cjs"),t=require("./chunks/probe-Cn1DtL-C.cjs"),n=require("./chunks/errors-0oBg1Ai6.cjs");async function analyze(e,t={}){let{name:n=typeof File<`u`&&e instanceof File?e.name:``,engine:r,...i}=t,a=await(r?await r.load():(await Promise.resolve().then(()=>require("./chunks/runtime-BgAlQQee.cjs"))).ownEngine).run(e,n,i);if(!a.videoTracks.some(e=>e.codecConfig?.family===`isobmff-moov`))return a;try{let{isoBoxes:e}=await Promise.resolve().then(()=>require("./chunks/config-CSbXzEqr.cjs"));return{...a,videoTracks:a.videoTracks.map(t=>t.codecConfig?.family===`isobmff-moov`?{...t,codecConfig:e(t.codecConfig.moov,t.codecConfig.trak)}:t)}}catch{return a}}const r=new Set([`estimated`,`sampled`]);function summarize(e){let t=e.primaryTrackId===null?null:e.videoTracks.find(t=>t.trackId===e.primaryTrackId)??null,n=e.error??(t?t.avgFps?null:`no frame rate`:`no video track`);return{ok:n===null,name:e.name,container:e.container,engine:e.engine,fps:t?.avgFps?.value??null,fpsRational:t?.avgFps?`${t.avgFps.num}/${t.avgFps.den}`:null,nominalFps:t?.nominalFps?.value??null,frames:t?t.presentedFrames??t.frames:null,durationSec:t?.durationSec??e.formatDurationSec,width:t?.width??null,height:t?.height??null,rotation:t?.rotation??null,isVfr:t?.isVfr??null,minFps:t?.minFps?.value??null,maxFps:t?.maxFps?.value??null,approximate:!!t&&[`avgFps`,`nominalFps`,`frames`].some(e=>r.has(t.provenance[e])),error:n,warnings:[...e.warnings,...t?.warnings??[]]}}const i=[`isobmff`,`matroska`],splitBy=e=>(t,n,r,a,o,s)=>{let pool=e=>{let i=n[e];return i?r(`${t}-${e}`,i):r(t,s)},c=e(pool,a,o);return{kind:t,pool:()=>c,warmPools:e=>i.filter(t=>e.includes(t)).map(e=>[pool(e),[e]])}},a=[26,69,223,163],familyOf=(e,t)=>new Promise(n=>{let iso=()=>n(`isobmff`);if(t?.aborted)return iso();t?.addEventListener(`abort`,iso),Promise.resolve().then(()=>e.slice(0,4).arrayBuffer()).then(e=>{let t=new Uint8Array(e);return a.every((e,n)=>t[n]===e)?`matroska`:`isobmff`}).then(n,iso).finally(()=>t?.removeEventListener(`abort`,iso))}),runByHead=(e,t,n)=>({run:(r,i)=>t(familyOf(r.file,i.signal),r.engine).then(a=>n.aborted?t(Promise.reject(),r.engine):e(a).run(r,i))}),o=splitBy(runByHead),s={id:`own`,thread:`worker`,createWorkers:{isobmff:()=>new Worker(new URL(`../worker/own-isobmff.worker.js`,require("url").pathToFileURL(__filename).href),{type:`module`}),matroska:()=>new Worker(new URL(`../worker/own-matroska.worker.js`,require("url").pathToFileURL(__filename).href),{type:`module`})},load:()=>Promise.resolve().then(()=>require("./chunks/runtime-BgAlQQee.cjs")).then(e=>e.ownEngine)};function createProbe(e={}){let{engines:n=[],worker:r,...i}=e;return t.t({...i,engines:[s,...n],worker:r===`inline`?`inline`:void 0,split:o})}exports.ProbeError=n.t,exports.analyze=analyze,exports.compareResults=t.r,exports.createProbe=createProbe,exports.errorClass=e.i,exports.errorCodeOf=e.a,exports.isAbortError=e.t,exports.isTimeoutError=e.r,exports.summarize=summarize,exports.warningCodeOf=e.o,exports.warningCodesOf=e.s;
@@ -0,0 +1,13 @@
1
+ export { analyze, type InThreadOptions } from './analyze.cjs';
2
+ export { compareResults, type Agreement, type ComparedField, type ComparedFieldName, type ComparedValue } from './api/agreement.cjs';
3
+ export { errorClass, errorCodeOf, warningCodeOf, warningCodesOf, type ErrorClass } from './api/codes.cjs';
4
+ export { ProbeError, type ProbeErrorCode } from './api/errors.cjs';
5
+ export { isAbortError, isTimeoutError } from './api/guards.cjs';
6
+ export { summarize, type VideoFpsSummary } from './api/summary.cjs';
7
+ export type * from './client/types.cjs';
8
+ export type { BlobLike, BlobSlice } from './core/reader.cjs';
9
+ export type * from './core/types.cjs';
10
+ export type { MediabunnyNative, MediabunnyTrackNative, Mp4boxNative, Mp4boxTrackNative, RvfcConfidence, RvfcDetails, RvfcPass } from './engines/types.cjs';
11
+ export type { ContainerFamily, EngineDescriptor, EngineModule, WorkerEngineId } from './plugins/types.cjs';
12
+ export type { WireOptions } from './worker/protocol.cjs';
13
+ export { createProbe } from './probe.cjs';
@@ -0,0 +1,17 @@
1
+ import type { BlobLike } from '../core/reader.cjs';
2
+ import type { AnalyzeOptions, EngineId, FileResult } from '../core/types.cjs';
3
+ export type { EngineId };
4
+ export type ContainerFamily = 'isobmff' | 'matroska';
5
+ export type WorkerEngineId = 'own' | 'mp4box' | 'mediabunny';
6
+ export interface EngineModule<R extends FileResult = FileResult> {
7
+ readonly run: (blob: BlobLike, name: string, opts: AnalyzeOptions) => Promise<R>;
8
+ readonly containers: readonly ContainerFamily[] | 'any';
9
+ readonly warm?: (containers: readonly ContainerFamily[]) => Promise<void>;
10
+ }
11
+ export interface EngineDescriptor<Id extends EngineId = EngineId> {
12
+ readonly id: Id;
13
+ readonly thread: 'worker' | 'main';
14
+ readonly createWorker?: () => Worker;
15
+ readonly load: () => Promise<EngineModule>;
16
+ readonly loadOwn?: () => Promise<EngineModule>;
17
+ }
@@ -0,0 +1,19 @@
1
+ import type { CheckRules } from '../check/index.cjs';
2
+ export type PresetSourceKind = 'official' | 'gateway';
3
+ export interface ProviderPreset {
4
+ readonly name: string;
5
+ /** @deprecated Use video-fps-probe/presets/sources.json; sourceUrl will be removed in the next major. */
6
+ readonly sourceUrl: string;
7
+ readonly sourceKind: PresetSourceKind;
8
+ readonly verifiedAt: string;
9
+ readonly note: string;
10
+ readonly rules: {
11
+ readonly [K in keyof CheckRules]: Readonly<CheckRules[K]>;
12
+ };
13
+ }
14
+ export declare const klingO1ReferenceVideo: ProviderPreset;
15
+ export declare const klingMotionControl: ProviderPreset;
16
+ export declare const runwayAleph2: ProviderPreset;
17
+ export declare const veoExtend: ProviderPreset;
18
+ export declare const wan26ReferenceToVideoFal: ProviderPreset;
19
+ export declare const seedance2ReferenceVideo: ProviderPreset;
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const frozen=e=>{for(let t of Object.values(e))t&&typeof t==`object`&&frozen(t);return Object.freeze(e)},e=frozen({name:`Kling O1 reference video`,sourceUrl:`https://kling.ai/document-api/api/video/o1/video-omni.md`,sourceKind:`official`,verifiedAt:`2026-10-25`,note:`The feature or base video of the Kling O1 omni video API, one per request. No codec, aspect ratio or audio rule is published for it. The page does not say whether 200 MB is decimal or binary; the larger reading, 209,715,200 bytes, is checked.`,rules:{fps:{min:24,max:60},duration:{min:3,max:10},frameSize:{minShortSide:700,maxLongSide:2160},fileSize:{max:209715200},containers:[`mp4`,`mov`]}}),t=frozen({name:`Kling Motion Control 3.0 reference video`,sourceUrl:`https://kling.ai/document-api/api/video/motion-control.md`,sourceKind:`official`,verifiedAt:`2026-10-25`,note:`The 30 s maximum applies when the character orientation follows the video; it is 10 s when it follows the image. No frame rate rule is published. The page does not say whether 100 MB is decimal or binary; the larger reading, 104,857,600 bytes, is checked.`,rules:{duration:{min:3,max:30},frameSize:{minShortSide:340,maxLongSide:3850},fileSize:{max:104857600},containers:[`mp4`,`mov`]}}),n=frozen({name:`Runway Aleph 2.0 input video`,sourceUrl:`https://docs.dev.runwayml.com/assets/inputs`,sourceKind:`official`,verifiedAt:`2026-10-25`,note:`The file size is the ephemeral upload limit; a URL is limited to 32 MB and a data URI to 16 MB. The codecs join what the four containers accept, including the discouraged ones, without pairing codec and container. Aleph keeps the input resolution up to 1080p, and the page does not say whether a larger input is refused, so no size rule. The page does not say whether 200 MB is decimal or binary; the larger reading, 209,715,200 bytes, is checked.`,rules:{fps:{max:30},duration:{min:2,max:30},fileSize:{max:209715200},containers:[`mp4`,`mov`,`matroska`,`webm`],codecs:[`h264`,`hevc`,`av1`,`prores`,`vp8`,`vp9`,`mjpeg`,`mpeg2video`]}}),r=frozen({name:`Google Veo 3.1 video to extend`,sourceUrl:`https://docs.cloud.google.com/gemini-enterprise-agent-platform/models/video/extend-a-veo-video`,sourceKind:`official`,verifiedAt:`2026-10-25`,note:`The frame rate must be 24, so 23.976 fails. The resolution must be 720p, 1080p or 4k; 4k is not given in pixels, so only the 720 px short side is checked. No codec or file size limit is published.`,rules:{fps:{exact:24},duration:{min:1,max:30},frameSize:{minShortSide:720},aspectRatio:{oneOf:[`16/9`,`9/16`]},containers:[`mp4`]}}),i=frozen({name:`Wan 2.6 reference to video on fal`,sourceUrl:`https://fal.ai/models/wan/v2.6/reference-to-video/api`,sourceKind:`gateway`,verifiedAt:`2026-10-25`,note:`As the fal endpoint documents it, for each of 1 to 3 reference videos; fal serves a model it does not make, and no first-party page was verified. No resolution, aspect ratio or codec limit is published for the input. The page does not say whether 30 MB is decimal or binary; the larger reading, 31,457,280 bytes, is checked.`,rules:{fps:{min:16},duration:{min:2,max:30},fileSize:{max:31457280},containers:[`mp4`,`mov`],severity:`warn`}}),a=frozen({name:`Seedance 2.0 reference video`,sourceUrl:`https://evolink.ai/docs/en/api-manual/video-series/seedance2.0/seedance-2.0-reference-to-video`,sourceKind:`gateway`,verifiedAt:`2026-10-25`,note:`As the EvoLink gateway documents it; no first-party page was verified, and the Runware model page gives only the count. Up to 3 videos of at most 15 s together. The 480p, 720p or 1080p list is checked only through the pixel range. The page does not say whether 50 MB is decimal or binary; the larger reading, 52,428,800 bytes, is checked.`,rules:{fps:{min:24,max:60},duration:{min:2,max:15},frameSize:{minShortSide:300,maxLongSide:6e3},pixels:{min:409600,max:2086876},aspectRatio:{min:.4,max:2.5},fileSize:{max:52428800},containers:[`mp4`,`mov`],severity:`warn`}});exports.klingMotionControl=t,exports.klingO1ReferenceVideo=e,exports.runwayAleph2=n,exports.seedance2ReferenceVideo=a,exports.veoExtend=r,exports.wan26ReferenceToVideoFal=i;
@@ -0,0 +1,3 @@
1
+ import type { IdOf, Probe, ProbeOptions } from './client/types.cjs';
2
+ import type { EngineDescriptor } from './plugins/types.cjs';
3
+ export declare function createProbe<const E extends readonly EngineDescriptor[] = []>(options?: ProbeOptions<E>): Probe<'own' | IdOf<E[number]>>;
@@ -0,0 +1,47 @@
1
+ import type { BlobLike } from '../core/reader.cjs';
2
+ export type { BlobLike, BlobSlice } from '../core/reader.cjs';
3
+ export interface RangeRequest {
4
+ readonly start: number;
5
+ readonly end: number;
6
+ readonly range: string;
7
+ readonly signal: AbortSignal | null;
8
+ }
9
+ export interface RangeReply {
10
+ readonly status?: number | null;
11
+ readonly contentRange?: string | null;
12
+ readonly contentLength?: number | null;
13
+ readonly bytes?: ArrayBuffer | ArrayBufferView | null;
14
+ readonly body?: (expectedLength: number) => Promise<ArrayBuffer | ArrayBufferView>;
15
+ readonly discard?: () => void;
16
+ }
17
+ export type RangeFetcher = (request: RangeRequest) => Promise<RangeReply>;
18
+ export interface RangeSource extends BlobLike {
19
+ read(start: number, end: number, signal?: AbortSignal | null): Promise<Uint8Array>;
20
+ }
21
+ export interface RangeSourceInit {
22
+ size: number;
23
+ signal?: AbortSignal | null;
24
+ }
25
+ export interface OpenRangeSourceInit {
26
+ size?: number;
27
+ headBytes?: number;
28
+ signal?: AbortSignal | null;
29
+ }
30
+ export interface HttpRangeInit {
31
+ headers?: Readonly<Record<string, string>>;
32
+ fetch?: typeof globalThis.fetch;
33
+ }
34
+ export type RangeSourceErrorCode = 'unverifiable-reply' | 'unexpected-status' | 'range-mismatch' | 'size-changed' | 'length-mismatch' | 'body-too-long' | 'body-too-short' | 'invalid-reply';
35
+ export interface RangeSourceErrorOptions {
36
+ status?: number | null;
37
+ cause?: unknown;
38
+ }
39
+ export declare class RangeSourceError extends Error {
40
+ readonly code: RangeSourceErrorCode;
41
+ readonly status: number | null;
42
+ constructor(code: RangeSourceErrorCode, message: string, options?: RangeSourceErrorOptions);
43
+ }
44
+ export declare function rangeSource(fetcher: RangeFetcher, init: RangeSourceInit): RangeSource;
45
+ export declare function openRangeSource(fetcher: RangeFetcher, init?: OpenRangeSourceInit): Promise<RangeSource>;
46
+ export declare function httpRange(url: string | URL, init?: HttpRangeInit): RangeFetcher;
47
+ export declare function readCapped(body: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>, expectedLength: number, signal?: AbortSignal | null): Promise<Uint8Array>;