@hyperframes/engine 0.6.119 → 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,326 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
2
- import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { tmpdir } from "node:os";
5
-
6
- const { runFfmpegMock } = vi.hoisted(() => ({
7
- runFfmpegMock: vi.fn(async () => ({
8
- success: true,
9
- durationMs: 1,
10
- stderr: "",
11
- exitCode: 0,
12
- })),
13
- }));
14
-
15
- vi.mock("../utils/runFfmpeg.js", () => ({
16
- runFfmpeg: runFfmpegMock,
17
- }));
18
-
19
- import { processCompositionAudio } from "./audioMixer.js";
20
-
21
- describe("processCompositionAudio", () => {
22
- const tempDirs: string[] = [];
23
-
24
- afterEach(() => {
25
- runFfmpegMock.mockClear();
26
- for (const dir of tempDirs.splice(0)) {
27
- rmSync(dir, { recursive: true, force: true });
28
- }
29
- });
30
-
31
- it("preserves muted tracks and uses unity master gain by default", async () => {
32
- const baseDir = mkdtempSync(join(tmpdir(), "hf-audio-base-"));
33
- const workDir = mkdtempSync(join(tmpdir(), "hf-audio-work-"));
34
- tempDirs.push(baseDir, workDir);
35
-
36
- writeFileSync(join(baseDir, "voice.wav"), "stub");
37
-
38
- const result = await processCompositionAudio(
39
- [
40
- {
41
- id: "voice",
42
- src: "voice.wav",
43
- start: 0,
44
- end: 2,
45
- mediaStart: 0,
46
- layer: 0,
47
- volume: 0,
48
- type: "audio",
49
- },
50
- ],
51
- baseDir,
52
- workDir,
53
- join(baseDir, "out.m4a"),
54
- 2,
55
- );
56
-
57
- expect(result.success).toBe(true);
58
- expect(runFfmpegMock).toHaveBeenCalledTimes(2);
59
-
60
- const mixArgs = runFfmpegMock.mock.calls[1]?.[0];
61
- const filterIndex = mixArgs.indexOf("-filter_complex");
62
- const filter = mixArgs[filterIndex + 1];
63
-
64
- expect(filter).toContain("volume=0");
65
- expect(filter).toContain("[mixed]volume=1[out]");
66
- expect(filter).not.toContain("normalize=");
67
- expect(filter).not.toContain("weights=");
68
- });
69
-
70
- it("compensates amix normalization so multi-track master gain equals track count", async () => {
71
- const baseDir = mkdtempSync(join(tmpdir(), "hf-audio-base-"));
72
- const workDir = mkdtempSync(join(tmpdir(), "hf-audio-work-"));
73
- tempDirs.push(baseDir, workDir);
74
-
75
- writeFileSync(join(baseDir, "a.wav"), "stub");
76
- writeFileSync(join(baseDir, "b.wav"), "stub");
77
- writeFileSync(join(baseDir, "c.wav"), "stub");
78
-
79
- const result = await processCompositionAudio(
80
- [
81
- {
82
- id: "a",
83
- src: "a.wav",
84
- start: 0,
85
- end: 2,
86
- mediaStart: 0,
87
- layer: 0,
88
- volume: 0.8,
89
- type: "audio",
90
- },
91
- {
92
- id: "b",
93
- src: "b.wav",
94
- start: 0,
95
- end: 2,
96
- mediaStart: 0,
97
- layer: 1,
98
- volume: 1,
99
- type: "audio",
100
- },
101
- {
102
- id: "c",
103
- src: "c.wav",
104
- start: 0,
105
- end: 2,
106
- mediaStart: 0,
107
- layer: 2,
108
- volume: 0.5,
109
- type: "audio",
110
- },
111
- ],
112
- baseDir,
113
- workDir,
114
- join(baseDir, "out.m4a"),
115
- 2,
116
- );
117
-
118
- expect(result.success).toBe(true);
119
- // 3 prepare calls (one per track via Promise.all) precede the mix call,
120
- // so the mix is at index 3, not index 1.
121
- expect(runFfmpegMock).toHaveBeenCalledTimes(4);
122
- const mixArgs = runFfmpegMock.mock.calls[3]?.[0];
123
- const filter = mixArgs[mixArgs.indexOf("-filter_complex") + 1];
124
-
125
- expect(filter).toContain("amix=inputs=3");
126
- expect(filter).not.toContain("normalize=");
127
- // masterOutputGain(1) × tracks(3) = 3
128
- expect(filter).toContain("[mixed]volume=3[out]");
129
- });
130
-
131
- it("uses frame-evaluated volume automation when keyframes are present", async () => {
132
- const baseDir = mkdtempSync(join(tmpdir(), "hf-audio-base-"));
133
- const workDir = mkdtempSync(join(tmpdir(), "hf-audio-work-"));
134
- tempDirs.push(baseDir, workDir);
135
-
136
- writeFileSync(join(baseDir, "voice.wav"), "stub");
137
-
138
- const result = await processCompositionAudio(
139
- [
140
- {
141
- id: "voice",
142
- src: "voice.wav",
143
- start: 2,
144
- end: 5,
145
- mediaStart: 0,
146
- layer: 0,
147
- volume: 0,
148
- volumeKeyframes: [
149
- { time: 2, volume: 0 },
150
- { time: 3, volume: 1 },
151
- { time: 5, volume: 0.5 },
152
- ],
153
- type: "audio",
154
- },
155
- ],
156
- baseDir,
157
- workDir,
158
- join(baseDir, "out.m4a"),
159
- 5,
160
- );
161
-
162
- expect(result.success).toBe(true);
163
-
164
- const mixArgs = runFfmpegMock.mock.calls[1]?.[0];
165
- const filterIndex = mixArgs.indexOf("-filter_complex");
166
- const filter = mixArgs[filterIndex + 1];
167
-
168
- expect(filter).toContain("volume=");
169
- expect(filter).toContain(":eval=frame");
170
- expect(filter).toContain("lt(t\\,1)");
171
- expect(filter).toContain("adelay=2000|2000");
172
- });
173
-
174
- it("bounds expression nesting for dense keyframe automation without dropping the envelope", async () => {
175
- const baseDir = mkdtempSync(join(tmpdir(), "hf-audio-base-"));
176
- const workDir = mkdtempSync(join(tmpdir(), "hf-audio-work-"));
177
- tempDirs.push(baseDir, workDir);
178
-
179
- writeFileSync(join(baseDir, "bgm.wav"), "stub");
180
-
181
- // Mirrors the 60 Hz timeline probe: a 10s eased fade emits hundreds of
182
- // keyframes. The nested-if volume expression must not grow one level per
183
- // keyframe — past ~95 levels FFmpeg fails filter-graph init and the audio
184
- // track is dropped entirely (GH #1066 follow-up).
185
- const keyframes = Array.from({ length: 300 }, (_, i) => {
186
- const time = (i / 299) * 10;
187
- const volume =
188
- time < 3 ? 0.8 * (time / 3) ** 2 : time < 7 ? 0.8 : 0.8 * (1 - (time - 7) / 3) ** 2;
189
- return { time, volume };
190
- });
191
-
192
- const result = await processCompositionAudio(
193
- [
194
- {
195
- id: "bgm",
196
- src: "bgm.wav",
197
- start: 0,
198
- end: 10,
199
- mediaStart: 0,
200
- layer: 0,
201
- volume: 0,
202
- volumeKeyframes: keyframes,
203
- type: "audio",
204
- },
205
- ],
206
- baseDir,
207
- workDir,
208
- join(baseDir, "out.m4a"),
209
- 10,
210
- );
211
-
212
- expect(result.success).toBe(true);
213
-
214
- const mixArgs = runFfmpegMock.mock.calls[1]?.[0];
215
- const filterIndex = mixArgs.indexOf("-filter_complex");
216
- const filter = mixArgs[filterIndex + 1];
217
-
218
- // One nested `if(lt(...))` is emitted per segment; cap it well under the
219
- // FFmpeg evaluator's nesting limit (MAX_VOLUME_SEGMENTS = 32).
220
- const nestingDepth = (filter.match(/if\(lt\(t/g) ?? []).length;
221
- expect(nestingDepth).toBeGreaterThan(1);
222
- expect(nestingDepth).toBeLessThan(32);
223
-
224
- // The simplified envelope still spans the clip: silent start, audible peak.
225
- expect(filter).toContain(":eval=frame");
226
- expect(filter).toMatch(/volume=if\(lt\(t\\,[0-9.]+\)\\,0\+/);
227
- });
228
-
229
- it("falls back to a static-volume mix instead of dropping audio when the automated mix fails", async () => {
230
- const baseDir = mkdtempSync(join(tmpdir(), "hf-audio-base-"));
231
- const workDir = mkdtempSync(join(tmpdir(), "hf-audio-work-"));
232
- tempDirs.push(baseDir, workDir);
233
-
234
- writeFileSync(join(baseDir, "bgm.wav"), "stub");
235
-
236
- // Simulate an ffmpeg build that rejects the automation expression: the
237
- // first mix attempt fails, the static-volume retry succeeds. (prepare =
238
- // call 0, automated mix = call 1, fallback mix = call 2.)
239
- runFfmpegMock
240
- .mockImplementationOnce(async () => ({
241
- success: true,
242
- durationMs: 1,
243
- stderr: "",
244
- exitCode: 0,
245
- }))
246
- .mockImplementationOnce(async () => ({
247
- success: false,
248
- durationMs: 1,
249
- stderr: "Error initializing filters",
250
- exitCode: 234,
251
- }));
252
-
253
- const result = await processCompositionAudio(
254
- [
255
- {
256
- id: "bgm",
257
- src: "bgm.wav",
258
- start: 0,
259
- end: 5,
260
- mediaStart: 0,
261
- layer: 0,
262
- volume: 0.8,
263
- volumeKeyframes: [
264
- { time: 0, volume: 0.8 },
265
- { time: 5, volume: 0 },
266
- ],
267
- type: "audio",
268
- },
269
- ],
270
- baseDir,
271
- workDir,
272
- join(baseDir, "out.m4a"),
273
- 5,
274
- );
275
-
276
- expect(result.success).toBe(true);
277
- expect(result.tracksProcessed).toBe(1);
278
- expect(runFfmpegMock).toHaveBeenCalledTimes(3);
279
- // Degradation is surfaced, not silent — the track rendered at base volume.
280
- expect(result.error).toMatch(/base volume/i);
281
-
282
- // The fallback mix omits the automation expression (base volume only).
283
- const fallbackArgs = runFfmpegMock.mock.calls[2]?.[0];
284
- const fallbackFilter = fallbackArgs[fallbackArgs.indexOf("-filter_complex") + 1];
285
- expect(fallbackFilter).not.toContain(":eval=frame");
286
- expect(fallbackFilter).toContain("volume=0.8");
287
- });
288
-
289
- it("prepares percent-encoded non-Latin audio srcs from decoded filesystem paths", async () => {
290
- const baseDir = mkdtempSync(join(tmpdir(), "hf-audio-base-"));
291
- const workDir = mkdtempSync(join(tmpdir(), "hf-audio-work-"));
292
- tempDirs.push(baseDir, workDir);
293
-
294
- const encodedFilename =
295
- "%D9%87%D9%86%D8%A7%20%D9%85%D8%B1%D9%88%D8%A7%20-%20%D9%85%D8%A8%D8%A7%D8%B1%D9%83.mp4";
296
- const filename = decodeURIComponent(encodedFilename);
297
- mkdirSync(join(baseDir, "assets"), { recursive: true });
298
- writeFileSync(join(baseDir, "assets", filename), "stub");
299
-
300
- const result = await processCompositionAudio(
301
- [
302
- {
303
- id: "voice",
304
- src: `assets/${encodedFilename}`,
305
- start: 0,
306
- end: 2,
307
- mediaStart: 0,
308
- layer: 0,
309
- volume: 1,
310
- type: "audio",
311
- },
312
- ],
313
- baseDir,
314
- workDir,
315
- join(baseDir, "out.m4a"),
316
- 2,
317
- );
318
-
319
- expect(result.success).toBe(true);
320
- expect(result.error).toBeUndefined();
321
- expect(runFfmpegMock).toHaveBeenCalledTimes(2);
322
-
323
- const prepareArgs = runFfmpegMock.mock.calls[0]?.[0];
324
- expect(prepareArgs).toContain(join(baseDir, "assets", filename));
325
- });
326
- });