@hyperframes/engine 0.6.119 → 0.6.121
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,130 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for assertSwiftShader and its companion readWebGlVendorInfo helper.
|
|
3
|
-
*
|
|
4
|
-
* We don't spin up a real Chrome here — the assertion's contract is "given a
|
|
5
|
-
* WebGL info pair, accept SwiftShader and reject anything else." Tests inject
|
|
6
|
-
* the info pair through the optional `readInfo` override that the
|
|
7
|
-
* production code path leaves as a default.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { describe, expect, it } from "vitest";
|
|
11
|
-
import type { Page } from "puppeteer-core";
|
|
12
|
-
import {
|
|
13
|
-
BROWSER_GPU_NOT_SOFTWARE,
|
|
14
|
-
SwiftShaderAssertionError,
|
|
15
|
-
assertSwiftShader,
|
|
16
|
-
} from "./assertSwiftShader.js";
|
|
17
|
-
|
|
18
|
-
// Minimal Page stub. Only assertSwiftShader's default `readInfo` ever touches
|
|
19
|
-
// `page.goto` / `page.evaluate`; when we inject a custom `readInfo` the page
|
|
20
|
-
// object is never used, so an empty cast is safe.
|
|
21
|
-
const stubPage = {} as unknown as Page;
|
|
22
|
-
|
|
23
|
-
describe("assertSwiftShader", () => {
|
|
24
|
-
it("accepts the canonical SwiftShader vendor + renderer pair", async () => {
|
|
25
|
-
await assertSwiftShader(stubPage, async () => ({
|
|
26
|
-
vendor: "Google Inc. (Google)",
|
|
27
|
-
renderer:
|
|
28
|
-
"ANGLE (Google, Vulkan 1.3.0 (SwiftShader Device (Subzero) (0x0000C0DE)), SwiftShader driver)",
|
|
29
|
-
}));
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it("accepts SwiftShader regardless of trailing whitespace on vendor", async () => {
|
|
33
|
-
await assertSwiftShader(stubPage, async () => ({
|
|
34
|
-
vendor: " Google Inc. (Google) ",
|
|
35
|
-
renderer: "SwiftShader",
|
|
36
|
-
}));
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("accepts case-insensitive renderer token", async () => {
|
|
40
|
-
await assertSwiftShader(stubPage, async () => ({
|
|
41
|
-
vendor: "Google Inc. (Google)",
|
|
42
|
-
renderer: "ANGLE (Google, swiftshader Device, swiftshader driver)",
|
|
43
|
-
}));
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("throws SwiftShaderAssertionError when vendor is hardware-accelerated", async () => {
|
|
47
|
-
let caught: unknown;
|
|
48
|
-
try {
|
|
49
|
-
await assertSwiftShader(stubPage, async () => ({
|
|
50
|
-
vendor: "Google Inc. (NVIDIA Corporation)",
|
|
51
|
-
renderer: "ANGLE (NVIDIA, NVIDIA GeForce RTX 4090, OpenGL 4.6)",
|
|
52
|
-
}));
|
|
53
|
-
} catch (err) {
|
|
54
|
-
caught = err;
|
|
55
|
-
}
|
|
56
|
-
expect(caught).toBeInstanceOf(SwiftShaderAssertionError);
|
|
57
|
-
expect((caught as SwiftShaderAssertionError).code).toBe(BROWSER_GPU_NOT_SOFTWARE);
|
|
58
|
-
expect((caught as Error).message).toContain("non-SwiftShader");
|
|
59
|
-
expect((caught as Error).message).toContain("--use-gl=swiftshader");
|
|
60
|
-
expect((caught as SwiftShaderAssertionError).vendor).toBe("Google Inc. (NVIDIA Corporation)");
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("throws when the renderer string lacks SwiftShader even if vendor matches", async () => {
|
|
64
|
-
// Google Inc. is the umbrella vendor for many ANGLE backends — vendor
|
|
65
|
-
// alone is not enough; the renderer must actually mention SwiftShader.
|
|
66
|
-
let caught: unknown;
|
|
67
|
-
try {
|
|
68
|
-
await assertSwiftShader(stubPage, async () => ({
|
|
69
|
-
vendor: "Google Inc. (Google)",
|
|
70
|
-
renderer: "ANGLE (Google, Vulkan 1.3.0 (Intel(R) UHD Graphics 630), OpenGL ES 3.0)",
|
|
71
|
-
}));
|
|
72
|
-
} catch (err) {
|
|
73
|
-
caught = err;
|
|
74
|
-
}
|
|
75
|
-
expect(caught).toBeInstanceOf(SwiftShaderAssertionError);
|
|
76
|
-
expect((caught as SwiftShaderAssertionError).code).toBe(BROWSER_GPU_NOT_SOFTWARE);
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("throws when both vendor and renderer are empty", async () => {
|
|
80
|
-
// Some chrome:// pages return empty strings before the GPU info table
|
|
81
|
-
// populates. We treat that as failure rather than silently passing.
|
|
82
|
-
let caught: unknown;
|
|
83
|
-
try {
|
|
84
|
-
await assertSwiftShader(stubPage, async () => ({ vendor: "", renderer: "" }));
|
|
85
|
-
} catch (err) {
|
|
86
|
-
caught = err;
|
|
87
|
-
}
|
|
88
|
-
expect(caught).toBeInstanceOf(SwiftShaderAssertionError);
|
|
89
|
-
expect((caught as SwiftShaderAssertionError).code).toBe(BROWSER_GPU_NOT_SOFTWARE);
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it("propagates errors from the info reader without wrapping", async () => {
|
|
93
|
-
const upstream = new Error("simulated CDP failure");
|
|
94
|
-
let caught: unknown;
|
|
95
|
-
try {
|
|
96
|
-
await assertSwiftShader(stubPage, async () => {
|
|
97
|
-
throw upstream;
|
|
98
|
-
});
|
|
99
|
-
} catch (err) {
|
|
100
|
-
caught = err;
|
|
101
|
-
}
|
|
102
|
-
// Reader errors should not be masked by SwiftShaderAssertionError —
|
|
103
|
-
// they are a separate failure class (probably retryable).
|
|
104
|
-
expect(caught).toBe(upstream);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("rejects an unrelated vendor that happens to contain the SwiftShader token in the renderer", async () => {
|
|
108
|
-
// Defensive: if some future ANGLE build uses a non-Google vendor string
|
|
109
|
-
// but still mentions SwiftShader in the renderer for some reason, we
|
|
110
|
-
// still want to require the exact Google vendor signature.
|
|
111
|
-
let caught: unknown;
|
|
112
|
-
try {
|
|
113
|
-
await assertSwiftShader(stubPage, async () => ({
|
|
114
|
-
vendor: "Mesa/X.org",
|
|
115
|
-
renderer: "llvmpipe (SwiftShader compatible)",
|
|
116
|
-
}));
|
|
117
|
-
} catch (err) {
|
|
118
|
-
caught = err;
|
|
119
|
-
}
|
|
120
|
-
expect(caught).toBeInstanceOf(SwiftShaderAssertionError);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
describe("SwiftShaderAssertionError", () => {
|
|
125
|
-
it("exposes the BROWSER_GPU_NOT_SOFTWARE typed-failure code", () => {
|
|
126
|
-
const err = new SwiftShaderAssertionError("test", "v", "r");
|
|
127
|
-
expect(err.code).toBe(BROWSER_GPU_NOT_SOFTWARE);
|
|
128
|
-
expect(err.code).toBe("BROWSER_GPU_NOT_SOFTWARE");
|
|
129
|
-
});
|
|
130
|
-
});
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* assertSwiftShader — verify Chrome's WebGL is rendered by SwiftShader.
|
|
3
|
-
*
|
|
4
|
-
* Distributed renders pixel-lock on the GPU backend: hardware GL is bitwise
|
|
5
|
-
* unstable across worker machines (different drivers, driver versions, GL
|
|
6
|
-
* extension sets, even differing fp32 rounding on the same vendor). Chunk
|
|
7
|
-
* workers launch Chrome with `--use-gl=swiftshader --use-angle=swiftshader`
|
|
8
|
-
* so every worker uses the same pure-software GL implementation.
|
|
9
|
-
*
|
|
10
|
-
* Those Chrome flags are advisory: a misconfigured base image, a missing
|
|
11
|
-
* SwiftShader library, or a `chrome://gpu` blocklist override can silently
|
|
12
|
-
* downgrade to system GL. The distributed pipeline cannot detect the
|
|
13
|
-
* downgrade by sampling pixels (one machine = one render), so we read
|
|
14
|
-
* `chrome://gpu` directly after launch and refuse to render if the active
|
|
15
|
-
* GL renderer is anything other than SwiftShader.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import type { Page } from "puppeteer-core";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Error code classifying this failure as non-retryable for distributed
|
|
22
|
-
* workflow adapters — a downgraded GPU on a worker will not heal on retry.
|
|
23
|
-
*/
|
|
24
|
-
export const BROWSER_GPU_NOT_SOFTWARE = "BROWSER_GPU_NOT_SOFTWARE";
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Error thrown when chrome://gpu reports a non-SwiftShader WebGL backend.
|
|
28
|
-
*
|
|
29
|
-
* Carries a `code` property so the adapter can match on it without parsing
|
|
30
|
-
* the message string — Temporal/Step Functions retry policies key off the
|
|
31
|
-
* code, not the message.
|
|
32
|
-
*/
|
|
33
|
-
export class SwiftShaderAssertionError extends Error {
|
|
34
|
-
readonly code: typeof BROWSER_GPU_NOT_SOFTWARE = BROWSER_GPU_NOT_SOFTWARE;
|
|
35
|
-
readonly vendor: string;
|
|
36
|
-
readonly renderer: string;
|
|
37
|
-
|
|
38
|
-
constructor(message: string, vendor: string, renderer: string) {
|
|
39
|
-
super(message);
|
|
40
|
-
this.name = "SwiftShaderAssertionError";
|
|
41
|
-
this.vendor = vendor;
|
|
42
|
-
this.renderer = renderer;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* SwiftShader identifies itself on `chrome://gpu` and in
|
|
48
|
-
* `WEBGL_debug_renderer_info` with this exact vendor string. Locking to
|
|
49
|
-
* Google's own GL string (rather than a substring match on "swiftshader")
|
|
50
|
-
* avoids false-positives from third-party ANGLE backends that incidentally
|
|
51
|
-
* mention SwiftShader in unrelated diagnostic text.
|
|
52
|
-
*/
|
|
53
|
-
const SWIFTSHADER_VENDOR_SIGNATURE = "Google Inc. (Google)";
|
|
54
|
-
/**
|
|
55
|
-
* Renderer string contains the literal "SwiftShader" token. We match
|
|
56
|
-
* case-insensitively and only require the substring; Chrome occasionally
|
|
57
|
-
* appends a build suffix (e.g. " Vulkan 1.3").
|
|
58
|
-
*/
|
|
59
|
-
const SWIFTSHADER_RENDERER_TOKEN = "swiftshader";
|
|
60
|
-
|
|
61
|
-
interface WebGlInfo {
|
|
62
|
-
vendor: string;
|
|
63
|
-
renderer: string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Read the WebGL vendor/renderer strings from a live `chrome://gpu` page.
|
|
68
|
-
*
|
|
69
|
-
* Extracted from `assertSwiftShader` so tests can stub the navigation +
|
|
70
|
-
* extraction step. Returns the raw values; callers decide how to interpret
|
|
71
|
-
* them. Both fields are best-effort — Chrome returns empty strings if the
|
|
72
|
-
* GPU info table hasn't populated yet, which the caller treats as failure.
|
|
73
|
-
*/
|
|
74
|
-
export async function readWebGlVendorInfo(page: Page): Promise<WebGlInfo> {
|
|
75
|
-
await page.goto("chrome://gpu", { waitUntil: "domcontentloaded", timeout: 30_000 });
|
|
76
|
-
// The "GL_VENDOR" / "GL_RENDERER" rows live inside <info-view> shadow DOM
|
|
77
|
-
// in modern Chrome. We pull the structured `info_log_` payload off the
|
|
78
|
-
// page-level globals instead of querying the DOM, since the DOM layout has
|
|
79
|
-
// drifted across versions.
|
|
80
|
-
const info = await page.evaluate((): WebGlInfo => {
|
|
81
|
-
type Row = { description?: string; value?: string };
|
|
82
|
-
type InfoLog = { graphics_info?: { basic_info?: Row[] } };
|
|
83
|
-
const w = window as unknown as { browserBridge?: { gpuInfo_?: InfoLog } };
|
|
84
|
-
const rows: Row[] = w.browserBridge?.gpuInfo_?.graphics_info?.basic_info ?? [];
|
|
85
|
-
let vendor = "";
|
|
86
|
-
let renderer = "";
|
|
87
|
-
for (const row of rows) {
|
|
88
|
-
if (typeof row.description !== "string" || typeof row.value !== "string") continue;
|
|
89
|
-
if (row.description === "GL_VENDOR") vendor = row.value;
|
|
90
|
-
else if (row.description === "GL_RENDERER") renderer = row.value;
|
|
91
|
-
}
|
|
92
|
-
return { vendor, renderer };
|
|
93
|
-
});
|
|
94
|
-
return info;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Validate that the active WebGL renderer is SwiftShader. Throws
|
|
99
|
-
* `SwiftShaderAssertionError` otherwise.
|
|
100
|
-
*
|
|
101
|
-
* Pass an optional `readInfo` override for tests that don't have a real
|
|
102
|
-
* Puppeteer `Page`. The default implementation navigates to `chrome://gpu`
|
|
103
|
-
* and parses the GL_VENDOR / GL_RENDERER rows.
|
|
104
|
-
*/
|
|
105
|
-
export async function assertSwiftShader(
|
|
106
|
-
page: Page,
|
|
107
|
-
readInfo: (page: Page) => Promise<WebGlInfo> = readWebGlVendorInfo,
|
|
108
|
-
): Promise<void> {
|
|
109
|
-
const { vendor, renderer } = await readInfo(page);
|
|
110
|
-
|
|
111
|
-
const vendorMatches = vendor.trim() === SWIFTSHADER_VENDOR_SIGNATURE;
|
|
112
|
-
const rendererMatches = renderer.toLowerCase().includes(SWIFTSHADER_RENDERER_TOKEN);
|
|
113
|
-
|
|
114
|
-
if (vendorMatches && rendererMatches) return;
|
|
115
|
-
|
|
116
|
-
throw new SwiftShaderAssertionError(
|
|
117
|
-
`[assertSwiftShader] Chrome reported a non-SwiftShader WebGL backend. ` +
|
|
118
|
-
`Distributed renders require pure-software GL for pixel-identical retries. ` +
|
|
119
|
-
`Got vendor=${JSON.stringify(vendor)} renderer=${JSON.stringify(renderer)}; ` +
|
|
120
|
-
`expected vendor=${JSON.stringify(SWIFTSHADER_VENDOR_SIGNATURE)} renderer to contain "SwiftShader". ` +
|
|
121
|
-
`Ensure Chrome was launched with --use-gl=swiftshader --use-angle=swiftshader and that the ` +
|
|
122
|
-
`SwiftShader libraries are present in the runtime image.`,
|
|
123
|
-
vendor,
|
|
124
|
-
renderer,
|
|
125
|
-
);
|
|
126
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
// fallow-ignore-file code-duplication
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
4
|
-
import {
|
|
5
|
-
assertConfiguredFfmpegBinariesExist,
|
|
6
|
-
getFfmpegBinary,
|
|
7
|
-
getFfprobeBinary,
|
|
8
|
-
} from "./ffmpegBinaries.js";
|
|
9
|
-
|
|
10
|
-
describe("ffmpeg binary env resolution", () => {
|
|
11
|
-
const originalFfmpegPath = process.env.HYPERFRAMES_FFMPEG_PATH;
|
|
12
|
-
const originalFfprobePath = process.env.HYPERFRAMES_FFPROBE_PATH;
|
|
13
|
-
|
|
14
|
-
afterEach(() => {
|
|
15
|
-
if (originalFfmpegPath === undefined) delete process.env.HYPERFRAMES_FFMPEG_PATH;
|
|
16
|
-
else process.env.HYPERFRAMES_FFMPEG_PATH = originalFfmpegPath;
|
|
17
|
-
if (originalFfprobePath === undefined) delete process.env.HYPERFRAMES_FFPROBE_PATH;
|
|
18
|
-
else process.env.HYPERFRAMES_FFPROBE_PATH = originalFfprobePath;
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it("uses configured absolute paths when env vars are set", () => {
|
|
22
|
-
process.env.HYPERFRAMES_FFMPEG_PATH = "/tools/ffmpeg.exe";
|
|
23
|
-
process.env.HYPERFRAMES_FFPROBE_PATH = "/tools/ffprobe.exe";
|
|
24
|
-
|
|
25
|
-
expect(getFfmpegBinary()).toBe(resolve("/tools/ffmpeg.exe"));
|
|
26
|
-
expect(getFfprobeBinary()).toBe(resolve("/tools/ffprobe.exe"));
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("throws a clear error when a configured FFmpeg path is missing", () => {
|
|
30
|
-
process.env.HYPERFRAMES_FFMPEG_PATH = "/missing/ffmpeg.exe";
|
|
31
|
-
|
|
32
|
-
expect(() => assertConfiguredFfmpegBinariesExist()).toThrow(
|
|
33
|
-
/FFmpeg binary not found at HYPERFRAMES_FFMPEG_PATH/,
|
|
34
|
-
);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("accepts existing configured paths", () => {
|
|
38
|
-
process.env.HYPERFRAMES_FFMPEG_PATH = process.execPath;
|
|
39
|
-
process.env.HYPERFRAMES_FFPROBE_PATH = process.execPath;
|
|
40
|
-
|
|
41
|
-
expect(() => assertConfiguredFfmpegBinariesExist()).not.toThrow();
|
|
42
|
-
});
|
|
43
|
-
});
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// fallow-ignore-file code-duplication
|
|
2
|
-
import { execFileSync } from "child_process";
|
|
3
|
-
import { existsSync } from "fs";
|
|
4
|
-
import { resolve } from "path";
|
|
5
|
-
|
|
6
|
-
export const FFMPEG_PATH_ENV = "HYPERFRAMES_FFMPEG_PATH";
|
|
7
|
-
export const FFPROBE_PATH_ENV = "HYPERFRAMES_FFPROBE_PATH";
|
|
8
|
-
|
|
9
|
-
const pathCache = new Map<string, string | undefined>();
|
|
10
|
-
|
|
11
|
-
function findOnPath(name: "ffmpeg" | "ffprobe"): string | undefined {
|
|
12
|
-
if (pathCache.has(name)) return pathCache.get(name);
|
|
13
|
-
try {
|
|
14
|
-
const command = process.platform === "win32" ? "where" : "which";
|
|
15
|
-
const output = execFileSync(command, [name], {
|
|
16
|
-
encoding: "utf-8",
|
|
17
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
18
|
-
timeout: 5000,
|
|
19
|
-
});
|
|
20
|
-
const first = output
|
|
21
|
-
.split(/\r?\n/)
|
|
22
|
-
.map((s) => s.trim())
|
|
23
|
-
.find(Boolean);
|
|
24
|
-
const resolved = first ? resolve(first) : undefined;
|
|
25
|
-
pathCache.set(name, resolved);
|
|
26
|
-
return resolved;
|
|
27
|
-
} catch {
|
|
28
|
-
pathCache.set(name, undefined);
|
|
29
|
-
return undefined;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function getConfiguredBinary(envName: string, binaryName: "ffmpeg" | "ffprobe"): string {
|
|
34
|
-
const configured = process.env[envName]?.trim();
|
|
35
|
-
if (configured) return resolve(configured);
|
|
36
|
-
return findOnPath(binaryName) ?? binaryName;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function getFfmpegBinary(): string {
|
|
40
|
-
return getConfiguredBinary(FFMPEG_PATH_ENV, "ffmpeg");
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function getFfprobeBinary(): string {
|
|
44
|
-
return getConfiguredBinary(FFPROBE_PATH_ENV, "ffprobe");
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function assertConfiguredFfmpegBinariesExist(): void {
|
|
48
|
-
const ffmpegPath = process.env[FFMPEG_PATH_ENV]?.trim();
|
|
49
|
-
if (ffmpegPath && !existsSync(ffmpegPath)) {
|
|
50
|
-
throw new Error(
|
|
51
|
-
`[FFmpeg] FFmpeg binary not found at ${FFMPEG_PATH_ENV}="${ffmpegPath}". ` +
|
|
52
|
-
"Install FFmpeg or unset the override.",
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const ffprobePath = process.env[FFPROBE_PATH_ENV]?.trim();
|
|
57
|
-
if (ffprobePath && !existsSync(ffprobePath)) {
|
|
58
|
-
throw new Error(
|
|
59
|
-
`[FFmpeg] FFprobe binary not found at ${FFPROBE_PATH_ENV}="${ffprobePath}". ` +
|
|
60
|
-
"Install FFmpeg or unset the override.",
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
}
|