@hyperframes/engine 0.6.118 → 0.6.120

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 (73) hide show
  1. package/package.json +24 -7
  2. package/scripts/generate-lut-reference.py +0 -168
  3. package/scripts/test-fitTextFontSize-browser.ts +0 -135
  4. package/src/cdp-headless-experimental.d.ts +0 -54
  5. package/src/config.test.ts +0 -213
  6. package/src/config.ts +0 -417
  7. package/src/index.ts +0 -273
  8. package/src/services/audioMixer.test.ts +0 -326
  9. package/src/services/audioMixer.ts +0 -604
  10. package/src/services/audioMixer.types.ts +0 -35
  11. package/src/services/audioVolumeEnvelope.test.ts +0 -176
  12. package/src/services/audioVolumeEnvelope.ts +0 -138
  13. package/src/services/browserManager.test.ts +0 -330
  14. package/src/services/browserManager.ts +0 -670
  15. package/src/services/chunkEncoder.test.ts +0 -1415
  16. package/src/services/chunkEncoder.ts +0 -831
  17. package/src/services/chunkEncoder.types.ts +0 -60
  18. package/src/services/extractionCache.test.ts +0 -199
  19. package/src/services/extractionCache.ts +0 -216
  20. package/src/services/fileServer.ts +0 -110
  21. package/src/services/frameCapture-discardWarmup.test.ts +0 -183
  22. package/src/services/frameCapture-namePolyfill.test.ts +0 -78
  23. package/src/services/frameCapture-pollImagesReady.test.ts +0 -153
  24. package/src/services/frameCapture-staticDedupIndex.test.ts +0 -76
  25. package/src/services/frameCapture-warmupTicks.test.ts +0 -174
  26. package/src/services/frameCapture.test.ts +0 -192
  27. package/src/services/frameCapture.ts +0 -1934
  28. package/src/services/hdrCapture.test.ts +0 -159
  29. package/src/services/hdrCapture.ts +0 -315
  30. package/src/services/parallelCoordinator.test.ts +0 -139
  31. package/src/services/parallelCoordinator.ts +0 -437
  32. package/src/services/screenshotService.test.ts +0 -510
  33. package/src/services/screenshotService.ts +0 -615
  34. package/src/services/streamingEncoder.test.ts +0 -832
  35. package/src/services/streamingEncoder.ts +0 -594
  36. package/src/services/systemMemory.test.ts +0 -324
  37. package/src/services/systemMemory.ts +0 -180
  38. package/src/services/videoFrameExtractor.test.ts +0 -1062
  39. package/src/services/videoFrameExtractor.ts +0 -1139
  40. package/src/services/videoFrameInjector.test.ts +0 -300
  41. package/src/services/videoFrameInjector.ts +0 -687
  42. package/src/services/vp9Options.ts +0 -13
  43. package/src/types.ts +0 -191
  44. package/src/utils/alphaBlit.test.ts +0 -1349
  45. package/src/utils/alphaBlit.ts +0 -1015
  46. package/src/utils/assertSwiftShader.test.ts +0 -130
  47. package/src/utils/assertSwiftShader.ts +0 -126
  48. package/src/utils/ffmpegBinaries.test.ts +0 -43
  49. package/src/utils/ffmpegBinaries.ts +0 -63
  50. package/src/utils/ffprobe.test.ts +0 -342
  51. package/src/utils/ffprobe.ts +0 -457
  52. package/src/utils/gpuEncoder.test.ts +0 -140
  53. package/src/utils/gpuEncoder.ts +0 -268
  54. package/src/utils/hdr.test.ts +0 -191
  55. package/src/utils/hdr.ts +0 -137
  56. package/src/utils/hdrCompositing.test.ts +0 -130
  57. package/src/utils/htmlTemplate.test.ts +0 -42
  58. package/src/utils/htmlTemplate.ts +0 -42
  59. package/src/utils/layerCompositor.test.ts +0 -150
  60. package/src/utils/layerCompositor.ts +0 -58
  61. package/src/utils/parityContract.ts +0 -1
  62. package/src/utils/processTracker.test.ts +0 -74
  63. package/src/utils/processTracker.ts +0 -41
  64. package/src/utils/readWebGlVendorInfoFromCanvas.ts +0 -52
  65. package/src/utils/runFfmpeg.test.ts +0 -102
  66. package/src/utils/runFfmpeg.ts +0 -136
  67. package/src/utils/shaderTransitions.test.ts +0 -738
  68. package/src/utils/shaderTransitions.ts +0 -1130
  69. package/src/utils/uint16-alignment-audit.test.ts +0 -125
  70. package/src/utils/urlDownloader.test.ts +0 -65
  71. package/src/utils/urlDownloader.ts +0 -143
  72. package/tsconfig.json +0 -19
  73. package/vitest.config.ts +0 -7
@@ -1,342 +0,0 @@
1
- // fallow-ignore-file code-duplication
2
- import { EventEmitter } from "events";
3
- import { readFileSync } from "fs";
4
- import { resolve } from "path";
5
- import { afterEach, describe, expect, it, vi } from "vitest";
6
- import { extractMediaMetadata, extractPngMetadataFromBuffer } from "./ffprobe.js";
7
-
8
- function crc32(buf: Buffer): number {
9
- let crc = 0xffffffff;
10
- for (let i = 0; i < buf.length; i++) {
11
- crc ^= buf[i] ?? 0;
12
- for (let bit = 0; bit < 8; bit++) {
13
- const mask = -(crc & 1);
14
- crc = (crc >>> 1) ^ (0xedb88320 & mask);
15
- }
16
- }
17
- return (crc ^ 0xffffffff) >>> 0;
18
- }
19
-
20
- function pngChunk(type: string, data: number[]): Buffer {
21
- const chunkData = Buffer.from(data);
22
- const header = Buffer.alloc(8);
23
- header.writeUInt32BE(chunkData.length, 0);
24
- header.write(type, 4, 4, "ascii");
25
- const crc = Buffer.alloc(4);
26
- crc.writeUInt32BE(crc32(Buffer.concat([Buffer.from(type, "ascii"), chunkData])), 0);
27
- return Buffer.concat([header, chunkData, crc]);
28
- }
29
-
30
- function buildPngWithChunks(chunks: Buffer[]): Buffer {
31
- return Buffer.concat([Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]), ...chunks]);
32
- }
33
-
34
- function buildMinimalPng(options?: {
35
- cIcpAfterIdat?: boolean;
36
- invalidCrc?: boolean;
37
- longCicp?: boolean;
38
- }) {
39
- const ihdr = pngChunk("IHDR", [0, 0, 0, 1, 0, 0, 0, 1, 16, 2, 0, 0, 0]);
40
- const cicpData = options?.longCicp ? [9, 16, 0, 1, 255] : [9, 16, 0, 1];
41
- let cicp = pngChunk("cICP", cicpData);
42
- if (options?.invalidCrc) {
43
- cicp = Buffer.from(cicp);
44
- cicp[cicp.length - 1] ^= 0xff;
45
- }
46
- const idat = pngChunk(
47
- "IDAT",
48
- [0x78, 0x9c, 0x63, 0x60, 0x60, 0x60, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01],
49
- );
50
- const iend = pngChunk("IEND", []);
51
- return options?.cIcpAfterIdat
52
- ? buildPngWithChunks([ihdr, idat, cicp, iend])
53
- : buildPngWithChunks([ihdr, cicp, idat, iend]);
54
- }
55
-
56
- describe("extractMediaMetadata", () => {
57
- it("reads HDR PNG cICP metadata when ffprobe color fields are absent", async () => {
58
- const fixturePath = resolve(
59
- __dirname,
60
- "../../../producer/tests/hdr-regression/src/hdr-photo-pq.png",
61
- );
62
-
63
- const metadata = await extractMediaMetadata(fixturePath);
64
-
65
- expect(metadata.colorSpace).toEqual({
66
- colorPrimaries: "bt2020",
67
- colorTransfer: "smpte2084",
68
- colorSpace: "gbr",
69
- });
70
- });
71
- });
72
-
73
- describe("extractPngMetadataFromBuffer", () => {
74
- it("accepts a valid cICP chunk before IDAT", () => {
75
- const metadata = extractPngMetadataFromBuffer(buildMinimalPng());
76
- expect(metadata?.colorSpace).toEqual({
77
- colorPrimaries: "bt2020",
78
- colorTransfer: "smpte2084",
79
- colorSpace: "gbr",
80
- });
81
- });
82
-
83
- it("rejects cICP chunks after IDAT", () => {
84
- const metadata = extractPngMetadataFromBuffer(buildMinimalPng({ cIcpAfterIdat: true }));
85
- expect(metadata).toEqual({
86
- width: 1,
87
- height: 1,
88
- colorSpace: null,
89
- });
90
- });
91
-
92
- it("rejects cICP chunks with invalid CRC", () => {
93
- expect(extractPngMetadataFromBuffer(buildMinimalPng({ invalidCrc: true }))).toBeNull();
94
- });
95
-
96
- it("rejects cICP chunks whose payload is not exactly four bytes", () => {
97
- const metadata = extractPngMetadataFromBuffer(buildMinimalPng({ longCicp: true }));
98
- expect(metadata).toEqual({
99
- width: 1,
100
- height: 1,
101
- colorSpace: null,
102
- });
103
- });
104
-
105
- it("continues to parse the checked-in HDR PNG fixture", () => {
106
- const fixture = readFileSync(
107
- resolve(__dirname, "../../../producer/tests/hdr-regression/src/hdr-photo-pq.png"),
108
- );
109
- expect(extractPngMetadataFromBuffer(fixture)?.colorSpace?.colorTransfer).toBe("smpte2084");
110
- });
111
- });
112
-
113
- interface SpawnCall {
114
- command: string;
115
- args: readonly string[];
116
- }
117
-
118
- interface FakeProc extends EventEmitter {
119
- stdout: EventEmitter;
120
- stderr: EventEmitter;
121
- }
122
-
123
- type SpawnOutcome =
124
- | { kind: "missing" }
125
- | { kind: "error"; message: string; code?: string }
126
- | { kind: "exit"; code: number; stdout?: string; stderr?: string };
127
-
128
- function createSpawnSpy(outcomes: SpawnOutcome[]): {
129
- spawn: (command: string, args: readonly string[]) => FakeProc;
130
- calls: SpawnCall[];
131
- } {
132
- const calls: SpawnCall[] = [];
133
- let invocation = 0;
134
- const spawn = (command: string, args: readonly string[]): FakeProc => {
135
- calls.push({ command, args });
136
- const outcome = outcomes[invocation] ?? outcomes[outcomes.length - 1];
137
- invocation += 1;
138
-
139
- const proc = new EventEmitter() as FakeProc;
140
- proc.stdout = new EventEmitter();
141
- proc.stderr = new EventEmitter();
142
-
143
- process.nextTick(() => {
144
- if (!outcome) return;
145
- if (outcome.kind === "missing") {
146
- const err = new Error("spawn ffprobe ENOENT") as NodeJS.ErrnoException;
147
- err.code = "ENOENT";
148
- proc.emit("error", err);
149
- return;
150
- }
151
- if (outcome.kind === "error") {
152
- const err = new Error(outcome.message) as NodeJS.ErrnoException;
153
- if (outcome.code) err.code = outcome.code;
154
- proc.emit("error", err);
155
- return;
156
- }
157
- if (outcome.stdout) proc.stdout.emit("data", Buffer.from(outcome.stdout));
158
- if (outcome.stderr) proc.stderr.emit("data", Buffer.from(outcome.stderr));
159
- proc.emit("close", outcome.code);
160
- });
161
-
162
- return proc;
163
- };
164
- return { spawn, calls };
165
- }
166
-
167
- describe("ffprobe missing-binary fallback", () => {
168
- const originalFfprobePath = process.env.HYPERFRAMES_FFPROBE_PATH;
169
-
170
- afterEach(() => {
171
- vi.resetModules();
172
- vi.doUnmock("child_process");
173
- if (originalFfprobePath === undefined) delete process.env.HYPERFRAMES_FFPROBE_PATH;
174
- else process.env.HYPERFRAMES_FFPROBE_PATH = originalFfprobePath;
175
- });
176
-
177
- it("spawns the configured absolute FFprobe path when HYPERFRAMES_FFPROBE_PATH is set", async () => {
178
- process.env.HYPERFRAMES_FFPROBE_PATH = "/tools/ffprobe.exe";
179
- const { spawn, calls } = createSpawnSpy([
180
- {
181
- kind: "exit",
182
- code: 0,
183
- stdout: JSON.stringify({
184
- streams: [{ codec_type: "audio", codec_name: "aac", sample_rate: "48000", channels: 2 }],
185
- format: { duration: "1.25", bit_rate: "128000" },
186
- }),
187
- },
188
- ]);
189
- vi.resetModules();
190
- vi.doMock("child_process", () => ({ spawn }));
191
-
192
- const { extractAudioMetadata } = await import("./ffprobe.js");
193
- const meta = await extractAudioMetadata("/tmp/uses-configured-ffprobe.wav");
194
-
195
- expect(meta.durationSeconds).toBe(1.25);
196
- expect(calls[0]?.command).toBe(resolve("/tools/ffprobe.exe"));
197
- });
198
-
199
- it("extractMediaMetadata falls back to PNG cICP metadata when ffprobe is missing", async () => {
200
- const { spawn, calls } = createSpawnSpy([{ kind: "missing" }]);
201
- vi.resetModules();
202
- vi.doMock("child_process", () => ({ spawn }));
203
-
204
- const { extractMediaMetadata: extractMediaMetadataMocked } = await import("./ffprobe.js");
205
- const fixture = resolve(
206
- __dirname,
207
- "../../../producer/tests/hdr-regression/src/hdr-photo-pq.png",
208
- );
209
- const meta = await extractMediaMetadataMocked(fixture);
210
-
211
- expect(calls.length).toBe(1);
212
- expect(calls[0]?.command).toBe("ffprobe");
213
- expect(meta.videoCodec).toBe("png");
214
- expect(meta.durationSeconds).toBe(0);
215
- expect(meta.fps).toBe(0);
216
- expect(meta.hasAudio).toBe(false);
217
- expect(meta.isVFR).toBe(false);
218
- expect(meta.hasAlpha).toBe(false);
219
- expect(meta.colorSpace?.colorTransfer).toBe("smpte2084");
220
- expect(meta.colorSpace?.colorPrimaries).toBe("bt2020");
221
- });
222
-
223
- it("extractMediaMetadata detects VP9 alpha_mode streams", async () => {
224
- const { spawn } = createSpawnSpy([
225
- {
226
- kind: "exit",
227
- code: 0,
228
- stdout: JSON.stringify({
229
- streams: [
230
- {
231
- codec_type: "video",
232
- codec_name: "vp9",
233
- width: 320,
234
- height: 180,
235
- r_frame_rate: "30/1",
236
- avg_frame_rate: "30/1",
237
- pix_fmt: "yuv420p",
238
- tags: { alpha_mode: "1" },
239
- },
240
- ],
241
- format: { duration: "1.5" },
242
- }),
243
- },
244
- ]);
245
- vi.resetModules();
246
- vi.doMock("child_process", () => ({ spawn }));
247
-
248
- const { extractMediaMetadata: extractMediaMetadataMocked } = await import("./ffprobe.js");
249
- const meta = await extractMediaMetadataMocked("/tmp/alpha.webm");
250
-
251
- expect(meta.videoCodec).toBe("vp9");
252
- expect(meta.hasAlpha).toBe(true);
253
- });
254
-
255
- // Regression: newer libavformat builds (and the output of `hyperframes
256
- // remove-background` itself) write the VP9-alpha sidecar tag as
257
- // `ALPHA_MODE` (uppercase). The lowercase-only check classified those
258
- // files as having no alpha, the producer extracted them as JPGs, and
259
- // the injected <img> overlays were fully opaque rectangles that hid
260
- // every static element below them on the z-stack. The bug was silent —
261
- // studio preview rendered correctly via native <video> playback while
262
- // production renders covered headlines and captions with the avatar.
263
- it("extractMediaMetadata detects ALPHA_MODE (uppercase) streams from newer ffmpeg builds", async () => {
264
- const { spawn } = createSpawnSpy([
265
- {
266
- kind: "exit",
267
- code: 0,
268
- stdout: JSON.stringify({
269
- streams: [
270
- {
271
- codec_type: "video",
272
- codec_name: "vp9",
273
- width: 320,
274
- height: 180,
275
- r_frame_rate: "30/1",
276
- avg_frame_rate: "30/1",
277
- pix_fmt: "yuv420p",
278
- tags: { ALPHA_MODE: "1" },
279
- },
280
- ],
281
- format: { duration: "1.5" },
282
- }),
283
- },
284
- ]);
285
- vi.resetModules();
286
- vi.doMock("child_process", () => ({ spawn }));
287
-
288
- const { extractMediaMetadata: extractMediaMetadataMocked } = await import("./ffprobe.js");
289
- const meta = await extractMediaMetadataMocked("/tmp/alpha-uppercase.webm");
290
-
291
- expect(meta.videoCodec).toBe("vp9");
292
- expect(meta.hasAlpha).toBe(true);
293
- });
294
-
295
- it("extractMediaMetadata rethrows ffprobe-missing error for non-image files without fallback", async () => {
296
- const { spawn } = createSpawnSpy([{ kind: "missing" }]);
297
- vi.resetModules();
298
- vi.doMock("child_process", () => ({ spawn }));
299
-
300
- const { extractMediaMetadata: extractMediaMetadataMocked } = await import("./ffprobe.js");
301
-
302
- await expect(extractMediaMetadataMocked("/tmp/no-such-video.mp4")).rejects.toThrow(/ffprobe/);
303
- });
304
-
305
- it("extractAudioMetadata surfaces a ffprobe-missing error verbatim", async () => {
306
- const { spawn, calls } = createSpawnSpy([{ kind: "missing" }]);
307
- vi.resetModules();
308
- vi.doMock("child_process", () => ({ spawn }));
309
-
310
- const { extractAudioMetadata } = await import("./ffprobe.js");
311
-
312
- await expect(extractAudioMetadata("/tmp/no-such-audio.wav")).rejects.toThrow(
313
- /ffprobe not found/,
314
- );
315
- expect(calls.length).toBe(1);
316
- expect(calls[0]?.command).toBe("ffprobe");
317
- });
318
-
319
- it("analyzeKeyframeIntervals surfaces a ffprobe-missing error verbatim", async () => {
320
- const { spawn, calls } = createSpawnSpy([{ kind: "missing" }]);
321
- vi.resetModules();
322
- vi.doMock("child_process", () => ({ spawn }));
323
-
324
- const { analyzeKeyframeIntervals } = await import("./ffprobe.js");
325
-
326
- await expect(analyzeKeyframeIntervals("/tmp/no-such-video.mp4")).rejects.toThrow(
327
- /ffprobe not found/,
328
- );
329
- expect(calls.length).toBe(1);
330
- expect(calls[0]?.command).toBe("ffprobe");
331
- });
332
-
333
- it("ffprobe-missing error message includes install hint", async () => {
334
- const { spawn } = createSpawnSpy([{ kind: "missing" }]);
335
- vi.resetModules();
336
- vi.doMock("child_process", () => ({ spawn }));
337
-
338
- const { extractAudioMetadata } = await import("./ffprobe.js");
339
-
340
- await expect(extractAudioMetadata("/tmp/example.mp3")).rejects.toThrow(/install FFmpeg/i);
341
- });
342
- });