@hyperframes/producer 0.8.198 → 0.8.199

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -5,8 +5,8 @@ import {
5
5
  mkdirSync as mkdirSync9,
6
6
  readFileSync as readFileSync8,
7
7
  readdirSync as readdirSync5,
8
- rmSync as rmSync10,
9
- statSync as statSync6,
8
+ rmSync as rmSync11,
9
+ statSync as statSync7,
10
10
  writeFileSync as writeFileSync5,
11
11
  copyFileSync as copyFileSync4,
12
12
  appendFileSync
@@ -55,7 +55,7 @@ import {
55
55
  augmentProtocolTimeoutError,
56
56
  augmentPageNavigationTimeoutError
57
57
  } from "@hyperframes/engine";
58
- import { join as join25, dirname as dirname10, resolve as resolve7 } from "path";
58
+ import { join as join26, dirname as dirname10, resolve as resolve7 } from "path";
59
59
  import { totalmem as totalmem2 } from "node:os";
60
60
  import { randomUUID } from "crypto";
61
61
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -1890,8 +1890,8 @@ function replanAfterFailure(plan2, failure) {
1890
1890
  }
1891
1891
 
1892
1892
  // src/services/render/stages/captureSegmentedStage.ts
1893
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, rmSync as rmSync5, statSync as statSync2 } from "node:fs";
1894
- import { join as join9 } from "node:path";
1893
+ import { existsSync as existsSync6, mkdirSync as mkdirSync3, rmSync as rmSync6, statSync as statSync3 } from "node:fs";
1894
+ import { join as join10 } from "node:path";
1895
1895
  import {
1896
1896
  captureFrameToBuffer as captureFrameToBuffer2,
1897
1897
  closeCaptureSession as closeCaptureSession2,
@@ -2275,7 +2275,7 @@ import {
2275
2275
  } from "@hyperframes/engine";
2276
2276
 
2277
2277
  // src/services/hdrCompositor.ts
2278
- import { readSync as readSync2, closeSync as closeSync3 } from "fs";
2278
+ import { readSync as readSync2 } from "fs";
2279
2279
  import { join as join6 } from "path";
2280
2280
  import {
2281
2281
  captureAlphaPng,
@@ -2328,10 +2328,10 @@ function cropRgb48le(src, srcW, srcH, cropX, cropY, cropW, cropH) {
2328
2328
  }
2329
2329
  function closeHdrVideoFrameSource(source, log) {
2330
2330
  try {
2331
- closeSync3(source.fd);
2331
+ source.windows.close();
2332
2332
  } catch (err) {
2333
- log?.warn("Failed to close HDR raw frame file", {
2334
- rawPath: source.rawPath,
2333
+ log?.warn("Failed to close HDR raw frame windows", {
2334
+ dir: source.dir,
2335
2335
  error: err instanceof Error ? err.message : String(err)
2336
2336
  });
2337
2337
  }
@@ -2341,7 +2341,7 @@ function resolveHdrVideoFrameIndex(time, startTime, fps, frameCount, loop = fals
2341
2341
  if (frameIndex < 0 || frameCount < 1) return null;
2342
2342
  return loop ? frameIndex % frameCount : Math.min(frameIndex, frameCount - 1);
2343
2343
  }
2344
- function blitHdrVideoLayer(canvas, el, time, fps, hdrVideoFrameSources, hdrStartTimes, width, height, log, sourceTransfer, targetTransfer, hdrPerf) {
2344
+ async function blitHdrVideoLayer(canvas, el, time, fps, hdrVideoFrameSources, hdrStartTimes, width, height, log, sourceTransfer, targetTransfer, hdrPerf) {
2345
2345
  const frameSource = hdrVideoFrameSources.get(el.id);
2346
2346
  const startTime = hdrStartTimes.get(el.id);
2347
2347
  if (!frameSource || startTime === void 0 || el.opacity <= 0) {
@@ -2357,13 +2357,32 @@ function blitHdrVideoLayer(canvas, el, time, fps, hdrVideoFrameSources, hdrStart
2357
2357
  if (effectiveIndex === null) return;
2358
2358
  const mediaTime = (frameSource.mediaStart ?? 0) + effectiveIndex / fps;
2359
2359
  if (isVideoHiddenBeforeStreamStart(frameSource.videoStreamLeadSeconds, mediaTime)) return;
2360
- const frameOffset = effectiveIndex * frameSource.frameSize;
2360
+ if (hdrPerf) hdrPerf.hdrVideoLayerBlits += 1;
2361
+ const lease = await frameSource.windows.lease(effectiveIndex);
2362
+ if (!lease) return;
2363
+ try {
2364
+ blitScratchFrameWithoutYielding(
2365
+ canvas,
2366
+ el,
2367
+ frameSource,
2368
+ lease,
2369
+ width,
2370
+ height,
2371
+ log,
2372
+ sourceTransfer,
2373
+ targetTransfer,
2374
+ hdrPerf
2375
+ );
2376
+ } finally {
2377
+ lease.release();
2378
+ }
2379
+ }
2380
+ function blitScratchFrameWithoutYielding(canvas, el, frameSource, lease, width, height, log, sourceTransfer, targetTransfer, hdrPerf) {
2361
2381
  try {
2362
- if (hdrPerf) hdrPerf.hdrVideoLayerBlits += 1;
2363
2382
  const bytesRead = timeHdrPhase(
2364
2383
  hdrPerf,
2365
2384
  "hdrVideoReadDecodeMs",
2366
- () => readSync2(frameSource.fd, frameSource.scratch, 0, frameSource.frameSize, frameOffset)
2385
+ () => readSync2(lease.fd, frameSource.scratch, 0, frameSource.frameSize, lease.offset)
2367
2386
  );
2368
2387
  if (bytesRead !== frameSource.frameSize) return;
2369
2388
  const hdrRgb = frameSource.scratch;
@@ -2592,7 +2611,7 @@ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter,
2592
2611
  hdrPerf
2593
2612
  );
2594
2613
  } else {
2595
- blitHdrVideoLayer(
2614
+ await blitHdrVideoLayer(
2596
2615
  canvas,
2597
2616
  layer.element,
2598
2617
  time,
@@ -2636,7 +2655,7 @@ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter,
2636
2655
  startTime,
2637
2656
  localTime: localTime.toFixed(3),
2638
2657
  hdrFrameNum: frameNum,
2639
- rawPath: frameSource?.rawPath ?? null,
2658
+ dir: frameSource?.dir ?? null,
2640
2659
  frameCount: frameSource?.frameCount ?? null
2641
2660
  });
2642
2661
  }
@@ -2732,18 +2751,8 @@ async function compositeHdrFrame(ctx, canvas, time, fullStacking, elementFilter,
2732
2751
  }
2733
2752
 
2734
2753
  // src/services/render/stages/captureHdrResources.ts
2735
- import {
2736
- closeSync as closeSync4,
2737
- constants as constants2,
2738
- fstatSync as fstatSync3,
2739
- mkdirSync as mkdirSync2,
2740
- mkdtempSync as mkdtempSync2,
2741
- openSync as openSync3,
2742
- readFileSync as readFileSync3,
2743
- rmSync as rmSync4,
2744
- statfsSync
2745
- } from "node:fs";
2746
- import { join as join7 } from "node:path";
2754
+ import { mkdirSync as mkdirSync2, mkdtempSync as mkdtempSync2, readFileSync as readFileSync3, rmSync as rmSync5, statSync as statSync2, statfsSync } from "node:fs";
2755
+ import { join as join8 } from "node:path";
2747
2756
  import {
2748
2757
  decodePngToRgb48le,
2749
2758
  extractMediaMetadata as extractMediaMetadata2,
@@ -2757,7 +2766,90 @@ import {
2757
2766
  runFfmpeg
2758
2767
  } from "@hyperframes/engine";
2759
2768
  import { fpsToFfmpegArg, fpsToNumber as fpsToNumber4 } from "@hyperframes/core";
2760
- var NO_FOLLOW_FLAG = constants2.O_NOFOLLOW ?? 0;
2769
+
2770
+ // src/services/hdrFrameWindows.ts
2771
+ import { closeSync as closeSync3, constants as constants2, openSync as openSync3, rmSync as rmSync4 } from "node:fs";
2772
+ import { join as join7 } from "node:path";
2773
+ function createHdrFrameWindows(opts) {
2774
+ const { dir, frameSize, windowFrames, maxResident, extract } = opts;
2775
+ let totalFrames = opts.totalFrames;
2776
+ const windows = /* @__PURE__ */ new Map();
2777
+ const drop = (index, w) => {
2778
+ windows.delete(index);
2779
+ if (w.fd >= 0) closeSync3(w.fd);
2780
+ rmSync4(w.path, { force: true });
2781
+ };
2782
+ const open2 = (index) => {
2783
+ const known = windows.get(index);
2784
+ if (known) {
2785
+ windows.delete(index);
2786
+ windows.set(index, known);
2787
+ return known;
2788
+ }
2789
+ for (const [i, w2] of windows) {
2790
+ if (windows.size < maxResident) break;
2791
+ if (w2.pins === 0) drop(i, w2);
2792
+ }
2793
+ const firstFrame = index * windowFrames;
2794
+ const w = {
2795
+ firstFrame,
2796
+ path: join7(dir, `window-${index}.rgb48le`),
2797
+ frames: 0,
2798
+ fd: -1,
2799
+ pins: 0,
2800
+ ready: Promise.resolve()
2801
+ };
2802
+ w.ready = extract({
2803
+ firstFrame,
2804
+ frames: Math.min(windowFrames, totalFrames - firstFrame),
2805
+ path: w.path
2806
+ }).then((produced) => {
2807
+ if (produced < 1) return;
2808
+ w.fd = openSync3(w.path, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
2809
+ w.frames = produced;
2810
+ });
2811
+ windows.set(index, w);
2812
+ return w;
2813
+ };
2814
+ return {
2815
+ get totalFrames() {
2816
+ return totalFrames;
2817
+ },
2818
+ async lease(frameIndex) {
2819
+ for (; ; ) {
2820
+ if (totalFrames < 1) return null;
2821
+ const index = Math.min(Math.max(0, frameIndex), totalFrames - 1);
2822
+ const key = Math.floor(index / windowFrames);
2823
+ const w = open2(key);
2824
+ w.pins += 1;
2825
+ try {
2826
+ await w.ready;
2827
+ } catch (error) {
2828
+ w.pins -= 1;
2829
+ windows.delete(key);
2830
+ throw error;
2831
+ }
2832
+ const local = index - w.firstFrame;
2833
+ if (local < w.frames) {
2834
+ return {
2835
+ fd: w.fd,
2836
+ offset: local * frameSize,
2837
+ release: () => {
2838
+ w.pins -= 1;
2839
+ }
2840
+ };
2841
+ }
2842
+ w.pins -= 1;
2843
+ totalFrames = Math.min(totalFrames, w.firstFrame + w.frames);
2844
+ }
2845
+ },
2846
+ close() {
2847
+ for (const [i, w] of [...windows]) drop(i, w);
2848
+ }
2849
+ };
2850
+ }
2851
+
2852
+ // src/services/render/stages/captureHdrResources.ts
2761
2853
  function tempDirSafePrefix(id) {
2762
2854
  const safe = id.replace(/[^A-Za-z0-9_.-]/g, "_").slice(0, 80);
2763
2855
  return safe || "video";
@@ -2802,10 +2894,16 @@ async function probeHdrExtractionDims(args) {
2802
2894
  const stacking = await queryElementStacking(domSession.page, nativeHdrIds);
2803
2895
  for (const el of stacking) {
2804
2896
  if (el.isHdr && el.layoutWidth > 0 && el.layoutHeight > 0 && !prep.hdrExtractionDims.has(el.id)) {
2805
- prep.hdrExtractionDims.set(el.id, { width: el.layoutWidth, height: el.layoutHeight });
2897
+ prep.hdrExtractionDims.set(el.id, {
2898
+ width: el.layoutWidth,
2899
+ height: el.layoutHeight
2900
+ });
2806
2901
  }
2807
2902
  if (el.isHdr && nativeHdrImageIds.has(el.id) && !prep.hdrImageFitInfo.has(el.id)) {
2808
- prep.hdrImageFitInfo.set(el.id, { fit: el.objectFit, position: el.objectPosition });
2903
+ prep.hdrImageFitInfo.set(el.id, {
2904
+ fit: el.objectFit,
2905
+ position: el.objectPosition
2906
+ });
2809
2907
  }
2810
2908
  }
2811
2909
  }
@@ -2824,23 +2922,21 @@ async function probeHdrExtractionDims(args) {
2824
2922
  const retryStacking = await queryElementStacking(domSession.page, nativeHdrIds);
2825
2923
  for (const el of retryStacking) {
2826
2924
  if (el.id === imageId && el.isHdr && el.layoutWidth > 0 && el.layoutHeight > 0) {
2827
- prep.hdrExtractionDims.set(el.id, { width: el.layoutWidth, height: el.layoutHeight });
2925
+ prep.hdrExtractionDims.set(el.id, {
2926
+ width: el.layoutWidth,
2927
+ height: el.layoutHeight
2928
+ });
2828
2929
  if (!prep.hdrImageFitInfo.has(el.id)) {
2829
- prep.hdrImageFitInfo.set(el.id, { fit: el.objectFit, position: el.objectPosition });
2930
+ prep.hdrImageFitInfo.set(el.id, {
2931
+ fit: el.objectFit,
2932
+ position: el.objectPosition
2933
+ });
2830
2934
  }
2831
2935
  break;
2832
2936
  }
2833
2937
  }
2834
2938
  }
2835
2939
  }
2836
- function estimateHdrExtractionBytes(videos, fps) {
2837
- let total = 0;
2838
- for (const v of videos) {
2839
- const frames = Math.ceil(Math.max(0, v.durationSeconds) * fps);
2840
- total += frames * v.width * v.height * 6;
2841
- }
2842
- return total;
2843
- }
2844
2940
  var HDR_EXTRACTION_HEADROOM_FRACTION = 0.9;
2845
2941
  var HDR_EXTRACTION_CGROUP_BUDGET_FRACTION = 0.5;
2846
2942
  var HDR_EXTRACTION_WARN_BYTES = 1e10;
@@ -2904,24 +3000,22 @@ function resolveHdrExtractionWindow(video, compositionDuration, metadata) {
2904
3000
  }
2905
3001
  return window2;
2906
3002
  }
2907
- function cleanupHdrFrameDirectory(frameDir, rawPath, log) {
3003
+ function cleanupHdrFrameDirectory(frameDir, log) {
2908
3004
  if (process.env.KEEP_TEMP === "1") return;
2909
3005
  try {
2910
- rmSync4(frameDir, { recursive: true, force: true });
3006
+ rmSync5(frameDir, { recursive: true, force: true });
2911
3007
  } catch (err) {
2912
3008
  log?.warn("Failed to clean up HDR raw frame directory", {
2913
3009
  frameDir,
2914
- rawPath,
2915
3010
  error: err instanceof Error ? err.message : String(err)
2916
3011
  });
2917
3012
  }
2918
3013
  }
2919
3014
  function cleanupHdrVideoFrameSource(source, log) {
2920
3015
  closeHdrVideoFrameSource(source, log);
2921
- cleanupHdrFrameDirectory(source.dir, source.rawPath, log);
3016
+ cleanupHdrFrameDirectory(source.dir, log);
2922
3017
  }
2923
- function assertHdrExtractionDiskHeadroom(framesDir, plannedVideos, fps, log) {
2924
- const estimatedBytes = estimateHdrExtractionBytes(plannedVideos, fps);
3018
+ function assertHdrExtractionDiskHeadroom(framesDir, estimatedBytes, log) {
2925
3019
  const configuredBudget = resolveHdrExtractionBudgetBytes(
2926
3020
  process.env[HDR_EXTRACTION_MAX_BYTES_ENV]
2927
3021
  );
@@ -2955,14 +3049,29 @@ function assertHdrExtractionDiskHeadroom(framesDir, plannedVideos, fps, log) {
2955
3049
  budgetBytes: resolveHdrExtractionActiveBudgetBytes(configuredBudget, freeBytes)
2956
3050
  };
2957
3051
  }
3052
+ var DEFAULT_HDR_WINDOW_BYTES = 512 * BYTES_PER_MIB;
3053
+ function hdrWindowFrames(frameSize, windowBytes) {
3054
+ return Math.max(8, Math.floor(windowBytes / frameSize));
3055
+ }
3056
+ async function probeFirstTick(srcPath, mediaStart, fps, runFfmpegImpl, abortSignal) {
3057
+ const args = ["-ss", String(mediaStart), "-i", srcPath, "-frames:v", "1"];
3058
+ const result = await runFfmpegImpl([...args, ...["-vf", "showinfo", "-f", "null", "-"]], {
3059
+ signal: abortSignal
3060
+ });
3061
+ const match = /pts_time:(-?[\d.]+)/.exec(result.stderr);
3062
+ return match ? Math.max(0, Math.floor(Number(match[1]) * fps + 0.5)) : 0;
3063
+ }
2958
3064
  async function extractHdrVideoFrames(args) {
2959
3065
  const { job, log, framesDir, composition, prep, width, height, abortSignal, hdrDiagnostics } = args;
3066
+ const maxResident = Math.max(2, args.maxResidentWindows ?? 2);
3067
+ const windowBytes = args.windowBytes ?? DEFAULT_HDR_WINDOW_BYTES;
2960
3068
  const runFfmpegImpl = args.runFfmpegImpl ?? runFfmpeg;
2961
3069
  const extractMediaMetadataImpl = args.extractMediaMetadataImpl ?? extractMediaMetadata2;
2962
3070
  const resolveFinalFrameExtractionWindowImpl = args.resolveFinalFrameExtractionWindowImpl ?? resolveFinalFrameExtractionWindow;
3071
+ const fps = fpsToNumber4(job.config.fps);
2963
3072
  const out = /* @__PURE__ */ new Map();
2964
3073
  mkdirSync2(framesDir, { recursive: true });
2965
- const plannedVideos = [];
3074
+ let scratchBytes = 0;
2966
3075
  const extractionWindows = /* @__PURE__ */ new Map();
2967
3076
  for (const [videoId, srcPath] of prep.hdrVideoSrcPaths) {
2968
3077
  const video = composition.videos.find((v) => v.id === videoId);
@@ -2983,16 +3092,13 @@ async function extractHdrVideoFrames(args) {
2983
3092
  videoStreamLeadSeconds: metadata.videoStreamLeadSeconds
2984
3093
  });
2985
3094
  prep.hdrVideoStartTimes.set(videoId, window2.compositionStart);
2986
- plannedVideos.push({
2987
- durationSeconds: window2.durationSeconds,
2988
- width: dims.width,
2989
- height: dims.height
2990
- });
3095
+ const frameSize = dims.width * dims.height * 6;
3096
+ const frames = window2.finalFrameOnly ? 1 : Math.ceil(window2.durationSeconds * fps);
3097
+ scratchBytes += Math.min(frames, maxResident * hdrWindowFrames(frameSize, windowBytes)) * frameSize;
2991
3098
  }
2992
3099
  const { estimatedBytes, budgetBytes } = assertHdrExtractionDiskHeadroom(
2993
3100
  framesDir,
2994
- plannedVideos,
2995
- fpsToNumber4(job.config.fps),
3101
+ scratchBytes,
2996
3102
  log
2997
3103
  );
2998
3104
  const releaseReservation = reserveHdrExtractionBytes(estimatedBytes, budgetBytes);
@@ -3004,79 +3110,79 @@ async function extractHdrVideoFrames(args) {
3004
3110
  if (!video || !planned) continue;
3005
3111
  const { window: window2 } = planned;
3006
3112
  mkdirSync2(framesDir, { recursive: true });
3007
- const frameDir = mkdtempSync2(join7(framesDir, `hdr_${tempDirSafePrefix(videoId)}-`));
3113
+ const frameDir = mkdtempSync2(join8(framesDir, `hdr_${tempDirSafePrefix(videoId)}-`));
3008
3114
  createdFrameDirs.add(frameDir);
3009
3115
  const dims = prep.hdrExtractionDims.get(videoId) ?? { width, height };
3010
- const rawPath = join7(frameDir, "frames.rgb48le");
3011
- const extractionMediaStart = window2.extractionMediaStart ?? window2.mediaStart;
3012
- const extractionStart = String(extractionMediaStart);
3013
- const ffmpegArgs = [];
3014
- if (window2.finalFrameOnly) {
3015
- ffmpegArgs.push("-i", srcPath, "-ss", extractionStart, "-frames:v", "1");
3016
- } else {
3017
- ffmpegArgs.push(
3018
- "-ss",
3019
- extractionStart,
3020
- "-i",
3021
- srcPath,
3022
- "-t",
3023
- String(window2.durationSeconds)
3024
- );
3025
- }
3026
- if (!window2.finalFrameOnly) {
3027
- ffmpegArgs.push("-r", fpsToFfmpegArg(job.config.fps));
3028
- }
3029
- ffmpegArgs.push(
3030
- "-vf",
3031
- `scale=${dims.width}:${dims.height}:force_original_aspect_ratio=increase,crop=${dims.width}:${dims.height}`,
3032
- "-pix_fmt",
3033
- "rgb48le",
3034
- "-f",
3035
- "rawvideo",
3036
- "-y",
3037
- rawPath
3038
- );
3039
- const result = await runFfmpegImpl(ffmpegArgs, { signal: abortSignal });
3040
- if (!result.success) {
3041
- hdrDiagnostics.videoExtractionFailures += 1;
3042
- log.error("HDR frame pre-extraction failed; aborting render", {
3043
- videoId,
3044
- srcPath,
3045
- stderr: result.stderr.slice(-400)
3046
- });
3047
- throw encoderFailureError(`HDR frame extraction failed for video "${videoId}"`, {
3048
- error: `Aborting render to avoid shipping black HDR layers: ${result.stderr.slice(-400)}`,
3049
- failureReason: result.failureReason
3050
- });
3051
- }
3052
3116
  const frameSize = dims.width * dims.height * 6;
3053
- const fd = openSync3(rawPath, constants2.O_RDONLY | NO_FOLLOW_FLAG);
3054
- let handedOff = false;
3055
- try {
3056
- const frameCount = Math.floor(fstatSync3(fd).size / frameSize);
3057
- if (frameCount < 1) {
3058
- hdrDiagnostics.videoExtractionFailures += 1;
3059
- throw new Error(
3060
- `HDR frame extraction produced no frames for video "${videoId}". Aborting render to avoid shipping black HDR layers.`
3117
+ const extractionMediaStart = window2.extractionMediaStart ?? window2.mediaStart;
3118
+ const scale = `scale=${dims.width}:${dims.height}:force_original_aspect_ratio=increase,crop=${dims.width}:${dims.height}`;
3119
+ const firstTick = window2.finalFrameOnly ? 0 : await probeFirstTick(srcPath, extractionMediaStart, fps, runFfmpegImpl, abortSignal);
3120
+ const totalFrames = window2.finalFrameOnly ? 1 : Math.max(1, Math.ceil(window2.durationSeconds * fps - firstTick - 1e-9)) + Number(video.loop);
3121
+ const windowFrames = Math.min(totalFrames, hdrWindowFrames(frameSize, windowBytes));
3122
+ const windows = createHdrFrameWindows({
3123
+ dir: frameDir,
3124
+ frameSize,
3125
+ totalFrames,
3126
+ windowFrames,
3127
+ maxResident,
3128
+ extract: async (spec) => {
3129
+ const ffmpegArgs = window2.finalFrameOnly ? (
3130
+ // Decode before seeking for the one-frame path. Input-side seeking can
3131
+ // return zero frames for valid unindexed/negative-base transports.
3132
+ ["-i", srcPath, "-ss", String(extractionMediaStart), "-frames:v", "1", "-vf", scale]
3133
+ ) : hdrWindowFfmpegArgs({
3134
+ srcPath,
3135
+ mediaStart: extractionMediaStart,
3136
+ fps,
3137
+ fpsArg: fpsToFfmpegArg(job.config.fps),
3138
+ firstTick,
3139
+ firstFrame: spec.firstFrame,
3140
+ frames: spec.frames,
3141
+ scale
3142
+ });
3143
+ const result = await runFfmpegImpl(
3144
+ [...ffmpegArgs, "-pix_fmt", "rgb48le", "-f", "rawvideo", "-y", spec.path],
3145
+ { signal: abortSignal }
3061
3146
  );
3147
+ if (!result.success) {
3148
+ hdrDiagnostics.videoExtractionFailures += 1;
3149
+ log.error("HDR frame extraction failed; aborting render", {
3150
+ videoId,
3151
+ srcPath,
3152
+ firstFrame: spec.firstFrame,
3153
+ stderr: result.stderr.slice(-400)
3154
+ });
3155
+ throw encoderFailureError(`HDR frame extraction failed for video "${videoId}"`, {
3156
+ error: `Aborting render to avoid shipping black HDR layers: ${result.stderr.slice(-400)}`,
3157
+ failureReason: result.failureReason
3158
+ });
3159
+ }
3160
+ return Math.min(Math.floor(statSync2(spec.path).size / frameSize), spec.frames);
3062
3161
  }
3063
- out.set(videoId, {
3064
- dir: frameDir,
3065
- rawPath,
3066
- fd,
3067
- width: dims.width,
3068
- height: dims.height,
3069
- frameSize,
3070
- frameCount,
3071
- scratch: Buffer.allocUnsafe(frameSize),
3072
- loop: video.loop,
3073
- mediaStart: extractionMediaStart,
3074
- videoStreamLeadSeconds: planned.videoStreamLeadSeconds
3075
- });
3076
- handedOff = true;
3077
- } finally {
3078
- if (!handedOff) closeSync4(fd);
3162
+ });
3163
+ const first = await windows.lease(0);
3164
+ if (!first) {
3165
+ hdrDiagnostics.videoExtractionFailures += 1;
3166
+ throw new Error(
3167
+ `HDR frame extraction produced no frames for video "${videoId}". Aborting render to avoid shipping black HDR layers.`
3168
+ );
3079
3169
  }
3170
+ first.release();
3171
+ if (video.loop) (await windows.lease(totalFrames - 1))?.release();
3172
+ out.set(videoId, {
3173
+ dir: frameDir,
3174
+ windows,
3175
+ width: dims.width,
3176
+ height: dims.height,
3177
+ frameSize,
3178
+ get frameCount() {
3179
+ return windows.totalFrames;
3180
+ },
3181
+ scratch: Buffer.allocUnsafe(frameSize),
3182
+ loop: video.loop,
3183
+ mediaStart: extractionMediaStart,
3184
+ videoStreamLeadSeconds: planned.videoStreamLeadSeconds
3185
+ });
3080
3186
  }
3081
3187
  return { sources: out, estimatedBytes, releaseReservation };
3082
3188
  } catch (error) {
@@ -3085,12 +3191,23 @@ async function extractHdrVideoFrames(args) {
3085
3191
  createdFrameDirs.delete(source.dir);
3086
3192
  }
3087
3193
  for (const frameDir of createdFrameDirs) {
3088
- cleanupHdrFrameDirectory(frameDir, void 0, log);
3194
+ cleanupHdrFrameDirectory(frameDir, log);
3089
3195
  }
3090
3196
  releaseReservation();
3091
3197
  throw error;
3092
3198
  }
3093
3199
  }
3200
+ function hdrWindowFfmpegArgs(a) {
3201
+ const unit = Math.ceil(a.fps);
3202
+ const seekFrame = a.firstFrame < unit ? 0 : (Math.floor(a.firstFrame / unit) - 1) * unit;
3203
+ const trim = a.firstFrame - seekFrame;
3204
+ const filters = [`fps=${a.fpsArg}:start_time=${(a.firstTick / a.fps).toFixed(6)}`, a.scale];
3205
+ if (trim > 0) filters.push(`trim=start_frame=${trim}`);
3206
+ return [
3207
+ ...["-ss", (a.mediaStart + seekFrame / a.fps).toFixed(6), "-i", a.srcPath],
3208
+ ...["-vf", filters.join(","), "-frames:v", String(a.frames)]
3209
+ ];
3210
+ }
3094
3211
  function decodeHdrImageBuffers(args) {
3095
3212
  const { log, hdrImageSrcPaths, prep, hdrDiagnostics } = args;
3096
3213
  const out = /* @__PURE__ */ new Map();
@@ -3110,7 +3227,11 @@ function decodeHdrImageBuffers(args) {
3110
3227
  fit,
3111
3228
  fitInfo?.position
3112
3229
  );
3113
- out.set(imageId, { data: resampled, width: layout.width, height: layout.height });
3230
+ out.set(imageId, {
3231
+ data: resampled,
3232
+ width: layout.width,
3233
+ height: layout.height
3234
+ });
3114
3235
  } else {
3115
3236
  out.set(imageId, {
3116
3237
  data: Buffer.from(decoded.data),
@@ -3222,7 +3343,7 @@ async function captureSceneIntoBuffer(a) {
3222
3343
  hdrPerf
3223
3344
  );
3224
3345
  } else {
3225
- blitHdrVideoLayer(
3346
+ await blitHdrVideoLayer(
3226
3347
  sceneBuf,
3227
3348
  el,
3228
3349
  time,
@@ -3424,7 +3545,7 @@ function isTargetLossError(error) {
3424
3545
  // src/services/render/stages/captureStreamingStage.ts
3425
3546
  import { mkdtemp, writeFile } from "node:fs/promises";
3426
3547
  import { tmpdir } from "node:os";
3427
- import { join as join8 } from "node:path";
3548
+ import { join as join9 } from "node:path";
3428
3549
  import {
3429
3550
  DrawElementVerificationError,
3430
3551
  captureFrameToBuffer,
@@ -3586,11 +3707,11 @@ function createDrainFrameGuard(args) {
3586
3707
  return buf;
3587
3708
  }
3588
3709
  if (db < verifyMinDb) {
3589
- const dumpDir = await mkdtemp(join8(tmpdir(), "hf-de-verify-fail-")).catch(() => null);
3710
+ const dumpDir = await mkdtemp(join9(tmpdir(), "hf-de-verify-fail-")).catch(() => null);
3590
3711
  if (dumpDir) {
3591
3712
  await Promise.all([
3592
- writeFile(join8(dumpDir, `frame-${idx}-de.jpg`), buf),
3593
- writeFile(join8(dumpDir, `frame-${idx}-truth.jpg`), truth)
3713
+ writeFile(join9(dumpDir, `frame-${idx}-de.jpg`), buf),
3714
+ writeFile(join9(dumpDir, `frame-${idx}-truth.jpg`), truth)
3594
3715
  ]).catch(() => {
3595
3716
  });
3596
3717
  }
@@ -4039,7 +4160,7 @@ async function runCaptureStreamingStage(input) {
4039
4160
 
4040
4161
  // src/services/render/stages/captureSegmentedStage.ts
4041
4162
  function segmentOutputPath(segmentDir, index) {
4042
- return join9(segmentDir, `segment_${String(index).padStart(5, "0")}.mp4`);
4163
+ return join10(segmentDir, `segment_${String(index).padStart(5, "0")}.mp4`);
4043
4164
  }
4044
4165
  function segmentEncoderOptions(base, segment) {
4045
4166
  return {
@@ -4115,7 +4236,7 @@ async function captureSegment(ctx, segment) {
4115
4236
  path: segmentPath,
4116
4237
  // Recorded so resume can prove the file on disk is the one that
4117
4238
  // finished; absent only in unit tests, whose fake encoder writes none.
4118
- bytes: existsSync6(segmentPath) ? statSync2(segmentPath).size : 0
4239
+ bytes: existsSync6(segmentPath) ? statSync3(segmentPath).size : 0
4119
4240
  });
4120
4241
  return encodeResult.durationMs;
4121
4242
  } finally {
@@ -4133,7 +4254,7 @@ var DEFAULT_DEPS = {
4133
4254
  // is invisible to a test that swaps the engine module after this file has
4134
4255
  // been evaluated (bun updates live bindings, not copies).
4135
4256
  closeSession: (session) => closeCaptureSession2(session),
4136
- removeFile: (path) => rmSync5(path, { force: true })
4257
+ removeFile: (path) => rmSync6(path, { force: true })
4137
4258
  };
4138
4259
  function defaultSessionFactory(input) {
4139
4260
  let probeSession = input.probeSession;
@@ -4300,7 +4421,7 @@ async function runCaptureSegmentedStage(input) {
4300
4421
  const { log, dedupPerfs, assertNotAborted } = input;
4301
4422
  const deps = { ...DEFAULT_DEPS, ...input.deps };
4302
4423
  const segments = planSegments(input.totalFrames, input.segmentFrames);
4303
- const segmentDir = input.segmentDir ?? join9(input.workDir, "segments");
4424
+ const segmentDir = input.segmentDir ?? join10(input.workDir, "segments");
4304
4425
  mkdirSync3(segmentDir, { recursive: true });
4305
4426
  const skip = input.completedSegments ?? /* @__PURE__ */ new Set();
4306
4427
  const pending = segments.filter((s) => !skip.has(s.index));
@@ -4371,10 +4492,83 @@ async function runCaptureSegmentedStage(input) {
4371
4492
  };
4372
4493
  }
4373
4494
 
4495
+ // src/services/render/renderEtaGuard.ts
4496
+ var RENDER_TOO_SLOW = "RENDER_TOO_SLOW";
4497
+ var ETA_MARGIN = 1.2;
4498
+ var MIN_SAMPLE_MS = 6e4;
4499
+ var MIN_SAMPLE_FRAMES = 3;
4500
+ var POLL_MS = 5e3;
4501
+ var RenderTooSlowError = class extends Error {
4502
+ code = RENDER_TOO_SLOW;
4503
+ // The producer server reads these off the error for the wire envelope.
4504
+ // fallow-ignore-next-line unused-class-member
4505
+ owner = "user";
4506
+ // fallow-ignore-next-line unused-class-member
4507
+ retryable = false;
4508
+ publicMetadata;
4509
+ constructor(stage, input) {
4510
+ const { framesCompleted, totalFrames, msPerFrame, etaMs, maxRenderMs } = input;
4511
+ super(
4512
+ `render too slow: stage ${stage}, ${framesCompleted}/${totalFrames} frames, ${Math.round(msPerFrame)} ms/frame, ETA ${etaMs} ms exceeds maxRenderMs ${maxRenderMs}`
4513
+ );
4514
+ this.name = "RenderTooSlowError";
4515
+ this.publicMetadata = { stage, framesCompleted, totalFrames, msPerFrame, etaMs, maxRenderMs };
4516
+ }
4517
+ };
4518
+ function checkRenderEta(stage, input) {
4519
+ const { maxRenderMs, elapsedMs, framesCompleted, totalFrames, msPerFrame } = input;
4520
+ if (!maxRenderMs || maxRenderMs <= 0 || !(msPerFrame > 0)) return void 0;
4521
+ const etaMs = Math.round(elapsedMs + (totalFrames - framesCompleted) * msPerFrame);
4522
+ if (etaMs <= maxRenderMs * ETA_MARGIN) return void 0;
4523
+ return new RenderTooSlowError(stage, { ...input, maxRenderMs, etaMs });
4524
+ }
4525
+ function createRateSampler() {
4526
+ let base;
4527
+ let lastFrames = 0;
4528
+ return {
4529
+ observe(done, at) {
4530
+ if (done === 0 || done < lastFrames) base = void 0;
4531
+ lastFrames = done;
4532
+ if (done === 0) return void 0;
4533
+ base ??= { at, frames: done };
4534
+ const sampleMs = at - base.at;
4535
+ const sampleFrames = done - base.frames;
4536
+ if (sampleMs < MIN_SAMPLE_MS || sampleFrames < MIN_SAMPLE_FRAMES) return void 0;
4537
+ return sampleMs / sampleFrames;
4538
+ }
4539
+ };
4540
+ }
4541
+ function startRenderEtaGuard(options) {
4542
+ const { maxRenderMs, job, totalFrames, pipelineStartMs, onTooSlow, now = Date.now } = options;
4543
+ if (!maxRenderMs || maxRenderMs <= 0) return () => {
4544
+ };
4545
+ const sampler = createRateSampler();
4546
+ const stop = () => clearInterval(timer);
4547
+ const timer = setInterval(() => {
4548
+ const done = job.framesRendered ?? 0;
4549
+ if (done >= totalFrames) return stop();
4550
+ const at = now();
4551
+ const msPerFrame = sampler.observe(done, at);
4552
+ if (msPerFrame === void 0) return;
4553
+ const error = checkRenderEta("capture", {
4554
+ maxRenderMs,
4555
+ elapsedMs: at - pipelineStartMs,
4556
+ framesCompleted: done,
4557
+ totalFrames,
4558
+ msPerFrame
4559
+ });
4560
+ if (!error) return;
4561
+ stop();
4562
+ onTooSlow(error);
4563
+ }, options.pollMs ?? POLL_MS);
4564
+ timer.unref?.();
4565
+ return stop;
4566
+ }
4567
+
4374
4568
  // src/services/render/segmentManifest.ts
4375
4569
  import { createHash as createHash2 } from "node:crypto";
4376
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, statSync as statSync3, writeFileSync as writeFileSync3 } from "node:fs";
4377
- import { join as join10 } from "node:path";
4570
+ import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, statSync as statSync4, writeFileSync as writeFileSync3 } from "node:fs";
4571
+ import { join as join11 } from "node:path";
4378
4572
  import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
4379
4573
  var MANIFEST_FILENAME = "segments.json";
4380
4574
  function computeSegmentPlanHash(input) {
@@ -4400,7 +4594,7 @@ function computeSegmentPlanHash(input) {
4400
4594
  return createHash2("sha256").update(ordered).digest("hex").slice(0, 16);
4401
4595
  }
4402
4596
  function segmentDirFor(projectRendersDir, planHash) {
4403
- return join10(projectRendersDir, ".hf-segments", planHash);
4597
+ return join11(projectRendersDir, ".hf-segments", planHash);
4404
4598
  }
4405
4599
  function hasNumber(value, key) {
4406
4600
  return key in value && typeof Reflect.get(value, key) === "number";
@@ -4418,7 +4612,7 @@ function isManifest(value) {
4418
4612
  return Reflect.get(value, "version") === 1 && hasString(value, "planHash") && hasNumber(value, "totalFrames") && hasNumber(value, "segmentFrames") && Array.isArray(completed) && completed.every(isEntry);
4419
4613
  }
4420
4614
  function readSegmentManifest(segmentDir) {
4421
- const path = join10(segmentDir, MANIFEST_FILENAME);
4615
+ const path = join11(segmentDir, MANIFEST_FILENAME);
4422
4616
  if (!existsSync7(path)) return null;
4423
4617
  try {
4424
4618
  const parsed = JSON.parse(readFileSync4(path, "utf-8"));
@@ -4429,9 +4623,9 @@ function readSegmentManifest(segmentDir) {
4429
4623
  }
4430
4624
  function writeSegmentManifest(segmentDir, manifest) {
4431
4625
  mkdirSync4(segmentDir, { recursive: true });
4432
- const tmp = join10(segmentDir, `${MANIFEST_FILENAME}.${process.pid}.tmp`);
4626
+ const tmp = join11(segmentDir, `${MANIFEST_FILENAME}.${process.pid}.tmp`);
4433
4627
  writeFileSync3(tmp, JSON.stringify(manifest, null, 2), "utf-8");
4434
- renameWithRetry2(tmp, join10(segmentDir, MANIFEST_FILENAME));
4628
+ renameWithRetry2(tmp, join11(segmentDir, MANIFEST_FILENAME));
4435
4629
  }
4436
4630
  async function probeSegmentFrameCount(path, probe) {
4437
4631
  try {
@@ -4449,7 +4643,7 @@ async function validateCompletedSegments(manifest, expectedHash, probeFrames) {
4449
4643
  if (manifest.planHash !== expectedHash) return ok;
4450
4644
  for (const entry of manifest.completed) {
4451
4645
  if (!existsSync7(entry.path)) continue;
4452
- const size = statSync3(entry.path).size;
4646
+ const size = statSync4(entry.path).size;
4453
4647
  if (size <= 0 || size !== entry.bytes) continue;
4454
4648
  const frames = await probeFrames(entry.path);
4455
4649
  if (frames !== entry.endFrame - entry.startFrame) continue;
@@ -4543,7 +4737,7 @@ function resolveVideoCaptureBeyondViewport(videoCount) {
4543
4737
  }
4544
4738
 
4545
4739
  // src/services/render/captureCost.ts
4546
- import { join as join11 } from "node:path";
4740
+ import { join as join12 } from "node:path";
4547
4741
  import { fpsToNumber as fpsToNumber5 } from "@hyperframes/core";
4548
4742
  import {
4549
4743
  calculateOptimalWorkers,
@@ -4788,7 +4982,7 @@ async function runCaptureCalibration(input) {
4788
4982
  });
4789
4983
  let calibration;
4790
4984
  try {
4791
- calibration = await runOneCalibration(join11(workDir, "capture-calibration"), calibrationCfg);
4985
+ calibration = await runOneCalibration(join12(workDir, "capture-calibration"), calibrationCfg);
4792
4986
  } catch (error) {
4793
4987
  const shouldFallback = !forceScreenshot && shouldFallbackToScreenshotAfterCalibrationError(error);
4794
4988
  if (!shouldFallback) {
@@ -4821,7 +5015,7 @@ async function runCaptureCalibration(input) {
4821
5015
  const screenshotCfg = createCaptureCalibrationConfig({ ...cfg, forceScreenshot: true });
4822
5016
  try {
4823
5017
  calibration = await runOneCalibration(
4824
- join11(workDir, "capture-calibration-screenshot"),
5018
+ join12(workDir, "capture-calibration-screenshot"),
4825
5019
  screenshotCfg
4826
5020
  );
4827
5021
  } catch (fallbackError) {
@@ -5253,14 +5447,14 @@ function countAuthoredTimedClips(html) {
5253
5447
  }
5254
5448
 
5255
5449
  // src/services/render/stages/compileStage.ts
5256
- import { join as join14 } from "node:path";
5450
+ import { join as join15 } from "node:path";
5257
5451
  import { suggestMatchingPreset } from "@hyperframes/core";
5258
5452
 
5259
5453
  // src/services/htmlCompiler.ts
5260
5454
  import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
5261
5455
  import { createHash as createHash5 } from "node:crypto";
5262
5456
  import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync6 } from "fs";
5263
- import { join as join13, dirname as dirname6, resolve as resolve6, basename as basename4, relative as relative2 } from "path";
5457
+ import { join as join14, dirname as dirname6, resolve as resolve6, basename as basename4, relative as relative2 } from "path";
5264
5458
  import { parseHTML as parseHTML4 } from "linkedom";
5265
5459
  import {
5266
5460
  COMPOSITION_SOURCE_URL,
@@ -5487,8 +5681,8 @@ import {
5487
5681
 
5488
5682
  // src/services/animatedGifPrep.ts
5489
5683
  import { createHash as createHash4 } from "node:crypto";
5490
- import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync6, statSync as statSync4 } from "node:fs";
5491
- import { dirname as dirname5, isAbsolute as isAbsolute2, join as join12, resolve as resolve5 } from "node:path";
5684
+ import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync5, readFileSync as readFileSync5, rmSync as rmSync7, statSync as statSync5 } from "node:fs";
5685
+ import { dirname as dirname5, isAbsolute as isAbsolute2, join as join13, resolve as resolve5 } from "node:path";
5492
5686
  import { parseHTML as parseHTML3 } from "linkedom";
5493
5687
  import { parseAnimatedGifMetadata } from "@hyperframes/core";
5494
5688
  import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2, renameWithRetry as renameWithRetry3 } from "@hyperframes/engine";
@@ -5537,7 +5731,7 @@ function resolveGifSourcePath(src, options) {
5537
5731
  }
5538
5732
  function isUsableFile(path) {
5539
5733
  try {
5540
- const stat = statSync4(path);
5734
+ const stat = statSync5(path);
5541
5735
  return stat.isFile() && stat.size > 0;
5542
5736
  } catch {
5543
5737
  return false;
@@ -5672,10 +5866,10 @@ async function ensurePreparedWebm(input) {
5672
5866
  if (!isUsableFile(input.cachePath)) {
5673
5867
  renameWithRetry3(tmpPath, input.cachePath);
5674
5868
  } else {
5675
- rmSync6(tmpPath, { force: true });
5869
+ rmSync7(tmpPath, { force: true });
5676
5870
  }
5677
5871
  } catch (error) {
5678
- rmSync6(tmpPath, { force: true });
5872
+ rmSync7(tmpPath, { force: true });
5679
5873
  throw error;
5680
5874
  }
5681
5875
  }
@@ -5731,7 +5925,7 @@ function replaceImageWithVideo(input) {
5731
5925
  return video;
5732
5926
  }
5733
5927
  async function prepareAnimatedGifInputs(html, options) {
5734
- const outputDir = options.outputDir ?? join12(options.downloadDir, PREPARED_GIF_SUBDIR);
5928
+ const outputDir = options.outputDir ?? join13(options.downloadDir, PREPARED_GIF_SUBDIR);
5735
5929
  const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_GIF_SUBDIR);
5736
5930
  const cacheDir = options.cacheDir ?? outputDir;
5737
5931
  const { document: document2 } = parseHTML3(html);
@@ -5753,8 +5947,8 @@ async function prepareAnimatedGifInputs(html, options) {
5753
5947
  const { loopIterations, padSeconds } = resolvePreparedPlayback(metadata, loop, windowSeconds);
5754
5948
  const hash = computePreparedGifHash(bytes, loopIterations, padSeconds);
5755
5949
  const filename = `${CACHE_SCHEMA}-${hash.slice(0, 24)}.webm`;
5756
- const cachePath = join12(cacheDir, filename);
5757
- const outputPath = join12(outputDir, filename);
5950
+ const cachePath = join13(cacheDir, filename);
5951
+ const outputPath = join13(outputDir, filename);
5758
5952
  const outputSrc = `${outputSrcPrefix}/${filename}`;
5759
5953
  await ensurePreparedWebm({
5760
5954
  sourcePath,
@@ -6168,7 +6362,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
6168
6362
  }
6169
6363
  } else {
6170
6364
  filePath = decodedUrlPath(src);
6171
- if (!filePath.startsWith("/")) filePath = join13(baseDir, filePath);
6365
+ if (!filePath.startsWith("/")) filePath = join14(baseDir, filePath);
6172
6366
  filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
6173
6367
  }
6174
6368
  if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
@@ -6788,7 +6982,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
6788
6982
  return downloadAndRewriteUrls(
6789
6983
  urlSet,
6790
6984
  html,
6791
- join13(downloadDir, REMOTE_MEDIA_SUBDIR),
6985
+ join14(downloadDir, REMOTE_MEDIA_SUBDIR),
6792
6986
  "Remote media download failed",
6793
6987
  "Localized remote media source(s)"
6794
6988
  );
@@ -6803,7 +6997,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
6803
6997
  return downloadAndRewriteUrls(
6804
6998
  urlSet,
6805
6999
  html,
6806
- join13(downloadDir, REMOTE_MEDIA_SUBDIR),
7000
+ join14(downloadDir, REMOTE_MEDIA_SUBDIR),
6807
7001
  "Remote image download failed",
6808
7002
  "Localized remote image source(s)"
6809
7003
  );
@@ -6819,7 +7013,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
6819
7013
  return downloadAndRewriteUrls(
6820
7014
  urlSet,
6821
7015
  html,
6822
- join13(downloadDir, REMOTE_MEDIA_SUBDIR),
7016
+ join14(downloadDir, REMOTE_MEDIA_SUBDIR),
6823
7017
  "Remote background-image download failed",
6824
7018
  "Localized remote background-image(s)",
6825
7019
  // Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
@@ -6954,7 +7148,7 @@ async function localizeRemoteFontFaces(html, downloadDir) {
6954
7148
  return downloadAndRewriteUrls(
6955
7149
  urlSet,
6956
7150
  processed,
6957
- join13(downloadDir, REMOTE_MEDIA_SUBDIR),
7151
+ join14(downloadDir, REMOTE_MEDIA_SUBDIR),
6958
7152
  "Remote font download failed",
6959
7153
  "Localized remote font face(s)",
6960
7154
  (h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
@@ -7621,7 +7815,7 @@ async function runCompileStage(input) {
7621
7815
  abortSignal
7622
7816
  } = input;
7623
7817
  const compileStart = Date.now();
7624
- const compiled = await compileForRender(projectDir, htmlPath, join14(workDir, "downloads"), {
7818
+ const compiled = await compileForRender(projectDir, htmlPath, join15(workDir, "downloads"), {
7625
7819
  log,
7626
7820
  failClosedFontFetch: failClosedFontFetch === true,
7627
7821
  allowSystemFontCapture,
@@ -7629,7 +7823,7 @@ async function runCompileStage(input) {
7629
7823
  variables: input.variables,
7630
7824
  urlByAbsentLocalPath: input.urlByAbsentLocalPath,
7631
7825
  pageMotionBlur: input.pageMotionBlur,
7632
- animatedGifCacheDir: cfg.extractCacheDir ? join14(cfg.extractCacheDir, "animated-gif") : void 0,
7826
+ animatedGifCacheDir: cfg.extractCacheDir ? join15(cfg.extractCacheDir, "animated-gif") : void 0,
7633
7827
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
7634
7828
  });
7635
7829
  assertNotAborted();
@@ -7692,7 +7886,7 @@ async function runCompileStage(input) {
7692
7886
  };
7693
7887
  await preflightCompositionAssetMediaTypes({
7694
7888
  projectDir,
7695
- compiledDir: join14(workDir, "compiled"),
7889
+ compiledDir: join15(workDir, "compiled"),
7696
7890
  composition,
7697
7891
  signal: abortSignal
7698
7892
  });
@@ -7737,7 +7931,7 @@ async function runCompileStage(input) {
7737
7931
  }
7738
7932
 
7739
7933
  // src/services/render/stages/probeStage.ts
7740
- import { join as join15 } from "node:path";
7934
+ import { join as join16 } from "node:path";
7741
7935
  import { parseHTML as parseHTML5 } from "linkedom";
7742
7936
  import {
7743
7937
  closeCaptureSession as closeCaptureSession4,
@@ -7929,7 +8123,7 @@ async function runProbeStage(input) {
7929
8123
  });
7930
8124
  fileServer = await createFileServer({
7931
8125
  projectDir,
7932
- compiledDir: join15(workDir, "compiled"),
8126
+ compiledDir: join16(workDir, "compiled"),
7933
8127
  port: 0,
7934
8128
  preHeadScripts: [VIRTUAL_TIME_SHIM],
7935
8129
  fps: job.config.fps
@@ -7957,7 +8151,7 @@ async function runProbeStage(input) {
7957
8151
  log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
7958
8152
  probeSession = await createCaptureSession4(
7959
8153
  fileServer.url,
7960
- join15(workDir, "probe"),
8154
+ join16(workDir, "probe"),
7961
8155
  captureOpts,
7962
8156
  null,
7963
8157
  probeCfg
@@ -8042,7 +8236,7 @@ async function runProbeStage(input) {
8042
8236
  });
8043
8237
  probeSession = await createCaptureSession4(
8044
8238
  fileServer.url,
8045
- join15(workDir, "probe-screenshot"),
8239
+ join16(workDir, "probe-screenshot"),
8046
8240
  captureOpts,
8047
8241
  null,
8048
8242
  { ...probeCfg, forceScreenshot: true }
@@ -8078,7 +8272,7 @@ async function runProbeStage(input) {
8078
8272
  compiled,
8079
8273
  resolutions,
8080
8274
  projectDir,
8081
- join15(workDir, "downloads")
8275
+ join16(workDir, "downloads")
8082
8276
  );
8083
8277
  assertNotAborted();
8084
8278
  composition.videos = compiled.videos;
@@ -8269,7 +8463,7 @@ async function runProbeStage(input) {
8269
8463
  }
8270
8464
  await preflightCompositionAssetMediaTypes({
8271
8465
  projectDir,
8272
- compiledDir: join15(workDir, "compiled"),
8466
+ compiledDir: join16(workDir, "compiled"),
8273
8467
  composition,
8274
8468
  signal: abortSignal
8275
8469
  });
@@ -8434,7 +8628,7 @@ function validateDistributedDuration(input) {
8434
8628
 
8435
8629
  // src/services/render/stages/extractVideosStage.ts
8436
8630
  import { existsSync as existsSync11 } from "node:fs";
8437
- import { join as join16 } from "node:path";
8631
+ import { join as join17 } from "node:path";
8438
8632
  import {
8439
8633
  classifyVideoExtractionError,
8440
8634
  createFrameLookupTable,
@@ -8754,14 +8948,14 @@ async function runExtractVideosStage(input) {
8754
8948
  // because short boundary counts can differ by one frame.
8755
8949
  {
8756
8950
  fps: job.config.fps,
8757
- outputDir: join16(compiledDir, "__hyperframes_video_frames"),
8951
+ outputDir: join17(compiledDir, "__hyperframes_video_frames"),
8758
8952
  format: job.config.videoFrameFormat ?? "auto",
8759
8953
  toneMapHdrToSdr: job.config.hdrMode === "force-sdr",
8760
8954
  timelineEnd: composition.duration,
8761
8955
  maxTransientRetries: extractionPolicy.maxTransientRetries,
8762
8956
  collectProbeFailures: extractionPolicy.failureMode === "enforce",
8763
8957
  deferRangeExtraction,
8764
- contentKeyedDirs: [join16(compiledDir, REMOTE_MEDIA_SUBDIR)],
8958
+ contentKeyedDirs: [join17(compiledDir, REMOTE_MEDIA_SUBDIR)],
8765
8959
  urlByAbsentLocalPath
8766
8960
  },
8767
8961
  abortSignal,
@@ -8828,8 +9022,8 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
8828
9022
  }
8829
9023
 
8830
9024
  // src/services/render/stages/audioStage.ts
8831
- import { join as join18 } from "node:path";
8832
- import { rmSync as rmSync8 } from "node:fs";
9025
+ import { join as join19 } from "node:path";
9026
+ import { rmSync as rmSync9 } from "node:fs";
8833
9027
  import {
8834
9028
  MIXED_AUDIO_FILENAME,
8835
9029
  audioFailureDiagnostics,
@@ -8838,8 +9032,8 @@ import {
8838
9032
 
8839
9033
  // src/services/render/audioPadTrim.ts
8840
9034
  import { spawn } from "node:child_process";
8841
- import { mkdtempSync as mkdtempSync3, rmSync as rmSync7 } from "node:fs";
8842
- import { dirname as dirname7, join as join17 } from "node:path";
9035
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync8 } from "node:fs";
9036
+ import { dirname as dirname7, join as join18 } from "node:path";
8843
9037
  import {
8844
9038
  DEFAULT_CONFIG,
8845
9039
  describeFfmpegFailure as describeFfmpegFailure2,
@@ -9042,7 +9236,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
9042
9236
  error: `audioPadTrim: failed to materialize ${plan2.operation}: ${err instanceof Error ? err.message : String(err)}`
9043
9237
  };
9044
9238
  } finally {
9045
- for (const path of plan2.cleanupPaths) rmSync7(path, { force: true });
9239
+ for (const path of plan2.cleanupPaths) rmSync8(path, { force: true });
9046
9240
  }
9047
9241
  let loweredDb = 0;
9048
9242
  if (probeTruePeak) {
@@ -9088,8 +9282,8 @@ async function constrainAacTruePeak(input) {
9088
9282
  async function enforceAacTruePeak(input) {
9089
9283
  let scratchDir;
9090
9284
  try {
9091
- scratchDir = mkdtempSync3(join17(dirname7(input.audioPath), ".true-peak-"));
9092
- const correctedPath = join17(scratchDir, "audio.m4a");
9285
+ scratchDir = mkdtempSync3(join18(dirname7(input.audioPath), ".true-peak-"));
9286
+ const correctedPath = join18(scratchDir, "audio.m4a");
9093
9287
  const measurements = [];
9094
9288
  let attenuationDb = 0;
9095
9289
  let measuredPath = input.audioPath;
@@ -9140,7 +9334,7 @@ async function enforceAacTruePeak(input) {
9140
9334
  )}`
9141
9335
  };
9142
9336
  } finally {
9143
- if (scratchDir) rmSync7(scratchDir, { recursive: true, force: true });
9337
+ if (scratchDir) rmSync8(scratchDir, { recursive: true, force: true });
9144
9338
  }
9145
9339
  }
9146
9340
  function failResult(outputPath, target, source, error) {
@@ -9287,7 +9481,7 @@ function assertAudioNotInterrupted(failures) {
9287
9481
  }
9288
9482
  async function runAudioStage(input) {
9289
9483
  const stageStart = Date.now();
9290
- const pcmSourcePath = join18(input.workDir, "audio-source.wav");
9484
+ const pcmSourcePath = join19(input.workDir, "audio-source.wav");
9291
9485
  const duration = input.totalFrames * input.fps.den / input.fps.num;
9292
9486
  try {
9293
9487
  const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
@@ -9307,7 +9501,7 @@ async function runAudioStage(input) {
9307
9501
  }
9308
9502
  return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
9309
9503
  } finally {
9310
- rmSync8(pcmSourcePath, { force: true });
9504
+ rmSync9(pcmSourcePath, { force: true });
9311
9505
  }
9312
9506
  }
9313
9507
  async function mixAudioForDelivery(input, duration, pcmSourcePath) {
@@ -9325,7 +9519,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9325
9519
  assertNotAborted,
9326
9520
  log
9327
9521
  } = input;
9328
- const audioOutputPath = join18(workDir, MIXED_AUDIO_FILENAME);
9522
+ const audioOutputPath = join19(workDir, MIXED_AUDIO_FILENAME);
9329
9523
  let hasAudio = false;
9330
9524
  let audioError;
9331
9525
  let audioFailures;
@@ -9336,7 +9530,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9336
9530
  audioResult = await processCompositionAudio(
9337
9531
  audios,
9338
9532
  projectDir,
9339
- join18(workDir, "audio-work"),
9533
+ join19(workDir, "audio-work"),
9340
9534
  audioOutputPath,
9341
9535
  duration,
9342
9536
  abortSignal,
@@ -9405,7 +9599,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9405
9599
 
9406
9600
  // src/services/render/stages/captureStage.ts
9407
9601
  import { existsSync as existsSync12, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
9408
- import { join as join19 } from "node:path";
9602
+ import { join as join20 } from "node:path";
9409
9603
  import {
9410
9604
  classifyCaptureFailure as classifyCaptureFailure2,
9411
9605
  frameFileExtension,
@@ -9478,13 +9672,13 @@ function measureCaptureFrameBytes(workDir, framesDir) {
9478
9672
  if (!existsSync12(workDir)) return total;
9479
9673
  for (const attempt of readdirSync3(workDir)) {
9480
9674
  if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
9481
- total += sampleDirectoryBytes(join19(workDir, attempt));
9675
+ total += sampleDirectoryBytes(join20(workDir, attempt));
9482
9676
  continue;
9483
9677
  }
9484
9678
  if (!/^capture-attempt-\d+$/.test(attempt)) continue;
9485
- const attemptDir = join19(workDir, attempt);
9679
+ const attemptDir = join20(workDir, attempt);
9486
9680
  for (const name of readdirSync3(attemptDir)) {
9487
- if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join19(attemptDir, name));
9681
+ if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join20(attemptDir, name));
9488
9682
  }
9489
9683
  }
9490
9684
  return total;
@@ -9771,7 +9965,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
9771
9965
 
9772
9966
  // src/services/render/stages/captureHdrStage.ts
9773
9967
  import { existsSync as existsSync14, mkdirSync as mkdirSync7 } from "node:fs";
9774
- import { join as join23 } from "node:path";
9968
+ import { join as join24 } from "node:path";
9775
9969
  import {
9776
9970
  closeCaptureSession as closeCaptureSession7,
9777
9971
  cloneCaptureWarnings as cloneCaptureWarnings2,
@@ -9843,7 +10037,7 @@ function createHdrImageTransferCache(options = {}) {
9843
10037
  }
9844
10038
 
9845
10039
  // src/services/render/stages/captureHdrSequentialLoop.ts
9846
- import { join as join20 } from "node:path";
10040
+ import { join as join21 } from "node:path";
9847
10041
  import {
9848
10042
  TRANSITIONS,
9849
10043
  crossfade
@@ -9968,7 +10162,7 @@ async function runSequentialLayeredFrameLoop(input) {
9968
10162
  );
9969
10163
  if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
9970
10164
  writeFileExclusiveSync(
9971
- join20(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
10165
+ join21(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
9972
10166
  normalCanvas
9973
10167
  );
9974
10168
  }
@@ -10000,7 +10194,7 @@ async function runSequentialLayeredFrameLoop(input) {
10000
10194
  }
10001
10195
 
10002
10196
  // src/services/render/stages/captureHdrHybridLoop.ts
10003
- import { join as join22 } from "node:path";
10197
+ import { join as join23 } from "node:path";
10004
10198
  import {
10005
10199
  TRANSITIONS as TRANSITIONS2,
10006
10200
  closeCaptureSession as closeCaptureSession6,
@@ -10014,7 +10208,7 @@ import {
10014
10208
  // src/services/shaderTransitionWorkerPool.ts
10015
10209
  import { Worker } from "node:worker_threads";
10016
10210
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
10017
- import { dirname as dirname8, join as join21 } from "node:path";
10211
+ import { dirname as dirname8, join as join22 } from "node:path";
10018
10212
  import { createRequire } from "node:module";
10019
10213
  import { existsSync as existsSync13 } from "node:fs";
10020
10214
  import { cpus as cpus2 } from "node:os";
@@ -10028,9 +10222,9 @@ function resolveWorkerEntry(explicit) {
10028
10222
  return { path: override, isTs };
10029
10223
  }
10030
10224
  const moduleDir = dirname8(fileURLToPath3(import.meta.url));
10031
- const jsPath = join21(moduleDir, "shaderTransitionWorker.js");
10225
+ const jsPath = join22(moduleDir, "shaderTransitionWorker.js");
10032
10226
  if (existsSync13(jsPath)) return { path: jsPath, isTs: false };
10033
- const tsPath = join21(moduleDir, "shaderTransitionWorker.ts");
10227
+ const tsPath = join22(moduleDir, "shaderTransitionWorker.ts");
10034
10228
  return { path: tsPath, isTs: true };
10035
10229
  }
10036
10230
  function buildExecArgv(entryIsTs) {
@@ -10398,7 +10592,7 @@ async function runHybridLayeredFrameLoop(input) {
10398
10592
  );
10399
10593
  if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
10400
10594
  writeFileExclusiveSync(
10401
- join22(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
10595
+ join23(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
10402
10596
  canvas
10403
10597
  );
10404
10598
  }
@@ -10567,7 +10761,8 @@ async function runCaptureHdrStage(input) {
10567
10761
  width,
10568
10762
  height,
10569
10763
  abortSignal,
10570
- hdrDiagnostics
10764
+ hdrDiagnostics,
10765
+ maxResidentWindows: Math.max(1, workerCount) + 1
10571
10766
  });
10572
10767
  releaseHdrExtractionReservation = extracted.releaseReservation;
10573
10768
  for (const [id, source] of extracted.sources) hdrVideoFrameSources.set(id, source);
@@ -10585,7 +10780,7 @@ async function runCaptureHdrStage(input) {
10585
10780
  if (hdrVideoFrameSources.has(v.id)) hdrVideoEndTimes.set(v.id, v.end);
10586
10781
  }
10587
10782
  const debugDumpEnabled = process.env.KEEP_TEMP === "1";
10588
- const debugDumpDir = debugDumpEnabled ? join23(framesDir, "debug-composite") : null;
10783
+ const debugDumpDir = debugDumpEnabled ? join24(framesDir, "debug-composite") : null;
10589
10784
  if (debugDumpDir && !existsSync14(debugDumpDir)) {
10590
10785
  mkdirSync7(debugDumpDir, { recursive: true });
10591
10786
  }
@@ -10745,12 +10940,12 @@ import {
10745
10940
  mkdirSync as mkdirSync8,
10746
10941
  readdirSync as readdirSync4,
10747
10942
  readFileSync as readFileSync7,
10748
- rmSync as rmSync9,
10749
- statSync as statSync5,
10943
+ rmSync as rmSync10,
10944
+ statSync as statSync6,
10750
10945
  writeFileSync as writeFileSync4
10751
10946
  } from "node:fs";
10752
10947
  import { readFile as readFile2 } from "node:fs/promises";
10753
- import { dirname as dirname9, join as join24 } from "node:path";
10948
+ import { dirname as dirname9, join as join25 } from "node:path";
10754
10949
  import {
10755
10950
  encodeFramesChunkedConcat,
10756
10951
  encodeFramesFromDir,
@@ -10875,7 +11070,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
10875
11070
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
10876
11071
  if (failure) return failure;
10877
11072
  }
10878
- const fileSize = existsSync15(outputPath) ? statSync5(outputPath).size : 0;
11073
+ const fileSize = existsSync15(outputPath) ? statSync6(outputPath).size : 0;
10879
11074
  return {
10880
11075
  success: true,
10881
11076
  outputPath,
@@ -10884,7 +11079,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
10884
11079
  fileSize
10885
11080
  };
10886
11081
  } finally {
10887
- rmSync9(input.palettePath, { force: true });
11082
+ rmSync10(input.palettePath, { force: true });
10888
11083
  }
10889
11084
  }
10890
11085
  function startEncodeProgress(job, stage, onProgress) {
@@ -10959,7 +11154,7 @@ function encodeWhileCapturing(input, totalFrames) {
10959
11154
  await new Promise((resolve12) => wake = resolve12);
10960
11155
  }
10961
11156
  if (signal.aborted) return;
10962
- const mergedPath = join24(input.framesDir, formatCaptureFrameName(next, "jpg"));
11157
+ const mergedPath = join25(input.framesDir, formatCaptureFrameName(next, "jpg"));
10963
11158
  const frame = await readLandedFrame(landed.get(next), mergedPath);
10964
11159
  landed.delete(next);
10965
11160
  if (!await stream.writeFrame(frame)) {
@@ -11042,11 +11237,11 @@ async function runEncodeStage(input) {
11042
11237
  );
11043
11238
  }
11044
11239
  captured.forEach((name, i) => {
11045
- const dst = join24(outputPath, formatExportFrameName(i, "png"));
11046
- copyFileSync3(join24(framesDir, name), dst);
11240
+ const dst = join25(outputPath, formatExportFrameName(i, "png"));
11241
+ copyFileSync3(join25(framesDir, name), dst);
11047
11242
  });
11048
11243
  if (hasAudio && audioOutputPath && existsSync15(audioOutputPath)) {
11049
- copyFileSync3(audioOutputPath, join24(outputPath, MIXED_AUDIO_FILENAME2));
11244
+ copyFileSync3(audioOutputPath, join25(outputPath, MIXED_AUDIO_FILENAME2));
11050
11245
  log.info(
11051
11246
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
11052
11247
  );
@@ -11066,7 +11261,7 @@ async function runEncodeStage(input) {
11066
11261
  const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
11067
11262
  fps: job.config.fps,
11068
11263
  loop,
11069
- palettePath: join24(dirname9(videoOnlyPath), "gif-palette.png"),
11264
+ palettePath: join25(dirname9(videoOnlyPath), "gif-palette.png"),
11070
11265
  preserveAlpha: needsAlpha,
11071
11266
  signal: abortSignal,
11072
11267
  timeout: engineCfg.ffmpegEncodeTimeout
@@ -11279,9 +11474,9 @@ function sampleDirectoryBytes(dir) {
11279
11474
  continue;
11280
11475
  }
11281
11476
  for (const name of entries) {
11282
- const full = join25(current, name);
11477
+ const full = join26(current, name);
11283
11478
  try {
11284
- const st = statSync6(full);
11479
+ const st = statSync7(full);
11285
11480
  if (st.isDirectory()) {
11286
11481
  stack.push(full);
11287
11482
  } else if (st.isFile()) {
@@ -11428,8 +11623,8 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
11428
11623
  const ranges = [];
11429
11624
  let rangeStart = null;
11430
11625
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
11431
- const framePath = join25(framesDir, formatCaptureFrameName(frameIndex, frameExt));
11432
- const missing = !existsSync16(framePath) || statSync6(framePath).size <= 8;
11626
+ const framePath = join26(framesDir, formatCaptureFrameName(frameIndex, frameExt));
11627
+ const missing = !existsSync16(framePath) || statSync7(framePath).size <= 8;
11433
11628
  if (missing && rangeStart === null) {
11434
11629
  rangeStart = frameIndex;
11435
11630
  } else if (!missing && rangeStart !== null) {
@@ -11451,7 +11646,7 @@ function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, ran
11451
11646
  workerId,
11452
11647
  startFrame: rangeStart + range.startFrame,
11453
11648
  endFrame: rangeStart + range.endFrame,
11454
- outputDir: join25(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`),
11649
+ outputDir: join26(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`),
11455
11650
  outputFrameOffset: rangeStart
11456
11651
  }));
11457
11652
  batches.push(batch);
@@ -11479,8 +11674,8 @@ function getNextRetryWorkerCount(currentWorkers) {
11479
11674
  return Math.max(1, Math.floor(currentWorkers / 2));
11480
11675
  }
11481
11676
  function resolveRenderWorkDirPrefix(outputPath, jobId, platform2 = process.platform, systemTempDir = tmpdir2()) {
11482
- if (platform2 === "win32" || platform2 === "darwin") return join25(systemTempDir, "hf-render-");
11483
- return join25(dirname10(outputPath), `work-${jobId}-`);
11677
+ if (platform2 === "win32" || platform2 === "darwin") return join26(systemTempDir, "hf-render-");
11678
+ return join26(dirname10(outputPath), `work-${jobId}-`);
11484
11679
  }
11485
11680
  var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
11486
11681
  function isTransientCaptureRetryEligible(failureKind, missing, retriesUsed) {
@@ -11512,7 +11707,7 @@ The composition is too large for the available memory. To reduce memory pressure
11512
11707
  function countCapturedFrames(totalFrames, framesDir, frameExt) {
11513
11708
  let captured = 0;
11514
11709
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
11515
- const framePath = join25(framesDir, formatCaptureFrameName(frameIndex, frameExt));
11710
+ const framePath = join26(framesDir, formatCaptureFrameName(frameIndex, frameExt));
11516
11711
  if (existsSync16(framePath)) captured++;
11517
11712
  }
11518
11713
  return captured;
@@ -11538,7 +11733,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
11538
11733
  reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
11539
11734
  });
11540
11735
  pendingTransientRetry = false;
11541
- const attemptWorkDir = join25(options.workDir, `capture-attempt-${attempt}`);
11736
+ const attemptWorkDir = join26(options.workDir, `capture-attempt-${attempt}`);
11542
11737
  const batches = missingRanges ? buildMissingFrameRetryBatches(
11543
11738
  missingRanges,
11544
11739
  currentWorkers,
@@ -11995,7 +12190,7 @@ function resolveDeShortBand(args) {
11995
12190
  return args.bandOpen ? "applied" : "skipped_elements";
11996
12191
  }
11997
12192
  function shouldPreferSingleWorkerDrawElement(args) {
11998
- return args.workerCount > 1 && typeof args.requestedWorkers !== "number" && args.useDrawElement && !args.deCompileGate && !args.forceScreenshot && outputUsesH264Pipeline(args.outputFormat) && args.minFrames > 0 && args.totalFrames >= args.minFrames && args.singleWorkerStreamingOk && !args.layeredOrEffectRoute && !args.supersampling && !args.probeDeGated && !args.experimentalParallelDeOptIn;
12193
+ return args.workerCount > 1 && args.useDrawElement && !args.deCompileGate && !args.forceScreenshot && outputUsesH264Pipeline(args.outputFormat) && args.minFrames > 0 && args.totalFrames >= args.minFrames && args.singleWorkerStreamingOk && !args.layeredOrEffectRoute && !args.supersampling && !args.probeDeGated && !args.experimentalParallelDeOptIn;
11999
12194
  }
12000
12195
  function isProbeDeGated(session) {
12001
12196
  return session !== null && session.captureMode !== "drawelement" && !session.deInitDeferred;
@@ -12131,17 +12326,17 @@ function deVerifyFallbackTelemetry(err) {
12131
12326
  }
12132
12327
  function resolveRenderDebugDir() {
12133
12328
  const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve7(process.env.PRODUCER_RENDERS_DIR, "..") : resolve7(dirname10(fileURLToPath4(import.meta.url)), "../..");
12134
- return join25(producerRoot, ".debug");
12329
+ return join26(producerRoot, ".debug");
12135
12330
  }
12136
12331
  async function executeRenderJob(job, projectDir, outputPath, progressSink, abortSignal, assertRenderActive) {
12137
12332
  validateHlsRenderConfig(job.config);
12138
12333
  const debugDir = resolveRenderDebugDir();
12139
12334
  const outputDir = dirname10(outputPath);
12140
12335
  if (!existsSync16(outputDir)) mkdirSync9(outputDir, { recursive: true });
12141
- const workDir = job.config.debug ? join25(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
12336
+ const workDir = job.config.debug ? join26(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
12142
12337
  const pipelineStart = Date.now();
12143
12338
  const baseLog = job.config.logger ?? defaultLogger;
12144
- const logPath = job.config.debug ? join25(workDir, "render.log") : null;
12339
+ const logPath = job.config.debug ? join26(workDir, "render.log") : null;
12145
12340
  const execution = new RenderExecutionContext({
12146
12341
  request: { renderJobId: job.id, projectDir, outputPath },
12147
12342
  logger: logPath ? createRenderFileLogger(logPath, baseLog) : baseLog,
@@ -12155,7 +12350,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
12155
12350
  log.info("KEEP_TEMP=1, leaving workDir on disk for inspection", { workDir });
12156
12351
  return;
12157
12352
  }
12158
- rmSync10(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
12353
+ rmSync11(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
12159
12354
  });
12160
12355
  try {
12161
12356
  await executeRenderPipeline({
@@ -12186,7 +12381,9 @@ async function executeRenderPipeline(input) {
12186
12381
  const log = execution.logger;
12187
12382
  const eventPublisher = execution.events;
12188
12383
  const onProgress = execution.onProgress;
12189
- const executionSignal = execution.signal;
12384
+ const tooSlow = new AbortController();
12385
+ let tooSlowError;
12386
+ const executionSignal = job.config.maxRenderMs ? AbortSignal.any([tooSlow.signal, ...execution.signal ? [execution.signal] : []]) : execution.signal;
12190
12387
  let fileServer = null;
12191
12388
  let probeSession = null;
12192
12389
  let lastBrowserConsole = [];
@@ -12199,7 +12396,7 @@ async function executeRenderPipeline(input) {
12199
12396
  imageDecodeFailures: 0
12200
12397
  };
12201
12398
  let hdrPerf;
12202
- const perfOutputPath = join25(workDir, "perf-summary.json");
12399
+ const perfOutputPath = join26(workDir, "perf-summary.json");
12203
12400
  const cfg = { ...job.config.producerConfig ?? resolveConfig2() };
12204
12401
  const observability = new RenderObservabilityRecorder({
12205
12402
  pipelineStartMs: pipelineStart,
@@ -12301,15 +12498,15 @@ async function executeRenderPipeline(input) {
12301
12498
  requestedWorkers: job.config.workers ?? "auto"
12302
12499
  });
12303
12500
  const entryFile = job.config.entryFile || "index.html";
12304
- let htmlPath = join25(projectDir, entryFile);
12501
+ let htmlPath = join26(projectDir, entryFile);
12305
12502
  if (!existsSync16(htmlPath)) {
12306
12503
  throw new Error(`Entry file not found: ${htmlPath}`);
12307
12504
  }
12308
12505
  assertNotAborted();
12309
12506
  const rawEntry = readFileSync8(htmlPath, "utf-8");
12310
12507
  if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
12311
- const wrapperPath = join25(workDir, "standalone-entry.html");
12312
- const projectIndexPath = join25(projectDir, "index.html");
12508
+ const wrapperPath = join26(workDir, "standalone-entry.html");
12509
+ const projectIndexPath = join26(projectDir, "index.html");
12313
12510
  if (!existsSync16(projectIndexPath)) {
12314
12511
  throw new Error(
12315
12512
  `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
@@ -12472,7 +12669,7 @@ async function executeRenderPipeline(input) {
12472
12669
  updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress, {
12473
12670
  code: "extract_video"
12474
12671
  });
12475
- const compiledDir = join25(workDir, "compiled");
12672
+ const compiledDir = join26(workDir, "compiled");
12476
12673
  const extractResult = await observeRenderStage(
12477
12674
  observability,
12478
12675
  "video_extract",
@@ -12606,7 +12803,7 @@ async function executeRenderPipeline(input) {
12606
12803
  try {
12607
12804
  fileServer = await createFileServer({
12608
12805
  projectDir,
12609
- compiledDir: join25(workDir, "compiled"),
12806
+ compiledDir: join26(workDir, "compiled"),
12610
12807
  port: 0,
12611
12808
  preHeadScripts: [VIRTUAL_TIME_SHIM],
12612
12809
  fps: job.config.fps
@@ -12624,7 +12821,7 @@ async function executeRenderPipeline(input) {
12624
12821
  if (!activeFileServer) {
12625
12822
  throw new Error("File server failed to initialize before frame capture");
12626
12823
  }
12627
- const framesDir = join25(workDir, "captured-frames");
12824
+ const framesDir = join26(workDir, "captured-frames");
12628
12825
  if (!existsSync16(framesDir)) mkdirSync9(framesDir, { recursive: true });
12629
12826
  updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
12630
12827
  code: "start_browsers"
@@ -12728,7 +12925,6 @@ async function executeRenderPipeline(input) {
12728
12925
  const WOULD_RESOLVE_MULTI_WORKER = 2;
12729
12926
  const deInversionArgs = {
12730
12927
  workerCount: WOULD_RESOLVE_MULTI_WORKER,
12731
- requestedWorkers: job.config.workers,
12732
12928
  useDrawElement: cfg.useDrawElement,
12733
12929
  deCompileGate,
12734
12930
  forceScreenshot: captureForceScreenshot,
@@ -12934,7 +13130,7 @@ async function executeRenderPipeline(input) {
12934
13130
  } else if (deInversionEligible && workerCount > 1) {
12935
13131
  deWorkerInversion = "inverted";
12936
13132
  log.info(
12937
- `[Render] Fast capture: single-worker drawElement streaming preferred over ${workerCount}-worker screenshot capture (${totalFrames} frames >= ${deInversionEffectiveMinFrames}; verified path, measured faster at every worker count). Set HF_DE_SINGLE_MIN_FRAMES=0 or --workers N to override.`
13133
+ `[Render] Fast capture: single-worker drawElement streaming preferred over ${workerCount}-worker screenshot capture (${totalFrames} frames >= ${deInversionEffectiveMinFrames}; verified path, measured faster at every worker count). Set HF_DE_SINGLE_MIN_FRAMES=0 to override.`
12938
13134
  );
12939
13135
  workerCount = 1;
12940
13136
  }
@@ -12978,6 +13174,27 @@ async function executeRenderPipeline(input) {
12978
13174
  deWorkerInversion: deWorkerInversion ?? "none",
12979
13175
  deParallelRouter: deParallelRouter ?? "none"
12980
13176
  });
13177
+ const calibratedTooSlow = checkRenderEta("capture_calibration", {
13178
+ maxRenderMs: job.config.maxRenderMs,
13179
+ elapsedMs: Date.now() - pipelineStart,
13180
+ framesCompleted: 0,
13181
+ totalFrames,
13182
+ msPerFrame: (captureCalibration?.estimate.p95Ms ?? 0) / workerCount
13183
+ });
13184
+ if (calibratedTooSlow) throw calibratedTooSlow;
13185
+ execution.defer(
13186
+ "stop render ETA guard",
13187
+ startRenderEtaGuard({
13188
+ maxRenderMs: job.config.maxRenderMs,
13189
+ job,
13190
+ totalFrames,
13191
+ pipelineStartMs: pipelineStart,
13192
+ onTooSlow: (error) => {
13193
+ tooSlowError = error;
13194
+ tooSlow.abort(error);
13195
+ }
13196
+ })
13197
+ );
12981
13198
  let useStreamingEncode = shouldUseStreamingEncode(
12982
13199
  cfg,
12983
13200
  outputFormat,
@@ -13071,7 +13288,7 @@ async function executeRenderPipeline(input) {
13071
13288
  hls: ".mp4"
13072
13289
  };
13073
13290
  const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
13074
- const videoOnlyPath = join25(workDir, `video-only${videoExt}`);
13291
+ const videoOnlyPath = join26(workDir, `video-only${videoExt}`);
13075
13292
  const usePageSideCompositingForTransitions = (cfg.enablePageSideCompositing || isGif) && compiled.hasShaderTransitions && !hasHdrContent && outputSupportsPageSideShaderCompositing(outputFormat);
13076
13293
  if (usePageSideCompositingForTransitions) {
13077
13294
  activeFileServer.addPreHeadScript(HF_PAGE_SIDE_COMPOSITING_STUB);
@@ -13317,7 +13534,7 @@ async function executeRenderPipeline(input) {
13317
13534
  pageMotionBlur: job.config.pageMotionBlur,
13318
13535
  captureQuality: captureOptions.quality
13319
13536
  });
13320
- const segmentDir = segmentDirFor(join25(projectDir, "renders"), segmentPlanHash);
13537
+ const segmentDir = segmentDirFor(join26(projectDir, "renders"), segmentPlanHash);
13321
13538
  let completedSegments = /* @__PURE__ */ new Set();
13322
13539
  if (job.config.resumeSegments === true) {
13323
13540
  const existing = readSegmentManifest(segmentDir);
@@ -13332,10 +13549,10 @@ async function executeRenderPipeline(input) {
13332
13549
  });
13333
13550
  }
13334
13551
  if (completedSegments.size === 0) {
13335
- rmSync10(segmentDir, { recursive: true, force: true });
13552
+ rmSync11(segmentDir, { recursive: true, force: true });
13336
13553
  }
13337
13554
  } else {
13338
- rmSync10(segmentDir, { recursive: true, force: true });
13555
+ rmSync11(segmentDir, { recursive: true, force: true });
13339
13556
  }
13340
13557
  const segmentManifest = readSegmentManifest(segmentDir) ?? {
13341
13558
  version: 1,
@@ -13416,7 +13633,7 @@ async function executeRenderPipeline(input) {
13416
13633
  );
13417
13634
  updateCaptureObservability({ segmentRetries: segmentedRes.segmentRetries });
13418
13635
  if (job.config.keepSegments !== true) {
13419
- rmSync10(segmentDir, { recursive: true, force: true });
13636
+ rmSync11(segmentDir, { recursive: true, force: true });
13420
13637
  }
13421
13638
  } else {
13422
13639
  capturePlan = replanAfterFailure(capturePlan, { kind: "streaming_unavailable" });
@@ -13685,7 +13902,7 @@ async function executeRenderPipeline(input) {
13685
13902
  "capture_disk",
13686
13903
  "drawElement capture failed; retrying with a fresh screenshot session"
13687
13904
  );
13688
- rmSync10(framesDir, { recursive: true, force: true });
13905
+ rmSync11(framesDir, { recursive: true, force: true });
13689
13906
  mkdirSync9(framesDir, { recursive: true });
13690
13907
  resetCaptureAttemptProgress(job);
13691
13908
  dedupPerfs.length = 0;
@@ -13872,7 +14089,7 @@ async function executeRenderPipeline(input) {
13872
14089
  }
13873
14090
  if (job.config.debug) {
13874
14091
  if (!isPngSequence && !isHls && existsSync16(stagedOutputPath)) {
13875
- const debugOutput = join25(workDir, `output${videoExt}`);
14092
+ const debugOutput = join26(workDir, `output${videoExt}`);
13876
14093
  copyFileSync4(stagedOutputPath, debugOutput);
13877
14094
  }
13878
14095
  }
@@ -13883,8 +14100,9 @@ async function executeRenderPipeline(input) {
13883
14100
  job.outputPath = outputPath;
13884
14101
  updateJobStatus(job, "complete", "Render complete", 100, onProgress);
13885
14102
  await eventPublisher.flush();
13886
- } catch (error) {
13887
- if (error instanceof RenderCancelledError || executionSignal?.aborted) {
14103
+ } catch (caught) {
14104
+ const error = tooSlowError ?? caught;
14105
+ if (!tooSlowError && (error instanceof RenderCancelledError || executionSignal?.aborted)) {
13888
14106
  job.error = error instanceof Error ? error.message : "render_cancelled";
13889
14107
  updateJobStatus(job, "cancelled", "Render cancelled", job.progress, onProgress);
13890
14108
  await eventPublisher.flush();
@@ -14001,10 +14219,10 @@ import {
14001
14219
  existsSync as existsSync21,
14002
14220
  linkSync,
14003
14221
  mkdirSync as mkdirSync13,
14004
- rmSync as rmSync12,
14222
+ rmSync as rmSync13,
14005
14223
  writeFileSync as writeFileSync8
14006
14224
  } from "node:fs";
14007
- import { extname as extname6, isAbsolute as isAbsolute3, join as join33, relative as relative5, sep as sep2 } from "node:path";
14225
+ import { extname as extname6, isAbsolute as isAbsolute3, join as join34, relative as relative5, sep as sep2 } from "node:path";
14008
14226
  import {
14009
14227
  fpsToNumber as fpsToNumber10,
14010
14228
  HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
@@ -14019,7 +14237,7 @@ import {
14019
14237
  } from "@hyperframes/engine";
14020
14238
 
14021
14239
  // src/services/distributed/planFrameCost.ts
14022
- import { join as join28 } from "node:path";
14240
+ import { join as join29 } from "node:path";
14023
14241
  import {
14024
14242
  closeCaptureSession as closeCaptureSession9,
14025
14243
  createCaptureSession as createCaptureSession9,
@@ -14029,7 +14247,7 @@ import {
14029
14247
 
14030
14248
  // src/services/distributed/chunkFrames.ts
14031
14249
  import { existsSync as existsSync18, readdirSync as readdirSync6 } from "node:fs";
14032
- import { extname as extname4, join as join27 } from "node:path";
14250
+ import { extname as extname4, join as join28 } from "node:path";
14033
14251
  import {
14034
14252
  compositionRequiresWebGpu as compositionRequiresWebGpu3,
14035
14253
  createFrameLookupTable as createFrameLookupTable2,
@@ -14039,7 +14257,7 @@ import {
14039
14257
 
14040
14258
  // src/services/distributed/shared.ts
14041
14259
  import { execFile as execFileCallback } from "node:child_process";
14042
- import { dirname as dirname11, join as join26 } from "node:path";
14260
+ import { dirname as dirname11, join as join27 } from "node:path";
14043
14261
  import { existsSync as existsSync17, readFileSync as readFileSync9 } from "node:fs";
14044
14262
  import { fileURLToPath as fileURLToPath5 } from "node:url";
14045
14263
  import { promisify as promisify2 } from "node:util";
@@ -14055,7 +14273,7 @@ function isPlanAudioArtifactPath(path) {
14055
14273
  }
14056
14274
  function resolvePlanAudioPath(planDir) {
14057
14275
  for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
14058
- const candidate = join26(planDir, name);
14276
+ const candidate = join27(planDir, name);
14059
14277
  if (existsSync17(candidate)) return candidate;
14060
14278
  }
14061
14279
  return null;
@@ -14315,7 +14533,7 @@ function readProducerVersion() {
14315
14533
  const startDir = dirname11(fileURLToPath5(import.meta.url));
14316
14534
  let current = startDir;
14317
14535
  for (let i = 0; i < 10; i++) {
14318
- const candidate = join26(current, "package.json");
14536
+ const candidate = join27(current, "package.json");
14319
14537
  if (existsSync17(candidate)) {
14320
14538
  try {
14321
14539
  const pkg = JSON.parse(readFileSync9(candidate, "utf-8"));
@@ -14369,7 +14587,7 @@ function frameNumberFromFileName(name) {
14369
14587
  function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
14370
14588
  const result = [];
14371
14589
  for (const v of videos) {
14372
- const outputDir = join27(planDir, "video-frames", v.videoId);
14590
+ const outputDir = join28(planDir, "video-frames", v.videoId);
14373
14591
  if (!existsSync18(outputDir)) {
14374
14592
  throw new Error(
14375
14593
  `[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
@@ -14388,7 +14606,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
14388
14606
  if (!frameName) continue;
14389
14607
  const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
14390
14608
  const frameIndex = frameNumber === null ? i : frameNumber - 1;
14391
- framePaths.set(frameIndex, join27(outputDir, frameName));
14609
+ framePaths.set(frameIndex, join28(outputDir, frameName));
14392
14610
  }
14393
14611
  result.push({
14394
14612
  videoId: v.videoId,
@@ -14478,14 +14696,14 @@ async function extractDeferredVideoFramesForChunk(input) {
14478
14696
  const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
14479
14697
  return Promise.all(
14480
14698
  deferred.map(async (video) => {
14481
- const outputDir = join27(input.outputRoot, video.videoId);
14699
+ const outputDir = join28(input.outputRoot, video.videoId);
14482
14700
  const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
14483
14701
  if (indexes.length === 0) {
14484
14702
  return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
14485
14703
  }
14486
14704
  const range = video.deferredRange;
14487
14705
  const remoteUrl = remoteSources[range.sourcePath];
14488
- const sourcePath = remoteUrl ?? join27(input.planDir, ...range.sourcePath.split("/"));
14706
+ const sourcePath = remoteUrl ?? join28(input.planDir, ...range.sourcePath.split("/"));
14489
14707
  if (remoteUrl === void 0 && !existsSync18(sourcePath)) {
14490
14708
  throw new RenderChunkValidationError(
14491
14709
  MISSING_PLAN_ARTIFACT,
@@ -14534,12 +14752,12 @@ async function measurePlanSecondsPerFrame(input) {
14534
14752
  planVideos,
14535
14753
  frames: sampled,
14536
14754
  fps: fpsRatio,
14537
- outputRoot: join28(workDir, "frame-cost-video-frames"),
14755
+ outputRoot: join29(workDir, "frame-cost-video-frames"),
14538
14756
  cfg: input.cfg
14539
14757
  })
14540
14758
  ];
14541
14759
  const lookup = extracted.length > 0 ? createFrameLookupTable3(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
14542
- const compiledDir = join28(planDir, "compiled");
14760
+ const compiledDir = join29(planDir, "compiled");
14543
14761
  const fileServer = await createFileServer({
14544
14762
  projectDir: compiledDir,
14545
14763
  compiledDir,
@@ -14551,7 +14769,7 @@ async function measurePlanSecondsPerFrame(input) {
14551
14769
  try {
14552
14770
  session = await createCaptureSession9(
14553
14771
  fileServer.url,
14554
- join28(workDir, "frame-cost"),
14772
+ join29(workDir, "frame-cost"),
14555
14773
  buildChunkCaptureOptions({
14556
14774
  dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
14557
14775
  encoder: input,
@@ -14637,7 +14855,7 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
14637
14855
 
14638
14856
  // src/services/render/stages/freezePlan.ts
14639
14857
  import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync10, readdirSync as readdirSync7, writeFileSync as writeFileSync6 } from "node:fs";
14640
- import { join as join29, relative as relative3, resolve as resolve8 } from "node:path";
14858
+ import { join as join30, relative as relative3, resolve as resolve8 } from "node:path";
14641
14859
 
14642
14860
  // src/services/distributed/planProtocol.ts
14643
14861
  var PLAN_SCHEMA_VERSION = 1;
@@ -14736,7 +14954,7 @@ function readPlanProtocolV1(planJson, capabilities = DISTRIBUTED_RENDER_CAPABILI
14736
14954
 
14737
14955
  // src/services/render/stages/planHash.ts
14738
14956
  import { createHash as createHash6 } from "node:crypto";
14739
- import { closeSync as closeSync5, createReadStream as createReadStream3, openSync as openSync4, readSync as readSync3 } from "node:fs";
14957
+ import { closeSync as closeSync4, createReadStream as createReadStream3, openSync as openSync4, readSync as readSync3 } from "node:fs";
14740
14958
  var PLAN_HASH_SCHEMA_PREFIX = "hyperframes-plan-hash-v1\0";
14741
14959
  var FIELD_DELIMITER = Buffer.from([0]);
14742
14960
  function computePlanHash(input) {
@@ -14802,7 +15020,7 @@ function sha256File(path) {
14802
15020
  if (bytesRead > 0) hash.update(buffer.subarray(0, bytesRead));
14803
15021
  } while (bytesRead > 0);
14804
15022
  } finally {
14805
- closeSync5(fd);
15023
+ closeSync4(fd);
14806
15024
  }
14807
15025
  return hash.digest("hex");
14808
15026
  }
@@ -14855,7 +15073,7 @@ function listPlanFiles(planDir) {
14855
15073
  function walk(dir) {
14856
15074
  const entries = readdirSync7(dir, { withFileTypes: true });
14857
15075
  for (const entry of entries) {
14858
- const full = join29(dir, entry.name);
15076
+ const full = join30(dir, entry.name);
14859
15077
  if (entry.isDirectory()) {
14860
15078
  walk(full);
14861
15079
  } else if (entry.isFile()) {
@@ -14896,15 +15114,15 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
14896
15114
  function writePlanRemoteFiles(planDir, files) {
14897
15115
  if (files.length === 0) return;
14898
15116
  const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
14899
- writeFileSync6(join29(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
15117
+ writeFileSync6(join30(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
14900
15118
  }
14901
15119
  function readPlanRemoteFiles(planDir) {
14902
- const path = join29(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
15120
+ const path = join30(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
14903
15121
  return existsSync19(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
14904
15122
  }
14905
15123
  function recomputePlanHashFromPlanDir(planDir, digests) {
14906
- const planJsonPath = join29(planDir, "plan.json");
14907
- const encoderJsonPath = join29(planDir, "meta", "encoder.json");
15124
+ const planJsonPath = join30(planDir, "plan.json");
15125
+ const encoderJsonPath = join30(planDir, "meta", "encoder.json");
14908
15126
  if (!existsSync19(planJsonPath)) {
14909
15127
  throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
14910
15128
  }
@@ -14941,18 +15159,18 @@ async function freezePlan(input) {
14941
15159
  if (!existsSync19(planDir)) {
14942
15160
  throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
14943
15161
  }
14944
- const metaDir = join29(planDir, "meta");
15162
+ const metaDir = join30(planDir, "meta");
14945
15163
  if (!existsSync19(metaDir)) mkdirSync10(metaDir, { recursive: true });
14946
15164
  writeFileSync6(
14947
- join29(metaDir, "composition.json"),
15165
+ join30(metaDir, "composition.json"),
14948
15166
  `${JSON.stringify(composition, null, 2)}
14949
15167
  `,
14950
15168
  "utf-8"
14951
15169
  );
14952
15170
  const encoderForCanonical = stripUndefined(encoder);
14953
15171
  const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
14954
- writeFileSync6(join29(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
14955
- writeFileSync6(join29(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
15172
+ writeFileSync6(join30(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
15173
+ writeFileSync6(join30(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
14956
15174
  `, "utf-8");
14957
15175
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
14958
15176
  const planHash = computePlanHash({
@@ -14976,7 +15194,7 @@ async function freezePlan(input) {
14976
15194
  duration: durationSeconds,
14977
15195
  hasAudio
14978
15196
  };
14979
- const planJsonPath = join29(planDir, "plan.json");
15197
+ const planJsonPath = join30(planDir, "plan.json");
14980
15198
  writeFileSync6(planJsonPath, `${JSON.stringify(planJson, null, 2)}
14981
15199
  `, "utf-8");
14982
15200
  return { planJsonPath, planHash };
@@ -15018,16 +15236,16 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
15018
15236
  }
15019
15237
 
15020
15238
  // src/services/distributed/chunkAudio.ts
15021
- import { closeSync as closeSync6, existsSync as existsSync20, ftruncateSync, mkdirSync as mkdirSync12, openSync as openSync5, readFileSync as readFileSync11 } from "node:fs";
15239
+ import { closeSync as closeSync5, existsSync as existsSync20, ftruncateSync, mkdirSync as mkdirSync12, openSync as openSync5, readFileSync as readFileSync11 } from "node:fs";
15022
15240
  import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
15023
- import { join as join31 } from "node:path";
15241
+ import { join as join32 } from "node:path";
15024
15242
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
15025
15243
 
15026
15244
  // src/services/distributed/mp4Stitch.ts
15027
- import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync4, rmSync as rmSync11 } from "node:fs";
15245
+ import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync4, rmSync as rmSync12 } from "node:fs";
15028
15246
  import { open } from "node:fs/promises";
15029
15247
  import { renameWithRetry as renameWithRetry5 } from "@hyperframes/engine";
15030
- import { dirname as dirname12, join as join30 } from "node:path";
15248
+ import { dirname as dirname12, join as join31 } from "node:path";
15031
15249
  import { finished, pipeline } from "node:stream/promises";
15032
15250
  function boxAt(src, off, at, limit) {
15033
15251
  let size = src.readUInt32BE(off);
@@ -15393,16 +15611,16 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
15393
15611
  const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
15394
15612
  mkdirSync11(dirname12(outputPath), { recursive: true });
15395
15613
  const workDir = mkdtempSync4(`${outputPath}.stitch-`);
15396
- const out = createWriteStream(join30(workDir, "out.mp4"));
15614
+ const out = createWriteStream(join31(workDir, "out.mp4"));
15397
15615
  try {
15398
15616
  out.write(header);
15399
15617
  for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
15400
15618
  out.end();
15401
15619
  await finished(out);
15402
- renameWithRetry5(join30(workDir, "out.mp4"), outputPath);
15620
+ renameWithRetry5(join31(workDir, "out.mp4"), outputPath);
15403
15621
  } finally {
15404
15622
  out.destroy();
15405
- rmSync11(workDir, { recursive: true, force: true });
15623
+ rmSync12(workDir, { recursive: true, force: true });
15406
15624
  }
15407
15625
  }
15408
15626
 
@@ -15493,7 +15711,7 @@ async function writeChunkAudioSlices(input) {
15493
15711
  });
15494
15712
  const bytes = readFileSync11(input.audioPath);
15495
15713
  const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
15496
- mkdirSync12(join31(input.planDir, SLICE_DIR), { recursive: true });
15714
+ mkdirSync12(join32(input.planDir, SLICE_DIR), { recursive: true });
15497
15715
  input.chunks.forEach((_, index) => {
15498
15716
  const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
15499
15717
  if (!cut) return;
@@ -15501,17 +15719,17 @@ async function writeChunkAudioSlices(input) {
15501
15719
  const start = offsets[cut.firstPacket];
15502
15720
  layout.partStarts[index] = start;
15503
15721
  const end = offsets[last] + track.sizes[last];
15504
- writeFileSync7(join31(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
15722
+ writeFileSync7(join32(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
15505
15723
  });
15506
15724
  const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
15507
- writeFileSync7(join31(input.planDir, SLICE_FRAME), Buffer.concat(frame));
15508
- writeFileSync7(join31(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
15725
+ writeFileSync7(join32(input.planDir, SLICE_FRAME), Buffer.concat(frame));
15726
+ writeFileSync7(join32(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
15509
15727
  }
15510
15728
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
15511
- const layoutPath = join31(planDir, SLICE_LAYOUT);
15729
+ const layoutPath = join32(planDir, SLICE_LAYOUT);
15512
15730
  if (!existsSync20(layoutPath)) return resolvePlanAudioPath(planDir);
15513
15731
  const layout = JSON.parse(readFileSync11(layoutPath, "utf-8"));
15514
- const frame = readFileSync11(join31(planDir, SLICE_FRAME));
15732
+ const frame = readFileSync11(join32(planDir, SLICE_FRAME));
15515
15733
  const fd = openSync5(scratchPath, "w");
15516
15734
  try {
15517
15735
  writeSync(fd, frame, 0, layout.payloadStart, 0);
@@ -15524,12 +15742,12 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
15524
15742
  );
15525
15743
  const start = layout.partStarts[chunkIndex];
15526
15744
  if (start !== void 0) {
15527
- const part = readFileSync11(join31(planDir, chunkAudioSlicePath(chunkIndex)));
15745
+ const part = readFileSync11(join32(planDir, chunkAudioSlicePath(chunkIndex)));
15528
15746
  writeSync(fd, part, 0, part.length, start);
15529
15747
  }
15530
15748
  ftruncateSync(fd, layout.sizeBytes);
15531
15749
  } finally {
15532
- closeSync6(fd);
15750
+ closeSync5(fd);
15533
15751
  }
15534
15752
  return scratchPath;
15535
15753
  }
@@ -15580,7 +15798,7 @@ async function muxChunkWithAudio(input) {
15580
15798
  // src/services/distributed/planSize.ts
15581
15799
  import { createHash as createHash7 } from "node:crypto";
15582
15800
  import { lstatSync as lstatSync2, readdirSync as readdirSync8 } from "node:fs";
15583
- import { extname as extname5, join as join32, relative as relative4 } from "node:path";
15801
+ import { extname as extname5, join as join33, relative as relative4 } from "node:path";
15584
15802
  var MAX_TOP_COMPONENTS = 10;
15585
15803
  var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
15586
15804
  ".aac",
@@ -15692,7 +15910,7 @@ function walkRegularFiles(dir, visit) {
15692
15910
  return;
15693
15911
  }
15694
15912
  for (const entry of entries) {
15695
- const full = join32(dir, entry.name);
15913
+ const full = join33(dir, entry.name);
15696
15914
  if (entry.isDirectory()) {
15697
15915
  walkRegularFiles(full, visit);
15698
15916
  } else if (entry.isFile()) {
@@ -15904,13 +16122,13 @@ function assertPositiveInteger2(name, value) {
15904
16122
  }
15905
16123
  var FREEZE_OWNED_PLAN_FILES = [
15906
16124
  "plan.json",
15907
- join33("meta", "composition.json"),
15908
- join33("meta", "encoder.json"),
15909
- join33("meta", "chunks.json")
16125
+ join34("meta", "composition.json"),
16126
+ join34("meta", "encoder.json"),
16127
+ join34("meta", "chunks.json")
15910
16128
  ];
15911
16129
  function removeFreezeOwnedPlanFiles(planDir) {
15912
16130
  for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
15913
- rmSync12(join33(planDir, relativePath), { force: true });
16131
+ rmSync13(join34(planDir, relativePath), { force: true });
15914
16132
  }
15915
16133
  }
15916
16134
  function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
@@ -16052,19 +16270,19 @@ function createVideoSourceShipper(input) {
16052
16270
  const known = shipped.get(srcPath);
16053
16271
  if (known) return known;
16054
16272
  const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
16055
- const current = stagedRel === null ? srcPath : join33(input.planDir, "video-frames", stagedRel);
16273
+ const current = stagedRel === null ? srcPath : join34(input.planDir, "video-frames", stagedRel);
16056
16274
  let result = null;
16057
16275
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
16058
16276
  const rel = pathInside(root, srcPath);
16059
- if (rel !== null && (existsSync21(join33(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
16277
+ if (rel !== null && (existsSync21(join34(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
16060
16278
  result = ["compiled", ...rel.split(sep2)].join("/");
16061
16279
  break;
16062
16280
  }
16063
16281
  }
16064
16282
  if (result === null) {
16065
16283
  result = `video-sources/${shipped.size}${extname6(current)}`;
16066
- const destination = join33(input.planDir, ...result.split("/"));
16067
- mkdirSync13(join33(input.planDir, "video-sources"), { recursive: true });
16284
+ const destination = join34(input.planDir, ...result.split("/"));
16285
+ mkdirSync13(join34(input.planDir, "video-sources"), { recursive: true });
16068
16286
  try {
16069
16287
  linkSync(current, destination);
16070
16288
  } catch {
@@ -16113,14 +16331,14 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
16113
16331
  variables: config.variables
16114
16332
  });
16115
16333
  const entryFile = config.entryFile ?? "index.html";
16116
- const htmlPath = join33(projectDir, entryFile);
16334
+ const htmlPath = join34(projectDir, entryFile);
16117
16335
  if (!existsSync21(htmlPath)) {
16118
16336
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
16119
16337
  }
16120
- const workDir = join33(planDir, ".plan-work");
16121
- rmSync12(workDir, { recursive: true, force: true });
16338
+ const workDir = join34(planDir, ".plan-work");
16339
+ rmSync13(workDir, { recursive: true, force: true });
16122
16340
  mkdirSync13(workDir, { recursive: true });
16123
- const compiledDir = join33(workDir, "compiled");
16341
+ const compiledDir = join34(workDir, "compiled");
16124
16342
  mkdirSync13(compiledDir, { recursive: true });
16125
16343
  let stageStarted = Date.now();
16126
16344
  cpSync2(projectDir, compiledDir, {
@@ -16134,10 +16352,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
16134
16352
  }
16135
16353
  });
16136
16354
  logPlanStage(log, "copy", stageStarted);
16137
- const finalCompiledDir = join33(planDir, "compiled");
16355
+ const finalCompiledDir = join34(planDir, "compiled");
16138
16356
  const needsAlpha = outputNeedsAlpha(config.format);
16139
16357
  const remoteFiles = Object.entries(options.remoteFiles ?? {});
16140
- const remoteLocalPath = (rel) => join33(projectDir, ...rel.split("/"));
16358
+ const remoteLocalPath = (rel) => join34(projectDir, ...rel.split("/"));
16141
16359
  for (const [, file] of remoteFiles) {
16142
16360
  declareContentAddressedUrl(file.url, file.sha256);
16143
16361
  if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
@@ -16323,17 +16541,17 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16323
16541
  let stageStarted = Date.now();
16324
16542
  const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
16325
16543
  logPlanStage(log, "extract", stageStarted);
16326
- const stagedVideoFrames = join33(compiledDir, "__hyperframes_video_frames");
16327
- const videoFramesDst = join33(planDir, "video-frames");
16328
- if (existsSync21(videoFramesDst)) rmSync12(videoFramesDst, { recursive: true, force: true });
16329
- const videoSourcesDir = join33(planDir, "video-sources");
16330
- rmSync12(videoSourcesDir, { recursive: true, force: true });
16544
+ const stagedVideoFrames = join34(compiledDir, "__hyperframes_video_frames");
16545
+ const videoFramesDst = join34(planDir, "video-frames");
16546
+ if (existsSync21(videoFramesDst)) rmSync13(videoFramesDst, { recursive: true, force: true });
16547
+ const videoSourcesDir = join34(planDir, "video-sources");
16548
+ rmSync13(videoSourcesDir, { recursive: true, force: true });
16331
16549
  if (existsSync21(stagedVideoFrames)) {
16332
16550
  renameWithRetry6(stagedVideoFrames, videoFramesDst);
16333
16551
  } else {
16334
16552
  mkdirSync13(videoFramesDst, { recursive: true });
16335
16553
  }
16336
- if (existsSync21(finalCompiledDir)) rmSync12(finalCompiledDir, { recursive: true, force: true });
16554
+ if (existsSync21(finalCompiledDir)) rmSync13(finalCompiledDir, { recursive: true, force: true });
16337
16555
  renameWithRetry6(compiledDir, finalCompiledDir);
16338
16556
  const shipVideoSource = createVideoSourceShipper({
16339
16557
  planDir,
@@ -16363,7 +16581,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16363
16581
  };
16364
16582
  })
16365
16583
  });
16366
- mkdirSync13(join33(planDir, "meta"), { recursive: true });
16584
+ mkdirSync13(join34(planDir, "meta"), { recursive: true });
16367
16585
  writePlanRemoteFiles(
16368
16586
  planDir,
16369
16587
  remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
@@ -16373,7 +16591,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16373
16591
  }))
16374
16592
  );
16375
16593
  writeFileSync8(
16376
- join33(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
16594
+ join34(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
16377
16595
  JSON.stringify(planVideosJson, null, 2),
16378
16596
  "utf-8"
16379
16597
  );
@@ -16452,7 +16670,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16452
16670
  if (audioResult?.audioError) {
16453
16671
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
16454
16672
  }
16455
- const planAudioPath = join33(planDir, PLAN_AUDIO_RELATIVE_PATH);
16673
+ const planAudioPath = join34(planDir, PLAN_AUDIO_RELATIVE_PATH);
16456
16674
  if (audioResult?.hasAudio && existsSync21(audioResult.audioOutputPath)) {
16457
16675
  renameWithRetry6(audioResult.audioOutputPath, planAudioPath);
16458
16676
  }
@@ -16501,7 +16719,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16501
16719
  format: config.format
16502
16720
  };
16503
16721
  try {
16504
- rmSync12(workDir, { recursive: true, force: true });
16722
+ rmSync13(workDir, { recursive: true, force: true });
16505
16723
  } catch (err) {
16506
16724
  log.warn("[plan] failed to remove temp work dir", {
16507
16725
  workDir,
@@ -16876,6 +17094,7 @@ function assertRequestOptionScalars(options) {
16876
17094
  assertOptionalInteger(options, "gifLoop");
16877
17095
  assertOptionalInteger(options, "hlsSegmentSeconds", 1);
16878
17096
  assertOptionalInteger(options, "workers", 1);
17097
+ assertOptionalInteger(options, "maxRenderMs");
16879
17098
  assertOptionalInteger(options, "crf");
16880
17099
  for (const field of ["useGpu", "debug", "outputResolutionAspectAgnostic"]) {
16881
17100
  assertOptionalBoolean(options, field);
@@ -17072,13 +17291,13 @@ import {
17072
17291
  import {
17073
17292
  existsSync as existsSync24,
17074
17293
  mkdirSync as mkdirSync14,
17075
- statSync as statSync8,
17294
+ statSync as statSync9,
17076
17295
  mkdtempSync as mkdtempSync5,
17077
17296
  writeFileSync as writeFileSync9,
17078
- rmSync as rmSync13,
17297
+ rmSync as rmSync14,
17079
17298
  createReadStream as createReadStream5
17080
17299
  } from "node:fs";
17081
- import { resolve as resolve10, dirname as dirname14, join as join36 } from "node:path";
17300
+ import { resolve as resolve10, dirname as dirname14, join as join37 } from "node:path";
17082
17301
  import { tmpdir as tmpdir3 } from "node:os";
17083
17302
  import { parseArgs } from "node:util";
17084
17303
  import crypto from "node:crypto";
@@ -17091,13 +17310,13 @@ import { fetchPublicHttpsText as fetchPublicHttpsText2 } from "@hyperframes/engi
17091
17310
  import {
17092
17311
  existsSync as existsSync22,
17093
17312
  readFileSync as readFileSync12,
17094
- statSync as statSync7,
17313
+ statSync as statSync8,
17095
17314
  openSync as openSync6,
17096
- fstatSync as fstatSync4,
17097
- closeSync as closeSync7,
17315
+ fstatSync as fstatSync3,
17316
+ closeSync as closeSync6,
17098
17317
  constants as constants3
17099
17318
  } from "node:fs";
17100
- import { resolve as resolve9, join as join34, relative as relative6, isAbsolute as isAbsolute4, sep as sep3 } from "node:path";
17319
+ import { resolve as resolve9, join as join35, relative as relative6, isAbsolute as isAbsolute4, sep as sep3 } from "node:path";
17101
17320
  import { lintHyperframeHtml } from "@hyperframes/lint";
17102
17321
  function isStringRecord(value) {
17103
17322
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -17131,19 +17350,19 @@ function readEntryHtml(filePath) {
17131
17350
  } catch (error) {
17132
17351
  if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
17133
17352
  return null;
17134
- if (!statSync7(filePath, { throwIfNoEntry: false })?.isFile()) return null;
17353
+ if (!statSync8(filePath, { throwIfNoEntry: false })?.isFile()) return null;
17135
17354
  throw error;
17136
17355
  }
17137
17356
  try {
17138
- if (!fstatSync4(fd).isFile()) return null;
17357
+ if (!fstatSync3(fd).isFile()) return null;
17139
17358
  return readFileSync12(fd, "utf-8");
17140
17359
  } finally {
17141
- closeSync7(fd);
17360
+ closeSync6(fd);
17142
17361
  }
17143
17362
  }
17144
17363
  function readProjectEntryFile(projectDir, preferredEntryFile) {
17145
17364
  const absProjectDir = resolve9(projectDir);
17146
- if (!existsSync22(absProjectDir) || !statSync7(absProjectDir).isDirectory()) {
17365
+ if (!existsSync22(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
17147
17366
  return { error: `Project directory not found: ${absProjectDir}` };
17148
17367
  }
17149
17368
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
@@ -17165,7 +17384,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
17165
17384
  }
17166
17385
  }
17167
17386
  return {
17168
- error: `No HTML entry file found in project directory: ${join34(absProjectDir, preferredEntryFile || "index.html")}`
17387
+ error: `No HTML entry file found in project directory: ${join35(absProjectDir, preferredEntryFile || "index.html")}`
17169
17388
  };
17170
17389
  }
17171
17390
  function prepareHyperframeLintBody(body) {
@@ -17211,7 +17430,7 @@ async function runHyperframeLint(prepared) {
17211
17430
  // src/services/healthWorker.ts
17212
17431
  import { Worker as Worker2 } from "node:worker_threads";
17213
17432
  import { fileURLToPath as fileURLToPath6 } from "node:url";
17214
- import { dirname as dirname13, join as join35 } from "node:path";
17433
+ import { dirname as dirname13, join as join36 } from "node:path";
17215
17434
  import { existsSync as existsSync23 } from "node:fs";
17216
17435
  var DEFAULT_HEALTH_PORT = 9848;
17217
17436
  async function startHealthWorker(options = {}) {
@@ -17287,7 +17506,7 @@ function defaultLogger2() {
17287
17506
  }
17288
17507
  function resolveWorkerEntry2() {
17289
17508
  const here = dirname13(fileURLToPath6(import.meta.url));
17290
- const candidates = [join35(here, "healthWorkerThread.js"), join35(here, "healthWorkerThread.ts")];
17509
+ const candidates = [join36(here, "healthWorkerThread.js"), join36(here, "healthWorkerThread.ts")];
17291
17510
  for (const candidate of candidates) {
17292
17511
  if (existsSync23(candidate)) return candidate;
17293
17512
  }
@@ -17308,7 +17527,8 @@ var SAFE_RENDER_ERROR_CODES = /* @__PURE__ */ new Set([
17308
17527
  "INVALID_VIDEO_METADATA",
17309
17528
  "VIDEO_SOURCE_UNRENDERABLE",
17310
17529
  "VIDEO_EXTRACTION_FAILED",
17311
- "ENCODER_INTERRUPTED"
17530
+ "ENCODER_INTERRUPTED",
17531
+ "RENDER_TOO_SLOW"
17312
17532
  ]);
17313
17533
  function extractSafeRenderErrorCode(error) {
17314
17534
  if (typeof error !== "object" || error === null || !("code" in error)) return void 0;
@@ -17367,6 +17587,7 @@ function parseRenderOptions(body) {
17367
17587
  const fps = parseServerFps(body.fps);
17368
17588
  const quality = parseServerQuality(body.quality);
17369
17589
  const workers = typeof body.workers === "number" ? body.workers : void 0;
17590
+ const maxRenderMs = typeof body.maxRenderMs === "number" ? body.maxRenderMs : void 0;
17370
17591
  const useGpu = body.gpu === true;
17371
17592
  const debug = body.debug === true;
17372
17593
  const strictness = body.bestEffort === false ? "strict" : "best-effort";
@@ -17382,6 +17603,7 @@ function parseRenderOptions(body) {
17382
17603
  fps,
17383
17604
  quality,
17384
17605
  workers,
17606
+ maxRenderMs,
17385
17607
  crf,
17386
17608
  videoBitrate,
17387
17609
  useGpu,
@@ -17415,6 +17637,7 @@ function buildRenderJobConfig(input, outputPath, log) {
17415
17637
  quality: input.quality,
17416
17638
  format: input.format ?? "mp4",
17417
17639
  workers: input.workers,
17640
+ maxRenderMs: input.maxRenderMs,
17418
17641
  crf: input.crf,
17419
17642
  videoBitrate: input.videoBitrate,
17420
17643
  useGpu: input.useGpu,
@@ -17441,6 +17664,9 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
17441
17664
  function validateRenderOverrides(body) {
17442
17665
  const encoder = parseEncoderOptions(body, parseServerFormat(body.format) ?? "mp4");
17443
17666
  if (!encoder.ok) return encoder.error;
17667
+ if (body.maxRenderMs !== void 0 && (typeof body.maxRenderMs !== "number" || !Number.isInteger(body.maxRenderMs) || body.maxRenderMs < 0)) {
17668
+ return "maxRenderMs must be a non-negative integer number of milliseconds";
17669
+ }
17444
17670
  if (body.variables !== void 0 && !isPlainObject2(body.variables)) {
17445
17671
  return 'variables must be a JSON object keyed by variable id (e.g. {"title":"Hello"})';
17446
17672
  }
@@ -17473,7 +17699,7 @@ function prepareProjectDirectory(projectDir, options) {
17473
17699
  const candidate = nonEmptyString(projectDir);
17474
17700
  if (!candidate) return null;
17475
17701
  const absProjectDir = resolve10(candidate);
17476
- if (!existsSync24(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
17702
+ if (!existsSync24(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
17477
17703
  return { error: `Project directory not found: ${absProjectDir}` };
17478
17704
  }
17479
17705
  const entry = options.entryFile || "index.html";
@@ -17498,8 +17724,8 @@ async function resolveInlineRenderHtml(body) {
17498
17724
  }
17499
17725
  function materializeInlineProject(html, options) {
17500
17726
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
17501
- const tempProjectDir = mkdtempSync5(join36(tempRoot, "producer-project-"));
17502
- writeFileSync9(join36(tempProjectDir, "index.html"), html, "utf-8");
17727
+ const tempProjectDir = mkdtempSync5(join37(tempRoot, "producer-project-"));
17728
+ writeFileSync9(join37(tempProjectDir, "index.html"), html, "utf-8");
17503
17729
  return {
17504
17730
  prepared: {
17505
17731
  input: { projectDir: tempProjectDir, ...options },
@@ -17560,7 +17786,7 @@ function createArtifactStore(ttlMs) {
17560
17786
  function cleanupTempDir(dir, log) {
17561
17787
  if (!dir) return;
17562
17788
  try {
17563
- rmSync13(dir, { recursive: true, force: true });
17789
+ rmSync14(dir, { recursive: true, force: true });
17564
17790
  } catch (error) {
17565
17791
  log.warn("Failed to cleanup temp project dir", {
17566
17792
  cleanupProjectDir: dir,
@@ -17569,7 +17795,7 @@ function cleanupTempDir(dir, log) {
17569
17795
  }
17570
17796
  }
17571
17797
  function outputFileSize(path) {
17572
- return existsSync24(path) ? statSync8(path).size : 0;
17798
+ return existsSync24(path) ? statSync9(path).size : 0;
17573
17799
  }
17574
17800
  function createBlockingProgressReporter(log, requestId) {
17575
17801
  let lastLoggedPct = -10;
@@ -17869,7 +18095,7 @@ function createRenderHandlers(options = {}) {
17869
18095
  store.delete(token);
17870
18096
  return c.json({ success: false, error: "Output artifact file missing" }, 404);
17871
18097
  }
17872
- const stats = statSync8(artifact.path);
18098
+ const stats = statSync9(artifact.path);
17873
18099
  return new Response(createReadStream5(artifact.path), {
17874
18100
  headers: {
17875
18101
  "content-type": "video/mp4",
@@ -17968,12 +18194,12 @@ import {
17968
18194
  mkdtempSync as mkdtempSync7,
17969
18195
  readFileSync as readFileSync13,
17970
18196
  readdirSync as readdirSync9,
17971
- rmSync as rmSync15,
17972
- statSync as statSync10,
18197
+ rmSync as rmSync16,
18198
+ statSync as statSync11,
17973
18199
  writeFileSync as writeFileSync11
17974
18200
  } from "node:fs";
17975
18201
  import { tmpdir as tmpdir4 } from "node:os";
17976
- import { dirname as dirname16, isAbsolute as isAbsolute5, join as join39, relative as relative7, resolve as resolve11, sep as sep4 } from "node:path";
18202
+ import { dirname as dirname16, isAbsolute as isAbsolute5, join as join40, relative as relative7, resolve as resolve11, sep as sep4 } from "node:path";
17977
18203
  import { DISTRIBUTED_FPS as DISTRIBUTED_FPS2, isDistributedFps as isDistributedFps3 } from "@hyperframes/core";
17978
18204
  import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
17979
18205
 
@@ -17984,12 +18210,12 @@ import {
17984
18210
  linkSync as linkSync2,
17985
18211
  mkdirSync as mkdirSync15,
17986
18212
  mkdtempSync as mkdtempSync6,
17987
- rmSync as rmSync14,
17988
- statSync as statSync9,
18213
+ rmSync as rmSync15,
18214
+ statSync as statSync10,
17989
18215
  writeFileSync as writeFileSync10
17990
18216
  } from "node:fs";
17991
18217
  import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
17992
- import { dirname as dirname15, join as join38 } from "node:path";
18218
+ import { dirname as dirname15, join as join39 } from "node:path";
17993
18219
 
17994
18220
  // src/services/distributed/planV2Errors.ts
17995
18221
  var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
@@ -18004,7 +18230,7 @@ var PlanV2IntegrityError = class extends Error {
18004
18230
  };
18005
18231
 
18006
18232
  // src/services/distributed/planV2Layout.ts
18007
- import { join as join37 } from "node:path";
18233
+ import { join as join38 } from "node:path";
18008
18234
  var SHA256_PATTERN = /^[0-9a-f]{64}$/;
18009
18235
  function assertPlanV2Sha256(value, field) {
18010
18236
  if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
@@ -18014,7 +18240,7 @@ function assertPlanV2Sha256(value, field) {
18014
18240
  }
18015
18241
  function planV2BlobPath(root, sha256) {
18016
18242
  const digest = assertPlanV2Sha256(sha256, "artifact sha256");
18017
- return join37(root, "artifacts", "sha256", digest.slice(0, 2), digest);
18243
+ return join38(root, "artifacts", "sha256", digest.slice(0, 2), digest);
18018
18244
  }
18019
18245
 
18020
18246
  // src/services/distributed/planV2Publisher.ts
@@ -18055,11 +18281,11 @@ var LocalPlanV2ArtifactPublisher = class {
18055
18281
  this.destinationDir = destinationDir;
18056
18282
  this.#linkFile = options.linkFile ?? linkSync2;
18057
18283
  mkdirSync15(dirname15(destinationDir), { recursive: true });
18058
- this.temporaryDir = mkdtempSync6(join38(dirname15(destinationDir), ".plan-v2-publish-"));
18284
+ this.temporaryDir = mkdtempSync6(join39(dirname15(destinationDir), ".plan-v2-publish-"));
18059
18285
  }
18060
18286
  async putBlob(blob) {
18061
18287
  const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
18062
- const sourceSize = statSync9(blob.sourcePath).size;
18288
+ const sourceSize = statSync10(blob.sourcePath).size;
18063
18289
  if (sourceSize !== blob.sizeBytes) {
18064
18290
  throw new PlanV2IntegrityError(
18065
18291
  `published blob size changed for ${digest}: expected ${blob.sizeBytes}, got ${sourceSize}`
@@ -18068,8 +18294,8 @@ var LocalPlanV2ArtifactPublisher = class {
18068
18294
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
18069
18295
  if (existsSync25(destinationPath)) return;
18070
18296
  mkdirSync15(dirname15(destinationPath), { recursive: true });
18071
- const stagingDir = mkdtempSync6(join38(dirname15(destinationPath), ".plan-v2-blob-"));
18072
- const temporaryPath = join38(stagingDir, "blob");
18297
+ const stagingDir = mkdtempSync6(join39(dirname15(destinationPath), ".plan-v2-blob-"));
18298
+ const temporaryPath = join39(stagingDir, "blob");
18073
18299
  try {
18074
18300
  try {
18075
18301
  this.#linkFile(blob.sourcePath, temporaryPath);
@@ -18079,7 +18305,7 @@ var LocalPlanV2ArtifactPublisher = class {
18079
18305
  }
18080
18306
  renameWithRetry7(temporaryPath, destinationPath);
18081
18307
  } finally {
18082
- rmSync14(stagingDir, { recursive: true, force: true });
18308
+ rmSync15(stagingDir, { recursive: true, force: true });
18083
18309
  }
18084
18310
  }
18085
18311
  async commitManifest(manifestBytes) {
@@ -18090,13 +18316,13 @@ var LocalPlanV2ArtifactPublisher = class {
18090
18316
  );
18091
18317
  }
18092
18318
  }
18093
- writeFileSync10(join38(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
18319
+ writeFileSync10(join39(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
18094
18320
  renameWithRetry7(this.temporaryDir, this.destinationDir);
18095
18321
  this.#committed = true;
18096
18322
  }
18097
18323
  async abort() {
18098
18324
  if (!this.#committed) {
18099
- rmSync14(this.temporaryDir, { recursive: true, force: true });
18325
+ rmSync15(this.temporaryDir, { recursive: true, force: true });
18100
18326
  }
18101
18327
  }
18102
18328
  };
@@ -18135,7 +18361,7 @@ function listFiles(root) {
18135
18361
  const rootResolved = resolve11(root);
18136
18362
  function walk(dir) {
18137
18363
  for (const entry of readdirSync9(dir, { withFileTypes: true })) {
18138
- const absolutePath = join39(dir, entry.name);
18364
+ const absolutePath = join40(dir, entry.name);
18139
18365
  if (entry.isDirectory()) {
18140
18366
  walk(absolutePath);
18141
18367
  } else if (entry.isFile()) {
@@ -18162,13 +18388,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
18162
18388
  }
18163
18389
  }
18164
18390
  function validateExtractionCacheCompleteSentinels(executionPlanDir) {
18165
- const videoRoot = join39(executionPlanDir, "video-frames");
18391
+ const videoRoot = join40(executionPlanDir, "video-frames");
18166
18392
  if (!existsSync26(videoRoot)) return;
18167
18393
  for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
18168
18394
  if (!videoEntry.isDirectory()) continue;
18169
- const videoDir = join39(videoRoot, videoEntry.name);
18395
+ const videoDir = join40(videoRoot, videoEntry.name);
18170
18396
  if (readdirSync9(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
18171
- const sentinelPath = join39(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
18397
+ const sentinelPath = join40(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
18172
18398
  assertValidExtractionCacheCompleteSentinel(sentinelPath);
18173
18399
  }
18174
18400
  }
@@ -18215,7 +18441,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
18215
18441
  if (video.deferredRange) {
18216
18442
  const framePaths2 = /* @__PURE__ */ new Map();
18217
18443
  for (let index = 0; index < video.totalFrames; index++) {
18218
- framePaths2.set(index, join39(outputDir, String(index)));
18444
+ framePaths2.set(index, join40(outputDir, String(index)));
18219
18445
  }
18220
18446
  return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
18221
18447
  }
@@ -18223,7 +18449,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
18223
18449
  const framePaths = /* @__PURE__ */ new Map();
18224
18450
  for (const frameName of frameNames) {
18225
18451
  if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
18226
- assertValidExtractionCacheCompleteSentinel(join39(outputDir, frameName));
18452
+ assertValidExtractionCacheCompleteSentinel(join40(outputDir, frameName));
18227
18453
  continue;
18228
18454
  }
18229
18455
  const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
@@ -18242,7 +18468,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
18242
18468
  `duplicate extracted frame index ${frameIndex} in ${outputDir}`
18243
18469
  );
18244
18470
  }
18245
- framePaths.set(frameIndex, join39(outputDir, frameName));
18471
+ framePaths.set(frameIndex, join40(outputDir, frameName));
18246
18472
  }
18247
18473
  return {
18248
18474
  ...video,
@@ -18263,7 +18489,7 @@ function parsePlanVideosJson2(value) {
18263
18489
  }
18264
18490
  }
18265
18491
  function materializeExtractedVideoDirectories(planDir) {
18266
- const videosPath = join39(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18492
+ const videosPath = join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18267
18493
  if (!existsSync26(videosPath)) return;
18268
18494
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
18269
18495
  for (const video of videos.extracted) {
@@ -18292,13 +18518,13 @@ function parseChunkSlices(value) {
18292
18518
  });
18293
18519
  }
18294
18520
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
18295
- const videoRoot = join39(executionPlanDir, "video-frames");
18521
+ const videoRoot = join40(executionPlanDir, "video-frames");
18296
18522
  const hasExtractedFrames = existsSync26(videoRoot) && listFiles(videoRoot).length > 0;
18297
- const videosPath = join39(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18523
+ const videosPath = join40(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18298
18524
  if (!existsSync26(videosPath)) {
18299
18525
  return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
18300
18526
  }
18301
- const chunksPath = join39(executionPlanDir, "meta", "chunks.json");
18527
+ const chunksPath = join40(executionPlanDir, "meta", "chunks.json");
18302
18528
  const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
18303
18529
  const chunks = readJsonFile(chunksPath, "meta/chunks.json");
18304
18530
  const parsedVideos = parsePlanVideosJson2(videos);
@@ -18354,20 +18580,20 @@ function computeManifestHash(manifest) {
18354
18580
  function writeBlob(sourcePath, destinationPath) {
18355
18581
  if (existsSync26(destinationPath)) return;
18356
18582
  mkdirSync16(dirname16(destinationPath), { recursive: true });
18357
- const temporaryDir = mkdtempSync7(join39(dirname16(destinationPath), ".plan-v2-blob-"));
18358
- const temporaryPath = join39(temporaryDir, "blob");
18583
+ const temporaryDir = mkdtempSync7(join40(dirname16(destinationPath), ".plan-v2-blob-"));
18584
+ const temporaryPath = join40(temporaryDir, "blob");
18359
18585
  try {
18360
18586
  copyFileSync7(sourcePath, temporaryPath);
18361
18587
  renameWithRetry8(temporaryPath, destinationPath);
18362
18588
  } finally {
18363
- rmSync15(temporaryDir, { recursive: true, force: true });
18589
+ rmSync16(temporaryDir, { recursive: true, force: true });
18364
18590
  }
18365
18591
  }
18366
18592
  function publishBlobFor(absolutePath, digests) {
18367
18593
  return {
18368
18594
  sourcePath: absolutePath,
18369
18595
  sha256: planFileSha256(absolutePath, digests),
18370
- sizeBytes: statSync10(absolutePath).size
18596
+ sizeBytes: statSync11(absolutePath).size
18371
18597
  };
18372
18598
  }
18373
18599
  async function putBlobs(publisher, blobs) {
@@ -18391,7 +18617,7 @@ async function putTasks(tasks) {
18391
18617
  if (failure) throw failure.reason;
18392
18618
  }
18393
18619
  function buildPlanV2Publication(executionPlanDir, digests) {
18394
- const executionPlanPath = join39(executionPlanDir, "plan.json");
18620
+ const executionPlanPath = join40(executionPlanDir, "plan.json");
18395
18621
  if (!existsSync26(executionPlanPath)) {
18396
18622
  throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
18397
18623
  }
@@ -18419,7 +18645,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
18419
18645
  }
18420
18646
  validateExtractionCacheCompleteSentinels(executionPlanDir);
18421
18647
  const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
18422
- const encoder = readJsonFile(join39(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
18648
+ const encoder = readJsonFile(join40(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
18423
18649
  const files = listFiles(executionPlanDir);
18424
18650
  const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
18425
18651
  for (const file of files) {
@@ -18480,20 +18706,20 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
18480
18706
  throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
18481
18707
  }
18482
18708
  mkdirSync16(dirname16(planV2Dir), { recursive: true });
18483
- const tempDir = mkdtempSync7(join39(dirname16(planV2Dir), ".plan-v2-build-"));
18709
+ const tempDir = mkdtempSync7(join40(dirname16(planV2Dir), ".plan-v2-build-"));
18484
18710
  try {
18485
18711
  for (const blob of publication.blobs) {
18486
18712
  writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
18487
18713
  }
18488
18714
  writeFileSync11(
18489
- join39(tempDir, "plan.json"),
18715
+ join40(tempDir, "plan.json"),
18490
18716
  canonicalJsonStringify(publication.manifest),
18491
18717
  "utf-8"
18492
18718
  );
18493
18719
  renameWithRetry8(tempDir, planV2Dir);
18494
18720
  return resultFromManifest(planV2Dir, publication.manifest);
18495
18721
  } catch (error) {
18496
- rmSync15(tempDir, { recursive: true, force: true });
18722
+ rmSync16(tempDir, { recursive: true, force: true });
18497
18723
  throw error;
18498
18724
  }
18499
18725
  }
@@ -18533,7 +18759,7 @@ async function publishPlanV2FromV1(executionPlanDir, publisher) {
18533
18759
  return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
18534
18760
  }
18535
18761
  async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
18536
- const stagingRoot = mkdtempSync7(join39(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
18762
+ const stagingRoot = mkdtempSync7(join40(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
18537
18763
  const digests = /* @__PURE__ */ new Map();
18538
18764
  try {
18539
18765
  await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
@@ -18544,7 +18770,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
18544
18770
  // artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
18545
18771
  whileAudioMixes: () => putBlobs(
18546
18772
  publisher,
18547
- listFiles(join39(stagingRoot, "compiled")).map(
18773
+ listFiles(join40(stagingRoot, "compiled")).map(
18548
18774
  (file) => publishBlobFor(file.absolutePath, digests)
18549
18775
  )
18550
18776
  ),
@@ -18560,7 +18786,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
18560
18786
  }
18561
18787
  throw error;
18562
18788
  } finally {
18563
- rmSync15(stagingRoot, { recursive: true, force: true });
18789
+ rmSync16(stagingRoot, { recursive: true, force: true });
18564
18790
  }
18565
18791
  }
18566
18792
  async function planV2(projectDir, config, planV2Dir) {
@@ -18576,7 +18802,7 @@ async function planV2(projectDir, config, planV2Dir) {
18576
18802
  function resultFromManifest(planV2Dir, manifest) {
18577
18803
  return {
18578
18804
  planDir: planV2Dir,
18579
- manifestPath: join39(planV2Dir, "plan.json"),
18805
+ manifestPath: join40(planV2Dir, "plan.json"),
18580
18806
  planProtocol: PLAN_PROTOCOL_V2,
18581
18807
  planHash: manifest.planHash,
18582
18808
  sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
@@ -18712,7 +18938,7 @@ function parsePlanV2Manifest(value) {
18712
18938
  return parsed;
18713
18939
  }
18714
18940
  function readPlanV2Manifest(planV2Dir) {
18715
- const manifestPath = join39(planV2Dir, "plan.json");
18941
+ const manifestPath = join40(planV2Dir, "plan.json");
18716
18942
  if (!existsSync26(manifestPath)) {
18717
18943
  throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
18718
18944
  }
@@ -18735,7 +18961,7 @@ function verifyBlob(planV2Dir, artifact) {
18735
18961
  if (!existsSync26(sourcePath)) {
18736
18962
  throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
18737
18963
  }
18738
- const stats = statSync10(sourcePath);
18964
+ const stats = statSync11(sourcePath);
18739
18965
  if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
18740
18966
  throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
18741
18967
  }
@@ -18758,16 +18984,16 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
18758
18984
  sourcePath: verifyBlob(planV2Dir, artifact)
18759
18985
  }));
18760
18986
  mkdirSync16(dirname16(destinationDir), { recursive: true });
18761
- const tempDir = mkdtempSync7(join39(dirname16(destinationDir), ".plan-v2-materialize-"));
18987
+ const tempDir = mkdtempSync7(join40(dirname16(destinationDir), ".plan-v2-materialize-"));
18762
18988
  try {
18763
18989
  for (const { artifact, sourcePath } of verified) {
18764
- const destinationPath = join39(tempDir, ...artifact.path.split("/"));
18990
+ const destinationPath = join40(tempDir, ...artifact.path.split("/"));
18765
18991
  mkdirSync16(dirname16(destinationPath), { recursive: true });
18766
18992
  copyFileSync7(sourcePath, destinationPath);
18767
18993
  }
18768
18994
  if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
18769
18995
  writeFileSync11(
18770
- join39(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
18996
+ join40(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
18771
18997
  canonicalJsonStringify({
18772
18998
  manifest,
18773
18999
  target,
@@ -18777,7 +19003,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
18777
19003
  );
18778
19004
  renameWithRetry8(tempDir, destinationDir);
18779
19005
  } catch (error) {
18780
- rmSync15(tempDir, { recursive: true, force: true });
19006
+ rmSync16(tempDir, { recursive: true, force: true });
18781
19007
  throw error;
18782
19008
  }
18783
19009
  return {
@@ -18790,7 +19016,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
18790
19016
  // Join the artifact's OWN name, not the current constant: a plan written
18791
19017
  // before the container change carries the legacy name, and materializing it
18792
19018
  // under the new one would point at a file that was never written.
18793
- audioPath: target.role === "assembler" ? audioArtifact ? join39(destinationDir, audioArtifact.path) : null : null
19019
+ audioPath: target.role === "assembler" ? audioArtifact ? join40(destinationDir, audioArtifact.path) : null : null
18794
19020
  };
18795
19021
  }
18796
19022
  function assertRemoteSources(remoteSources, artifacts) {
@@ -18809,7 +19035,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
18809
19035
  return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
18810
19036
  }
18811
19037
  function readValidatedPlanV2Materialization(planDir, expectedTarget) {
18812
- const markerPath = join39(planDir, PLAN_V2_MATERIALIZATION_MARKER);
19038
+ const markerPath = join40(planDir, PLAN_V2_MATERIALIZATION_MARKER);
18813
19039
  if (!existsSync26(markerPath)) return null;
18814
19040
  const marker = readJsonFile(markerPath, "materialization marker");
18815
19041
  if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
@@ -18827,8 +19053,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
18827
19053
  assertRemoteSources(remoteSources, required);
18828
19054
  for (const artifact of required) {
18829
19055
  if (Object.hasOwn(remoteSources, artifact.path)) continue;
18830
- const path = join39(planDir, ...artifact.path.split("/"));
18831
- if (!existsSync26(path) || statSync10(path).size !== artifact.sizeBytes) {
19056
+ const path = join40(planDir, ...artifact.path.split("/"));
19057
+ if (!existsSync26(path) || statSync11(path).size !== artifact.sizeBytes) {
18832
19058
  throw new PlanV2IntegrityError(
18833
19059
  `materialized artifact missing or truncated: ${artifact.path}`
18834
19060
  );
@@ -18841,9 +19067,9 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
18841
19067
  }
18842
19068
 
18843
19069
  // src/services/distributed/planV2Execution.ts
18844
- import { mkdtempSync as mkdtempSync9, rmSync as rmSync18 } from "node:fs";
19070
+ import { mkdtempSync as mkdtempSync9, rmSync as rmSync19 } from "node:fs";
18845
19071
  import { tmpdir as tmpdir5 } from "node:os";
18846
- import { join as join42 } from "node:path";
19072
+ import { join as join43 } from "node:path";
18847
19073
 
18848
19074
  // src/services/distributed/assemble.ts
18849
19075
  import {
@@ -18853,11 +19079,11 @@ import {
18853
19079
  mkdtempSync as mkdtempSync8,
18854
19080
  readFileSync as readFileSync14,
18855
19081
  readdirSync as readdirSync10,
18856
- rmSync as rmSync16,
18857
- statSync as statSync11,
19082
+ rmSync as rmSync17,
19083
+ statSync as statSync12,
18858
19084
  writeFileSync as writeFileSync12
18859
19085
  } from "node:fs";
18860
- import { dirname as dirname17, join as join40 } from "node:path";
19086
+ import { dirname as dirname17, join as join41 } from "node:path";
18861
19087
  import {
18862
19088
  appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
18863
19089
  describeFfmpegFailure as describeFfmpegFailure4,
@@ -18893,7 +19119,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
18893
19119
  outputPath,
18894
19120
  durationMs: Date.now() - start,
18895
19121
  framesEncoded: plan2.totalFrames,
18896
- fileSize: statSync11(outputPath).size
19122
+ fileSize: statSync12(outputPath).size
18897
19123
  };
18898
19124
  }
18899
19125
  const format = plan2.dimensions.format;
@@ -18921,7 +19147,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
18921
19147
  };
18922
19148
  try {
18923
19149
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
18924
- const finalPath = join40(workDir, `output.${format}`);
19150
+ const finalPath = join41(workDir, `output.${format}`);
18925
19151
  const audio = audioPath !== null && existsSync27(audioPath) ? audioPath : null;
18926
19152
  await writeJoined(ctx, videoSources, audio, finalPath);
18927
19153
  renameWithRetry9(finalPath, outputPath);
@@ -18932,7 +19158,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
18932
19158
  });
18933
19159
  } finally {
18934
19160
  try {
18935
- rmSync16(workDir, { recursive: true, force: true });
19161
+ rmSync17(workDir, { recursive: true, force: true });
18936
19162
  } catch (err) {
18937
19163
  log.warn("[assemble] failed to remove work dir", {
18938
19164
  workDir,
@@ -18940,7 +19166,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
18940
19166
  });
18941
19167
  }
18942
19168
  }
18943
- const fileSize = existsSync27(outputPath) ? statSync11(outputPath).size : 0;
19169
+ const fileSize = existsSync27(outputPath) ? statSync12(outputPath).size : 0;
18944
19170
  return {
18945
19171
  outputPath,
18946
19172
  durationMs: Date.now() - start,
@@ -18949,8 +19175,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
18949
19175
  };
18950
19176
  }
18951
19177
  function readAssemblePlan(planDir, chunkPaths) {
18952
- const planJsonPath = join40(planDir, "plan.json");
18953
- const chunksJsonPath = join40(planDir, "meta", "chunks.json");
19178
+ const planJsonPath = join41(planDir, "plan.json");
19179
+ const chunksJsonPath = join41(planDir, "meta", "chunks.json");
18954
19180
  if (!existsSync27(planJsonPath)) {
18955
19181
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
18956
19182
  }
@@ -18998,7 +19224,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
18998
19224
  return args;
18999
19225
  }
19000
19226
  async function reencodeCfr(ctx, chunkPaths, encoder) {
19001
- const cfrOutputPath = join40(ctx.workDir, `cfr.${ctx.format}`);
19227
+ const cfrOutputPath = join41(ctx.workDir, `cfr.${ctx.format}`);
19002
19228
  const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
19003
19229
  appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
19004
19230
  cfrArgs.push("-y", cfrOutputPath);
@@ -19025,7 +19251,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
19025
19251
  );
19026
19252
  return;
19027
19253
  }
19028
- const paddedAudioPath = join40(ctx.workDir, "audio-padded.m4a");
19254
+ const paddedAudioPath = join41(ctx.workDir, "audio-padded.m4a");
19029
19255
  const padTrimResult = await ctx.timed(
19030
19256
  "audioMs",
19031
19257
  () => padOrTrimAudioToVideoFrameCount({
@@ -19065,7 +19291,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
19065
19291
  }
19066
19292
  }
19067
19293
  function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
19068
- const listPath = join40(workDir, "concat-list.txt");
19294
+ const listPath = join41(workDir, "concat-list.txt");
19069
19295
  const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
19070
19296
  writeFileSync12(listPath, `${body}
19071
19297
  `, "utf-8");
@@ -19103,11 +19329,11 @@ function stitchesSeparateAudio(planDir) {
19103
19329
  return lockedAudio(planDir).separateAudio === true;
19104
19330
  }
19105
19331
  function planFormat(planDir) {
19106
- const plan2 = JSON.parse(readFileSync14(join40(planDir, "plan.json"), "utf-8"));
19332
+ const plan2 = JSON.parse(readFileSync14(join41(planDir, "plan.json"), "utf-8"));
19107
19333
  return plan2.dimensions.format;
19108
19334
  }
19109
19335
  function lockedAudio(planDir) {
19110
- const encoderJsonPath = join40(planDir, "meta", "encoder.json");
19336
+ const encoderJsonPath = join41(planDir, "meta", "encoder.json");
19111
19337
  if (!existsSync27(encoderJsonPath)) return {};
19112
19338
  return JSON.parse(readFileSync14(encoderJsonPath, "utf-8"));
19113
19339
  }
@@ -19117,7 +19343,7 @@ function readCfrEncoder(planDir, format) {
19117
19343
  `[assemble] cfr=true is only supported for format="mp4" (got "${format}"). Stream-copy paths for webm and mov already produce exact avg_frame_rate; cfr re-encode is not needed.`
19118
19344
  );
19119
19345
  }
19120
- const encoderJsonPath = join40(planDir, "meta", "encoder.json");
19346
+ const encoderJsonPath = join41(planDir, "meta", "encoder.json");
19121
19347
  if (!existsSync27(encoderJsonPath)) {
19122
19348
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
19123
19349
  }
@@ -19130,11 +19356,11 @@ function readCfrEncoder(planDir, format) {
19130
19356
  return encoderJson;
19131
19357
  }
19132
19358
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
19133
- if (existsSync27(outputPath)) rmSync16(outputPath, { recursive: true, force: true });
19359
+ if (existsSync27(outputPath)) rmSync17(outputPath, { recursive: true, force: true });
19134
19360
  mkdirSync17(outputPath, { recursive: true });
19135
19361
  let globalIdx = 0;
19136
19362
  for (const chunkDir of chunkPaths) {
19137
- if (!statSync11(chunkDir).isDirectory()) {
19363
+ if (!statSync12(chunkDir).isDirectory()) {
19138
19364
  throw new Error(
19139
19365
  `[assemble] png-sequence chunk must be a directory: ${chunkDir} (got a file)`
19140
19366
  );
@@ -19144,8 +19370,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
19144
19370
  throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
19145
19371
  }
19146
19372
  for (const frame of frames) {
19147
- const dst = join40(outputPath, formatExportFrameName(globalIdx, "png"));
19148
- cpSync3(join40(chunkDir, frame), dst);
19373
+ const dst = join41(outputPath, formatExportFrameName(globalIdx, "png"));
19374
+ cpSync3(join41(chunkDir, frame), dst);
19149
19375
  globalIdx += 1;
19150
19376
  }
19151
19377
  }
@@ -19155,13 +19381,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
19155
19381
  );
19156
19382
  }
19157
19383
  if (audioPath !== null && existsSync27(audioPath)) {
19158
- const sidecar = join40(outputPath, MIXED_AUDIO_FILENAME4);
19384
+ const sidecar = join41(outputPath, MIXED_AUDIO_FILENAME4);
19159
19385
  cpSync3(audioPath, sidecar);
19160
19386
  }
19161
19387
  let fileSize = 0;
19162
19388
  for (const name of readdirSync10(outputPath)) {
19163
19389
  try {
19164
- fileSize += statSync11(join40(outputPath, name)).size;
19390
+ fileSize += statSync12(join41(outputPath, name)).size;
19165
19391
  } catch {
19166
19392
  }
19167
19393
  }
@@ -19175,9 +19401,9 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
19175
19401
 
19176
19402
  // src/services/distributed/renderChunk.ts
19177
19403
  import { randomBytes } from "node:crypto";
19178
- import { existsSync as existsSync28, mkdirSync as mkdirSync18, readFileSync as readFileSync15, readdirSync as readdirSync11, rmSync as rmSync17, writeFileSync as writeFileSync13 } from "node:fs";
19404
+ import { existsSync as existsSync28, mkdirSync as mkdirSync18, readFileSync as readFileSync15, readdirSync as readdirSync11, rmSync as rmSync18, writeFileSync as writeFileSync13 } from "node:fs";
19179
19405
  import { cpus as cpus3 } from "node:os";
19180
- import { join as join41 } from "node:path";
19406
+ import { join as join42 } from "node:path";
19181
19407
  import {
19182
19408
  assertSwiftShader,
19183
19409
  BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
@@ -19296,7 +19522,7 @@ function hashChunkOutput(outputPath, kind) {
19296
19522
  if (kind === "file") return sha256Hex(readFileSync15(outputPath));
19297
19523
  const entries = readdirSync11(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
19298
19524
  const lines = entries.map(
19299
- (name) => `${name}\0${sha256Hex(readFileSync15(join41(outputPath, name)))}`
19525
+ (name) => `${name}\0${sha256Hex(readFileSync15(join42(outputPath, name)))}`
19300
19526
  );
19301
19527
  return sha256Hex(lines.join("\0"));
19302
19528
  }
@@ -19313,9 +19539,9 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
19313
19539
  async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19314
19540
  const start = Date.now();
19315
19541
  const log = defaultLogger;
19316
- const planJsonPath = join41(planDir, "plan.json");
19317
- const encoderJsonPath = join41(planDir, "meta", "encoder.json");
19318
- const chunksJsonPath = join41(planDir, "meta", "chunks.json");
19542
+ const planJsonPath = join42(planDir, "plan.json");
19543
+ const encoderJsonPath = join42(planDir, "meta", "encoder.json");
19544
+ const chunksJsonPath = join42(planDir, "meta", "chunks.json");
19319
19545
  if (!existsSync28(planJsonPath)) {
19320
19546
  throw new RenderChunkValidationError(
19321
19547
  MISSING_PLAN_ARTIFACT,
@@ -19341,7 +19567,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19341
19567
  }
19342
19568
  const encoder = JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
19343
19569
  const chunks = JSON.parse(readFileSync15(chunksJsonPath, "utf-8"));
19344
- const videosJsonPath = join41(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19570
+ const videosJsonPath = join42(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19345
19571
  let planVideos = null;
19346
19572
  if (existsSync28(videosJsonPath)) {
19347
19573
  try {
@@ -19373,7 +19599,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19373
19599
  }
19374
19600
  const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
19375
19601
  const framesInChunk = range.endFrame - range.startFrame;
19376
- const compiledDir = join41(planDir, "compiled");
19602
+ const compiledDir = join42(planDir, "compiled");
19377
19603
  if (!existsSync28(compiledDir)) {
19378
19604
  throw new RenderChunkValidationError(
19379
19605
  MISSING_PLAN_ARTIFACT,
@@ -19431,7 +19657,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19431
19657
  };
19432
19658
  const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
19433
19659
  mkdirSync18(workDir, { recursive: true });
19434
- const framesDir = join41(workDir, "captured-frames");
19660
+ const framesDir = join42(workDir, "captured-frames");
19435
19661
  mkdirSync18(framesDir, { recursive: true });
19436
19662
  const videoFramesDir = `${workDir}.video-frames`;
19437
19663
  const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
@@ -19456,7 +19682,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19456
19682
  dimensions: plan2.dimensions,
19457
19683
  encoder,
19458
19684
  videoCount: planVideos?.videos.length ?? 0,
19459
- html: readFileSync15(join41(compiledDir, "index.html"), "utf-8"),
19685
+ html: readFileSync15(join42(compiledDir, "index.html"), "utf-8"),
19460
19686
  // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
19461
19687
  skipReadinessVideoIds: deferredVideos.filter(
19462
19688
  (video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
@@ -19581,7 +19807,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19581
19807
  if (isPngSequence) {
19582
19808
  if (!existsSync28(outputChunkPath)) mkdirSync18(outputChunkPath, { recursive: true });
19583
19809
  } else {
19584
- const outDir = join41(outputChunkPath, "..");
19810
+ const outDir = join42(outputChunkPath, "..");
19585
19811
  if (!existsSync28(outDir)) mkdirSync18(outDir, { recursive: true });
19586
19812
  }
19587
19813
  const encodeInput = {
@@ -19685,7 +19911,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19685
19911
  return observedChunkCaptureMode(capturePerfs);
19686
19912
  },
19687
19913
  resetForScreenshotRetry: () => {
19688
- rmSync17(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
19914
+ rmSync18(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
19689
19915
  mkdirSync18(framesDir, { recursive: true });
19690
19916
  capturePerfs.length = 0;
19691
19917
  captureAttempts.length = 0;
@@ -19728,8 +19954,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19728
19954
  fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
19729
19955
  timeoutMs: cfg.ffmpegProcessTimeout
19730
19956
  });
19731
- rmSync17(videoOnlyPath, { force: true });
19732
- rmSync17(audioScratchPath, { force: true });
19957
+ rmSync18(videoOnlyPath, { force: true });
19958
+ rmSync18(audioScratchPath, { force: true });
19733
19959
  }
19734
19960
  encodeStageMs = Date.now() - encodeStarted;
19735
19961
  } finally {
@@ -19777,8 +20003,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19777
20003
  writeFileSync13(perfPath, `${JSON.stringify(perfPayload, null, 2)}
19778
20004
  `, "utf-8");
19779
20005
  try {
19780
- rmSync17(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
19781
- rmSync17(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
20006
+ rmSync18(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
20007
+ rmSync18(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
19782
20008
  } catch (err) {
19783
20009
  log.warn("[renderChunk] failed to remove work dir", {
19784
20010
  workDir,
@@ -19807,18 +20033,18 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19807
20033
 
19808
20034
  // src/services/distributed/planV2Execution.ts
19809
20035
  async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
19810
- const workRoot = mkdtempSync9(join42(tmpdir5(), "hf-plan-v2-chunk-"));
19811
- const materializedPlanDir = join42(workRoot, "plan");
20036
+ const workRoot = mkdtempSync9(join43(tmpdir5(), "hf-plan-v2-chunk-"));
20037
+ const materializedPlanDir = join43(workRoot, "plan");
19812
20038
  try {
19813
20039
  materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
19814
20040
  return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
19815
20041
  } finally {
19816
- rmSync18(workRoot, { recursive: true, force: true });
20042
+ rmSync19(workRoot, { recursive: true, force: true });
19817
20043
  }
19818
20044
  }
19819
20045
  async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
19820
- const workRoot = mkdtempSync9(join42(tmpdir5(), "hf-plan-v2-assembler-"));
19821
- const materializedPlanDir = join42(workRoot, "plan");
20046
+ const workRoot = mkdtempSync9(join43(tmpdir5(), "hf-plan-v2-assembler-"));
20047
+ const materializedPlanDir = join43(workRoot, "plan");
19822
20048
  try {
19823
20049
  const materialized = materializePlanV2Target(
19824
20050
  planV2Dir,
@@ -19833,7 +20059,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
19833
20059
  options
19834
20060
  );
19835
20061
  } finally {
19836
- rmSync18(workRoot, { recursive: true, force: true });
20062
+ rmSync19(workRoot, { recursive: true, force: true });
19837
20063
  }
19838
20064
  }
19839
20065