@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.
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
package/package.json CHANGED
@@ -1,19 +1,36 @@
1
1
  {
2
2
  "name": "@hyperframes/engine",
3
- "version": "0.6.118",
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": "./src/index.ts",
12
- "types": "./src/index.ts",
15
+ "main": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
13
17
  "exports": {
14
- ".": "./src/index.ts",
15
- "./alpha-blit": "./src/utils/alphaBlit.ts",
16
- "./shader-transitions": "./src/utils/shaderTransitions.ts"
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.118"
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
- }
@@ -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
- });