@hyperframes/engine 0.6.93 → 0.6.94
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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/services/browserManager.d.ts.map +1 -1
- package/dist/services/browserManager.js +6 -1
- package/dist/services/browserManager.js.map +1 -1
- package/dist/services/chunkEncoder.d.ts.map +1 -1
- package/dist/services/chunkEncoder.js +5 -3
- package/dist/services/chunkEncoder.js.map +1 -1
- package/dist/services/streamingEncoder.d.ts.map +1 -1
- package/dist/services/streamingEncoder.js +3 -1
- package/dist/services/streamingEncoder.js.map +1 -1
- package/dist/services/videoFrameExtractor.d.ts.map +1 -1
- package/dist/services/videoFrameExtractor.js +3 -1
- package/dist/services/videoFrameExtractor.js.map +1 -1
- package/dist/utils/ffmpegBinaries.d.ts +6 -0
- package/dist/utils/ffmpegBinaries.d.ts.map +1 -0
- package/dist/utils/ffmpegBinaries.js +55 -0
- package/dist/utils/ffmpegBinaries.js.map +1 -0
- package/dist/utils/ffprobe.d.ts.map +1 -1
- package/dist/utils/ffprobe.js +8 -2
- package/dist/utils/ffprobe.js.map +1 -1
- package/dist/utils/gpuEncoder.d.ts.map +1 -1
- package/dist/utils/gpuEncoder.js +4 -2
- package/dist/utils/gpuEncoder.js.map +1 -1
- package/dist/utils/runFfmpeg.d.ts.map +1 -1
- package/dist/utils/runFfmpeg.js +20 -1
- package/dist/utils/runFfmpeg.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +7 -0
- package/src/services/browserManager.test.ts +18 -0
- package/src/services/browserManager.ts +8 -1
- package/src/services/chunkEncoder.ts +5 -3
- package/src/services/streamingEncoder.ts +3 -1
- package/src/services/videoFrameExtractor.ts +3 -1
- package/src/utils/ffmpegBinaries.test.ts +43 -0
- package/src/utils/ffmpegBinaries.ts +63 -0
- package/src/utils/ffprobe.test.ts +27 -0
- package/src/utils/ffprobe.ts +12 -2
- package/src/utils/gpuEncoder.ts +4 -2
- package/src/utils/runFfmpeg.test.ts +57 -1
- package/src/utils/runFfmpeg.ts +24 -1
package/src/utils/ffprobe.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
// fallow-ignore-file code-duplication complexity
|
|
1
2
|
import { spawn } from "child_process";
|
|
2
3
|
import { readFileSync } from "fs";
|
|
3
4
|
import { extname } from "path";
|
|
5
|
+
import { FFPROBE_PATH_ENV, getFfprobeBinary } from "./ffmpegBinaries.js";
|
|
4
6
|
|
|
5
7
|
/** Spawn ffprobe with given args, return stdout. Throws on non-zero exit or missing binary. */
|
|
6
8
|
function runFfprobe(args: string[]): Promise<string> {
|
|
7
9
|
return new Promise((resolve, reject) => {
|
|
8
|
-
const
|
|
10
|
+
const command = getFfprobeBinary();
|
|
11
|
+
const proc = spawn(command, args);
|
|
9
12
|
let stdout = "";
|
|
10
13
|
let stderr = "";
|
|
11
14
|
proc.stdout.on("data", (data) => {
|
|
@@ -23,7 +26,14 @@ function runFfprobe(args: string[]): Promise<string> {
|
|
|
23
26
|
});
|
|
24
27
|
proc.on("error", (err) => {
|
|
25
28
|
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
26
|
-
|
|
29
|
+
const configured = process.env[FFPROBE_PATH_ENV]?.trim();
|
|
30
|
+
reject(
|
|
31
|
+
new Error(
|
|
32
|
+
configured
|
|
33
|
+
? `[FFmpeg] ffprobe not found at ${FFPROBE_PATH_ENV}="${configured}". Please install FFmpeg.`
|
|
34
|
+
: "[FFmpeg] ffprobe not found. Please install FFmpeg.",
|
|
35
|
+
),
|
|
36
|
+
);
|
|
27
37
|
} else {
|
|
28
38
|
reject(err);
|
|
29
39
|
}
|
package/src/utils/gpuEncoder.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// fallow-ignore-file complexity
|
|
1
2
|
/**
|
|
2
3
|
* GPU Encoder Detection
|
|
3
4
|
*
|
|
@@ -6,6 +7,7 @@
|
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import { spawn } from "child_process";
|
|
10
|
+
import { getFfmpegBinary } from "./ffmpegBinaries.js";
|
|
9
11
|
|
|
10
12
|
export type ConcreteGpuEncoder = "nvenc" | "videotoolbox" | "vaapi" | "qsv" | "amf";
|
|
11
13
|
export type GpuEncoder = ConcreteGpuEncoder | null;
|
|
@@ -59,7 +61,7 @@ export async function selectUsableGpuEncoder(
|
|
|
59
61
|
|
|
60
62
|
export async function detectGpuEncoder(): Promise<GpuEncoder> {
|
|
61
63
|
return new Promise((resolve) => {
|
|
62
|
-
const ffmpeg = spawn(
|
|
64
|
+
const ffmpeg = spawn(getFfmpegBinary(), ["-encoders"], {
|
|
63
65
|
stdio: ["pipe", "pipe", "pipe"],
|
|
64
66
|
});
|
|
65
67
|
let stdout = "";
|
|
@@ -147,7 +149,7 @@ async function canUseGpuEncoder(encoder: ConcreteGpuEncoder): Promise<boolean> {
|
|
|
147
149
|
if (killTimer) clearTimeout(killTimer);
|
|
148
150
|
resolve(usable);
|
|
149
151
|
};
|
|
150
|
-
const ffmpeg = spawn(
|
|
152
|
+
const ffmpeg = spawn(getFfmpegBinary(), getProbeArgs(encoder), {
|
|
151
153
|
stdio: ["ignore", "ignore", "pipe"],
|
|
152
154
|
});
|
|
153
155
|
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
4
|
|
|
3
5
|
import { formatFfmpegError } from "./runFfmpeg.js";
|
|
4
6
|
|
|
5
7
|
describe("formatFfmpegError", () => {
|
|
8
|
+
const originalPlatform = process.platform;
|
|
9
|
+
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
Object.defineProperty(process, "platform", { value: originalPlatform, configurable: true });
|
|
12
|
+
});
|
|
13
|
+
|
|
6
14
|
it("reports exit code alone when stderr is empty", () => {
|
|
7
15
|
expect(formatFfmpegError(-22, "")).toBe("FFmpeg exited with code -22");
|
|
8
16
|
});
|
|
@@ -43,4 +51,52 @@ describe("formatFfmpegError", () => {
|
|
|
43
51
|
it("wraps stderr in [FFmpeg] prefix when exit code is null (spawn failure)", () => {
|
|
44
52
|
expect(formatFfmpegError(null, "spawn ffmpeg ENOENT")).toBe("[FFmpeg] spawn ffmpeg ENOENT");
|
|
45
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
|
+
});
|
|
46
102
|
});
|
package/src/utils/runFfmpeg.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// fallow-ignore-file code-duplication
|
|
1
2
|
/**
|
|
2
3
|
* Shared FFmpeg process runner.
|
|
3
4
|
*
|
|
@@ -6,6 +7,7 @@
|
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import { spawn } from "child_process";
|
|
10
|
+
import { getFfmpegBinary } from "./ffmpegBinaries.js";
|
|
9
11
|
import { trackChildProcess } from "./processTracker.js";
|
|
10
12
|
|
|
11
13
|
export interface RunFfmpegOptions {
|
|
@@ -25,6 +27,23 @@ const DEFAULT_TIMEOUT = 300_000;
|
|
|
25
27
|
|
|
26
28
|
const DEFAULT_STDERR_TAIL_LINES = 15;
|
|
27
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
|
+
|
|
28
47
|
/**
|
|
29
48
|
* Build a user-facing error message for a failed ffmpeg invocation.
|
|
30
49
|
*
|
|
@@ -48,6 +67,10 @@ export function formatFfmpegError(
|
|
|
48
67
|
if (exitCode === null) {
|
|
49
68
|
return tail ? `[FFmpeg] ${tail}` : "[FFmpeg] process error";
|
|
50
69
|
}
|
|
70
|
+
const windowsMessage = formatWindowsFfmpegExit(exitCode);
|
|
71
|
+
if (windowsMessage) {
|
|
72
|
+
return tail ? `${windowsMessage}\nffmpeg stderr (tail):\n${tail}` : windowsMessage;
|
|
73
|
+
}
|
|
51
74
|
return tail
|
|
52
75
|
? `FFmpeg exited with code ${exitCode}\nffmpeg stderr (tail):\n${tail}`
|
|
53
76
|
: `FFmpeg exited with code ${exitCode}`;
|
|
@@ -60,7 +83,7 @@ export async function runFfmpeg(args: string[], opts?: RunFfmpegOptions): Promis
|
|
|
60
83
|
const onStderr = opts?.onStderr;
|
|
61
84
|
|
|
62
85
|
return new Promise<RunFfmpegResult>((resolve) => {
|
|
63
|
-
const ffmpeg = spawn(
|
|
86
|
+
const ffmpeg = spawn(getFfmpegBinary(), args);
|
|
64
87
|
trackChildProcess(ffmpeg);
|
|
65
88
|
let stderr = "";
|
|
66
89
|
|