@hyperframes/producer 0.4.15 → 0.4.17

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
@@ -57865,7 +57865,7 @@ var require_util2 = __commonJS({
57865
57865
  }
57866
57866
  path12 = url.path;
57867
57867
  }
57868
- var isAbsolute3 = exports.isAbsolute(path12);
57868
+ var isAbsolute5 = exports.isAbsolute(path12);
57869
57869
  var parts = path12.split(/\/+/);
57870
57870
  for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
57871
57871
  part = parts[i];
@@ -57885,7 +57885,7 @@ var require_util2 = __commonJS({
57885
57885
  }
57886
57886
  path12 = parts.join("/");
57887
57887
  if (path12 === "") {
57888
- path12 = isAbsolute3 ? "/" : ".";
57888
+ path12 = isAbsolute5 ? "/" : ".";
57889
57889
  }
57890
57890
  if (url) {
57891
57891
  url.path = path12;
@@ -101618,6 +101618,34 @@ function getGpuEncoderName(encoder, codec) {
101618
101618
  return codec === "h264" ? "libx264" : "libx265";
101619
101619
  }
101620
101620
  }
101621
+ var NVENC_PRESET_MAP = {
101622
+ ultrafast: "p1",
101623
+ superfast: "p1",
101624
+ veryfast: "p2",
101625
+ faster: "p3",
101626
+ fast: "p4",
101627
+ medium: "p4",
101628
+ slow: "p5",
101629
+ slower: "p6",
101630
+ veryslow: "p7",
101631
+ placebo: "p7"
101632
+ };
101633
+ var QSV_PRESET_MAP = {
101634
+ ultrafast: "veryfast",
101635
+ superfast: "veryfast",
101636
+ placebo: "veryslow"
101637
+ };
101638
+ function mapPresetForGpuEncoder(encoder, preset) {
101639
+ switch (encoder) {
101640
+ case "nvenc":
101641
+ if (/^p[1-7]$/.test(preset)) return preset;
101642
+ return NVENC_PRESET_MAP[preset] ?? "p4";
101643
+ case "qsv":
101644
+ return QSV_PRESET_MAP[preset] ?? preset;
101645
+ default:
101646
+ return preset;
101647
+ }
101648
+ }
101621
101649
 
101622
101650
  // ../engine/src/utils/hdr.ts
101623
101651
  function isHdrColorSpace(cs) {
@@ -101661,6 +101689,16 @@ function analyzeCompositionHdr(colorSpaces) {
101661
101689
  // ../engine/src/utils/runFfmpeg.ts
101662
101690
  import { spawn as spawn4 } from "child_process";
101663
101691
  var DEFAULT_TIMEOUT2 = 3e5;
101692
+ var DEFAULT_STDERR_TAIL_LINES = 15;
101693
+ function formatFfmpegError(exitCode, stderr, tailLines = DEFAULT_STDERR_TAIL_LINES) {
101694
+ const tail = (stderr ?? "").split(/\r?\n/).filter((line) => line.length > 0).slice(-tailLines).join("\n");
101695
+ if (exitCode === null) {
101696
+ return tail ? `[FFmpeg] ${tail}` : "[FFmpeg] process error";
101697
+ }
101698
+ return tail ? `FFmpeg exited with code ${exitCode}
101699
+ ffmpeg stderr (tail):
101700
+ ${tail}` : `FFmpeg exited with code ${exitCode}`;
101701
+ }
101664
101702
  async function runFfmpeg(args, opts) {
101665
101703
  const startMs = Date.now();
101666
101704
  const signal = opts?.signal;
@@ -101771,7 +101809,7 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
101771
101809
  args.push("-c:v", encoderName);
101772
101810
  switch (gpuEncoder) {
101773
101811
  case "nvenc":
101774
- args.push("-preset", preset);
101812
+ args.push("-preset", mapPresetForGpuEncoder("nvenc", preset));
101775
101813
  if (bitrate) args.push("-b:v", bitrate);
101776
101814
  else args.push("-cq", String(quality));
101777
101815
  break;
@@ -101790,7 +101828,7 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
101790
101828
  else args.push("-qp", String(quality));
101791
101829
  break;
101792
101830
  case "qsv":
101793
- args.push("-preset", preset);
101831
+ args.push("-preset", mapPresetForGpuEncoder("qsv", preset));
101794
101832
  if (bitrate) args.push("-b:v", bitrate);
101795
101833
  else args.push("-global_quality", String(quality));
101796
101834
  break;
@@ -101930,7 +101968,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
101930
101968
  durationMs,
101931
101969
  framesEncoded: 0,
101932
101970
  fileSize: 0,
101933
- error: `FFmpeg exited with code ${code}`
101971
+ error: formatFfmpegError(code, stderr)
101934
101972
  });
101935
101973
  return;
101936
101974
  }
@@ -102100,7 +102138,7 @@ async function muxVideoWithAudio(videoPath, audioPath, outputPath, signal, confi
102100
102138
  success: result.success,
102101
102139
  outputPath,
102102
102140
  durationMs: result.durationMs,
102103
- error: !result.success ? result.exitCode !== null ? `FFmpeg exited with code ${result.exitCode}` : `[FFmpeg] ${result.stderr}` : void 0
102141
+ error: !result.success ? formatFfmpegError(result.exitCode, result.stderr) : void 0
102104
102142
  };
102105
102143
  }
102106
102144
  async function applyFaststart(inputPath, outputPath, signal, config2) {
@@ -102123,7 +102161,7 @@ async function applyFaststart(inputPath, outputPath, signal, config2) {
102123
102161
  success: result.success,
102124
102162
  outputPath,
102125
102163
  durationMs: result.durationMs,
102126
- error: !result.success ? result.exitCode !== null ? `FFmpeg exited with code ${result.exitCode}` : `[FFmpeg] ${result.stderr}` : void 0
102164
+ error: !result.success ? formatFfmpegError(result.exitCode, result.stderr) : void 0
102127
102165
  };
102128
102166
  }
102129
102167
 
@@ -102216,7 +102254,7 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
102216
102254
  args.push("-c:v", encoderName);
102217
102255
  switch (gpuEncoder) {
102218
102256
  case "nvenc":
102219
- args.push("-preset", preset);
102257
+ args.push("-preset", mapPresetForGpuEncoder("nvenc", preset));
102220
102258
  if (bitrate) args.push("-b:v", bitrate);
102221
102259
  else args.push("-cq", String(quality));
102222
102260
  break;
@@ -102235,7 +102273,7 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
102235
102273
  else args.push("-qp", String(quality));
102236
102274
  break;
102237
102275
  case "qsv":
102238
- args.push("-preset", preset);
102276
+ args.push("-preset", mapPresetForGpuEncoder("qsv", preset));
102239
102277
  if (bitrate) args.push("-b:v", bitrate);
102240
102278
  else args.push("-global_quality", String(quality));
102241
102279
  break;
@@ -102391,7 +102429,7 @@ Process error: ${err.message}`;
102391
102429
  success: false,
102392
102430
  durationMs,
102393
102431
  fileSize: 0,
102394
- error: `FFmpeg exited with code ${exitCode}`
102432
+ error: formatFfmpegError(exitCode, stderr)
102395
102433
  };
102396
102434
  }
102397
102435
  const fileSize = existsSync6(outputPath) ? statSync4(outputPath).size : 0;
@@ -102405,7 +102443,7 @@ Process error: ${err.message}`;
102405
102443
  // ../engine/src/services/videoFrameExtractor.ts
102406
102444
  import { spawn as spawn8 } from "child_process";
102407
102445
  import { existsSync as existsSync8, mkdirSync as mkdirSync5, readdirSync as readdirSync4, rmSync } from "fs";
102408
- import { join as join8 } from "path";
102446
+ import { isAbsolute as isAbsolute2, join as join8 } from "path";
102409
102447
 
102410
102448
  // ../engine/src/utils/ffprobe.ts
102411
102449
  import { spawn as spawn7 } from "child_process";
@@ -102520,7 +102558,7 @@ function parseFrameRate(frameRateStr) {
102520
102558
  }
102521
102559
  return parseFloat(frameRateStr) || 0;
102522
102560
  }
102523
- async function extractVideoMetadata(filePath) {
102561
+ async function extractMediaMetadata(filePath) {
102524
102562
  const cached = videoMetadataCache.get(filePath);
102525
102563
  if (cached) return cached;
102526
102564
  const probePromise = (async () => {
@@ -102806,7 +102844,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
102806
102844
  const { fps, outputDir, quality = 95, format: format3 = "jpg" } = options;
102807
102845
  const videoOutputDir = join8(outputDir, videoId);
102808
102846
  if (!existsSync8(videoOutputDir)) mkdirSync5(videoOutputDir, { recursive: true });
102809
- const metadata = await extractVideoMetadata(videoPath);
102847
+ const metadata = await extractMediaMetadata(videoPath);
102810
102848
  const framePattern = `frame_%05d.${format3}`;
102811
102849
  const outputPattern = join8(videoOutputDir, framePattern);
102812
102850
  const isHdr = isHdrColorSpace(metadata.colorSpace);
@@ -102955,7 +102993,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
102955
102993
  if (signal?.aborted) break;
102956
102994
  try {
102957
102995
  let videoPath = video.src;
102958
- if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
102996
+ if (!isAbsolute2(videoPath) && !isHttpUrl(videoPath)) {
102959
102997
  const fromCompiled = compiledDir ? join8(compiledDir, videoPath) : null;
102960
102998
  videoPath = fromCompiled && existsSync8(fromCompiled) ? fromCompiled : join8(baseDir, videoPath);
102961
102999
  }
@@ -102975,7 +103013,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
102975
103013
  }
102976
103014
  const videoColorSpaces = await Promise.all(
102977
103015
  resolvedVideos.map(async ({ videoPath }) => {
102978
- const metadata = await extractVideoMetadata(videoPath);
103016
+ const metadata = await extractMediaMetadata(videoPath);
102979
103017
  return metadata.colorSpace;
102980
103018
  })
102981
103019
  );
@@ -103008,7 +103046,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
103008
103046
  if (signal?.aborted) break;
103009
103047
  const entry = resolvedVideos[i];
103010
103048
  if (!entry) continue;
103011
- const metadata = await extractVideoMetadata(entry.videoPath);
103049
+ const metadata = await extractMediaMetadata(entry.videoPath);
103012
103050
  if (!metadata.isVFR) continue;
103013
103051
  let segDuration = entry.video.end - entry.video.start;
103014
103052
  if (!Number.isFinite(segDuration) || segDuration <= 0) {
@@ -103044,7 +103082,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
103044
103082
  try {
103045
103083
  let videoDuration = video.end - video.start;
103046
103084
  if (!Number.isFinite(videoDuration) || videoDuration <= 0) {
103047
- const metadata = await extractVideoMetadata(videoPath);
103085
+ const metadata = await extractMediaMetadata(videoPath);
103048
103086
  const sourceDuration = metadata.durationSeconds - video.mediaStart;
103049
103087
  videoDuration = sourceDuration > 0 ? sourceDuration : metadata.durationSeconds;
103050
103088
  video.end = video.start + videoDuration;
@@ -103419,7 +103457,7 @@ async function queryElementStacking(page, nativeHdrIds) {
103419
103457
 
103420
103458
  // ../engine/src/services/audioMixer.ts
103421
103459
  import { existsSync as existsSync9, mkdirSync as mkdirSync6, rmSync as rmSync2 } from "fs";
103422
- import { join as join9, dirname as dirname7 } from "path";
103460
+ import { isAbsolute as isAbsolute3, join as join9, dirname as dirname7 } from "path";
103423
103461
  function parseAudioElements(html) {
103424
103462
  const elements = [];
103425
103463
  const { document: document2 } = parseHTML(html);
@@ -103646,7 +103684,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
103646
103684
  }
103647
103685
  try {
103648
103686
  let srcPath = element.src;
103649
- if (!srcPath.startsWith("/") && !isHttpUrl(srcPath)) {
103687
+ if (!isAbsolute3(srcPath) && !isHttpUrl(srcPath)) {
103650
103688
  const fromCompiled = compiledDir ? join9(compiledDir, srcPath) : null;
103651
103689
  srcPath = fromCompiled && existsSync9(fromCompiled) ? fromCompiled : join9(baseDir, srcPath);
103652
103690
  }
@@ -106996,13 +107034,51 @@ function normalizeObjectFit(value) {
106996
107034
  }
106997
107035
  function parseTransformMatrix(css) {
106998
107036
  if (!css || css === "none") return null;
106999
- const match2 = css.match(
107037
+ const match2d = css.match(
107000
107038
  /^matrix\(\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^,)]+)\s*\)$/
107001
107039
  );
107002
- if (!match2) return null;
107003
- const values = match2.slice(1, 7).map(Number);
107004
- if (!values.every(Number.isFinite)) return null;
107005
- return values;
107040
+ if (match2d) {
107041
+ const values = match2d.slice(1, 7).map(Number);
107042
+ if (!values.every(Number.isFinite)) return null;
107043
+ return values;
107044
+ }
107045
+ const match3d = css.match(/^matrix3d\(\s*([^)]+)\)$/);
107046
+ if (match3d) {
107047
+ const raw2 = match3d[1];
107048
+ if (!raw2) return null;
107049
+ const parts = raw2.split(",").map((s) => Number(s.trim()));
107050
+ if (parts.length !== 16 || !parts.every(Number.isFinite)) return null;
107051
+ warnIfZSignificant(parts);
107052
+ return [
107053
+ parts[0],
107054
+ parts[1],
107055
+ parts[4],
107056
+ parts[5],
107057
+ parts[12],
107058
+ parts[13]
107059
+ ];
107060
+ }
107061
+ return null;
107062
+ }
107063
+ var warnedZSignificant = false;
107064
+ var Z_EPSILON = 1e-6;
107065
+ function warnIfZSignificant(parts) {
107066
+ if (warnedZSignificant) return;
107067
+ const a3 = parts[8] ?? 0;
107068
+ const b3 = parts[9] ?? 0;
107069
+ const c1 = parts[2] ?? 0;
107070
+ const c2 = parts[6] ?? 0;
107071
+ const c3 = parts[10] ?? 1;
107072
+ const d1 = parts[3] ?? 0;
107073
+ const d2 = parts[7] ?? 0;
107074
+ const d3 = parts[11] ?? 0;
107075
+ const d4 = parts[15] ?? 1;
107076
+ if (Math.abs(a3) > Z_EPSILON || Math.abs(b3) > Z_EPSILON || Math.abs(c1) > Z_EPSILON || Math.abs(c2) > Z_EPSILON || Math.abs(c3 - 1) > Z_EPSILON || Math.abs(d1) > Z_EPSILON || Math.abs(d2) > Z_EPSILON || Math.abs(d3) > Z_EPSILON || Math.abs(d4 - 1) > Z_EPSILON) {
107077
+ warnedZSignificant = true;
107078
+ console.warn(
107079
+ `[alphaBlit] parseTransformMatrix received a matrix3d with non-trivial 3D components (a3=${a3}, b3=${b3}, c1=${c1}, c2=${c2}, c3=${c3}, d1=${d1}, d2=${d2}, d3=${d3}, d4=${d4}). The engine projects 3D transforms to 2D (m11, m12, m21, m22, m41, m42) and silently discards perspective and out-of-plane rotation. If your composition uses real 3D (rotateX/Y, perspective), the rendered output will not match the studio preview. Z translation (translateZ) is dropped by design and does not trigger this warning. This warning is emitted once per process.`
107080
+ );
107081
+ }
107006
107082
  }
107007
107083
 
107008
107084
  // ../engine/src/utils/layerCompositor.ts
@@ -108206,14 +108282,14 @@ import { join as join14, dirname as dirname9, resolve as resolve10 } from "path"
108206
108282
  import postcss from "postcss";
108207
108283
 
108208
108284
  // src/utils/paths.ts
108209
- import { resolve as resolve9, basename as basename2, join as join12, relative as relative2, isAbsolute as isAbsolute2 } from "node:path";
108285
+ import { resolve as resolve9, basename as basename2, join as join12, relative as relative2, isAbsolute as isAbsolute4 } from "node:path";
108210
108286
  var DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? resolve9(new URL(import.meta.url).pathname, "../../..", "renders");
108211
108287
  function isPathInside2(childPath, parentPath) {
108212
108288
  const absChild = resolve9(childPath);
108213
108289
  const absParent = resolve9(parentPath);
108214
108290
  if (absChild === absParent) return true;
108215
108291
  const rel = relative2(absParent, absChild);
108216
- return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
108292
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute4(rel);
108217
108293
  }
108218
108294
  function toExternalAssetKey(absPath) {
108219
108295
  if (absPath.startsWith("hf-ext/")) return absPath;
@@ -108676,7 +108752,7 @@ async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagNa
108676
108752
  if (!existsSync14(filePath)) {
108677
108753
  return { duration: 0, resolvedPath: filePath };
108678
108754
  }
108679
- const metadata = tagName19 === "video" ? await extractVideoMetadata(filePath) : await extractAudioMetadata(filePath);
108755
+ const metadata = tagName19 === "video" ? await extractMediaMetadata(filePath) : await extractAudioMetadata(filePath);
108680
108756
  const fileDuration = metadata.durationSeconds;
108681
108757
  const effectiveDuration = fileDuration - mediaStart;
108682
108758
  const duration = effectiveDuration > 0 ? effectiveDuration : fileDuration;
@@ -109259,7 +109335,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
109259
109335
  if (isHttpUrl(video.src)) continue;
109260
109336
  const videoPath = resolve10(projectDir, video.src);
109261
109337
  const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
109262
- Promise.all([analyzeKeyframeIntervals(videoPath), extractVideoMetadata(videoPath)]).then(([analysis, metadata]) => {
109338
+ Promise.all([analyzeKeyframeIntervals(videoPath), extractMediaMetadata(videoPath)]).then(([analysis, metadata]) => {
109263
109339
  if (analysis.isProblematic) {
109264
109340
  console.warn(
109265
109341
  `[Compiler] WARNING: Video "${video.id}" has sparse keyframes (max interval: ${analysis.maxIntervalSeconds}s). This causes seek failures and frame freezing. Re-encode with: ${reencode}`
@@ -109425,6 +109501,9 @@ function createConsoleLogger(level = "info") {
109425
109501
  if (shouldLog("debug")) {
109426
109502
  console.log(`[DEBUG] ${message}${formatMeta(meta)}`);
109427
109503
  }
109504
+ },
109505
+ isLevelEnabled(msgLevel) {
109506
+ return shouldLog(msgLevel);
109428
109507
  }
109429
109508
  };
109430
109509
  }
@@ -109458,6 +109537,21 @@ function getMaxFrameIndex(frameDir) {
109458
109537
  frameDirMaxIndexCache.set(frameDir, max);
109459
109538
  return max;
109460
109539
  }
109540
+ function countNonZeroAlpha(rgba) {
109541
+ let n = 0;
109542
+ for (let p = 3; p < rgba.length; p += 4) {
109543
+ if (rgba[p] !== 0) n++;
109544
+ }
109545
+ return n;
109546
+ }
109547
+ function countNonZeroRgb48(buf) {
109548
+ let n = 0;
109549
+ for (let p = 0; p < buf.length; p += 6) {
109550
+ if (buf[p] !== 0 || buf[p + 1] !== 0 || buf[p + 2] !== 0 || buf[p + 3] !== 0 || buf[p + 4] !== 0 || buf[p + 5] !== 0)
109551
+ n++;
109552
+ }
109553
+ return n;
109554
+ }
109461
109555
  var RenderCancelledError = class extends Error {
109462
109556
  reason;
109463
109557
  constructor(message = "render_cancelled", reason = "aborted") {
@@ -109665,6 +109759,165 @@ function blitHdrImageLayer(canvas, el, hdrImageBuffers, width, height, log, sour
109665
109759
  }
109666
109760
  }
109667
109761
  }
109762
+ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter, debugFrameIndex = -1) {
109763
+ const {
109764
+ log,
109765
+ domSession,
109766
+ beforeCaptureHook,
109767
+ width,
109768
+ height,
109769
+ fps,
109770
+ effectiveHdr,
109771
+ nativeHdrImageIds,
109772
+ hdrImageBuffers,
109773
+ hdrFrameDirs,
109774
+ hdrVideoStartTimes,
109775
+ imageTransfers,
109776
+ videoTransfers,
109777
+ debugDumpEnabled,
109778
+ debugDumpDir
109779
+ } = ctx;
109780
+ const filteredStacking = elementFilter ? fullStacking.filter((e) => elementFilter.has(e.id)) : fullStacking;
109781
+ const layers = groupIntoLayers(filteredStacking);
109782
+ const shouldLog = debugDumpEnabled && debugFrameIndex >= 0;
109783
+ if (shouldLog) {
109784
+ log.info("[diag] compositeToBuffer plan", {
109785
+ frame: debugFrameIndex,
109786
+ time: time.toFixed(3),
109787
+ filterSize: elementFilter?.size,
109788
+ fullStackingCount: fullStacking.length,
109789
+ filteredCount: filteredStacking.length,
109790
+ layerCount: layers.length,
109791
+ layers: layers.map(
109792
+ (l) => l.type === "hdr" ? {
109793
+ type: "hdr",
109794
+ id: l.element.id,
109795
+ z: l.element.zIndex,
109796
+ visible: l.element.visible,
109797
+ opacity: l.element.opacity,
109798
+ bounds: `${Math.round(l.element.x)},${Math.round(l.element.y)} ${Math.round(l.element.width)}x${Math.round(l.element.height)}`
109799
+ } : { type: "dom", ids: l.elementIds }
109800
+ )
109801
+ });
109802
+ }
109803
+ for (const [layerIdx, layer] of layers.entries()) {
109804
+ if (layer.type === "hdr") {
109805
+ const before2 = shouldLog ? countNonZeroRgb48(canvas) : 0;
109806
+ const isHdrImage = nativeHdrImageIds.has(layer.element.id);
109807
+ if (isHdrImage) {
109808
+ blitHdrImageLayer(
109809
+ canvas,
109810
+ layer.element,
109811
+ hdrImageBuffers,
109812
+ width,
109813
+ height,
109814
+ log,
109815
+ imageTransfers.get(layer.element.id),
109816
+ effectiveHdr.transfer
109817
+ );
109818
+ } else {
109819
+ blitHdrVideoLayer(
109820
+ canvas,
109821
+ layer.element,
109822
+ time,
109823
+ fps,
109824
+ hdrFrameDirs,
109825
+ hdrVideoStartTimes,
109826
+ width,
109827
+ height,
109828
+ log,
109829
+ videoTransfers.get(layer.element.id),
109830
+ effectiveHdr.transfer
109831
+ );
109832
+ }
109833
+ if (shouldLog) {
109834
+ const after2 = countNonZeroRgb48(canvas);
109835
+ if (isHdrImage) {
109836
+ const buf = hdrImageBuffers.get(layer.element.id);
109837
+ log.info("[diag] hdr layer blit", {
109838
+ frame: debugFrameIndex,
109839
+ layerIdx,
109840
+ id: layer.element.id,
109841
+ kind: "image",
109842
+ pixelsAdded: after2 - before2,
109843
+ totalNonZero: after2,
109844
+ bufferDecoded: !!buf,
109845
+ bufferDims: buf ? `${buf.width}x${buf.height}` : null
109846
+ });
109847
+ } else {
109848
+ const frameDir = hdrFrameDirs.get(layer.element.id);
109849
+ const startTime = hdrVideoStartTimes.get(layer.element.id) ?? 0;
109850
+ const localTime = time - startTime;
109851
+ const frameNum = Math.floor(localTime * fps) + 1;
109852
+ const expectedFrame = frameDir ? join15(frameDir, `frame_${String(frameNum).padStart(4, "0")}.png`) : null;
109853
+ log.info("[diag] hdr layer blit", {
109854
+ frame: debugFrameIndex,
109855
+ layerIdx,
109856
+ id: layer.element.id,
109857
+ kind: "video",
109858
+ pixelsAdded: after2 - before2,
109859
+ totalNonZero: after2,
109860
+ startTime,
109861
+ localTime: localTime.toFixed(3),
109862
+ hdrFrameNum: frameNum,
109863
+ expectedFrame,
109864
+ expectedFrameExists: expectedFrame ? existsSync15(expectedFrame) : false
109865
+ });
109866
+ }
109867
+ }
109868
+ } else {
109869
+ const allElementIds = fullStacking.map((e) => e.id);
109870
+ const layerIds = new Set(layer.elementIds);
109871
+ const hideIds = allElementIds.filter((id) => !layerIds.has(id));
109872
+ await domSession.page.evaluate((t) => {
109873
+ if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t);
109874
+ }, time);
109875
+ if (beforeCaptureHook) {
109876
+ await beforeCaptureHook(domSession.page, time);
109877
+ }
109878
+ await applyDomLayerMask(domSession.page, layer.elementIds, hideIds);
109879
+ const domPng = await captureAlphaPng(domSession.page, width, height);
109880
+ await removeDomLayerMask(domSession.page, hideIds);
109881
+ try {
109882
+ const { data: domRgba } = decodePng(domPng);
109883
+ const before2 = shouldLog ? countNonZeroRgb48(canvas) : 0;
109884
+ const alphaPixels = shouldLog ? countNonZeroAlpha(domRgba) : 0;
109885
+ blitRgba8OverRgb48le(domRgba, canvas, width, height, effectiveHdr.transfer);
109886
+ if (shouldLog && debugDumpDir) {
109887
+ const after2 = countNonZeroRgb48(canvas);
109888
+ const dumpName = `frame_${String(debugFrameIndex).padStart(4, "0")}_layer_${String(layerIdx).padStart(2, "0")}_dom.png`;
109889
+ const dumpPath = join15(debugDumpDir, dumpName);
109890
+ writeFileSync4(dumpPath, domPng);
109891
+ log.info("[diag] dom layer blit", {
109892
+ frame: debugFrameIndex,
109893
+ layerIdx,
109894
+ layerIds: layer.elementIds,
109895
+ hideCount: hideIds.length,
109896
+ pngBytes: domPng.length,
109897
+ alphaPixels,
109898
+ pixelsAdded: after2 - before2,
109899
+ totalNonZero: after2,
109900
+ dumpPath
109901
+ });
109902
+ }
109903
+ } catch (err) {
109904
+ log.warn("DOM layer decode/blit failed; skipping overlay", {
109905
+ layerIds: layer.elementIds,
109906
+ error: err instanceof Error ? err.message : String(err)
109907
+ });
109908
+ }
109909
+ }
109910
+ }
109911
+ if (shouldLog && debugDumpDir) {
109912
+ const finalNonZero = countNonZeroRgb48(canvas);
109913
+ log.info("[diag] compositeToBuffer end", {
109914
+ frame: debugFrameIndex,
109915
+ finalNonZeroPixels: finalNonZero,
109916
+ totalPixels: width * height,
109917
+ coverage: (finalNonZero / (width * height) * 100).toFixed(1) + "%"
109918
+ });
109919
+ }
109920
+ }
109668
109921
  function createRenderJob(config2) {
109669
109922
  return {
109670
109923
  id: randomUUID(),
@@ -109732,6 +109985,19 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
109732
109985
  const enableChunkedEncode = cfg.enableChunkedEncode;
109733
109986
  const chunkedEncodeSize = cfg.chunkSizeFrames;
109734
109987
  const enableStreamingEncode = cfg.enableStreamingEncode;
109988
+ let peakRssBytes = 0;
109989
+ let peakHeapUsedBytes = 0;
109990
+ const sampleMemory = () => {
109991
+ try {
109992
+ const m = process.memoryUsage();
109993
+ if (m.rss > peakRssBytes) peakRssBytes = m.rss;
109994
+ if (m.heapUsed > peakHeapUsedBytes) peakHeapUsedBytes = m.heapUsed;
109995
+ } catch {
109996
+ }
109997
+ };
109998
+ sampleMemory();
109999
+ const memSamplerInterval = setInterval(sampleMemory, 250);
110000
+ memSamplerInterval.unref?.();
109735
110001
  try {
109736
110002
  const assertNotAborted = () => {
109737
110003
  if (abortSignal?.aborted) {
@@ -110009,7 +110275,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110009
110275
  videoPath = fromCompiled;
110010
110276
  }
110011
110277
  if (!existsSync15(videoPath)) return;
110012
- const meta = await extractVideoMetadata(videoPath);
110278
+ const meta = await extractMediaMetadata(videoPath);
110013
110279
  if (isHdrColorSpace(meta.colorSpace)) {
110014
110280
  nativeHdrVideoIds.add(v.id);
110015
110281
  videoTransfers.set(v.id, detectTransfer(meta.colorSpace));
@@ -110030,7 +110296,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110030
110296
  imgPath = fromCompiled;
110031
110297
  }
110032
110298
  if (!existsSync15(imgPath)) return null;
110033
- const meta = await extractVideoMetadata(imgPath);
110299
+ const meta = await extractMediaMetadata(imgPath);
110034
110300
  if (isHdrColorSpace(meta.colorSpace)) {
110035
110301
  nativeHdrImageIds.add(img.id);
110036
110302
  imageTransfers.set(img.id, detectTransfer(meta.colorSpace));
@@ -110142,6 +110408,10 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110142
110408
  format: needsAlpha ? "png" : "jpeg",
110143
110409
  quality: needsAlpha ? void 0 : job.config.quality === "draft" ? 80 : 95
110144
110410
  };
110411
+ const buildHdrCaptureOptions = () => ({
110412
+ ...captureOptions,
110413
+ skipReadinessVideoIds: Array.from(nativeHdrVideoIds)
110414
+ });
110145
110415
  const workerCount = calculateOptimalWorkers(totalFrames, job.config.workers, cfg);
110146
110416
  const FORMAT_EXT = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
110147
110417
  const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
@@ -110176,7 +110446,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110176
110446
  const domSession = await createCaptureSession(
110177
110447
  fileServer.url,
110178
110448
  framesDir,
110179
- { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
110449
+ buildHdrCaptureOptions(),
110180
110450
  createVideoFrameInjector(frameLookup),
110181
110451
  cfg
110182
110452
  );
@@ -110272,6 +110542,29 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110272
110542
  }
110273
110543
  }
110274
110544
  }
110545
+ for (const [imageId, startTime] of hdrImageStartTimes) {
110546
+ if (hdrExtractionDims.has(imageId)) continue;
110547
+ const img = composition.images.find((i) => i.id === imageId);
110548
+ if (!img) continue;
110549
+ const duration = img.end - img.start;
110550
+ const retryTime = startTime + Math.min(0.5, duration * 0.1);
110551
+ await domSession.page.evaluate((t) => {
110552
+ if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t);
110553
+ }, retryTime);
110554
+ if (domSession.onBeforeCapture) {
110555
+ await domSession.onBeforeCapture(domSession.page, retryTime);
110556
+ }
110557
+ const retryStacking = await queryElementStacking(domSession.page, nativeHdrIds);
110558
+ for (const el of retryStacking) {
110559
+ if (el.id === imageId && el.isHdr && el.layoutWidth > 0 && el.layoutHeight > 0) {
110560
+ hdrExtractionDims.set(el.id, { width: el.layoutWidth, height: el.layoutHeight });
110561
+ if (!hdrImageFitInfo.has(el.id)) {
110562
+ hdrImageFitInfo.set(el.id, { fit: el.objectFit, position: el.objectPosition });
110563
+ }
110564
+ break;
110565
+ }
110566
+ }
110567
+ }
110275
110568
  for (const [videoId, srcPath] of hdrVideoSrcPaths) {
110276
110569
  const video = composition.videos.find((v) => v.id === videoId);
110277
110570
  if (!video) continue;
@@ -110354,21 +110647,6 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110354
110647
  }
110355
110648
  assertNotAborted();
110356
110649
  try {
110357
- let countNonZeroAlpha2 = function(rgba) {
110358
- let n = 0;
110359
- for (let p = 3; p < rgba.length; p += 4) {
110360
- if (rgba[p] !== 0) n++;
110361
- }
110362
- return n;
110363
- }, countNonZeroRgb482 = function(buf) {
110364
- let n = 0;
110365
- for (let p = 0; p < buf.length; p += 6) {
110366
- if (buf[p] !== 0 || buf[p + 1] !== 0 || buf[p + 2] !== 0 || buf[p + 3] !== 0 || buf[p + 4] !== 0 || buf[p + 5] !== 0)
110367
- n++;
110368
- }
110369
- return n;
110370
- };
110371
- var countNonZeroAlpha = countNonZeroAlpha2, countNonZeroRgb48 = countNonZeroRgb482;
110372
110650
  const beforeCaptureHook = domSession.onBeforeCapture;
110373
110651
  const cleanedUpVideos = /* @__PURE__ */ new Set();
110374
110652
  const hdrVideoEndTimes = /* @__PURE__ */ new Map();
@@ -110382,153 +110660,28 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110382
110660
  if (debugDumpDir && !existsSync15(debugDumpDir)) {
110383
110661
  mkdirSync10(debugDumpDir, { recursive: true });
110384
110662
  }
110385
- async function compositeToBuffer(canvas, time, fullStacking, elementFilter, debugFrameIndex = -1) {
110386
- const filteredStacking = elementFilter ? fullStacking.filter((e) => elementFilter.has(e.id)) : fullStacking;
110387
- const layers = groupIntoLayers(filteredStacking);
110388
- const shouldLog = debugDumpEnabled && debugFrameIndex >= 0;
110389
- if (shouldLog) {
110390
- log.info("[diag] compositeToBuffer plan", {
110391
- frame: debugFrameIndex,
110392
- time: time.toFixed(3),
110393
- filterSize: elementFilter?.size,
110394
- fullStackingCount: fullStacking.length,
110395
- filteredCount: filteredStacking.length,
110396
- layerCount: layers.length,
110397
- layers: layers.map(
110398
- (l) => l.type === "hdr" ? {
110399
- type: "hdr",
110400
- id: l.element.id,
110401
- z: l.element.zIndex,
110402
- visible: l.element.visible,
110403
- opacity: l.element.opacity,
110404
- bounds: `${Math.round(l.element.x)},${Math.round(l.element.y)} ${Math.round(l.element.width)}x${Math.round(l.element.height)}`
110405
- } : { type: "dom", ids: l.elementIds }
110406
- )
110407
- });
110408
- }
110409
- for (const [layerIdx, layer] of layers.entries()) {
110410
- if (layer.type === "hdr") {
110411
- const before2 = shouldLog ? countNonZeroRgb482(canvas) : 0;
110412
- const isHdrImage = nativeHdrImageIds.has(layer.element.id);
110413
- if (isHdrImage) {
110414
- blitHdrImageLayer(
110415
- canvas,
110416
- layer.element,
110417
- hdrImageBuffers,
110418
- width,
110419
- height,
110420
- log,
110421
- imageTransfers.get(layer.element.id),
110422
- effectiveHdr?.transfer
110423
- );
110424
- } else {
110425
- blitHdrVideoLayer(
110426
- canvas,
110427
- layer.element,
110428
- time,
110429
- job.config.fps,
110430
- hdrFrameDirs,
110431
- hdrVideoStartTimes,
110432
- width,
110433
- height,
110434
- log,
110435
- videoTransfers.get(layer.element.id),
110436
- effectiveHdr?.transfer
110437
- );
110438
- }
110439
- if (shouldLog) {
110440
- const after2 = countNonZeroRgb482(canvas);
110441
- if (isHdrImage) {
110442
- const buf = hdrImageBuffers.get(layer.element.id);
110443
- log.info("[diag] hdr layer blit", {
110444
- frame: debugFrameIndex,
110445
- layerIdx,
110446
- id: layer.element.id,
110447
- kind: "image",
110448
- pixelsAdded: after2 - before2,
110449
- totalNonZero: after2,
110450
- bufferDecoded: !!buf,
110451
- bufferDims: buf ? `${buf.width}x${buf.height}` : null
110452
- });
110453
- } else {
110454
- const frameDir = hdrFrameDirs.get(layer.element.id);
110455
- const startTime = hdrVideoStartTimes.get(layer.element.id) ?? 0;
110456
- const localTime = time - startTime;
110457
- const frameNum = Math.floor(localTime * job.config.fps) + 1;
110458
- const expectedFrame = frameDir ? join15(frameDir, `frame_${String(frameNum).padStart(4, "0")}.png`) : null;
110459
- log.info("[diag] hdr layer blit", {
110460
- frame: debugFrameIndex,
110461
- layerIdx,
110462
- id: layer.element.id,
110463
- kind: "video",
110464
- pixelsAdded: after2 - before2,
110465
- totalNonZero: after2,
110466
- startTime,
110467
- localTime: localTime.toFixed(3),
110468
- hdrFrameNum: frameNum,
110469
- expectedFrame,
110470
- expectedFrameExists: expectedFrame ? existsSync15(expectedFrame) : false
110471
- });
110472
- }
110473
- }
110474
- } else {
110475
- const allElementIds = fullStacking.map((e) => e.id);
110476
- const layerIds = new Set(layer.elementIds);
110477
- const hideIds = allElementIds.filter((id) => !layerIds.has(id));
110478
- await domSession.page.evaluate((t) => {
110479
- if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t);
110480
- }, time);
110481
- if (beforeCaptureHook) {
110482
- await beforeCaptureHook(domSession.page, time);
110483
- }
110484
- await applyDomLayerMask(domSession.page, layer.elementIds, hideIds);
110485
- const domPng = await captureAlphaPng(domSession.page, width, height);
110486
- await removeDomLayerMask(domSession.page, hideIds);
110487
- try {
110488
- const { data: domRgba } = decodePng(domPng);
110489
- if (!effectiveHdr) {
110490
- throw new Error(
110491
- "Invariant violation: effectiveHdr is undefined inside HDR layer branch"
110492
- );
110493
- }
110494
- const before2 = shouldLog ? countNonZeroRgb482(canvas) : 0;
110495
- const alphaPixels = shouldLog ? countNonZeroAlpha2(domRgba) : 0;
110496
- blitRgba8OverRgb48le(domRgba, canvas, width, height, effectiveHdr.transfer);
110497
- if (shouldLog && debugDumpDir) {
110498
- const after2 = countNonZeroRgb482(canvas);
110499
- const dumpName = `frame_${String(debugFrameIndex).padStart(4, "0")}_layer_${String(layerIdx).padStart(2, "0")}_dom.png`;
110500
- const dumpPath = join15(debugDumpDir, dumpName);
110501
- writeFileSync4(dumpPath, domPng);
110502
- log.info("[diag] dom layer blit", {
110503
- frame: debugFrameIndex,
110504
- layerIdx,
110505
- layerIds: layer.elementIds,
110506
- hideCount: hideIds.length,
110507
- pngBytes: domPng.length,
110508
- alphaPixels,
110509
- pixelsAdded: after2 - before2,
110510
- totalNonZero: after2,
110511
- dumpPath
110512
- });
110513
- }
110514
- } catch (err) {
110515
- log.warn("DOM layer decode/blit failed; skipping overlay", {
110516
- layerIds: layer.elementIds,
110517
- error: err instanceof Error ? err.message : String(err)
110518
- });
110519
- }
110520
- }
110521
- }
110522
- if (shouldLog && debugDumpDir) {
110523
- const finalNonZero = countNonZeroRgb482(canvas);
110524
- log.info("[diag] compositeToBuffer end", {
110525
- frame: debugFrameIndex,
110526
- finalNonZeroPixels: finalNonZero,
110527
- totalPixels: width * height,
110528
- coverage: (finalNonZero / (width * height) * 100).toFixed(1) + "%"
110529
- });
110530
- }
110663
+ if (!effectiveHdr) {
110664
+ throw new Error(
110665
+ "Internal: HDR render path entered without effectiveHdr \u2014 this is a bug."
110666
+ );
110531
110667
  }
110668
+ const hdrCompositeCtx = {
110669
+ log,
110670
+ domSession,
110671
+ beforeCaptureHook,
110672
+ width,
110673
+ height,
110674
+ fps: job.config.fps,
110675
+ effectiveHdr,
110676
+ nativeHdrImageIds,
110677
+ hdrImageBuffers,
110678
+ hdrFrameDirs,
110679
+ hdrVideoStartTimes,
110680
+ imageTransfers,
110681
+ videoTransfers,
110682
+ debugDumpEnabled,
110683
+ debugDumpDir
110684
+ };
110532
110685
  const bufSize = width * height * 6;
110533
110686
  const hasTransitions = transitionRanges.length > 0;
110534
110687
  const transBufferA = hasTransitions ? Buffer.alloc(bufSize) : null;
@@ -110548,7 +110701,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110548
110701
  const activeTransition = transitionRanges.find(
110549
110702
  (t) => i >= t.startFrame && i <= t.endFrame
110550
110703
  );
110551
- if (i % 30 === 0) {
110704
+ if (i % 30 === 0 && (log.isLevelEnabled?.("debug") ?? true)) {
110552
110705
  const hdrEl = stackingInfo.find((e) => e.isHdr);
110553
110706
  log.debug("[Render] HDR layer composite frame", {
110554
110707
  frame: i,
@@ -110636,7 +110789,14 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110636
110789
  hdrEncoder.writeFrame(transOutput);
110637
110790
  } else {
110638
110791
  normalCanvas.fill(0);
110639
- await compositeToBuffer(normalCanvas, time, stackingInfo, void 0, i);
110792
+ await compositeHdrFrame(
110793
+ hdrCompositeCtx,
110794
+ normalCanvas,
110795
+ time,
110796
+ stackingInfo,
110797
+ void 0,
110798
+ i
110799
+ );
110640
110800
  if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
110641
110801
  const previewPath = join15(
110642
110802
  debugDumpDir,
@@ -110755,7 +110915,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110755
110915
  fileServer.url,
110756
110916
  workDir,
110757
110917
  tasks,
110758
- { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
110918
+ buildHdrCaptureOptions(),
110759
110919
  () => createVideoFrameInjector(frameLookup),
110760
110920
  abortSignal,
110761
110921
  (progress) => {
@@ -110785,7 +110945,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110785
110945
  const session = probeSession ?? await createCaptureSession(
110786
110946
  fileServer.url,
110787
110947
  framesDir,
110788
- { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
110948
+ buildHdrCaptureOptions(),
110789
110949
  videoInjector,
110790
110950
  cfg
110791
110951
  );
@@ -110837,7 +110997,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110837
110997
  fileServer.url,
110838
110998
  workDir,
110839
110999
  tasks,
110840
- { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
111000
+ buildHdrCaptureOptions(),
110841
111001
  () => createVideoFrameInjector(frameLookup),
110842
111002
  abortSignal,
110843
111003
  (progress) => {
@@ -110868,7 +111028,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110868
111028
  const session = probeSession ?? await createCaptureSession(
110869
111029
  fileServer.url,
110870
111030
  framesDir,
110871
- { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
111031
+ buildHdrCaptureOptions(),
110872
111032
  videoInjector,
110873
111033
  cfg
110874
111034
  );
@@ -110984,6 +111144,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110984
111144
  job.outputPath = outputPath;
110985
111145
  updateJobStatus(job, "complete", "Render complete", 100, onProgress);
110986
111146
  const totalElapsed = Date.now() - pipelineStart;
111147
+ sampleMemory();
110987
111148
  const perfSummary = {
110988
111149
  renderId: job.id,
110989
111150
  totalElapsedMs: totalElapsed,
@@ -110999,7 +111160,9 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110999
111160
  audioCount: composition.audios.length,
111000
111161
  stages: perfStages,
111001
111162
  hdrDiagnostics: hdrDiagnostics.videoExtractionFailures > 0 || hdrDiagnostics.imageDecodeFailures > 0 ? { ...hdrDiagnostics } : void 0,
111002
- captureAvgMs: totalFrames > 0 ? Math.round((perfStages.captureMs ?? 0) / totalFrames) : void 0
111163
+ captureAvgMs: totalFrames > 0 ? Math.round((perfStages.captureMs ?? 0) / totalFrames) : void 0,
111164
+ peakRssMb: Math.round(peakRssBytes / (1024 * 1024)),
111165
+ peakHeapUsedMb: Math.round(peakHeapUsedBytes / (1024 * 1024))
111003
111166
  };
111004
111167
  job.perfSummary = perfSummary;
111005
111168
  if (job.config.debug) {
@@ -111107,6 +111270,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111107
111270
  }
111108
111271
  if (restoreLogger) restoreLogger();
111109
111272
  throw error;
111273
+ } finally {
111274
+ clearInterval(memSamplerInterval);
111110
111275
  }
111111
111276
  }
111112
111277