@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
package/package.json
CHANGED
|
@@ -1,19 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/engine",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.120",
|
|
4
4
|
"description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "https://github.com/heygen-com/hyperframes",
|
|
8
8
|
"directory": "packages/engine"
|
|
9
9
|
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
10
14
|
"type": "module",
|
|
11
|
-
"main": "./
|
|
12
|
-
"types": "./
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
13
17
|
"exports": {
|
|
14
|
-
".":
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
".": {
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"types": "./dist/index.d.ts"
|
|
21
|
+
},
|
|
22
|
+
"./alpha-blit": {
|
|
23
|
+
"import": "./dist/utils/alphaBlit.js",
|
|
24
|
+
"types": "./dist/utils/alphaBlit.d.ts"
|
|
25
|
+
},
|
|
26
|
+
"./shader-transitions": {
|
|
27
|
+
"import": "./dist/utils/shaderTransitions.js",
|
|
28
|
+
"types": "./dist/utils/shaderTransitions.d.ts"
|
|
29
|
+
},
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
17
34
|
},
|
|
18
35
|
"dependencies": {
|
|
19
36
|
"@hono/node-server": "^1.13.0",
|
|
@@ -21,7 +38,7 @@
|
|
|
21
38
|
"linkedom": "^0.18.12",
|
|
22
39
|
"puppeteer": "^24.0.0",
|
|
23
40
|
"puppeteer-core": "^24.39.1",
|
|
24
|
-
"@hyperframes/core": "^0.6.
|
|
41
|
+
"@hyperframes/core": "^0.6.120"
|
|
25
42
|
},
|
|
26
43
|
"devDependencies": {
|
|
27
44
|
"@types/node": "^25.0.10",
|
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Regenerate the sRGB → BT.2020 (HLG/PQ) LUT reference values pinned by
|
|
4
|
-
packages/engine/src/utils/alphaBlit.test.ts.
|
|
5
|
-
|
|
6
|
-
This is a paste-helper for the *very rare* case the LUT genuinely needs to
|
|
7
|
-
shift — e.g. a spec update changes one of the OETF constants, or we change
|
|
8
|
-
the SDR-white reference level in the PQ branch. The reference values in
|
|
9
|
-
alphaBlit.test.ts are byte-exact integers, and updating ~12 hand-edited
|
|
10
|
-
literals (or all 256 of them, if the test grows) is exactly the kind of
|
|
11
|
-
mechanical churn we want to keep out of the diff.
|
|
12
|
-
|
|
13
|
-
Usage:
|
|
14
|
-
# Regenerate the probe table that lives in alphaBlit.test.ts (paste over
|
|
15
|
-
# the SRGB_TO_HDR_REFERENCE literal):
|
|
16
|
-
python3 packages/engine/scripts/generate-lut-reference.py --probes
|
|
17
|
-
|
|
18
|
-
# Dump the full 256-entry LUTs as JSON (for ad-hoc analysis or new tests):
|
|
19
|
-
python3 packages/engine/scripts/generate-lut-reference.py
|
|
20
|
-
|
|
21
|
-
# Override the probe set:
|
|
22
|
-
python3 packages/engine/scripts/generate-lut-reference.py --probes \
|
|
23
|
-
--probe-indices 0,32,64,128,192,255
|
|
24
|
-
|
|
25
|
-
## How to use this when the LUT changes
|
|
26
|
-
|
|
27
|
-
1. Edit buildSrgbToHdrLut() in packages/engine/src/utils/alphaBlit.ts.
|
|
28
|
-
2. Mirror the same edit here (constants, branch logic — keep them in sync).
|
|
29
|
-
3. Run with --probes and paste the output over SRGB_TO_HDR_REFERENCE in
|
|
30
|
-
alphaBlit.test.ts. Update the asymmetric-R/G/B and BT.2408-invariant
|
|
31
|
-
tests by hand if those probe values shifted.
|
|
32
|
-
4. Re-run `bun test src/utils/alphaBlit.test.ts` to confirm the engine LUT
|
|
33
|
-
and the test-pinned values still agree.
|
|
34
|
-
|
|
35
|
-
## Why Python (not TS)?
|
|
36
|
-
|
|
37
|
-
A standalone script avoids dragging the engine's bun/Node/build environment
|
|
38
|
-
into a one-off codegen flow, and matches the existing fixture-generation
|
|
39
|
-
pattern at packages/producer/tests/hdr-regression/scripts/generate-hdr-photo-pq.py.
|
|
40
|
-
Python's math.log / math.pow are libm-backed and produce IEEE-754-equivalent
|
|
41
|
-
results to JS's Math.log / Math.pow for these inputs — see js_round_nonneg
|
|
42
|
-
below for the one rounding quirk we have to match by hand.
|
|
43
|
-
|
|
44
|
-
## Drift contract
|
|
45
|
-
|
|
46
|
-
This file MIRRORS buildSrgbToHdrLut() in alphaBlit.ts. If the two diverge,
|
|
47
|
-
this script silently emits wrong values. Any change to one MUST be reflected
|
|
48
|
-
in the other; run the script and the test suite together to catch drift.
|
|
49
|
-
"""
|
|
50
|
-
|
|
51
|
-
import argparse
|
|
52
|
-
import json
|
|
53
|
-
import math
|
|
54
|
-
import sys
|
|
55
|
-
from collections.abc import Iterable
|
|
56
|
-
|
|
57
|
-
# HLG OETF constants (Rec. 2100) — keep in sync with alphaBlit.ts
|
|
58
|
-
HLG_A = 0.17883277
|
|
59
|
-
HLG_B = 1 - 4 * HLG_A
|
|
60
|
-
HLG_C = 0.5 - HLG_A * math.log(4 * HLG_A)
|
|
61
|
-
|
|
62
|
-
# PQ (SMPTE 2084) OETF constants — keep in sync with alphaBlit.ts
|
|
63
|
-
PQ_M1 = 0.1593017578125
|
|
64
|
-
PQ_M2 = 78.84375
|
|
65
|
-
PQ_C1 = 0.8359375
|
|
66
|
-
PQ_C2 = 18.8515625
|
|
67
|
-
PQ_C3 = 18.6875
|
|
68
|
-
PQ_MAX_NITS = 10000.0
|
|
69
|
-
SDR_NITS = 203.0 # BT.2408 SDR-reference white in PQ
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def js_round_nonneg(x: float) -> int:
|
|
73
|
-
"""
|
|
74
|
-
Match JS Math.round semantics for non-negative inputs.
|
|
75
|
-
|
|
76
|
-
JS Math.round rounds half toward +∞ (Math.round(0.5) === 1). Python's
|
|
77
|
-
built-in round() uses banker's rounding (round half to even, so
|
|
78
|
-
round(0.5) === 0 and round(2.5) === 2), which would diverge from
|
|
79
|
-
Math.round for the ~ten or so probe values that fall on a half-integer
|
|
80
|
-
after signal*65535. This helper is only correct for x >= 0 — that's
|
|
81
|
-
fine because signal is always in [0, 1] here.
|
|
82
|
-
"""
|
|
83
|
-
return int(math.floor(x + 0.5))
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def srgb_eotf(i: int) -> float:
|
|
87
|
-
"""sRGB 8-bit code value → linear light in [0, 1] relative to SDR white."""
|
|
88
|
-
v = i / 255
|
|
89
|
-
return v / 12.92 if v <= 0.04045 else math.pow((v + 0.055) / 1.055, 2.4)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
def hlg_oetf(linear: float) -> float:
|
|
93
|
-
if linear <= 1 / 12:
|
|
94
|
-
return math.sqrt(3 * linear)
|
|
95
|
-
return HLG_A * math.log(12 * linear - HLG_B) + HLG_C
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
def pq_oetf(linear: float) -> float:
|
|
99
|
-
# Place SDR-reference white at 203 nits within the 10000-nit PQ peak.
|
|
100
|
-
# This is what reserves headroom for HDR highlights above SDR-white.
|
|
101
|
-
lp = max(0.0, (linear * SDR_NITS) / PQ_MAX_NITS)
|
|
102
|
-
lm1 = math.pow(lp, PQ_M1)
|
|
103
|
-
return math.pow((PQ_C1 + PQ_C2 * lm1) / (1.0 + PQ_C3 * lm1), PQ_M2)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def build_lut(transfer: str) -> list[int]:
|
|
107
|
-
out: list[int] = []
|
|
108
|
-
for i in range(256):
|
|
109
|
-
linear = srgb_eotf(i)
|
|
110
|
-
signal = hlg_oetf(linear) if transfer == "hlg" else pq_oetf(linear)
|
|
111
|
-
out.append(min(65535, js_round_nonneg(signal * 65535)))
|
|
112
|
-
return out
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
# Mirror SRGB_TO_HDR_REFERENCE indices in alphaBlit.test.ts. Endpoints
|
|
116
|
-
# (0, 1, 254, 255) catch off-by-one regressions; mid-range values (32, 64,
|
|
117
|
-
# 96, 128, 160, 192, 224) sample the middle of both transfer curves.
|
|
118
|
-
DEFAULT_PROBES: tuple[int, ...] = (0, 1, 10, 32, 64, 96, 128, 160, 192, 224, 254, 255)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def emit_json(hlg: list[int], pq: list[int]) -> None:
|
|
122
|
-
print(json.dumps({"size": 256, "hlg": hlg, "pq": pq}, indent=2))
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def emit_probes(hlg: list[int], pq: list[int], probes: Iterable[int]) -> None:
|
|
126
|
-
# Output is paste-ready TS for SRGB_TO_HDR_REFERENCE in alphaBlit.test.ts.
|
|
127
|
-
print("const SRGB_TO_HDR_REFERENCE: readonly SrgbHdrProbe[] = [")
|
|
128
|
-
for i in probes:
|
|
129
|
-
if not 0 <= i <= 255:
|
|
130
|
-
raise ValueError(f"probe index {i} out of range [0, 255]")
|
|
131
|
-
print(f" {{ srgb: {i}, hlg: {hlg[i]}, pq: {pq[i]} }},")
|
|
132
|
-
print("];")
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def parse_indices(s: str) -> list[int]:
|
|
136
|
-
return [int(x.strip()) for x in s.split(",") if x.strip()]
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def main() -> int:
|
|
140
|
-
parser = argparse.ArgumentParser(
|
|
141
|
-
description="Regenerate sRGB → BT.2020 (HLG/PQ) LUT reference values.",
|
|
142
|
-
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
143
|
-
)
|
|
144
|
-
parser.add_argument(
|
|
145
|
-
"--probes",
|
|
146
|
-
action="store_true",
|
|
147
|
-
help="Emit a TS snippet ready to paste over SRGB_TO_HDR_REFERENCE.",
|
|
148
|
-
)
|
|
149
|
-
parser.add_argument(
|
|
150
|
-
"--probe-indices",
|
|
151
|
-
type=parse_indices,
|
|
152
|
-
default=list(DEFAULT_PROBES),
|
|
153
|
-
help="Comma-separated probe indices (default mirrors alphaBlit.test.ts).",
|
|
154
|
-
)
|
|
155
|
-
args = parser.parse_args()
|
|
156
|
-
|
|
157
|
-
hlg = build_lut("hlg")
|
|
158
|
-
pq = build_lut("pq")
|
|
159
|
-
|
|
160
|
-
if args.probes:
|
|
161
|
-
emit_probes(hlg, pq, args.probe_indices)
|
|
162
|
-
else:
|
|
163
|
-
emit_json(hlg, pq)
|
|
164
|
-
return 0
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if __name__ == "__main__":
|
|
168
|
-
sys.exit(main())
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser integration test for fitTextFontSize.
|
|
3
|
-
*
|
|
4
|
-
* Launches headless Chrome, loads the runtime IIFE into a page,
|
|
5
|
-
* and verifies that window.__hyperframes.fitTextFontSize produces
|
|
6
|
-
* correct results with real canvas measureText.
|
|
7
|
-
*
|
|
8
|
-
* Requires: puppeteer (dep of @hyperframes/engine)
|
|
9
|
-
* Run: cd packages/engine && npx tsx scripts/test-fitTextFontSize-browser.ts
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { buildHyperframesRuntimeScript } from "../../core/src/inline-scripts/hyperframesRuntime.engine";
|
|
13
|
-
|
|
14
|
-
function assert(condition: unknown, message: string): void {
|
|
15
|
-
if (!condition) {
|
|
16
|
-
throw new Error(`FAIL: ${message}`);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function main() {
|
|
21
|
-
// Dynamic import — puppeteer is a monorepo dep, not a core dep
|
|
22
|
-
let puppeteer;
|
|
23
|
-
try {
|
|
24
|
-
puppeteer = (await import("puppeteer")).default;
|
|
25
|
-
} catch {
|
|
26
|
-
console.log(
|
|
27
|
-
JSON.stringify({
|
|
28
|
-
event: "fitTextFontSize_browser_test_skipped",
|
|
29
|
-
reason: "puppeteer not available",
|
|
30
|
-
}),
|
|
31
|
-
);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const runtimeSource = buildHyperframesRuntimeScript({ minify: false });
|
|
36
|
-
assert(
|
|
37
|
-
runtimeSource !== null,
|
|
38
|
-
"buildHyperframesRuntimeScript returned null — entry.ts not found",
|
|
39
|
-
);
|
|
40
|
-
|
|
41
|
-
const html = `<!DOCTYPE html>
|
|
42
|
-
<html><head>
|
|
43
|
-
<style>
|
|
44
|
-
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@900&display=block');
|
|
45
|
-
</style>
|
|
46
|
-
</head><body>
|
|
47
|
-
<script>${runtimeSource}</script>
|
|
48
|
-
<script>
|
|
49
|
-
window.__testResults = {};
|
|
50
|
-
|
|
51
|
-
// Test 1: Short text should fit at base size
|
|
52
|
-
var r1 = window.__hyperframes.fitTextFontSize("HI");
|
|
53
|
-
window.__testResults.shortText = r1;
|
|
54
|
-
|
|
55
|
-
// Test 2: Wide text should shrink below base size but still fit at 1600px
|
|
56
|
-
var r2 = window.__hyperframes.fitTextFontSize(
|
|
57
|
-
"CONGRATULATIONS TO EVERYBODY IN THE WORLD",
|
|
58
|
-
{ fontFamily: "sans-serif", fontWeight: 900, maxWidth: 1600 }
|
|
59
|
-
);
|
|
60
|
-
window.__testResults.wideText = r2;
|
|
61
|
-
|
|
62
|
-
// Test 3: Extremely wide text that can't fit should return minFontSize
|
|
63
|
-
var r3 = window.__hyperframes.fitTextFontSize(
|
|
64
|
-
"WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW",
|
|
65
|
-
{ fontFamily: "sans-serif", fontWeight: 900, maxWidth: 400 }
|
|
66
|
-
);
|
|
67
|
-
window.__testResults.extremeText = r3;
|
|
68
|
-
|
|
69
|
-
// Test 4: Function exists and is callable
|
|
70
|
-
window.__testResults.exists = typeof window.__hyperframes.fitTextFontSize === "function";
|
|
71
|
-
</script>
|
|
72
|
-
</body></html>`;
|
|
73
|
-
|
|
74
|
-
const browser = await puppeteer.launch({
|
|
75
|
-
headless: true,
|
|
76
|
-
args: ["--no-sandbox", "--disable-setuid-sandbox"],
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
const page = await browser.newPage();
|
|
81
|
-
await page.setContent(html, { waitUntil: "networkidle0", timeout: 10000 });
|
|
82
|
-
|
|
83
|
-
// Wait for font to potentially load (best effort — sans-serif fallback is fine for testing)
|
|
84
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
85
|
-
|
|
86
|
-
const results = await page.evaluate(() => (window as any).__testResults);
|
|
87
|
-
|
|
88
|
-
// Test 1: Short text fits at base size (78px default)
|
|
89
|
-
assert(results.exists === true, "fitTextFontSize should exist on window.__hyperframes");
|
|
90
|
-
assert(
|
|
91
|
-
results.shortText.fits === true,
|
|
92
|
-
`Short text should fit, got fits=${results.shortText.fits}`,
|
|
93
|
-
);
|
|
94
|
-
assert(
|
|
95
|
-
results.shortText.fontSize === 78,
|
|
96
|
-
`Short text should use base size 78, got ${results.shortText.fontSize}`,
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
// Test 2: Wide text should shrink below base size
|
|
100
|
-
assert(
|
|
101
|
-
results.wideText.fits === true,
|
|
102
|
-
`Wide text should still fit, got fits=${results.wideText.fits}`,
|
|
103
|
-
);
|
|
104
|
-
assert(
|
|
105
|
-
results.wideText.fontSize < 78,
|
|
106
|
-
`Wide text should shrink below 78, got ${results.wideText.fontSize}`,
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
// Test 3: Extreme text should hit floor
|
|
110
|
-
assert(
|
|
111
|
-
results.extremeText.fontSize === 42,
|
|
112
|
-
`Extreme text should hit minFontSize 42, got ${results.extremeText.fontSize}`,
|
|
113
|
-
);
|
|
114
|
-
assert(
|
|
115
|
-
results.extremeText.fits === false,
|
|
116
|
-
`Extreme text should not fit, got fits=${results.extremeText.fits}`,
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
console.log(
|
|
120
|
-
JSON.stringify({
|
|
121
|
-
event: "fitTextFontSize_browser_test_passed",
|
|
122
|
-
shortText: results.shortText,
|
|
123
|
-
wideText: results.wideText,
|
|
124
|
-
extremeText: results.extremeText,
|
|
125
|
-
}),
|
|
126
|
-
);
|
|
127
|
-
} finally {
|
|
128
|
-
await browser.close();
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
main().catch((err) => {
|
|
133
|
-
console.error(err.message);
|
|
134
|
-
process.exit(1);
|
|
135
|
-
});
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Type augmentation for Chrome's HeadlessExperimental CDP domain.
|
|
3
|
-
*
|
|
4
|
-
* Puppeteer's CDPSession.send() is typed against devtools-protocol's
|
|
5
|
-
* ProtocolMapping.Commands, which does not include HeadlessExperimental.
|
|
6
|
-
* This module augmentation adds proper types so we can call these methods
|
|
7
|
-
* without unsafe `as any` casts.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/** Parameters for HeadlessExperimental.beginFrame */
|
|
11
|
-
interface HeadlessExperimentalBeginFrameRequest {
|
|
12
|
-
/** Timestamp in milliseconds since epoch for the frame */
|
|
13
|
-
frameTimeTicks: number;
|
|
14
|
-
/** Interval in milliseconds between frames */
|
|
15
|
-
interval: number;
|
|
16
|
-
/** If true, do not produce display updates (warmup mode) */
|
|
17
|
-
noDisplayUpdates?: boolean;
|
|
18
|
-
/** Optional screenshot configuration */
|
|
19
|
-
screenshot?: {
|
|
20
|
-
format: "jpeg" | "png";
|
|
21
|
-
quality?: number;
|
|
22
|
-
optimizeForSpeed?: boolean;
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Response from HeadlessExperimental.beginFrame */
|
|
27
|
-
interface HeadlessExperimentalBeginFrameResponse {
|
|
28
|
-
/** Whether the compositor reported visual damage */
|
|
29
|
-
hasDamage: boolean;
|
|
30
|
-
/** Base64-encoded screenshot data (present only when screenshot was requested and hasDamage is true) */
|
|
31
|
-
screenshotData?: string;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export {};
|
|
35
|
-
|
|
36
|
-
declare module "devtools-protocol/types/protocol-mapping.js" {
|
|
37
|
-
// Merge into the existing ProtocolMapping namespace
|
|
38
|
-
export namespace ProtocolMapping {
|
|
39
|
-
interface Commands {
|
|
40
|
-
"HeadlessExperimental.enable": {
|
|
41
|
-
paramsType: [];
|
|
42
|
-
returnType: void;
|
|
43
|
-
};
|
|
44
|
-
"HeadlessExperimental.disable": {
|
|
45
|
-
paramsType: [];
|
|
46
|
-
returnType: void;
|
|
47
|
-
};
|
|
48
|
-
"HeadlessExperimental.beginFrame": {
|
|
49
|
-
paramsType: [HeadlessExperimentalBeginFrameRequest];
|
|
50
|
-
returnType: HeadlessExperimentalBeginFrameResponse;
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
package/src/config.test.ts
DELETED
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { resolveConfig, DEFAULT_CONFIG } from "./config.js";
|
|
3
|
-
import { isLowMemorySystem } from "./services/systemMemory.js";
|
|
4
|
-
|
|
5
|
-
describe("resolveConfig", () => {
|
|
6
|
-
const savedEnv = new Map<string, string | undefined>();
|
|
7
|
-
|
|
8
|
-
function setEnv(key: string, value: string) {
|
|
9
|
-
savedEnv.set(key, process.env[key]);
|
|
10
|
-
process.env[key] = value;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
savedEnv.clear();
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
afterEach(() => {
|
|
18
|
-
for (const [key, value] of savedEnv) {
|
|
19
|
-
if (value === undefined) {
|
|
20
|
-
delete process.env[key];
|
|
21
|
-
} else {
|
|
22
|
-
process.env[key] = value;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it("returns defaults when no overrides or env vars are set", () => {
|
|
28
|
-
const config = resolveConfig();
|
|
29
|
-
expect(config.fps).toBe(30);
|
|
30
|
-
expect(config.quality).toBe("standard");
|
|
31
|
-
expect(config.format).toBe("jpeg");
|
|
32
|
-
expect(config.jpegQuality).toBe(80);
|
|
33
|
-
expect(config.browserGpuMode).toBe("software");
|
|
34
|
-
expect(config.enableStreamingEncode).toBe(true);
|
|
35
|
-
expect(config.streamingEncodeMaxDurationSeconds).toBe(240);
|
|
36
|
-
expect((config as Record<string, unknown>).vp9CpuUsed).toBe(4);
|
|
37
|
-
expect(config.audioGain).toBe(1);
|
|
38
|
-
expect(config.debug).toBe(false);
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("applies explicit overrides over defaults", () => {
|
|
42
|
-
const config = resolveConfig({ fps: 60, debug: true });
|
|
43
|
-
expect(config.fps).toBe(60);
|
|
44
|
-
expect(config.debug).toBe(true);
|
|
45
|
-
// Non-overridden fields remain at defaults
|
|
46
|
-
expect(config.quality).toBe("standard");
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it("reads numeric env vars with PRODUCER_ prefix", () => {
|
|
50
|
-
setEnv("PRODUCER_MAX_WORKERS", "4");
|
|
51
|
-
setEnv("PRODUCER_CORES_PER_WORKER", "3");
|
|
52
|
-
|
|
53
|
-
const config = resolveConfig();
|
|
54
|
-
expect(config.concurrency).toBe(4);
|
|
55
|
-
expect(config.coresPerWorker).toBe(3);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("reads boolean env vars (true/false strings)", () => {
|
|
59
|
-
setEnv("PRODUCER_DISABLE_GPU", "true");
|
|
60
|
-
setEnv("PRODUCER_ENABLE_BROWSER_POOL", "true");
|
|
61
|
-
|
|
62
|
-
const config = resolveConfig();
|
|
63
|
-
expect(config.disableGpu).toBe(true);
|
|
64
|
-
expect(config.enableBrowserPool).toBe(true);
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("lets env vars opt out of default streaming encode", () => {
|
|
68
|
-
setEnv("PRODUCER_ENABLE_STREAMING_ENCODE", "false");
|
|
69
|
-
|
|
70
|
-
const config = resolveConfig();
|
|
71
|
-
expect(config.enableStreamingEncode).toBe(false);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("reads the streaming encode duration cutoff from env", () => {
|
|
75
|
-
setEnv("PRODUCER_STREAMING_ENCODE_MAX_DURATION_SECONDS", "120");
|
|
76
|
-
|
|
77
|
-
const config = resolveConfig();
|
|
78
|
-
expect(config.streamingEncodeMaxDurationSeconds).toBe(120);
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
it("clamps negative streaming encode duration cutoff env values to zero", () => {
|
|
82
|
-
setEnv("PRODUCER_STREAMING_ENCODE_MAX_DURATION_SECONDS", "-1");
|
|
83
|
-
|
|
84
|
-
const config = resolveConfig();
|
|
85
|
-
expect(config.streamingEncodeMaxDurationSeconds).toBe(0);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it("reads VP9 cpu-used from env", () => {
|
|
89
|
-
setEnv("PRODUCER_VP9_CPU_USED", "6");
|
|
90
|
-
|
|
91
|
-
const config = resolveConfig();
|
|
92
|
-
expect((config as Record<string, unknown>).vp9CpuUsed).toBe(6);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("falls back to the VP9 cpu-used default for invalid env values", () => {
|
|
96
|
-
setEnv("PRODUCER_VP9_CPU_USED", "fast");
|
|
97
|
-
|
|
98
|
-
const config = resolveConfig();
|
|
99
|
-
expect((config as Record<string, unknown>).vp9CpuUsed).toBe(4);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("clamps VP9 cpu-used env values to libvpx's supported range", () => {
|
|
103
|
-
setEnv("PRODUCER_VP9_CPU_USED", "99");
|
|
104
|
-
expect((resolveConfig() as Record<string, unknown>).vp9CpuUsed).toBe(8);
|
|
105
|
-
|
|
106
|
-
process.env.PRODUCER_VP9_CPU_USED = "-99";
|
|
107
|
-
expect((resolveConfig() as Record<string, unknown>).vp9CpuUsed).toBe(-8);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("treats non-'true' boolean env vars as false", () => {
|
|
111
|
-
setEnv("PRODUCER_DISABLE_GPU", "yes");
|
|
112
|
-
|
|
113
|
-
const config = resolveConfig();
|
|
114
|
-
expect(config.disableGpu).toBe(false);
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("reads browser GPU mode from env", () => {
|
|
118
|
-
setEnv("PRODUCER_BROWSER_GPU_MODE", "hardware");
|
|
119
|
-
|
|
120
|
-
const config = resolveConfig();
|
|
121
|
-
expect(config.browserGpuMode).toBe("hardware");
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it("accepts 'auto' as a valid browser GPU mode env value", () => {
|
|
125
|
-
setEnv("PRODUCER_BROWSER_GPU_MODE", "auto");
|
|
126
|
-
|
|
127
|
-
const config = resolveConfig();
|
|
128
|
-
expect(config.browserGpuMode).toBe("auto");
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("falls back to software browser GPU mode for invalid env values", () => {
|
|
132
|
-
setEnv("PRODUCER_BROWSER_GPU_MODE", "native");
|
|
133
|
-
|
|
134
|
-
const config = resolveConfig();
|
|
135
|
-
expect(config.browserGpuMode).toBe("software");
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
it("explicit overrides take precedence over env vars", () => {
|
|
139
|
-
setEnv("PRODUCER_CORES_PER_WORKER", "5");
|
|
140
|
-
|
|
141
|
-
const config = resolveConfig({ coresPerWorker: 8 });
|
|
142
|
-
expect(config.coresPerWorker).toBe(8);
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
it("falls back to defaults for invalid numeric env vars", () => {
|
|
146
|
-
setEnv("PRODUCER_CORES_PER_WORKER", "not-a-number");
|
|
147
|
-
|
|
148
|
-
const config = resolveConfig();
|
|
149
|
-
expect(config.coresPerWorker).toBe(DEFAULT_CONFIG.coresPerWorker);
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it("clamps chunkSizeFrames to minimum of 120", () => {
|
|
153
|
-
setEnv("PRODUCER_CHUNK_SIZE_FRAMES", "50");
|
|
154
|
-
|
|
155
|
-
const config = resolveConfig();
|
|
156
|
-
expect(config.chunkSizeFrames).toBe(120);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it("clamps frameDataUriCacheLimit to minimum of 32", () => {
|
|
160
|
-
setEnv("PRODUCER_FRAME_DATA_URI_CACHE_LIMIT", "10");
|
|
161
|
-
|
|
162
|
-
const config = resolveConfig();
|
|
163
|
-
expect(config.frameDataUriCacheLimit).toBe(32);
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
describe("enablePageSideCompositing (HF_PAGE_SIDE_COMPOSITING)", () => {
|
|
167
|
-
it("defaults to true", () => {
|
|
168
|
-
const config = resolveConfig();
|
|
169
|
-
expect(config.enablePageSideCompositing).toBe(true);
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
it("disabled when HF_PAGE_SIDE_COMPOSITING=false", () => {
|
|
173
|
-
setEnv("HF_PAGE_SIDE_COMPOSITING", "false");
|
|
174
|
-
const config = resolveConfig();
|
|
175
|
-
expect(config.enablePageSideCompositing).toBe(false);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it("explicit override wins over the env var", () => {
|
|
179
|
-
setEnv("HF_PAGE_SIDE_COMPOSITING", "true");
|
|
180
|
-
const config = resolveConfig({ enablePageSideCompositing: false });
|
|
181
|
-
expect(config.enablePageSideCompositing).toBe(false);
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
describe("lowMemoryMode", () => {
|
|
186
|
-
it("forces on for truthy PRODUCER_LOW_MEMORY_MODE values", () => {
|
|
187
|
-
setEnv("PRODUCER_LOW_MEMORY_MODE", "true");
|
|
188
|
-
for (const v of ["true", "on", "1", "TRUE"]) {
|
|
189
|
-
process.env.PRODUCER_LOW_MEMORY_MODE = v;
|
|
190
|
-
expect(resolveConfig().lowMemoryMode).toBe(true);
|
|
191
|
-
}
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it("forces off for falsy PRODUCER_LOW_MEMORY_MODE values", () => {
|
|
195
|
-
setEnv("PRODUCER_LOW_MEMORY_MODE", "false");
|
|
196
|
-
for (const v of ["false", "off", "0", "OFF"]) {
|
|
197
|
-
process.env.PRODUCER_LOW_MEMORY_MODE = v;
|
|
198
|
-
expect(resolveConfig().lowMemoryMode).toBe(false);
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it("auto-detects from total RAM when the env var is unset", () => {
|
|
203
|
-
setEnv("PRODUCER_LOW_MEMORY_MODE", "");
|
|
204
|
-
delete process.env.PRODUCER_LOW_MEMORY_MODE;
|
|
205
|
-
expect(resolveConfig().lowMemoryMode).toBe(isLowMemorySystem());
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
it("explicit override beats both env and auto-detection", () => {
|
|
209
|
-
setEnv("PRODUCER_LOW_MEMORY_MODE", "true");
|
|
210
|
-
expect(resolveConfig({ lowMemoryMode: false }).lowMemoryMode).toBe(false);
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
});
|