@hyperframes/engine 0.8.173 → 0.8.174

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.
@@ -9,7 +9,7 @@ import { closeSync, copyFileSync, existsSync, ftruncateSync, mkdirSync, mkdtempS
9
9
  import { join, dirname, extname } from "path";
10
10
  import { DEFAULT_CONFIG } from "../config.js";
11
11
  import { getCachedGpuEncoder, getGpuEncoderName, buildVideoToolboxRateControlArgs, mapPresetForGpuEncoder, } from "../utils/gpuEncoder.js";
12
- import { getHdrEncoderColorParams } from "../utils/hdr.js";
12
+ import { getHdrEncoderColorParams, hdrFrameTagsFilter } from "../utils/hdr.js";
13
13
  import { withEvenDimensionPad } from "../utils/evenDimensions.js";
14
14
  import { SDR_CAPTURE_TO_BT709_FILTER, SDR_RGB_TO_TAGGED_BT709_FILTER, } from "../utils/sdrCaptureColor.js";
15
15
  import { describeFfmpegFailure, ffmpegStatsReader, formatFfmpegError, isExternalFfmpegInterruption, runFfmpeg, } from "../utils/runFfmpeg.js";
@@ -325,8 +325,9 @@ export function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = nu
325
325
  }
326
326
  // Range conversion: Chrome's full-range capture → limited/TV range; SDR also
327
327
  // converts to the BT.709 matrix it is tagged with.
328
+ const hdrTags = options.hdr ? hdrFrameTagsFilter(options.hdr.transfer) : undefined;
328
329
  const sdrFilter = options.hdr ? undefined : SDR_CAPTURE_TO_BT709_FILTER;
329
- const captureFilter = sdrFilter ?? "scale=in_range=pc:out_range=tv";
330
+ const captureFilter = sdrFilter ?? `scale=in_range=pc:out_range=tv,${hdrTags}`;
330
331
  if (gpuEncoder === "vaapi") {
331
332
  // vaapi already runs `format=nv12,hwupload`; the nv12 conversion aligns
332
333
  // odd dimensions before upload, so only prepend the colour conversion.
@@ -340,7 +341,7 @@ export function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = nu
340
341
  // encoder. They hit the same "height not divisible by 2" abort as
341
342
  // libx264 on an odd-sized 4:2:0 canvas, so pad odd dimensions up to even
342
343
  // on the software side before the encode.
343
- const vf = withEvenDimensionPad(sdrFilter ?? "", pixelFormat, options.width, options.height);
344
+ const vf = withEvenDimensionPad(sdrFilter ?? hdrTags ?? "", pixelFormat, options.width, options.height);
344
345
  if (vf)
345
346
  args.push("-vf", vf);
346
347
  }
@@ -130,6 +130,7 @@ export interface CaptureSession {
130
130
  beginFrameIntervalMs: number;
131
131
  beginFrameHasDamageCount: number;
132
132
  beginFrameNoDamageCount: number;
133
+ beginFrameSeekPresented?: boolean;
133
134
  /** Optional producer config — when set, overrides module-level env var constants. */
134
135
  config?: Partial<EngineConfig>;
135
136
  /** True if running on SwiftShader (detected at init). Undefined before init. */
@@ -2971,6 +2971,17 @@ async function captureFrameSurface(session, frameIndex, time, seekOptions, heldV
2971
2971
  let screenshotBuffer;
2972
2972
  if (session.captureMode === "beginframe") {
2973
2973
  const frameTimeTicks = session.beginFrameTimeTicks + frameIndex * session.beginFrameIntervalMs;
2974
+ if (!session.beginFrameSeekPresented) {
2975
+ // A session's first capture can return the frame composited during init instead of
2976
+ // this seek, so display the seek once at a tick between the probe and this capture.
2977
+ const client = await getCdpSession(page);
2978
+ await client.send("HeadlessExperimental.beginFrame", {
2979
+ frameTimeTicks: frameTimeTicks - session.beginFrameIntervalMs / 2,
2980
+ interval: session.beginFrameIntervalMs,
2981
+ noDisplayUpdates: false,
2982
+ });
2983
+ session.beginFrameSeekPresented = true;
2984
+ }
2974
2985
  const result = await beginFrameCapture(page, options, frameTimeTicks, session.beginFrameIntervalMs);
2975
2986
  if (result.hasDamage)
2976
2987
  session.beginFrameHasDamageCount++;
@@ -22,9 +22,9 @@ import { dirname } from "path";
22
22
  import { getCachedGpuEncoder, getGpuEncoderName, buildVideoToolboxRateControlArgs, mapPresetForGpuEncoder, } from "../utils/gpuEncoder.js";
23
23
  import { ffmpegStatsReader, formatFfmpegError, isExternalFfmpegInterruption, } from "../utils/runFfmpeg.js";
24
24
  import { getFfmpegBinary } from "../utils/ffmpegBinaries.js";
25
- import { getHdrEncoderColorParams } from "../utils/hdr.js";
25
+ import { getHdrEncoderColorParams, hdrFrameTagsFilter } from "../utils/hdr.js";
26
26
  import { withEvenDimensionPad } from "../utils/evenDimensions.js";
27
- import { SDR_CAPTURE_TO_BT709_FILTER, SDR_RGB_TO_BT709_FILTER, SDR_RGB_TO_TAGGED_BT709_FILTER, } from "../utils/sdrCaptureColor.js";
27
+ import { SDR_CAPTURE_TO_BT709_FILTER, SDR_RGB_TO_TAGGED_BT709_FILTER, } from "../utils/sdrCaptureColor.js";
28
28
  import { DEFAULT_CONFIG } from "../config.js";
29
29
  import { fpsToFfmpegArg, fpsToNumber } from "@hyperframes/core";
30
30
  import { appendVp9CpuUsedArg } from "./vp9Options.js";
@@ -248,26 +248,25 @@ export function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
248
248
  else {
249
249
  args.push("-colorspace:v", "bt709", "-color_primaries:v", "bt709", "-color_trc:v", "bt709", "-color_range", "tv");
250
250
  }
251
- // Raw HDR input (from WebGPU pipeline) is already PQ-encoded, so only SDR gets a
252
- // conversion: raw sRGB or Chrome's BT.601 JPEG screenshots to BT.709 limited range.
253
- const sdrFilter = options.rawInputFormat
254
- ? SDR_RGB_TO_BT709_FILTER
255
- : SDR_CAPTURE_TO_BT709_FILTER;
256
- if (options.rawInputFormat && options.hdr) {
257
- // No filter needed — PQ data goes straight to encoder
258
- }
259
- else if (gpuEncoder === "vaapi") {
251
+ // Raw HDR input (from WebGPU pipeline) is already PQ-encoded and is only tagged; SDR is
252
+ // converted: raw sRGB or Chrome's BT.601 JPEG screenshots to tagged BT.709 limited range.
253
+ const frameFilter = options.rawInputFormat && options.hdr
254
+ ? hdrFrameTagsFilter(options.hdr.transfer)
255
+ : options.rawInputFormat
256
+ ? SDR_RGB_TO_TAGGED_BT709_FILTER
257
+ : SDR_CAPTURE_TO_BT709_FILTER;
258
+ if (gpuEncoder === "vaapi") {
260
259
  // vaapi already runs `format=nv12,hwupload`; the nv12 conversion aligns
261
260
  // odd dimensions before upload, so only prepend the colour conversion.
262
261
  const vfIdx = args.indexOf("-vf");
263
262
  if (vfIdx !== -1) {
264
- args[vfIdx + 1] = `${sdrFilter},${args[vfIdx + 1]}`;
263
+ args[vfIdx + 1] = `${frameFilter},${args[vfIdx + 1]}`;
265
264
  }
266
265
  }
267
266
  else {
268
267
  // Pad odd dimensions up to even so 4:2:0 encoders (software and
269
268
  // nvenc/videotoolbox/qsv/amf) don't abort with "height not divisible by 2".
270
- args.push("-vf", withEvenDimensionPad(sdrFilter, pixelFormat, options.width, options.height));
269
+ args.push("-vf", withEvenDimensionPad(frameFilter, pixelFormat, options.width, options.height));
271
270
  }
272
271
  // Fixed timescale for consistent A/V timing across platforms.
273
272
  args.push("-video_track_timescale", "90000");
@@ -70,6 +70,7 @@ export interface HdrEncoderColorParams {
70
70
  * stream is rejected as SDR by most HDR-aware players and CDNs.
71
71
  */
72
72
  export declare function getHdrEncoderColorParams(transfer: HdrTransfer, mastering?: HdrMasteringMetadata): HdrEncoderColorParams;
73
+ export declare function hdrFrameTagsFilter(transfer: HdrTransfer): string;
73
74
  export interface CompositionHdrInfo {
74
75
  hasHdr: boolean;
75
76
  dominantTransfer: HdrTransfer | null;
package/dist/utils/hdr.js CHANGED
@@ -64,6 +64,10 @@ export function getHdrEncoderColorParams(transfer, mastering = DEFAULT_HDR10_MAS
64
64
  mastering,
65
65
  };
66
66
  }
67
+ export function hdrFrameTagsFilter(transfer) {
68
+ const { colorPrimaries, colorTrc, colorspace } = getHdrEncoderColorParams(transfer);
69
+ return `setparams=color_primaries=${colorPrimaries}:color_trc=${colorTrc}:colorspace=${colorspace}:range=tv`;
70
+ }
67
71
  export const HDR_AUTO_PROMOTION_PIPELINE = {
68
72
  colorSpace: "BT.2020",
69
73
  codec: "HEVC Main10",
@@ -1,3 +1,3 @@
1
1
  export declare const SDR_RGB_TO_BT709_FILTER = "scale=out_color_matrix=bt709:out_range=tv:flags=neighbor";
2
- export declare const SDR_CAPTURE_TO_BT709_FILTER = "scale=in_color_matrix=bt601:in_range=pc:flags=neighbor+accurate_rnd,format=gbrp,scale=out_color_matrix=bt709:out_range=tv:flags=neighbor";
3
2
  export declare const SDR_RGB_TO_TAGGED_BT709_FILTER = "scale=out_color_matrix=bt709:out_range=tv:flags=neighbor,setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
3
+ export declare const SDR_CAPTURE_TO_BT709_FILTER = "scale=in_color_matrix=bt601:in_range=pc:flags=neighbor+accurate_rnd,format=gbrp,scale=out_color_matrix=bt709:out_range=tv:flags=neighbor,setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
@@ -1,3 +1,3 @@
1
1
  export const SDR_RGB_TO_BT709_FILTER = "scale=out_color_matrix=bt709:out_range=tv:flags=neighbor";
2
- export const SDR_CAPTURE_TO_BT709_FILTER = `scale=in_color_matrix=bt601:in_range=pc:flags=neighbor+accurate_rnd,format=gbrp,${SDR_RGB_TO_BT709_FILTER}`;
3
2
  export const SDR_RGB_TO_TAGGED_BT709_FILTER = `${SDR_RGB_TO_BT709_FILTER},setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv`;
3
+ export const SDR_CAPTURE_TO_BT709_FILTER = `scale=in_color_matrix=bt601:in_range=pc:flags=neighbor+accurate_rnd,format=gbrp,${SDR_RGB_TO_TAGGED_BT709_FILTER}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/engine",
3
- "version": "0.8.173",
3
+ "version": "0.8.174",
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/parsers": "^0.8.173",
54
- "@hyperframes/core": "^0.8.173"
53
+ "@hyperframes/core": "^0.8.174",
54
+ "@hyperframes/parsers": "^0.8.174"
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/node": "^25.0.10",