@hyperframes/engine 0.8.167 → 0.8.169
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/config.d.ts +2 -1
- package/dist/config.js +2 -1
- package/dist/services/browserManager.d.ts +1 -1
- package/dist/services/browserManager.js +4 -3
- package/dist/services/chunkEncoder.d.ts +2 -0
- package/dist/services/chunkEncoder.js +1 -1
- package/dist/services/frameCapture.d.ts +2 -1
- package/dist/services/frameCapture.js +19 -18
- package/package.json +3 -3
package/dist/config.d.ts
CHANGED
|
@@ -5,13 +5,14 @@
|
|
|
5
5
|
* env var sprawl with a structured interface. Env vars still work as
|
|
6
6
|
* fallbacks for backward compatibility during migration.
|
|
7
7
|
*/
|
|
8
|
+
import { type DistributedFps } from "@hyperframes/core/distributed-fps";
|
|
8
9
|
/**
|
|
9
10
|
* Full engine configuration. All fields are wired through the config
|
|
10
11
|
* object; env vars serve as backward-compatible fallbacks resolved
|
|
11
12
|
* in `resolveConfig()`.
|
|
12
13
|
*/
|
|
13
14
|
export interface EngineConfig {
|
|
14
|
-
fps:
|
|
15
|
+
fps: DistributedFps;
|
|
15
16
|
quality: "draft" | "standard" | "high";
|
|
16
17
|
format: "jpeg" | "png";
|
|
17
18
|
jpegQuality: number;
|
package/dist/config.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* fallbacks for backward compatibility during migration.
|
|
7
7
|
*/
|
|
8
8
|
import { tmpdir } from "node:os";
|
|
9
|
+
import { DISTRIBUTED_FPS } from "@hyperframes/core/distributed-fps";
|
|
9
10
|
import { chromeMajorCeiling } from "./services/chromeHostCeiling.js";
|
|
10
11
|
import { join } from "node:path";
|
|
11
12
|
import { getSystemTotalMb, isLowMemorySystem, LOW_MEMORY_TOTAL_MB_THRESHOLD, } from "./services/systemMemory.js";
|
|
@@ -96,7 +97,7 @@ const POSITIVE_NUMBER_ENGINE_CONFIG_FIELDS = [
|
|
|
96
97
|
"extractCacheMaxBytes",
|
|
97
98
|
];
|
|
98
99
|
const ENUM_ENGINE_CONFIG_FIELDS = {
|
|
99
|
-
fps:
|
|
100
|
+
fps: DISTRIBUTED_FPS,
|
|
100
101
|
quality: ["draft", "standard", "high"],
|
|
101
102
|
format: ["jpeg", "png"],
|
|
102
103
|
browserGpuMode: ["software", "hardware", "auto"],
|
|
@@ -122,7 +122,7 @@ type GpuConfig = Partial<Pick<EngineConfig, "browserGpuMode" | "disableGpu" | "c
|
|
|
122
122
|
*/
|
|
123
123
|
export declare function usesSoftwareWebGpu(requiresWebGpu: boolean | undefined, config?: GpuConfig): boolean;
|
|
124
124
|
export declare function buildChromeArgs(options: BuildChromeArgsOptions, config?: GpuConfig): string[];
|
|
125
|
-
/** Does the
|
|
125
|
+
/** Does the root, or a mounted root (data-hf-inner-root once bundled), declare `data-requires-webgpu`? */
|
|
126
126
|
export declare function compositionRequiresWebGpu(html: string): boolean;
|
|
127
127
|
/** No hardware WebGPU adapter on this host; distinct from a browser or navigation failure. */
|
|
128
128
|
export declare class WebGpuUnavailableError extends Error {
|
|
@@ -879,12 +879,13 @@ export function buildChromeArgs(options, config) {
|
|
|
879
879
|
}
|
|
880
880
|
return chromeArgs;
|
|
881
881
|
}
|
|
882
|
-
/** Does the
|
|
882
|
+
/** Does the root, or a mounted root (data-hf-inner-root once bundled), declare `data-requires-webgpu`? */
|
|
883
883
|
export function compositionRequiresWebGpu(html) {
|
|
884
884
|
for (const tag of scanHtmlOpeningTags(html)) {
|
|
885
885
|
const names = tag.attributes.map((attribute) => attribute.name);
|
|
886
|
-
|
|
887
|
-
|
|
886
|
+
const isRoot = names.includes("data-composition-id") || names.includes("data-hf-inner-root");
|
|
887
|
+
if (isRoot && names.includes("data-requires-webgpu"))
|
|
888
|
+
return true;
|
|
888
889
|
}
|
|
889
890
|
return false;
|
|
890
891
|
}
|
|
@@ -43,6 +43,8 @@ export interface MuxVideoWithAudioOptions extends Partial<Pick<EngineConfig, "ff
|
|
|
43
43
|
* depend on the file extension alone.
|
|
44
44
|
*/
|
|
45
45
|
audioCodec?: "aac";
|
|
46
|
+
/** Replaces `-i <videoPath>` (which is then unused), e.g. with a concat-demuxer input. */
|
|
47
|
+
videoInputArgs?: readonly string[];
|
|
46
48
|
/**
|
|
47
49
|
* @deprecated No longer used. `-avoid_negative_ts` is never passed for
|
|
48
50
|
* mp4/mov muxing (ffmpeg's `auto` default already resolves to `disabled`
|
|
@@ -587,7 +587,7 @@ export async function muxVideoWithAudio(videoPath, audioPath, outputPath, signal
|
|
|
587
587
|
const isWebm = outputPath.endsWith(".webm");
|
|
588
588
|
const isMov = outputPath.endsWith(".mov");
|
|
589
589
|
const shouldCopyAudio = isWebm ? false : await shouldCopyAacSidecar(audioPath, config);
|
|
590
|
-
const args = ["-i", videoPath, "-i", audioPath, "-c:v", "copy"];
|
|
590
|
+
const args = [...(config?.videoInputArgs ?? ["-i", videoPath]), "-i", audioPath, "-c:v", "copy"];
|
|
591
591
|
if (isWebm) {
|
|
592
592
|
args.push("-c:a", "libopus", "-b:a", "128k");
|
|
593
593
|
}
|
|
@@ -12,7 +12,7 @@ import { type MotionBlurPlan } from "./motionBlur.js";
|
|
|
12
12
|
import { type BrowserLease, type CaptureMode } from "./browserManager.js";
|
|
13
13
|
import { pageScreenshotCapture } from "./screenshotService.js";
|
|
14
14
|
import { type EngineConfig } from "../config.js";
|
|
15
|
-
import type { CaptureOptions, CaptureResult, CaptureBufferResult, CapturePerfSummary, CaptureWarning, SubTimelineWaitMemo, SubTimelineWaitOutcome } from "../types.js";
|
|
15
|
+
import type { CaptureOptions, CaptureVideoMetadataHint, CaptureResult, CaptureBufferResult, CapturePerfSummary, CaptureWarning, SubTimelineWaitMemo, SubTimelineWaitOutcome } from "../types.js";
|
|
16
16
|
import { type ChromeMemorySampler, type ChromePids } from "./chromeMemorySampler.js";
|
|
17
17
|
export { isMemoryExhaustionError, isTransientBrowserError } from "./captureFailure.js";
|
|
18
18
|
export type { CaptureOptions, CaptureResult, CaptureBufferResult, CapturePerfSummary };
|
|
@@ -427,6 +427,7 @@ export declare function recordSubTimelineWarning(session: CaptureSession, timeou
|
|
|
427
427
|
* PRINFRA-300 (blank hook scene, zero diagnostics, "fixed" by re-render).
|
|
428
428
|
*/
|
|
429
429
|
export declare function buildLiveMapWarning(libraries: readonly string[]): CaptureWarning;
|
|
430
|
+
export declare function applyVideoMetadataHints(page: Page, hints: readonly CaptureVideoMetadataHint[] | undefined): Promise<void>;
|
|
430
431
|
/** Thrown once a VFX chain fails: the frames it paints would render without the effect. */
|
|
431
432
|
export declare class VfxFailureError extends Error {
|
|
432
433
|
readonly detail: string;
|
|
@@ -434,14 +434,14 @@ async function waitForCloseWithTimeout(promise) {
|
|
|
434
434
|
* screenshots carry a real alpha channel (Chrome resets the override on every
|
|
435
435
|
* navigation, so this must run after page load).
|
|
436
436
|
*
|
|
437
|
-
* drawElement is also skipped when
|
|
437
|
+
* drawElement is also skipped when scaling (deviceScaleFactor other than 1):
|
|
438
438
|
* `drawElementImage` reads the canvas at CSS pixels and has no equivalent of
|
|
439
439
|
* `Page.captureScreenshot`'s clip+scale, so it would silently capture at 1x and
|
|
440
440
|
* drop the requested supersample. Such renders fall through to the screenshot
|
|
441
|
-
* path (preMode already forces "screenshot" for DPR
|
|
441
|
+
* path (preMode already forces "screenshot" for DPR other than 1).
|
|
442
442
|
*/
|
|
443
443
|
async function initDrawElementOrTransparentBackground(session, page, logInitPhase) {
|
|
444
|
-
const supersampling = (session.options.deviceScaleFactor ?? 1)
|
|
444
|
+
const supersampling = (session.options.deviceScaleFactor ?? 1) !== 1;
|
|
445
445
|
// forceScreenshot is an explicit routing decision made upstream (render-mode
|
|
446
446
|
// compat hints like raw requestAnimationFrame, alpha formats, low-memory) —
|
|
447
447
|
// drawElement must not override it. Concretely: an rAF-compat comp on
|
|
@@ -469,7 +469,7 @@ async function initDrawElementOrTransparentBackground(session, page, logInitPhas
|
|
|
469
469
|
session.deGateReason = "supersampling";
|
|
470
470
|
session.deFallbackTrigger = "supersampling";
|
|
471
471
|
console.log("[engine] --experimental-fast-capture disabled for this render: drawElementImage " +
|
|
472
|
-
"ignores deviceScaleFactor, so
|
|
472
|
+
"ignores deviceScaleFactor, so scaled (DPR other than 1) output uses screenshot capture.");
|
|
473
473
|
}
|
|
474
474
|
if ((session.config?.useDrawElement ?? false) && !supersampling && forceScreenshot) {
|
|
475
475
|
session.deGateReason = "render_mode_hint";
|
|
@@ -769,8 +769,8 @@ export async function createCaptureSession(serverUrl, outputDir, options, onBefo
|
|
|
769
769
|
// (the captured surface is sized by the OS window in CSS pixels regardless
|
|
770
770
|
// of `Emulation.setDeviceMetricsOverride`'s DPR). When supersampling we
|
|
771
771
|
// need explicit clip+scale on `Page.captureScreenshot`, so fall back to
|
|
772
|
-
// the screenshot path for any DPR
|
|
773
|
-
const supersampling = (options.deviceScaleFactor ?? 1)
|
|
772
|
+
// the screenshot path for any DPR other than 1.
|
|
773
|
+
const supersampling = (options.deviceScaleFactor ?? 1) !== 1;
|
|
774
774
|
const requestedGpuMode = config?.browserGpuMode ?? DEFAULT_CONFIG.browserGpuMode;
|
|
775
775
|
const resolvedGpuMode = await resolveBrowserGpuMode(requestedGpuMode, {
|
|
776
776
|
chromePath: headlessShell ?? undefined,
|
|
@@ -1529,11 +1529,12 @@ async function decodeAllImages(page) {
|
|
|
1529
1529
|
}));
|
|
1530
1530
|
});
|
|
1531
1531
|
}
|
|
1532
|
-
async function applyVideoMetadataHints(page, hints) {
|
|
1532
|
+
export async function applyVideoMetadataHints(page, hints) {
|
|
1533
1533
|
if (!hints || hints.length === 0)
|
|
1534
1534
|
return;
|
|
1535
1535
|
// fallow-ignore-next-line complexity
|
|
1536
|
-
await page.evaluate((metadataHints) => {
|
|
1536
|
+
await page.evaluate(async (metadataHints) => {
|
|
1537
|
+
const posters = new Set();
|
|
1537
1538
|
for (const hint of metadataHints) {
|
|
1538
1539
|
if (!hint.id ||
|
|
1539
1540
|
!Number.isFinite(hint.width) ||
|
|
@@ -1544,18 +1545,18 @@ async function applyVideoMetadataHints(page, hints) {
|
|
|
1544
1545
|
}
|
|
1545
1546
|
const video = (window.__hfMediaEl?.(hint.id) ??
|
|
1546
1547
|
document.getElementById(hint.id));
|
|
1547
|
-
if (!video)
|
|
1548
|
+
if (!video || video.hasAttribute("poster"))
|
|
1548
1549
|
continue;
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
const computed = window.getComputedStyle(video);
|
|
1554
|
-
if (!video.style.aspectRatio &&
|
|
1555
|
-
(!computed.aspectRatio || computed.aspectRatio === "auto")) {
|
|
1556
|
-
video.style.aspectRatio = `${hint.width} / ${hint.height}`;
|
|
1557
|
-
}
|
|
1550
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${hint.width}" height="${hint.height}"/>`;
|
|
1551
|
+
const intrinsicSizePoster = `data:image/svg+xml,${encodeURIComponent(svg)}`;
|
|
1552
|
+
video.poster = intrinsicSizePoster;
|
|
1553
|
+
posters.add(intrinsicSizePoster);
|
|
1558
1554
|
}
|
|
1555
|
+
await Promise.all([...posters].map((src) => {
|
|
1556
|
+
const image = new Image();
|
|
1557
|
+
image.src = src;
|
|
1558
|
+
return image.decode();
|
|
1559
|
+
}));
|
|
1559
1560
|
}, [...hints]);
|
|
1560
1561
|
}
|
|
1561
1562
|
async function waitForOptionalTailwindReady(page, timeoutMs) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/engine",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.169",
|
|
4
4
|
"description": "Seekable web page to video rendering engine (Puppeteer + FFmpeg)",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -50,8 +50,8 @@
|
|
|
50
50
|
"linkedom": "^0.18.12",
|
|
51
51
|
"puppeteer": "^25.8.0",
|
|
52
52
|
"puppeteer-core": "^25.8.0",
|
|
53
|
-
"@hyperframes/
|
|
54
|
-
"@hyperframes/
|
|
53
|
+
"@hyperframes/core": "^0.8.169",
|
|
54
|
+
"@hyperframes/parsers": "^0.8.169"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@types/node": "^25.0.10",
|