video-fps-probe 0.2.0 → 0.3.0

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 (52) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +26 -14
  3. package/dist/api/errors.js +1 -1
  4. package/dist/api/guards.d.ts +2 -0
  5. package/dist/api/guards.js +2 -1
  6. package/dist/check/index.js +12 -2
  7. package/dist/client/compare.js +2 -2
  8. package/dist/client/create.d.ts +3 -0
  9. package/dist/client/create.js +8 -0
  10. package/dist/client/fallback.js +2 -2
  11. package/dist/client/index.d.ts +1 -1
  12. package/dist/client/index.js +1 -1
  13. package/dist/client/inline.js +1 -1
  14. package/dist/client/lazy.js +1 -1
  15. package/dist/client/probe-client.js +4 -0
  16. package/dist/client/probe.js +23 -22
  17. package/dist/client/route.js +20 -0
  18. package/dist/client/split.js +9 -0
  19. package/dist/client/stub.js +2 -2
  20. package/dist/client/types.d.ts +1 -0
  21. package/dist/core/analyze.js +4 -78
  22. package/dist/core/dispatch.js +76 -0
  23. package/dist/core/isobmff/boxes.js +4 -7
  24. package/dist/core/isobmff/index.js +8 -5
  25. package/dist/core/isobmff/model.js +1 -1
  26. package/dist/core/isobmff/moov.js +12 -7
  27. package/dist/core/isobmff/track.js +4 -2
  28. package/dist/core/matroska/ebml.js +1 -1
  29. package/dist/core/reader.d.ts +0 -12
  30. package/dist/core/reader.js +10 -27
  31. package/dist/engines/common.js +6 -0
  32. package/dist/engines/mediabunny.js +17 -4
  33. package/dist/engines/mp4box.js +2 -1
  34. package/dist/plugins/own.js +5 -2
  35. package/dist/presets/index.d.ts +1 -0
  36. package/dist/presets/sources.json +8 -0
  37. package/dist/probe.js +2 -1
  38. package/dist/react/store.js +1 -1
  39. package/dist/standalone/own-isobmff.worker.js +1 -0
  40. package/dist/standalone/own-matroska.worker.js +1 -0
  41. package/dist/standalone/own.worker.js +1 -1
  42. package/dist/worker/own-isobmff.worker.js +4 -0
  43. package/dist/worker/own-matroska.worker.js +4 -0
  44. package/dist/worker/runtime.d.ts +2 -3
  45. package/dist/worker/runtime.js +2 -97
  46. package/dist/worker/serve.d.ts +3 -0
  47. package/dist/worker/serve.js +97 -0
  48. package/dist/worker/single.js +13 -0
  49. package/package.json +8 -2
  50. package/dist/client/inline.d.ts +0 -19
  51. package/dist/client/pool.d.ts +0 -29
  52. package/dist/client/probe.d.ts +0 -29
@@ -0,0 +1,97 @@
1
+ import { errorMessage, isAbortError } from "../core/dispatch.js";
2
+ import { containerFamily } from "../core/sniff.js";
3
+ import { PROTOCOL_VERSION } from "./protocol.js";
4
+ const PROGRESS_INTERVAL_MS = 50;
5
+ const failure = (message, code) => Object.assign(new Error(message), { code });
6
+ const isFamily = (c) => c === 'isobmff' || c === 'matroska';
7
+ export function serveProbeWorker(engines) {
8
+ const ctx = self;
9
+ const loaded = new Map();
10
+ const running = new Map();
11
+ const post = (reply) => ctx.postMessage({ v: PROTOCOL_VERSION, ...reply });
12
+ const load = (id) => {
13
+ let module = loaded.get(id);
14
+ if (!module) {
15
+ const source = Object.hasOwn(engines, id) ? engines[id] : undefined;
16
+ if (!source)
17
+ return Promise.reject(failure(`unknown engine ${id}`, 'unknown-engine'));
18
+ module = (typeof source === 'function' ? Promise.all([source(), import("../core/stats.js")]).then(([m]) => m) : Promise.resolve(source)).catch((e) => {
19
+ loaded.delete(id);
20
+ throw e;
21
+ });
22
+ loaded.set(id, module);
23
+ }
24
+ return module;
25
+ };
26
+ const fail = (id, e, aborted = false, loading = false) => {
27
+ const code = e?.code;
28
+ const known = code === 'unknown-engine' || code === 'protocol';
29
+ post({
30
+ type: 'error',
31
+ id,
32
+ message: errorMessage(e),
33
+ aborted: aborted || isAbortError(e),
34
+ ...(known ? { code } : loading && { reason: 'load' }),
35
+ });
36
+ };
37
+ const analyzeJob = async (job) => {
38
+ const controller = new AbortController();
39
+ running.set(job.id, controller);
40
+ let lastProgress = 0;
41
+ let loading = true;
42
+ try {
43
+ if (!(job.file instanceof Blob))
44
+ throw failure('malformed analyze request', 'protocol');
45
+ const engine = await load(job.engine);
46
+ loading = false;
47
+ if (engine.containers !== 'any') {
48
+ const family = await containerFamily(job.file);
49
+ if (!family || !engine.containers.includes(family))
50
+ return post({ type: 'skipped', id: job.id, reason: 'unsupported-container', family });
51
+ }
52
+ const result = await engine.run(job.file, job.name, {
53
+ ...job.options,
54
+ signal: controller.signal,
55
+ onProgress: (progress) => {
56
+ const now = performance.now();
57
+ if (now - lastProgress < PROGRESS_INTERVAL_MS)
58
+ return;
59
+ lastProgress = now;
60
+ post({ type: 'progress', id: job.id, progress });
61
+ },
62
+ });
63
+ post({ type: 'result', id: job.id, result });
64
+ }
65
+ catch (e) {
66
+ fail(job.id, e, controller.signal.aborted, loading);
67
+ }
68
+ finally {
69
+ running.delete(job.id);
70
+ }
71
+ };
72
+ const warmJob = async (job) => {
73
+ try {
74
+ if (![job.engines, job.containers].every(Array.isArray) || !job.containers.every(isFamily))
75
+ throw failure('malformed warm request', 'protocol');
76
+ await Promise.all(job.engines.map(async (id) => (await load(id)).warm?.(job.containers)));
77
+ post({ type: 'warmed', id: job.id });
78
+ }
79
+ catch (e) {
80
+ fail(job.id, e, false, true);
81
+ }
82
+ };
83
+ ctx.onmessage = ({ data }) => {
84
+ if (data?.v !== PROTOCOL_VERSION)
85
+ return fail(data?.id ?? 0, failure(`unsupported protocol version ${data?.v}`, 'protocol'));
86
+ if (data.type === 'cancel')
87
+ running.get(data.id)?.abort();
88
+ else if (data.type === 'analyze')
89
+ void analyzeJob(data);
90
+ else if (data.type === 'warm')
91
+ void warmJob(data);
92
+ else {
93
+ const unknown = data;
94
+ fail(unknown.id, failure(`unknown request type ${unknown.type}`, 'protocol'));
95
+ }
96
+ };
97
+ }
@@ -0,0 +1,13 @@
1
+ import { analyzeWith } from "../core/dispatch.js";
2
+ const LABEL = { isobmff: 'ISO BMFF', matroska: 'Matroska' };
3
+ export const wrongFamily = (served, found) => `the own ${LABEL[served]} worker does not read ${LABEL[found]}`;
4
+ export function singleContainerEngine(kind, analyzer) {
5
+ return {
6
+ containers: 'any',
7
+ run: (blob, name, opts) => analyzeWith(blob, name, opts, (found) => {
8
+ if (found !== kind)
9
+ throw new Error(wrongFamily(kind, found));
10
+ return analyzer;
11
+ }),
12
+ };
13
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "video-fps-probe",
3
3
  "private": false,
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "description": "Video frame rate, frame count and VFR detection from MP4/MOV/WebM/MKV container headers in a Web Worker. Matches ffprobe 8.1.1 on the test corpus (0 mismatches), no decoding.",
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -54,6 +54,9 @@
54
54
  "presets": [
55
55
  "dist/presets/index.d.ts"
56
56
  ],
57
+ "presets/sources.json": [
58
+ "dist/presets/sources.json"
59
+ ],
57
60
  "mp4box": [
58
61
  "dist/plugins/mp4box.d.ts"
59
62
  ],
@@ -103,6 +106,7 @@
103
106
  "types": "./dist/presets/index.d.ts",
104
107
  "default": "./dist/presets/index.js"
105
108
  },
109
+ "./presets/sources.json": "./dist/presets/sources.json",
106
110
  "./mp4box": {
107
111
  "types": "./dist/plugins/mp4box.d.ts",
108
112
  "default": "./dist/plugins/mp4box.js"
@@ -128,6 +132,8 @@
128
132
  "default": "./dist/engines/mediabunny.js"
129
133
  },
130
134
  "./standalone/own.worker.js": "./dist/standalone/own.worker.js",
135
+ "./standalone/own-isobmff.worker.js": "./dist/standalone/own-isobmff.worker.js",
136
+ "./standalone/own-matroska.worker.js": "./dist/standalone/own-matroska.worker.js",
131
137
  "./package.json": "./package.json"
132
138
  },
133
139
  "peerDependencies": {
@@ -160,7 +166,7 @@
160
166
  "build:demo": "tsc -b && vite build --config demo/vite.config.ts",
161
167
  "preview": "vite preview --config demo/vite.config.ts",
162
168
  "check:package": "publint --strict && node scripts/check-dist.mjs",
163
- "check:types": "npx --yes @arethetypeswrong/cli@0.18.5 --pack . --ignore-rules cjs-resolves-to-esm --exclude-entrypoints ./standalone/own.worker.js",
169
+ "check:types": "npx --yes @arethetypeswrong/cli@0.18.5 --pack . --ignore-rules cjs-resolves-to-esm --exclude-entrypoints ./standalone/own.worker.js ./standalone/own-isobmff.worker.js ./standalone/own-matroska.worker.js",
164
170
  "prepack": "npm run build:lib && node scripts/check-dist.mjs",
165
171
  "check:aliases": "node scripts/src-aliases.mjs --check",
166
172
  "check:stable-chunks": "node scripts/check-stable-chunks.mjs",
@@ -1,19 +0,0 @@
1
- import type { AnalyzeProgress, EngineId, FileResult } from '../core/types.js';
2
- import type { ContainerFamily, EngineModule } from '../plugins/types.js';
3
- import type { SkipReason, WireOptions } from '../worker/protocol.js';
4
- export interface InlineRun {
5
- engine: EngineId;
6
- signals: readonly (AbortSignal | null | undefined)[];
7
- timeoutMs?: number;
8
- onProgress?: (progress: AnalyzeProgress) => void;
9
- host?: HTMLElement;
10
- }
11
- export type InlineReply = {
12
- type: 'result';
13
- result: FileResult;
14
- } | {
15
- type: 'skipped';
16
- reason: SkipReason;
17
- family: ContainerFamily | null;
18
- };
19
- export declare function runInline(load: () => Promise<EngineModule>, file: Blob, name: string, options: WireOptions, run: InlineRun): Promise<InlineReply>;
@@ -1,29 +0,0 @@
1
- import { ProbeError } from '../api/errors.js';
2
- import type { AnalyzeProgress, EngineId } from '../core/types.js';
3
- import { type JobRequest, type WorkerResponse } from '../worker/protocol.js';
4
- export declare const KILL_AFTER_MS = 1500;
5
- export declare const DEFAULT_IDLE_TIMEOUT_MS = 30000;
6
- export declare const LOAD_FAILED = "worker script failed to load or parse; the app may have been redeployed";
7
- export declare const workerFailed: (message: string, reason: NonNullable<ProbeError["reason"]>, engine?: EngineId | null, cause?: unknown) => ProbeError;
8
- export interface PoolOptions {
9
- create: () => Worker;
10
- size?: number;
11
- idleTimeoutMs?: number;
12
- }
13
- export interface RunOptions {
14
- signal?: AbortSignal | null;
15
- timeoutMs?: number;
16
- onProgress?: (progress: AnalyzeProgress) => void;
17
- engine?: EngineId | null;
18
- }
19
- export type PoolReply = Extract<WorkerResponse, {
20
- type: 'result' | 'skipped' | 'warmed';
21
- }>;
22
- export declare class WorkerPool {
23
- #private;
24
- constructor(options: PoolOptions);
25
- get workers(): number;
26
- run(request: JobRequest, options?: RunOptions): Promise<PoolReply>;
27
- dispose(): void;
28
- destroy(): void;
29
- }
@@ -1,29 +0,0 @@
1
- import { ProbeError } from '../api/errors.js';
2
- import type { EngineId } from '../core/types.js';
3
- import type { EngineDescriptor, EngineModule, WorkerEngineId } from '../plugins/types.js';
4
- import type { WireOptions } from '../worker/protocol.js';
5
- import type { InlineReply } from './inline.js';
6
- import { WorkerPool } from './pool.js';
7
- import type { BaseOptions, CallOptions, ClientOptions, CustomWorker, IdOf, Probe } from './types.js';
8
- export interface CoreOptions extends BaseOptions<EngineId> {
9
- worker?: CustomWorker<WorkerEngineId> | 'inline';
10
- engines?: readonly EngineDescriptor[];
11
- }
12
- export type Route = {
13
- kind: string;
14
- pool: () => WorkerPool;
15
- } | {
16
- load: () => Promise<EngineModule>;
17
- } | {
18
- error: ProbeError;
19
- };
20
- export type Run = (id: EngineId, file: Blob, name: string, wire: WireOptions, call: CallOptions<EngineId>) => Promise<InlineReply>;
21
- export interface CompareContext {
22
- engines: readonly EngineId[];
23
- routes: ReadonlyMap<EngineId, Route>;
24
- run: Run;
25
- defaults?: WireOptions;
26
- }
27
- export declare const nameOf: (file: Blob) => string;
28
- export declare function createProbeCore(options: CoreOptions): Probe<EngineId>;
29
- export declare function createProbeClient<const E extends readonly EngineDescriptor[] = [], const W extends WorkerEngineId = never>(options: ClientOptions<E, W>): Probe<W | IdOf<E[number]> | 'own'>;