@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.
- package/package.json +24 -7
- package/scripts/generate-lut-reference.py +0 -168
- package/scripts/test-fitTextFontSize-browser.ts +0 -135
- package/src/cdp-headless-experimental.d.ts +0 -54
- package/src/config.test.ts +0 -213
- package/src/config.ts +0 -417
- package/src/index.ts +0 -273
- package/src/services/audioMixer.test.ts +0 -326
- package/src/services/audioMixer.ts +0 -604
- package/src/services/audioMixer.types.ts +0 -35
- package/src/services/audioVolumeEnvelope.test.ts +0 -176
- package/src/services/audioVolumeEnvelope.ts +0 -138
- package/src/services/browserManager.test.ts +0 -330
- package/src/services/browserManager.ts +0 -670
- package/src/services/chunkEncoder.test.ts +0 -1415
- package/src/services/chunkEncoder.ts +0 -831
- package/src/services/chunkEncoder.types.ts +0 -60
- package/src/services/extractionCache.test.ts +0 -199
- package/src/services/extractionCache.ts +0 -216
- package/src/services/fileServer.ts +0 -110
- package/src/services/frameCapture-discardWarmup.test.ts +0 -183
- package/src/services/frameCapture-namePolyfill.test.ts +0 -78
- package/src/services/frameCapture-pollImagesReady.test.ts +0 -153
- package/src/services/frameCapture-staticDedupIndex.test.ts +0 -76
- package/src/services/frameCapture-warmupTicks.test.ts +0 -174
- package/src/services/frameCapture.test.ts +0 -192
- package/src/services/frameCapture.ts +0 -1934
- package/src/services/hdrCapture.test.ts +0 -159
- package/src/services/hdrCapture.ts +0 -315
- package/src/services/parallelCoordinator.test.ts +0 -139
- package/src/services/parallelCoordinator.ts +0 -437
- package/src/services/screenshotService.test.ts +0 -510
- package/src/services/screenshotService.ts +0 -615
- package/src/services/streamingEncoder.test.ts +0 -832
- package/src/services/streamingEncoder.ts +0 -594
- package/src/services/systemMemory.test.ts +0 -324
- package/src/services/systemMemory.ts +0 -180
- package/src/services/videoFrameExtractor.test.ts +0 -1062
- package/src/services/videoFrameExtractor.ts +0 -1139
- package/src/services/videoFrameInjector.test.ts +0 -300
- package/src/services/videoFrameInjector.ts +0 -687
- package/src/services/vp9Options.ts +0 -13
- package/src/types.ts +0 -191
- package/src/utils/alphaBlit.test.ts +0 -1349
- package/src/utils/alphaBlit.ts +0 -1015
- package/src/utils/assertSwiftShader.test.ts +0 -130
- package/src/utils/assertSwiftShader.ts +0 -126
- package/src/utils/ffmpegBinaries.test.ts +0 -43
- package/src/utils/ffmpegBinaries.ts +0 -63
- package/src/utils/ffprobe.test.ts +0 -342
- package/src/utils/ffprobe.ts +0 -457
- package/src/utils/gpuEncoder.test.ts +0 -140
- package/src/utils/gpuEncoder.ts +0 -268
- package/src/utils/hdr.test.ts +0 -191
- package/src/utils/hdr.ts +0 -137
- package/src/utils/hdrCompositing.test.ts +0 -130
- package/src/utils/htmlTemplate.test.ts +0 -42
- package/src/utils/htmlTemplate.ts +0 -42
- package/src/utils/layerCompositor.test.ts +0 -150
- package/src/utils/layerCompositor.ts +0 -58
- package/src/utils/parityContract.ts +0 -1
- package/src/utils/processTracker.test.ts +0 -74
- package/src/utils/processTracker.ts +0 -41
- package/src/utils/readWebGlVendorInfoFromCanvas.ts +0 -52
- package/src/utils/runFfmpeg.test.ts +0 -102
- package/src/utils/runFfmpeg.ts +0 -136
- package/src/utils/shaderTransitions.test.ts +0 -738
- package/src/utils/shaderTransitions.ts +0 -1130
- package/src/utils/uint16-alignment-audit.test.ts +0 -125
- package/src/utils/urlDownloader.test.ts +0 -65
- package/src/utils/urlDownloader.ts +0 -143
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -7
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
import { EventEmitter } from "node:events";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
|
-
|
|
5
|
-
import { formatFfmpegError } from "./runFfmpeg.js";
|
|
6
|
-
|
|
7
|
-
describe("formatFfmpegError", () => {
|
|
8
|
-
const originalPlatform = process.platform;
|
|
9
|
-
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
Object.defineProperty(process, "platform", { value: originalPlatform, configurable: true });
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("reports exit code alone when stderr is empty", () => {
|
|
15
|
-
expect(formatFfmpegError(-22, "")).toBe("FFmpeg exited with code -22");
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it("appends stderr tail when present", () => {
|
|
19
|
-
const stderr =
|
|
20
|
-
"ffmpeg version 8.1\nbuilt with gcc 13.2.0\n" +
|
|
21
|
-
"[h264_nvenc @ 0x7f] Error applying encoder options: Invalid argument\n" +
|
|
22
|
-
"Error while opening encoder\n";
|
|
23
|
-
const message = formatFfmpegError(-22, stderr);
|
|
24
|
-
expect(message).toContain("FFmpeg exited with code -22");
|
|
25
|
-
expect(message).toContain("ffmpeg stderr (tail):");
|
|
26
|
-
expect(message).toContain("Error applying encoder options: Invalid argument");
|
|
27
|
-
expect(message).toContain("Error while opening encoder");
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it("keeps only the last N non-empty lines in the tail", () => {
|
|
31
|
-
const lines = Array.from({ length: 30 }, (_, i) => `line-${i}`).join("\n");
|
|
32
|
-
const message = formatFfmpegError(1, lines, 5);
|
|
33
|
-
expect(message).toContain("line-29");
|
|
34
|
-
expect(message).toContain("line-25");
|
|
35
|
-
expect(message).not.toContain("line-24");
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("strips blank lines from the tail so real signal isn't hidden", () => {
|
|
39
|
-
const stderr = "\n\nError applying encoder options: Invalid argument\n\n\n";
|
|
40
|
-
const message = formatFfmpegError(-22, stderr);
|
|
41
|
-
expect(message).toContain("Error applying encoder options: Invalid argument");
|
|
42
|
-
// Only one non-empty stderr line should appear in the tail.
|
|
43
|
-
const tailPart = message.split("ffmpeg stderr (tail):\n")[1] ?? "";
|
|
44
|
-
expect(tailPart.trim().split(/\r?\n/).length).toBe(1);
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it("falls back to a process-error string when exit code is null and stderr is empty", () => {
|
|
48
|
-
expect(formatFfmpegError(null, "")).toBe("[FFmpeg] process error");
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("wraps stderr in [FFmpeg] prefix when exit code is null (spawn failure)", () => {
|
|
52
|
-
expect(formatFfmpegError(null, "spawn ffmpeg ENOENT")).toBe("[FFmpeg] spawn ffmpeg ENOENT");
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("maps Windows invalid-image exit codes to an actionable architecture hint", () => {
|
|
56
|
-
Object.defineProperty(process, "platform", { value: "win32", configurable: true });
|
|
57
|
-
|
|
58
|
-
expect(formatFfmpegError(3221225595, "")).toContain("wrong architecture");
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
function createSpawnSpy() {
|
|
63
|
-
const calls: Array<{ command: string; args: string[] }> = [];
|
|
64
|
-
const spawn = vi.fn((command: string, args: string[]) => {
|
|
65
|
-
calls.push({ command, args });
|
|
66
|
-
const proc = new EventEmitter() as EventEmitter & {
|
|
67
|
-
stderr: EventEmitter;
|
|
68
|
-
kill: ReturnType<typeof vi.fn>;
|
|
69
|
-
killed: boolean;
|
|
70
|
-
};
|
|
71
|
-
proc.stderr = new EventEmitter();
|
|
72
|
-
proc.kill = vi.fn();
|
|
73
|
-
proc.killed = false;
|
|
74
|
-
process.nextTick(() => proc.emit("close", 0));
|
|
75
|
-
return proc;
|
|
76
|
-
});
|
|
77
|
-
return { spawn, calls };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
describe("runFfmpeg binary resolution", () => {
|
|
81
|
-
const originalFfmpegPath = process.env.HYPERFRAMES_FFMPEG_PATH;
|
|
82
|
-
|
|
83
|
-
afterEach(() => {
|
|
84
|
-
vi.resetModules();
|
|
85
|
-
vi.doUnmock("child_process");
|
|
86
|
-
if (originalFfmpegPath === undefined) delete process.env.HYPERFRAMES_FFMPEG_PATH;
|
|
87
|
-
else process.env.HYPERFRAMES_FFMPEG_PATH = originalFfmpegPath;
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("spawns the configured absolute FFmpeg path when HYPERFRAMES_FFMPEG_PATH is set", async () => {
|
|
91
|
-
process.env.HYPERFRAMES_FFMPEG_PATH = "/tools/ffmpeg.exe";
|
|
92
|
-
const { spawn, calls } = createSpawnSpy();
|
|
93
|
-
vi.resetModules();
|
|
94
|
-
vi.doMock("child_process", () => ({ spawn }));
|
|
95
|
-
|
|
96
|
-
const { runFfmpeg } = await import("./runFfmpeg.js");
|
|
97
|
-
const result = await runFfmpeg(["-version"]);
|
|
98
|
-
|
|
99
|
-
expect(result.success).toBe(true);
|
|
100
|
-
expect(calls[0]).toEqual({ command: resolve("/tools/ffmpeg.exe"), args: ["-version"] });
|
|
101
|
-
});
|
|
102
|
-
});
|
package/src/utils/runFfmpeg.ts
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
// fallow-ignore-file code-duplication
|
|
2
|
-
/**
|
|
3
|
-
* Shared FFmpeg process runner.
|
|
4
|
-
*
|
|
5
|
-
* Extracts the repeated spawn-stderr-timeout-abort-close-error pattern
|
|
6
|
-
* that appears across audioMixer and chunkEncoder into a single helper.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { spawn } from "child_process";
|
|
10
|
-
import { getFfmpegBinary } from "./ffmpegBinaries.js";
|
|
11
|
-
import { trackChildProcess } from "./processTracker.js";
|
|
12
|
-
|
|
13
|
-
export interface RunFfmpegOptions {
|
|
14
|
-
signal?: AbortSignal;
|
|
15
|
-
timeout?: number;
|
|
16
|
-
onStderr?: (line: string) => void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface RunFfmpegResult {
|
|
20
|
-
success: boolean;
|
|
21
|
-
exitCode: number | null;
|
|
22
|
-
stderr: string;
|
|
23
|
-
durationMs: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const DEFAULT_TIMEOUT = 300_000;
|
|
27
|
-
|
|
28
|
-
const DEFAULT_STDERR_TAIL_LINES = 15;
|
|
29
|
-
|
|
30
|
-
function formatWindowsFfmpegExit(exitCode: number | null): string | undefined {
|
|
31
|
-
if (process.platform !== "win32" || exitCode === null) return undefined;
|
|
32
|
-
if (exitCode === 3221225595 || exitCode === -1073741701) {
|
|
33
|
-
return (
|
|
34
|
-
"[FFmpeg] Windows could not start ffmpeg.exe (STATUS_INVALID_IMAGE_FORMAT). " +
|
|
35
|
-
"The binary may be corrupted or the wrong architecture. Reinstall a 64-bit Windows FFmpeg build."
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
if (exitCode === 3221225794 || exitCode === -1073741502) {
|
|
39
|
-
return (
|
|
40
|
-
"[FFmpeg] Windows failed while initializing ffmpeg.exe. " +
|
|
41
|
-
"The binary may be corrupted, blocked, or missing runtime DLLs. Reinstall a 64-bit Windows FFmpeg build."
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
return undefined;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Build a user-facing error message for a failed ffmpeg invocation.
|
|
49
|
-
*
|
|
50
|
-
* Historically we reported only `FFmpeg exited with code N`, which is useless
|
|
51
|
-
* for diagnosing encoder-options failures — a rejected `-preset` surfaces as a
|
|
52
|
-
* bare `code -22` with no hint at which argument ffmpeg objected to. Including
|
|
53
|
-
* the tail of stderr turns those into a one-line signal (e.g.
|
|
54
|
-
* `Error applying encoder options: Invalid argument`) that tells the caller
|
|
55
|
-
* exactly which option to fix.
|
|
56
|
-
*/
|
|
57
|
-
export function formatFfmpegError(
|
|
58
|
-
exitCode: number | null,
|
|
59
|
-
stderr: string,
|
|
60
|
-
tailLines: number = DEFAULT_STDERR_TAIL_LINES,
|
|
61
|
-
): string {
|
|
62
|
-
const tail = (stderr ?? "")
|
|
63
|
-
.split(/\r?\n/)
|
|
64
|
-
.filter((line) => line.length > 0)
|
|
65
|
-
.slice(-tailLines)
|
|
66
|
-
.join("\n");
|
|
67
|
-
if (exitCode === null) {
|
|
68
|
-
return tail ? `[FFmpeg] ${tail}` : "[FFmpeg] process error";
|
|
69
|
-
}
|
|
70
|
-
const windowsMessage = formatWindowsFfmpegExit(exitCode);
|
|
71
|
-
if (windowsMessage) {
|
|
72
|
-
return tail ? `${windowsMessage}\nffmpeg stderr (tail):\n${tail}` : windowsMessage;
|
|
73
|
-
}
|
|
74
|
-
return tail
|
|
75
|
-
? `FFmpeg exited with code ${exitCode}\nffmpeg stderr (tail):\n${tail}`
|
|
76
|
-
: `FFmpeg exited with code ${exitCode}`;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export async function runFfmpeg(args: string[], opts?: RunFfmpegOptions): Promise<RunFfmpegResult> {
|
|
80
|
-
const startMs = Date.now();
|
|
81
|
-
const signal = opts?.signal;
|
|
82
|
-
const timeout = opts?.timeout ?? DEFAULT_TIMEOUT;
|
|
83
|
-
const onStderr = opts?.onStderr;
|
|
84
|
-
|
|
85
|
-
return new Promise<RunFfmpegResult>((resolve) => {
|
|
86
|
-
const ffmpeg = spawn(getFfmpegBinary(), args);
|
|
87
|
-
trackChildProcess(ffmpeg);
|
|
88
|
-
let stderr = "";
|
|
89
|
-
|
|
90
|
-
const onAbort = () => {
|
|
91
|
-
ffmpeg.kill("SIGTERM");
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
if (signal) {
|
|
95
|
-
if (signal.aborted) {
|
|
96
|
-
ffmpeg.kill("SIGTERM");
|
|
97
|
-
} else {
|
|
98
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const timer = setTimeout(() => {
|
|
103
|
-
ffmpeg.kill("SIGTERM");
|
|
104
|
-
}, timeout);
|
|
105
|
-
|
|
106
|
-
ffmpeg.stderr.on("data", (data: Buffer) => {
|
|
107
|
-
const chunk = data.toString();
|
|
108
|
-
stderr += chunk;
|
|
109
|
-
if (onStderr) {
|
|
110
|
-
onStderr(chunk);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
ffmpeg.on("close", (code) => {
|
|
115
|
-
clearTimeout(timer);
|
|
116
|
-
if (signal) signal.removeEventListener("abort", onAbort);
|
|
117
|
-
resolve({
|
|
118
|
-
success: !signal?.aborted && code === 0,
|
|
119
|
-
exitCode: code,
|
|
120
|
-
stderr,
|
|
121
|
-
durationMs: Date.now() - startMs,
|
|
122
|
-
});
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
ffmpeg.on("error", (err) => {
|
|
126
|
-
clearTimeout(timer);
|
|
127
|
-
if (signal) signal.removeEventListener("abort", onAbort);
|
|
128
|
-
resolve({
|
|
129
|
-
success: false,
|
|
130
|
-
exitCode: null,
|
|
131
|
-
stderr: err.message,
|
|
132
|
-
durationMs: Date.now() - startMs,
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
}
|