@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.
@@ -57866,7 +57866,7 @@ var require_util2 = __commonJS({
57866
57866
  }
57867
57867
  path12 = url.path;
57868
57868
  }
57869
- var isAbsolute3 = exports.isAbsolute(path12);
57869
+ var isAbsolute5 = exports.isAbsolute(path12);
57870
57870
  var parts = path12.split(/\/+/);
57871
57871
  for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
57872
57872
  part = parts[i];
@@ -57886,7 +57886,7 @@ var require_util2 = __commonJS({
57886
57886
  }
57887
57887
  path12 = parts.join("/");
57888
57888
  if (path12 === "") {
57889
- path12 = isAbsolute3 ? "/" : ".";
57889
+ path12 = isAbsolute5 ? "/" : ".";
57890
57890
  }
57891
57891
  if (url) {
57892
57892
  url.path = path12;
@@ -104407,6 +104407,34 @@ function getGpuEncoderName(encoder, codec) {
104407
104407
  return codec === "h264" ? "libx264" : "libx265";
104408
104408
  }
104409
104409
  }
104410
+ var NVENC_PRESET_MAP = {
104411
+ ultrafast: "p1",
104412
+ superfast: "p1",
104413
+ veryfast: "p2",
104414
+ faster: "p3",
104415
+ fast: "p4",
104416
+ medium: "p4",
104417
+ slow: "p5",
104418
+ slower: "p6",
104419
+ veryslow: "p7",
104420
+ placebo: "p7"
104421
+ };
104422
+ var QSV_PRESET_MAP = {
104423
+ ultrafast: "veryfast",
104424
+ superfast: "veryfast",
104425
+ placebo: "veryslow"
104426
+ };
104427
+ function mapPresetForGpuEncoder(encoder, preset) {
104428
+ switch (encoder) {
104429
+ case "nvenc":
104430
+ if (/^p[1-7]$/.test(preset)) return preset;
104431
+ return NVENC_PRESET_MAP[preset] ?? "p4";
104432
+ case "qsv":
104433
+ return QSV_PRESET_MAP[preset] ?? preset;
104434
+ default:
104435
+ return preset;
104436
+ }
104437
+ }
104410
104438
 
104411
104439
  // ../engine/src/utils/hdr.ts
104412
104440
  function isHdrColorSpace(cs) {
@@ -104450,6 +104478,16 @@ function analyzeCompositionHdr(colorSpaces) {
104450
104478
  // ../engine/src/utils/runFfmpeg.ts
104451
104479
  import { spawn as spawn4 } from "child_process";
104452
104480
  var DEFAULT_TIMEOUT2 = 3e5;
104481
+ var DEFAULT_STDERR_TAIL_LINES = 15;
104482
+ function formatFfmpegError(exitCode, stderr, tailLines = DEFAULT_STDERR_TAIL_LINES) {
104483
+ const tail = (stderr ?? "").split(/\r?\n/).filter((line) => line.length > 0).slice(-tailLines).join("\n");
104484
+ if (exitCode === null) {
104485
+ return tail ? `[FFmpeg] ${tail}` : "[FFmpeg] process error";
104486
+ }
104487
+ return tail ? `FFmpeg exited with code ${exitCode}
104488
+ ffmpeg stderr (tail):
104489
+ ${tail}` : `FFmpeg exited with code ${exitCode}`;
104490
+ }
104453
104491
  async function runFfmpeg(args, opts) {
104454
104492
  const startMs = Date.now();
104455
104493
  const signal = opts?.signal;
@@ -104560,7 +104598,7 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
104560
104598
  args.push("-c:v", encoderName);
104561
104599
  switch (gpuEncoder) {
104562
104600
  case "nvenc":
104563
- args.push("-preset", preset);
104601
+ args.push("-preset", mapPresetForGpuEncoder("nvenc", preset));
104564
104602
  if (bitrate) args.push("-b:v", bitrate);
104565
104603
  else args.push("-cq", String(quality));
104566
104604
  break;
@@ -104579,7 +104617,7 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
104579
104617
  else args.push("-qp", String(quality));
104580
104618
  break;
104581
104619
  case "qsv":
104582
- args.push("-preset", preset);
104620
+ args.push("-preset", mapPresetForGpuEncoder("qsv", preset));
104583
104621
  if (bitrate) args.push("-b:v", bitrate);
104584
104622
  else args.push("-global_quality", String(quality));
104585
104623
  break;
@@ -104719,7 +104757,7 @@ async function encodeFramesFromDir(framesDir, framePattern, outputPath, options,
104719
104757
  durationMs,
104720
104758
  framesEncoded: 0,
104721
104759
  fileSize: 0,
104722
- error: `FFmpeg exited with code ${code}`
104760
+ error: formatFfmpegError(code, stderr)
104723
104761
  });
104724
104762
  return;
104725
104763
  }
@@ -104889,7 +104927,7 @@ async function muxVideoWithAudio(videoPath, audioPath, outputPath, signal, confi
104889
104927
  success: result.success,
104890
104928
  outputPath,
104891
104929
  durationMs: result.durationMs,
104892
- error: !result.success ? result.exitCode !== null ? `FFmpeg exited with code ${result.exitCode}` : `[FFmpeg] ${result.stderr}` : void 0
104930
+ error: !result.success ? formatFfmpegError(result.exitCode, result.stderr) : void 0
104893
104931
  };
104894
104932
  }
104895
104933
  async function applyFaststart(inputPath, outputPath, signal, config2) {
@@ -104912,7 +104950,7 @@ async function applyFaststart(inputPath, outputPath, signal, config2) {
104912
104950
  success: result.success,
104913
104951
  outputPath,
104914
104952
  durationMs: result.durationMs,
104915
- error: !result.success ? result.exitCode !== null ? `FFmpeg exited with code ${result.exitCode}` : `[FFmpeg] ${result.stderr}` : void 0
104953
+ error: !result.success ? formatFfmpegError(result.exitCode, result.stderr) : void 0
104916
104954
  };
104917
104955
  }
104918
104956
 
@@ -105005,7 +105043,7 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
105005
105043
  args.push("-c:v", encoderName);
105006
105044
  switch (gpuEncoder) {
105007
105045
  case "nvenc":
105008
- args.push("-preset", preset);
105046
+ args.push("-preset", mapPresetForGpuEncoder("nvenc", preset));
105009
105047
  if (bitrate) args.push("-b:v", bitrate);
105010
105048
  else args.push("-cq", String(quality));
105011
105049
  break;
@@ -105024,7 +105062,7 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
105024
105062
  else args.push("-qp", String(quality));
105025
105063
  break;
105026
105064
  case "qsv":
105027
- args.push("-preset", preset);
105065
+ args.push("-preset", mapPresetForGpuEncoder("qsv", preset));
105028
105066
  if (bitrate) args.push("-b:v", bitrate);
105029
105067
  else args.push("-global_quality", String(quality));
105030
105068
  break;
@@ -105180,7 +105218,7 @@ Process error: ${err.message}`;
105180
105218
  success: false,
105181
105219
  durationMs,
105182
105220
  fileSize: 0,
105183
- error: `FFmpeg exited with code ${exitCode}`
105221
+ error: formatFfmpegError(exitCode, stderr)
105184
105222
  };
105185
105223
  }
105186
105224
  const fileSize = existsSync6(outputPath) ? statSync4(outputPath).size : 0;
@@ -105194,7 +105232,7 @@ Process error: ${err.message}`;
105194
105232
  // ../engine/src/services/videoFrameExtractor.ts
105195
105233
  import { spawn as spawn8 } from "child_process";
105196
105234
  import { existsSync as existsSync8, mkdirSync as mkdirSync5, readdirSync as readdirSync4, rmSync } from "fs";
105197
- import { join as join8 } from "path";
105235
+ import { isAbsolute as isAbsolute2, join as join8 } from "path";
105198
105236
 
105199
105237
  // ../engine/src/utils/ffprobe.ts
105200
105238
  import { spawn as spawn7 } from "child_process";
@@ -105309,7 +105347,7 @@ function parseFrameRate(frameRateStr) {
105309
105347
  }
105310
105348
  return parseFloat(frameRateStr) || 0;
105311
105349
  }
105312
- async function extractVideoMetadata(filePath) {
105350
+ async function extractMediaMetadata(filePath) {
105313
105351
  const cached = videoMetadataCache.get(filePath);
105314
105352
  if (cached) return cached;
105315
105353
  const probePromise = (async () => {
@@ -105595,7 +105633,7 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
105595
105633
  const { fps, outputDir, quality = 95, format: format3 = "jpg" } = options;
105596
105634
  const videoOutputDir = join8(outputDir, videoId);
105597
105635
  if (!existsSync8(videoOutputDir)) mkdirSync5(videoOutputDir, { recursive: true });
105598
- const metadata = await extractVideoMetadata(videoPath);
105636
+ const metadata = await extractMediaMetadata(videoPath);
105599
105637
  const framePattern = `frame_%05d.${format3}`;
105600
105638
  const outputPattern = join8(videoOutputDir, framePattern);
105601
105639
  const isHdr = isHdrColorSpace(metadata.colorSpace);
@@ -105744,7 +105782,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
105744
105782
  if (signal?.aborted) break;
105745
105783
  try {
105746
105784
  let videoPath = video.src;
105747
- if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
105785
+ if (!isAbsolute2(videoPath) && !isHttpUrl(videoPath)) {
105748
105786
  const fromCompiled = compiledDir ? join8(compiledDir, videoPath) : null;
105749
105787
  videoPath = fromCompiled && existsSync8(fromCompiled) ? fromCompiled : join8(baseDir, videoPath);
105750
105788
  }
@@ -105764,7 +105802,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
105764
105802
  }
105765
105803
  const videoColorSpaces = await Promise.all(
105766
105804
  resolvedVideos.map(async ({ videoPath }) => {
105767
- const metadata = await extractVideoMetadata(videoPath);
105805
+ const metadata = await extractMediaMetadata(videoPath);
105768
105806
  return metadata.colorSpace;
105769
105807
  })
105770
105808
  );
@@ -105797,7 +105835,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
105797
105835
  if (signal?.aborted) break;
105798
105836
  const entry = resolvedVideos[i];
105799
105837
  if (!entry) continue;
105800
- const metadata = await extractVideoMetadata(entry.videoPath);
105838
+ const metadata = await extractMediaMetadata(entry.videoPath);
105801
105839
  if (!metadata.isVFR) continue;
105802
105840
  let segDuration = entry.video.end - entry.video.start;
105803
105841
  if (!Number.isFinite(segDuration) || segDuration <= 0) {
@@ -105833,7 +105871,7 @@ async function extractAllVideoFrames(videos, baseDir, options, signal, config2,
105833
105871
  try {
105834
105872
  let videoDuration = video.end - video.start;
105835
105873
  if (!Number.isFinite(videoDuration) || videoDuration <= 0) {
105836
- const metadata = await extractVideoMetadata(videoPath);
105874
+ const metadata = await extractMediaMetadata(videoPath);
105837
105875
  const sourceDuration = metadata.durationSeconds - video.mediaStart;
105838
105876
  videoDuration = sourceDuration > 0 ? sourceDuration : metadata.durationSeconds;
105839
105877
  video.end = video.start + videoDuration;
@@ -106208,7 +106246,7 @@ async function queryElementStacking(page, nativeHdrIds) {
106208
106246
 
106209
106247
  // ../engine/src/services/audioMixer.ts
106210
106248
  import { existsSync as existsSync9, mkdirSync as mkdirSync6, rmSync as rmSync2 } from "fs";
106211
- import { join as join9, dirname as dirname7 } from "path";
106249
+ import { isAbsolute as isAbsolute3, join as join9, dirname as dirname7 } from "path";
106212
106250
  function parseAudioElements(html) {
106213
106251
  const elements = [];
106214
106252
  const { document: document2 } = parseHTML(html);
@@ -106435,7 +106473,7 @@ async function processCompositionAudio(elements, baseDir, workDir, outputPath, t
106435
106473
  }
106436
106474
  try {
106437
106475
  let srcPath = element.src;
106438
- if (!srcPath.startsWith("/") && !isHttpUrl(srcPath)) {
106476
+ if (!isAbsolute3(srcPath) && !isHttpUrl(srcPath)) {
106439
106477
  const fromCompiled = compiledDir ? join9(compiledDir, srcPath) : null;
106440
106478
  srcPath = fromCompiled && existsSync9(fromCompiled) ? fromCompiled : join9(baseDir, srcPath);
106441
106479
  }
@@ -107161,13 +107199,51 @@ function normalizeObjectFit(value) {
107161
107199
  }
107162
107200
  function parseTransformMatrix(css) {
107163
107201
  if (!css || css === "none") return null;
107164
- const match2 = css.match(
107202
+ const match2d = css.match(
107165
107203
  /^matrix\(\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^,)]+)\s*\)$/
107166
107204
  );
107167
- if (!match2) return null;
107168
- const values = match2.slice(1, 7).map(Number);
107169
- if (!values.every(Number.isFinite)) return null;
107170
- return values;
107205
+ if (match2d) {
107206
+ const values = match2d.slice(1, 7).map(Number);
107207
+ if (!values.every(Number.isFinite)) return null;
107208
+ return values;
107209
+ }
107210
+ const match3d = css.match(/^matrix3d\(\s*([^)]+)\)$/);
107211
+ if (match3d) {
107212
+ const raw2 = match3d[1];
107213
+ if (!raw2) return null;
107214
+ const parts = raw2.split(",").map((s) => Number(s.trim()));
107215
+ if (parts.length !== 16 || !parts.every(Number.isFinite)) return null;
107216
+ warnIfZSignificant(parts);
107217
+ return [
107218
+ parts[0],
107219
+ parts[1],
107220
+ parts[4],
107221
+ parts[5],
107222
+ parts[12],
107223
+ parts[13]
107224
+ ];
107225
+ }
107226
+ return null;
107227
+ }
107228
+ var warnedZSignificant = false;
107229
+ var Z_EPSILON = 1e-6;
107230
+ function warnIfZSignificant(parts) {
107231
+ if (warnedZSignificant) return;
107232
+ const a3 = parts[8] ?? 0;
107233
+ const b3 = parts[9] ?? 0;
107234
+ const c1 = parts[2] ?? 0;
107235
+ const c2 = parts[6] ?? 0;
107236
+ const c3 = parts[10] ?? 1;
107237
+ const d1 = parts[3] ?? 0;
107238
+ const d2 = parts[7] ?? 0;
107239
+ const d3 = parts[11] ?? 0;
107240
+ const d4 = parts[15] ?? 1;
107241
+ 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) {
107242
+ warnedZSignificant = true;
107243
+ console.warn(
107244
+ `[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.`
107245
+ );
107246
+ }
107171
107247
  }
107172
107248
 
107173
107249
  // ../engine/src/utils/layerCompositor.ts
@@ -108371,14 +108447,14 @@ import { join as join14, dirname as dirname9, resolve as resolve10 } from "path"
108371
108447
  import postcss from "postcss";
108372
108448
 
108373
108449
  // src/utils/paths.ts
108374
- import { resolve as resolve9, basename as basename2, join as join12, relative as relative2, isAbsolute as isAbsolute2 } from "node:path";
108450
+ import { resolve as resolve9, basename as basename2, join as join12, relative as relative2, isAbsolute as isAbsolute4 } from "node:path";
108375
108451
  var DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? resolve9(new URL(import.meta.url).pathname, "../../..", "renders");
108376
108452
  function isPathInside2(childPath, parentPath) {
108377
108453
  const absChild = resolve9(childPath);
108378
108454
  const absParent = resolve9(parentPath);
108379
108455
  if (absChild === absParent) return true;
108380
108456
  const rel = relative2(absParent, absChild);
108381
- return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
108457
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute4(rel);
108382
108458
  }
108383
108459
  function toExternalAssetKey(absPath) {
108384
108460
  if (absPath.startsWith("hf-ext/")) return absPath;
@@ -108841,7 +108917,7 @@ async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagNa
108841
108917
  if (!existsSync14(filePath)) {
108842
108918
  return { duration: 0, resolvedPath: filePath };
108843
108919
  }
108844
- const metadata = tagName19 === "video" ? await extractVideoMetadata(filePath) : await extractAudioMetadata(filePath);
108920
+ const metadata = tagName19 === "video" ? await extractMediaMetadata(filePath) : await extractAudioMetadata(filePath);
108845
108921
  const fileDuration = metadata.durationSeconds;
108846
108922
  const effectiveDuration = fileDuration - mediaStart;
108847
108923
  const duration = effectiveDuration > 0 ? effectiveDuration : fileDuration;
@@ -109424,7 +109500,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
109424
109500
  if (isHttpUrl(video.src)) continue;
109425
109501
  const videoPath = resolve10(projectDir, video.src);
109426
109502
  const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
109427
- Promise.all([analyzeKeyframeIntervals(videoPath), extractVideoMetadata(videoPath)]).then(([analysis, metadata]) => {
109503
+ Promise.all([analyzeKeyframeIntervals(videoPath), extractMediaMetadata(videoPath)]).then(([analysis, metadata]) => {
109428
109504
  if (analysis.isProblematic) {
109429
109505
  console.warn(
109430
109506
  `[Compiler] WARNING: Video "${video.id}" has sparse keyframes (max interval: ${analysis.maxIntervalSeconds}s). This causes seek failures and frame freezing. Re-encode with: ${reencode}`
@@ -109590,6 +109666,9 @@ function createConsoleLogger(level = "info") {
109590
109666
  if (shouldLog("debug")) {
109591
109667
  console.log(`[DEBUG] ${message}${formatMeta(meta)}`);
109592
109668
  }
109669
+ },
109670
+ isLevelEnabled(msgLevel) {
109671
+ return shouldLog(msgLevel);
109593
109672
  }
109594
109673
  };
109595
109674
  }
@@ -109623,6 +109702,21 @@ function getMaxFrameIndex(frameDir) {
109623
109702
  frameDirMaxIndexCache.set(frameDir, max);
109624
109703
  return max;
109625
109704
  }
109705
+ function countNonZeroAlpha(rgba) {
109706
+ let n = 0;
109707
+ for (let p = 3; p < rgba.length; p += 4) {
109708
+ if (rgba[p] !== 0) n++;
109709
+ }
109710
+ return n;
109711
+ }
109712
+ function countNonZeroRgb48(buf) {
109713
+ let n = 0;
109714
+ for (let p = 0; p < buf.length; p += 6) {
109715
+ if (buf[p] !== 0 || buf[p + 1] !== 0 || buf[p + 2] !== 0 || buf[p + 3] !== 0 || buf[p + 4] !== 0 || buf[p + 5] !== 0)
109716
+ n++;
109717
+ }
109718
+ return n;
109719
+ }
109626
109720
  var RenderCancelledError = class extends Error {
109627
109721
  reason;
109628
109722
  constructor(message = "render_cancelled", reason = "aborted") {
@@ -109830,6 +109924,165 @@ function blitHdrImageLayer(canvas, el, hdrImageBuffers, width, height, log, sour
109830
109924
  }
109831
109925
  }
109832
109926
  }
109927
+ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter, debugFrameIndex = -1) {
109928
+ const {
109929
+ log,
109930
+ domSession,
109931
+ beforeCaptureHook,
109932
+ width,
109933
+ height,
109934
+ fps,
109935
+ effectiveHdr,
109936
+ nativeHdrImageIds,
109937
+ hdrImageBuffers,
109938
+ hdrFrameDirs,
109939
+ hdrVideoStartTimes,
109940
+ imageTransfers,
109941
+ videoTransfers,
109942
+ debugDumpEnabled,
109943
+ debugDumpDir
109944
+ } = ctx;
109945
+ const filteredStacking = elementFilter ? fullStacking.filter((e) => elementFilter.has(e.id)) : fullStacking;
109946
+ const layers = groupIntoLayers(filteredStacking);
109947
+ const shouldLog = debugDumpEnabled && debugFrameIndex >= 0;
109948
+ if (shouldLog) {
109949
+ log.info("[diag] compositeToBuffer plan", {
109950
+ frame: debugFrameIndex,
109951
+ time: time.toFixed(3),
109952
+ filterSize: elementFilter?.size,
109953
+ fullStackingCount: fullStacking.length,
109954
+ filteredCount: filteredStacking.length,
109955
+ layerCount: layers.length,
109956
+ layers: layers.map(
109957
+ (l) => l.type === "hdr" ? {
109958
+ type: "hdr",
109959
+ id: l.element.id,
109960
+ z: l.element.zIndex,
109961
+ visible: l.element.visible,
109962
+ opacity: l.element.opacity,
109963
+ bounds: `${Math.round(l.element.x)},${Math.round(l.element.y)} ${Math.round(l.element.width)}x${Math.round(l.element.height)}`
109964
+ } : { type: "dom", ids: l.elementIds }
109965
+ )
109966
+ });
109967
+ }
109968
+ for (const [layerIdx, layer] of layers.entries()) {
109969
+ if (layer.type === "hdr") {
109970
+ const before2 = shouldLog ? countNonZeroRgb48(canvas) : 0;
109971
+ const isHdrImage = nativeHdrImageIds.has(layer.element.id);
109972
+ if (isHdrImage) {
109973
+ blitHdrImageLayer(
109974
+ canvas,
109975
+ layer.element,
109976
+ hdrImageBuffers,
109977
+ width,
109978
+ height,
109979
+ log,
109980
+ imageTransfers.get(layer.element.id),
109981
+ effectiveHdr.transfer
109982
+ );
109983
+ } else {
109984
+ blitHdrVideoLayer(
109985
+ canvas,
109986
+ layer.element,
109987
+ time,
109988
+ fps,
109989
+ hdrFrameDirs,
109990
+ hdrVideoStartTimes,
109991
+ width,
109992
+ height,
109993
+ log,
109994
+ videoTransfers.get(layer.element.id),
109995
+ effectiveHdr.transfer
109996
+ );
109997
+ }
109998
+ if (shouldLog) {
109999
+ const after2 = countNonZeroRgb48(canvas);
110000
+ if (isHdrImage) {
110001
+ const buf = hdrImageBuffers.get(layer.element.id);
110002
+ log.info("[diag] hdr layer blit", {
110003
+ frame: debugFrameIndex,
110004
+ layerIdx,
110005
+ id: layer.element.id,
110006
+ kind: "image",
110007
+ pixelsAdded: after2 - before2,
110008
+ totalNonZero: after2,
110009
+ bufferDecoded: !!buf,
110010
+ bufferDims: buf ? `${buf.width}x${buf.height}` : null
110011
+ });
110012
+ } else {
110013
+ const frameDir = hdrFrameDirs.get(layer.element.id);
110014
+ const startTime = hdrVideoStartTimes.get(layer.element.id) ?? 0;
110015
+ const localTime = time - startTime;
110016
+ const frameNum = Math.floor(localTime * fps) + 1;
110017
+ const expectedFrame = frameDir ? join15(frameDir, `frame_${String(frameNum).padStart(4, "0")}.png`) : null;
110018
+ log.info("[diag] hdr layer blit", {
110019
+ frame: debugFrameIndex,
110020
+ layerIdx,
110021
+ id: layer.element.id,
110022
+ kind: "video",
110023
+ pixelsAdded: after2 - before2,
110024
+ totalNonZero: after2,
110025
+ startTime,
110026
+ localTime: localTime.toFixed(3),
110027
+ hdrFrameNum: frameNum,
110028
+ expectedFrame,
110029
+ expectedFrameExists: expectedFrame ? existsSync15(expectedFrame) : false
110030
+ });
110031
+ }
110032
+ }
110033
+ } else {
110034
+ const allElementIds = fullStacking.map((e) => e.id);
110035
+ const layerIds = new Set(layer.elementIds);
110036
+ const hideIds = allElementIds.filter((id) => !layerIds.has(id));
110037
+ await domSession.page.evaluate((t) => {
110038
+ if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t);
110039
+ }, time);
110040
+ if (beforeCaptureHook) {
110041
+ await beforeCaptureHook(domSession.page, time);
110042
+ }
110043
+ await applyDomLayerMask(domSession.page, layer.elementIds, hideIds);
110044
+ const domPng = await captureAlphaPng(domSession.page, width, height);
110045
+ await removeDomLayerMask(domSession.page, hideIds);
110046
+ try {
110047
+ const { data: domRgba } = decodePng(domPng);
110048
+ const before2 = shouldLog ? countNonZeroRgb48(canvas) : 0;
110049
+ const alphaPixels = shouldLog ? countNonZeroAlpha(domRgba) : 0;
110050
+ blitRgba8OverRgb48le(domRgba, canvas, width, height, effectiveHdr.transfer);
110051
+ if (shouldLog && debugDumpDir) {
110052
+ const after2 = countNonZeroRgb48(canvas);
110053
+ const dumpName = `frame_${String(debugFrameIndex).padStart(4, "0")}_layer_${String(layerIdx).padStart(2, "0")}_dom.png`;
110054
+ const dumpPath = join15(debugDumpDir, dumpName);
110055
+ writeFileSync4(dumpPath, domPng);
110056
+ log.info("[diag] dom layer blit", {
110057
+ frame: debugFrameIndex,
110058
+ layerIdx,
110059
+ layerIds: layer.elementIds,
110060
+ hideCount: hideIds.length,
110061
+ pngBytes: domPng.length,
110062
+ alphaPixels,
110063
+ pixelsAdded: after2 - before2,
110064
+ totalNonZero: after2,
110065
+ dumpPath
110066
+ });
110067
+ }
110068
+ } catch (err) {
110069
+ log.warn("DOM layer decode/blit failed; skipping overlay", {
110070
+ layerIds: layer.elementIds,
110071
+ error: err instanceof Error ? err.message : String(err)
110072
+ });
110073
+ }
110074
+ }
110075
+ }
110076
+ if (shouldLog && debugDumpDir) {
110077
+ const finalNonZero = countNonZeroRgb48(canvas);
110078
+ log.info("[diag] compositeToBuffer end", {
110079
+ frame: debugFrameIndex,
110080
+ finalNonZeroPixels: finalNonZero,
110081
+ totalPixels: width * height,
110082
+ coverage: (finalNonZero / (width * height) * 100).toFixed(1) + "%"
110083
+ });
110084
+ }
110085
+ }
109833
110086
  function createRenderJob(config2) {
109834
110087
  return {
109835
110088
  id: randomUUID(),
@@ -109897,6 +110150,19 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
109897
110150
  const enableChunkedEncode = cfg.enableChunkedEncode;
109898
110151
  const chunkedEncodeSize = cfg.chunkSizeFrames;
109899
110152
  const enableStreamingEncode = cfg.enableStreamingEncode;
110153
+ let peakRssBytes = 0;
110154
+ let peakHeapUsedBytes = 0;
110155
+ const sampleMemory = () => {
110156
+ try {
110157
+ const m = process.memoryUsage();
110158
+ if (m.rss > peakRssBytes) peakRssBytes = m.rss;
110159
+ if (m.heapUsed > peakHeapUsedBytes) peakHeapUsedBytes = m.heapUsed;
110160
+ } catch {
110161
+ }
110162
+ };
110163
+ sampleMemory();
110164
+ const memSamplerInterval = setInterval(sampleMemory, 250);
110165
+ memSamplerInterval.unref?.();
109900
110166
  try {
109901
110167
  const assertNotAborted = () => {
109902
110168
  if (abortSignal?.aborted) {
@@ -110174,7 +110440,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110174
110440
  videoPath = fromCompiled;
110175
110441
  }
110176
110442
  if (!existsSync15(videoPath)) return;
110177
- const meta = await extractVideoMetadata(videoPath);
110443
+ const meta = await extractMediaMetadata(videoPath);
110178
110444
  if (isHdrColorSpace(meta.colorSpace)) {
110179
110445
  nativeHdrVideoIds.add(v.id);
110180
110446
  videoTransfers.set(v.id, detectTransfer(meta.colorSpace));
@@ -110195,7 +110461,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110195
110461
  imgPath = fromCompiled;
110196
110462
  }
110197
110463
  if (!existsSync15(imgPath)) return null;
110198
- const meta = await extractVideoMetadata(imgPath);
110464
+ const meta = await extractMediaMetadata(imgPath);
110199
110465
  if (isHdrColorSpace(meta.colorSpace)) {
110200
110466
  nativeHdrImageIds.add(img.id);
110201
110467
  imageTransfers.set(img.id, detectTransfer(meta.colorSpace));
@@ -110307,6 +110573,10 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110307
110573
  format: needsAlpha ? "png" : "jpeg",
110308
110574
  quality: needsAlpha ? void 0 : job.config.quality === "draft" ? 80 : 95
110309
110575
  };
110576
+ const buildHdrCaptureOptions = () => ({
110577
+ ...captureOptions,
110578
+ skipReadinessVideoIds: Array.from(nativeHdrVideoIds)
110579
+ });
110310
110580
  const workerCount = calculateOptimalWorkers(totalFrames, job.config.workers, cfg);
110311
110581
  const FORMAT_EXT = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
110312
110582
  const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
@@ -110341,7 +110611,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110341
110611
  const domSession = await createCaptureSession(
110342
110612
  fileServer.url,
110343
110613
  framesDir,
110344
- { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
110614
+ buildHdrCaptureOptions(),
110345
110615
  createVideoFrameInjector(frameLookup),
110346
110616
  cfg
110347
110617
  );
@@ -110437,6 +110707,29 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110437
110707
  }
110438
110708
  }
110439
110709
  }
110710
+ for (const [imageId, startTime] of hdrImageStartTimes) {
110711
+ if (hdrExtractionDims.has(imageId)) continue;
110712
+ const img = composition.images.find((i) => i.id === imageId);
110713
+ if (!img) continue;
110714
+ const duration = img.end - img.start;
110715
+ const retryTime = startTime + Math.min(0.5, duration * 0.1);
110716
+ await domSession.page.evaluate((t) => {
110717
+ if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t);
110718
+ }, retryTime);
110719
+ if (domSession.onBeforeCapture) {
110720
+ await domSession.onBeforeCapture(domSession.page, retryTime);
110721
+ }
110722
+ const retryStacking = await queryElementStacking(domSession.page, nativeHdrIds);
110723
+ for (const el of retryStacking) {
110724
+ if (el.id === imageId && el.isHdr && el.layoutWidth > 0 && el.layoutHeight > 0) {
110725
+ hdrExtractionDims.set(el.id, { width: el.layoutWidth, height: el.layoutHeight });
110726
+ if (!hdrImageFitInfo.has(el.id)) {
110727
+ hdrImageFitInfo.set(el.id, { fit: el.objectFit, position: el.objectPosition });
110728
+ }
110729
+ break;
110730
+ }
110731
+ }
110732
+ }
110440
110733
  for (const [videoId, srcPath] of hdrVideoSrcPaths) {
110441
110734
  const video = composition.videos.find((v) => v.id === videoId);
110442
110735
  if (!video) continue;
@@ -110519,21 +110812,6 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110519
110812
  }
110520
110813
  assertNotAborted();
110521
110814
  try {
110522
- let countNonZeroAlpha2 = function(rgba) {
110523
- let n = 0;
110524
- for (let p = 3; p < rgba.length; p += 4) {
110525
- if (rgba[p] !== 0) n++;
110526
- }
110527
- return n;
110528
- }, countNonZeroRgb482 = function(buf) {
110529
- let n = 0;
110530
- for (let p = 0; p < buf.length; p += 6) {
110531
- if (buf[p] !== 0 || buf[p + 1] !== 0 || buf[p + 2] !== 0 || buf[p + 3] !== 0 || buf[p + 4] !== 0 || buf[p + 5] !== 0)
110532
- n++;
110533
- }
110534
- return n;
110535
- };
110536
- var countNonZeroAlpha = countNonZeroAlpha2, countNonZeroRgb48 = countNonZeroRgb482;
110537
110815
  const beforeCaptureHook = domSession.onBeforeCapture;
110538
110816
  const cleanedUpVideos = /* @__PURE__ */ new Set();
110539
110817
  const hdrVideoEndTimes = /* @__PURE__ */ new Map();
@@ -110547,153 +110825,28 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110547
110825
  if (debugDumpDir && !existsSync15(debugDumpDir)) {
110548
110826
  mkdirSync10(debugDumpDir, { recursive: true });
110549
110827
  }
110550
- async function compositeToBuffer(canvas, time, fullStacking, elementFilter, debugFrameIndex = -1) {
110551
- const filteredStacking = elementFilter ? fullStacking.filter((e) => elementFilter.has(e.id)) : fullStacking;
110552
- const layers = groupIntoLayers(filteredStacking);
110553
- const shouldLog = debugDumpEnabled && debugFrameIndex >= 0;
110554
- if (shouldLog) {
110555
- log.info("[diag] compositeToBuffer plan", {
110556
- frame: debugFrameIndex,
110557
- time: time.toFixed(3),
110558
- filterSize: elementFilter?.size,
110559
- fullStackingCount: fullStacking.length,
110560
- filteredCount: filteredStacking.length,
110561
- layerCount: layers.length,
110562
- layers: layers.map(
110563
- (l) => l.type === "hdr" ? {
110564
- type: "hdr",
110565
- id: l.element.id,
110566
- z: l.element.zIndex,
110567
- visible: l.element.visible,
110568
- opacity: l.element.opacity,
110569
- bounds: `${Math.round(l.element.x)},${Math.round(l.element.y)} ${Math.round(l.element.width)}x${Math.round(l.element.height)}`
110570
- } : { type: "dom", ids: l.elementIds }
110571
- )
110572
- });
110573
- }
110574
- for (const [layerIdx, layer] of layers.entries()) {
110575
- if (layer.type === "hdr") {
110576
- const before2 = shouldLog ? countNonZeroRgb482(canvas) : 0;
110577
- const isHdrImage = nativeHdrImageIds.has(layer.element.id);
110578
- if (isHdrImage) {
110579
- blitHdrImageLayer(
110580
- canvas,
110581
- layer.element,
110582
- hdrImageBuffers,
110583
- width,
110584
- height,
110585
- log,
110586
- imageTransfers.get(layer.element.id),
110587
- effectiveHdr?.transfer
110588
- );
110589
- } else {
110590
- blitHdrVideoLayer(
110591
- canvas,
110592
- layer.element,
110593
- time,
110594
- job.config.fps,
110595
- hdrFrameDirs,
110596
- hdrVideoStartTimes,
110597
- width,
110598
- height,
110599
- log,
110600
- videoTransfers.get(layer.element.id),
110601
- effectiveHdr?.transfer
110602
- );
110603
- }
110604
- if (shouldLog) {
110605
- const after2 = countNonZeroRgb482(canvas);
110606
- if (isHdrImage) {
110607
- const buf = hdrImageBuffers.get(layer.element.id);
110608
- log.info("[diag] hdr layer blit", {
110609
- frame: debugFrameIndex,
110610
- layerIdx,
110611
- id: layer.element.id,
110612
- kind: "image",
110613
- pixelsAdded: after2 - before2,
110614
- totalNonZero: after2,
110615
- bufferDecoded: !!buf,
110616
- bufferDims: buf ? `${buf.width}x${buf.height}` : null
110617
- });
110618
- } else {
110619
- const frameDir = hdrFrameDirs.get(layer.element.id);
110620
- const startTime = hdrVideoStartTimes.get(layer.element.id) ?? 0;
110621
- const localTime = time - startTime;
110622
- const frameNum = Math.floor(localTime * job.config.fps) + 1;
110623
- const expectedFrame = frameDir ? join15(frameDir, `frame_${String(frameNum).padStart(4, "0")}.png`) : null;
110624
- log.info("[diag] hdr layer blit", {
110625
- frame: debugFrameIndex,
110626
- layerIdx,
110627
- id: layer.element.id,
110628
- kind: "video",
110629
- pixelsAdded: after2 - before2,
110630
- totalNonZero: after2,
110631
- startTime,
110632
- localTime: localTime.toFixed(3),
110633
- hdrFrameNum: frameNum,
110634
- expectedFrame,
110635
- expectedFrameExists: expectedFrame ? existsSync15(expectedFrame) : false
110636
- });
110637
- }
110638
- }
110639
- } else {
110640
- const allElementIds = fullStacking.map((e) => e.id);
110641
- const layerIds = new Set(layer.elementIds);
110642
- const hideIds = allElementIds.filter((id) => !layerIds.has(id));
110643
- await domSession.page.evaluate((t) => {
110644
- if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t);
110645
- }, time);
110646
- if (beforeCaptureHook) {
110647
- await beforeCaptureHook(domSession.page, time);
110648
- }
110649
- await applyDomLayerMask(domSession.page, layer.elementIds, hideIds);
110650
- const domPng = await captureAlphaPng(domSession.page, width, height);
110651
- await removeDomLayerMask(domSession.page, hideIds);
110652
- try {
110653
- const { data: domRgba } = decodePng(domPng);
110654
- if (!effectiveHdr) {
110655
- throw new Error(
110656
- "Invariant violation: effectiveHdr is undefined inside HDR layer branch"
110657
- );
110658
- }
110659
- const before2 = shouldLog ? countNonZeroRgb482(canvas) : 0;
110660
- const alphaPixels = shouldLog ? countNonZeroAlpha2(domRgba) : 0;
110661
- blitRgba8OverRgb48le(domRgba, canvas, width, height, effectiveHdr.transfer);
110662
- if (shouldLog && debugDumpDir) {
110663
- const after2 = countNonZeroRgb482(canvas);
110664
- const dumpName = `frame_${String(debugFrameIndex).padStart(4, "0")}_layer_${String(layerIdx).padStart(2, "0")}_dom.png`;
110665
- const dumpPath = join15(debugDumpDir, dumpName);
110666
- writeFileSync4(dumpPath, domPng);
110667
- log.info("[diag] dom layer blit", {
110668
- frame: debugFrameIndex,
110669
- layerIdx,
110670
- layerIds: layer.elementIds,
110671
- hideCount: hideIds.length,
110672
- pngBytes: domPng.length,
110673
- alphaPixels,
110674
- pixelsAdded: after2 - before2,
110675
- totalNonZero: after2,
110676
- dumpPath
110677
- });
110678
- }
110679
- } catch (err) {
110680
- log.warn("DOM layer decode/blit failed; skipping overlay", {
110681
- layerIds: layer.elementIds,
110682
- error: err instanceof Error ? err.message : String(err)
110683
- });
110684
- }
110685
- }
110686
- }
110687
- if (shouldLog && debugDumpDir) {
110688
- const finalNonZero = countNonZeroRgb482(canvas);
110689
- log.info("[diag] compositeToBuffer end", {
110690
- frame: debugFrameIndex,
110691
- finalNonZeroPixels: finalNonZero,
110692
- totalPixels: width * height,
110693
- coverage: (finalNonZero / (width * height) * 100).toFixed(1) + "%"
110694
- });
110695
- }
110828
+ if (!effectiveHdr) {
110829
+ throw new Error(
110830
+ "Internal: HDR render path entered without effectiveHdr \u2014 this is a bug."
110831
+ );
110696
110832
  }
110833
+ const hdrCompositeCtx = {
110834
+ log,
110835
+ domSession,
110836
+ beforeCaptureHook,
110837
+ width,
110838
+ height,
110839
+ fps: job.config.fps,
110840
+ effectiveHdr,
110841
+ nativeHdrImageIds,
110842
+ hdrImageBuffers,
110843
+ hdrFrameDirs,
110844
+ hdrVideoStartTimes,
110845
+ imageTransfers,
110846
+ videoTransfers,
110847
+ debugDumpEnabled,
110848
+ debugDumpDir
110849
+ };
110697
110850
  const bufSize = width * height * 6;
110698
110851
  const hasTransitions = transitionRanges.length > 0;
110699
110852
  const transBufferA = hasTransitions ? Buffer.alloc(bufSize) : null;
@@ -110713,7 +110866,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110713
110866
  const activeTransition = transitionRanges.find(
110714
110867
  (t) => i >= t.startFrame && i <= t.endFrame
110715
110868
  );
110716
- if (i % 30 === 0) {
110869
+ if (i % 30 === 0 && (log.isLevelEnabled?.("debug") ?? true)) {
110717
110870
  const hdrEl = stackingInfo.find((e) => e.isHdr);
110718
110871
  log.debug("[Render] HDR layer composite frame", {
110719
110872
  frame: i,
@@ -110801,7 +110954,14 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110801
110954
  hdrEncoder.writeFrame(transOutput);
110802
110955
  } else {
110803
110956
  normalCanvas.fill(0);
110804
- await compositeToBuffer(normalCanvas, time, stackingInfo, void 0, i);
110957
+ await compositeHdrFrame(
110958
+ hdrCompositeCtx,
110959
+ normalCanvas,
110960
+ time,
110961
+ stackingInfo,
110962
+ void 0,
110963
+ i
110964
+ );
110805
110965
  if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
110806
110966
  const previewPath = join15(
110807
110967
  debugDumpDir,
@@ -110920,7 +111080,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110920
111080
  fileServer.url,
110921
111081
  workDir,
110922
111082
  tasks,
110923
- { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
111083
+ buildHdrCaptureOptions(),
110924
111084
  () => createVideoFrameInjector(frameLookup),
110925
111085
  abortSignal,
110926
111086
  (progress) => {
@@ -110950,7 +111110,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
110950
111110
  const session = probeSession ?? await createCaptureSession(
110951
111111
  fileServer.url,
110952
111112
  framesDir,
110953
- { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
111113
+ buildHdrCaptureOptions(),
110954
111114
  videoInjector,
110955
111115
  cfg
110956
111116
  );
@@ -111002,7 +111162,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111002
111162
  fileServer.url,
111003
111163
  workDir,
111004
111164
  tasks,
111005
- { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
111165
+ buildHdrCaptureOptions(),
111006
111166
  () => createVideoFrameInjector(frameLookup),
111007
111167
  abortSignal,
111008
111168
  (progress) => {
@@ -111033,7 +111193,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111033
111193
  const session = probeSession ?? await createCaptureSession(
111034
111194
  fileServer.url,
111035
111195
  framesDir,
111036
- { ...captureOptions, skipReadinessVideoIds: Array.from(nativeHdrVideoIds) },
111196
+ buildHdrCaptureOptions(),
111037
111197
  videoInjector,
111038
111198
  cfg
111039
111199
  );
@@ -111149,6 +111309,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111149
111309
  job.outputPath = outputPath;
111150
111310
  updateJobStatus(job, "complete", "Render complete", 100, onProgress);
111151
111311
  const totalElapsed = Date.now() - pipelineStart;
111312
+ sampleMemory();
111152
111313
  const perfSummary = {
111153
111314
  renderId: job.id,
111154
111315
  totalElapsedMs: totalElapsed,
@@ -111164,7 +111325,9 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111164
111325
  audioCount: composition.audios.length,
111165
111326
  stages: perfStages,
111166
111327
  hdrDiagnostics: hdrDiagnostics.videoExtractionFailures > 0 || hdrDiagnostics.imageDecodeFailures > 0 ? { ...hdrDiagnostics } : void 0,
111167
- captureAvgMs: totalFrames > 0 ? Math.round((perfStages.captureMs ?? 0) / totalFrames) : void 0
111328
+ captureAvgMs: totalFrames > 0 ? Math.round((perfStages.captureMs ?? 0) / totalFrames) : void 0,
111329
+ peakRssMb: Math.round(peakRssBytes / (1024 * 1024)),
111330
+ peakHeapUsedMb: Math.round(peakHeapUsedBytes / (1024 * 1024))
111168
111331
  };
111169
111332
  job.perfSummary = perfSummary;
111170
111333
  if (job.config.debug) {
@@ -111272,6 +111435,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
111272
111435
  }
111273
111436
  if (restoreLogger) restoreLogger();
111274
111437
  throw error;
111438
+ } finally {
111439
+ clearInterval(memSamplerInterval);
111275
111440
  }
111276
111441
  }
111277
111442