@hyperframes/engine 0.8.185 → 0.8.186

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 CHANGED
@@ -58,6 +58,7 @@ export { createFileServer, type FileServerOptions, type FileServerHandle, } from
58
58
  export { quantizeTimeToFrame, MEDIA_VISUAL_STYLE_PROPERTIES } from "@hyperframes/core";
59
59
  export { frameFileExtension } from "./services/frameCapture.js";
60
60
  export type { MotionBlurOptions, MotionBlurBlendSpace } from "./services/motionBlur.js";
61
+ export type { PageMotionBlurOff } from "./types.js";
61
62
  export { assertSwiftShader, readWebGlVendorInfo, SwiftShaderAssertionError, BROWSER_GPU_NOT_SOFTWARE, } from "./utils/assertSwiftShader.js";
62
63
  export { readWebGlVendorInfoFromCanvas } from "./utils/readWebGlVendorInfoFromCanvas.js";
63
64
  export { extractMediaMetadata, extractVideoMetadata, extractFinalVideoFrameTimestamp, extractAudioMetadata, probeMediaProfile, analyzeKeyframeIntervals, type VideoMetadata, type AudioMetadata, type MediaProbeProfile, type KeyframeAnalysis, } from "./utils/ffprobe.js";
@@ -13,6 +13,7 @@ import { COMPOSITION_SOURCE_URL, quantizeSeekTime, quantizeTimeToFrame, fpsToNum
13
13
  import { DrawElementCaptureError } from "./drawElementCaptureError.js";
14
14
  import { encodePng } from "../utils/alphaBlit.js";
15
15
  import { DEFAULT_SAMPLES_PER_FRAME, MotionBlurAccumulator, SPATIAL_TWEEN_PROPERTIES, adaptiveSampleCount, motionBlurProbeTimes, motionBlurSampleTimes, motionBlurWindowIsStatic, probeDiffMagnitude, resolveMotionBlurPlan, } from "./motionBlur.js";
16
+ import { describePageMotionBlur, describePageMotionBlurAtEnd, installPageMotionBlur, } from "./pageMotionBlur.js";
16
17
  // ── Extracted modules ───────────────────────────────────────────────────────
17
18
  import { acquireBrowser, releaseBrowser, forceReleaseBrowser, buildChromeArgs, resolveBrowserGpuMode, captureWithoutWaylandDisplay, resolveHeadlessShellPath, compositionRequiresWebGpu, assertWebGpuAdapterAvailable, usesSoftwareWebGpu, } from "./browserManager.js";
18
19
  import { beginFrameCapture, ensureRenderFrameSiblings, getCdpSession, pageContentExceedsCaptureHeight, pageScreenshotCapture, initTransparentBackground, shouldDefaultCaptureBeyondViewport, } from "./screenshotService.js";
@@ -946,6 +947,7 @@ async function constructCaptureSession(input) {
946
947
  }
947
948
  }, variablesJson);
948
949
  }
950
+ await installPageMotionBlur(page, options);
949
951
  const browserVersion = await browser.version();
950
952
  const sessionOptions = resolveCaptureSessionOptions(options, browserVersion);
951
953
  const expectedMajor = config?.expectedChromiumMajor;
@@ -2462,6 +2464,7 @@ export async function createStaticVerificationPage(session) {
2462
2464
  JSON.parse(json);
2463
2465
  }, variablesJson);
2464
2466
  }
2467
+ await installPageMotionBlur(page, session.options);
2465
2468
  await page.setViewport({
2466
2469
  width: session.options.width,
2467
2470
  height: session.options.height,
@@ -3192,6 +3195,11 @@ async function captureFrameCore(session, frameIndex, time) {
3192
3195
  session.capturePerf.screenshotMs += screenshotMs;
3193
3196
  session.capturePerf.totalMs += captureTimeMs;
3194
3197
  session.capturePerf.frameMs.push(captureTimeMs);
3198
+ if (session.capturePerf.frames === 1) {
3199
+ const blur = await describePageMotionBlur(session.page).catch(() => null);
3200
+ if (blur)
3201
+ console.log(blur);
3202
+ }
3195
3203
  // Retain this freshly-captured buffer so the following static frames can reuse it.
3196
3204
  if (session.staticFrames) {
3197
3205
  session.lastFrameBuffer = buffer;
@@ -3527,6 +3535,11 @@ export async function closeCaptureSession(session) {
3527
3535
  // point up to `intervalMs` earlier. Best effort; the page may be gone.
3528
3536
  await session.chromeMemory.sampleOnce();
3529
3537
  }
3538
+ if (session.page && !session.pageReleased) {
3539
+ const blur = await describePageMotionBlurAtEnd(session.page);
3540
+ if (blur)
3541
+ console.log(blur);
3542
+ }
3530
3543
  // Realized static-dedup telemetry: how much the cache actually helped this
3531
3544
  // render (vs the prediction logged at arm time). Both capture paths
3532
3545
  // (sequential orchestrator + parallel workers) close their session here, so
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Tells the page's capture motion blur (`data-motion-blur="on"`) to stand down, before any of
3
+ * its scripts run: when the engine blurs (it would average frames that are already blurred) or
4
+ * when the render must match a Studio preview that could not blur (`pageMotionBlur: "off"`).
5
+ */
6
+ import type { Page } from "puppeteer-core";
7
+ import type { CaptureOptions } from "../types.js";
8
+ export declare function installPageMotionBlur(page: Pick<Page, "evaluateOnNewDocument">, options: Pick<CaptureOptions, "motionBlur" | "pageMotionBlur" | "deviceScaleFactor">): Promise<void>;
9
+ /** One line saying what the page's blur did for this session; null when the page never asked for blur. */
10
+ export declare function describePageMotionBlur(page: Pick<Page, "evaluate">): Promise<string | null>;
11
+ /** The session's closing line, only when frames fell back to plain DOM: the first-frame line cannot know. */
12
+ export declare function describePageMotionBlurAtEnd(page: Pick<Page, "evaluate">, timeoutMs?: number): Promise<string | null>;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Tells the page's capture motion blur (`data-motion-blur="on"`) to stand down, before any of
3
+ * its scripts run: when the engine blurs (it would average frames that are already blurred) or
4
+ * when the render must match a Studio preview that could not blur (`pageMotionBlur: "off"`).
5
+ */
6
+ import { PAGE_DEVICE_SCALE_GLOBAL, PAGE_MOTION_BLUR_OFF_GLOBAL, acceleratedCanvasTrackerSource, } from "@hyperframes/core";
7
+ function pageOffReason(options) {
8
+ if (options.motionBlur)
9
+ return "engine-motion-blur";
10
+ if (options.pageMotionBlur === "off")
11
+ return "preview-unsupported";
12
+ return options.pageMotionBlur ?? null;
13
+ }
14
+ export async function installPageMotionBlur(page, options) {
15
+ await page.evaluateOnNewDocument(acceleratedCanvasTrackerSource());
16
+ const scale = options.deviceScaleFactor ?? 1;
17
+ if (scale > 1) {
18
+ await page.evaluateOnNewDocument((key, v) => {
19
+ window[key] = v;
20
+ }, PAGE_DEVICE_SCALE_GLOBAL, scale);
21
+ }
22
+ const off = pageOffReason(options);
23
+ if (!off)
24
+ return;
25
+ await page.evaluateOnNewDocument((key, v) => {
26
+ window[key] = v;
27
+ }, PAGE_MOTION_BLUR_OFF_GLOBAL, off);
28
+ }
29
+ async function readPageMotionBlur(page) {
30
+ return page.evaluate(() => {
31
+ const api = Reflect.get(window, "__hfMotionBlur");
32
+ const report = api && typeof api === "object" ? Reflect.get(api, "report") : null;
33
+ const got = typeof report === "function" ? report() : null;
34
+ if (!got || typeof got !== "object")
35
+ return null;
36
+ return {
37
+ state: String(Reflect.get(got, "state")),
38
+ code: String(Reflect.get(got, "reasonCode")),
39
+ movers: Number(Reflect.get(got, "movers")),
40
+ failedFrames: Number(Reflect.get(got, "failedFrames")) || 0,
41
+ };
42
+ });
43
+ }
44
+ const formatPageMotionBlur = (r) => `[motion-blur] ${r.state} reasonCode=${r.code} movers=${r.movers} failedFrames=${r.failedFrames}`;
45
+ /** One line saying what the page's blur did for this session; null when the page never asked for blur. */
46
+ export async function describePageMotionBlur(page) {
47
+ const r = await readPageMotionBlur(page);
48
+ return r && formatPageMotionBlur(r);
49
+ }
50
+ /** The session's closing line, only when frames fell back to plain DOM: the first-frame line cannot know. */
51
+ export async function describePageMotionBlurAtEnd(page, timeoutMs = 1500) {
52
+ let timer;
53
+ const stuck = new Promise((resolve) => {
54
+ timer = setTimeout(() => resolve(null), timeoutMs);
55
+ });
56
+ try {
57
+ const r = await Promise.race([readPageMotionBlur(page), stuck]);
58
+ return r && r.failedFrames > 0 ? `${formatPageMotionBlur(r)} (session end)` : null;
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ finally {
64
+ clearTimeout(timer);
65
+ }
66
+ }
package/dist/types.d.ts CHANGED
@@ -8,6 +8,7 @@ import type { AudioFailureDiagnostic } from "./services/audioMixer.types.js";
8
8
  import type { Fps } from "@hyperframes/core";
9
9
  import type { ChromeMemoryStats } from "./services/chromeMemorySampler.js";
10
10
  import type { MotionBlurOptions } from "./services/motionBlur.js";
11
+ export type PageMotionBlurOff = "off" | "hdr-render" | "layered-composite" | "distributed-render";
11
12
  /**
12
13
  * Outcome of waiting for a sub-composition's GSAP timelines to register.
13
14
  * Threaded string-typed through `CapturePerfSummary` / `RenderPerfSummary` /
@@ -151,6 +152,8 @@ export interface CaptureOptions {
151
152
  * screenshot capture mode; see `services/motionBlur.ts`.
152
153
  */
153
154
  motionBlur?: MotionBlurOptions;
155
+ /** Why the page's capture motion blur stands down: the preview could not blur, or the route cannot host it. */
156
+ pageMotionBlur?: PageMotionBlurOff;
154
157
  deviceScaleFactor?: number;
155
158
  /**
156
159
  * Opt into Chrome's capture-beyond-viewport screenshot path. Leave undefined
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/engine",
3
- "version": "0.8.185",
3
+ "version": "0.8.186",
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/core": "^0.8.185",
54
- "@hyperframes/parsers": "^0.8.185"
53
+ "@hyperframes/core": "^0.8.186",
54
+ "@hyperframes/parsers": "^0.8.186"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/node": "^25.0.10",