@hyperframes/producer 0.4.6 → 0.4.8

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.
@@ -5117,12 +5117,12 @@ var require_common = __commonJS({
5117
5117
  createDebug.skips = [];
5118
5118
  createDebug.formatters = {};
5119
5119
  function selectColor(namespace) {
5120
- let hash = 0;
5120
+ let hash2 = 0;
5121
5121
  for (let i = 0; i < namespace.length; i++) {
5122
- hash = (hash << 5) - hash + namespace.charCodeAt(i);
5123
- hash |= 0;
5122
+ hash2 = (hash2 << 5) - hash2 + namespace.charCodeAt(i);
5123
+ hash2 |= 0;
5124
5124
  }
5125
- return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
5125
+ return createDebug.colors[Math.abs(hash2) % createDebug.colors.length];
5126
5126
  }
5127
5127
  createDebug.selectColor = selectColor;
5128
5128
  function createDebug(namespace) {
@@ -54350,25 +54350,25 @@ var require_data = __commonJS({
54350
54350
  var notmodified_1 = __importDefault2(require_notmodified());
54351
54351
  var debug6 = (0, debug_1.default)("get-uri:data");
54352
54352
  var DataReadable = class extends stream_1.Readable {
54353
- constructor(hash, buf) {
54353
+ constructor(hash2, buf) {
54354
54354
  super();
54355
54355
  this.push(buf);
54356
54356
  this.push(null);
54357
- this.hash = hash;
54357
+ this.hash = hash2;
54358
54358
  }
54359
54359
  };
54360
54360
  var data = async ({ href: uri }, { cache } = {}) => {
54361
54361
  const shasum = (0, crypto_1.createHash)("sha1");
54362
54362
  shasum.update(uri);
54363
- const hash = shasum.digest("hex");
54364
- debug6('generated SHA1 hash for "data:" URI: %o', hash);
54365
- if (cache?.hash === hash) {
54366
- debug6("got matching cache SHA1 hash: %o", hash);
54363
+ const hash2 = shasum.digest("hex");
54364
+ debug6('generated SHA1 hash for "data:" URI: %o', hash2);
54365
+ if (cache?.hash === hash2) {
54366
+ debug6("got matching cache SHA1 hash: %o", hash2);
54367
54367
  throw new notmodified_1.default();
54368
54368
  } else {
54369
54369
  debug6('creating Readable stream from "data:" URI buffer');
54370
54370
  const { buffer } = (0, data_uri_to_buffer_1.dataUriToBuffer)(uri);
54371
- return new DataReadable(hash, Buffer.from(buffer));
54371
+ return new DataReadable(hash2, Buffer.from(buffer));
54372
54372
  }
54373
54373
  };
54374
54374
  exports.data = data;
@@ -75363,14 +75363,14 @@ var require_dist10 = __commonJS({
75363
75363
  (0, quickjs_emscripten_1.getQuickJS)(),
75364
75364
  this.loadPacFile()
75365
75365
  ]);
75366
- const hash = crypto3.createHash("sha1").update(code).digest("hex");
75367
- if (this.resolver && this.resolverHash === hash) {
75366
+ const hash2 = crypto3.createHash("sha1").update(code).digest("hex");
75367
+ if (this.resolver && this.resolverHash === hash2) {
75368
75368
  debug6("Same sha1 hash for code - contents have not changed, reusing previous proxy resolver");
75369
75369
  return this.resolver;
75370
75370
  }
75371
75371
  debug6("Creating new proxy resolver instance");
75372
75372
  this.resolver = (0, pac_resolver_1.createPacResolver)(qjs, code, this.opts);
75373
- this.resolverHash = hash;
75373
+ this.resolverHash = hash2;
75374
75374
  return this.resolver;
75375
75375
  } catch (err) {
75376
75376
  if (this.resolver && err.code === "ENOTMODIFIED") {
@@ -92210,6 +92210,7 @@ import {
92210
92210
  mkdirSync as mkdirSync10,
92211
92211
  rmSync as rmSync3,
92212
92212
  readFileSync as readFileSync9,
92213
+ readdirSync as readdirSync6,
92213
92214
  writeFileSync as writeFileSync4,
92214
92215
  copyFileSync as copyFileSync2,
92215
92216
  appendFileSync
@@ -101457,6 +101458,8 @@ var DEFAULT_CONFIG = {
101457
101458
  ffmpegEncodeTimeout: 6e5,
101458
101459
  ffmpegProcessTimeout: 3e5,
101459
101460
  ffmpegStreamingTimeout: 6e5,
101461
+ hdr: false,
101462
+ hdrAutoDetect: true,
101460
101463
  audioGain: 1.35,
101461
101464
  frameDataUriCacheLimit: 256,
101462
101465
  playerReadyTimeout: 45e3,
@@ -101513,6 +101516,12 @@ function resolveConfig(overrides) {
101513
101516
  "FFMPEG_STREAMING_TIMEOUT_MS",
101514
101517
  DEFAULT_CONFIG.ffmpegStreamingTimeout
101515
101518
  ),
101519
+ hdr: (() => {
101520
+ const raw2 = env2("PRODUCER_HDR_TRANSFER");
101521
+ if (raw2 === "hlg" || raw2 === "pq") return { transfer: raw2 };
101522
+ return void 0;
101523
+ })(),
101524
+ hdrAutoDetect: envBool("PRODUCER_HDR_AUTO_DETECT", DEFAULT_CONFIG.hdrAutoDetect),
101516
101525
  audioGain: envNum("PRODUCER_AUDIO_GAIN", DEFAULT_CONFIG.audioGain),
101517
101526
  frameDataUriCacheLimit: Math.max(
101518
101527
  32,
@@ -101709,7 +101718,8 @@ function buildChromeArgs(options, config2) {
101709
101718
  "--font-render-hinting=none",
101710
101719
  "--force-color-profile=srgb",
101711
101720
  `--window-size=${options.width},${options.height}`,
101712
- // Remotion perf flags — prevent Chrome from throttling background tabs/timers
101721
+ // Prevent Chrome from throttling background tabs/timers — critical when the
101722
+ // page is offscreen during headless capture
101713
101723
  "--disable-background-timer-throttling",
101714
101724
  "--disable-backgrounding-occluded-windows",
101715
101725
  "--disable-renderer-backgrounding",
@@ -102423,6 +102433,7 @@ var mediaRules = [
102423
102433
  const timedTagPositions = [];
102424
102434
  for (const tag of tags) {
102425
102435
  if (tag.name === "video" || tag.name === "audio") continue;
102436
+ if (readAttr(tag.raw, "data-composition-id")) continue;
102426
102437
  if (readAttr(tag.raw, "data-start")) {
102427
102438
  timedTagPositions.push({
102428
102439
  name: tag.name,
@@ -103529,7 +103540,8 @@ function lintHyperframeHtml(html, options = {}) {
103529
103540
  }
103530
103541
 
103531
103542
  // ../core/src/compiler/rewriteSubCompPaths.ts
103532
- import { join as join4, resolve as resolve6, dirname as dirname4 } from "path";
103543
+ import { posix } from "path";
103544
+ var { join: join4, resolve: resolve6, dirname: dirname4 } = posix;
103533
103545
  var PATH_ATTRS = ["src", "href"];
103534
103546
  var CSS_URL_RE = /\burl\(\s*(["']?)([^)"']+)\1\s*\)/g;
103535
103547
  function isAbsoluteOrSpecial(val) {
@@ -103696,6 +103708,83 @@ async function pageScreenshotCapture(page, options) {
103696
103708
  });
103697
103709
  return Buffer.from(result.data, "base64");
103698
103710
  }
103711
+ var TRANSPARENT_BG_STYLE_ID = "__hf_transparent_bg__";
103712
+ async function initTransparentBackground(page) {
103713
+ const client = await getCdpSession(page);
103714
+ await client.send("Emulation.setDefaultBackgroundColorOverride", {
103715
+ color: { r: 0, g: 0, b: 0, a: 0 }
103716
+ });
103717
+ await page.evaluate((styleId) => {
103718
+ if (document.getElementById(styleId)) return;
103719
+ const style = document.createElement("style");
103720
+ style.id = styleId;
103721
+ style.textContent = "html,body,[data-composition-id]{background:transparent !important;background-color:transparent !important;background-image:none !important;}";
103722
+ document.head.appendChild(style);
103723
+ }, TRANSPARENT_BG_STYLE_ID);
103724
+ }
103725
+ async function captureAlphaPng(page, width, height) {
103726
+ const client = await getCdpSession(page);
103727
+ const result = await client.send("Page.captureScreenshot", {
103728
+ format: "png",
103729
+ fromSurface: true,
103730
+ captureBeyondViewport: false,
103731
+ optimizeForSpeed: false,
103732
+ // must be false to preserve alpha
103733
+ clip: { x: 0, y: 0, width, height, scale: 1 }
103734
+ });
103735
+ return Buffer.from(result.data, "base64");
103736
+ }
103737
+ var DOM_LAYER_MASK_STYLE_ID = "__hf_dom_layer_mask__";
103738
+ async function applyDomLayerMask(page, showIds, extraHideIds) {
103739
+ await page.evaluate(
103740
+ (args) => {
103741
+ const existing = document.getElementById(args.styleId);
103742
+ if (existing) existing.remove();
103743
+ const showSelectors = [];
103744
+ for (const id of args.show) {
103745
+ const escaped = CSS.escape(id);
103746
+ showSelectors.push(`#${escaped}`, `#${escaped} *`);
103747
+ const renderEscaped = CSS.escape(`__render_frame_${id}__`);
103748
+ showSelectors.push(`#${renderEscaped}`, `#${renderEscaped} *`);
103749
+ }
103750
+ const massHideRule = "body *{visibility:hidden !important;}";
103751
+ const showRule = showSelectors.length === 0 ? "" : `${showSelectors.join(",")}{visibility:visible !important;}`;
103752
+ const style = document.createElement("style");
103753
+ style.id = args.styleId;
103754
+ style.textContent = `${massHideRule}
103755
+ ${showRule}`;
103756
+ document.head.appendChild(style);
103757
+ for (const id of args.hide) {
103758
+ const el = document.getElementById(id);
103759
+ if (el) {
103760
+ el.style.setProperty("visibility", "hidden", "important");
103761
+ }
103762
+ const img = document.getElementById(`__render_frame_${id}__`);
103763
+ if (img) {
103764
+ img.style.setProperty("visibility", "hidden", "important");
103765
+ }
103766
+ }
103767
+ },
103768
+ { show: showIds, hide: extraHideIds, styleId: DOM_LAYER_MASK_STYLE_ID }
103769
+ );
103770
+ }
103771
+ async function removeDomLayerMask(page, extraHideIds) {
103772
+ await page.evaluate(
103773
+ (args) => {
103774
+ const style = document.getElementById(args.styleId);
103775
+ if (style) style.remove();
103776
+ for (const id of args.hide) {
103777
+ const el = document.getElementById(id);
103778
+ if (el) {
103779
+ el.style.removeProperty("visibility");
103780
+ }
103781
+ const img = document.getElementById(`__render_frame_${id}__`);
103782
+ if (img) img.style.removeProperty("visibility");
103783
+ }
103784
+ },
103785
+ { hide: extraHideIds, styleId: DOM_LAYER_MASK_STYLE_ID }
103786
+ );
103787
+ }
103699
103788
  async function injectVideoFramesBatch(page, updates) {
103700
103789
  if (updates.length === 0) return;
103701
103790
  await page.evaluate(
@@ -103717,16 +103806,7 @@ async function injectVideoFramesBatch(page, updates) {
103717
103806
  video.parentNode?.insertBefore(img, video.nextSibling);
103718
103807
  }
103719
103808
  if (!img) continue;
103720
- if (!sourceIsStatic) {
103721
- img.style.position = computedStyle.position;
103722
- img.style.width = computedStyle.width;
103723
- img.style.height = computedStyle.height;
103724
- img.style.top = computedStyle.top;
103725
- img.style.left = computedStyle.left;
103726
- img.style.right = computedStyle.right;
103727
- img.style.bottom = computedStyle.bottom;
103728
- img.style.inset = computedStyle.inset;
103729
- } else {
103809
+ {
103730
103810
  const videoRect = video.getBoundingClientRect();
103731
103811
  const offsetLeft = Number.isFinite(video.offsetLeft) ? video.offsetLeft : 0;
103732
103812
  const offsetTop = Number.isFinite(video.offsetTop) ? video.offsetTop : 0;
@@ -103745,6 +103825,7 @@ async function injectVideoFramesBatch(page, updates) {
103745
103825
  img.style.objectPosition = computedStyle.objectPosition;
103746
103826
  img.style.zIndex = computedStyle.zIndex;
103747
103827
  for (const property of visualProperties) {
103828
+ if (property === "opacity") continue;
103748
103829
  if (sourceIsStatic && (property === "top" || property === "left" || property === "right" || property === "bottom" || property === "inset")) {
103749
103830
  continue;
103750
103831
  }
@@ -103777,14 +103858,22 @@ async function syncVideoFrameVisibility(page, activeVideoIds) {
103777
103858
  const active = new Set(ids);
103778
103859
  const videos = Array.from(document.querySelectorAll("video[data-start]"));
103779
103860
  for (const video of videos) {
103780
- if (active.has(video.id)) continue;
103781
- video.style.removeProperty("display");
103782
- video.style.setProperty("visibility", "hidden", "important");
103783
- video.style.setProperty("opacity", "0", "important");
103784
- video.style.setProperty("pointer-events", "none", "important");
103785
103861
  const img = video.nextElementSibling;
103786
- if (img && img.classList.contains("__render_frame__")) {
103787
- img.style.visibility = "hidden";
103862
+ const hasImg = img && img.classList.contains("__render_frame__");
103863
+ if (active.has(video.id)) {
103864
+ video.style.setProperty("visibility", "hidden", "important");
103865
+ video.style.setProperty("pointer-events", "none", "important");
103866
+ if (hasImg) {
103867
+ img.style.visibility = "visible";
103868
+ }
103869
+ } else {
103870
+ video.style.removeProperty("display");
103871
+ video.style.setProperty("visibility", "hidden", "important");
103872
+ video.style.setProperty("opacity", "0", "important");
103873
+ video.style.setProperty("pointer-events", "none", "important");
103874
+ if (hasImg) {
103875
+ img.style.visibility = "hidden";
103876
+ }
103788
103877
  }
103789
103878
  }
103790
103879
  }, activeVideoIds);
@@ -104241,7 +104330,7 @@ var ENCODER_PRESETS = {
104241
104330
  standard: { preset: "medium", quality: 18, codec: "h264" },
104242
104331
  high: { preset: "slow", quality: 15, codec: "h264" }
104243
104332
  };
104244
- function getEncoderPreset(quality, format3 = "mp4") {
104333
+ function getEncoderPreset(quality, format3 = "mp4", hdr) {
104245
104334
  const base = ENCODER_PRESETS[quality];
104246
104335
  if (format3 === "webm") {
104247
104336
  return {
@@ -104259,6 +104348,15 @@ function getEncoderPreset(quality, format3 = "mp4") {
104259
104348
  pixelFormat: "yuva444p10le"
104260
104349
  };
104261
104350
  }
104351
+ if (hdr) {
104352
+ return {
104353
+ preset: base.preset === "ultrafast" ? "fast" : base.preset,
104354
+ quality: base.quality,
104355
+ codec: "h265",
104356
+ pixelFormat: "yuv420p10le",
104357
+ hdr
104358
+ };
104359
+ }
104262
104360
  return { ...base, pixelFormat: "yuv420p" };
104263
104361
  }
104264
104362
  function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
@@ -104316,6 +104414,9 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
104316
104414
  args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}`);
104317
104415
  }
104318
104416
  }
104417
+ if (codec === "h265") {
104418
+ args.push("-tag:v", "hvc1");
104419
+ }
104319
104420
  } else if (codec === "vp9") {
104320
104421
  args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
104321
104422
  args.push("-deadline", preset === "ultrafast" ? "realtime" : "good");
@@ -104622,31 +104723,83 @@ async function applyFaststart(inputPath, outputPath, signal, config2) {
104622
104723
  import { spawn as spawn6 } from "child_process";
104623
104724
  import { existsSync as existsSync6, mkdirSync as mkdirSync3, statSync as statSync4 } from "fs";
104624
104725
  import { dirname as dirname6 } from "path";
104726
+
104727
+ // ../engine/src/utils/hdr.ts
104728
+ function isHdrColorSpace(cs) {
104729
+ if (!cs) return false;
104730
+ return cs.colorPrimaries.includes("bt2020") || cs.colorSpace.includes("bt2020") || cs.colorTransfer === "smpte2084" || cs.colorTransfer === "arib-std-b67";
104731
+ }
104732
+ function detectTransfer(cs) {
104733
+ if (cs?.colorTransfer === "smpte2084") return "pq";
104734
+ return "hlg";
104735
+ }
104736
+ var DEFAULT_HDR10_MASTERING = {
104737
+ masterDisplay: "G(13250,34500)B(7500,3000)R(34000,16000)WP(15635,16450)L(10000000,1)",
104738
+ maxCll: "1000,400"
104739
+ };
104740
+ function getHdrEncoderColorParams(transfer, mastering = DEFAULT_HDR10_MASTERING) {
104741
+ const colorTrc = transfer === "pq" ? "smpte2084" : "arib-std-b67";
104742
+ const tagging = `colorprim=bt2020:transfer=${colorTrc}:colormatrix=bt2020nc`;
104743
+ const metadata = `master-display=${mastering.masterDisplay}:max-cll=${mastering.maxCll}`;
104744
+ return {
104745
+ colorPrimaries: "bt2020",
104746
+ colorTrc,
104747
+ colorspace: "bt2020nc",
104748
+ pixelFormat: "yuv420p10le",
104749
+ x265ColorParams: `${tagging}:${metadata}`,
104750
+ mastering
104751
+ };
104752
+ }
104753
+ function analyzeCompositionHdr(colorSpaces) {
104754
+ let hasPq = false;
104755
+ let hasHdr = false;
104756
+ for (const cs of colorSpaces) {
104757
+ if (!isHdrColorSpace(cs)) continue;
104758
+ hasHdr = true;
104759
+ if (cs?.colorTransfer === "smpte2084") hasPq = true;
104760
+ }
104761
+ if (!hasHdr) return { hasHdr: false, dominantTransfer: null };
104762
+ const dominantTransfer = hasPq ? "pq" : "hlg";
104763
+ return { hasHdr: true, dominantTransfer };
104764
+ }
104765
+
104766
+ // ../engine/src/services/streamingEncoder.ts
104625
104767
  function createFrameReorderBuffer(startFrame, endFrame) {
104626
- let nextFrame = startFrame;
104627
- let waiters = [];
104628
- const resolveWaiters = () => {
104629
- for (const waiter of waiters.slice()) {
104630
- if (waiter.frame === nextFrame) {
104631
- waiter.resolve();
104632
- waiters = waiters.filter((w) => w !== waiter);
104633
- }
104768
+ let cursor = startFrame;
104769
+ const pending = /* @__PURE__ */ new Map();
104770
+ const enqueueAt = (frame, resolve13) => {
104771
+ const list = pending.get(frame);
104772
+ if (list === void 0) {
104773
+ pending.set(frame, [resolve13]);
104774
+ } else {
104775
+ list.push(resolve13);
104634
104776
  }
104635
104777
  };
104636
- return {
104637
- waitForFrame: (frame) => new Promise((resolve13) => {
104638
- waiters.push({ frame, resolve: resolve13 });
104639
- resolveWaiters();
104640
- }),
104641
- advanceTo: (frame) => {
104642
- nextFrame = frame;
104643
- resolveWaiters();
104644
- },
104645
- waitForAllDone: () => new Promise((resolve13) => {
104646
- waiters.push({ frame: endFrame, resolve: resolve13 });
104647
- resolveWaiters();
104648
- })
104778
+ const flushAt = (frame) => {
104779
+ const list = pending.get(frame);
104780
+ if (list === void 0) return;
104781
+ pending.delete(frame);
104782
+ for (const resolve13 of list) resolve13();
104783
+ };
104784
+ const waitForFrame = (frame) => new Promise((resolve13) => {
104785
+ if (frame === cursor) {
104786
+ resolve13();
104787
+ return;
104788
+ }
104789
+ enqueueAt(frame, resolve13);
104790
+ });
104791
+ const advanceTo = (frame) => {
104792
+ cursor = frame;
104793
+ flushAt(frame);
104649
104794
  };
104795
+ const waitForAllDone = () => new Promise((resolve13) => {
104796
+ if (cursor >= endFrame) {
104797
+ resolve13();
104798
+ return;
104799
+ }
104800
+ enqueueAt(endFrame, resolve13);
104801
+ });
104802
+ return { waitForFrame, advanceTo, waitForAllDone };
104650
104803
  }
104651
104804
  function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
104652
104805
  const {
@@ -104659,19 +104812,36 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
104659
104812
  useGpu = false,
104660
104813
  imageFormat = "jpeg"
104661
104814
  } = options;
104662
- const inputCodec = imageFormat === "png" ? "png" : "mjpeg";
104663
- const args = [
104664
- "-f",
104665
- "image2pipe",
104666
- "-vcodec",
104667
- inputCodec,
104668
- "-framerate",
104669
- String(fps),
104670
- "-i",
104671
- "-",
104672
- "-r",
104673
- String(fps)
104674
- ];
104815
+ const args = [];
104816
+ if (options.rawInputFormat) {
104817
+ const hdrTransfer = options.hdr?.transfer;
104818
+ const inputColorTrc = hdrTransfer === "pq" ? "smpte2084" : hdrTransfer === "hlg" ? "arib-std-b67" : void 0;
104819
+ args.push(
104820
+ "-f",
104821
+ "rawvideo",
104822
+ "-pix_fmt",
104823
+ options.rawInputFormat,
104824
+ "-s",
104825
+ `${options.width}x${options.height}`,
104826
+ "-framerate",
104827
+ String(fps)
104828
+ );
104829
+ if (inputColorTrc) {
104830
+ args.push(
104831
+ "-color_primaries",
104832
+ "bt2020",
104833
+ "-color_trc",
104834
+ inputColorTrc,
104835
+ "-colorspace",
104836
+ "bt2020nc"
104837
+ );
104838
+ }
104839
+ args.push("-i", "-");
104840
+ } else {
104841
+ const inputCodec = imageFormat === "png" ? "png" : "mjpeg";
104842
+ args.push("-f", "image2pipe", "-vcodec", inputCodec, "-framerate", String(fps), "-i", "-");
104843
+ }
104844
+ args.push("-r", String(fps));
104675
104845
  const shouldUseGpu = useGpu && gpuEncoder !== null;
104676
104846
  if (codec === "h264" || codec === "h265") {
104677
104847
  if (shouldUseGpu) {
@@ -104709,12 +104879,15 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
104709
104879
  if (bitrate) args.push("-b:v", bitrate);
104710
104880
  else args.push("-crf", String(quality));
104711
104881
  const xParamsFlag = codec === "h264" ? "-x264-params" : "-x265-params";
104712
- const colorParams = "colorprim=bt709:transfer=bt709:colormatrix=bt709";
104882
+ const colorParams = options.rawInputFormat && options.hdr ? getHdrEncoderColorParams(options.hdr.transfer).x265ColorParams : "colorprim=bt709:transfer=bt709:colormatrix=bt709";
104713
104883
  if (preset === "ultrafast") {
104714
104884
  args.push(xParamsFlag, `aq-mode=3:${colorParams}`);
104715
104885
  } else {
104716
104886
  args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}`);
104717
104887
  }
104888
+ if (codec === "h265") {
104889
+ args.push("-tag:v", "hvc1");
104890
+ }
104718
104891
  }
104719
104892
  } else if (codec === "vp9") {
104720
104893
  args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
@@ -104730,17 +104903,31 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
104730
104903
  return [...args, "-y", outputPath];
104731
104904
  }
104732
104905
  if (codec === "h264" || codec === "h265") {
104733
- args.push(
104734
- "-colorspace:v",
104735
- "bt709",
104736
- "-color_primaries:v",
104737
- "bt709",
104738
- "-color_trc:v",
104739
- "bt709",
104740
- "-color_range",
104741
- "tv"
104742
- );
104743
- if (gpuEncoder === "vaapi") {
104906
+ if (options.rawInputFormat && options.hdr) {
104907
+ args.push(
104908
+ "-colorspace:v",
104909
+ "bt2020nc",
104910
+ "-color_primaries:v",
104911
+ "bt2020",
104912
+ "-color_trc:v",
104913
+ options.hdr.transfer === "pq" ? "smpte2084" : "arib-std-b67",
104914
+ "-color_range",
104915
+ "tv"
104916
+ );
104917
+ } else {
104918
+ args.push(
104919
+ "-colorspace:v",
104920
+ "bt709",
104921
+ "-color_primaries:v",
104922
+ "bt709",
104923
+ "-color_trc:v",
104924
+ "bt709",
104925
+ "-color_range",
104926
+ "tv"
104927
+ );
104928
+ }
104929
+ if (options.rawInputFormat) {
104930
+ } else if (gpuEncoder === "vaapi") {
104744
104931
  const vfIdx = args.indexOf("-vf");
104745
104932
  if (vfIdx !== -1) {
104746
104933
  args[vfIdx + 1] = `scale=in_range=pc:out_range=tv,${args[vfIdx + 1]}`;
@@ -104810,14 +104997,16 @@ Process error: ${err.message}`;
104810
104997
  if (exitStatus !== "running" || !ffmpeg.stdin || ffmpeg.stdin.destroyed) {
104811
104998
  return false;
104812
104999
  }
104813
- return ffmpeg.stdin.write(buffer);
105000
+ const copy = Buffer.from(buffer);
105001
+ return ffmpeg.stdin.write(copy);
104814
105002
  },
104815
105003
  close: async () => {
104816
105004
  clearTimeout(timer2);
104817
105005
  if (signal) signal.removeEventListener("abort", onAbort);
104818
- if (ffmpeg.stdin && !ffmpeg.stdin.destroyed) {
105006
+ const stdin = ffmpeg.stdin;
105007
+ if (stdin && !stdin.destroyed) {
104819
105008
  await new Promise((resolve13) => {
104820
- ffmpeg.stdin.end(() => resolve13());
105009
+ stdin.end(() => resolve13());
104821
105010
  });
104822
105011
  }
104823
105012
  await exitPromise;
@@ -104921,6 +105110,10 @@ async function extractVideoMetadata(filePath) {
104921
105110
  const avgFps = parseFrameRate(videoStream.avg_frame_rate);
104922
105111
  const fps = avgFps || rFps;
104923
105112
  const isVFR = rFps > 0 && avgFps > 0 && Math.abs(rFps - avgFps) / Math.max(rFps, avgFps) > 0.1;
105113
+ const colorTransfer = videoStream.color_transfer || "";
105114
+ const colorPrimaries = videoStream.color_primaries || "";
105115
+ const colorSpaceVal = videoStream.color_space || "";
105116
+ const hasColorInfo = !!(colorTransfer || colorPrimaries || colorSpaceVal);
104924
105117
  return {
104925
105118
  durationSeconds: output2.format.duration ? parseFloat(output2.format.duration) : 0,
104926
105119
  width: videoStream.width || 0,
@@ -104928,7 +105121,8 @@ async function extractVideoMetadata(filePath) {
104928
105121
  fps,
104929
105122
  videoCodec: videoStream.codec_name || "unknown",
104930
105123
  hasAudio: output2.streams.some((s) => s.codec_type === "audio"),
104931
- isVFR
105124
+ isVFR,
105125
+ colorSpace: hasColorInfo ? { colorTransfer, colorPrimaries, colorSpace: colorSpaceVal } : null
104932
105126
  };
104933
105127
  })();
104934
105128
  videoMetadataCache.set(filePath, probePromise);
@@ -105033,10 +105227,10 @@ import { finished } from "stream/promises";
105033
105227
  var downloadPathCache = /* @__PURE__ */ new Map();
105034
105228
  var inFlightDownloads = /* @__PURE__ */ new Map();
105035
105229
  function getFilenameFromUrl(url) {
105036
- const hash = createHash("md5").update(url).digest("hex").slice(0, 12);
105230
+ const hash2 = createHash("md5").update(url).digest("hex").slice(0, 12);
105037
105231
  const urlObj = new URL(url);
105038
105232
  const ext = extname2(urlObj.pathname) || ".mp4";
105039
- return `download_${hash}${ext}`;
105233
+ return `download_${hash2}${ext}`;
105040
105234
  }
105041
105235
  async function downloadToTemp(url, destDir, timeoutMs = 3e5) {
105042
105236
  const cachedPath = downloadPathCache.get(url);
@@ -105136,18 +105330,20 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
105136
105330
  const metadata = await extractVideoMetadata(videoPath);
105137
105331
  const framePattern = `frame_%05d.${format3}`;
105138
105332
  const outputPattern = join8(videoOutputDir, framePattern);
105139
- const args = [
105140
- "-ss",
105141
- String(startTime),
105142
- "-i",
105143
- videoPath,
105144
- "-t",
105145
- String(duration),
105146
- "-vf",
105147
- `fps=${fps}`,
105148
- "-q:v",
105149
- format3 === "jpg" ? String(Math.ceil((100 - quality) / 3)) : "0"
105150
- ];
105333
+ const isHdr = isHdrColorSpace(metadata.colorSpace);
105334
+ const isMacOS = process.platform === "darwin";
105335
+ const args = [];
105336
+ if (isHdr && isMacOS) {
105337
+ args.push("-hwaccel", "videotoolbox");
105338
+ }
105339
+ args.push("-ss", String(startTime), "-i", videoPath, "-t", String(duration));
105340
+ const vfFilters = [];
105341
+ if (isHdr && isMacOS) {
105342
+ vfFilters.push("format=nv12");
105343
+ }
105344
+ vfFilters.push(`fps=${fps}`);
105345
+ args.push("-vf", vfFilters.join(","));
105346
+ args.push("-q:v", format3 === "jpg" ? String(Math.ceil((100 - quality) / 3)) : "0");
105151
105347
  if (format3 === "png") args.push("-compression_level", "6");
105152
105348
  args.push("-y", outputPattern);
105153
105349
  return new Promise((resolve13, reject) => {
@@ -105207,30 +105403,100 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
105207
105403
  });
105208
105404
  });
105209
105405
  }
105406
+ async function convertSdrToHdr(inputPath, outputPath, signal, config2) {
105407
+ const timeout2 = config2?.ffmpegProcessTimeout ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
105408
+ const args = [
105409
+ "-i",
105410
+ inputPath,
105411
+ "-vf",
105412
+ "colorspace=all=bt2020:iall=bt709:range=tv",
105413
+ "-color_primaries",
105414
+ "bt2020",
105415
+ "-color_trc",
105416
+ "arib-std-b67",
105417
+ "-colorspace",
105418
+ "bt2020nc",
105419
+ "-c:v",
105420
+ "libx264",
105421
+ "-preset",
105422
+ "fast",
105423
+ "-crf",
105424
+ "16",
105425
+ "-c:a",
105426
+ "copy",
105427
+ "-y",
105428
+ outputPath
105429
+ ];
105430
+ const result = await runFfmpeg(args, { signal, timeout: timeout2 });
105431
+ if (!result.success) {
105432
+ throw new Error(
105433
+ `SDR\u2192HDR conversion failed (exit ${result.exitCode}): ${result.stderr.slice(-300)}`
105434
+ );
105435
+ }
105436
+ }
105210
105437
  async function extractAllVideoFrames(videos, baseDir, options, signal, config2, compiledDir) {
105211
105438
  const startTime = Date.now();
105212
105439
  const extracted = [];
105213
105440
  const errors = [];
105214
105441
  let totalFramesExtracted = 0;
105442
+ const resolvedVideos = [];
105443
+ for (const video of videos) {
105444
+ if (signal?.aborted) break;
105445
+ try {
105446
+ let videoPath = video.src;
105447
+ if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
105448
+ const fromCompiled = compiledDir ? join8(compiledDir, videoPath) : null;
105449
+ videoPath = fromCompiled && existsSync8(fromCompiled) ? fromCompiled : join8(baseDir, videoPath);
105450
+ }
105451
+ if (isHttpUrl(videoPath)) {
105452
+ const downloadDir = join8(options.outputDir, "_downloads");
105453
+ mkdirSync5(downloadDir, { recursive: true });
105454
+ videoPath = await downloadToTemp(videoPath, downloadDir);
105455
+ }
105456
+ if (!existsSync8(videoPath)) {
105457
+ errors.push({ videoId: video.id, error: `Video file not found: ${videoPath}` });
105458
+ continue;
105459
+ }
105460
+ resolvedVideos.push({ video, videoPath });
105461
+ } catch (err) {
105462
+ errors.push({ videoId: video.id, error: err instanceof Error ? err.message : String(err) });
105463
+ }
105464
+ }
105465
+ const videoColorSpaces = await Promise.all(
105466
+ resolvedVideos.map(async ({ videoPath }) => {
105467
+ const metadata = await extractVideoMetadata(videoPath);
105468
+ return metadata.colorSpace;
105469
+ })
105470
+ );
105471
+ const hasAnyHdr = videoColorSpaces.some(isHdrColorSpace);
105472
+ if (hasAnyHdr) {
105473
+ const convertDir = join8(options.outputDir, "_hdr_normalized");
105474
+ mkdirSync5(convertDir, { recursive: true });
105475
+ for (let i = 0; i < resolvedVideos.length; i++) {
105476
+ if (signal?.aborted) break;
105477
+ const cs = videoColorSpaces[i] ?? null;
105478
+ if (!isHdrColorSpace(cs)) {
105479
+ const entry = resolvedVideos[i];
105480
+ if (!entry) continue;
105481
+ const convertedPath = join8(convertDir, `${entry.video.id}_hdr.mp4`);
105482
+ try {
105483
+ await convertSdrToHdr(entry.videoPath, convertedPath, signal, config2);
105484
+ entry.videoPath = convertedPath;
105485
+ } catch (err) {
105486
+ errors.push({
105487
+ videoId: entry.video.id,
105488
+ error: `SDR\u2192HDR conversion failed: ${err instanceof Error ? err.message : String(err)}`
105489
+ });
105490
+ }
105491
+ }
105492
+ }
105493
+ }
105215
105494
  const results = await Promise.all(
105216
- videos.map(async (video) => {
105495
+ resolvedVideos.map(async ({ video, videoPath }) => {
105217
105496
  if (signal?.aborted) {
105218
105497
  throw new Error("Video frame extraction cancelled");
105219
105498
  }
105220
105499
  try {
105221
- let videoPath = video.src;
105222
- if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
105223
- const fromCompiled = compiledDir ? join8(compiledDir, videoPath) : null;
105224
- videoPath = fromCompiled && existsSync8(fromCompiled) ? fromCompiled : join8(baseDir, videoPath);
105225
- }
105226
- if (isHttpUrl(videoPath)) {
105227
- const downloadDir = join8(options.outputDir, "_downloads");
105228
- mkdirSync5(downloadDir, { recursive: true });
105229
- videoPath = await downloadToTemp(videoPath, downloadDir);
105230
- }
105231
- if (!existsSync8(videoPath)) {
105232
- return { error: { videoId: video.id, error: `Video file not found: ${videoPath}` } };
105233
- }
105234
105500
  let videoDuration = video.end - video.start;
105235
105501
  if (!Number.isFinite(videoDuration) || videoDuration <= 0) {
105236
105502
  const metadata = await extractVideoMetadata(videoPath);
@@ -105465,6 +105731,142 @@ function createVideoFrameInjector(frameLookup, config2) {
105465
105731
  }
105466
105732
  };
105467
105733
  }
105734
+ async function queryElementStacking(page, nativeHdrVideoIds) {
105735
+ const hdrIds = Array.from(nativeHdrVideoIds);
105736
+ return page.evaluate((hdrIdList) => {
105737
+ const hdrSet = new Set(hdrIdList);
105738
+ const elements = document.querySelectorAll("[data-start]");
105739
+ const results = [];
105740
+ function getEffectiveZIndex(node) {
105741
+ let current = node;
105742
+ while (current) {
105743
+ const cs = window.getComputedStyle(current);
105744
+ const pos = cs.position;
105745
+ const z = parseInt(cs.zIndex);
105746
+ if (!Number.isNaN(z) && pos !== "static") return z;
105747
+ current = current.parentElement;
105748
+ }
105749
+ return 0;
105750
+ }
105751
+ function getEffectiveBorderRadius(node) {
105752
+ function resolveRadius(value, el) {
105753
+ if (value.includes("%")) {
105754
+ const pct = parseFloat(value) / 100;
105755
+ const htmlEl = el;
105756
+ const w = htmlEl.offsetWidth || 0;
105757
+ const h = htmlEl.offsetHeight || 0;
105758
+ return pct * Math.min(w, h);
105759
+ }
105760
+ return parseFloat(value) || 0;
105761
+ }
105762
+ const selfCs = window.getComputedStyle(node);
105763
+ const selfRadii = [
105764
+ resolveRadius(selfCs.borderTopLeftRadius, node),
105765
+ resolveRadius(selfCs.borderTopRightRadius, node),
105766
+ resolveRadius(selfCs.borderBottomRightRadius, node),
105767
+ resolveRadius(selfCs.borderBottomLeftRadius, node)
105768
+ ];
105769
+ if (selfRadii[0] > 0 || selfRadii[1] > 0 || selfRadii[2] > 0 || selfRadii[3] > 0) {
105770
+ return selfRadii;
105771
+ }
105772
+ let current = node.parentElement;
105773
+ while (current) {
105774
+ const cs = window.getComputedStyle(current);
105775
+ if (cs.overflow !== "visible") {
105776
+ const tl = resolveRadius(cs.borderTopLeftRadius, current);
105777
+ const tr = resolveRadius(cs.borderTopRightRadius, current);
105778
+ const brr = resolveRadius(cs.borderBottomRightRadius, current);
105779
+ const bl = resolveRadius(cs.borderBottomLeftRadius, current);
105780
+ if (tl > 0 || tr > 0 || brr > 0 || bl > 0) {
105781
+ return [tl, tr, brr, bl];
105782
+ }
105783
+ }
105784
+ current = current.parentElement;
105785
+ }
105786
+ return [0, 0, 0, 0];
105787
+ }
105788
+ function getEffectiveOpacity(node) {
105789
+ let opacity = 1;
105790
+ let current = node;
105791
+ while (current) {
105792
+ const cs = window.getComputedStyle(current);
105793
+ const val = parseFloat(cs.opacity);
105794
+ opacity *= Number.isNaN(val) ? 1 : val;
105795
+ current = current.parentElement;
105796
+ }
105797
+ return opacity;
105798
+ }
105799
+ function getViewportMatrix(node) {
105800
+ const chain = [];
105801
+ let current = node;
105802
+ while (current instanceof HTMLElement) {
105803
+ chain.push(current);
105804
+ const next = current.offsetParent ?? current.parentElement;
105805
+ if (next === current) break;
105806
+ current = next;
105807
+ }
105808
+ let mat = new DOMMatrix();
105809
+ for (let i = chain.length - 1; i >= 0; i--) {
105810
+ const htmlEl = chain[i];
105811
+ if (!htmlEl) continue;
105812
+ mat = mat.translate(htmlEl.offsetLeft, htmlEl.offsetTop);
105813
+ const cs = window.getComputedStyle(htmlEl);
105814
+ if (cs.transform && cs.transform !== "none") {
105815
+ const origin = cs.transformOrigin.split(" ");
105816
+ const ox = resolveLength(origin[0] ?? "0", htmlEl.offsetWidth);
105817
+ const oy = resolveLength(origin[1] ?? "0", htmlEl.offsetHeight);
105818
+ try {
105819
+ const t = new DOMMatrix(cs.transform);
105820
+ if (Number.isFinite(t.a) && Number.isFinite(t.b) && Number.isFinite(t.c) && Number.isFinite(t.d) && Number.isFinite(t.e) && Number.isFinite(t.f)) {
105821
+ mat = mat.translate(ox, oy).multiply(t).translate(-ox, -oy);
105822
+ }
105823
+ } catch {
105824
+ }
105825
+ }
105826
+ }
105827
+ return mat.toString();
105828
+ }
105829
+ function resolveLength(value, basis) {
105830
+ if (value.endsWith("%")) {
105831
+ const pct = parseFloat(value) / 100;
105832
+ return Number.isFinite(pct) ? pct * basis : 0;
105833
+ }
105834
+ const n = parseFloat(value);
105835
+ return Number.isFinite(n) ? n : 0;
105836
+ }
105837
+ for (const el of elements) {
105838
+ const id = el.id;
105839
+ if (!id) continue;
105840
+ const rect = el.getBoundingClientRect();
105841
+ const style = window.getComputedStyle(el);
105842
+ const zIndex = getEffectiveZIndex(el);
105843
+ const isHdrEl = hdrSet.has(id);
105844
+ const opacityStartNode = isHdrEl ? el.parentElement : el;
105845
+ const opacity = opacityStartNode ? getEffectiveOpacity(opacityStartNode) : 1;
105846
+ const visible = style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
105847
+ const htmlEl = el instanceof HTMLElement ? el : null;
105848
+ results.push({
105849
+ id,
105850
+ zIndex,
105851
+ x: Math.round(rect.x),
105852
+ y: Math.round(rect.y),
105853
+ width: Math.round(rect.width),
105854
+ height: Math.round(rect.height),
105855
+ layoutWidth: htmlEl?.offsetWidth || Math.round(rect.width),
105856
+ layoutHeight: htmlEl?.offsetHeight || Math.round(rect.height),
105857
+ opacity,
105858
+ visible,
105859
+ isHdr: hdrSet.has(id),
105860
+ // For HDR elements, use the full accumulated viewport matrix so the
105861
+ // affine blit can apply rotation/scale/translate properly. For DOM
105862
+ // elements, the element-level transform is sufficient for reference.
105863
+ transform: isHdrEl ? getViewportMatrix(el) : style.transform || "none",
105864
+ borderRadius: isHdrEl ? getEffectiveBorderRadius(el) : [0, 0, 0, 0]
105865
+ });
105866
+ }
105867
+ return results;
105868
+ }, hdrIds);
105869
+ }
105468
105870
 
105469
105871
  // ../engine/src/services/audioMixer.ts
105470
105872
  import { existsSync as existsSync9, mkdirSync as mkdirSync6, rmSync as rmSync2 } from "fs";
@@ -105951,6 +106353,1016 @@ async function mergeWorkerFrames(workDir, tasks, outputDir) {
105951
106353
  return totalFrames;
105952
106354
  }
105953
106355
 
106356
+ // ../engine/src/utils/alphaBlit.ts
106357
+ import { inflateSync } from "zlib";
106358
+ function paeth(a, b, c) {
106359
+ const p = a + b - c;
106360
+ const pa = Math.abs(p - a);
106361
+ const pb = Math.abs(p - b);
106362
+ const pc = Math.abs(p - c);
106363
+ if (pa <= pb && pa <= pc) return a;
106364
+ if (pb <= pc) return b;
106365
+ return c;
106366
+ }
106367
+ function decodePngRaw(buf, caller) {
106368
+ if (buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71 || buf[4] !== 13 || buf[5] !== 10 || buf[6] !== 26 || buf[7] !== 10) {
106369
+ throw new Error(`${caller}: not a PNG file`);
106370
+ }
106371
+ let pos = 8;
106372
+ let width = 0;
106373
+ let height = 0;
106374
+ let bitDepth = 0;
106375
+ let colorType = 0;
106376
+ let interlace = 0;
106377
+ let sawIhdr = false;
106378
+ const idatChunks = [];
106379
+ while (pos + 12 <= buf.length) {
106380
+ const chunkLen = buf.readUInt32BE(pos);
106381
+ const chunkType = buf.toString("ascii", pos + 4, pos + 8);
106382
+ const chunkData = buf.subarray(pos + 8, pos + 8 + chunkLen);
106383
+ if (chunkType === "IHDR") {
106384
+ width = chunkData.readUInt32BE(0);
106385
+ height = chunkData.readUInt32BE(4);
106386
+ bitDepth = chunkData[8] ?? 0;
106387
+ colorType = chunkData[9] ?? 0;
106388
+ interlace = chunkData[12] ?? 0;
106389
+ sawIhdr = true;
106390
+ } else if (chunkType === "IDAT") {
106391
+ idatChunks.push(Buffer.from(chunkData));
106392
+ } else if (chunkType === "IEND") {
106393
+ break;
106394
+ }
106395
+ pos += 12 + chunkLen;
106396
+ }
106397
+ if (!sawIhdr) {
106398
+ throw new Error(`${caller}: PNG missing IHDR chunk`);
106399
+ }
106400
+ if (colorType !== 2 && colorType !== 6) {
106401
+ throw new Error(`${caller}: unsupported color type ${colorType} (expected 2=RGB or 6=RGBA)`);
106402
+ }
106403
+ if (interlace !== 0) {
106404
+ throw new Error(
106405
+ `${caller}: Adam7-interlaced PNGs are not supported (interlace method ${interlace})`
106406
+ );
106407
+ }
106408
+ const channels = colorType === 6 ? 4 : 3;
106409
+ const bpp = channels * (bitDepth / 8);
106410
+ const stride = width * bpp;
106411
+ const compressed = Buffer.concat(idatChunks);
106412
+ const decompressed = inflateSync(compressed);
106413
+ const rawPixels = Buffer.allocUnsafe(height * stride);
106414
+ const prevRow = new Uint8Array(stride);
106415
+ const currRow = new Uint8Array(stride);
106416
+ let srcPos = 0;
106417
+ for (let y = 0; y < height; y++) {
106418
+ const filterType = decompressed[srcPos++] ?? 0;
106419
+ const rawRow = decompressed.subarray(srcPos, srcPos + stride);
106420
+ srcPos += stride;
106421
+ switch (filterType) {
106422
+ case 0:
106423
+ currRow.set(rawRow);
106424
+ break;
106425
+ case 1:
106426
+ for (let x = 0; x < stride; x++) {
106427
+ currRow[x] = (rawRow[x] ?? 0) + (x >= bpp ? currRow[x - bpp] ?? 0 : 0) & 255;
106428
+ }
106429
+ break;
106430
+ case 2:
106431
+ for (let x = 0; x < stride; x++) {
106432
+ currRow[x] = (rawRow[x] ?? 0) + (prevRow[x] ?? 0) & 255;
106433
+ }
106434
+ break;
106435
+ case 3:
106436
+ for (let x = 0; x < stride; x++) {
106437
+ const left2 = x >= bpp ? currRow[x - bpp] ?? 0 : 0;
106438
+ const up = prevRow[x] ?? 0;
106439
+ currRow[x] = (rawRow[x] ?? 0) + Math.floor((left2 + up) / 2) & 255;
106440
+ }
106441
+ break;
106442
+ case 4:
106443
+ for (let x = 0; x < stride; x++) {
106444
+ const left2 = x >= bpp ? currRow[x - bpp] ?? 0 : 0;
106445
+ const up = prevRow[x] ?? 0;
106446
+ const upLeft = x >= bpp ? prevRow[x - bpp] ?? 0 : 0;
106447
+ currRow[x] = (rawRow[x] ?? 0) + paeth(left2, up, upLeft) & 255;
106448
+ }
106449
+ break;
106450
+ default:
106451
+ throw new Error(`${caller}: unknown filter type ${filterType} at row ${y}`);
106452
+ }
106453
+ rawPixels.set(currRow, y * stride);
106454
+ prevRow.set(currRow);
106455
+ }
106456
+ return { width, height, bitDepth, colorType, rawPixels };
106457
+ }
106458
+ function decodePng(buf) {
106459
+ const { width, height, bitDepth, colorType, rawPixels } = decodePngRaw(buf, "decodePng");
106460
+ if (bitDepth !== 8) {
106461
+ throw new Error(`decodePng: unsupported bit depth ${bitDepth} (expected 8)`);
106462
+ }
106463
+ const output2 = new Uint8Array(width * height * 4);
106464
+ if (colorType === 6) {
106465
+ output2.set(rawPixels);
106466
+ } else {
106467
+ for (let i = 0; i < width * height; i++) {
106468
+ output2[i * 4 + 0] = rawPixels[i * 3 + 0] ?? 0;
106469
+ output2[i * 4 + 1] = rawPixels[i * 3 + 1] ?? 0;
106470
+ output2[i * 4 + 2] = rawPixels[i * 3 + 2] ?? 0;
106471
+ output2[i * 4 + 3] = 255;
106472
+ }
106473
+ }
106474
+ return { width, height, data: output2 };
106475
+ }
106476
+ function decodePngToRgb48le(buf) {
106477
+ const { width, height, bitDepth, colorType, rawPixels } = decodePngRaw(buf, "decodePngToRgb48le");
106478
+ if (bitDepth !== 16) {
106479
+ throw new Error(`decodePngToRgb48le: unsupported bit depth ${bitDepth} (expected 16)`);
106480
+ }
106481
+ const bpp = colorType === 6 ? 8 : 6;
106482
+ const output2 = Buffer.allocUnsafe(width * height * 6);
106483
+ for (let y = 0; y < height; y++) {
106484
+ const dstBase = y * width * 6;
106485
+ const srcRowBase = y * width * bpp;
106486
+ for (let x = 0; x < width; x++) {
106487
+ const srcBase = srcRowBase + x * bpp;
106488
+ output2[dstBase + x * 6 + 0] = rawPixels[srcBase + 1] ?? 0;
106489
+ output2[dstBase + x * 6 + 1] = rawPixels[srcBase + 0] ?? 0;
106490
+ output2[dstBase + x * 6 + 2] = rawPixels[srcBase + 3] ?? 0;
106491
+ output2[dstBase + x * 6 + 3] = rawPixels[srcBase + 2] ?? 0;
106492
+ output2[dstBase + x * 6 + 4] = rawPixels[srcBase + 5] ?? 0;
106493
+ output2[dstBase + x * 6 + 5] = rawPixels[srcBase + 4] ?? 0;
106494
+ }
106495
+ }
106496
+ return { width, height, data: output2 };
106497
+ }
106498
+ function buildSrgbToHdrLut(transfer) {
106499
+ const lut = new Uint16Array(256);
106500
+ const hlgA = 0.17883277;
106501
+ const hlgB = 1 - 4 * hlgA;
106502
+ const hlgC = 0.5 - hlgA * Math.log(4 * hlgA);
106503
+ const pqM1 = 0.1593017578125;
106504
+ const pqM2 = 78.84375;
106505
+ const pqC1 = 0.8359375;
106506
+ const pqC2 = 18.8515625;
106507
+ const pqC3 = 18.6875;
106508
+ const pqMaxNits = 1e4;
106509
+ const sdrNits = 203;
106510
+ for (let i = 0; i < 256; i++) {
106511
+ const v = i / 255;
106512
+ const linear = v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
106513
+ let signal;
106514
+ if (transfer === "hlg") {
106515
+ signal = linear <= 1 / 12 ? Math.sqrt(3 * linear) : hlgA * Math.log(12 * linear - hlgB) + hlgC;
106516
+ } else {
106517
+ const Lp = Math.max(0, linear * sdrNits / pqMaxNits);
106518
+ const Lm1 = Math.pow(Lp, pqM1);
106519
+ signal = Math.pow((pqC1 + pqC2 * Lm1) / (1 + pqC3 * Lm1), pqM2);
106520
+ }
106521
+ lut[i] = Math.min(65535, Math.round(signal * 65535));
106522
+ }
106523
+ return lut;
106524
+ }
106525
+ var SRGB_TO_HLG = buildSrgbToHdrLut("hlg");
106526
+ var SRGB_TO_PQ = buildSrgbToHdrLut("pq");
106527
+ function getSrgbToHdrLut(transfer) {
106528
+ return transfer === "pq" ? SRGB_TO_PQ : SRGB_TO_HLG;
106529
+ }
106530
+ function blitRgba8OverRgb48le(domRgba, canvas, width, height, transfer = "hlg") {
106531
+ const pixelCount = width * height;
106532
+ const lut = getSrgbToHdrLut(transfer);
106533
+ for (let i = 0; i < pixelCount; i++) {
106534
+ const da = domRgba[i * 4 + 3] ?? 0;
106535
+ if (da === 0) {
106536
+ continue;
106537
+ } else if (da === 255) {
106538
+ const r16 = lut[domRgba[i * 4 + 0] ?? 0] ?? 0;
106539
+ const g16 = lut[domRgba[i * 4 + 1] ?? 0] ?? 0;
106540
+ const b16 = lut[domRgba[i * 4 + 2] ?? 0] ?? 0;
106541
+ canvas.writeUInt16LE(r16, i * 6);
106542
+ canvas.writeUInt16LE(g16, i * 6 + 2);
106543
+ canvas.writeUInt16LE(b16, i * 6 + 4);
106544
+ } else {
106545
+ const alpha = da / 255;
106546
+ const invAlpha = 1 - alpha;
106547
+ const hdrR = (canvas[i * 6 + 0] ?? 0) | (canvas[i * 6 + 1] ?? 0) << 8;
106548
+ const hdrG = (canvas[i * 6 + 2] ?? 0) | (canvas[i * 6 + 3] ?? 0) << 8;
106549
+ const hdrB = (canvas[i * 6 + 4] ?? 0) | (canvas[i * 6 + 5] ?? 0) << 8;
106550
+ const domR = lut[domRgba[i * 4 + 0] ?? 0] ?? 0;
106551
+ const domG = lut[domRgba[i * 4 + 1] ?? 0] ?? 0;
106552
+ const domB = lut[domRgba[i * 4 + 2] ?? 0] ?? 0;
106553
+ canvas.writeUInt16LE(Math.round(domR * alpha + hdrR * invAlpha), i * 6);
106554
+ canvas.writeUInt16LE(Math.round(domG * alpha + hdrG * invAlpha), i * 6 + 2);
106555
+ canvas.writeUInt16LE(Math.round(domB * alpha + hdrB * invAlpha), i * 6 + 4);
106556
+ }
106557
+ }
106558
+ }
106559
+ function cornerAlpha(px, py, cx, cy, r) {
106560
+ const dx = px - cx;
106561
+ const dy = py - cy;
106562
+ const dist = Math.sqrt(dx * dx + dy * dy);
106563
+ if (dist > r + 0.5) return 0;
106564
+ if (dist > r - 0.5) return r + 0.5 - dist;
106565
+ return 1;
106566
+ }
106567
+ function roundedRectAlpha(px, py, w, h, radii) {
106568
+ const [tl, tr, br, bl] = radii;
106569
+ if (px < tl && py < tl) return cornerAlpha(px, py, tl, tl, tl);
106570
+ if (px >= w - tr && py < tr) return cornerAlpha(px, py, w - tr, tr, tr);
106571
+ if (px >= w - br && py >= h - br) return cornerAlpha(px, py, w - br, h - br, br);
106572
+ if (px < bl && py >= h - bl) return cornerAlpha(px, py, bl, h - bl, bl);
106573
+ return 1;
106574
+ }
106575
+ function blitRgb48leRegion(canvas, source2, dx, dy, sw, sh, canvasWidth, canvasHeight, opacity, borderRadius) {
106576
+ if (sw <= 0 || sh <= 0) return;
106577
+ const op = opacity ?? 1;
106578
+ const x0 = Math.max(0, dx);
106579
+ const y0 = Math.max(0, dy);
106580
+ const x1 = Math.min(canvasWidth, dx + sw);
106581
+ const y1 = Math.min(canvasHeight, dy + sh);
106582
+ if (x0 >= x1 || y0 >= y1) return;
106583
+ const clippedW = x1 - x0;
106584
+ const srcOffsetX = x0 - dx;
106585
+ const srcOffsetY = y0 - dy;
106586
+ const hasMask = borderRadius !== void 0;
106587
+ if (op >= 0.999 && !hasMask) {
106588
+ for (let y = 0; y < y1 - y0; y++) {
106589
+ const srcRowOff = ((srcOffsetY + y) * sw + srcOffsetX) * 6;
106590
+ const dstRowOff = ((y0 + y) * canvasWidth + x0) * 6;
106591
+ source2.copy(canvas, dstRowOff, srcRowOff, srcRowOff + clippedW * 6);
106592
+ }
106593
+ } else {
106594
+ for (let y = 0; y < y1 - y0; y++) {
106595
+ for (let x = 0; x < clippedW; x++) {
106596
+ let effectiveOp = op;
106597
+ if (hasMask) {
106598
+ const ma = roundedRectAlpha(srcOffsetX + x, srcOffsetY + y, sw, sh, borderRadius);
106599
+ if (ma <= 0) continue;
106600
+ effectiveOp *= ma;
106601
+ }
106602
+ const srcOff = ((srcOffsetY + y) * sw + srcOffsetX + x) * 6;
106603
+ const dstOff = ((y0 + y) * canvasWidth + x0 + x) * 6;
106604
+ if (effectiveOp >= 0.999) {
106605
+ source2.copy(canvas, dstOff, srcOff, srcOff + 6);
106606
+ } else {
106607
+ const invEff = 1 - effectiveOp;
106608
+ const sr = source2.readUInt16LE(srcOff);
106609
+ const sg = source2.readUInt16LE(srcOff + 2);
106610
+ const sb = source2.readUInt16LE(srcOff + 4);
106611
+ const dr = canvas.readUInt16LE(dstOff);
106612
+ const dg = canvas.readUInt16LE(dstOff + 2);
106613
+ const db = canvas.readUInt16LE(dstOff + 4);
106614
+ canvas.writeUInt16LE(Math.round(sr * effectiveOp + dr * invEff), dstOff);
106615
+ canvas.writeUInt16LE(Math.round(sg * effectiveOp + dg * invEff), dstOff + 2);
106616
+ canvas.writeUInt16LE(Math.round(sb * effectiveOp + db * invEff), dstOff + 4);
106617
+ }
106618
+ }
106619
+ }
106620
+ }
106621
+ }
106622
+ function blitRgb48leAffine(canvas, source2, matrix, srcW, srcH, canvasW, canvasH, opacity, borderRadius) {
106623
+ const a = matrix[0];
106624
+ const b = matrix[1];
106625
+ const c = matrix[2];
106626
+ const d = matrix[3];
106627
+ const tx = matrix[4];
106628
+ const ty = matrix[5];
106629
+ if (a === void 0 || b === void 0 || c === void 0 || d === void 0 || tx === void 0 || ty === void 0)
106630
+ return;
106631
+ const det = a * d - b * c;
106632
+ if (Math.abs(det) < 1e-10) return;
106633
+ const invA = d / det;
106634
+ const invB = -b / det;
106635
+ const invC = -c / det;
106636
+ const invD = a / det;
106637
+ const invTx = -(invA * tx + invC * ty);
106638
+ const invTy = -(invB * tx + invD * ty);
106639
+ const op = opacity ?? 1;
106640
+ const hasMask = borderRadius !== void 0;
106641
+ const corners = [
106642
+ [tx, ty],
106643
+ [a * srcW + tx, b * srcW + ty],
106644
+ [c * srcH + tx, d * srcH + ty],
106645
+ [a * srcW + c * srcH + tx, b * srcW + d * srcH + ty]
106646
+ ];
106647
+ let minX = canvasW, maxX = 0, minY = canvasH, maxY = 0;
106648
+ for (const corner of corners) {
106649
+ const cx = corner[0] ?? 0;
106650
+ const cy = corner[1] ?? 0;
106651
+ if (cx < minX) minX = cx;
106652
+ if (cx > maxX) maxX = cx;
106653
+ if (cy < minY) minY = cy;
106654
+ if (cy > maxY) maxY = cy;
106655
+ }
106656
+ const startX = Math.max(0, Math.floor(minX));
106657
+ const endX = Math.min(canvasW, Math.ceil(maxX));
106658
+ const startY = Math.max(0, Math.floor(minY));
106659
+ const endY = Math.min(canvasH, Math.ceil(maxY));
106660
+ for (let dy = startY; dy < endY; dy++) {
106661
+ for (let dx = startX; dx < endX; dx++) {
106662
+ const sx = invA * dx + invC * dy + invTx;
106663
+ const sy = invB * dx + invD * dy + invTy;
106664
+ if (sx < 0 || sy < 0 || sx >= srcW || sy >= srcH) continue;
106665
+ let effectiveOp = op;
106666
+ if (hasMask) {
106667
+ const ma = roundedRectAlpha(sx, sy, srcW, srcH, borderRadius);
106668
+ if (ma <= 0) continue;
106669
+ effectiveOp *= ma;
106670
+ }
106671
+ const x0 = Math.floor(sx);
106672
+ const y0 = Math.floor(sy);
106673
+ const fx = sx - x0;
106674
+ const fy = sy - y0;
106675
+ const x1 = Math.min(x0 + 1, srcW - 1);
106676
+ const y1 = Math.min(y0 + 1, srcH - 1);
106677
+ const off00 = (y0 * srcW + x0) * 6;
106678
+ const off10 = (y0 * srcW + x1) * 6;
106679
+ const off01 = (y1 * srcW + x0) * 6;
106680
+ const off11 = (y1 * srcW + x1) * 6;
106681
+ const w00 = (1 - fx) * (1 - fy);
106682
+ const w10 = fx * (1 - fy);
106683
+ const w01 = (1 - fx) * fy;
106684
+ const w11 = fx * fy;
106685
+ const sr = source2.readUInt16LE(off00) * w00 + source2.readUInt16LE(off10) * w10 + source2.readUInt16LE(off01) * w01 + source2.readUInt16LE(off11) * w11;
106686
+ const sg = source2.readUInt16LE(off00 + 2) * w00 + source2.readUInt16LE(off10 + 2) * w10 + source2.readUInt16LE(off01 + 2) * w01 + source2.readUInt16LE(off11 + 2) * w11;
106687
+ const sb = source2.readUInt16LE(off00 + 4) * w00 + source2.readUInt16LE(off10 + 4) * w10 + source2.readUInt16LE(off01 + 4) * w01 + source2.readUInt16LE(off11 + 4) * w11;
106688
+ const dstOff = (dy * canvasW + dx) * 6;
106689
+ if (effectiveOp >= 0.999) {
106690
+ canvas.writeUInt16LE(Math.round(sr), dstOff);
106691
+ canvas.writeUInt16LE(Math.round(sg), dstOff + 2);
106692
+ canvas.writeUInt16LE(Math.round(sb), dstOff + 4);
106693
+ } else {
106694
+ const invEff = 1 - effectiveOp;
106695
+ const dr = canvas.readUInt16LE(dstOff);
106696
+ const dg = canvas.readUInt16LE(dstOff + 2);
106697
+ const db = canvas.readUInt16LE(dstOff + 4);
106698
+ canvas.writeUInt16LE(Math.round(sr * effectiveOp + dr * invEff), dstOff);
106699
+ canvas.writeUInt16LE(Math.round(sg * effectiveOp + dg * invEff), dstOff + 2);
106700
+ canvas.writeUInt16LE(Math.round(sb * effectiveOp + db * invEff), dstOff + 4);
106701
+ }
106702
+ }
106703
+ }
106704
+ }
106705
+ function parseTransformMatrix(css) {
106706
+ if (!css || css === "none") return null;
106707
+ const match2 = css.match(
106708
+ /^matrix\(\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^,]+),\s*([^,)]+)\s*\)$/
106709
+ );
106710
+ if (!match2) return null;
106711
+ const values = match2.slice(1, 7).map(Number);
106712
+ if (!values.every(Number.isFinite)) return null;
106713
+ return values;
106714
+ }
106715
+
106716
+ // ../engine/src/utils/layerCompositor.ts
106717
+ function groupIntoLayers(elements) {
106718
+ const sorted = [...elements].sort((a, b) => a.zIndex - b.zIndex);
106719
+ const layers = [];
106720
+ for (const el of sorted) {
106721
+ if (el.isHdr) {
106722
+ layers.push({ type: "hdr", element: el });
106723
+ } else {
106724
+ const last2 = layers[layers.length - 1];
106725
+ if (last2 && last2.type === "dom") {
106726
+ last2.elementIds.push(el.id);
106727
+ } else {
106728
+ layers.push({ type: "dom", elementIds: [el.id] });
106729
+ }
106730
+ }
106731
+ }
106732
+ return layers;
106733
+ }
106734
+
106735
+ // ../engine/src/utils/shaderTransitions.ts
106736
+ var PQ_M1 = 0.1593017578125;
106737
+ var PQ_M2 = 78.84375;
106738
+ var PQ_C1 = 0.8359375;
106739
+ var PQ_C2 = 18.8515625;
106740
+ var PQ_C3 = 18.6875;
106741
+ function pqEotf(signal) {
106742
+ const sp = Math.pow(Math.max(0, signal), 1 / PQ_M2);
106743
+ const num = Math.max(sp - PQ_C1, 0);
106744
+ const den = PQ_C2 - PQ_C3 * sp;
106745
+ return den > 0 ? Math.pow(num / den, 1 / PQ_M1) : 0;
106746
+ }
106747
+ function pqOetf(linear) {
106748
+ const lp = Math.pow(Math.max(0, linear), PQ_M1);
106749
+ return Math.pow((PQ_C1 + PQ_C2 * lp) / (1 + PQ_C3 * lp), PQ_M2);
106750
+ }
106751
+ function hlgEotf(signal) {
106752
+ const a = 0.17883277;
106753
+ const b = 1 - 4 * a;
106754
+ const c = 0.5 - a * Math.log(4 * a);
106755
+ if (signal <= 0.5) {
106756
+ return signal * signal / 3;
106757
+ }
106758
+ return (Math.exp((signal - c) / a) + b) / 12;
106759
+ }
106760
+ function hlgOetf(linear) {
106761
+ const a = 0.17883277;
106762
+ const b = 1 - 4 * a;
106763
+ const c = 0.5 - a * Math.log(4 * a);
106764
+ if (linear <= 1 / 12) {
106765
+ return Math.sqrt(3 * linear);
106766
+ }
106767
+ return a * Math.log(12 * linear - b) + c;
106768
+ }
106769
+ function buildLut(fn) {
106770
+ const lut = new Uint16Array(65536);
106771
+ for (let i = 0; i < 65536; i++) {
106772
+ lut[i] = Math.round(fn(i / 65535) * 65535);
106773
+ }
106774
+ return lut;
106775
+ }
106776
+ var HLG_OOTF_LW = 1e3;
106777
+ var HLG_OOTF_GAMMA = 1.2 * Math.pow(1.111, Math.log2(HLG_OOTF_LW / 1e3));
106778
+ function hlgSceneToPqDisplay(sceneLinear) {
106779
+ const displayNits = HLG_OOTF_LW * Math.pow(Math.max(0, sceneLinear), HLG_OOTF_GAMMA);
106780
+ return displayNits / 1e4;
106781
+ }
106782
+ function pqDisplayToHlgScene(displayNormalized) {
106783
+ const displayNits = displayNormalized * 1e4;
106784
+ return Math.pow(Math.max(0, displayNits / HLG_OOTF_LW), 1 / HLG_OOTF_GAMMA);
106785
+ }
106786
+ var hlgToPqLut = null;
106787
+ var pqToHlgLut = null;
106788
+ function getHlgToPqLut() {
106789
+ if (!hlgToPqLut) hlgToPqLut = buildLut((v) => pqOetf(hlgSceneToPqDisplay(hlgEotf(v))));
106790
+ return hlgToPqLut;
106791
+ }
106792
+ function getPqToHlgLut() {
106793
+ if (!pqToHlgLut) pqToHlgLut = buildLut((v) => hlgOetf(pqDisplayToHlgScene(pqEotf(v))));
106794
+ return pqToHlgLut;
106795
+ }
106796
+ function convertTransfer(buf, from2, to) {
106797
+ if (from2 === to) return;
106798
+ const lut = from2 === "hlg" ? getHlgToPqLut() : getPqToHlgLut();
106799
+ const len = buf.length / 2;
106800
+ for (let i = 0; i < len; i++) {
106801
+ const off = i * 2;
106802
+ buf.writeUInt16LE(lut[buf.readUInt16LE(off)] ?? 0, off);
106803
+ }
106804
+ }
106805
+ function sampleRgb48le(buf, u, v, w, h) {
106806
+ const uc = Math.max(0, Math.min(1, u));
106807
+ const vc = Math.max(0, Math.min(1, v));
106808
+ const sx = uc * (w - 1);
106809
+ const sy = vc * (h - 1);
106810
+ const x0 = Math.floor(sx);
106811
+ const y0 = Math.floor(sy);
106812
+ const x1 = Math.min(x0 + 1, w - 1);
106813
+ const y1 = Math.min(y0 + 1, h - 1);
106814
+ const fx = sx - x0;
106815
+ const fy = sy - y0;
106816
+ const w00 = (1 - fx) * (1 - fy);
106817
+ const w10 = fx * (1 - fy);
106818
+ const w01 = (1 - fx) * fy;
106819
+ const w11 = fx * fy;
106820
+ const off00 = (y0 * w + x0) * 6;
106821
+ const off10 = (y0 * w + x1) * 6;
106822
+ const off01 = (y1 * w + x0) * 6;
106823
+ const off11 = (y1 * w + x1) * 6;
106824
+ const r = Math.round(
106825
+ buf.readUInt16LE(off00) * w00 + buf.readUInt16LE(off10) * w10 + buf.readUInt16LE(off01) * w01 + buf.readUInt16LE(off11) * w11
106826
+ );
106827
+ const g = Math.round(
106828
+ buf.readUInt16LE(off00 + 2) * w00 + buf.readUInt16LE(off10 + 2) * w10 + buf.readUInt16LE(off01 + 2) * w01 + buf.readUInt16LE(off11 + 2) * w11
106829
+ );
106830
+ const b = Math.round(
106831
+ buf.readUInt16LE(off00 + 4) * w00 + buf.readUInt16LE(off10 + 4) * w10 + buf.readUInt16LE(off01 + 4) * w01 + buf.readUInt16LE(off11 + 4) * w11
106832
+ );
106833
+ return [r, g, b];
106834
+ }
106835
+ function mix16(a, b, t) {
106836
+ return Math.round(a * (1 - t) + b * t);
106837
+ }
106838
+ function clamp16(v) {
106839
+ return Math.max(0, Math.min(65535, v));
106840
+ }
106841
+ function smoothstep(edge0, edge1, x) {
106842
+ const t = Math.max(0, Math.min(1, (x - edge0) / (edge1 - edge0)));
106843
+ return t * t * (3 - 2 * t);
106844
+ }
106845
+ function hash(x, y) {
106846
+ return (Math.sin(x * 127.1 + y * 311.7) * 43758.5453 % 1 + 1) % 1;
106847
+ }
106848
+ function vnoise(px, py) {
106849
+ const ix = Math.floor(px);
106850
+ const iy = Math.floor(py);
106851
+ let fx = px - ix;
106852
+ let fy = py - iy;
106853
+ fx = fx * fx * fx * (fx * (fx * 6 - 15) + 10);
106854
+ fy = fy * fy * fy * (fy * (fy * 6 - 15) + 10);
106855
+ const h00 = hash(ix, iy);
106856
+ const h10 = hash(ix + 1, iy);
106857
+ const h01 = hash(ix, iy + 1);
106858
+ const h11 = hash(ix + 1, iy + 1);
106859
+ return h00 * (1 - fx) * (1 - fy) + h10 * fx * (1 - fy) + h01 * (1 - fx) * fy + h11 * fx * fy;
106860
+ }
106861
+ var ROT_A = 0.8;
106862
+ var ROT_B = 0.6;
106863
+ function fbm(px, py) {
106864
+ let value = 0;
106865
+ let amplitude = 0.5;
106866
+ let x = px;
106867
+ let y = py;
106868
+ for (let i = 0; i < 5; i++) {
106869
+ value += amplitude * vnoise(x, y);
106870
+ const nx = ROT_A * x - ROT_B * y;
106871
+ const ny = ROT_B * x + ROT_A * y;
106872
+ x = nx * 2.02;
106873
+ y = ny * 2.02;
106874
+ amplitude *= 0.5;
106875
+ }
106876
+ return value;
106877
+ }
106878
+ var TRANSITIONS = {};
106879
+ var crossfade = (from2, to, out, w, h, p) => {
106880
+ const inv = 1 - p;
106881
+ for (let i = 0; i < w * h; i++) {
106882
+ const o = i * 6;
106883
+ out.writeUInt16LE(Math.round(from2.readUInt16LE(o) * inv + to.readUInt16LE(o) * p), o);
106884
+ out.writeUInt16LE(
106885
+ Math.round(from2.readUInt16LE(o + 2) * inv + to.readUInt16LE(o + 2) * p),
106886
+ o + 2
106887
+ );
106888
+ out.writeUInt16LE(
106889
+ Math.round(from2.readUInt16LE(o + 4) * inv + to.readUInt16LE(o + 4) * p),
106890
+ o + 4
106891
+ );
106892
+ }
106893
+ };
106894
+ TRANSITIONS["crossfade"] = crossfade;
106895
+ var flashThroughWhite = (from2, to, out, w, h, p) => {
106896
+ const toWhite = smoothstep(0, 0.45, p);
106897
+ const fromWhite = 1 - smoothstep(0.5, 1, p);
106898
+ const blend = smoothstep(0.35, 0.65, p);
106899
+ for (let i = 0; i < w * h; i++) {
106900
+ const o = i * 6;
106901
+ const fromR = mix16(from2.readUInt16LE(o), 65535, toWhite);
106902
+ const fromG = mix16(from2.readUInt16LE(o + 2), 65535, toWhite);
106903
+ const fromB = mix16(from2.readUInt16LE(o + 4), 65535, toWhite);
106904
+ const toR = mix16(to.readUInt16LE(o), 65535, fromWhite);
106905
+ const toG = mix16(to.readUInt16LE(o + 2), 65535, fromWhite);
106906
+ const toB = mix16(to.readUInt16LE(o + 4), 65535, fromWhite);
106907
+ out.writeUInt16LE(mix16(fromR, toR, blend), o);
106908
+ out.writeUInt16LE(mix16(fromG, toG, blend), o + 2);
106909
+ out.writeUInt16LE(mix16(fromB, toB, blend), o + 4);
106910
+ }
106911
+ };
106912
+ TRANSITIONS["flash-through-white"] = flashThroughWhite;
106913
+ var chromaticSplit = (from2, to, out, w, h, p) => {
106914
+ for (let i = 0; i < w * h; i++) {
106915
+ const ux = i % w / w;
106916
+ const uy = Math.floor(i / w) / h;
106917
+ const o = i * 6;
106918
+ const cx = ux - 0.5;
106919
+ const cy = uy - 0.5;
106920
+ const fromShift = p * 0.06;
106921
+ const fr = sampleRgb48le(from2, ux + cx * fromShift, uy + cy * fromShift, w, h)[0];
106922
+ const fg = sampleRgb48le(from2, ux, uy, w, h)[1];
106923
+ const fb = sampleRgb48le(from2, ux - cx * fromShift, uy - cy * fromShift, w, h)[2];
106924
+ const toShift = (1 - p) * 0.06;
106925
+ const tr = sampleRgb48le(to, ux - cx * toShift, uy - cy * toShift, w, h)[0];
106926
+ const tg = sampleRgb48le(to, ux, uy, w, h)[1];
106927
+ const tb = sampleRgb48le(to, ux + cx * toShift, uy + cy * toShift, w, h)[2];
106928
+ out.writeUInt16LE(clamp16(mix16(fr, tr, p)), o);
106929
+ out.writeUInt16LE(clamp16(mix16(fg, tg, p)), o + 2);
106930
+ out.writeUInt16LE(clamp16(mix16(fb, tb, p)), o + 4);
106931
+ }
106932
+ };
106933
+ TRANSITIONS["chromatic-split"] = chromaticSplit;
106934
+ var sdfIris = (from2, to, out, w, h, p) => {
106935
+ const accentBright = [65535, 55e3, 35e3];
106936
+ for (let i = 0; i < w * h; i++) {
106937
+ const ux = i % w / w;
106938
+ const uy = Math.floor(i / w) / h;
106939
+ const o = i * 6;
106940
+ const ax = (ux - 0.5) * (w / h);
106941
+ const ay = uy - 0.5;
106942
+ const d = Math.sqrt(ax * ax + ay * ay);
106943
+ const radius = p * 1.2;
106944
+ const fw = 3e-3;
106945
+ const edge = smoothstep(radius + fw, radius - fw, d);
106946
+ const ring1 = Math.exp(-Math.abs(d - radius) * 25);
106947
+ const ring2 = Math.exp(-Math.abs(d - radius + 0.04) * 20) * 0.5;
106948
+ const ring3 = Math.exp(-Math.abs(d - radius + 0.08) * 15) * 0.25;
106949
+ const glow = (ring1 + ring2 + ring3) * p * (1 - p) * 4;
106950
+ const [fromR, fromG, fromB] = sampleRgb48le(from2, ux, uy, w, h);
106951
+ const [toR, toG, toB] = sampleRgb48le(to, ux, uy, w, h);
106952
+ out.writeUInt16LE(clamp16(mix16(fromR, toR, edge) + accentBright[0] * glow * 0.6), o);
106953
+ out.writeUInt16LE(clamp16(mix16(fromG, toG, edge) + accentBright[1] * glow * 0.6), o + 2);
106954
+ out.writeUInt16LE(clamp16(mix16(fromB, toB, edge) + accentBright[2] * glow * 0.6), o + 4);
106955
+ }
106956
+ };
106957
+ TRANSITIONS["sdf-iris"] = sdfIris;
106958
+ function glitchRand(x, y) {
106959
+ return (Math.sin(x * 12.9898 + y * 78.233) * 43758.5453 % 1 + 1) % 1;
106960
+ }
106961
+ var glitch = (from2, to, out, w, h, p) => {
106962
+ const intensity = p * (1 - p) * 4;
106963
+ for (let i = 0; i < w * h; i++) {
106964
+ const ux = i % w / w;
106965
+ const uy = Math.floor(i / w) / h;
106966
+ const o = i * 6;
106967
+ const lineY = Math.floor(uy * 60) / 60;
106968
+ const lineDisp = (glitchRand(lineY, Math.floor(p * 17)) - 0.5) * 0.18 * intensity;
106969
+ const blockX = Math.floor(ux * 12);
106970
+ const blockY = Math.floor(uy * 8);
106971
+ const progressStep = Math.floor(p * 11);
106972
+ const br = glitchRand(blockX + progressStep, blockY + progressStep);
106973
+ const ba = (br >= 0.83 ? 1 : 0) * intensity;
106974
+ const bdx = (glitchRand(blockX * 2.1, blockY * 2.1) - 0.5) * 0.35 * ba;
106975
+ const bdy = (glitchRand(blockX * 3.7, blockY * 3.7) - 0.5) * 0.35 * ba;
106976
+ const uvx = Math.max(0, Math.min(1, ux + lineDisp + bdx));
106977
+ const uvy = Math.max(0, Math.min(1, uy + bdy));
106978
+ const shift = intensity * 0.035;
106979
+ const r = sampleRgb48le(from2, uvx + shift, uvy, w, h)[0];
106980
+ const g = sampleRgb48le(from2, uvx, uvy, w, h)[1];
106981
+ const b = sampleRgb48le(from2, uvx - shift, uvy, w, h)[2];
106982
+ let cr = r / 65535;
106983
+ let cg = g / 65535;
106984
+ let cb = b / 65535;
106985
+ const scanline = (uy * h * 0.5 % 1 + 1) % 1 >= 0.5 ? 0.05 * intensity : 0;
106986
+ cr -= scanline;
106987
+ cg -= scanline;
106988
+ cb -= scanline;
106989
+ const flicker = 1 + (glitchRand(Math.floor(p * 23), 0) - 0.5) * 0.3 * intensity;
106990
+ cr *= flicker;
106991
+ cg *= flicker;
106992
+ cb *= flicker;
106993
+ const levels = 256 - (256 - 8) * (intensity * 0.5);
106994
+ cr = Math.floor(cr * levels) / levels;
106995
+ cg = Math.floor(cg * levels) / levels;
106996
+ cb = Math.floor(cb * levels) / levels;
106997
+ const [toR, toG, toB] = sampleRgb48le(to, ux, uy, w, h);
106998
+ out.writeUInt16LE(clamp16(mix16(Math.round(cr * 65535), toR, p)), o);
106999
+ out.writeUInt16LE(clamp16(mix16(Math.round(cg * 65535), toG, p)), o + 2);
107000
+ out.writeUInt16LE(clamp16(mix16(Math.round(cb * 65535), toB, p)), o + 4);
107001
+ }
107002
+ };
107003
+ TRANSITIONS["glitch"] = glitch;
107004
+ function aces(x) {
107005
+ return Math.max(0, Math.min(1, x * (2.51 * x + 0.03) / (x * (2.43 * x + 0.59) + 0.14)));
107006
+ }
107007
+ var lightLeak = (from2, to, out, w, h, p) => {
107008
+ const accent = [5e4 / 65535, 25e3 / 65535, 5e3 / 65535];
107009
+ const accentBright = [65535 / 65535, 55e3 / 65535, 35e3 / 65535];
107010
+ const lpx = 1.3;
107011
+ const lpy = -0.2;
107012
+ for (let i = 0; i < w * h; i++) {
107013
+ const ux = i % w / w;
107014
+ const uy = Math.floor(i / w) / h;
107015
+ const o = i * 6;
107016
+ const dx = ux - lpx;
107017
+ const dy = uy - lpy;
107018
+ const dist = Math.sqrt(dx * dx + dy * dy);
107019
+ const leak = Math.max(0, Math.min(1, Math.exp(-dist * 1.8) * p * 4));
107020
+ const warmR = accent[0] + (accentBright[0] - accent[0]) * dist * 0.7;
107021
+ const warmG = accent[1] + (accentBright[1] - accent[1]) * dist * 0.7;
107022
+ const warmB = accent[2] + (accentBright[2] - accent[2]) * dist * 0.7;
107023
+ const flare = Math.exp(-Math.abs(uy - (-0.2 + ux * 0.3)) * 15) * leak * 0.3;
107024
+ const [fr, fg, fb] = sampleRgb48le(from2, ux, uy, w, h);
107025
+ const fromR = fr / 65535;
107026
+ const fromG = fg / 65535;
107027
+ const fromB = fb / 65535;
107028
+ const overR = aces(fromR + warmR * leak * 3 + accentBright[0] * flare);
107029
+ const overG = aces(fromG + warmG * leak * 3 + accentBright[1] * flare);
107030
+ const overB = aces(fromB + warmB * leak * 3 + accentBright[2] * flare);
107031
+ const [toR, toG, toB] = sampleRgb48le(to, ux, uy, w, h);
107032
+ const blend = smoothstep(0.15, 0.85, p);
107033
+ out.writeUInt16LE(clamp16(mix16(Math.round(overR * 65535), toR, blend)), o);
107034
+ out.writeUInt16LE(clamp16(mix16(Math.round(overG * 65535), toG, blend)), o + 2);
107035
+ out.writeUInt16LE(clamp16(mix16(Math.round(overB * 65535), toB, blend)), o + 4);
107036
+ }
107037
+ };
107038
+ TRANSITIONS["light-leak"] = lightLeak;
107039
+ var crossWarpMorph = (from2, to, out, w, h, p) => {
107040
+ for (let i = 0; i < w * h; i++) {
107041
+ const ux = i % w / w;
107042
+ const uy = Math.floor(i / w) / h;
107043
+ const o = i * 6;
107044
+ const dispX = fbm(ux * 3, uy * 3) - 0.5;
107045
+ const dispY = fbm(ux * 3 + 7.3, uy * 3 + 3.7) - 0.5;
107046
+ const fromUx = Math.max(0, Math.min(1, ux + dispX * p * 0.5));
107047
+ const fromUy = Math.max(0, Math.min(1, uy + dispY * p * 0.5));
107048
+ const toUx = Math.max(0, Math.min(1, ux - dispX * (1 - p) * 0.5));
107049
+ const toUy = Math.max(0, Math.min(1, uy - dispY * (1 - p) * 0.5));
107050
+ const [fromR, fromG, fromB] = sampleRgb48le(from2, fromUx, fromUy, w, h);
107051
+ const [toR, toG, toB] = sampleRgb48le(to, toUx, toUy, w, h);
107052
+ const n = fbm(ux * 4 + 3.1, uy * 4 + 1.7);
107053
+ const blend = smoothstep(0.4, 0.6, n + p * 1.2 - 0.6);
107054
+ out.writeUInt16LE(clamp16(mix16(fromR, toR, blend)), o);
107055
+ out.writeUInt16LE(clamp16(mix16(fromG, toG, blend)), o + 2);
107056
+ out.writeUInt16LE(clamp16(mix16(fromB, toB, blend)), o + 4);
107057
+ }
107058
+ };
107059
+ TRANSITIONS["cross-warp-morph"] = crossWarpMorph;
107060
+ var whipPan = (from2, to, out, w, h, p) => {
107061
+ const fromOff = p * 1.5;
107062
+ const toOff = (1 - p) * 1.5;
107063
+ for (let i = 0; i < w * h; i++) {
107064
+ const ux = i % w / w;
107065
+ const uy = Math.floor(i / w) / h;
107066
+ const o = i * 6;
107067
+ let fromR = 0, fromG = 0, fromB = 0;
107068
+ for (let s = 0; s < 10; s++) {
107069
+ const f = s / 10;
107070
+ const fuv = Math.max(0, Math.min(1, ux + fromOff + p * 0.08 * f));
107071
+ const [r, g, b] = sampleRgb48le(from2, fuv, uy, w, h);
107072
+ fromR += r;
107073
+ fromG += g;
107074
+ fromB += b;
107075
+ }
107076
+ fromR /= 10;
107077
+ fromG /= 10;
107078
+ fromB /= 10;
107079
+ let toR = 0, toG = 0, toB = 0;
107080
+ for (let s = 0; s < 10; s++) {
107081
+ const f = s / 10;
107082
+ const tuv = Math.max(0, Math.min(1, ux - toOff - (1 - p) * 0.08 * f));
107083
+ const [r, g, b] = sampleRgb48le(to, tuv, uy, w, h);
107084
+ toR += r;
107085
+ toG += g;
107086
+ toB += b;
107087
+ }
107088
+ toR /= 10;
107089
+ toG /= 10;
107090
+ toB /= 10;
107091
+ out.writeUInt16LE(clamp16(mix16(Math.round(fromR), Math.round(toR), p)), o);
107092
+ out.writeUInt16LE(clamp16(mix16(Math.round(fromG), Math.round(toG), p)), o + 2);
107093
+ out.writeUInt16LE(clamp16(mix16(Math.round(fromB), Math.round(toB), p)), o + 4);
107094
+ }
107095
+ };
107096
+ TRANSITIONS["whip-pan"] = whipPan;
107097
+ var cinematicZoom = (from2, to, out, w, h, p) => {
107098
+ const fromS = p * 0.08;
107099
+ const toS = (1 - p) * 0.06;
107100
+ for (let i = 0; i < w * h; i++) {
107101
+ const ux = i % w / w;
107102
+ const uy = Math.floor(i / w) / h;
107103
+ const o = i * 6;
107104
+ const dx = ux - 0.5;
107105
+ const dy = uy - 0.5;
107106
+ let fr = 0, fg = 0, fb = 0;
107107
+ for (let s = 0; s < 12; s++) {
107108
+ const f = s / 12;
107109
+ const rr = sampleRgb48le(
107110
+ from2,
107111
+ ux - dx * fromS * 1.06 * f,
107112
+ uy - dy * fromS * 1.06 * f,
107113
+ w,
107114
+ h
107115
+ )[0];
107116
+ const gg = sampleRgb48le(from2, ux - dx * fromS * f, uy - dy * fromS * f, w, h)[1];
107117
+ const bb = sampleRgb48le(
107118
+ from2,
107119
+ ux - dx * fromS * 0.94 * f,
107120
+ uy - dy * fromS * 0.94 * f,
107121
+ w,
107122
+ h
107123
+ )[2];
107124
+ fr += rr;
107125
+ fg += gg;
107126
+ fb += bb;
107127
+ }
107128
+ fr /= 12;
107129
+ fg /= 12;
107130
+ fb /= 12;
107131
+ let tr = 0, tg = 0, tb = 0;
107132
+ for (let s = 0; s < 12; s++) {
107133
+ const f = s / 12;
107134
+ const rr = sampleRgb48le(to, ux + dx * toS * 1.06 * f, uy + dy * toS * 1.06 * f, w, h)[0];
107135
+ const gg = sampleRgb48le(to, ux + dx * toS * f, uy + dy * toS * f, w, h)[1];
107136
+ const bb = sampleRgb48le(to, ux + dx * toS * 0.94 * f, uy + dy * toS * 0.94 * f, w, h)[2];
107137
+ tr += rr;
107138
+ tg += gg;
107139
+ tb += bb;
107140
+ }
107141
+ tr /= 12;
107142
+ tg /= 12;
107143
+ tb /= 12;
107144
+ out.writeUInt16LE(clamp16(mix16(Math.round(fr), Math.round(tr), p)), o);
107145
+ out.writeUInt16LE(clamp16(mix16(Math.round(fg), Math.round(tg), p)), o + 2);
107146
+ out.writeUInt16LE(clamp16(mix16(Math.round(fb), Math.round(tb), p)), o + 4);
107147
+ }
107148
+ };
107149
+ TRANSITIONS["cinematic-zoom"] = cinematicZoom;
107150
+ var gravitationalLens = (from2, to, out, w, h, p) => {
107151
+ for (let i = 0; i < w * h; i++) {
107152
+ const ux = i % w / w;
107153
+ const uy = Math.floor(i / w) / h;
107154
+ const o = i * 6;
107155
+ const uvx = ux - 0.5;
107156
+ const uvy = uy - 0.5;
107157
+ const dist = Math.sqrt(uvx * uvx + uvy * uvy);
107158
+ const pull = p * 2;
107159
+ const warpStr = pull * 0.3 / (dist + 0.1);
107160
+ const warpedX = Math.max(0, Math.min(1, ux - uvx * warpStr));
107161
+ const warpedY = Math.max(0, Math.min(1, uy - uvy * warpStr));
107162
+ const [, ag] = sampleRgb48le(from2, warpedX, warpedY, w, h);
107163
+ const horizon = smoothstep(0, 0.3, dist / (1 - p * 0.85 + 1e-3));
107164
+ const shift = pull * 0.02 / (dist + 0.2);
107165
+ const rSampX = Math.max(0, Math.min(1, ux - uvx * (warpStr + shift)));
107166
+ const rSampY = Math.max(0, Math.min(1, uy - uvy * (warpStr + shift)));
107167
+ const bSampX = Math.max(0, Math.min(1, ux - uvx * (warpStr - shift)));
107168
+ const bSampY = Math.max(0, Math.min(1, uy - uvy * (warpStr - shift)));
107169
+ const ar = sampleRgb48le(from2, rSampX, rSampY, w, h)[0];
107170
+ const ab = sampleRgb48le(from2, bSampX, bSampY, w, h)[2];
107171
+ const lensedR = Math.round(ar * horizon);
107172
+ const lensedG = Math.round(ag * horizon);
107173
+ const lensedB = Math.round(ab * horizon);
107174
+ const [toR, toG, toB] = sampleRgb48le(to, ux, uy, w, h);
107175
+ const blend = smoothstep(0.3, 0.9, p);
107176
+ out.writeUInt16LE(clamp16(mix16(lensedR, toR, blend)), o);
107177
+ out.writeUInt16LE(clamp16(mix16(lensedG, toG, blend)), o + 2);
107178
+ out.writeUInt16LE(clamp16(mix16(lensedB, toB, blend)), o + 4);
107179
+ }
107180
+ };
107181
+ TRANSITIONS["gravitational-lens"] = gravitationalLens;
107182
+ var rippleWaves = (from2, to, out, w, h, p) => {
107183
+ const accentBright = [65535, 55e3, 35e3];
107184
+ for (let i = 0; i < w * h; i++) {
107185
+ const ux = i % w / w;
107186
+ const uy = Math.floor(i / w) / h;
107187
+ const o = i * 6;
107188
+ const uvx = ux - 0.5;
107189
+ const uvy = uy - 0.5;
107190
+ const dist = Math.sqrt(uvx * uvx + uvy * uvy);
107191
+ const nux = uvx + 1e-3;
107192
+ const nuy = uvy + 1e-3;
107193
+ const nlen = Math.sqrt(nux * nux + nuy * nuy);
107194
+ const dirx = nux / nlen;
107195
+ const diry = nuy / nlen;
107196
+ const fromAmp = p * 0.04;
107197
+ const fw1 = Math.exp(Math.sin(dist * 25 - p * 12) - 1);
107198
+ const fw2 = Math.exp(Math.sin(dist * 50 - p * 18) - 1) * 0.5;
107199
+ const fromUx = Math.max(0, Math.min(1, ux + dirx * (fw1 + fw2) * fromAmp));
107200
+ const fromUy = Math.max(0, Math.min(1, uy + diry * (fw1 + fw2) * fromAmp));
107201
+ const toAmp = (1 - p) * 0.04;
107202
+ const tw1 = Math.exp(Math.sin(dist * 25 + p * 12) - 1);
107203
+ const tw2 = Math.exp(Math.sin(dist * 50 + p * 18) - 1) * 0.5;
107204
+ const toUx = Math.max(0, Math.min(1, ux - dirx * (tw1 + tw2) * toAmp));
107205
+ const toUy = Math.max(0, Math.min(1, uy - diry * (tw1 + tw2) * toAmp));
107206
+ const [fromR, fromG, fromB] = sampleRgb48le(from2, fromUx, fromUy, w, h);
107207
+ const [toR, toG, toB] = sampleRgb48le(to, toUx, toUy, w, h);
107208
+ const peak = fw1 * p;
107209
+ const tintR = accentBright[0] * peak * 0.1;
107210
+ const tintG = accentBright[1] * peak * 0.1;
107211
+ const tintB = accentBright[2] * peak * 0.1;
107212
+ out.writeUInt16LE(clamp16(mix16(Math.round(fromR + tintR), toR, p)), o);
107213
+ out.writeUInt16LE(clamp16(mix16(Math.round(fromG + tintG), toG, p)), o + 2);
107214
+ out.writeUInt16LE(clamp16(mix16(Math.round(fromB + tintB), toB, p)), o + 4);
107215
+ }
107216
+ };
107217
+ TRANSITIONS["ripple-waves"] = rippleWaves;
107218
+ var swirlVortex = (from2, to, out, w, h, p) => {
107219
+ for (let i = 0; i < w * h; i++) {
107220
+ const ux = i % w / w;
107221
+ const uy = Math.floor(i / w) / h;
107222
+ const o = i * 6;
107223
+ const uvx = ux - 0.5;
107224
+ const uvy = uy - 0.5;
107225
+ const dist = Math.sqrt(uvx * uvx + uvy * uvy);
107226
+ const warp = fbm(ux * 4, uy * 4) * 0.5;
107227
+ const fromAng = p * (1 - dist) * 10 + warp * p * 3;
107228
+ const fs8 = Math.sin(fromAng);
107229
+ const fc = Math.cos(fromAng);
107230
+ const fromUx = Math.max(0, Math.min(1, uvx * fc - uvy * fs8 + 0.5));
107231
+ const fromUy = Math.max(0, Math.min(1, uvx * fs8 + uvy * fc + 0.5));
107232
+ const toAng = -(1 - p) * (1 - dist) * 10 - warp * (1 - p) * 3;
107233
+ const ts = Math.sin(toAng);
107234
+ const tc = Math.cos(toAng);
107235
+ const toUx = Math.max(0, Math.min(1, uvx * tc - uvy * ts + 0.5));
107236
+ const toUy = Math.max(0, Math.min(1, uvx * ts + uvy * tc + 0.5));
107237
+ const [fromR, fromG, fromB] = sampleRgb48le(from2, fromUx, fromUy, w, h);
107238
+ const [toR, toG, toB] = sampleRgb48le(to, toUx, toUy, w, h);
107239
+ out.writeUInt16LE(clamp16(mix16(fromR, toR, p)), o);
107240
+ out.writeUInt16LE(clamp16(mix16(fromG, toG, p)), o + 2);
107241
+ out.writeUInt16LE(clamp16(mix16(fromB, toB, p)), o + 4);
107242
+ }
107243
+ };
107244
+ TRANSITIONS["swirl-vortex"] = swirlVortex;
107245
+ var thermalDistortion = (from2, to, out, w, h, p) => {
107246
+ const accentBright = [65535, 55e3, 35e3];
107247
+ for (let i = 0; i < w * h; i++) {
107248
+ const ux = i % w / w;
107249
+ const uy = Math.floor(i / w) / h;
107250
+ const o = i * 6;
107251
+ const heat = p * 1.5;
107252
+ const yFade = smoothstep(1, 0, uy);
107253
+ const shimmer = Math.sin(uy * 40 + fbm(ux * 6, uy * 6) * 8) * fbm(ux * 3 + 0, uy * 3 + p * 2);
107254
+ const dispX = shimmer * heat * 0.03 * yFade;
107255
+ const fromUx = Math.max(0, Math.min(1, ux + dispX));
107256
+ const [fromR, fromG, fromB] = sampleRgb48le(from2, fromUx, uy, w, h);
107257
+ const invShimmer = Math.sin(uy * 40 + fbm(ux * 6 + 3, uy * 6 + 3) * 8) * fbm(ux * 3 + 3, uy * 3 + p * 2);
107258
+ const dispX2 = invShimmer * (1 - p) * 0.03 * yFade;
107259
+ const toUx = Math.max(0, Math.min(1, ux + dispX2));
107260
+ const [toR, toG, toB] = sampleRgb48le(to, toUx, uy, w, h);
107261
+ const haze = heat * yFade * 0.15 * (1 - p);
107262
+ out.writeUInt16LE(clamp16(mix16(fromR, toR, p) + Math.round(accentBright[0] * haze)), o);
107263
+ out.writeUInt16LE(clamp16(mix16(fromG, toG, p) + Math.round(accentBright[1] * haze)), o + 2);
107264
+ out.writeUInt16LE(clamp16(mix16(fromB, toB, p) + Math.round(accentBright[2] * haze)), o + 4);
107265
+ }
107266
+ };
107267
+ TRANSITIONS["thermal-distortion"] = thermalDistortion;
107268
+ var domainWarp = (from2, to, out, w, h, p) => {
107269
+ const accentDark = [25e3, 8e3, 2e3];
107270
+ const accentBright = [65535, 55e3, 35e3];
107271
+ for (let i = 0; i < w * h; i++) {
107272
+ const ux = i % w / w;
107273
+ const uy = Math.floor(i / w) / h;
107274
+ const o = i * 6;
107275
+ const qx = fbm(ux * 3, uy * 3);
107276
+ const qy = fbm(ux * 3 + 5.2, uy * 3 + 1.3);
107277
+ const rx = fbm(ux * 3 + qx * 4 + 1.7, uy * 3 + qy * 4 + 9.2);
107278
+ const ry = fbm(ux * 3 + qx * 4 + 8.3, uy * 3 + qy * 4 + 2.8);
107279
+ const n = fbm(ux * 3 + rx * 2, uy * 3 + ry * 2);
107280
+ const warpDirX = (qx - 0.5) * 0.4;
107281
+ const warpDirY = (qy - 0.5) * 0.4;
107282
+ const aUx = Math.max(0, Math.min(1, ux + warpDirX * p));
107283
+ const aUy = Math.max(0, Math.min(1, uy + warpDirY * p));
107284
+ const bUx = Math.max(0, Math.min(1, ux - warpDirX * (1 - p)));
107285
+ const bUy = Math.max(0, Math.min(1, uy - warpDirY * (1 - p)));
107286
+ const [aR, aG, aB] = sampleRgb48le(from2, aUx, aUy, w, h);
107287
+ const [bR, bG, bB] = sampleRgb48le(to, bUx, bUy, w, h);
107288
+ const e = smoothstep(p - 0.08, p + 0.08, n);
107289
+ const ed = Math.abs(n - p);
107290
+ const pStep = p >= 1 ? 1 : 0;
107291
+ const em = smoothstep(0.1, 0, ed) * (1 - pStep);
107292
+ const ecBlend = smoothstep(0, 0.1, ed);
107293
+ const ecR = accentDark[0] + (accentBright[0] - accentDark[0]) * (1 - ecBlend);
107294
+ const ecG = accentDark[1] + (accentBright[1] - accentDark[1]) * (1 - ecBlend);
107295
+ const ecB = accentDark[2] + (accentBright[2] - accentDark[2]) * (1 - ecBlend);
107296
+ out.writeUInt16LE(clamp16(mix16(bR, aR, e) + Math.round(ecR * em * 2)), o);
107297
+ out.writeUInt16LE(clamp16(mix16(bG, aG, e) + Math.round(ecG * em * 2)), o + 2);
107298
+ out.writeUInt16LE(clamp16(mix16(bB, aB, e) + Math.round(ecB * em * 2)), o + 4);
107299
+ }
107300
+ };
107301
+ TRANSITIONS["domain-warp"] = domainWarp;
107302
+ function ridged(px, py) {
107303
+ let value = 0;
107304
+ let amplitude = 0.5;
107305
+ let x = px;
107306
+ let y = py;
107307
+ for (let i = 0; i < 5; i++) {
107308
+ value += amplitude * Math.abs(vnoise(x, y) * 2 - 1);
107309
+ const nx = ROT_A * x - ROT_B * y;
107310
+ const ny = ROT_B * x + ROT_A * y;
107311
+ x = nx * 2.02;
107312
+ y = ny * 2.02;
107313
+ amplitude *= 0.5;
107314
+ }
107315
+ return value;
107316
+ }
107317
+ var ridgedBurn = (from2, to, out, w, h, p) => {
107318
+ const accent = [5e4, 25e3, 5e3];
107319
+ const accentDark = [25e3, 8e3, 2e3];
107320
+ const accentBright = [65535, 55e3, 35e3];
107321
+ for (let i = 0; i < w * h; i++) {
107322
+ const ux = i % w / w;
107323
+ const uy = Math.floor(i / w) / h;
107324
+ const o = i * 6;
107325
+ const [aR, aG, aB] = sampleRgb48le(from2, ux, uy, w, h);
107326
+ const [bR, bG, bB] = sampleRgb48le(to, ux, uy, w, h);
107327
+ const n = ridged(ux * 4, uy * 4);
107328
+ const e = smoothstep(p - 0.04, p + 0.04, n);
107329
+ const heat = smoothstep(0.12, 0, Math.abs(n - p));
107330
+ const pStep = p >= 1 ? 1 : 0;
107331
+ const heatMasked = heat * (1 - pStep);
107332
+ let burnR = accentDark[0] + (accent[0] - accentDark[0]) * smoothstep(0, 0.25, heatMasked);
107333
+ let burnG = accentDark[1] + (accent[1] - accentDark[1]) * smoothstep(0, 0.25, heatMasked);
107334
+ let burnB = accentDark[2] + (accent[2] - accentDark[2]) * smoothstep(0, 0.25, heatMasked);
107335
+ const blend2 = smoothstep(0.25, 0.5, heatMasked);
107336
+ burnR = burnR + (accentBright[0] - burnR) * blend2;
107337
+ burnG = burnG + (accentBright[1] - burnG) * blend2;
107338
+ burnB = burnB + (accentBright[2] - burnB) * blend2;
107339
+ const blend3 = smoothstep(0.5, 1, heatMasked);
107340
+ burnR = burnR + (65535 - burnR) * blend3;
107341
+ burnG = burnG + (65535 - burnG) * blend3;
107342
+ burnB = burnB + (65535 - burnB) * blend3;
107343
+ const sparks = (vnoise(ux * 80, uy * 80) >= 0.92 ? 1 : 0) * heatMasked * 3;
107344
+ out.writeUInt16LE(
107345
+ clamp16(
107346
+ mix16(bR, aR, e) + Math.round(burnR * heatMasked * 3.5) + Math.round(accentBright[0] * sparks)
107347
+ ),
107348
+ o
107349
+ );
107350
+ out.writeUInt16LE(
107351
+ clamp16(
107352
+ mix16(bG, aG, e) + Math.round(burnG * heatMasked * 3.5) + Math.round(accentBright[1] * sparks)
107353
+ ),
107354
+ o + 2
107355
+ );
107356
+ out.writeUInt16LE(
107357
+ clamp16(
107358
+ mix16(bB, aB, e) + Math.round(burnB * heatMasked * 3.5) + Math.round(accentBright[2] * sparks)
107359
+ ),
107360
+ o + 4
107361
+ );
107362
+ }
107363
+ };
107364
+ TRANSITIONS["ridged-burn"] = ridgedBurn;
107365
+
105954
107366
  // src/services/renderOrchestrator.ts
105955
107367
  import { join as join15, dirname as dirname10, resolve as resolve10 } from "path";
105956
107368
  import { randomUUID } from "crypto";
@@ -106271,6 +107683,10 @@ var RENDER_MODE_SCRIPT = `(function() {
106271
107683
  }
106272
107684
  waitForPlayer();
106273
107685
  })();`;
107686
+ var HF_EARLY_STUB = `(function() {
107687
+ if (typeof window === "undefined") return;
107688
+ if (!window.__hf) window.__hf = {};
107689
+ })();`;
106274
107690
  var HF_BRIDGE_SCRIPT = `(function() {
106275
107691
  var __realSetInterval =
106276
107692
  window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetInterval === "function"
@@ -106316,20 +107732,24 @@ var HF_BRIDGE_SCRIPT = `(function() {
106316
107732
  if (!p || typeof p.renderSeek !== "function" || typeof p.getDuration !== "function") {
106317
107733
  return false;
106318
107734
  }
106319
- window.__hf = {
106320
- get duration() {
107735
+ var hf = window.__hf || {};
107736
+ Object.defineProperty(hf, "duration", {
107737
+ configurable: true,
107738
+ enumerable: true,
107739
+ get: function() {
106321
107740
  var d = p.getDuration();
106322
107741
  return d > 0 ? d : getDeclaredDuration();
106323
107742
  },
106324
- seek: function(t) {
106325
- p.renderSeek(t);
106326
- var nextTimeMs = (Math.max(0, Number(t) || 0)) * 1000;
106327
- if (window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.seekToTime === "function") {
106328
- window.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);
106329
- }
106330
- seekSameOriginChildFrames(window, nextTimeMs);
106331
- },
107743
+ });
107744
+ hf.seek = function(t) {
107745
+ p.renderSeek(t);
107746
+ var nextTimeMs = (Math.max(0, Number(t) || 0)) * 1000;
107747
+ if (window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.seekToTime === "function") {
107748
+ window.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);
107749
+ }
107750
+ seekSameOriginChildFrames(window, nextTimeMs);
106332
107751
  };
107752
+ window.__hf = hf;
106333
107753
  return true;
106334
107754
  }
106335
107755
  if (bridge()) return;
@@ -106411,7 +107831,7 @@ ${headTags}`);
106411
107831
  }
106412
107832
  function createFileServer2(options) {
106413
107833
  const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
106414
- const preHeadScripts = options.preHeadScripts ?? [];
107834
+ const preHeadScripts = [HF_EARLY_STUB, ...options.preHeadScripts ?? []];
106415
107835
  const headScripts = options.headScripts ?? [getVerifiedHyperframeRuntimeSource()];
106416
107836
  const bodyScripts = options.bodyScripts ?? [RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT];
106417
107837
  const app = new Hono2();
@@ -107671,6 +109091,24 @@ async function safeCleanup(label, fn, log = defaultLogger) {
107671
109091
  });
107672
109092
  }
107673
109093
  }
109094
+ var frameDirMaxIndexCache = /* @__PURE__ */ new Map();
109095
+ var FRAME_FILENAME_RE = /^frame_(\d+)\.png$/;
109096
+ function getMaxFrameIndex(frameDir) {
109097
+ const cached = frameDirMaxIndexCache.get(frameDir);
109098
+ if (cached !== void 0) return cached;
109099
+ let max = 0;
109100
+ try {
109101
+ for (const name of readdirSync6(frameDir)) {
109102
+ const m = FRAME_FILENAME_RE.exec(name);
109103
+ if (!m) continue;
109104
+ const n = Number(m[1]);
109105
+ if (Number.isFinite(n) && n > max) max = n;
109106
+ }
109107
+ } catch {
109108
+ }
109109
+ frameDirMaxIndexCache.set(frameDir, max);
109110
+ return max;
109111
+ }
107674
109112
  var RenderCancelledError = class extends Error {
107675
109113
  reason;
107676
109114
  constructor(message = "render_cancelled", reason = "aborted") {
@@ -107772,6 +109210,63 @@ function applyRenderModeHints(cfg, compiled, log = defaultLogger) {
107772
109210
  reasons: compiled.renderModeHints.reasons.map((reason) => reason.message)
107773
109211
  });
107774
109212
  }
109213
+ function blitHdrVideoLayer(canvas, el, time, fps, hdrFrameDirs, hdrStartTimes, width, height, log, sourceTransfer, targetTransfer) {
109214
+ const frameDir = hdrFrameDirs.get(el.id);
109215
+ const startTime = hdrStartTimes.get(el.id);
109216
+ if (!frameDir || startTime === void 0) {
109217
+ return;
109218
+ }
109219
+ const videoFrameIndex = Math.round((time - startTime) * fps) + 1;
109220
+ if (videoFrameIndex < 1) return;
109221
+ const maxIndex = getMaxFrameIndex(frameDir);
109222
+ const effectiveIndex = maxIndex > 0 ? Math.min(videoFrameIndex, maxIndex) : videoFrameIndex;
109223
+ const framePath = join15(frameDir, `frame_${String(effectiveIndex).padStart(4, "0")}.png`);
109224
+ if (!existsSync15(framePath)) {
109225
+ return;
109226
+ }
109227
+ try {
109228
+ const { data: hdrRgb, width: srcW, height: srcH } = decodePngToRgb48le(readFileSync9(framePath));
109229
+ if (sourceTransfer && targetTransfer && sourceTransfer !== targetTransfer) {
109230
+ convertTransfer(hdrRgb, sourceTransfer, targetTransfer);
109231
+ }
109232
+ const viewportMatrix = parseTransformMatrix(el.transform);
109233
+ const br = el.borderRadius;
109234
+ const hasBorderRadius = br[0] > 0 || br[1] > 0 || br[2] > 0 || br[3] > 0;
109235
+ const borderRadiusParam = hasBorderRadius ? br : void 0;
109236
+ if (viewportMatrix) {
109237
+ blitRgb48leAffine(
109238
+ canvas,
109239
+ hdrRgb,
109240
+ viewportMatrix,
109241
+ srcW,
109242
+ srcH,
109243
+ width,
109244
+ height,
109245
+ el.opacity < 0.999 ? el.opacity : void 0,
109246
+ borderRadiusParam
109247
+ );
109248
+ } else {
109249
+ blitRgb48leRegion(
109250
+ canvas,
109251
+ hdrRgb,
109252
+ el.x,
109253
+ el.y,
109254
+ srcW,
109255
+ srcH,
109256
+ width,
109257
+ height,
109258
+ el.opacity < 0.999 ? el.opacity : void 0,
109259
+ borderRadiusParam
109260
+ );
109261
+ }
109262
+ } catch (err) {
109263
+ if (log) {
109264
+ log.debug(`HDR blit failed for ${el.id}`, {
109265
+ error: err instanceof Error ? err.message : String(err)
109266
+ });
109267
+ }
109268
+ }
109269
+ }
107775
109270
  function createRenderJob(config2) {
107776
109271
  return {
107777
109272
  id: randomUUID(),
@@ -108042,6 +109537,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
108042
109537
  perfStages.browserProbeMs = Date.now() - probeStart;
108043
109538
  job.duration = composition.duration;
108044
109539
  job.totalFrames = Math.ceil(composition.duration * job.config.fps);
109540
+ const totalFrames = job.totalFrames;
108045
109541
  if (job.duration <= 0) {
108046
109542
  const diagnostics = [];
108047
109543
  try {
@@ -108070,7 +109566,10 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
108070
109566
  }
108071
109567
  }
108072
109568
  }
108073
- } catch {
109569
+ } catch (err) {
109570
+ log.warn("Failed to gather browser diagnostics for zero-duration composition", {
109571
+ error: err instanceof Error ? err.message : String(err)
109572
+ });
108074
109573
  diagnostics.push("(Could not gather browser diagnostics \u2014 page may have crashed)");
108075
109574
  }
108076
109575
  const hint = diagnostics.length > 0 ? "\n\nDiagnostics:\n - " + diagnostics.join("\n - ") : "\n\nCheck that GSAP timelines are registered on window.__timelines.";
@@ -108094,8 +109593,28 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
108094
109593
  updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress);
108095
109594
  let frameLookup = null;
108096
109595
  const compiledDir = join15(workDir, "compiled");
109596
+ let extractionResult = null;
109597
+ const nativeHdrVideoIds = /* @__PURE__ */ new Set();
109598
+ const videoTransfers = /* @__PURE__ */ new Map();
109599
+ if (job.config.hdr && composition.videos.length > 0) {
109600
+ await Promise.all(
109601
+ composition.videos.map(async (v) => {
109602
+ let videoPath = v.src;
109603
+ if (!videoPath.startsWith("/")) {
109604
+ const fromCompiled = existsSync15(join15(compiledDir, videoPath)) ? join15(compiledDir, videoPath) : join15(projectDir, videoPath);
109605
+ videoPath = fromCompiled;
109606
+ }
109607
+ if (!existsSync15(videoPath)) return;
109608
+ const meta = await extractVideoMetadata(videoPath);
109609
+ if (isHdrColorSpace(meta.colorSpace)) {
109610
+ nativeHdrVideoIds.add(v.id);
109611
+ videoTransfers.set(v.id, detectTransfer(meta.colorSpace));
109612
+ }
109613
+ })
109614
+ );
109615
+ }
108097
109616
  if (composition.videos.length > 0) {
108098
- const extractionResult = await extractAllVideoFrames(
109617
+ extractionResult = await extractAllVideoFrames(
108099
109618
  composition.videos,
108100
109619
  projectDir,
108101
109620
  { fps: job.config.fps, outputDir: join15(workDir, "video-frames") },
@@ -108130,6 +109649,29 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
108130
109649
  } else {
108131
109650
  perfStages.videoExtractMs = Date.now() - stage2Start;
108132
109651
  }
109652
+ let effectiveHdr;
109653
+ if (job.config.hdr && frameLookup) {
109654
+ const colorSpaces = (extractionResult?.extracted ?? []).map((ext) => ext.metadata.colorSpace);
109655
+ const info = analyzeCompositionHdr(colorSpaces);
109656
+ if (info.hasHdr && info.dominantTransfer) {
109657
+ effectiveHdr = { transfer: info.dominantTransfer };
109658
+ }
109659
+ }
109660
+ if (job.config.hdr && !effectiveHdr && nativeHdrVideoIds.size > 0) {
109661
+ const firstTransfer = videoTransfers.values().next().value;
109662
+ if (firstTransfer) {
109663
+ effectiveHdr = { transfer: firstTransfer };
109664
+ }
109665
+ }
109666
+ if (effectiveHdr && outputFormat !== "mp4") {
109667
+ log.info(`[Render] HDR source detected but format is ${outputFormat} \u2014 using SDR`);
109668
+ effectiveHdr = void 0;
109669
+ }
109670
+ if (effectiveHdr) {
109671
+ log.info(
109672
+ `[Render] HDR source detected \u2014 output: ${effectiveHdr.transfer.toUpperCase()} (BT.2020, 10-bit H.265)`
109673
+ );
109674
+ }
108133
109675
  const stage3Start = Date.now();
108134
109676
  updateJobStatus(job, "preprocessing", "Processing audio tracks", 20, onProgress);
108135
109677
  const audioOutputPath = join15(workDir, "audio.aac");
@@ -108171,222 +109713,655 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
108171
109713
  format: needsAlpha ? "png" : "jpeg",
108172
109714
  quality: needsAlpha ? void 0 : job.config.quality === "draft" ? 80 : 95
108173
109715
  };
108174
- const workerCount = calculateOptimalWorkers(job.totalFrames, job.config.workers, cfg);
109716
+ const workerCount = calculateOptimalWorkers(totalFrames, job.config.workers, cfg);
108175
109717
  const FORMAT_EXT = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
108176
109718
  const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
108177
109719
  const videoOnlyPath = join15(workDir, `video-only${videoExt}`);
108178
- const preset = getEncoderPreset(job.config.quality, outputFormat);
108179
- const effectiveQuality = job.config.crf ?? preset.quality;
108180
- const effectiveBitrate = job.config.videoBitrate;
108181
- const baseEncoderOpts = {
108182
- fps: job.config.fps,
108183
- width,
108184
- height,
108185
- codec: preset.codec,
108186
- preset: preset.preset,
108187
- quality: effectiveQuality,
108188
- bitrate: effectiveBitrate,
108189
- pixelFormat: preset.pixelFormat,
108190
- useGpu: job.config.useGpu
108191
- };
109720
+ const hasHdrContent = effectiveHdr && nativeHdrVideoIds.size > 0;
109721
+ const encoderHdr = hasHdrContent ? effectiveHdr : void 0;
109722
+ const preset = getEncoderPreset(job.config.quality, outputFormat, encoderHdr);
108192
109723
  job.framesRendered = 0;
108193
- let streamingEncoder = null;
108194
- if (enableStreamingEncode) {
108195
- streamingEncoder = await spawnStreamingEncoder(
109724
+ if (hasHdrContent) {
109725
+ log.info("[Render] HDR layered composite: z-ordered DOM + native HLG video layers");
109726
+ const hdrVideoIds = composition.videos.filter((v) => nativeHdrVideoIds.has(v.id)).map((v) => v.id);
109727
+ const hdrVideoSrcPaths = /* @__PURE__ */ new Map();
109728
+ for (const v of composition.videos) {
109729
+ if (!hdrVideoIds.includes(v.id)) continue;
109730
+ let srcPath = v.src;
109731
+ if (!srcPath.startsWith("/")) {
109732
+ const fromCompiled = join15(compiledDir, srcPath);
109733
+ srcPath = existsSync15(fromCompiled) ? fromCompiled : join15(projectDir, srcPath);
109734
+ }
109735
+ hdrVideoSrcPaths.set(v.id, srcPath);
109736
+ }
109737
+ if (!fileServer) throw new Error("fileServer must be initialized before HDR compositing");
109738
+ const domSession = await createCaptureSession(
109739
+ fileServer.url,
109740
+ framesDir,
109741
+ captureOptions,
109742
+ createVideoFrameInjector(frameLookup),
109743
+ cfg
109744
+ );
109745
+ await initializeSession(domSession);
109746
+ assertNotAborted();
109747
+ lastBrowserConsole = domSession.browserConsoleBuffer;
109748
+ await initTransparentBackground(domSession.page);
109749
+ const transitionMeta = await domSession.page.evaluate(() => {
109750
+ return window.__hf?.transitions ?? [];
109751
+ });
109752
+ const sceneElements = await domSession.page.evaluate(() => {
109753
+ const scenes = document.querySelectorAll(".scene");
109754
+ const map2 = {};
109755
+ for (const scene of scenes) {
109756
+ const els = scene.querySelectorAll("[data-start]");
109757
+ map2[scene.id] = Array.from(els).map((e) => e.id);
109758
+ }
109759
+ return map2;
109760
+ });
109761
+ const transitionRanges = transitionMeta.map((t) => ({
109762
+ ...t,
109763
+ startFrame: Math.floor(t.time * job.config.fps),
109764
+ endFrame: Math.ceil((t.time + t.duration) * job.config.fps)
109765
+ }));
109766
+ if (transitionRanges.length > 0) {
109767
+ log.info("[Render] Detected shader transitions for HDR compositing", {
109768
+ count: transitionRanges.length,
109769
+ transitions: transitionRanges.map((t) => ({
109770
+ shader: t.shader,
109771
+ from: t.fromScene,
109772
+ to: t.toScene,
109773
+ frames: `${t.startFrame}-${t.endFrame}`
109774
+ }))
109775
+ });
109776
+ }
109777
+ const hdrEncoder = await spawnStreamingEncoder(
108196
109778
  videoOnlyPath,
108197
109779
  {
108198
- ...baseEncoderOpts,
108199
- imageFormat: captureOptions.format || "jpeg"
109780
+ fps: job.config.fps,
109781
+ width,
109782
+ height,
109783
+ codec: preset.codec,
109784
+ preset: preset.preset,
109785
+ quality: preset.quality,
109786
+ pixelFormat: preset.pixelFormat,
109787
+ hdr: preset.hdr,
109788
+ rawInputFormat: "rgb48le"
108200
109789
  },
108201
- abortSignal
109790
+ abortSignal,
109791
+ { ffmpegStreamingTimeout: 36e5 }
108202
109792
  );
108203
109793
  assertNotAborted();
108204
- }
108205
- if (enableStreamingEncode && streamingEncoder) {
108206
- const reorderBuffer = createFrameReorderBuffer(0, job.totalFrames);
108207
- const currentEncoder = streamingEncoder;
108208
- if (workerCount > 1) {
108209
- const tasks = distributeFrames(job.totalFrames, workerCount, workDir);
108210
- const onFrameBuffer = async (frameIndex, buffer) => {
108211
- await reorderBuffer.waitForFrame(frameIndex);
108212
- currentEncoder.writeFrame(buffer);
108213
- reorderBuffer.advanceTo(frameIndex + 1);
109794
+ const hdrExtractionDims = /* @__PURE__ */ new Map();
109795
+ const hdrVideoStartTimes = /* @__PURE__ */ new Map();
109796
+ for (const v of composition.videos) {
109797
+ if (hdrVideoIds.includes(v.id)) {
109798
+ hdrVideoStartTimes.set(v.id, v.start);
109799
+ }
109800
+ }
109801
+ const uniqueStartTimes = [...new Set(hdrVideoStartTimes.values())].sort((a, b) => a - b);
109802
+ for (const seekTime of uniqueStartTimes) {
109803
+ await domSession.page.evaluate((t) => {
109804
+ if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t);
109805
+ }, seekTime);
109806
+ if (domSession.onBeforeCapture) {
109807
+ await domSession.onBeforeCapture(domSession.page, seekTime);
109808
+ }
109809
+ const stacking = await queryElementStacking(domSession.page, nativeHdrVideoIds);
109810
+ for (const el of stacking) {
109811
+ if (el.isHdr && el.layoutWidth > 0 && el.layoutHeight > 0 && !hdrExtractionDims.has(el.id)) {
109812
+ hdrExtractionDims.set(el.id, { width: el.layoutWidth, height: el.layoutHeight });
109813
+ }
109814
+ }
109815
+ }
109816
+ const hdrFrameDirs = /* @__PURE__ */ new Map();
109817
+ for (const [videoId, srcPath] of hdrVideoSrcPaths) {
109818
+ const video = composition.videos.find((v) => v.id === videoId);
109819
+ if (!video) continue;
109820
+ const frameDir = join15(framesDir, `hdr_${videoId}`);
109821
+ mkdirSync10(frameDir, { recursive: true });
109822
+ const duration = video.end - video.start;
109823
+ const dims = hdrExtractionDims.get(videoId) ?? { width, height };
109824
+ const ffmpegArgs = [
109825
+ "-ss",
109826
+ String(video.mediaStart),
109827
+ "-i",
109828
+ srcPath,
109829
+ "-t",
109830
+ String(duration),
109831
+ "-r",
109832
+ String(job.config.fps),
109833
+ "-vf",
109834
+ `scale=${dims.width}:${dims.height}:force_original_aspect_ratio=increase,crop=${dims.width}:${dims.height}`,
109835
+ "-pix_fmt",
109836
+ "rgb48le",
109837
+ "-c:v",
109838
+ "png",
109839
+ "-y",
109840
+ join15(frameDir, "frame_%04d.png")
109841
+ ];
109842
+ const result = await runFfmpeg(ffmpegArgs, { signal: abortSignal });
109843
+ if (!result.success) {
109844
+ log.warn("HDR frame pre-extraction failed; loop will fill with black", {
109845
+ videoId,
109846
+ srcPath,
109847
+ stderr: result.stderr.slice(-400)
109848
+ });
109849
+ }
109850
+ hdrFrameDirs.set(videoId, frameDir);
109851
+ }
109852
+ assertNotAborted();
109853
+ try {
109854
+ let countNonZeroAlpha2 = function(rgba) {
109855
+ let n = 0;
109856
+ for (let p = 3; p < rgba.length; p += 4) {
109857
+ if (rgba[p] !== 0) n++;
109858
+ }
109859
+ return n;
109860
+ }, countNonZeroRgb482 = function(buf) {
109861
+ let n = 0;
109862
+ for (let p = 0; p < buf.length; p += 6) {
109863
+ if (buf[p] !== 0 || buf[p + 1] !== 0 || buf[p + 2] !== 0) n++;
109864
+ }
109865
+ return n;
108214
109866
  };
108215
- await executeParallelCapture(
108216
- fileServer.url,
108217
- workDir,
108218
- tasks,
108219
- captureOptions,
108220
- () => createVideoFrameInjector(frameLookup),
108221
- abortSignal,
108222
- (progress) => {
108223
- job.framesRendered = progress.capturedFrames;
108224
- const frameProgress = progress.capturedFrames / progress.totalFrames;
108225
- const progressPct = 25 + frameProgress * 55;
108226
- if (progress.capturedFrames % 30 === 0 || progress.capturedFrames === progress.totalFrames) {
108227
- updateJobStatus(
108228
- job,
108229
- "rendering",
108230
- `Streaming frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
108231
- Math.round(progressPct),
108232
- onProgress
109867
+ var countNonZeroAlpha = countNonZeroAlpha2, countNonZeroRgb48 = countNonZeroRgb482;
109868
+ const beforeCaptureHook = domSession.onBeforeCapture;
109869
+ const cleanedUpVideos = /* @__PURE__ */ new Set();
109870
+ const hdrVideoEndTimes = /* @__PURE__ */ new Map();
109871
+ for (const v of composition.videos) {
109872
+ if (hdrFrameDirs.has(v.id)) {
109873
+ hdrVideoEndTimes.set(v.id, v.end);
109874
+ }
109875
+ }
109876
+ const debugDumpEnabled = process.env.KEEP_TEMP === "1";
109877
+ const debugDumpDir = debugDumpEnabled ? join15(framesDir, "debug-composite") : null;
109878
+ if (debugDumpDir && !existsSync15(debugDumpDir)) {
109879
+ mkdirSync10(debugDumpDir, { recursive: true });
109880
+ }
109881
+ async function compositeToBuffer(canvas, time, fullStacking, elementFilter, debugFrameIndex = -1) {
109882
+ const filteredStacking = elementFilter ? fullStacking.filter((e) => elementFilter.has(e.id)) : fullStacking;
109883
+ const layers = groupIntoLayers(filteredStacking);
109884
+ const shouldLog = debugDumpEnabled && debugFrameIndex >= 0;
109885
+ if (shouldLog) {
109886
+ log.info("[diag] compositeToBuffer plan", {
109887
+ frame: debugFrameIndex,
109888
+ time: time.toFixed(3),
109889
+ filterSize: elementFilter?.size,
109890
+ fullStackingCount: fullStacking.length,
109891
+ filteredCount: filteredStacking.length,
109892
+ layerCount: layers.length,
109893
+ layers: layers.map(
109894
+ (l) => l.type === "hdr" ? {
109895
+ type: "hdr",
109896
+ id: l.element.id,
109897
+ z: l.element.zIndex,
109898
+ visible: l.element.visible,
109899
+ opacity: l.element.opacity,
109900
+ bounds: `${Math.round(l.element.x)},${Math.round(l.element.y)} ${Math.round(l.element.width)}x${Math.round(l.element.height)}`
109901
+ } : { type: "dom", ids: l.elementIds }
109902
+ )
109903
+ });
109904
+ }
109905
+ for (let layerIdx = 0; layerIdx < layers.length; layerIdx++) {
109906
+ const layer = layers[layerIdx];
109907
+ if (layer.type === "hdr") {
109908
+ const before2 = shouldLog ? countNonZeroRgb482(canvas) : 0;
109909
+ blitHdrVideoLayer(
109910
+ canvas,
109911
+ layer.element,
109912
+ time,
109913
+ job.config.fps,
109914
+ hdrFrameDirs,
109915
+ hdrVideoStartTimes,
109916
+ width,
109917
+ height,
109918
+ log,
109919
+ videoTransfers.get(layer.element.id),
109920
+ effectiveHdr?.transfer
108233
109921
  );
109922
+ if (shouldLog) {
109923
+ const after2 = countNonZeroRgb482(canvas);
109924
+ const frameDir = hdrFrameDirs.get(layer.element.id);
109925
+ const startTime = hdrVideoStartTimes.get(layer.element.id) ?? 0;
109926
+ const localTime = time - startTime;
109927
+ const frameNum = Math.floor(localTime * job.config.fps) + 1;
109928
+ const expectedFrame = frameDir ? join15(frameDir, `frame_${String(frameNum).padStart(4, "0")}.png`) : null;
109929
+ log.info("[diag] hdr layer blit", {
109930
+ frame: debugFrameIndex,
109931
+ layerIdx,
109932
+ id: layer.element.id,
109933
+ pixelsAdded: after2 - before2,
109934
+ totalNonZero: after2,
109935
+ startTime,
109936
+ localTime: localTime.toFixed(3),
109937
+ hdrFrameNum: frameNum,
109938
+ expectedFrame,
109939
+ expectedFrameExists: expectedFrame ? existsSync15(expectedFrame) : false
109940
+ });
109941
+ }
109942
+ } else {
109943
+ const allElementIds = fullStacking.map((e) => e.id);
109944
+ const layerIds = new Set(layer.elementIds);
109945
+ const hideIds = allElementIds.filter((id) => !layerIds.has(id));
109946
+ await domSession.page.evaluate((t) => {
109947
+ if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t);
109948
+ }, time);
109949
+ if (beforeCaptureHook) {
109950
+ await beforeCaptureHook(domSession.page, time);
109951
+ }
109952
+ await applyDomLayerMask(domSession.page, layer.elementIds, hideIds);
109953
+ const domPng = await captureAlphaPng(domSession.page, width, height);
109954
+ await removeDomLayerMask(domSession.page, hideIds);
109955
+ try {
109956
+ const { data: domRgba } = decodePng(domPng);
109957
+ if (!effectiveHdr) {
109958
+ throw new Error(
109959
+ "Invariant violation: effectiveHdr is undefined inside HDR layer branch"
109960
+ );
109961
+ }
109962
+ const before2 = shouldLog ? countNonZeroRgb482(canvas) : 0;
109963
+ const alphaPixels = shouldLog ? countNonZeroAlpha2(domRgba) : 0;
109964
+ blitRgba8OverRgb48le(domRgba, canvas, width, height, effectiveHdr.transfer);
109965
+ if (shouldLog && debugDumpDir) {
109966
+ const after2 = countNonZeroRgb482(canvas);
109967
+ const dumpName = `frame_${String(debugFrameIndex).padStart(4, "0")}_layer_${String(layerIdx).padStart(2, "0")}_dom.png`;
109968
+ const dumpPath = join15(debugDumpDir, dumpName);
109969
+ writeFileSync4(dumpPath, domPng);
109970
+ log.info("[diag] dom layer blit", {
109971
+ frame: debugFrameIndex,
109972
+ layerIdx,
109973
+ layerIds: layer.elementIds,
109974
+ hideCount: hideIds.length,
109975
+ pngBytes: domPng.length,
109976
+ alphaPixels,
109977
+ pixelsAdded: after2 - before2,
109978
+ totalNonZero: after2,
109979
+ dumpPath
109980
+ });
109981
+ }
109982
+ } catch (err) {
109983
+ log.warn("DOM layer decode/blit failed; skipping overlay", {
109984
+ layerIds: layer.elementIds,
109985
+ error: err instanceof Error ? err.message : String(err)
109986
+ });
109987
+ }
108234
109988
  }
108235
- },
108236
- onFrameBuffer,
108237
- cfg
108238
- );
108239
- if (probeSession) {
108240
- lastBrowserConsole = probeSession.browserConsoleBuffer;
108241
- await closeCaptureSession(probeSession);
108242
- probeSession = null;
108243
- }
108244
- } else {
108245
- const videoInjector = createVideoFrameInjector(frameLookup);
108246
- const session = probeSession ?? await createCaptureSession(
108247
- fileServer.url,
108248
- framesDir,
108249
- captureOptions,
108250
- videoInjector,
108251
- cfg
108252
- );
108253
- if (probeSession) {
108254
- prepareCaptureSessionForReuse(session, framesDir, videoInjector);
108255
- probeSession = null;
108256
- }
108257
- try {
108258
- if (!session.isInitialized) {
108259
- await initializeSession(session);
108260
109989
  }
109990
+ if (shouldLog && debugDumpDir) {
109991
+ const finalNonZero = countNonZeroRgb482(canvas);
109992
+ log.info("[diag] compositeToBuffer end", {
109993
+ frame: debugFrameIndex,
109994
+ finalNonZeroPixels: finalNonZero,
109995
+ totalPixels: width * height,
109996
+ coverage: (finalNonZero / (width * height) * 100).toFixed(1) + "%"
109997
+ });
109998
+ }
109999
+ }
110000
+ const bufSize = width * height * 6;
110001
+ const hasTransitions = transitionRanges.length > 0;
110002
+ const transBufferA = hasTransitions ? Buffer.alloc(bufSize) : null;
110003
+ const transBufferB = hasTransitions ? Buffer.alloc(bufSize) : null;
110004
+ const transOutput = hasTransitions ? Buffer.alloc(bufSize) : null;
110005
+ const normalCanvas = Buffer.alloc(bufSize);
110006
+ for (let i = 0; i < totalFrames; i++) {
108261
110007
  assertNotAborted();
108262
- lastBrowserConsole = session.browserConsoleBuffer;
108263
- for (let i = 0; i < job.totalFrames; i++) {
108264
- assertNotAborted();
108265
- const time = i / job.config.fps;
108266
- const { buffer } = await captureFrameToBuffer(session, i, time);
108267
- await reorderBuffer.waitForFrame(i);
108268
- currentEncoder.writeFrame(buffer);
108269
- reorderBuffer.advanceTo(i + 1);
108270
- job.framesRendered = i + 1;
108271
- const frameProgress = (i + 1) / job.totalFrames;
108272
- const progress = 25 + frameProgress * 55;
110008
+ const time = i / job.config.fps;
110009
+ await domSession.page.evaluate((t) => {
110010
+ if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t);
110011
+ }, time);
110012
+ if (beforeCaptureHook) {
110013
+ await beforeCaptureHook(domSession.page, time);
110014
+ }
110015
+ const stackingInfo = await queryElementStacking(domSession.page, nativeHdrVideoIds);
110016
+ const activeTransition = transitionRanges.find(
110017
+ (t) => i >= t.startFrame && i <= t.endFrame
110018
+ );
110019
+ if (i % 30 === 0) {
110020
+ const hdrEl = stackingInfo.find((e) => e.isHdr);
110021
+ log.debug("[Render] HDR layer composite frame", {
110022
+ frame: i,
110023
+ time: time.toFixed(2),
110024
+ hdrElement: hdrEl ? { z: hdrEl.zIndex, visible: hdrEl.visible, width: hdrEl.width } : null,
110025
+ stackingCount: stackingInfo.length,
110026
+ activeTransition: activeTransition?.shader
110027
+ });
110028
+ }
110029
+ if (activeTransition && transBufferA && transBufferB && transOutput) {
110030
+ const progress = activeTransition.endFrame === activeTransition.startFrame ? 1 : (i - activeTransition.startFrame) / (activeTransition.endFrame - activeTransition.startFrame);
110031
+ const sceneAIds = new Set(sceneElements[activeTransition.fromScene] ?? []);
110032
+ const sceneBIds = new Set(sceneElements[activeTransition.toScene] ?? []);
110033
+ transBufferA.fill(0);
110034
+ transBufferB.fill(0);
110035
+ for (const [sceneBuf, sceneIds] of [
110036
+ [transBufferA, sceneAIds],
110037
+ [transBufferB, sceneBIds]
110038
+ ]) {
110039
+ await domSession.page.evaluate((t) => {
110040
+ if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t);
110041
+ }, time);
110042
+ if (beforeCaptureHook) {
110043
+ await beforeCaptureHook(domSession.page, time);
110044
+ }
110045
+ for (const el of stackingInfo) {
110046
+ if (!el.isHdr || !sceneIds.has(el.id)) continue;
110047
+ blitHdrVideoLayer(
110048
+ sceneBuf,
110049
+ el,
110050
+ time,
110051
+ job.config.fps,
110052
+ hdrFrameDirs,
110053
+ hdrVideoStartTimes,
110054
+ width,
110055
+ height,
110056
+ log,
110057
+ videoTransfers.get(el.id),
110058
+ effectiveHdr?.transfer
110059
+ );
110060
+ }
110061
+ const showIds = Array.from(sceneIds);
110062
+ const hideIds = stackingInfo.map((e) => e.id).filter((id) => !sceneIds.has(id) || nativeHdrVideoIds.has(id));
110063
+ await applyDomLayerMask(domSession.page, showIds, hideIds);
110064
+ const domPng = await captureAlphaPng(domSession.page, width, height);
110065
+ await removeDomLayerMask(domSession.page, hideIds);
110066
+ try {
110067
+ const { data: domRgba } = decodePng(domPng);
110068
+ if (!effectiveHdr) {
110069
+ throw new Error(
110070
+ "Invariant violation: effectiveHdr is undefined inside hasHdrVideo branch"
110071
+ );
110072
+ }
110073
+ blitRgba8OverRgb48le(
110074
+ domRgba,
110075
+ sceneBuf,
110076
+ width,
110077
+ height,
110078
+ effectiveHdr.transfer
110079
+ );
110080
+ } catch (err) {
110081
+ log.warn("DOM layer decode/blit failed; skipping overlay for transition scene", {
110082
+ frameIndex: i,
110083
+ sceneIds: Array.from(sceneIds),
110084
+ error: err instanceof Error ? err.message : String(err)
110085
+ });
110086
+ }
110087
+ }
110088
+ const transitionFn = TRANSITIONS[activeTransition.shader] ?? crossfade;
110089
+ transitionFn(transBufferA, transBufferB, transOutput, width, height, progress);
110090
+ hdrEncoder.writeFrame(transOutput);
110091
+ } else {
110092
+ normalCanvas.fill(0);
110093
+ await compositeToBuffer(normalCanvas, time, stackingInfo, void 0, i);
110094
+ if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
110095
+ const previewPath = join15(
110096
+ debugDumpDir,
110097
+ `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`
110098
+ );
110099
+ writeFileSync4(previewPath, normalCanvas);
110100
+ }
110101
+ hdrEncoder.writeFrame(normalCanvas);
110102
+ }
110103
+ if (process.env.KEEP_TEMP !== "1") {
110104
+ for (const [videoId, endTime] of hdrVideoEndTimes) {
110105
+ if (time > endTime && !cleanedUpVideos.has(videoId)) {
110106
+ const stillNeeded = activeTransition && (sceneElements[activeTransition.fromScene]?.includes(videoId) || sceneElements[activeTransition.toScene]?.includes(videoId));
110107
+ if (!stillNeeded) {
110108
+ const frameDir = hdrFrameDirs.get(videoId);
110109
+ if (frameDir) {
110110
+ try {
110111
+ rmSync3(frameDir, { recursive: true, force: true });
110112
+ } catch (err) {
110113
+ log.warn("Failed to clean up HDR frame directory", {
110114
+ videoId,
110115
+ frameDir,
110116
+ error: err instanceof Error ? err.message : String(err)
110117
+ });
110118
+ }
110119
+ }
110120
+ cleanedUpVideos.add(videoId);
110121
+ }
110122
+ }
110123
+ }
110124
+ }
110125
+ job.framesRendered = i + 1;
110126
+ if ((i + 1) % 10 === 0 || i + 1 === totalFrames) {
110127
+ const frameProgress = (i + 1) / totalFrames;
108273
110128
  updateJobStatus(
108274
110129
  job,
108275
110130
  "rendering",
108276
- `Streaming frame ${i + 1}/${job.totalFrames}`,
108277
- Math.round(progress),
110131
+ `HDR composite frame ${i + 1}/${job.totalFrames}`,
110132
+ Math.round(25 + frameProgress * 55),
108278
110133
  onProgress
108279
110134
  );
108280
110135
  }
108281
- } finally {
108282
- lastBrowserConsole = session.browserConsoleBuffer;
108283
- await closeCaptureSession(session);
108284
110136
  }
110137
+ } finally {
110138
+ lastBrowserConsole = domSession.browserConsoleBuffer;
110139
+ await closeCaptureSession(domSession);
108285
110140
  }
108286
- const encodeResult = await currentEncoder.close();
110141
+ const hdrEncodeResult = await hdrEncoder.close();
108287
110142
  assertNotAborted();
108288
- if (!encodeResult.success) {
108289
- throw new Error(`Streaming encode failed: ${encodeResult.error}`);
110143
+ if (!hdrEncodeResult.success) {
110144
+ throw new Error(`HDR encode failed: ${hdrEncodeResult.error}`);
108290
110145
  }
108291
110146
  perfStages.captureMs = Date.now() - stage4Start;
108292
- perfStages.encodeMs = encodeResult.durationMs;
110147
+ perfStages.encodeMs = hdrEncodeResult.durationMs;
108293
110148
  } else {
108294
- if (workerCount > 1) {
108295
- const tasks = distributeFrames(job.totalFrames, workerCount, workDir);
108296
- await executeParallelCapture(
108297
- fileServer.url,
108298
- workDir,
108299
- tasks,
108300
- captureOptions,
108301
- () => createVideoFrameInjector(frameLookup),
108302
- abortSignal,
108303
- (progress) => {
108304
- job.framesRendered = progress.capturedFrames;
108305
- const frameProgress = progress.capturedFrames / progress.totalFrames;
108306
- const progressPct = 25 + frameProgress * 45;
108307
- if (progress.capturedFrames % 30 === 0 || progress.capturedFrames === progress.totalFrames) {
110149
+ let streamingEncoder = null;
110150
+ if (enableStreamingEncode) {
110151
+ streamingEncoder = await spawnStreamingEncoder(
110152
+ videoOnlyPath,
110153
+ {
110154
+ fps: job.config.fps,
110155
+ width,
110156
+ height,
110157
+ codec: preset.codec,
110158
+ preset: preset.preset,
110159
+ quality: preset.quality,
110160
+ pixelFormat: preset.pixelFormat,
110161
+ useGpu: job.config.useGpu,
110162
+ imageFormat: captureOptions.format || "jpeg",
110163
+ hdr: preset.hdr
110164
+ },
110165
+ abortSignal
110166
+ );
110167
+ assertNotAborted();
110168
+ }
110169
+ if (enableStreamingEncode && streamingEncoder) {
110170
+ const reorderBuffer = createFrameReorderBuffer(0, totalFrames);
110171
+ const currentEncoder = streamingEncoder;
110172
+ if (workerCount > 1) {
110173
+ const tasks = distributeFrames(job.totalFrames, workerCount, workDir);
110174
+ const onFrameBuffer = async (frameIndex, buffer) => {
110175
+ await reorderBuffer.waitForFrame(frameIndex);
110176
+ currentEncoder.writeFrame(buffer);
110177
+ reorderBuffer.advanceTo(frameIndex + 1);
110178
+ };
110179
+ await executeParallelCapture(
110180
+ fileServer.url,
110181
+ workDir,
110182
+ tasks,
110183
+ captureOptions,
110184
+ () => createVideoFrameInjector(frameLookup),
110185
+ abortSignal,
110186
+ (progress) => {
110187
+ job.framesRendered = progress.capturedFrames;
110188
+ const frameProgress = progress.capturedFrames / progress.totalFrames;
110189
+ const progressPct = 25 + frameProgress * 55;
110190
+ if (progress.capturedFrames % 30 === 0 || progress.capturedFrames === progress.totalFrames) {
110191
+ updateJobStatus(
110192
+ job,
110193
+ "rendering",
110194
+ `Streaming frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
110195
+ Math.round(progressPct),
110196
+ onProgress
110197
+ );
110198
+ }
110199
+ },
110200
+ onFrameBuffer,
110201
+ cfg
110202
+ );
110203
+ if (probeSession) {
110204
+ lastBrowserConsole = probeSession.browserConsoleBuffer;
110205
+ await closeCaptureSession(probeSession);
110206
+ probeSession = null;
110207
+ }
110208
+ } else {
110209
+ const videoInjector = createVideoFrameInjector(frameLookup);
110210
+ const session = probeSession ?? await createCaptureSession(
110211
+ fileServer.url,
110212
+ framesDir,
110213
+ captureOptions,
110214
+ videoInjector,
110215
+ cfg
110216
+ );
110217
+ if (probeSession) {
110218
+ prepareCaptureSessionForReuse(session, framesDir, videoInjector);
110219
+ probeSession = null;
110220
+ }
110221
+ try {
110222
+ if (!session.isInitialized) {
110223
+ await initializeSession(session);
110224
+ }
110225
+ assertNotAborted();
110226
+ lastBrowserConsole = session.browserConsoleBuffer;
110227
+ for (let i = 0; i < totalFrames; i++) {
110228
+ assertNotAborted();
110229
+ const time = i / job.config.fps;
110230
+ const { buffer } = await captureFrameToBuffer(session, i, time);
110231
+ await reorderBuffer.waitForFrame(i);
110232
+ currentEncoder.writeFrame(buffer);
110233
+ reorderBuffer.advanceTo(i + 1);
110234
+ job.framesRendered = i + 1;
110235
+ const frameProgress = (i + 1) / totalFrames;
110236
+ const progress = 25 + frameProgress * 55;
108308
110237
  updateJobStatus(
108309
110238
  job,
108310
110239
  "rendering",
108311
- `Capturing frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
108312
- Math.round(progressPct),
110240
+ `Streaming frame ${i + 1}/${job.totalFrames}`,
110241
+ Math.round(progress),
108313
110242
  onProgress
108314
110243
  );
108315
110244
  }
108316
- },
108317
- void 0,
108318
- cfg
108319
- );
108320
- await mergeWorkerFrames(workDir, tasks, framesDir);
108321
- if (probeSession) {
108322
- lastBrowserConsole = probeSession.browserConsoleBuffer;
108323
- await closeCaptureSession(probeSession);
108324
- probeSession = null;
110245
+ } finally {
110246
+ lastBrowserConsole = session.browserConsoleBuffer;
110247
+ await closeCaptureSession(session);
110248
+ }
108325
110249
  }
108326
- } else {
108327
- const videoInjector = createVideoFrameInjector(frameLookup);
108328
- const session = probeSession ?? await createCaptureSession(
108329
- fileServer.url,
108330
- framesDir,
108331
- captureOptions,
108332
- videoInjector,
108333
- cfg
108334
- );
108335
- if (probeSession) {
108336
- prepareCaptureSessionForReuse(session, framesDir, videoInjector);
108337
- probeSession = null;
110250
+ const encodeResult = await currentEncoder.close();
110251
+ assertNotAborted();
110252
+ if (!encodeResult.success) {
110253
+ throw new Error(`Streaming encode failed: ${encodeResult.error}`);
108338
110254
  }
108339
- try {
108340
- if (!session.isInitialized) {
108341
- await initializeSession(session);
110255
+ perfStages.captureMs = Date.now() - stage4Start;
110256
+ perfStages.encodeMs = encodeResult.durationMs;
110257
+ } else {
110258
+ if (workerCount > 1) {
110259
+ const tasks = distributeFrames(job.totalFrames, workerCount, workDir);
110260
+ await executeParallelCapture(
110261
+ fileServer.url,
110262
+ workDir,
110263
+ tasks,
110264
+ captureOptions,
110265
+ () => createVideoFrameInjector(frameLookup),
110266
+ abortSignal,
110267
+ (progress) => {
110268
+ job.framesRendered = progress.capturedFrames;
110269
+ const frameProgress = progress.capturedFrames / progress.totalFrames;
110270
+ const progressPct = 25 + frameProgress * 45;
110271
+ if (progress.capturedFrames % 30 === 0 || progress.capturedFrames === progress.totalFrames) {
110272
+ updateJobStatus(
110273
+ job,
110274
+ "rendering",
110275
+ `Capturing frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
110276
+ Math.round(progressPct),
110277
+ onProgress
110278
+ );
110279
+ }
110280
+ },
110281
+ void 0,
110282
+ cfg
110283
+ );
110284
+ await mergeWorkerFrames(workDir, tasks, framesDir);
110285
+ if (probeSession) {
110286
+ lastBrowserConsole = probeSession.browserConsoleBuffer;
110287
+ await closeCaptureSession(probeSession);
110288
+ probeSession = null;
108342
110289
  }
108343
- assertNotAborted();
108344
- lastBrowserConsole = session.browserConsoleBuffer;
108345
- for (let i = 0; i < job.totalFrames; i++) {
108346
- assertNotAborted();
108347
- const time = i / job.config.fps;
108348
- await captureFrame(session, i, time);
108349
- job.framesRendered = i + 1;
108350
- const frameProgress = (i + 1) / job.totalFrames;
108351
- const progress = 25 + frameProgress * 45;
108352
- updateJobStatus(
108353
- job,
108354
- "rendering",
108355
- `Capturing frame ${i + 1}/${job.totalFrames}`,
108356
- Math.round(progress),
108357
- onProgress
108358
- );
110290
+ } else {
110291
+ const videoInjector = createVideoFrameInjector(frameLookup);
110292
+ const session = probeSession ?? await createCaptureSession(
110293
+ fileServer.url,
110294
+ framesDir,
110295
+ captureOptions,
110296
+ videoInjector,
110297
+ cfg
110298
+ );
110299
+ if (probeSession) {
110300
+ prepareCaptureSessionForReuse(session, framesDir, videoInjector);
110301
+ probeSession = null;
108359
110302
  }
108360
- } finally {
108361
- lastBrowserConsole = session.browserConsoleBuffer;
108362
- await closeCaptureSession(session);
110303
+ try {
110304
+ if (!session.isInitialized) {
110305
+ await initializeSession(session);
110306
+ }
110307
+ assertNotAborted();
110308
+ lastBrowserConsole = session.browserConsoleBuffer;
110309
+ for (let i = 0; i < job.totalFrames; i++) {
110310
+ assertNotAborted();
110311
+ const time = i / job.config.fps;
110312
+ await captureFrame(session, i, time);
110313
+ job.framesRendered = i + 1;
110314
+ const frameProgress = (i + 1) / job.totalFrames;
110315
+ const progress = 25 + frameProgress * 45;
110316
+ updateJobStatus(
110317
+ job,
110318
+ "rendering",
110319
+ `Capturing frame ${i + 1}/${job.totalFrames}`,
110320
+ Math.round(progress),
110321
+ onProgress
110322
+ );
110323
+ }
110324
+ } finally {
110325
+ lastBrowserConsole = session.browserConsoleBuffer;
110326
+ await closeCaptureSession(session);
110327
+ }
110328
+ }
110329
+ perfStages.captureMs = Date.now() - stage4Start;
110330
+ const stage5Start = Date.now();
110331
+ updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
110332
+ const frameExt = needsAlpha ? "png" : "jpg";
110333
+ const framePattern = `frame_%06d.${frameExt}`;
110334
+ const encoderOpts = {
110335
+ fps: job.config.fps,
110336
+ width,
110337
+ height,
110338
+ codec: preset.codec,
110339
+ preset: preset.preset,
110340
+ quality: preset.quality,
110341
+ pixelFormat: preset.pixelFormat,
110342
+ useGpu: job.config.useGpu,
110343
+ hdr: preset.hdr
110344
+ };
110345
+ const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
110346
+ framesDir,
110347
+ framePattern,
110348
+ videoOnlyPath,
110349
+ encoderOpts,
110350
+ chunkedEncodeSize,
110351
+ abortSignal
110352
+ ) : await encodeFramesFromDir(
110353
+ framesDir,
110354
+ framePattern,
110355
+ videoOnlyPath,
110356
+ encoderOpts,
110357
+ abortSignal
110358
+ );
110359
+ assertNotAborted();
110360
+ if (!encodeResult.success) {
110361
+ throw new Error(`Encoding failed: ${encodeResult.error}`);
108363
110362
  }
110363
+ perfStages.encodeMs = Date.now() - stage5Start;
108364
110364
  }
108365
- perfStages.captureMs = Date.now() - stage4Start;
108366
- const stage5Start = Date.now();
108367
- updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
108368
- const frameExt = needsAlpha ? "png" : "jpg";
108369
- const framePattern = `frame_%06d.${frameExt}`;
108370
- const encoderOpts = baseEncoderOpts;
108371
- const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
108372
- framesDir,
108373
- framePattern,
108374
- videoOnlyPath,
108375
- encoderOpts,
108376
- chunkedEncodeSize,
108377
- abortSignal
108378
- ) : await encodeFramesFromDir(
108379
- framesDir,
108380
- framePattern,
108381
- videoOnlyPath,
108382
- encoderOpts,
108383
- abortSignal
108384
- );
108385
- assertNotAborted();
108386
- if (!encodeResult.success) {
108387
- throw new Error(`Encoding failed: ${encodeResult.error}`);
108388
- }
108389
- perfStages.encodeMs = Date.now() - stage5Start;
108390
110365
  }
108391
110366
  if (probeSession !== null) {
108392
110367
  const remainingProbeSession = probeSession;
@@ -108430,12 +110405,12 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
108430
110405
  chunkedEncode: enableChunkedEncode,
108431
110406
  chunkSizeFrames: enableChunkedEncode ? chunkedEncodeSize : null,
108432
110407
  compositionDurationSeconds: composition.duration,
108433
- totalFrames: job.totalFrames,
110408
+ totalFrames,
108434
110409
  resolution: { width, height },
108435
110410
  videoCount: composition.videos.length,
108436
110411
  audioCount: composition.audios.length,
108437
110412
  stages: perfStages,
108438
- captureAvgMs: job.totalFrames > 0 ? Math.round((perfStages.captureMs ?? 0) / job.totalFrames) : void 0
110413
+ captureAvgMs: totalFrames > 0 ? Math.round((perfStages.captureMs ?? 0) / totalFrames) : void 0
108439
110414
  };
108440
110415
  job.perfSummary = perfSummary;
108441
110416
  if (job.config.debug) {
@@ -108453,6 +110428,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
108453
110428
  const debugOutput = join15(workDir, `output${videoExt}`);
108454
110429
  copyFileSync2(outputPath, debugOutput);
108455
110430
  }
110431
+ } else if (process.env.KEEP_TEMP === "1") {
110432
+ log.info("KEEP_TEMP=1 \u2014 leaving workDir on disk for inspection", { workDir });
108456
110433
  } else {
108457
110434
  await safeCleanup(
108458
110435
  "remove workDir",