@hyperframes/producer 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.js CHANGED
@@ -6004,7 +6004,10 @@ function stripCompilerMountBootstrap(source) {
6004
6004
  ""
6005
6005
  );
6006
6006
  }
6007
- function detectRenderModeHints(html) {
6007
+ function runtimeRoot(document2) {
6008
+ return document2.querySelector('[data-composition-id][data-root="true"]') ?? document2.querySelector("[data-composition-id]");
6009
+ }
6010
+ function detectRenderModeHints(html, pageMotionBlur) {
6008
6011
  const reasons = [];
6009
6012
  const { document: document2 } = parseHTML4(html);
6010
6013
  if (document2.querySelector("canvas[layoutsubtree]")) {
@@ -6012,6 +6015,11 @@ function detectRenderModeHints(html) {
6012
6015
  code: "htmlInCanvas",
6013
6016
  message: "Detected html-in-canvas API (layoutsubtree canvas). Chrome does not support concurrent drawElementImage across multiple workers; render is pinned to a single worker."
6014
6017
  });
6018
+ } else if (pageMotionBlur !== "off" && runtimeRoot(document2)?.getAttribute("data-motion-blur") === "on") {
6019
+ reasons.push({
6020
+ code: "captureMotionBlur",
6021
+ message: 'Detected capture motion blur (data-motion-blur="on"), which draws each frame with html-in-canvas; routed through screenshot capture mode.'
6022
+ });
6015
6023
  }
6016
6024
  if (document2.querySelector("iframe")) {
6017
6025
  reasons.push({
@@ -7043,7 +7051,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
7043
7051
  /(<(?:video|audio)\b[^>]*?)\s+preload\s*=\s*["']none["']/gi,
7044
7052
  "$1"
7045
7053
  );
7046
- const renderModeHints = detectRenderModeHints(sanitizedHtml);
7054
+ const renderModeHints = detectRenderModeHints(sanitizedHtml, options.pageMotionBlur);
7047
7055
  const hasShaderTransitions = detectShaderTransitionUsage(sanitizedHtml);
7048
7056
  const usesThreeDTransforms = detectThreeDTransformUsage(sanitizedHtml);
7049
7057
  const usesMixBlendMode = detectMixBlendModeUsage(sanitizedHtml);
@@ -7528,6 +7536,7 @@ async function runCompileStage(input) {
7528
7536
  abortSignal,
7529
7537
  variables: input.variables,
7530
7538
  urlByAbsentLocalPath: input.urlByAbsentLocalPath,
7539
+ pageMotionBlur: input.pageMotionBlur,
7531
7540
  animatedGifCacheDir: cfg.extractCacheDir ? join14(cfg.extractCacheDir, "animated-gif") : void 0,
7532
7541
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
7533
7542
  });
@@ -7824,6 +7833,7 @@ async function runProbeStage(input) {
7824
7833
  variables: job.config.variables,
7825
7834
  deviceScaleFactor,
7826
7835
  motionBlur,
7836
+ pageMotionBlur: job.config.pageMotionBlur,
7827
7837
  requiresWebGpu: compositionRequiresWebGpu(compiled.html),
7828
7838
  subTimelineWaitMemo
7829
7839
  };
@@ -11084,6 +11094,10 @@ function assertMotionBlurSupported(motionBlur, route) {
11084
11094
  `[MotionBlur] sub-frame motion blur is not supported on the "${route}" capture route: ${reason}`
11085
11095
  );
11086
11096
  }
11097
+ function pageMotionBlurForRoute(route) {
11098
+ if (route.hasHdrContent) return "hdr-render";
11099
+ return route.useLayeredComposite ? "layered-composite" : void 0;
11100
+ }
11087
11101
 
11088
11102
  // src/services/render/outputScaleRoute.ts
11089
11103
  var UNSUPPORTED_REASON2 = {
@@ -12157,7 +12171,8 @@ async function executeRenderPipeline(input) {
12157
12171
  needsAlpha,
12158
12172
  log,
12159
12173
  assertNotAborted,
12160
- variables: job.config.variables
12174
+ variables: job.config.variables,
12175
+ pageMotionBlur: job.config.pageMotionBlur
12161
12176
  })
12162
12177
  );
12163
12178
  let compiled = compileResult.compiled;
@@ -12461,14 +12476,17 @@ async function executeRenderPipeline(input) {
12461
12476
  variables: job.config.variables,
12462
12477
  deviceScaleFactor,
12463
12478
  motionBlur: job.config.motionBlur,
12479
+ pageMotionBlur: job.config.pageMotionBlur,
12464
12480
  ...videoCaptureBeyondViewport !== void 0 ? { captureBeyondViewport: videoCaptureBeyondViewport } : {}
12465
12481
  };
12466
12482
  resolvedCaptureBeyondViewport = captureOptions.captureBeyondViewport ?? resolvedCaptureBeyondViewport;
12467
12483
  if (resolvedCaptureBeyondViewport !== void 0) {
12468
12484
  updateCaptureObservability({ captureBeyondViewport: resolvedCaptureBeyondViewport });
12469
12485
  }
12486
+ let routePageMotionBlur;
12470
12487
  const buildCaptureOptions = () => ({
12471
12488
  ...captureOptions,
12489
+ ...routePageMotionBlur ? { pageMotionBlur: routePageMotionBlur } : {},
12472
12490
  videoMetadataHints,
12473
12491
  skipReadinessVideoIds: videoReadinessSkipIds,
12474
12492
  subTimelineWaitMemo,
@@ -12502,6 +12520,7 @@ async function executeRenderPipeline(input) {
12502
12520
  const nativeHdrIds = /* @__PURE__ */ new Set([...nativeHdrVideoIds, ...nativeHdrImageIds]);
12503
12521
  const hasHdrContent = Boolean(effectiveHdr && nativeHdrIds.size > 0);
12504
12522
  if (hasHdrContent) assertOutputScaleSupported(deviceScaleFactor, "hdr_layered");
12523
+ routePageMotionBlur = pageMotionBlurForRoute({ hasHdrContent, useLayeredComposite: false });
12505
12524
  const deSingleMinFramesRaw = process.env.HF_DE_SINGLE_MIN_FRAMES;
12506
12525
  const deSingleMinFramesNum = deSingleMinFramesRaw === void 0 || deSingleMinFramesRaw.trim() === "" ? 900 : Number(deSingleMinFramesRaw);
12507
12526
  const deSingleMinFrames = Number.isFinite(deSingleMinFramesNum) ? deSingleMinFramesNum : 900;
@@ -12904,6 +12923,7 @@ async function executeRenderPipeline(input) {
12904
12923
  hasShaderTransitions: compiled.hasShaderTransitions && !isGif,
12905
12924
  isPngSequence
12906
12925
  });
12926
+ routePageMotionBlur = pageMotionBlurForRoute({ hasHdrContent, useLayeredComposite });
12907
12927
  const inversionFallback = resolveInversionRetryPlan({
12908
12928
  deWorkerInversion,
12909
12929
  preInversionWorkerCount: preRoutingWorkerCount,
@@ -14100,7 +14120,8 @@ function buildSyntheticRenderJob(input) {
14100
14120
  hdrMode: input.hdrMode,
14101
14121
  strictness: input.strictness,
14102
14122
  producerConfig: input.producerConfig,
14103
- variables: input.variables
14123
+ variables: input.variables,
14124
+ pageMotionBlur: "off"
14104
14125
  };
14105
14126
  return createRenderJob(renderConfig);
14106
14127
  }
@@ -14224,6 +14245,7 @@ function buildChunkCaptureOptions(input) {
14224
14245
  skipReadinessVideoIds: input.skipReadinessVideoIds,
14225
14246
  videoMetadataHints: input.videoMetadataHints,
14226
14247
  ...videoCaptureBeyondViewport !== void 0 ? { captureBeyondViewport: videoCaptureBeyondViewport } : {},
14248
+ pageMotionBlur: "distributed-render",
14227
14249
  // A fixed warmup count keeps `beginFrameTimeTicks` host-independent.
14228
14250
  lockWarmupTicks: true,
14229
14251
  requiresWebGpu: compositionRequiresWebGpu3(input.html)
@@ -15886,7 +15908,8 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
15886
15908
  // Distributed renders must not capture host-specific system fonts —
15887
15909
  // the Lambda/worker filesystem won't have the same fonts installed.
15888
15910
  allowSystemFontCapture: false,
15889
- variables: config.variables
15911
+ variables: config.variables,
15912
+ pageMotionBlur: job.config.pageMotionBlur
15890
15913
  });
15891
15914
  logPlanStage(log, "compile", stageStarted);
15892
15915
  let compiled = compileResult.compiled;
@@ -16597,6 +16620,7 @@ function assertRequestOptionScalars(options) {
16597
16620
  }
16598
16621
  assertOptionalEnum(options, "strictness", ["strict", "best-effort"]);
16599
16622
  assertOptionalEnum(options, "hdrMode", ["auto", "force-hdr", "force-sdr"]);
16623
+ assertOptionalEnum(options, "pageMotionBlur", ["off"]);
16600
16624
  if (options.videoFrameFormat !== void 0 && !isVideoFrameFormat2(options.videoFrameFormat)) {
16601
16625
  throw new Error("Render request videoFrameFormat is invalid");
16602
16626
  }
@@ -6023,7 +6023,10 @@ function stripCompilerMountBootstrap(source) {
6023
6023
  ""
6024
6024
  );
6025
6025
  }
6026
- function detectRenderModeHints(html) {
6026
+ function runtimeRoot(document2) {
6027
+ return document2.querySelector('[data-composition-id][data-root="true"]') ?? document2.querySelector("[data-composition-id]");
6028
+ }
6029
+ function detectRenderModeHints(html, pageMotionBlur) {
6027
6030
  const reasons = [];
6028
6031
  const { document: document2 } = parseHTML4(html);
6029
6032
  if (document2.querySelector("canvas[layoutsubtree]")) {
@@ -6031,6 +6034,11 @@ function detectRenderModeHints(html) {
6031
6034
  code: "htmlInCanvas",
6032
6035
  message: "Detected html-in-canvas API (layoutsubtree canvas). Chrome does not support concurrent drawElementImage across multiple workers; render is pinned to a single worker."
6033
6036
  });
6037
+ } else if (pageMotionBlur !== "off" && runtimeRoot(document2)?.getAttribute("data-motion-blur") === "on") {
6038
+ reasons.push({
6039
+ code: "captureMotionBlur",
6040
+ message: 'Detected capture motion blur (data-motion-blur="on"), which draws each frame with html-in-canvas; routed through screenshot capture mode.'
6041
+ });
6034
6042
  }
6035
6043
  if (document2.querySelector("iframe")) {
6036
6044
  reasons.push({
@@ -7062,7 +7070,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
7062
7070
  /(<(?:video|audio)\b[^>]*?)\s+preload\s*=\s*["']none["']/gi,
7063
7071
  "$1"
7064
7072
  );
7065
- const renderModeHints = detectRenderModeHints(sanitizedHtml);
7073
+ const renderModeHints = detectRenderModeHints(sanitizedHtml, options.pageMotionBlur);
7066
7074
  const hasShaderTransitions = detectShaderTransitionUsage(sanitizedHtml);
7067
7075
  const usesThreeDTransforms = detectThreeDTransformUsage(sanitizedHtml);
7068
7076
  const usesMixBlendMode = detectMixBlendModeUsage(sanitizedHtml);
@@ -7547,6 +7555,7 @@ async function runCompileStage(input) {
7547
7555
  abortSignal,
7548
7556
  variables: input.variables,
7549
7557
  urlByAbsentLocalPath: input.urlByAbsentLocalPath,
7558
+ pageMotionBlur: input.pageMotionBlur,
7550
7559
  animatedGifCacheDir: cfg.extractCacheDir ? join14(cfg.extractCacheDir, "animated-gif") : void 0,
7551
7560
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
7552
7561
  });
@@ -7843,6 +7852,7 @@ async function runProbeStage(input) {
7843
7852
  variables: job.config.variables,
7844
7853
  deviceScaleFactor,
7845
7854
  motionBlur,
7855
+ pageMotionBlur: job.config.pageMotionBlur,
7846
7856
  requiresWebGpu: compositionRequiresWebGpu(compiled.html),
7847
7857
  subTimelineWaitMemo
7848
7858
  };
@@ -11057,6 +11067,10 @@ function assertMotionBlurSupported(motionBlur, route) {
11057
11067
  `[MotionBlur] sub-frame motion blur is not supported on the "${route}" capture route: ${reason}`
11058
11068
  );
11059
11069
  }
11070
+ function pageMotionBlurForRoute(route) {
11071
+ if (route.hasHdrContent) return "hdr-render";
11072
+ return route.useLayeredComposite ? "layered-composite" : void 0;
11073
+ }
11060
11074
 
11061
11075
  // src/services/render/outputScaleRoute.ts
11062
11076
  var UNSUPPORTED_REASON2 = {
@@ -12130,7 +12144,8 @@ async function executeRenderPipeline(input) {
12130
12144
  needsAlpha,
12131
12145
  log,
12132
12146
  assertNotAborted,
12133
- variables: job.config.variables
12147
+ variables: job.config.variables,
12148
+ pageMotionBlur: job.config.pageMotionBlur
12134
12149
  })
12135
12150
  );
12136
12151
  let compiled = compileResult.compiled;
@@ -12434,14 +12449,17 @@ async function executeRenderPipeline(input) {
12434
12449
  variables: job.config.variables,
12435
12450
  deviceScaleFactor,
12436
12451
  motionBlur: job.config.motionBlur,
12452
+ pageMotionBlur: job.config.pageMotionBlur,
12437
12453
  ...videoCaptureBeyondViewport !== void 0 ? { captureBeyondViewport: videoCaptureBeyondViewport } : {}
12438
12454
  };
12439
12455
  resolvedCaptureBeyondViewport = captureOptions.captureBeyondViewport ?? resolvedCaptureBeyondViewport;
12440
12456
  if (resolvedCaptureBeyondViewport !== void 0) {
12441
12457
  updateCaptureObservability({ captureBeyondViewport: resolvedCaptureBeyondViewport });
12442
12458
  }
12459
+ let routePageMotionBlur;
12443
12460
  const buildCaptureOptions = () => ({
12444
12461
  ...captureOptions,
12462
+ ...routePageMotionBlur ? { pageMotionBlur: routePageMotionBlur } : {},
12445
12463
  videoMetadataHints,
12446
12464
  skipReadinessVideoIds: videoReadinessSkipIds,
12447
12465
  subTimelineWaitMemo,
@@ -12475,6 +12493,7 @@ async function executeRenderPipeline(input) {
12475
12493
  const nativeHdrIds = /* @__PURE__ */ new Set([...nativeHdrVideoIds, ...nativeHdrImageIds]);
12476
12494
  const hasHdrContent = Boolean(effectiveHdr && nativeHdrIds.size > 0);
12477
12495
  if (hasHdrContent) assertOutputScaleSupported(deviceScaleFactor, "hdr_layered");
12496
+ routePageMotionBlur = pageMotionBlurForRoute({ hasHdrContent, useLayeredComposite: false });
12478
12497
  const deSingleMinFramesRaw = process.env.HF_DE_SINGLE_MIN_FRAMES;
12479
12498
  const deSingleMinFramesNum = deSingleMinFramesRaw === void 0 || deSingleMinFramesRaw.trim() === "" ? 900 : Number(deSingleMinFramesRaw);
12480
12499
  const deSingleMinFrames = Number.isFinite(deSingleMinFramesNum) ? deSingleMinFramesNum : 900;
@@ -12877,6 +12896,7 @@ async function executeRenderPipeline(input) {
12877
12896
  hasShaderTransitions: compiled.hasShaderTransitions && !isGif,
12878
12897
  isPngSequence
12879
12898
  });
12899
+ routePageMotionBlur = pageMotionBlurForRoute({ hasHdrContent, useLayeredComposite });
12880
12900
  const inversionFallback = resolveInversionRetryPlan({
12881
12901
  deWorkerInversion,
12882
12902
  preInversionWorkerCount: preRoutingWorkerCount,
@@ -14234,6 +14254,7 @@ function assertRequestOptionScalars(options) {
14234
14254
  }
14235
14255
  assertOptionalEnum(options, "strictness", ["strict", "best-effort"]);
14236
14256
  assertOptionalEnum(options, "hdrMode", ["auto", "force-hdr", "force-sdr"]);
14257
+ assertOptionalEnum(options, "pageMotionBlur", ["off"]);
14237
14258
  if (options.videoFrameFormat !== void 0 && !isVideoFrameFormat2(options.videoFrameFormat)) {
14238
14259
  throw new Error("Render request videoFrameFormat is invalid");
14239
14260
  }
@@ -38,6 +38,7 @@ export interface RenderRequestOptions {
38
38
  videoBitrate?: string;
39
39
  videoFrameFormat?: VideoFrameFormat;
40
40
  hdrMode?: RenderConfig["hdrMode"];
41
+ pageMotionBlur?: RenderConfig["pageMotionBlur"];
41
42
  variables?: Record<string, unknown>;
42
43
  outputResolution?: CanvasResolution;
43
44
  outputResolutionAspectAgnostic?: boolean;
@@ -26,7 +26,7 @@ export interface CompiledComposition {
26
26
  hasAncestorBackgroundImage: boolean;
27
27
  }
28
28
  export declare function injectSdkPositionEditsRenderScript(html: string): string;
29
- export type RenderModeHintCode = "iframe" | "requestAnimationFrame" | "htmlInCanvas";
29
+ export type RenderModeHintCode = "iframe" | "requestAnimationFrame" | "htmlInCanvas" | "captureMotionBlur";
30
30
  export interface RenderModeHint {
31
31
  code: RenderModeHintCode;
32
32
  message: string;
@@ -35,7 +35,7 @@ export interface RenderModeHints {
35
35
  recommendScreenshot: boolean;
36
36
  reasons: RenderModeHint[];
37
37
  }
38
- export declare function detectRenderModeHints(html: string): RenderModeHints;
38
+ export declare function detectRenderModeHints(html: string, pageMotionBlur?: "off"): RenderModeHints;
39
39
  export declare function detectThreeDTransformUsage(html: string): boolean;
40
40
  /**
41
41
  * Background-image signals on ancestors of the composition root.
@@ -191,6 +191,7 @@ export interface CompileForRenderOptions {
191
191
  * `window.__hfVariables` injection covers script reads, not var() in CSS.
192
192
  */
193
193
  variables?: Record<string, unknown>;
194
+ pageMotionBlur?: "off";
194
195
  }
195
196
  /**
196
197
  * Compile an HTML composition project into a single self-contained HTML string
@@ -16,7 +16,7 @@
16
16
  * requested on one. There is nothing to reject, and threading it through the frozen plan
17
17
  * is a feature rather than a fix.
18
18
  */
19
- import type { MotionBlurOptions } from "@hyperframes/engine";
19
+ import type { MotionBlurOptions, PageMotionBlurOff } from "@hyperframes/engine";
20
20
  import type { CapturePlan } from "./capturePlan.js";
21
21
  /**
22
22
  * Throw a named error when motion blur was asked for on a route that cannot deliver it.
@@ -25,3 +25,13 @@ import type { CapturePlan } from "./capturePlan.js";
25
25
  * is a render that accepts `motionBlur` and returns frames with no blur in them.
26
26
  */
27
27
  export declare function assertMotionBlurSupported(motionBlur: MotionBlurOptions | undefined, route: CapturePlan["kind"]): void;
28
+ /**
29
+ * Why the page's own capture blur (`data-motion-blur="on"`) must stand down on this route, or
30
+ * undefined when it can run. The layered compositor screenshots each DOM layer after hiding the
31
+ * others; blur draws the whole composition into a canvas that those hides do not reach, so a
32
+ * layer comes out wrong (HDR: backgrounds and overlays lost).
33
+ */
34
+ export declare function pageMotionBlurForRoute(route: {
35
+ hasHdrContent: boolean;
36
+ useLayeredComposite: boolean;
37
+ }): PageMotionBlurOff | undefined;
@@ -80,6 +80,8 @@ export interface CompileStageInput {
80
80
  variables?: Record<string, unknown>;
81
81
  /** Absolute project paths not on disk, readable at these URLs instead. */
82
82
  urlByAbsentLocalPath?: ReadonlyMap<string, string>;
83
+ /** See RenderConfig.pageMotionBlur; decides whether the page renders through screenshots. */
84
+ pageMotionBlur?: "off";
83
85
  }
84
86
  export interface CompileStageResult {
85
87
  compiled: CompiledComposition;
@@ -94,6 +94,7 @@ export interface RenderConfig {
94
94
  * and JPEG samples would be averaged after a lossy quantization.
95
95
  */
96
96
  motionBlur?: MotionBlurOptions;
97
+ pageMotionBlur?: "off";
97
98
  workers?: number;
98
99
  useGpu?: boolean;
99
100
  debug?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/producer",
3
- "version": "0.8.185",
3
+ "version": "0.8.186",
4
4
  "description": "HTML-to-video rendering engine using Chrome's BeginFrame API",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,11 +35,11 @@
35
35
  "postcss": "^8.4.0",
36
36
  "puppeteer": "^25.2.1",
37
37
  "puppeteer-core": "^25.2.1",
38
- "@hyperframes/lint": "0.8.185",
39
- "@hyperframes/parsers": "0.8.185",
40
- "@hyperframes/studio-server": "0.8.185",
41
- "@hyperframes/engine": "0.8.185",
42
- "@hyperframes/core": "0.8.185"
38
+ "@hyperframes/engine": "0.8.186",
39
+ "@hyperframes/lint": "0.8.186",
40
+ "@hyperframes/parsers": "0.8.186",
41
+ "@hyperframes/core": "0.8.186",
42
+ "@hyperframes/studio-server": "0.8.186"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^25.0.10",
@@ -48,7 +48,7 @@
48
48
  "tsx": "^4.21.0",
49
49
  "typescript": "^5.7.2",
50
50
  "vitest": "^4.1.11",
51
- "@hyperframes/aws-lambda": "0.8.185"
51
+ "@hyperframes/aws-lambda": "0.8.186"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=22"