@hyperframes/producer 0.8.205 → 0.8.207

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
@@ -2,19 +2,19 @@
2
2
  import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
3
3
  import {
4
4
  existsSync as existsSync16,
5
- mkdirSync as mkdirSync9,
6
- readFileSync as readFileSync8,
5
+ mkdirSync as mkdirSync10,
6
+ readFileSync as readFileSync9,
7
7
  readdirSync as readdirSync5,
8
- rmSync as rmSync11,
9
- statSync as statSync7,
10
- writeFileSync as writeFileSync5,
11
- copyFileSync as copyFileSync4,
8
+ rmSync as rmSync10,
9
+ statSync as statSync8,
10
+ writeFileSync as writeFileSync6,
11
+ copyFileSync as copyFileSync5,
12
12
  appendFileSync
13
13
  } from "fs";
14
14
  import { tmpdir as tmpdir2 } from "node:os";
15
15
  import {
16
16
  chainCapture,
17
- fpsToNumber as fpsToNumber9,
17
+ fpsToNumber as fpsToNumber8,
18
18
  HF_COLOR_GRADING_ATTR,
19
19
  HF_VFX_ATTR,
20
20
  parseVfxChain,
@@ -56,7 +56,7 @@ import {
56
56
  augmentProtocolTimeoutError,
57
57
  augmentPageNavigationTimeoutError
58
58
  } from "@hyperframes/engine";
59
- import { join as join26, dirname as dirname10, resolve as resolve7 } from "path";
59
+ import { join as join27, dirname as dirname11, resolve as resolve8 } from "path";
60
60
  import { totalmem as totalmem2 } from "node:os";
61
61
  import { randomUUID } from "crypto";
62
62
  import { fileURLToPath as fileURLToPath4 } from "url";
@@ -80,6 +80,7 @@ import { Readable } from "node:stream";
80
80
  import { join, extname, resolve as resolve2, sep } from "node:path";
81
81
  import { injectScriptsAtHeadStart, injectScriptsIntoHtml } from "@hyperframes/core/compiler";
82
82
  import { fpsToNumber } from "@hyperframes/core";
83
+ import { stripSdrPngColourTags } from "@hyperframes/core/compiler/png-colour-tags";
83
84
 
84
85
  // src/services/hyperframeRuntimeLoader.ts
85
86
  import { createHash } from "node:crypto";
@@ -196,11 +197,11 @@ var defaultLogger = createConsoleLogger("info");
196
197
 
197
198
  // src/services/fileServer.ts
198
199
  var readFileAsync = promisify(readFile);
199
- function isPathInside(child2, parent, options = {}) {
200
+ function isPathInside(child3, parent, options = {}) {
200
201
  const { resolveSymlinks = false, pathModule } = options;
201
202
  const resolveFn = pathModule?.resolve ?? resolve2;
202
203
  const separator = pathModule?.sep ?? sep;
203
- const resolvedChild = resolveFn(child2);
204
+ const resolvedChild = resolveFn(child3);
204
205
  const resolvedParent = resolveFn(parent);
205
206
  const normalizedChild = resolveSymlinks && existsSync2(resolvedChild) ? realpathSync.native(resolvedChild) : resolvedChild;
206
207
  const normalizedParent = resolveSymlinks && existsSync2(resolvedParent) ? realpathSync.native(resolvedParent) : resolvedParent;
@@ -720,6 +721,18 @@ function createFileServer(options) {
720
721
  html = isIndex ? injectScriptsIntoHtml(html, headScripts, bodyScripts, stripEmbeddedRuntime) : html;
721
722
  return c.text(html, 200, { "Content-Type": contentType });
722
723
  }
724
+ if (ext === ".png") {
725
+ const untagged = stripSdrPngColourTags(await readFileAsync(fd));
726
+ if (untagged) {
727
+ const headers = {
728
+ "Content-Type": contentType,
729
+ "Content-Length": String(untagged.length)
730
+ };
731
+ return new Response(c.req.method === "HEAD" ? null : new Uint8Array(untagged), {
732
+ headers
733
+ });
734
+ }
735
+ }
723
736
  const totalSize = stat.size;
724
737
  const rangeHeader = c.req.header("range");
725
738
  const rangeRequest = parseRangeHeader(rangeHeader, totalSize);
@@ -747,7 +760,8 @@ function createFileServer(options) {
747
760
  const stream = createReadStream(filePath, {
748
761
  fd,
749
762
  autoClose: true,
750
- ...range ? { start: range.start, end: range.end } : {}
763
+ start: range?.start ?? 0,
764
+ ...range ? { end: range.end } : {}
751
765
  });
752
766
  streamOwnsFd = true;
753
767
  try {
@@ -761,10 +775,10 @@ function createFileServer(options) {
761
775
  if (!streamOwnsFd) closeSync(fd);
762
776
  }
763
777
  });
764
- return new Promise((resolve12) => {
778
+ return new Promise((resolve13) => {
765
779
  const connections = /* @__PURE__ */ new Set();
766
780
  const server = serve({ fetch: app.fetch, port, hostname: "127.0.0.1" }, (info) => {
767
- resolve12({
781
+ resolve13({
768
782
  url: `http://localhost:${info.port}`,
769
783
  port: info.port,
770
784
  addPreHeadScript: (script) => {
@@ -796,7 +810,7 @@ function outputUsesH264Pipeline(format) {
796
810
  }
797
811
 
798
812
  // src/services/render/hlsConfig.ts
799
- import { fpsToNumber as fpsToNumber2 } from "@hyperframes/core";
813
+ import { keyframeIntervalToGopSize } from "@hyperframes/engine";
800
814
  var DEFAULT_HLS_SEGMENT_SECONDS = 4;
801
815
  function validateHlsRenderConfig(config) {
802
816
  if (config.format !== "hls") return;
@@ -821,7 +835,7 @@ function resolveHlsSegmentSeconds(config) {
821
835
  return config.hlsSegmentSeconds ?? DEFAULT_HLS_SEGMENT_SECONDS;
822
836
  }
823
837
  function resolveHlsGopSize(fps, segmentSeconds) {
824
- return Math.max(1, Math.round(fpsToNumber2(fps) * segmentSeconds));
838
+ return keyframeIntervalToGopSize(fps, segmentSeconds);
825
839
  }
826
840
  function resolveHlsEncoderGopLock(outputFormat, fps, segmentSeconds) {
827
841
  if (outputFormat !== "hls") return { lockGopForChunkConcat: false, gopSize: void 0 };
@@ -831,6 +845,41 @@ function resolveHlsEncoderGopLock(outputFormat, fps, segmentSeconds) {
831
845
  };
832
846
  }
833
847
 
848
+ // src/services/render/deliveryEncoder.ts
849
+ import { appendRateCapArgs, keyframeIntervalToGopSize as keyframeIntervalToGopSize2 } from "@hyperframes/engine";
850
+ function validateDeliveryEncoderConfig(config) {
851
+ const { keyframeIntervalSeconds, maxBitrate, bufferSize, format } = config;
852
+ if (keyframeIntervalSeconds === void 0 && maxBitrate === void 0 && bufferSize === void 0)
853
+ return;
854
+ if (keyframeIntervalSeconds !== void 0) {
855
+ if (!Number.isFinite(keyframeIntervalSeconds) || keyframeIntervalSeconds <= 0) {
856
+ throw new Error(
857
+ `keyframeIntervalSeconds must be a positive number of seconds; got ${keyframeIntervalSeconds}`
858
+ );
859
+ }
860
+ if (format === "hls") {
861
+ throw new Error(
862
+ "keyframeIntervalSeconds does not apply to hls; hlsSegmentSeconds sets its keyframes"
863
+ );
864
+ }
865
+ }
866
+ if (format !== void 0 && format !== "mp4") {
867
+ throw new Error(`keyframeIntervalSeconds and maxBitrate apply to mp4 renders, not ${format}`);
868
+ }
869
+ appendRateCapArgs([], { maxBitrate, bufferSize });
870
+ }
871
+ function chunkGopFrames(framesInChunk, intervalFrames) {
872
+ return Math.min(framesInChunk, intervalFrames ?? framesInChunk);
873
+ }
874
+ function resolveDeliveryEncoderOptions(config) {
875
+ const { maxBitrate, bufferSize, keyframeIntervalSeconds } = config;
876
+ const gop = keyframeIntervalSeconds === void 0 ? resolveHlsEncoderGopLock(config.format ?? "mp4", config.fps, config.hlsSegmentSeconds) : {
877
+ lockGopForChunkConcat: true,
878
+ gopSize: keyframeIntervalToGopSize2(config.fps, keyframeIntervalSeconds)
879
+ };
880
+ return { maxBitrate, bufferSize, ...gop };
881
+ }
882
+
834
883
  // src/services/render/shared.ts
835
884
  import {
836
885
  copyFileSync,
@@ -1271,8 +1320,8 @@ var OrderedRenderEventPublisher = class {
1271
1320
  let timer;
1272
1321
  const drained = await Promise.race([
1273
1322
  this.tail.then(() => true),
1274
- new Promise((resolve12) => {
1275
- timer = setTimeout(() => resolve12(false), FLUSH_TIMEOUT_MS);
1323
+ new Promise((resolve13) => {
1324
+ timer = setTimeout(() => resolve13(false), FLUSH_TIMEOUT_MS);
1276
1325
  })
1277
1326
  ]);
1278
1327
  clearTimeout(timer);
@@ -1850,7 +1899,7 @@ function replanSegmentedAfterFailure(plan2, failure) {
1850
1899
  const keepSegmenting = failure.kind === "draw_element_verification" || failure.kind === "draw_element_capture";
1851
1900
  return createCapturePlan({
1852
1901
  ...plan2,
1853
- forceScreenshot: keepSegmenting || failure.kind === "capture_failure",
1902
+ forceScreenshot: plan2.forceScreenshot || keepSegmenting || failure.kind === "capture_failure",
1854
1903
  useStreamingEncode: true,
1855
1904
  useLayeredComposite: false,
1856
1905
  useSegmentedCapture: keepSegmenting,
@@ -1920,7 +1969,7 @@ import { CaptureFailure, classifyCaptureFailure } from "@hyperframes/engine";
1920
1969
 
1921
1970
  // src/services/render/perfSummary.ts
1922
1971
  import { arch, cpus, platform, totalmem } from "node:os";
1923
- import { fpsToNumber as fpsToNumber3 } from "@hyperframes/core";
1972
+ import { fpsToNumber as fpsToNumber2 } from "@hyperframes/core";
1924
1973
 
1925
1974
  // src/services/render/hdrPerf.ts
1926
1975
  function createHdrPerfCollector() {
@@ -2191,7 +2240,7 @@ function buildRenderPerfSummary(input) {
2191
2240
  // payloads (CLI telemetry, regression-harness reports) where a single
2192
2241
  // number is friendlier than `{num,den}`. Callers needing the rational
2193
2242
  // back can read `job.config.fps`.
2194
- fps: fpsToNumber3(input.job.config.fps),
2243
+ fps: fpsToNumber2(input.job.config.fps),
2195
2244
  quality: input.job.config.quality,
2196
2245
  workers: input.workerCount,
2197
2246
  workerSizing: input.workerSizing,
@@ -2776,7 +2825,7 @@ import {
2776
2825
  resolveVideoExtractionWindow,
2777
2826
  runFfmpeg
2778
2827
  } from "@hyperframes/engine";
2779
- import { fpsToFfmpegArg, fpsToNumber as fpsToNumber4 } from "@hyperframes/core";
2828
+ import { fpsToFfmpegArg, fpsToNumber as fpsToNumber3 } from "@hyperframes/core";
2780
2829
 
2781
2830
  // src/services/hdrFrameWindows.ts
2782
2831
  import { closeSync as closeSync3, constants as constants2, openSync as openSync3, rmSync as rmSync4 } from "node:fs";
@@ -3079,7 +3128,7 @@ async function extractHdrVideoFrames(args) {
3079
3128
  const runFfmpegImpl = args.runFfmpegImpl ?? runFfmpeg;
3080
3129
  const extractMediaMetadataImpl = args.extractMediaMetadataImpl ?? extractMediaMetadata2;
3081
3130
  const resolveFinalFrameExtractionWindowImpl = args.resolveFinalFrameExtractionWindowImpl ?? resolveFinalFrameExtractionWindow;
3082
- const fps = fpsToNumber4(job.config.fps);
3131
+ const fps = fpsToNumber3(job.config.fps);
3083
3132
  const out = /* @__PURE__ */ new Map();
3084
3133
  mkdirSync2(framesDir, { recursive: true });
3085
3134
  let scratchBytes = 0;
@@ -3616,7 +3665,7 @@ var SequentialCaptureStallError = class extends Error {
3616
3665
  }
3617
3666
  };
3618
3667
  function raceAgainstStall(promise, deadlineMs, input, signal) {
3619
- return new Promise((resolve12, reject) => {
3668
+ return new Promise((resolve13, reject) => {
3620
3669
  const timer = setTimeout(
3621
3670
  () => {
3622
3671
  reject(
@@ -3635,7 +3684,7 @@ function raceAgainstStall(promise, deadlineMs, input, signal) {
3635
3684
  promise.then(
3636
3685
  (value) => {
3637
3686
  clearTimeout(timer);
3638
- resolve12(value);
3687
+ resolve13(value);
3639
3688
  },
3640
3689
  (error) => {
3641
3690
  clearTimeout(timer);
@@ -3917,7 +3966,6 @@ async function runCaptureStreamingStage(input) {
3917
3966
  const reorderBuffer = createFrameReorderBuffer(0, totalFrames);
3918
3967
  if (workerCount > 1) {
3919
3968
  const deParallelStream = forceParallelStream === true || process.env.HF_DE_PARALLEL_STREAM === "true";
3920
- const tasks = deParallelStream ? distributeFramesInterleaved(totalFrames, workerCount, workDir) : distributeFrames(totalFrames, workerCount, workDir);
3921
3969
  const parallelStats = {
3922
3970
  verifyChecked: 0,
3923
3971
  blankSuspects: 0,
@@ -3930,143 +3978,158 @@ async function runCaptureStreamingStage(input) {
3930
3978
  frameTime: (i) => i * job.config.fps.den / job.config.fps.num
3931
3979
  });
3932
3980
  let parallelGuardRan = false;
3933
- let parallelDrainError = null;
3934
- const onFrameBuffer = async (frameIndex, buffer, workerSession) => {
3935
- try {
3936
- if (deParallelStream && workerSession.captureMode === "drawelement") {
3937
- parallelGuardRan = true;
3938
- buffer = await parallelGuard(workerSession, frameIndex, buffer);
3939
- }
3940
- await reorderBuffer.waitForFrame(frameIndex);
3941
- ensureFrameWritten(await currentEncoder.writeFrame(buffer), frameIndex, currentEncoder);
3942
- reorderBuffer.advanceTo(frameIndex + 1);
3943
- } catch (err) {
3944
- const e = err instanceof Error ? err : new Error(String(err));
3945
- if (!parallelDrainError) {
3946
- parallelDrainError = e;
3947
- reorderBuffer.abort(e);
3981
+ let writtenFrames = 0;
3982
+ let resumeFrom = 0;
3983
+ let resumedAfterStall = false;
3984
+ let workerResults;
3985
+ for (; ; ) {
3986
+ const parallelReorder = createFrameReorderBuffer(resumeFrom, totalFrames);
3987
+ const tasks = deParallelStream ? distributeFramesInterleaved(totalFrames - resumeFrom, workerCount, workDir, resumeFrom) : distributeFrames(totalFrames - resumeFrom, workerCount, workDir, resumeFrom);
3988
+ let parallelDrainError = null;
3989
+ const onFrameBuffer = async (frameIndex, buffer, workerSession) => {
3990
+ try {
3991
+ if (deParallelStream && workerSession.captureMode === "drawelement") {
3992
+ parallelGuardRan = true;
3993
+ buffer = await parallelGuard(workerSession, frameIndex, buffer);
3994
+ }
3995
+ await parallelReorder.waitForFrame(frameIndex);
3996
+ ensureFrameWritten(await currentEncoder.writeFrame(buffer), frameIndex, currentEncoder);
3997
+ parallelReorder.advanceTo(frameIndex + 1);
3998
+ writtenFrames = frameIndex + 1;
3999
+ } catch (err) {
4000
+ const e = err instanceof Error ? err : new Error(String(err));
4001
+ if (!parallelDrainError) {
4002
+ parallelDrainError = e;
4003
+ parallelReorder.abort(e);
4004
+ }
4005
+ throw e;
3948
4006
  }
3949
- throw e;
4007
+ };
4008
+ const captureLabel = input.parallelCaptureLabel ?? "worker";
4009
+ let watchdogArmed = false;
4010
+ const stallController = new AbortController();
4011
+ const forwardParentAbort = () => stallController.abort();
4012
+ if (abortSignal) {
4013
+ if (abortSignal.aborted) stallController.abort();
4014
+ else abortSignal.addEventListener("abort", forwardParentAbort, { once: true });
3950
4015
  }
3951
- };
3952
- const captureLabel = input.parallelCaptureLabel ?? "worker";
3953
- let watchdogArmed = false;
3954
- const stallController = new AbortController();
3955
- const forwardParentAbort = () => stallController.abort();
3956
- if (abortSignal) {
3957
- if (abortSignal.aborted) stallController.abort();
3958
- else abortSignal.addEventListener("abort", forwardParentAbort, { once: true });
3959
- }
3960
- const stallTimeoutMs = resolveCaptureStallTimeoutMs();
3961
- let lastCapturedFrames = 0;
3962
- let lastProgressAt = Date.now();
3963
- const workerPhases = /* @__PURE__ */ new Map();
3964
- const phaseSummary = () => [...workerPhases.entries()].sort(([left], [right]) => left - right).map(([workerId, detail]) => `worker=${workerId} ${detail}`).join("; ");
3965
- let stalled = false;
3966
- const stallTimer = setInterval(
3967
- () => {
3968
- if (!watchdogArmed || Date.now() - lastProgressAt <= stallTimeoutMs) return;
3969
- stalled = true;
3970
- const stallErr = new ParallelCaptureStallError({
3971
- variant: "drain",
3972
- captureLabel,
3973
- stallTimeoutMs,
3974
- lastCapturedFrames,
3975
- totalFrames,
3976
- phaseSummary: phaseSummary()
3977
- });
3978
- reorderBuffer.abort(stallErr);
3979
- stallController.abort();
3980
- },
3981
- Math.min(stallTimeoutMs, DE_STALL_POLL_MS)
3982
- );
3983
- let workerResults;
3984
- try {
3985
- workerResults = await executeParallelCapture(
3986
- fileServer.url,
3987
- workDir,
3988
- tasks,
3989
- buildCaptureOptions(),
3990
- createRenderVideoFrameInjector,
3991
- stallController.signal,
3992
- (progress) => {
3993
- if (progress.latestWorkerPhase) {
3994
- const phase = progress.latestWorkerPhase;
3995
- const detail = `phase=${phase.phase} frame=${phase.frameIndex ?? "n/a"} browser=${phase.browserExecutable} version=${phase.browserVersion} CanvasDrawElement=${phase.canvasDrawElement} gpu=${phase.gpuBackend}`;
3996
- workerPhases.set(phase.workerId, detail);
3997
- if (phase.phase === "frame_capture" && !watchdogArmed) {
3998
- watchdogArmed = true;
4016
+ const stallTimeoutMs = resolveCaptureStallTimeoutMs();
4017
+ let lastCapturedFrames = 0;
4018
+ let lastProgressAt = Date.now();
4019
+ const workerPhases = /* @__PURE__ */ new Map();
4020
+ const phaseDetail = (phase) => `phase=${phase.phase} frame=${phase.frameIndex ?? "n/a"} step=${phase.readStep?.() ?? "n/a"} browser=${phase.browserExecutable} version=${phase.browserVersion} CanvasDrawElement=${phase.canvasDrawElement} gpu=${phase.gpuBackend}`;
4021
+ const phaseSummary = () => [...workerPhases.entries()].sort(([left], [right]) => left - right).map(([workerId, detail]) => `worker=${workerId} ${detail}`).join("; ");
4022
+ let stalled = false;
4023
+ const stallTimer = setInterval(
4024
+ () => {
4025
+ if (!watchdogArmed || Date.now() - lastProgressAt <= stallTimeoutMs) return;
4026
+ stalled = true;
4027
+ const stallErr = new ParallelCaptureStallError({
4028
+ variant: "drain",
4029
+ captureLabel,
4030
+ stallTimeoutMs,
4031
+ lastCapturedFrames: resumeFrom + lastCapturedFrames,
4032
+ totalFrames,
4033
+ phaseSummary: phaseSummary()
4034
+ });
4035
+ parallelReorder.abort(stallErr);
4036
+ stallController.abort();
4037
+ },
4038
+ Math.min(stallTimeoutMs, DE_STALL_POLL_MS)
4039
+ );
4040
+ try {
4041
+ workerResults = await executeParallelCapture(
4042
+ fileServer.url,
4043
+ workDir,
4044
+ tasks,
4045
+ buildCaptureOptions(),
4046
+ createRenderVideoFrameInjector,
4047
+ stallController.signal,
4048
+ (progress) => {
4049
+ if (progress.latestWorkerPhase) {
4050
+ const phase = progress.latestWorkerPhase;
4051
+ workerPhases.set(phase.workerId, phaseDetail(phase));
4052
+ if (phase.phase === "frame_capture" && !watchdogArmed) {
4053
+ watchdogArmed = true;
4054
+ lastProgressAt = Date.now();
4055
+ }
4056
+ log.info("[Render] Parallel capture worker phase", {
4057
+ workerId: phase.workerId,
4058
+ phase: phase.phase,
4059
+ frameIndex: phase.frameIndex,
4060
+ browserExecutable: phase.browserExecutable,
4061
+ browserVersion: phase.browserVersion,
4062
+ canvasDrawElement: phase.canvasDrawElement,
4063
+ gpuBackend: phase.gpuBackend
4064
+ });
4065
+ if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
4066
+ return;
4067
+ }
4068
+ if (progress.capturedFrames > lastCapturedFrames) {
4069
+ lastCapturedFrames = progress.capturedFrames;
3999
4070
  lastProgressAt = Date.now();
4071
+ watchdogArmed = true;
4072
+ }
4073
+ const doneFrames = resumeFrom + progress.capturedFrames;
4074
+ job.framesRendered = doneFrames;
4075
+ const frameProgress = doneFrames / totalFrames;
4076
+ reportFrameProgress(
4077
+ job,
4078
+ `Streaming frame ${doneFrames}/${totalFrames} (${workerCount} workers)`,
4079
+ Math.round(25 + frameProgress * 55),
4080
+ onProgress,
4081
+ doneFrames,
4082
+ totalFrames
4083
+ );
4084
+ },
4085
+ onFrameBuffer,
4086
+ // Interleaved DE workers each get their own browser process: co-tenant pages starve
4087
+ // each other's paint events.
4088
+ deParallelStream ? { ...captureCfg, enableBrowserPool: false } : captureCfg,
4089
+ {
4090
+ // A worker died: release parked peers with its original failure, not the stall the
4091
+ // watchdog raises later.
4092
+ onWorkerOperation: (phase) => workerPhases.set(phase.workerId, phaseDetail(phase)),
4093
+ onWorkerFailure: (failure) => {
4094
+ if (parallelDrainError) return;
4095
+ parallelDrainError = failure;
4096
+ parallelReorder.abort(failure);
4000
4097
  }
4001
- log.info("[Render] Parallel capture worker phase", {
4002
- workerId: phase.workerId,
4003
- phase: phase.phase,
4004
- frameIndex: phase.frameIndex,
4005
- browserExecutable: phase.browserExecutable,
4006
- browserVersion: phase.browserVersion,
4007
- canvasDrawElement: phase.canvasDrawElement,
4008
- gpuBackend: phase.gpuBackend
4009
- });
4010
- if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
4011
- return;
4012
- }
4013
- if (progress.capturedFrames > lastCapturedFrames) {
4014
- lastCapturedFrames = progress.capturedFrames;
4015
- lastProgressAt = Date.now();
4016
- watchdogArmed = true;
4017
4098
  }
4018
- job.framesRendered = progress.capturedFrames;
4019
- const frameProgress = progress.capturedFrames / progress.totalFrames;
4020
- reportFrameProgress(
4021
- job,
4022
- `Streaming frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
4023
- Math.round(25 + frameProgress * 55),
4024
- onProgress,
4025
- progress.capturedFrames,
4026
- progress.totalFrames
4099
+ );
4100
+ } catch (err) {
4101
+ if (stalled && abortSignal?.aborted !== true && captureLabel === "BeginFrame" && !resumedAfterStall && writtenFrames > resumeFrom) {
4102
+ resumedAfterStall = true;
4103
+ log.warn(
4104
+ "[Render] Parallel BeginFrame capture stalled; resuming from the first unwritten frame",
4105
+ {
4106
+ resumeFrom: writtenFrames,
4107
+ totalFrames,
4108
+ workerPhases: phaseSummary()
4109
+ }
4027
4110
  );
4028
- },
4029
- onFrameBuffer,
4030
- // Interleaved DE workers each need their own browser PROCESS:
4031
- // pages co-tenant in one browser share one compositor, whose
4032
- // internal frame scheduling deprioritizes non-active pages — their
4033
- // canvas `paint` events starve and the drawElement paint-wait slows
4034
- // ~3x (measured: 86s vs 30s on a 3,245-frame rAF comp). This is
4035
- // Chromium's internal frame-production scheduling on ALL platforms,
4036
- // NOT the Linux-only HeadlessExperimental.beginFrame capture mode.
4037
- deParallelStream ? { ...captureCfg, enableBrowserPool: false } : captureCfg,
4038
- {
4039
- // A worker died (Target closed, Page crashed, launch failure).
4040
- // Its frames are gone and peers are parked in waitForFrame on the
4041
- // first of them; release them with the ORIGINAL failure so the
4042
- // orchestrator sees a transient browser error it knows how to
4043
- // retry, not the stall the watchdog would have raised a minute
4044
- // later. First failure wins, same as the drain guard above.
4045
- onWorkerFailure: (failure) => {
4046
- if (parallelDrainError) return;
4047
- parallelDrainError = failure;
4048
- reorderBuffer.abort(failure);
4049
- }
4111
+ resumeFrom = writtenFrames;
4112
+ continue;
4050
4113
  }
4051
- );
4052
- } catch (err) {
4053
- if (parallelDrainError) throw poolFailure(parallelDrainError, probeSession);
4054
- if (stalled && abortSignal?.aborted !== true) {
4055
- throw new ParallelCaptureStallError({
4056
- variant: "pool",
4057
- captureLabel,
4058
- stallTimeoutMs,
4059
- lastCapturedFrames,
4060
- totalFrames,
4061
- phaseSummary: phaseSummary()
4062
- });
4114
+ if (parallelDrainError) throw poolFailure(parallelDrainError, probeSession);
4115
+ if (stalled && abortSignal?.aborted !== true) {
4116
+ throw new ParallelCaptureStallError({
4117
+ variant: "pool",
4118
+ captureLabel,
4119
+ stallTimeoutMs,
4120
+ lastCapturedFrames: resumeFrom + lastCapturedFrames,
4121
+ totalFrames,
4122
+ phaseSummary: phaseSummary()
4123
+ });
4124
+ }
4125
+ throw poolFailure(err, probeSession);
4126
+ } finally {
4127
+ clearInterval(stallTimer);
4128
+ abortSignal?.removeEventListener("abort", forwardParentAbort);
4063
4129
  }
4064
- throw poolFailure(err, probeSession);
4065
- } finally {
4066
- clearInterval(stallTimer);
4067
- abortSignal?.removeEventListener("abort", forwardParentAbort);
4130
+ if (parallelDrainError) throw poolFailure(parallelDrainError, probeSession);
4131
+ break;
4068
4132
  }
4069
- if (parallelDrainError) throw poolFailure(parallelDrainError, probeSession);
4070
4133
  pushWorkerDedupPerfs(workerResults, dedupPerfs);
4071
4134
  const workerPageErrors = [];
4072
4135
  pushWorkerPageErrors(workerResults, workerPageErrors);
@@ -4227,7 +4290,7 @@ function segmentEncoderOptions(base, segment) {
4227
4290
  return {
4228
4291
  ...base,
4229
4292
  lockGopForChunkConcat: true,
4230
- gopSize: segment.endFrame - segment.startFrame
4293
+ gopSize: Math.min(base.gopSize ?? Infinity, segment.endFrame - segment.startFrame)
4231
4294
  };
4232
4295
  }
4233
4296
  var SegmentEncoderSpawnError = class extends Error {
@@ -4321,6 +4384,12 @@ function defaultSessionFactory(input) {
4321
4384
  let probeSession = input.probeSession;
4322
4385
  return {
4323
4386
  create: async () => {
4387
+ const probeCannotCaptureEveryFrame = Boolean(probeSession?.deRiskFrames?.size);
4388
+ if (probeSession && probeCannotCaptureEveryFrame) {
4389
+ await closeCaptureSession2(probeSession).catch(() => {
4390
+ });
4391
+ probeSession = null;
4392
+ }
4324
4393
  const session = probeSession ?? await openSegmentedSession(input);
4325
4394
  if (probeSession) {
4326
4395
  openSegmentedSessionReuse(input, probeSession);
@@ -4651,8 +4720,10 @@ function computeSegmentPlanHash(input) {
4651
4720
  input.motionBlur,
4652
4721
  input.pageMotionBlur ?? "",
4653
4722
  input.captureQuality === void 0 ? "" : String(input.captureQuality)
4654
- ].join(" ");
4655
- return createHash2("sha256").update(ordered).digest("hex").slice(0, 16);
4723
+ ];
4724
+ const delivery = [input.maxBitrate, input.bufferSize, input.gopSize].map((v) => v ?? "");
4725
+ if (delivery.some((v) => v !== "")) ordered.push(...delivery.map(String));
4726
+ return createHash2("sha256").update(ordered.join(" ")).digest("hex").slice(0, 16);
4656
4727
  }
4657
4728
  function segmentDirFor(projectRendersDir, planHash) {
4658
4729
  return join11(projectRendersDir, ".hf-segments", planHash);
@@ -4799,7 +4870,7 @@ function resolveVideoCaptureBeyondViewport(videoCount) {
4799
4870
 
4800
4871
  // src/services/render/captureCost.ts
4801
4872
  import { join as join12 } from "node:path";
4802
- import { fpsToNumber as fpsToNumber5 } from "@hyperframes/core";
4873
+ import { fpsToNumber as fpsToNumber4 } from "@hyperframes/core";
4803
4874
  import {
4804
4875
  calculateOptimalWorkers,
4805
4876
  captureFrameToBuffer as captureFrameToBuffer3,
@@ -5003,7 +5074,7 @@ async function runCaptureCalibration(input) {
5003
5074
  let probeSession = input.probeSession;
5004
5075
  let forceScreenshot = input.forceScreenshot;
5005
5076
  let lastBrowserConsole = [];
5006
- const fps = fpsToNumber5(job.config.fps);
5077
+ const fps = fpsToNumber4(job.config.fps);
5007
5078
  const sessionRef = { current: null };
5008
5079
  const runOneCalibration = async (sessionDir, sessionCfg) => {
5009
5080
  log.info("Launching browser for capture calibration...");
@@ -5463,7 +5534,7 @@ function emitFallbackCaptureProfile(recorder, perfSummaries, env = process.env)
5463
5534
  // src/services/render/videoFrameCoverage.ts
5464
5535
  import { parseHTML } from "linkedom";
5465
5536
  import {
5466
- fpsToNumber as fpsToNumber6,
5537
+ fpsToNumber as fpsToNumber5,
5467
5538
  normalizeRateSpec,
5468
5539
  sourceTimeAt,
5469
5540
  toFps
@@ -5498,7 +5569,7 @@ function resolveVideoCoverageThreshold(envValue = process.env.HF_VIDEO_COVERAGE_
5498
5569
  return parsed;
5499
5570
  }
5500
5571
  function expectedFramesForClip(start, end, fps, rounding = "ceil") {
5501
- const fpsValue = fpsToNumber6(toFps(fps));
5572
+ const fpsValue = fpsToNumber5(toFps(fps));
5502
5573
  if (!Number.isFinite(start) || !Number.isFinite(end) || !Number.isFinite(fpsValue)) return 0;
5503
5574
  if (fpsValue <= 0) return 0;
5504
5575
  const duration = Math.max(0, end - start);
@@ -5563,15 +5634,15 @@ function countAuthoredTimedClips(html) {
5563
5634
  }
5564
5635
 
5565
5636
  // src/services/render/stages/compileStage.ts
5566
- import { join as join15 } from "node:path";
5637
+ import { join as join16 } from "node:path";
5567
5638
  import { suggestMatchingPreset } from "@hyperframes/core";
5568
5639
 
5569
5640
  // src/services/htmlCompiler.ts
5570
5641
  import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
5571
- import { createHash as createHash5 } from "node:crypto";
5572
- import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync6 } from "fs";
5573
- import { join as join14, dirname as dirname6, resolve as resolve6, basename as basename4, relative as relative2 } from "path";
5574
- import { parseHTML as parseHTML4 } from "linkedom";
5642
+ import { createHash as createHash6 } from "node:crypto";
5643
+ import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7 } from "fs";
5644
+ import { join as join15, dirname as dirname7, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
5645
+ import { parseHTML as parseHTML5 } from "linkedom";
5575
5646
  import {
5576
5647
  COMPOSITION_SOURCE_URL,
5577
5648
  compileTimingAttrs,
@@ -5847,7 +5918,7 @@ function readLoopOverride(el) {
5847
5918
  }
5848
5919
  return null;
5849
5920
  }
5850
- function resolveGifSourcePath(src, options) {
5921
+ function resolveLocalImageSourcePath(src, options) {
5851
5922
  const trimmed = src.trim();
5852
5923
  if (!trimmed || trimmed.startsWith("data:")) return null;
5853
5924
  const { basePath } = splitUrlSuffix(trimmed);
@@ -6070,7 +6141,7 @@ async function prepareAnimatedGifInputs(html, options) {
6070
6141
  const img = images[i];
6071
6142
  const src = (img.getAttribute("src") || "").trim();
6072
6143
  if (!hasGifExtension(src)) continue;
6073
- const sourcePath = resolveGifSourcePath(src, options);
6144
+ const sourcePath = resolveLocalImageSourcePath(src, options);
6074
6145
  if (!sourcePath) continue;
6075
6146
  const bytes = readFileSync5(sourcePath);
6076
6147
  const metadata = parseAnimatedGifMetadata(bytes);
@@ -6118,19 +6189,74 @@ async function prepareAnimatedGifInputs(html, options) {
6118
6189
  };
6119
6190
  }
6120
6191
 
6121
- // src/services/htmlCompiler.ts
6122
- import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-server/manual-edits-render-script";
6123
- import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
6124
-
6125
- // src/services/assetMediaType.ts
6126
- import { existsSync as existsSync9 } from "node:fs";
6192
+ // src/services/heifImagePrep.ts
6193
+ import { createHash as createHash5 } from "node:crypto";
6127
6194
  import {
6128
- fingerprintElementId,
6129
- isNotMediaPayload,
6130
- NotMediaPayloadError,
6131
- probeMediaProfile,
6132
- resolveProjectRelativeSrc as resolveProjectRelativeSrc2
6133
- } from "@hyperframes/engine";
6195
+ closeSync as closeSync4,
6196
+ copyFileSync as copyFileSync3,
6197
+ mkdirSync as mkdirSync6,
6198
+ openSync as openSync4,
6199
+ readFileSync as readFileSync6,
6200
+ readSync as readSync3,
6201
+ rmSync as rmSync8,
6202
+ statSync as statSync6,
6203
+ writeFileSync as writeFileSync4
6204
+ } from "node:fs";
6205
+ import { dirname as dirname6, join as join14, resolve as resolve6 } from "node:path";
6206
+ import { parseHTML as parseHTML4 } from "linkedom";
6207
+ import { renameWithRetry as renameWithRetry4, runFfmpeg as runFfmpeg3, runFfprobe } from "@hyperframes/engine";
6208
+
6209
+ // src/services/heifRotation.ts
6210
+ function* boxes(buf, start, end) {
6211
+ let at = start;
6212
+ while (at + 8 <= end) {
6213
+ let size = buf.readUInt32BE(at);
6214
+ let header = 8;
6215
+ if (size === 1 && at + 16 <= end) {
6216
+ size = Number(buf.readBigUInt64BE(at + 8));
6217
+ header = 16;
6218
+ } else if (size === 0) {
6219
+ size = end - at;
6220
+ }
6221
+ if (size < header || at + size > end) return;
6222
+ yield { type: buf.toString("latin1", at + 4, at + 8), start: at + header, end: at + size };
6223
+ at += size;
6224
+ }
6225
+ }
6226
+ function child(buf, parent, type, skip = 0) {
6227
+ return [...boxes(buf, parent.start + skip, parent.end)].filter((box2) => box2.type === type);
6228
+ }
6229
+ function readHeifPrimaryRotation(buf) {
6230
+ try {
6231
+ const root = { type: "", start: 0, end: buf.length };
6232
+ const meta = child(buf, root, "meta")[0];
6233
+ const pitm = meta && child(buf, meta, "pitm", 4)[0];
6234
+ const iprp = meta && child(buf, meta, "iprp", 4)[0];
6235
+ const ipco = iprp && child(buf, iprp, "ipco")[0];
6236
+ if (!pitm || !ipco || !iprp) return 0;
6237
+ const primaryId = buf[pitm.start] === 0 ? buf.readUInt16BE(pitm.start + 4) : buf.readUInt32BE(pitm.start + 4);
6238
+ const props = [...boxes(buf, ipco.start, ipco.end)];
6239
+ for (const ipma of child(buf, iprp, "ipma")) {
6240
+ const version = buf[ipma.start];
6241
+ const wide = (buf[ipma.start + 3] & 1) === 1;
6242
+ let at = ipma.start + 8;
6243
+ for (let n = buf.readUInt32BE(ipma.start + 4); n > 0 && at < ipma.end; n--) {
6244
+ const id = version < 1 ? buf.readUInt16BE(at) : buf.readUInt32BE(at);
6245
+ at += version < 1 ? 2 : 4;
6246
+ const count = buf[at++];
6247
+ for (let i = 0; i < count; i++) {
6248
+ const index = wide ? buf.readUInt16BE(at) & 32767 : buf[at] & 127;
6249
+ at += wide ? 2 : 1;
6250
+ const prop = props[index - 1];
6251
+ if (id === primaryId && prop?.type === "irot")
6252
+ return (buf[prop.start] & 3) * 90;
6253
+ }
6254
+ }
6255
+ }
6256
+ } catch {
6257
+ }
6258
+ return 0;
6259
+ }
6134
6260
 
6135
6261
  // src/utils/semaphore.ts
6136
6262
  var Semaphore = class {
@@ -6145,10 +6271,10 @@ var Semaphore = class {
6145
6271
  this.active++;
6146
6272
  return () => this.release();
6147
6273
  }
6148
- return new Promise((resolve12) => {
6274
+ return new Promise((resolve13) => {
6149
6275
  this.queue.push(() => {
6150
6276
  this.active++;
6151
- resolve12(() => this.release());
6277
+ resolve13(() => this.release());
6152
6278
  });
6153
6279
  });
6154
6280
  }
@@ -6179,7 +6305,229 @@ async function withMediaProbeSlot(operation) {
6179
6305
  }
6180
6306
  }
6181
6307
 
6308
+ // src/services/heifImagePrep.ts
6309
+ var PREPARED_HEIF_SUBDIR = "_heif_images";
6310
+ var CACHE_SCHEMA2 = "hfheif-v1";
6311
+ var DEFAULT_TIMEOUT_MS = 12e4;
6312
+ var HEIF_IMAGE_CONVERSION_FAILED = "HEIF_IMAGE_CONVERSION_FAILED";
6313
+ var HEIF_TOOL_UNAVAILABLE = "HEIF_TOOL_UNAVAILABLE";
6314
+ var HEIF_BRANDS = /* @__PURE__ */ new Set(["heic", "heix", "hevc", "hevx", "heim", "heis", "mif1", "msf1"]);
6315
+ var AVIF_BRANDS = /* @__PURE__ */ new Set(["avif", "avis"]);
6316
+ var DATA_URL_RE = /^data:image\/(?:heic|heif)(?:-sequence)?;base64,(.*)$/is;
6317
+ var HeifImageConversionError = class extends Error {
6318
+ code = HEIF_IMAGE_CONVERSION_FAILED;
6319
+ owner = "user";
6320
+ retryable = false;
6321
+ constructor(label, reason) {
6322
+ super(
6323
+ `Could not convert HEIC/HEIF image ${label} to JPEG: ${reason}. Re-export the photo as JPEG or PNG and update its src.`
6324
+ );
6325
+ this.name = "HeifImageConversionError";
6326
+ }
6327
+ };
6328
+ var HeifToolUnavailableError = class extends Error {
6329
+ code = HEIF_TOOL_UNAVAILABLE;
6330
+ owner = "system";
6331
+ retryable = false;
6332
+ constructor(label, reason) {
6333
+ super(
6334
+ `Could not convert HEIC/HEIF image ${label}: ${reason}. Converting HEIC needs ffmpeg and ffprobe 7.0 or newer with the hevc decoder.`
6335
+ );
6336
+ this.name = "HeifToolUnavailableError";
6337
+ }
6338
+ };
6339
+ var TOOL_FAILURE_RE = /(?:ffmpeg|ffprobe) not found|ENOENT|Option not found|Unrecognized option|Unknown decoder|Decoder \S+ not found/i;
6340
+ function isHeifBytes(head) {
6341
+ const bytes = Buffer.from(head.buffer, head.byteOffset, head.byteLength);
6342
+ if (bytes.length < 16 || bytes.toString("ascii", 4, 8) !== "ftyp") return false;
6343
+ const boxEnd = Math.min(bytes.readUInt32BE(0), bytes.length);
6344
+ if (boxEnd < 16) return false;
6345
+ const brands = [bytes.toString("ascii", 8, 12)];
6346
+ for (let offset = 16; offset + 4 <= boxEnd; offset += 4) {
6347
+ brands.push(bytes.toString("ascii", offset, offset + 4));
6348
+ }
6349
+ return !brands.some((b) => AVIF_BRANDS.has(b)) && brands.some((b) => HEIF_BRANDS.has(b));
6350
+ }
6351
+ function readHead(path) {
6352
+ let fd;
6353
+ try {
6354
+ fd = openSync4(path, "r");
6355
+ const buf = Buffer.alloc(4096);
6356
+ return buf.subarray(0, readSync3(fd, buf, 0, buf.length, 0));
6357
+ } catch {
6358
+ return null;
6359
+ } finally {
6360
+ if (fd !== void 0) closeSync4(fd);
6361
+ }
6362
+ }
6363
+ async function probeJson(path, withGroups) {
6364
+ const stdout = await runFfprobe(
6365
+ path,
6366
+ withGroups ? ["-show_stream_groups", "-show_streams", "-of", "json"] : ["-show_streams", "-of", "json"]
6367
+ );
6368
+ return JSON.parse(stdout);
6369
+ }
6370
+ var ROTATION_FILTER = {
6371
+ 0: "",
6372
+ 90: "transpose=2",
6373
+ 180: "hflip,vflip",
6374
+ 270: "transpose=1"
6375
+ };
6376
+ function primaryGrid(probed) {
6377
+ const groups = probed.stream_groups ?? [];
6378
+ return (groups.find((g) => g.disposition?.default === 1) ?? groups[0])?.components?.[0];
6379
+ }
6380
+ async function convertHeifToJpeg(request) {
6381
+ const probed = await probeJson(request.inputPath, true);
6382
+ const grid = primaryGrid(probed);
6383
+ const rotation = readHeifPrimaryRotation(readFileSync6(request.inputPath));
6384
+ const filters = [];
6385
+ let inputs = [];
6386
+ let size = probed.streams?.[0];
6387
+ if (grid?.subcomponents?.length) {
6388
+ size = grid;
6389
+ const layout = grid.subcomponents.map(
6390
+ (t) => `${t.tile_horizontal_offset}_${t.tile_vertical_offset}`
6391
+ );
6392
+ inputs = grid.subcomponents.map((t) => `[0:${t.stream_index}]`);
6393
+ filters.push(
6394
+ `${inputs.join("")}xstack=inputs=${inputs.length}:layout=${layout.join("|")}`,
6395
+ `crop=${grid.width}:${grid.height}:${grid.horizontal_offset ?? 0}:${grid.vertical_offset ?? 0}`
6396
+ );
6397
+ }
6398
+ if (!size?.width || !size.height) throw new Error("no declared image size");
6399
+ if (rotation && grid) filters.push(ROTATION_FILTER[rotation]);
6400
+ const mapArgs = filters.length > 0 ? ["-filter_complex", `${filters.join(",")}[v]`, "-map", "[v]"] : [];
6401
+ const result = await runFfmpeg3(
6402
+ [
6403
+ "-hide_banner",
6404
+ "-loglevel",
6405
+ "error",
6406
+ "-i",
6407
+ request.inputPath,
6408
+ ...mapArgs,
6409
+ "-frames:v",
6410
+ "1",
6411
+ "-q:v",
6412
+ "2",
6413
+ "-f",
6414
+ "mjpeg",
6415
+ "-y",
6416
+ request.outputPath
6417
+ ],
6418
+ { timeout: request.timeoutMs ?? DEFAULT_TIMEOUT_MS }
6419
+ );
6420
+ if (!result.success) {
6421
+ throw result.error ?? new Error(`ffmpeg exited ${result.exitCode}: ${result.stderr.slice(-300)}`);
6422
+ }
6423
+ const out = (await probeJson(request.outputPath, false)).streams?.[0];
6424
+ const same = out?.width === size.width && out.height === size.height;
6425
+ const swapped = out?.width === size.height && out.height === size.width;
6426
+ if (!same && !swapped) {
6427
+ throw new Error(
6428
+ `wrong size: decoded ${out?.width}x${out?.height}, file declares ${size.width}x${size.height}`
6429
+ );
6430
+ }
6431
+ }
6432
+ function resolveImageSource(src, options) {
6433
+ const data = DATA_URL_RE.exec(src);
6434
+ if (data) {
6435
+ const bytes = Buffer.from(data[1], "base64");
6436
+ return isHeifBytes(bytes.subarray(0, 4096)) ? { path: null, bytes, label: '"inline image"' } : null;
6437
+ }
6438
+ const path = resolveLocalImageSourcePath(src, options);
6439
+ const head = path ? readHead(path) : null;
6440
+ if (!path || !head || !isHeifBytes(head)) return null;
6441
+ return { path, bytes: readFileSync6(path), label: `"${splitUrlSuffix(src).basePath}"` };
6442
+ }
6443
+ function isUsableFile2(path) {
6444
+ try {
6445
+ return statSync6(path).size > 0;
6446
+ } catch {
6447
+ return false;
6448
+ }
6449
+ }
6450
+ async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
6451
+ mkdirSync6(dirname6(cachePath), { recursive: true });
6452
+ mkdirSync6(dirname6(outputPath), { recursive: true });
6453
+ if (!isUsableFile2(cachePath)) {
6454
+ const tmpPath = `${cachePath}.tmp-${process.pid}-${Date.now()}`;
6455
+ const inlinePath = `${tmpPath}.heic`;
6456
+ const inputPath = source.path ?? inlinePath;
6457
+ try {
6458
+ if (!source.path) writeFileSync4(inlinePath, source.bytes);
6459
+ await (options.transcode ?? convertHeifToJpeg)({
6460
+ inputPath,
6461
+ outputPath: tmpPath,
6462
+ timeoutMs: options.timeoutMs
6463
+ });
6464
+ if (!isUsableFile2(tmpPath)) throw new Error("conversion produced an empty file");
6465
+ renameWithRetry4(tmpPath, cachePath);
6466
+ } catch (error) {
6467
+ const reason = error instanceof Error ? error.message : String(error);
6468
+ throw TOOL_FAILURE_RE.test(reason) ? new HeifToolUnavailableError(source.label, reason) : new HeifImageConversionError(source.label, reason);
6469
+ } finally {
6470
+ rmSync8(tmpPath, { force: true });
6471
+ rmSync8(inlinePath, { force: true });
6472
+ }
6473
+ }
6474
+ if (resolve6(outputPath) !== resolve6(cachePath) && !isUsableFile2(outputPath)) {
6475
+ copyFileSync3(cachePath, outputPath);
6476
+ }
6477
+ }
6478
+ async function prepareHeifImageInputs(html, options) {
6479
+ const outputDir = options.outputDir ?? join14(options.downloadDir, PREPARED_HEIF_SUBDIR);
6480
+ const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_HEIF_SUBDIR);
6481
+ const cacheDir = options.cacheDir ?? outputDir;
6482
+ const preparedAssets = /* @__PURE__ */ new Map();
6483
+ const preparedImages = [];
6484
+ if (!/<img\b/i.test(html)) return { html, preparedAssets, preparedImages };
6485
+ const { document: document2 } = parseHTML4(html);
6486
+ const found = [];
6487
+ for (const img of Array.from(document2.querySelectorAll("img[src]"))) {
6488
+ const src = (img.getAttribute("src") || "").trim();
6489
+ const source = src ? resolveImageSource(src, options) : null;
6490
+ if (!source) continue;
6491
+ const hash = createHash5("sha256").update(CACHE_SCHEMA2).update("\0").update(source.bytes).digest("hex");
6492
+ found.push({ img, src, source, filename: `${CACHE_SCHEMA2}-${hash.slice(0, 24)}.jpg` });
6493
+ }
6494
+ if (found.length === 0) return { html, preparedAssets, preparedImages };
6495
+ const unique = new Map(found.map((item) => [item.filename, item.source]));
6496
+ const failed = /* @__PURE__ */ new Set();
6497
+ await Promise.all(
6498
+ [...unique].map(
6499
+ ([filename, source]) => withMediaProbeSlot(
6500
+ () => ensurePreparedJpeg(source, join14(cacheDir, filename), join14(outputDir, filename), options)
6501
+ ).catch((error) => {
6502
+ if (!options.onFailure) throw error;
6503
+ failed.add(filename);
6504
+ options.onFailure(error);
6505
+ })
6506
+ )
6507
+ );
6508
+ for (const { img, src, filename } of found) {
6509
+ if (failed.has(filename)) continue;
6510
+ const outputSrc = `${outputSrcPrefix}/${filename}`;
6511
+ img.setAttribute("src", outputSrc);
6512
+ preparedAssets.set(outputSrc, join14(outputDir, filename));
6513
+ preparedImages.push({ sourceSrc: src, outputSrc, outputPath: join14(outputDir, filename) });
6514
+ }
6515
+ return { html: document2.toString(), preparedAssets, preparedImages };
6516
+ }
6517
+
6518
+ // src/services/htmlCompiler.ts
6519
+ import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-server/manual-edits-render-script";
6520
+ import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
6521
+
6182
6522
  // src/services/assetMediaType.ts
6523
+ import { existsSync as existsSync9 } from "node:fs";
6524
+ import {
6525
+ fingerprintElementId,
6526
+ isNotMediaPayload,
6527
+ NotMediaPayloadError,
6528
+ probeMediaProfile,
6529
+ resolveProjectRelativeSrc as resolveProjectRelativeSrc2
6530
+ } from "@hyperframes/engine";
6183
6531
  var ASSET_MEDIA_TYPE_MISMATCH = "ASSET_MEDIA_TYPE_MISMATCH";
6184
6532
  var AssetMediaTypeMismatchError = class extends Error {
6185
6533
  code = ASSET_MEDIA_TYPE_MISMATCH;
@@ -6289,7 +6637,7 @@ function logRemoteDownloadTelemetry(event) {
6289
6637
  }
6290
6638
  var INFERRED_MEDIA_DURATION_ATTR = "data-hf-inferred-duration";
6291
6639
  function parseSubCompHtmlForValidity(html) {
6292
- return parseHTML4(html).document;
6640
+ return parseHTML5(html).document;
6293
6641
  }
6294
6642
  function endsWithSourceUrl(code) {
6295
6643
  const trimmed = code.trimEnd();
@@ -6297,7 +6645,7 @@ function endsWithSourceUrl(code) {
6297
6645
  return lastLine.startsWith("//# sourceURL=") || lastLine.startsWith("//@ sourceURL=");
6298
6646
  }
6299
6647
  function prepareCompositionScripts(html) {
6300
- const { document: document2 } = parseHTML4(html);
6648
+ const { document: document2 } = parseHTML5(html);
6301
6649
  for (const el of document2.querySelectorAll("script:not([src])")) {
6302
6650
  const isModule = (el.getAttribute("type") || "").trim().toLowerCase() === "module";
6303
6651
  const runsAsScript = isModule || isJavaScriptType(el);
@@ -6334,14 +6682,14 @@ Check that each file referenced by data-composition-src contains valid HTML with
6334
6682
  }
6335
6683
  };
6336
6684
  function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */ new Set()) {
6337
- const { document: document2 } = parseHTML4(html);
6685
+ const { document: document2 } = parseHTML5(html);
6338
6686
  const hosts = [...document2.querySelectorAll("[data-composition-src]")];
6339
6687
  const problems = [];
6340
6688
  for (const el of hosts) {
6341
6689
  const srcPath = el.getAttribute("data-composition-src");
6342
6690
  if (!srcPath) continue;
6343
6691
  if (isUnresolvedAssetPlaceholder(srcPath)) continue;
6344
- const filePath = resolve6(projectDir, srcPath);
6692
+ const filePath = resolve7(projectDir, srcPath);
6345
6693
  if (visited.has(filePath)) continue;
6346
6694
  const read = readProjectFile(filePath);
6347
6695
  if (read.kind === "missing") {
@@ -6397,7 +6745,7 @@ function runtimeRoot(document2) {
6397
6745
  }
6398
6746
  function detectRenderModeHints(html, pageMotionBlur) {
6399
6747
  const reasons = [];
6400
- const { document: document2 } = parseHTML4(html);
6748
+ const { document: document2 } = parseHTML5(html);
6401
6749
  if (document2.querySelector("canvas[layoutsubtree]")) {
6402
6750
  reasons.push({
6403
6751
  code: "htmlInCanvas",
@@ -6439,7 +6787,7 @@ function detectThreeDTransformUsage(html) {
6439
6787
  }
6440
6788
  var BACKGROUND_IMAGE_DECL_PATTERN = /(?:^|;|\{)\s*background(?:-image)?\s*:[^;}]*(?:\bgradient\s*\(|url\s*\()/i;
6441
6789
  function detectAncestorBackgroundImage(html) {
6442
- const { document: document2 } = parseHTML4(html);
6790
+ const { document: document2 } = parseHTML5(html);
6443
6791
  const root = document2.querySelector("[data-composition-id]");
6444
6792
  if (!root) return false;
6445
6793
  const ancestors = [];
@@ -6483,10 +6831,10 @@ function detectShaderTransitionUsage(html) {
6483
6831
  }
6484
6832
  return false;
6485
6833
  }
6486
- async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath) {
6834
+ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath, probes) {
6487
6835
  let filePath = src;
6488
6836
  if (isHttpUrl(src)) {
6489
- if (!existsSync10(downloadDir)) mkdirSync6(downloadDir, { recursive: true });
6837
+ if (!existsSync10(downloadDir)) mkdirSync7(downloadDir, { recursive: true });
6490
6838
  try {
6491
6839
  filePath = await downloadToTemp(src, downloadDir, void 0, void 0, void 0, {
6492
6840
  onTelemetry: logRemoteDownloadTelemetry
@@ -6496,7 +6844,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
6496
6844
  }
6497
6845
  } else {
6498
6846
  filePath = decodedUrlPath(src);
6499
- if (!filePath.startsWith("/")) filePath = join14(baseDir, filePath);
6847
+ if (!filePath.startsWith("/")) filePath = join15(baseDir, filePath);
6500
6848
  filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
6501
6849
  }
6502
6850
  if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
@@ -6510,50 +6858,57 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
6510
6858
  if (error instanceof Error && error.stack) wrapped.stack = error.stack;
6511
6859
  return wrapped;
6512
6860
  };
6513
- return withMediaProbeSlot(async () => {
6514
- let profile;
6861
+ const probe = () => withMediaProbeSlot(async () => {
6515
6862
  try {
6516
6863
  await assertMediaPayload(filePath, elementIdentity);
6517
- profile = await probeMediaProfile2(filePath);
6518
- } catch (error) {
6519
- if (tagName !== "video") {
6520
- if (error instanceof NotMediaPayloadError2) {
6521
- log?.warn(
6522
- `[compile] Audio "${elementIdentity}" (${src}) is a text document, not a media file, so the element is dropped from the render. Point it at a rendered media file.`
6523
- );
6524
- }
6525
- return { duration: null, resolvedPath: filePath };
6864
+ const profile = await probeMediaProfile2(filePath);
6865
+ if (tagName === "video") {
6866
+ return { profile, fileDuration: (await extractMediaMetadata(filePath)).durationSeconds };
6526
6867
  }
6527
- throw withSrcContext(error);
6528
- }
6529
- assertAssetMediaTypeProfile(tagName === "video" ? "video" : "audio", profile, elementIdentity);
6530
- let metadata;
6531
- if (tagName === "video") {
6532
6868
  try {
6533
- metadata = await extractMediaMetadata(filePath);
6534
- } catch (error) {
6535
- throw withSrcContext(error);
6536
- }
6537
- } else {
6538
- try {
6539
- metadata = await extractAudioMetadata(filePath);
6869
+ return { profile, fileDuration: (await extractAudioMetadata(filePath)).durationSeconds };
6540
6870
  } catch {
6541
- return { duration: null, resolvedPath: filePath };
6871
+ return { profile, fileDuration: null };
6542
6872
  }
6873
+ } catch (error) {
6874
+ return { error };
6543
6875
  }
6544
- const fileDuration = metadata.durationSeconds;
6545
- const duration = resolveNaturalMediaTimelineDurationFromValues(
6546
- fileDuration,
6547
- mediaStart,
6548
- playbackRate
6549
- );
6550
- return { duration, resolvedPath: filePath };
6551
6876
  });
6877
+ const key = `${tagName}\0${filePath}`;
6878
+ let probed = probes?.get(key);
6879
+ if (!probed) {
6880
+ probed = probe();
6881
+ probes?.set(key, probed);
6882
+ }
6883
+ const outcome = await probed;
6884
+ if ("error" in outcome) {
6885
+ if (tagName !== "video") {
6886
+ if (outcome.error instanceof NotMediaPayloadError2) {
6887
+ log?.warn(
6888
+ `[compile] Audio "${elementIdentity}" (${src}) is a text document, not a media file, so the element is dropped from the render. Point it at a rendered media file.`
6889
+ );
6890
+ }
6891
+ return { duration: null, resolvedPath: filePath };
6892
+ }
6893
+ throw withSrcContext(outcome.error);
6894
+ }
6895
+ assertAssetMediaTypeProfile(
6896
+ tagName === "video" ? "video" : "audio",
6897
+ outcome.profile,
6898
+ elementIdentity
6899
+ );
6900
+ if (outcome.fileDuration === null) return { duration: null, resolvedPath: filePath };
6901
+ const duration = resolveNaturalMediaTimelineDurationFromValues(
6902
+ outcome.fileDuration,
6903
+ mediaStart,
6904
+ playbackRate
6905
+ );
6906
+ return { duration, resolvedPath: filePath };
6552
6907
  }
6553
6908
  function markInferredVariableMediaDurations(html, unresolvedMedia) {
6554
6909
  const unresolvedIds = new Set(unresolvedMedia.map((element) => element.id));
6555
6910
  if (unresolvedIds.size === 0) return html;
6556
- const { document: document2 } = parseHTML4(html);
6911
+ const { document: document2 } = parseHTML5(html);
6557
6912
  let changed = false;
6558
6913
  for (const element of document2.querySelectorAll("video[data-var-src], audio[data-var-src]")) {
6559
6914
  if (!unresolvedIds.has(element.id)) continue;
@@ -6562,7 +6917,7 @@ function markInferredVariableMediaDurations(html, unresolvedMedia) {
6562
6917
  }
6563
6918
  return changed ? document2.toString() : html;
6564
6919
  }
6565
- async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocalPath) {
6920
+ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocalPath, probes) {
6566
6921
  const { html: staticCompiled, unresolved } = compileTimingAttrs(html);
6567
6922
  const mediaUnresolved = unresolved.filter(
6568
6923
  (el) => (el.tagName === "video" || el.tagName === "audio") && el.src
@@ -6579,7 +6934,8 @@ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocal
6579
6934
  el.tagName,
6580
6935
  el.id,
6581
6936
  log,
6582
- urlByAbsentLocalPath
6937
+ urlByAbsentLocalPath,
6938
+ probes
6583
6939
  ).then(({ duration }) => ({ id: el.id, duration }))
6584
6940
  )
6585
6941
  );
@@ -6600,7 +6956,9 @@ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocal
6600
6956
  downloadDir,
6601
6957
  el.tagName,
6602
6958
  el.id,
6603
- log
6959
+ log,
6960
+ void 0,
6961
+ probes
6604
6962
  );
6605
6963
  return { id: el.id, tagName: el.tagName, duration: el.duration, maxDuration, src: el.src };
6606
6964
  })
@@ -6622,15 +6980,15 @@ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocal
6622
6980
  compiledHtml = compiledHtml.replace(/(<audio\b[^>]*)\s+crossorigin(?:=["'][^"']*["'])?/gi, "$1");
6623
6981
  return { html: compiledHtml, unresolvedCompositions };
6624
6982
  }
6625
- async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath) {
6983
+ async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath, probes) {
6626
6984
  const subCompositions = /* @__PURE__ */ new Map();
6627
- const { document: document2 } = parseHTML4(html);
6985
+ const { document: document2 } = parseHTML5(html);
6628
6986
  const compEls = document2.querySelectorAll("[data-composition-src]");
6629
6987
  const workItems = [];
6630
6988
  for (const el of compEls) {
6631
6989
  const srcPath = el.getAttribute("data-composition-src");
6632
6990
  if (!srcPath) continue;
6633
- const filePath = resolve6(projectDir, srcPath);
6991
+ const filePath = resolve7(projectDir, srcPath);
6634
6992
  if (visited.has(filePath)) {
6635
6993
  continue;
6636
6994
  }
@@ -6647,17 +7005,19 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
6647
7005
  workItems.map(async (item) => {
6648
7006
  const { html: compiledSub } = await compileHtmlFile(
6649
7007
  rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
6650
- dirname6(item.filePath),
7008
+ dirname7(item.filePath),
6651
7009
  downloadDir,
6652
7010
  void 0,
6653
- urlByAbsentLocalPath
7011
+ urlByAbsentLocalPath,
7012
+ probes
6654
7013
  );
6655
7014
  const nested = await parseSubCompositions(
6656
7015
  compiledSub,
6657
7016
  projectDir,
6658
7017
  downloadDir,
6659
7018
  item.nestedVisited,
6660
- urlByAbsentLocalPath
7019
+ urlByAbsentLocalPath,
7020
+ probes
6661
7021
  );
6662
7022
  return {
6663
7023
  srcPath: item.srcPath,
@@ -6675,7 +7035,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
6675
7035
  return { subCompositions };
6676
7036
  }
6677
7037
  function promoteCssImportsToLinkTags(html) {
6678
- const { document: document2 } = parseHTML4(html);
7038
+ const { document: document2 } = parseHTML5(html);
6679
7039
  const head = document2.querySelector("head");
6680
7040
  if (!head) return html;
6681
7041
  const importRe = /@import\s+url\(\s*['"]?([^'")\s]+)['"]?\s*\)\s*;?/gi;
@@ -6744,7 +7104,7 @@ var ProducerHostIdentityMap = class extends Map {
6744
7104
  }
6745
7105
  };
6746
7106
  function coalesceHeadStylesAndBodyScripts(html) {
6747
- const { document: document2 } = parseHTML4(html);
7107
+ const { document: document2 } = parseHTML5(html);
6748
7108
  const head = document2.querySelector("head");
6749
7109
  const body = document2.querySelector("body");
6750
7110
  if (!head && !body) return html;
@@ -6795,7 +7155,7 @@ function coalesceHeadStylesAndBodyScripts(html) {
6795
7155
  return document2.toString();
6796
7156
  }
6797
7157
  function inlineSubCompositions(html, subCompositions, projectDir, variableOverrides = {}) {
6798
- const { document: document2 } = parseHTML4(html);
7158
+ const { document: document2 } = parseHTML5(html);
6799
7159
  const head = document2.querySelector("head");
6800
7160
  const body = document2.querySelector("body");
6801
7161
  const hosts = Array.from(document2.querySelectorAll("[data-composition-src]"));
@@ -6824,17 +7184,17 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
6824
7184
  resolveHtml: (srcPath) => {
6825
7185
  let compHtml = subCompositions.get(srcPath) || null;
6826
7186
  if (!compHtml) {
6827
- const read = readProjectFile(resolve6(projectDir, srcPath));
7187
+ const read = readProjectFile(resolve7(projectDir, srcPath));
6828
7188
  if (read.kind === "file") {
6829
7189
  compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
6830
7190
  }
6831
7191
  }
6832
7192
  return compHtml;
6833
7193
  },
6834
- parseHtml: (htmlStr) => parseHTML4(htmlStr).document,
7194
+ parseHtml: (htmlStr) => parseHTML5(htmlStr).document,
6835
7195
  // Mirrors the preview bundler: a sub-composition's SIBLING assets resolve
6836
7196
  // against its own directory, project-root refs stay as authored.
6837
- assetExists: (path) => existsSync10(resolve6(projectDir, path)),
7197
+ assetExists: (path) => existsSync10(resolve7(projectDir, path)),
6838
7198
  // Preserve the authored root wrapper as a child of the host, matching
6839
7199
  // the preview bundler's shape (htmlBundler.ts's prepareFlattenedInnerRoot,
6840
7200
  // which the runtime compositionLoader mirrors with its own copy for the
@@ -6935,7 +7295,7 @@ ${html}
6935
7295
  </html>`;
6936
7296
  }
6937
7297
  function injectTextRenderingRule(html) {
6938
- const { document: document2 } = parseHTML4(html);
7298
+ const { document: document2 } = parseHTML5(html);
6939
7299
  const head = document2.querySelector("head");
6940
7300
  if (!head) return html;
6941
7301
  if (document2.querySelector("style[data-hyperframes-text-rendering]")) {
@@ -6959,7 +7319,7 @@ function matchesScriptIntegrity(bytes, metadata) {
6959
7319
  (alg) => hashes.some((hash) => hash[1]?.toLowerCase() === alg)
6960
7320
  );
6961
7321
  if (!strongest) return true;
6962
- const actual = createHash5(strongest).update(bytes).digest();
7322
+ const actual = createHash6(strongest).update(bytes).digest();
6963
7323
  return hashes.some(
6964
7324
  (hash) => hash[1]?.toLowerCase() === strongest && actual.equals(Buffer.from(hash[2], "base64"))
6965
7325
  );
@@ -6967,7 +7327,7 @@ function matchesScriptIntegrity(bytes, metadata) {
6967
7327
  async function inlineExternalScripts(html) {
6968
7328
  const fullHtml = ensureFullDocument(html);
6969
7329
  const wrappedFragment = fullHtml !== html;
6970
- const { document: document2 } = parseHTML4(fullHtml);
7330
+ const { document: document2 } = parseHTML5(fullHtml);
6971
7331
  const scripts = document2.querySelectorAll("script[src]");
6972
7332
  const externalScripts = [];
6973
7333
  for (const el of scripts) {
@@ -7016,12 +7376,12 @@ ${safeText}
7016
7376
  return wrappedFragment ? document2.body.innerHTML || "" : document2.toString();
7017
7377
  }
7018
7378
  function collectExternalAssets(html, projectDir) {
7019
- const absProjectDir = resolve6(projectDir);
7379
+ const absProjectDir = resolve7(projectDir);
7020
7380
  const externalAssets = /* @__PURE__ */ new Map();
7021
7381
  function processPath(rawPath) {
7022
7382
  const trimmed = rawPath.trim();
7023
7383
  if (isNonRelativeUrl(trimmed)) return null;
7024
- const absPath = resolve6(absProjectDir, trimmed);
7384
+ const absPath = resolve7(absProjectDir, trimmed);
7025
7385
  if (isPathInside2(absPath, absProjectDir)) {
7026
7386
  return null;
7027
7387
  }
@@ -7030,7 +7390,7 @@ function collectExternalAssets(html, projectDir) {
7030
7390
  externalAssets.set(safeKey, absPath);
7031
7391
  return safeKey;
7032
7392
  }
7033
- const { document: document2 } = parseHTML4(html);
7393
+ const { document: document2 } = parseHTML5(html);
7034
7394
  for (const el of document2.querySelectorAll("[src], [href]")) {
7035
7395
  for (const attr of ["src", "href"]) {
7036
7396
  const val = (el.getAttribute(attr) || "").trim();
@@ -7075,7 +7435,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
7075
7435
  var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
7076
7436
  async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
7077
7437
  if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
7078
- if (!existsSync10(remoteDir)) mkdirSync6(remoteDir, { recursive: true });
7438
+ if (!existsSync10(remoteDir)) mkdirSync7(remoteDir, { recursive: true });
7079
7439
  const urlToLocal = /* @__PURE__ */ new Map();
7080
7440
  await Promise.all(
7081
7441
  [...urlSet].map(async (url) => {
@@ -7122,7 +7482,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
7122
7482
  return downloadAndRewriteUrls(
7123
7483
  urlSet,
7124
7484
  html,
7125
- join14(downloadDir, REMOTE_MEDIA_SUBDIR),
7485
+ join15(downloadDir, REMOTE_MEDIA_SUBDIR),
7126
7486
  "Remote media download failed",
7127
7487
  "Localized remote media source(s)"
7128
7488
  );
@@ -7137,7 +7497,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
7137
7497
  return downloadAndRewriteUrls(
7138
7498
  urlSet,
7139
7499
  html,
7140
- join14(downloadDir, REMOTE_MEDIA_SUBDIR),
7500
+ join15(downloadDir, REMOTE_MEDIA_SUBDIR),
7141
7501
  "Remote image download failed",
7142
7502
  "Localized remote image source(s)"
7143
7503
  );
@@ -7153,7 +7513,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
7153
7513
  return downloadAndRewriteUrls(
7154
7514
  urlSet,
7155
7515
  html,
7156
- join14(downloadDir, REMOTE_MEDIA_SUBDIR),
7516
+ join15(downloadDir, REMOTE_MEDIA_SUBDIR),
7157
7517
  "Remote background-image download failed",
7158
7518
  "Localized remote background-image(s)",
7159
7519
  // Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
@@ -7288,7 +7648,7 @@ async function localizeRemoteFontFaces(html, downloadDir) {
7288
7648
  return downloadAndRewriteUrls(
7289
7649
  urlSet,
7290
7650
  processed,
7291
- join14(downloadDir, REMOTE_MEDIA_SUBDIR),
7651
+ join15(downloadDir, REMOTE_MEDIA_SUBDIR),
7292
7652
  "Remote font download failed",
7293
7653
  "Localized remote font face(s)",
7294
7654
  (h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
@@ -7333,7 +7693,7 @@ async function embedLocalFontFaces(html, projectDir) {
7333
7693
  while ((urlMatch = urlRe.exec(block)) !== null) {
7334
7694
  const localPath = urlMatch[1];
7335
7695
  if (!localPath || embeddedPaths.has(localPath)) continue;
7336
- const absPath = localPath.startsWith("/") ? localPath : resolve6(projectDir, localPath);
7696
+ const absPath = localPath.startsWith("/") ? localPath : resolve7(projectDir, localPath);
7337
7697
  if (!isPathInside2(absPath, projectDir)) continue;
7338
7698
  const ext = absPath.match(/\.(woff2?|ttf|otf|ttc)$/i)?.[1]?.toLowerCase() ?? "ttf";
7339
7699
  try {
@@ -7382,7 +7742,7 @@ function renderGsapDist() {
7382
7742
  }
7383
7743
  function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
7384
7744
  let gsapDist;
7385
- const exists = (path) => existsSync10(resolve6(projectDir, path));
7745
+ const exists = (path) => existsSync10(resolve7(projectDir, path));
7386
7746
  return html.replace(
7387
7747
  /(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
7388
7748
  (full, prefix, src, file, suffix) => {
@@ -7398,8 +7758,8 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
7398
7758
  if (!isPathInside2(htmlPath, projectDir)) return html;
7399
7759
  const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
7400
7760
  if (!entryPath.includes("/")) return html;
7401
- const { document: document2 } = parseHTML4(html);
7402
- const assetExists = (path) => existsSync10(resolve6(projectDir, path));
7761
+ const { document: document2 } = parseHTML5(html);
7762
+ const assetExists = (path) => existsSync10(resolve7(projectDir, path));
7403
7763
  rewriteAssetPaths(
7404
7764
  document2.querySelectorAll("[src], [href]"),
7405
7765
  entryPath,
@@ -7421,25 +7781,28 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
7421
7781
  }
7422
7782
  async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
7423
7783
  const entryHtml = rebaseDirectEntryAssetPaths(
7424
- readFileSync6(htmlPath, "utf-8"),
7784
+ readFileSync7(htmlPath, "utf-8"),
7425
7785
  projectDir,
7426
7786
  htmlPath
7427
7787
  );
7428
7788
  const rawHtml = rewriteUnresolvableGsapToCdn(entryHtml, projectDir);
7429
7789
  assertSubCompositionsUsable(rawHtml, projectDir);
7790
+ const probes = /* @__PURE__ */ new Map();
7430
7791
  const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
7431
7792
  rawHtml,
7432
7793
  projectDir,
7433
7794
  downloadDir,
7434
7795
  options.log,
7435
- options.urlByAbsentLocalPath
7796
+ options.urlByAbsentLocalPath,
7797
+ probes
7436
7798
  );
7437
7799
  const { subCompositions } = await parseSubCompositions(
7438
7800
  compiledHtml,
7439
7801
  projectDir,
7440
7802
  downloadDir,
7441
7803
  void 0,
7442
- options.urlByAbsentLocalPath
7804
+ options.urlByAbsentLocalPath,
7805
+ probes
7443
7806
  );
7444
7807
  const fullHtml = ensureFullDocument(compiledHtml);
7445
7808
  const inlinedHtml = inlineSubCompositions(
@@ -7491,12 +7854,26 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
7491
7854
  const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
7492
7855
  const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
7493
7856
  const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeRemoteFontFaces(htmlWithLocalBg, downloadDir);
7857
+ const {
7858
+ html: htmlWithJpegs,
7859
+ preparedAssets: preparedHeifAssets,
7860
+ preparedImages: preparedHeifImages
7861
+ } = await prepareHeifImageInputs(htmlWithLocalizedFonts, {
7862
+ projectDir,
7863
+ downloadDir,
7864
+ cacheDir: options.animatedGifCacheDir,
7865
+ sourceAssets: remoteImageAssets,
7866
+ timeoutMs: options.ffmpegProcessTimeout
7867
+ });
7868
+ if (preparedHeifImages.length > 0) {
7869
+ defaultLogger.info(`[Compiler] Converted ${preparedHeifImages.length} HEIC image(s) to JPEG`);
7870
+ }
7494
7871
  const gifSourceAssets = new Map(remoteImageAssets);
7495
7872
  const {
7496
7873
  html: htmlWithPreparedGifs,
7497
7874
  preparedAssets: preparedGifAssets,
7498
7875
  preparedGifs
7499
- } = await prepareAnimatedGifInputs(htmlWithLocalizedFonts, {
7876
+ } = await prepareAnimatedGifInputs(htmlWithJpegs, {
7500
7877
  projectDir,
7501
7878
  downloadDir,
7502
7879
  cacheDir: options.animatedGifCacheDir,
@@ -7523,11 +7900,17 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
7523
7900
  for (const [relPath, absPath] of preparedGifAssets) {
7524
7901
  externalAssets.set(relPath, absPath);
7525
7902
  }
7903
+ for (const [relPath, absPath] of preparedHeifAssets) {
7904
+ externalAssets.set(relPath, absPath);
7905
+ }
7526
7906
  const { videos, audios, images, droppedMediaIds } = collectRenderMedia(html);
7527
7907
  const masterFxChain = parseMasterFxChain(html);
7908
+ const advised = /* @__PURE__ */ new Set();
7528
7909
  for (const video of videos) {
7529
7910
  if (isHttpUrl(video.src)) continue;
7530
- const videoPath = resolve6(projectDir, video.src);
7911
+ const videoPath = resolve7(projectDir, video.src);
7912
+ if (advised.has(videoPath)) continue;
7913
+ advised.add(videoPath);
7531
7914
  const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
7532
7915
  (async () => {
7533
7916
  const metadata = await withMediaProbeSlot(() => extractMediaMetadata(videoPath));
@@ -7547,7 +7930,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
7547
7930
  })().catch(() => {
7548
7931
  });
7549
7932
  }
7550
- const { document: document2 } = parseHTML4(html);
7933
+ const { document: document2 } = parseHTML5(html);
7551
7934
  const rootEl = document2.querySelector("[data-composition-id]");
7552
7935
  const width = rootEl ? parseInt(rootEl.getAttribute("data-width") || "1080", 10) : 1080;
7553
7936
  const height = rootEl ? parseInt(rootEl.getAttribute("data-height") || "1920", 10) : 1920;
@@ -7954,7 +8337,7 @@ async function runCompileStage(input) {
7954
8337
  abortSignal
7955
8338
  } = input;
7956
8339
  const compileStart = Date.now();
7957
- const compiled = await compileForRender(projectDir, htmlPath, join15(workDir, "downloads"), {
8340
+ const compiled = await compileForRender(projectDir, htmlPath, join16(workDir, "downloads"), {
7958
8341
  log,
7959
8342
  failClosedFontFetch: failClosedFontFetch === true,
7960
8343
  allowSystemFontCapture,
@@ -7962,7 +8345,7 @@ async function runCompileStage(input) {
7962
8345
  variables: input.variables,
7963
8346
  urlByAbsentLocalPath: input.urlByAbsentLocalPath,
7964
8347
  pageMotionBlur: input.pageMotionBlur,
7965
- animatedGifCacheDir: cfg.extractCacheDir ? join15(cfg.extractCacheDir, "animated-gif") : void 0,
8348
+ animatedGifCacheDir: cfg.extractCacheDir ? join16(cfg.extractCacheDir, "animated-gif") : void 0,
7966
8349
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
7967
8350
  });
7968
8351
  assertNotAborted();
@@ -8018,7 +8401,7 @@ async function runCompileStage(input) {
8018
8401
  };
8019
8402
  await preflightCompositionAssetMediaTypes({
8020
8403
  projectDir,
8021
- compiledDir: join15(workDir, "compiled"),
8404
+ compiledDir: join16(workDir, "compiled"),
8022
8405
  composition,
8023
8406
  signal: abortSignal
8024
8407
  });
@@ -8063,8 +8446,8 @@ async function runCompileStage(input) {
8063
8446
  }
8064
8447
 
8065
8448
  // src/services/render/stages/probeStage.ts
8066
- import { join as join16 } from "node:path";
8067
- import { parseHTML as parseHTML5 } from "linkedom";
8449
+ import { join as join17 } from "node:path";
8450
+ import { parseHTML as parseHTML6 } from "linkedom";
8068
8451
  import {
8069
8452
  closeCaptureSession as closeCaptureSession4,
8070
8453
  compositionRequiresWebGpu,
@@ -8078,13 +8461,14 @@ import {
8078
8461
  } from "@hyperframes/engine";
8079
8462
  import {
8080
8463
  MEDIA_RENDER_ID_ATTR as MEDIA_RENDER_ID_ATTR2,
8081
- fpsToNumber as fpsToNumber7,
8464
+ fpsToNumber as fpsToNumber6,
8082
8465
  normalizeRateSpec as normalizeRateSpec3,
8083
8466
  readMediaStart as readMediaStart2,
8084
8467
  shiftRateLane as shiftRateLane2,
8085
8468
  sourceTimeAt as sourceTimeAt3,
8086
8469
  timeAtSourceTime
8087
8470
  } from "@hyperframes/core";
8471
+ import { parseHostVariableValues as parseHostVariableValues2, readDeclaredDefaults as readDeclaredDefaults2 } from "@hyperframes/core/compiler";
8088
8472
  import { extractMediaSrcMutations } from "@hyperframes/parsers";
8089
8473
 
8090
8474
  // src/services/render/stages/probeFailures.ts
@@ -8111,7 +8495,7 @@ function durationToFrameCount(duration, fps) {
8111
8495
  }
8112
8496
  function hasScriptedAudioVolumeAutomation(html, audioCount) {
8113
8497
  if (audioCount <= 0) return false;
8114
- const { document: document2 } = parseHTML5(html);
8498
+ const { document: document2 } = parseHTML6(html);
8115
8499
  const scriptBodies = [...document2.querySelectorAll("script")].map((script) => script.textContent ?? "").join("\n");
8116
8500
  if (!scriptBodies) return false;
8117
8501
  return /\.\s*volume\s*=/i.test(scriptBodies) || /\b(?:gsap|tl|timeline|tween)\s*\.\s*(?:to|fromTo|set)\s*\([\s\S]{0,2000}\bvolume\s*:/i.test(
@@ -8119,23 +8503,30 @@ function hasScriptedAudioVolumeAutomation(html, audioCount) {
8119
8503
  );
8120
8504
  }
8121
8505
  function hasAutoStartVideos(html) {
8122
- const { document: document2 } = parseHTML5(html);
8506
+ const { document: document2 } = parseHTML6(html);
8123
8507
  return document2.querySelector("video[data-hf-auto-start]") !== null;
8124
8508
  }
8509
+ function variableHasValue(element, variableId, variables) {
8510
+ if (variables && Object.hasOwn(variables, variableId)) return true;
8511
+ for (let scope = element.parentElement; scope; scope = scope.parentElement) {
8512
+ if (Object.hasOwn(parseHostVariableValues2(scope), variableId)) return true;
8513
+ if (Object.hasOwn(readDeclaredDefaults2(scope), variableId)) return true;
8514
+ }
8515
+ return Object.hasOwn(readDeclaredDefaults2(element.ownerDocument.documentElement), variableId);
8516
+ }
8125
8517
  function hasVariableBoundMedia(html, variables) {
8126
- if (!variables || Object.keys(variables).length === 0) return false;
8127
- const { document: document2 } = parseHTML5(html);
8518
+ const { document: document2 } = parseHTML6(html);
8128
8519
  return Array.from(
8129
8520
  document2.querySelectorAll(
8130
8521
  "img[data-var-src], audio[data-var-src], video[data-var-src], source[data-var-src]"
8131
8522
  )
8132
8523
  ).some((element) => {
8133
8524
  const variableId = element.getAttribute("data-var-src")?.trim();
8134
- return Boolean(variableId && Object.hasOwn(variables, variableId));
8525
+ return Boolean(variableId && variableHasValue(element, variableId, variables));
8135
8526
  });
8136
8527
  }
8137
8528
  function authoredMediaStarts(html) {
8138
- const { document: document2 } = parseHTML5(html);
8529
+ const { document: document2 } = parseHTML6(html);
8139
8530
  const starts = /* @__PURE__ */ new Map();
8140
8531
  for (const media of document2.querySelectorAll("video, audio")) {
8141
8532
  const id = media.getAttribute(MEDIA_RENDER_ID_ATTR2) || media.getAttribute("id");
@@ -8172,7 +8563,7 @@ function reconcileBrowserMediaEnd(existingEnd, projectedEnd, sourceChanged, dura
8172
8563
  return existingEnd <= 0 ? projectedEnd : Math.min(existingEnd, projectedEnd);
8173
8564
  }
8174
8565
  function hasRuntimeMediaChanges(html) {
8175
- const { document: document2 } = parseHTML5(html);
8566
+ const { document: document2 } = parseHTML6(html);
8176
8567
  const scriptBodies = [...document2.querySelectorAll("script")].map(
8177
8568
  (script) => script.textContent ?? ""
8178
8569
  );
@@ -8255,7 +8646,7 @@ async function runProbeStage(input) {
8255
8646
  });
8256
8647
  fileServer = await createFileServer({
8257
8648
  projectDir,
8258
- compiledDir: join16(workDir, "compiled"),
8649
+ compiledDir: join17(workDir, "compiled"),
8259
8650
  port: 0,
8260
8651
  preHeadScripts: [VIRTUAL_TIME_SHIM],
8261
8652
  fps: job.config.fps
@@ -8284,7 +8675,7 @@ async function runProbeStage(input) {
8284
8675
  log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
8285
8676
  probeSession = await createCaptureSession4(
8286
8677
  fileServer.url,
8287
- join16(workDir, "probe"),
8678
+ join17(workDir, "probe"),
8288
8679
  captureOpts,
8289
8680
  null,
8290
8681
  probeCfg
@@ -8369,7 +8760,7 @@ async function runProbeStage(input) {
8369
8760
  });
8370
8761
  probeSession = await createCaptureSession4(
8371
8762
  fileServer.url,
8372
- join16(workDir, "probe-screenshot"),
8763
+ join17(workDir, "probe-screenshot"),
8373
8764
  captureOpts,
8374
8765
  null,
8375
8766
  { ...probeCfg, forceScreenshot: true }
@@ -8405,7 +8796,7 @@ async function runProbeStage(input) {
8405
8796
  compiled,
8406
8797
  resolutions,
8407
8798
  projectDir,
8408
- join16(workDir, "downloads")
8799
+ join17(workDir, "downloads")
8409
8800
  );
8410
8801
  assertNotAborted();
8411
8802
  composition.videos = compiled.videos;
@@ -8557,7 +8948,7 @@ async function runProbeStage(input) {
8557
8948
  session.page,
8558
8949
  composition.audios.map((audio) => audio.id),
8559
8950
  composition.duration,
8560
- fpsToNumber7(job.config.fps)
8951
+ fpsToNumber6(job.config.fps)
8561
8952
  );
8562
8953
  assertNotAborted();
8563
8954
  if (automation.length > 0) {
@@ -8596,7 +8987,7 @@ async function runProbeStage(input) {
8596
8987
  }
8597
8988
  await preflightCompositionAssetMediaTypes({
8598
8989
  projectDir,
8599
- compiledDir: join16(workDir, "compiled"),
8990
+ compiledDir: join17(workDir, "compiled"),
8600
8991
  composition,
8601
8992
  signal: abortSignal
8602
8993
  });
@@ -8620,7 +9011,7 @@ async function runProbeStage(input) {
8620
9011
  }
8621
9012
  const browserProbeMs = Date.now() - probeStart;
8622
9013
  const duration = composition.duration;
8623
- const totalFrames = durationToFrameCount(duration, fpsToNumber7(job.config.fps));
9014
+ const totalFrames = durationToFrameCount(duration, fpsToNumber6(job.config.fps));
8624
9015
  if (duration <= 0) {
8625
9016
  const diagnostics = [];
8626
9017
  try {
@@ -8761,7 +9152,7 @@ function validateDistributedDuration(input) {
8761
9152
 
8762
9153
  // src/services/render/stages/extractVideosStage.ts
8763
9154
  import { existsSync as existsSync11 } from "node:fs";
8764
- import { join as join17 } from "node:path";
9155
+ import { join as join18 } from "node:path";
8765
9156
  import {
8766
9157
  classifyVideoExtractionError,
8767
9158
  createFrameLookupTable,
@@ -9081,14 +9472,14 @@ async function runExtractVideosStage(input) {
9081
9472
  // because short boundary counts can differ by one frame.
9082
9473
  {
9083
9474
  fps: job.config.fps,
9084
- outputDir: join17(compiledDir, "__hyperframes_video_frames"),
9475
+ outputDir: join18(compiledDir, "__hyperframes_video_frames"),
9085
9476
  format: job.config.videoFrameFormat ?? "auto",
9086
9477
  toneMapHdrToSdr: job.config.hdrMode === "force-sdr",
9087
9478
  timelineEnd: composition.duration,
9088
9479
  maxTransientRetries: extractionPolicy.maxTransientRetries,
9089
9480
  collectProbeFailures: extractionPolicy.failureMode === "enforce",
9090
9481
  deferRangeExtraction,
9091
- contentKeyedDirs: [join17(compiledDir, REMOTE_MEDIA_SUBDIR)],
9482
+ contentKeyedDirs: [join18(compiledDir, REMOTE_MEDIA_SUBDIR)],
9092
9483
  urlByAbsentLocalPath
9093
9484
  },
9094
9485
  abortSignal,
@@ -9155,18 +9546,19 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
9155
9546
  }
9156
9547
 
9157
9548
  // src/services/render/stages/audioStage.ts
9158
- import { join as join19 } from "node:path";
9159
- import { rmSync as rmSync9 } from "node:fs";
9549
+ import { join as join20 } from "node:path";
9160
9550
  import {
9161
9551
  MIXED_AUDIO_FILENAME,
9162
9552
  audioFailureDiagnostics,
9163
- processCompositionAudio
9553
+ processCompositionAudio,
9554
+ removeScratch as removeScratch2,
9555
+ collectScratchLeftovers
9164
9556
  } from "@hyperframes/engine";
9165
9557
 
9166
9558
  // src/services/render/audioPadTrim.ts
9167
9559
  import { spawn } from "node:child_process";
9168
- import { mkdtempSync as mkdtempSync3, rmSync as rmSync8 } from "node:fs";
9169
- import { dirname as dirname7, join as join18 } from "node:path";
9560
+ import { mkdtempSync as mkdtempSync3 } from "node:fs";
9561
+ import { dirname as dirname8, join as join19 } from "node:path";
9170
9562
  import {
9171
9563
  DEFAULT_CONFIG,
9172
9564
  describeFfmpegFailure as describeFfmpegFailure2,
@@ -9174,8 +9566,9 @@ import {
9174
9566
  extractAudioMetadata as extractAudioMetadata3,
9175
9567
  getFfprobeBinary,
9176
9568
  ManagedChildProcess,
9177
- renameWithRetry as renameWithRetry4,
9178
- runFfmpeg as runFfmpeg4,
9569
+ removeScratch,
9570
+ renameWithRetry as renameWithRetry5,
9571
+ runFfmpeg as runFfmpeg5,
9179
9572
  trackChildProcess
9180
9573
  } from "@hyperframes/engine";
9181
9574
  import { redactKnownPaths, redactTelemetryString as redactTelemetryString3 } from "@hyperframes/core";
@@ -9185,7 +9578,7 @@ import {
9185
9578
  aacSegmentCount,
9186
9579
  describeFfmpegFailure,
9187
9580
  extractAudioMetadata as extractAudioMetadata2,
9188
- runFfmpeg as runFfmpeg3
9581
+ runFfmpeg as runFfmpeg4
9189
9582
  } from "@hyperframes/engine";
9190
9583
  var toDbfs = (token) => token.toLowerCase() === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
9191
9584
  function checkedDbfs(value) {
@@ -9209,7 +9602,7 @@ async function scanTruePeak(audioPath, trim, timeoutMs, signal) {
9209
9602
  else laterBlocks = Math.max(laterBlocks, block);
9210
9603
  }
9211
9604
  };
9212
- const result = await runFfmpeg3(
9605
+ const result = await runFfmpeg4(
9213
9606
  ["-i", audioPath, "-map", "0:a:0", "-vn", "-af", `${trim}ebur128=peak=true`, "-f", "null", "-"],
9214
9607
  { signal, timeout: timeoutMs, onStderr: readLines }
9215
9608
  );
@@ -9369,7 +9762,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
9369
9762
  error: `audioPadTrim: failed to materialize ${plan2.operation}: ${err instanceof Error ? err.message : String(err)}`
9370
9763
  };
9371
9764
  } finally {
9372
- for (const path of plan2.cleanupPaths) rmSync8(path, { force: true });
9765
+ for (const path of plan2.cleanupPaths) removeScratch(path);
9373
9766
  }
9374
9767
  let loweredDb = 0;
9375
9768
  if (probeTruePeak) {
@@ -9416,8 +9809,8 @@ async function constrainAacTruePeak(input) {
9416
9809
  async function enforceAacTruePeak(input) {
9417
9810
  let scratchDir;
9418
9811
  try {
9419
- scratchDir = mkdtempSync3(join18(dirname7(input.audioPath), ".true-peak-"));
9420
- const correctedPath = join18(scratchDir, "audio.m4a");
9812
+ scratchDir = mkdtempSync3(join19(dirname8(input.audioPath), ".true-peak-"));
9813
+ const correctedPath = join19(scratchDir, "audio.m4a");
9421
9814
  const measurements = [];
9422
9815
  let attenuationDb = 0;
9423
9816
  let sourcePeakDbfs;
@@ -9432,7 +9825,7 @@ async function enforceAacTruePeak(input) {
9432
9825
  }
9433
9826
  if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
9434
9827
  if (measuredPath !== correctedPath) return { success: true };
9435
- renameWithRetry4(correctedPath, input.audioPath);
9828
+ renameWithRetry5(correctedPath, input.audioPath, { copyFallback: true });
9436
9829
  return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
9437
9830
  }
9438
9831
  if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
@@ -9473,7 +9866,7 @@ async function enforceAacTruePeak(input) {
9473
9866
  )}`
9474
9867
  };
9475
9868
  } finally {
9476
- if (scratchDir) rmSync8(scratchDir, { recursive: true, force: true });
9869
+ if (scratchDir) removeScratch(scratchDir);
9477
9870
  }
9478
9871
  }
9479
9872
  function failResult(outputPath, target, source, error) {
@@ -9550,7 +9943,7 @@ async function defaultProbeAudioInfo(audioPath, signal) {
9550
9943
  };
9551
9944
  }
9552
9945
  async function defaultRunFfmpeg(args, timeoutMs, signal) {
9553
- return toStepResult(await runFfmpeg4(args, { signal, timeout: timeoutMs }), timeoutMs);
9946
+ return toStepResult(await runFfmpeg5(args, { signal, timeout: timeoutMs }), timeoutMs);
9554
9947
  }
9555
9948
  function defaultAacEncoder(timeoutMs, signal, pcmSourcePath) {
9556
9949
  return async (inputPath, outputPath, durationSeconds, filter) => {
@@ -9618,9 +10011,17 @@ function assertAudioNotInterrupted(failures) {
9618
10011
  ])
9619
10012
  });
9620
10013
  }
9621
- async function runAudioStage(input) {
10014
+ function runAudioStage(input) {
10015
+ return collectScratchLeftovers(
10016
+ () => runAudioStageScoped(input),
10017
+ (left) => {
10018
+ if (left.length > 0) input.log?.warn("[audio] scratch left behind", { count: left.length });
10019
+ }
10020
+ );
10021
+ }
10022
+ async function runAudioStageScoped(input) {
9622
10023
  const stageStart = Date.now();
9623
- const pcmSourcePath = join19(input.workDir, "audio-source.wav");
10024
+ const pcmSourcePath = join20(input.workDir, "audio-source.wav");
9624
10025
  const duration = input.totalFrames * input.fps.den / input.fps.num;
9625
10026
  try {
9626
10027
  const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
@@ -9640,7 +10041,7 @@ async function runAudioStage(input) {
9640
10041
  }
9641
10042
  return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
9642
10043
  } finally {
9643
- rmSync9(pcmSourcePath, { force: true });
10044
+ removeScratch2(pcmSourcePath);
9644
10045
  }
9645
10046
  }
9646
10047
  async function mixAudioForDelivery(input, duration, pcmSourcePath) {
@@ -9658,7 +10059,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9658
10059
  assertNotAborted,
9659
10060
  log
9660
10061
  } = input;
9661
- const audioOutputPath = join19(workDir, MIXED_AUDIO_FILENAME);
10062
+ const audioOutputPath = join20(workDir, MIXED_AUDIO_FILENAME);
9662
10063
  let hasAudio = false;
9663
10064
  let audioError;
9664
10065
  let audioFailures;
@@ -9669,7 +10070,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9669
10070
  audioResult = await processCompositionAudio(
9670
10071
  audios,
9671
10072
  projectDir,
9672
- join19(workDir, "audio-work"),
10073
+ join20(workDir, "audio-work"),
9673
10074
  audioOutputPath,
9674
10075
  duration,
9675
10076
  abortSignal,
@@ -9738,7 +10139,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
9738
10139
 
9739
10140
  // src/services/render/stages/captureStage.ts
9740
10141
  import { existsSync as existsSync12, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
9741
- import { join as join20 } from "node:path";
10142
+ import { join as join21 } from "node:path";
9742
10143
  import {
9743
10144
  classifyCaptureFailure as classifyCaptureFailure2,
9744
10145
  frameFileExtension,
@@ -9811,13 +10212,13 @@ function measureCaptureFrameBytes(workDir, framesDir) {
9811
10212
  if (!existsSync12(workDir)) return total;
9812
10213
  for (const attempt of readdirSync3(workDir)) {
9813
10214
  if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
9814
- total += sampleDirectoryBytes(join20(workDir, attempt));
10215
+ total += sampleDirectoryBytes(join21(workDir, attempt));
9815
10216
  continue;
9816
10217
  }
9817
10218
  if (!/^capture-attempt-\d+$/.test(attempt)) continue;
9818
- const attemptDir = join20(workDir, attempt);
10219
+ const attemptDir = join21(workDir, attempt);
9819
10220
  for (const name of readdirSync3(attemptDir)) {
9820
- if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join20(attemptDir, name));
10221
+ if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join21(attemptDir, name));
9821
10222
  }
9822
10223
  }
9823
10224
  return total;
@@ -10110,8 +10511,8 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
10110
10511
  }
10111
10512
 
10112
10513
  // src/services/render/stages/captureHdrStage.ts
10113
- import { existsSync as existsSync14, mkdirSync as mkdirSync7 } from "node:fs";
10114
- import { join as join24 } from "node:path";
10514
+ import { existsSync as existsSync14, mkdirSync as mkdirSync8 } from "node:fs";
10515
+ import { join as join25 } from "node:path";
10115
10516
  import {
10116
10517
  closeCaptureSession as closeCaptureSession7,
10117
10518
  cloneCaptureWarnings as cloneCaptureWarnings2,
@@ -10120,7 +10521,7 @@ import {
10120
10521
  initializeSession as initializeSession7,
10121
10522
  spawnStreamingEncoder as spawnStreamingEncoder3
10122
10523
  } from "@hyperframes/engine";
10123
- import { fpsToNumber as fpsToNumber8 } from "@hyperframes/core";
10524
+ import { fpsToNumber as fpsToNumber7 } from "@hyperframes/core";
10124
10525
 
10125
10526
  // src/services/hdrImageTransferCache.ts
10126
10527
  import { convertTransfer as convertTransfer2 } from "@hyperframes/engine";
@@ -10183,7 +10584,7 @@ function createHdrImageTransferCache(options = {}) {
10183
10584
  }
10184
10585
 
10185
10586
  // src/services/render/stages/captureHdrSequentialLoop.ts
10186
- import { join as join21 } from "node:path";
10587
+ import { join as join22 } from "node:path";
10187
10588
  import {
10188
10589
  TRANSITIONS,
10189
10590
  crossfade
@@ -10308,7 +10709,7 @@ async function runSequentialLayeredFrameLoop(input) {
10308
10709
  );
10309
10710
  if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
10310
10711
  writeFileExclusiveSync(
10311
- join21(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
10712
+ join22(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
10312
10713
  normalCanvas
10313
10714
  );
10314
10715
  }
@@ -10340,7 +10741,7 @@ async function runSequentialLayeredFrameLoop(input) {
10340
10741
  }
10341
10742
 
10342
10743
  // src/services/render/stages/captureHdrHybridLoop.ts
10343
- import { join as join23 } from "node:path";
10744
+ import { join as join24 } from "node:path";
10344
10745
  import {
10345
10746
  TRANSITIONS as TRANSITIONS2,
10346
10747
  closeCaptureSession as closeCaptureSession6,
@@ -10354,7 +10755,7 @@ import {
10354
10755
  // src/services/shaderTransitionWorkerPool.ts
10355
10756
  import { Worker } from "node:worker_threads";
10356
10757
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
10357
- import { dirname as dirname8, join as join22 } from "node:path";
10758
+ import { dirname as dirname9, join as join23 } from "node:path";
10358
10759
  import { createRequire } from "node:module";
10359
10760
  import { existsSync as existsSync13 } from "node:fs";
10360
10761
  import { cpus as cpus2 } from "node:os";
@@ -10367,10 +10768,10 @@ function resolveWorkerEntry(explicit) {
10367
10768
  const isTs = override.endsWith(".ts");
10368
10769
  return { path: override, isTs };
10369
10770
  }
10370
- const moduleDir = dirname8(fileURLToPath3(import.meta.url));
10371
- const jsPath = join22(moduleDir, "shaderTransitionWorker.js");
10771
+ const moduleDir = dirname9(fileURLToPath3(import.meta.url));
10772
+ const jsPath = join23(moduleDir, "shaderTransitionWorker.js");
10372
10773
  if (existsSync13(jsPath)) return { path: jsPath, isTs: false };
10373
- const tsPath = join22(moduleDir, "shaderTransitionWorker.ts");
10774
+ const tsPath = join23(moduleDir, "shaderTransitionWorker.ts");
10374
10775
  return { path: tsPath, isTs: true };
10375
10776
  }
10376
10777
  function buildExecArgv(entryIsTs) {
@@ -10513,8 +10914,8 @@ async function createShaderTransitionWorkerPool(opts) {
10513
10914
  if (terminated) {
10514
10915
  throw new Error("shader-blend pool already terminated");
10515
10916
  }
10516
- return new Promise((resolve12, reject) => {
10517
- const task = traceEnabled ? { req, resolve: resolve12, reject, enqueuedAtMs: Date.now(), traceId: ++nextTaskId } : { req, resolve: resolve12, reject };
10917
+ return new Promise((resolve13, reject) => {
10918
+ const task = traceEnabled ? { req, resolve: resolve13, reject, enqueuedAtMs: Date.now(), traceId: ++nextTaskId } : { req, resolve: resolve13, reject };
10518
10919
  const idle = slots.find((s) => !s.busy && !s.dead);
10519
10920
  if (idle) {
10520
10921
  queue.unshift(task);
@@ -10738,7 +11139,7 @@ async function runHybridLayeredFrameLoop(input) {
10738
11139
  );
10739
11140
  if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
10740
11141
  writeFileExclusiveSync(
10741
- join23(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
11142
+ join24(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
10742
11143
  canvas
10743
11144
  );
10744
11145
  }
@@ -10794,6 +11195,7 @@ async function runCaptureHdrStage(input) {
10794
11195
  preset,
10795
11196
  effectiveQuality,
10796
11197
  effectiveBitrate,
11198
+ deliveryEncoder,
10797
11199
  fileServer,
10798
11200
  buildCaptureOptions,
10799
11201
  createRenderVideoFrameInjector,
@@ -10855,7 +11257,7 @@ async function runCaptureHdrStage(input) {
10855
11257
  }
10856
11258
  return map;
10857
11259
  });
10858
- const fpsDecimal = fpsToNumber8(job.config.fps);
11260
+ const fpsDecimal = fpsToNumber7(job.config.fps);
10859
11261
  const transitionRanges = transitionMeta.map((t) => ({
10860
11262
  ...t,
10861
11263
  startFrame: Math.floor(t.time * fpsDecimal),
@@ -10883,6 +11285,7 @@ async function runCaptureHdrStage(input) {
10883
11285
  preset: preset.preset,
10884
11286
  quality: effectiveQuality,
10885
11287
  bitrate: effectiveBitrate,
11288
+ ...deliveryEncoder,
10886
11289
  pixelFormat: preset.pixelFormat,
10887
11290
  hdr: preset.hdr,
10888
11291
  rawInputFormat: "rgb48le"
@@ -10926,9 +11329,9 @@ async function runCaptureHdrStage(input) {
10926
11329
  if (hdrVideoFrameSources.has(v.id)) hdrVideoEndTimes.set(v.id, v.end);
10927
11330
  }
10928
11331
  const debugDumpEnabled = process.env.KEEP_TEMP === "1";
10929
- const debugDumpDir = debugDumpEnabled ? join24(framesDir, "debug-composite") : null;
11332
+ const debugDumpDir = debugDumpEnabled ? join25(framesDir, "debug-composite") : null;
10930
11333
  if (debugDumpDir && !existsSync14(debugDumpDir)) {
10931
- mkdirSync7(debugDumpDir, { recursive: true });
11334
+ mkdirSync8(debugDumpDir, { recursive: true });
10932
11335
  }
10933
11336
  const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
10934
11337
  const hdrTargetTransfer = compositeTransfer === "srgb" ? void 0 : compositeTransfer;
@@ -10942,7 +11345,7 @@ async function runCaptureHdrStage(input) {
10942
11345
  beforeCaptureHook: domSession.onBeforeCapture,
10943
11346
  width,
10944
11347
  height,
10945
- fps: fpsToNumber8(job.config.fps),
11348
+ fps: fpsToNumber7(job.config.fps),
10946
11349
  compositeTransfer,
10947
11350
  nativeHdrImageIds,
10948
11351
  hdrImageBuffers,
@@ -11081,24 +11484,24 @@ async function runCaptureHdrStage(input) {
11081
11484
 
11082
11485
  // src/services/render/stages/encodeStage.ts
11083
11486
  import {
11084
- copyFileSync as copyFileSync3,
11487
+ copyFileSync as copyFileSync4,
11085
11488
  existsSync as existsSync15,
11086
- mkdirSync as mkdirSync8,
11489
+ mkdirSync as mkdirSync9,
11087
11490
  readdirSync as readdirSync4,
11088
- readFileSync as readFileSync7,
11089
- rmSync as rmSync10,
11090
- statSync as statSync6,
11091
- writeFileSync as writeFileSync4
11491
+ readFileSync as readFileSync8,
11492
+ rmSync as rmSync9,
11493
+ statSync as statSync7,
11494
+ writeFileSync as writeFileSync5
11092
11495
  } from "node:fs";
11093
11496
  import { readFile as readFile2 } from "node:fs/promises";
11094
- import { dirname as dirname9, join as join25 } from "node:path";
11497
+ import { dirname as dirname10, join as join26 } from "node:path";
11095
11498
  import {
11096
11499
  encodeFramesChunkedConcat,
11097
11500
  encodeFramesFromDir,
11098
11501
  formatFfmpegError,
11099
11502
  MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME2,
11100
11503
  resolveConfig,
11101
- runFfmpeg as runFfmpeg5,
11504
+ runFfmpeg as runFfmpeg6,
11102
11505
  spawnStreamingEncoder as spawnStreamingEncoder4
11103
11506
  } from "@hyperframes/engine";
11104
11507
  import {
@@ -11174,17 +11577,17 @@ function gifEncodeFailure(startTime, outputPath, error, failureReason) {
11174
11577
  }
11175
11578
  async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
11176
11579
  const { outputPath } = argsInput;
11177
- const result = await runFfmpeg5(buildGifPaletteuseArgs({ ...argsInput, wholeFrames: true }), run);
11580
+ const result = await runFfmpeg6(buildGifPaletteuseArgs({ ...argsInput, wholeFrames: true }), run);
11178
11581
  if (!result.success) {
11179
11582
  const error = formatFfmpegError(result.exitCode, result.stderr);
11180
11583
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
11181
11584
  }
11182
- const gif = readFileSync7(outputPath);
11585
+ const gif = readFileSync8(outputPath);
11183
11586
  if (!clearGifFramesBeforeNext(gif)) {
11184
11587
  const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
11185
11588
  return gifEncodeFailure(startTime, outputPath, error);
11186
11589
  }
11187
- writeFileSync4(outputPath, gif);
11590
+ writeFileSync5(outputPath, gif);
11188
11591
  return null;
11189
11592
  }
11190
11593
  async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
@@ -11206,17 +11609,17 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
11206
11609
  try {
11207
11610
  const run = { signal: input.signal, timeout: input.timeout };
11208
11611
  for (const args of [buildGifPalettegenArgs(argsInput), buildGifPaletteuseArgs(argsInput)]) {
11209
- const result = await runFfmpeg5(args, run);
11612
+ const result = await runFfmpeg6(args, run);
11210
11613
  if (!result.success) {
11211
11614
  const error = formatFfmpegError(result.exitCode, result.stderr);
11212
11615
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
11213
11616
  }
11214
11617
  }
11215
- if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync7(outputPath)) !== false) {
11618
+ if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync8(outputPath)) !== false) {
11216
11619
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
11217
11620
  if (failure) return failure;
11218
11621
  }
11219
- const fileSize = existsSync15(outputPath) ? statSync6(outputPath).size : 0;
11622
+ const fileSize = existsSync15(outputPath) ? statSync7(outputPath).size : 0;
11220
11623
  return {
11221
11624
  success: true,
11222
11625
  outputPath,
@@ -11225,7 +11628,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
11225
11628
  fileSize
11226
11629
  };
11227
11630
  } finally {
11228
- rmSync10(input.palettePath, { force: true });
11631
+ rmSync9(input.palettePath, { force: true });
11229
11632
  }
11230
11633
  }
11231
11634
  function startEncodeProgress(job, stage, onProgress) {
@@ -11249,6 +11652,8 @@ function videoEncoderOptions(input, engineCfg) {
11249
11652
  preset: preset.preset,
11250
11653
  quality: input.effectiveQuality,
11251
11654
  bitrate: input.effectiveBitrate,
11655
+ maxBitrate: input.maxBitrate,
11656
+ bufferSize: input.bufferSize,
11252
11657
  pixelFormat: preset.pixelFormat,
11253
11658
  vp9CpuUsed: engineCfg.vp9CpuUsed,
11254
11659
  useGpu: job.config.useGpu,
@@ -11297,10 +11702,10 @@ function encodeWhileCapturing(input, totalFrames) {
11297
11702
  const stream = await encoder;
11298
11703
  for (let next = 0; next < totalFrames; next++) {
11299
11704
  while (!landed.has(next) && !captureDone && !signal.aborted) {
11300
- await new Promise((resolve12) => wake = resolve12);
11705
+ await new Promise((resolve13) => wake = resolve13);
11301
11706
  }
11302
11707
  if (signal.aborted) return;
11303
- const mergedPath = join25(input.framesDir, formatCaptureFrameName(next, "jpg"));
11708
+ const mergedPath = join26(input.framesDir, formatCaptureFrameName(next, "jpg"));
11304
11709
  const frame = await readLandedFrame(landed.get(next), mergedPath);
11305
11710
  landed.delete(next);
11306
11711
  if (!await stream.writeFrame(frame)) {
@@ -11375,7 +11780,7 @@ async function runEncodeStage(input) {
11375
11780
  const stage5Start = Date.now();
11376
11781
  if (isPngSequence) {
11377
11782
  const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
11378
- if (!existsSync15(outputPath)) mkdirSync8(outputPath, { recursive: true });
11783
+ if (!existsSync15(outputPath)) mkdirSync9(outputPath, { recursive: true });
11379
11784
  const captured = readdirSync4(framesDir).filter((name) => name.endsWith(".png")).sort();
11380
11785
  if (captured.length === 0) {
11381
11786
  throw new Error(
@@ -11383,11 +11788,11 @@ async function runEncodeStage(input) {
11383
11788
  );
11384
11789
  }
11385
11790
  captured.forEach((name, i) => {
11386
- const dst = join25(outputPath, formatExportFrameName(i, "png"));
11387
- copyFileSync3(join25(framesDir, name), dst);
11791
+ const dst = join26(outputPath, formatExportFrameName(i, "png"));
11792
+ copyFileSync4(join26(framesDir, name), dst);
11388
11793
  });
11389
11794
  if (hasAudio && audioOutputPath && existsSync15(audioOutputPath)) {
11390
- copyFileSync3(audioOutputPath, join25(outputPath, MIXED_AUDIO_FILENAME2));
11795
+ copyFileSync4(audioOutputPath, join26(outputPath, MIXED_AUDIO_FILENAME2));
11391
11796
  log.info(
11392
11797
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
11393
11798
  );
@@ -11407,7 +11812,7 @@ async function runEncodeStage(input) {
11407
11812
  const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
11408
11813
  fps: job.config.fps,
11409
11814
  loop,
11410
- palettePath: join25(dirname9(videoOnlyPath), "gif-palette.png"),
11815
+ palettePath: join26(dirname10(videoOnlyPath), "gif-palette.png"),
11411
11816
  preserveAlpha: needsAlpha,
11412
11817
  signal: abortSignal,
11413
11818
  timeout: engineCfg.ffmpegEncodeTimeout
@@ -11617,9 +12022,9 @@ function sampleDirectoryBytes(dir) {
11617
12022
  continue;
11618
12023
  }
11619
12024
  for (const name of entries) {
11620
- const full = join26(current, name);
12025
+ const full = join27(current, name);
11621
12026
  try {
11622
- const st = statSync7(full);
12027
+ const st = statSync8(full);
11623
12028
  if (st.isDirectory()) {
11624
12029
  stack.push(full);
11625
12030
  } else if (st.isFile()) {
@@ -11766,8 +12171,8 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
11766
12171
  const ranges = [];
11767
12172
  let rangeStart = null;
11768
12173
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
11769
- const framePath = join26(framesDir, formatCaptureFrameName(frameIndex, frameExt));
11770
- const missing = !existsSync16(framePath) || statSync7(framePath).size <= 8;
12174
+ const framePath = join27(framesDir, formatCaptureFrameName(frameIndex, frameExt));
12175
+ const missing = !existsSync16(framePath) || statSync8(framePath).size <= 8;
11771
12176
  if (missing && rangeStart === null) {
11772
12177
  rangeStart = frameIndex;
11773
12178
  } else if (!missing && rangeStart !== null) {
@@ -11783,7 +12188,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
11783
12188
  function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
11784
12189
  const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
11785
12190
  const batches = [];
11786
- const outputDirFor = (batchIndex, workerId) => join26(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
12191
+ const outputDirFor = (batchIndex, workerId) => join27(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
11787
12192
  if (stride > 1) {
11788
12193
  const spans = /* @__PURE__ */ new Map();
11789
12194
  for (const range of ranges) {
@@ -11843,8 +12248,8 @@ function getNextRetryWorkerCount(currentWorkers) {
11843
12248
  return Math.max(1, Math.floor(currentWorkers / 2));
11844
12249
  }
11845
12250
  function resolveRenderWorkDirPrefix(outputPath, jobId, platform2 = process.platform, systemTempDir = tmpdir2()) {
11846
- if (platform2 === "win32" || platform2 === "darwin") return join26(systemTempDir, "hf-render-");
11847
- return join26(dirname10(outputPath), `work-${jobId}-`);
12251
+ if (platform2 === "win32" || platform2 === "darwin") return join27(systemTempDir, "hf-render-");
12252
+ return join27(dirname11(outputPath), `work-${jobId}-`);
11848
12253
  }
11849
12254
  var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
11850
12255
  function isTransientCaptureRetryEligible(failureKind, missing, retriesUsed) {
@@ -11886,7 +12291,7 @@ The composition is too large for the available memory. To reduce memory pressure
11886
12291
  function countCapturedFrames(totalFrames, framesDir, frameExt) {
11887
12292
  let captured = 0;
11888
12293
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
11889
- const framePath = join26(framesDir, formatCaptureFrameName(frameIndex, frameExt));
12294
+ const framePath = join27(framesDir, formatCaptureFrameName(frameIndex, frameExt));
11890
12295
  if (existsSync16(framePath)) captured++;
11891
12296
  }
11892
12297
  return captured;
@@ -11912,7 +12317,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
11912
12317
  reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
11913
12318
  });
11914
12319
  pendingTransientRetry = false;
11915
- const attemptWorkDir = join26(options.workDir, `capture-attempt-${attempt}`);
12320
+ const attemptWorkDir = join27(options.workDir, `capture-attempt-${attempt}`);
11916
12321
  const batches = missingRanges ? buildMissingFrameRetryBatches(
11917
12322
  missingRanges,
11918
12323
  currentWorkers,
@@ -12518,18 +12923,19 @@ function deVerifyFallbackTelemetry(err) {
12518
12923
  };
12519
12924
  }
12520
12925
  function resolveRenderDebugDir() {
12521
- const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve7(process.env.PRODUCER_RENDERS_DIR, "..") : resolve7(dirname10(fileURLToPath4(import.meta.url)), "../..");
12522
- return join26(producerRoot, ".debug");
12926
+ const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve8(process.env.PRODUCER_RENDERS_DIR, "..") : resolve8(dirname11(fileURLToPath4(import.meta.url)), "../..");
12927
+ return join27(producerRoot, ".debug");
12523
12928
  }
12524
12929
  async function executeRenderJob(job, projectDir, outputPath, progressSink, abortSignal, assertRenderActive) {
12525
12930
  validateHlsRenderConfig(job.config);
12931
+ validateDeliveryEncoderConfig(job.config);
12526
12932
  const debugDir = resolveRenderDebugDir();
12527
- const outputDir = dirname10(outputPath);
12528
- if (!existsSync16(outputDir)) mkdirSync9(outputDir, { recursive: true });
12529
- const workDir = job.config.debug ? join26(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
12933
+ const outputDir = dirname11(outputPath);
12934
+ if (!existsSync16(outputDir)) mkdirSync10(outputDir, { recursive: true });
12935
+ const workDir = job.config.debug ? join27(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
12530
12936
  const pipelineStart = Date.now();
12531
12937
  const baseLog = job.config.logger ?? defaultLogger;
12532
- const logPath = job.config.debug ? join26(workDir, "render.log") : null;
12938
+ const logPath = job.config.debug ? join27(workDir, "render.log") : null;
12533
12939
  const execution = new RenderExecutionContext({
12534
12940
  request: { renderJobId: job.id, projectDir, outputPath },
12535
12941
  logger: logPath ? createRenderFileLogger(logPath, baseLog) : baseLog,
@@ -12543,7 +12949,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
12543
12949
  log.info("KEEP_TEMP=1, leaving workDir on disk for inspection", { workDir });
12544
12950
  return;
12545
12951
  }
12546
- rmSync11(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
12952
+ rmSync10(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
12547
12953
  });
12548
12954
  try {
12549
12955
  await executeRenderPipeline({
@@ -12589,7 +12995,7 @@ async function executeRenderPipeline(input) {
12589
12995
  imageDecodeFailures: 0
12590
12996
  };
12591
12997
  let hdrPerf;
12592
- const perfOutputPath = join26(workDir, "perf-summary.json");
12998
+ const perfOutputPath = join27(workDir, "perf-summary.json");
12593
12999
  const cfg = { ...job.config.producerConfig ?? resolveConfig2() };
12594
13000
  const observability = new RenderObservabilityRecorder({
12595
13001
  pipelineStartMs: pipelineStart,
@@ -12601,11 +13007,14 @@ async function executeRenderPipeline(input) {
12601
13007
  const isGif = outputFormat === "gif";
12602
13008
  const isHls = outputFormat === "hls";
12603
13009
  const hlsSegmentSeconds = isHls ? resolveHlsSegmentSeconds(job.config) : void 0;
12604
- const hlsEncoderGopLock = resolveHlsEncoderGopLock(
12605
- outputFormat,
12606
- job.config.fps,
12607
- job.config.hlsSegmentSeconds
12608
- );
13010
+ const deliveryEncoder = resolveDeliveryEncoderOptions({
13011
+ format: outputFormat,
13012
+ fps: job.config.fps,
13013
+ hlsSegmentSeconds: job.config.hlsSegmentSeconds,
13014
+ keyframeIntervalSeconds: job.config.keyframeIntervalSeconds,
13015
+ maxBitrate: job.config.maxBitrate,
13016
+ bufferSize: job.config.bufferSize
13017
+ });
12609
13018
  const artifactTransaction = new ArtifactTransaction(
12610
13019
  outputPath,
12611
13020
  isPngSequence || isHls ? "directory" : "file",
@@ -12662,7 +13071,7 @@ async function executeRenderPipeline(input) {
12662
13071
  job.startedAt = /* @__PURE__ */ new Date();
12663
13072
  assertNotAborted();
12664
13073
  assertConfiguredFfmpegBinariesExist();
12665
- if (!existsSync16(workDir)) mkdirSync9(workDir, { recursive: true });
13074
+ if (!existsSync16(workDir)) mkdirSync10(workDir, { recursive: true });
12666
13075
  if (job.config.debug) {
12667
13076
  log.info("[Render] Debug artifacts enabled", { workDir, logPath });
12668
13077
  }
@@ -12691,22 +13100,22 @@ async function executeRenderPipeline(input) {
12691
13100
  requestedWorkers: job.config.workers ?? "auto"
12692
13101
  });
12693
13102
  const entryFile = job.config.entryFile || "index.html";
12694
- let htmlPath = join26(projectDir, entryFile);
13103
+ let htmlPath = join27(projectDir, entryFile);
12695
13104
  if (!existsSync16(htmlPath)) {
12696
13105
  throw new Error(`Entry file not found: ${htmlPath}`);
12697
13106
  }
12698
13107
  assertNotAborted();
12699
- const rawEntry = readFileSync8(htmlPath, "utf-8");
13108
+ const rawEntry = readFileSync9(htmlPath, "utf-8");
12700
13109
  if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
12701
- const wrapperPath = join26(workDir, "standalone-entry.html");
12702
- const projectIndexPath = join26(projectDir, "index.html");
13110
+ const wrapperPath = join27(workDir, "standalone-entry.html");
13111
+ const projectIndexPath = join27(projectDir, "index.html");
12703
13112
  if (!existsSync16(projectIndexPath)) {
12704
13113
  throw new Error(
12705
13114
  `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
12706
13115
  );
12707
13116
  }
12708
13117
  const standaloneHtml = extractStandaloneEntryFromIndex(
12709
- readFileSync8(projectIndexPath, "utf-8"),
13118
+ readFileSync9(projectIndexPath, "utf-8"),
12710
13119
  entryFile,
12711
13120
  rawEntry
12712
13121
  );
@@ -12715,7 +13124,7 @@ async function executeRenderPipeline(input) {
12715
13124
  `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
12716
13125
  );
12717
13126
  }
12718
- writeFileSync5(wrapperPath, standaloneHtml, "utf-8");
13127
+ writeFileSync6(wrapperPath, standaloneHtml, "utf-8");
12719
13128
  htmlPath = wrapperPath;
12720
13129
  log.info("Extracted standalone entry from index.html host context", {
12721
13130
  entryFile
@@ -12845,7 +13254,7 @@ async function executeRenderPipeline(input) {
12845
13254
  validateRenderDuration({
12846
13255
  duration: probeResult.duration,
12847
13256
  totalFrames,
12848
- fps: fpsToNumber9(job.config.fps)
13257
+ fps: fpsToNumber8(job.config.fps)
12849
13258
  });
12850
13259
  perfStages.browserProbeMs = probeResult.browserProbeMs;
12851
13260
  perfStages.compileMs = Date.now() - stage1Start;
@@ -12862,7 +13271,7 @@ async function executeRenderPipeline(input) {
12862
13271
  updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress, {
12863
13272
  code: "extract_video"
12864
13273
  });
12865
- const compiledDir = join26(workDir, "compiled");
13274
+ const compiledDir = join27(workDir, "compiled");
12866
13275
  const extractResult = await observeRenderStage(
12867
13276
  observability,
12868
13277
  "video_extract",
@@ -12996,7 +13405,7 @@ async function executeRenderPipeline(input) {
12996
13405
  try {
12997
13406
  fileServer = await createFileServer({
12998
13407
  projectDir,
12999
- compiledDir: join26(workDir, "compiled"),
13408
+ compiledDir: join27(workDir, "compiled"),
13000
13409
  port: 0,
13001
13410
  preHeadScripts: [VIRTUAL_TIME_SHIM],
13002
13411
  fps: job.config.fps
@@ -13014,8 +13423,8 @@ async function executeRenderPipeline(input) {
13014
13423
  if (!activeFileServer) {
13015
13424
  throw new Error("File server failed to initialize before frame capture");
13016
13425
  }
13017
- const framesDir = join26(workDir, "captured-frames");
13018
- if (!existsSync16(framesDir)) mkdirSync9(framesDir, { recursive: true });
13426
+ const framesDir = join27(workDir, "captured-frames");
13427
+ if (!existsSync16(framesDir)) mkdirSync10(framesDir, { recursive: true });
13019
13428
  updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
13020
13429
  code: "start_browsers"
13021
13430
  });
@@ -13486,7 +13895,7 @@ async function executeRenderPipeline(input) {
13486
13895
  hls: ".mp4"
13487
13896
  };
13488
13897
  const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
13489
- const videoOnlyPath = join26(workDir, `video-only${videoExt}`);
13898
+ const videoOnlyPath = join27(workDir, `video-only${videoExt}`);
13490
13899
  const usePageSideCompositingForTransitions = (cfg.enablePageSideCompositing || isGif) && compiled.hasShaderTransitions && !hasHdrContent && outputSupportsPageSideShaderCompositing(outputFormat);
13491
13900
  if (usePageSideCompositingForTransitions) {
13492
13901
  activeFileServer.addPreHeadScript(HF_PAGE_SIDE_COMPOSITING_STUB);
@@ -13688,6 +14097,7 @@ async function executeRenderPipeline(input) {
13688
14097
  preset,
13689
14098
  effectiveQuality,
13690
14099
  effectiveBitrate,
14100
+ deliveryEncoder,
13691
14101
  fileServer: activeFileServer,
13692
14102
  buildCaptureOptions,
13693
14103
  createRenderVideoFrameInjector,
@@ -13712,9 +14122,7 @@ async function executeRenderPipeline(input) {
13712
14122
  } else {
13713
14123
  let streamingHandled = false;
13714
14124
  if (capturePlan.kind === "sdr_segmented") {
13715
- const segmentedPlan = capturePlan;
13716
14125
  const captureFrameStart = Date.now();
13717
- resetCaptureAttemptProgress(job);
13718
14126
  const segmentFrames = resolveSegmentFrames(process.env);
13719
14127
  const segmentImageFormat = captureOptions.format || "jpeg";
13720
14128
  const segmentPlanHash = computeSegmentPlanHash({
@@ -13729,6 +14137,9 @@ async function executeRenderPipeline(input) {
13729
14137
  preset: preset.preset,
13730
14138
  quality: effectiveQuality,
13731
14139
  bitrate: effectiveBitrate,
14140
+ maxBitrate: deliveryEncoder.maxBitrate,
14141
+ bufferSize: deliveryEncoder.bufferSize,
14142
+ gopSize: deliveryEncoder.gopSize,
13732
14143
  pixelFormat: preset.pixelFormat,
13733
14144
  imageFormat: segmentImageFormat,
13734
14145
  useGpu: job.config.useGpu === true,
@@ -13739,7 +14150,7 @@ async function executeRenderPipeline(input) {
13739
14150
  pageMotionBlur: job.config.pageMotionBlur,
13740
14151
  captureQuality: captureOptions.quality
13741
14152
  });
13742
- const segmentDir = segmentDirFor(join26(projectDir, "renders"), segmentPlanHash);
14153
+ const segmentDir = segmentDirFor(join27(projectDir, "renders"), segmentPlanHash);
13743
14154
  let completedSegments = /* @__PURE__ */ new Set();
13744
14155
  if (job.config.resumeSegments === true) {
13745
14156
  const existing = readSegmentManifest(segmentDir);
@@ -13754,71 +14165,129 @@ async function executeRenderPipeline(input) {
13754
14165
  });
13755
14166
  }
13756
14167
  if (completedSegments.size === 0) {
13757
- rmSync11(segmentDir, { recursive: true, force: true });
14168
+ rmSync10(segmentDir, { recursive: true, force: true });
13758
14169
  }
13759
14170
  } else {
13760
- rmSync11(segmentDir, { recursive: true, force: true });
14171
+ rmSync10(segmentDir, { recursive: true, force: true });
13761
14172
  }
13762
- const segmentManifest = readSegmentManifest(segmentDir) ?? {
14173
+ const emptySegmentManifest = () => ({
13763
14174
  version: 1,
13764
14175
  planHash: segmentPlanHash,
13765
14176
  totalFrames,
13766
14177
  segmentFrames,
13767
14178
  completed: []
14179
+ });
14180
+ let segmentManifest = readSegmentManifest(segmentDir) ?? emptySegmentManifest();
14181
+ const invokeSegmented = () => {
14182
+ if (capturePlan.kind !== "sdr_segmented") {
14183
+ throw new Error(`Cannot invoke segmented stage with ${capturePlan.kind} plan`);
14184
+ }
14185
+ const segmentedPlan = capturePlan;
14186
+ resetCaptureAttemptProgress(job);
14187
+ return observeRenderStage(
14188
+ observability,
14189
+ "capture_segmented",
14190
+ captureStageObservationData(),
14191
+ () => runCaptureSegmentedStage({
14192
+ fileServer: activeFileServer,
14193
+ workDir,
14194
+ framesDir,
14195
+ videoOnlyPath,
14196
+ job,
14197
+ totalFrames,
14198
+ cfg,
14199
+ plan: segmentedPlan,
14200
+ log,
14201
+ probeSession,
14202
+ outputFormat,
14203
+ streamingEncoderOptions: {
14204
+ fps: job.config.fps,
14205
+ width: captureCompositionWidth ?? width,
14206
+ height: captureCompositionHeight ?? height,
14207
+ codec: preset.codec,
14208
+ preset: preset.preset,
14209
+ quality: effectiveQuality,
14210
+ bitrate: effectiveBitrate,
14211
+ pixelFormat: preset.pixelFormat,
14212
+ vp9CpuUsed: cfg.vp9CpuUsed,
14213
+ useGpu: job.config.useGpu,
14214
+ imageFormat: segmentImageFormat,
14215
+ hdr: preset.hdr,
14216
+ // Each segment sets its own GOP (at most the interval); hls never reaches this route.
14217
+ ...deliveryEncoder
14218
+ },
14219
+ buildCaptureOptions,
14220
+ createRenderVideoFrameInjector,
14221
+ abortSignal: executionSignal,
14222
+ assertNotAborted,
14223
+ onProgress,
14224
+ dedupPerfs,
14225
+ segmentFrames,
14226
+ segmentDir,
14227
+ workerCount: segmentedPlan.workerCount,
14228
+ browserRecycleEverySegments: resolveSegmentBrowserRecycle(process.env),
14229
+ completedSegments,
14230
+ onSegmentComplete: (entry) => {
14231
+ segmentManifest.completed = [
14232
+ ...segmentManifest.completed.filter((e) => e.index !== entry.index),
14233
+ { ...entry, completedAt: (/* @__PURE__ */ new Date()).toISOString() }
14234
+ ];
14235
+ writeSegmentManifest(segmentDir, segmentManifest);
14236
+ },
14237
+ updateCaptureObservability
14238
+ })
14239
+ );
13768
14240
  };
13769
- const segmentedRes = await observeRenderStage(
13770
- observability,
13771
- "capture_segmented",
13772
- captureStageObservationData(),
13773
- () => runCaptureSegmentedStage({
13774
- fileServer: activeFileServer,
13775
- workDir,
13776
- framesDir,
13777
- videoOnlyPath,
13778
- job,
13779
- totalFrames,
13780
- cfg,
13781
- plan: segmentedPlan,
13782
- log,
13783
- probeSession,
13784
- outputFormat,
13785
- streamingEncoderOptions: {
13786
- fps: job.config.fps,
13787
- width: captureCompositionWidth ?? width,
13788
- height: captureCompositionHeight ?? height,
13789
- codec: preset.codec,
13790
- preset: preset.preset,
13791
- quality: effectiveQuality,
13792
- bitrate: effectiveBitrate,
13793
- pixelFormat: preset.pixelFormat,
13794
- vp9CpuUsed: cfg.vp9CpuUsed,
13795
- useGpu: job.config.useGpu,
13796
- imageFormat: segmentImageFormat,
13797
- hdr: preset.hdr
13798
- // No hlsEncoderGopLock here: each segment sets its own GOP to
13799
- // its own length, and hls never reaches this route.
13800
- },
13801
- buildCaptureOptions,
13802
- createRenderVideoFrameInjector,
13803
- abortSignal: executionSignal,
13804
- assertNotAborted,
13805
- onProgress,
13806
- dedupPerfs,
13807
- segmentFrames,
13808
- segmentDir,
13809
- workerCount: segmentedPlan.workerCount,
13810
- browserRecycleEverySegments: resolveSegmentBrowserRecycle(process.env),
13811
- completedSegments,
13812
- onSegmentComplete: (entry) => {
13813
- segmentManifest.completed = [
13814
- ...segmentManifest.completed.filter((e) => e.index !== entry.index),
13815
- { ...entry, completedAt: (/* @__PURE__ */ new Date()).toISOString() }
13816
- ];
13817
- writeSegmentManifest(segmentDir, segmentManifest);
13818
- },
13819
- updateCaptureObservability
13820
- })
13821
- );
14241
+ let segmentedRes;
14242
+ try {
14243
+ segmentedRes = await invokeSegmented();
14244
+ } catch (err) {
14245
+ const isVerifyError = isDrawElementVerificationError(err);
14246
+ const isDeStall = isDeRendererStallError(err);
14247
+ if (!isVerifyError && !isDrawElementCaptureError(err) && !isDeStall || err instanceof RenderCancelledError || executionSignal?.aborted === true) {
14248
+ throw err;
14249
+ }
14250
+ deSelfVerifyFallback = isVerifyError;
14251
+ const t = deVerifyFallbackTelemetry(err);
14252
+ deFallbackReason = isVerifyError ? t.reason : isDeStall ? "de_renderer_stall" : "capture_error";
14253
+ deFallbackFailedDb = t.failedDb;
14254
+ deFallbackFrameIndex = t.frameIndex;
14255
+ deFallbackThresholdDb = t.thresholdDb;
14256
+ log.warn(
14257
+ "[Render] drawElement capture failed on the segmented path; re-rendering via screenshot",
14258
+ {
14259
+ error: err instanceof Error ? err.message : String(err)
14260
+ }
14261
+ );
14262
+ observability.checkpoint(
14263
+ "capture_segmented",
14264
+ "drawElement capture failed; retrying with a fresh screenshot session"
14265
+ );
14266
+ if (probeSession) {
14267
+ lastBrowserConsole = probeSession.browserConsoleBuffer;
14268
+ const orphaned = probeSession;
14269
+ probeSession = null;
14270
+ await closeOrphanedProbeForRetry(orphaned, closeCaptureSession8, log, "segmented");
14271
+ }
14272
+ rmSync10(segmentDir, { recursive: true, force: true });
14273
+ completedSegments = /* @__PURE__ */ new Set();
14274
+ segmentManifest = emptySegmentManifest();
14275
+ dedupPerfs.length = 0;
14276
+ capturePlan = replanAfterFailure(capturePlan, {
14277
+ kind: isVerifyError ? "draw_element_verification" : "draw_element_capture"
14278
+ });
14279
+ syncCapturePlan();
14280
+ updateCaptureObservability({
14281
+ forceScreenshot: capturePlan.forceScreenshot,
14282
+ deSelfVerifyFallback,
14283
+ deFallbackReason,
14284
+ deFallbackFailedDb,
14285
+ deFallbackFrameIndex,
14286
+ deFallbackThresholdDb
14287
+ });
14288
+ segmentedRes = await invokeSegmented();
14289
+ observability.clearFailure("capture_segmented");
14290
+ }
13822
14291
  if (segmentedRes.success) {
13823
14292
  streamingHandled = true;
13824
14293
  workerCount = segmentedRes.workerCount;
@@ -13838,7 +14307,7 @@ async function executeRenderPipeline(input) {
13838
14307
  );
13839
14308
  updateCaptureObservability({ segmentRetries: segmentedRes.segmentRetries });
13840
14309
  if (job.config.keepSegments !== true) {
13841
- rmSync11(segmentDir, { recursive: true, force: true });
14310
+ rmSync10(segmentDir, { recursive: true, force: true });
13842
14311
  }
13843
14312
  } else {
13844
14313
  capturePlan = replanAfterFailure(capturePlan, { kind: "streaming_unavailable" });
@@ -13891,7 +14360,7 @@ async function executeRenderPipeline(input) {
13891
14360
  // assemble stage's `-c copy` segmentation cuts exactly on
13892
14361
  // the segment boundary. Every other format leaves the
13893
14362
  // encoder's open-GOP output untouched.
13894
- ...hlsEncoderGopLock
14363
+ ...deliveryEncoder
13895
14364
  },
13896
14365
  buildCaptureOptions,
13897
14366
  createRenderVideoFrameInjector,
@@ -14054,7 +14523,7 @@ async function executeRenderPipeline(input) {
14054
14523
  engineConfig: cfg,
14055
14524
  // Same value the streaming encoder above spreads — the disk and
14056
14525
  // chunked-concat encoders need an identical GOP lock for HLS.
14057
- ...hlsEncoderGopLock,
14526
+ ...deliveryEncoder,
14058
14527
  abortSignal: executionSignal,
14059
14528
  assertNotAborted,
14060
14529
  onProgress
@@ -14110,8 +14579,8 @@ async function executeRenderPipeline(input) {
14110
14579
  "capture_disk",
14111
14580
  "drawElement capture failed; retrying with a fresh screenshot session"
14112
14581
  );
14113
- rmSync11(framesDir, { recursive: true, force: true });
14114
- mkdirSync9(framesDir, { recursive: true });
14582
+ rmSync10(framesDir, { recursive: true, force: true });
14583
+ mkdirSync10(framesDir, { recursive: true });
14115
14584
  resetCaptureAttemptProgress(job);
14116
14585
  dedupPerfs.length = 0;
14117
14586
  cfg.useDrawElement = false;
@@ -14208,7 +14677,7 @@ async function executeRenderPipeline(input) {
14208
14677
  buildArtifactExpectation({
14209
14678
  outputFormat,
14210
14679
  durationSeconds: job.duration,
14211
- fps: fpsToNumber9(job.config.fps),
14680
+ fps: fpsToNumber8(job.config.fps),
14212
14681
  expectedFrames: captureTotalFrames,
14213
14682
  width: outputWidth,
14214
14683
  height: outputHeight
@@ -14287,7 +14756,7 @@ async function executeRenderPipeline(input) {
14287
14756
  job.perfSummary = perfSummary;
14288
14757
  if (job.config.debug) {
14289
14758
  try {
14290
- writeFileSync5(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
14759
+ writeFileSync6(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
14291
14760
  } catch (err) {
14292
14761
  log.debug("Failed to write perf summary", {
14293
14762
  perfOutputPath,
@@ -14297,8 +14766,8 @@ async function executeRenderPipeline(input) {
14297
14766
  }
14298
14767
  if (job.config.debug) {
14299
14768
  if (!isPngSequence && !isHls && existsSync16(stagedOutputPath)) {
14300
- const debugOutput = join26(workDir, `output${videoExt}`);
14301
- copyFileSync4(stagedOutputPath, debugOutput);
14769
+ const debugOutput = join27(workDir, `output${videoExt}`);
14770
+ copyFileSync5(stagedOutputPath, debugOutput);
14302
14771
  }
14303
14772
  }
14304
14773
  await commitArtifactTransaction(artifactTransaction, () => {
@@ -14422,30 +14891,31 @@ import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
14422
14891
  // src/services/distributed/plan.ts
14423
14892
  import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframes/engine";
14424
14893
  import {
14425
- copyFileSync as copyFileSync5,
14894
+ copyFileSync as copyFileSync6,
14426
14895
  cpSync as cpSync2,
14427
14896
  existsSync as existsSync21,
14428
14897
  linkSync,
14429
- mkdirSync as mkdirSync13,
14430
- rmSync as rmSync13,
14431
- writeFileSync as writeFileSync8
14898
+ mkdirSync as mkdirSync14,
14899
+ rmSync as rmSync12,
14900
+ writeFileSync as writeFileSync9
14432
14901
  } from "node:fs";
14433
- import { extname as extname6, isAbsolute as isAbsolute3, join as join34, relative as relative5, sep as sep2 } from "node:path";
14902
+ import { extname as extname6, isAbsolute as isAbsolute3, join as join35, relative as relative5, sep as sep2 } from "node:path";
14434
14903
  import {
14435
- fpsToNumber as fpsToNumber10,
14904
+ fpsToNumber as fpsToNumber9,
14436
14905
  HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
14437
14906
  } from "@hyperframes/core";
14438
14907
  import {
14439
14908
  getEncoderPreset as getEncoderPreset4,
14909
+ keyframeIntervalToGopSize as keyframeIntervalToGopSize3,
14440
14910
  normalizeVp9CpuUsed,
14441
14911
  resolveConfig as resolveConfig3,
14442
14912
  declareContentAddressedUrl,
14443
- renameWithRetry as renameWithRetry6,
14913
+ renameWithRetry as renameWithRetry7,
14444
14914
  declareProbeFactsUrl
14445
14915
  } from "@hyperframes/engine";
14446
14916
 
14447
14917
  // src/services/distributed/planFrameCost.ts
14448
- import { join as join29 } from "node:path";
14918
+ import { join as join30 } from "node:path";
14449
14919
  import {
14450
14920
  closeCaptureSession as closeCaptureSession9,
14451
14921
  createCaptureSession as createCaptureSession9,
@@ -14455,7 +14925,7 @@ import {
14455
14925
 
14456
14926
  // src/services/distributed/chunkFrames.ts
14457
14927
  import { existsSync as existsSync18, readdirSync as readdirSync6 } from "node:fs";
14458
- import { extname as extname4, join as join28 } from "node:path";
14928
+ import { extname as extname4, join as join29 } from "node:path";
14459
14929
  import {
14460
14930
  compositionRequiresWebGpu as compositionRequiresWebGpu3,
14461
14931
  createFrameLookupTable as createFrameLookupTable2,
@@ -14465,8 +14935,8 @@ import {
14465
14935
 
14466
14936
  // src/services/distributed/shared.ts
14467
14937
  import { execFile as execFileCallback } from "node:child_process";
14468
- import { dirname as dirname11, join as join27 } from "node:path";
14469
- import { existsSync as existsSync17, readFileSync as readFileSync9 } from "node:fs";
14938
+ import { dirname as dirname12, join as join28 } from "node:path";
14939
+ import { existsSync as existsSync17, readFileSync as readFileSync10 } from "node:fs";
14470
14940
  import { fileURLToPath as fileURLToPath5 } from "node:url";
14471
14941
  import { promisify as promisify2 } from "node:util";
14472
14942
  import {
@@ -14481,7 +14951,7 @@ function isPlanAudioArtifactPath(path) {
14481
14951
  }
14482
14952
  function resolvePlanAudioPath(planDir) {
14483
14953
  for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
14484
- const candidate = join27(planDir, name);
14954
+ const candidate = join28(planDir, name);
14485
14955
  if (existsSync17(candidate)) return candidate;
14486
14956
  }
14487
14957
  return null;
@@ -14738,13 +15208,13 @@ function buildSyntheticRenderJob(input) {
14738
15208
  var cachedProducerVersion = null;
14739
15209
  function readProducerVersion() {
14740
15210
  if (cachedProducerVersion !== null) return cachedProducerVersion;
14741
- const startDir = dirname11(fileURLToPath5(import.meta.url));
15211
+ const startDir = dirname12(fileURLToPath5(import.meta.url));
14742
15212
  let current = startDir;
14743
15213
  for (let i = 0; i < 10; i++) {
14744
- const candidate = join27(current, "package.json");
15214
+ const candidate = join28(current, "package.json");
14745
15215
  if (existsSync17(candidate)) {
14746
15216
  try {
14747
- const pkg = JSON.parse(readFileSync9(candidate, "utf-8"));
15217
+ const pkg = JSON.parse(readFileSync10(candidate, "utf-8"));
14748
15218
  if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
14749
15219
  cachedProducerVersion = pkg.version;
14750
15220
  return pkg.version;
@@ -14752,7 +15222,7 @@ function readProducerVersion() {
14752
15222
  } catch {
14753
15223
  }
14754
15224
  }
14755
- const parent = dirname11(current);
15225
+ const parent = dirname12(current);
14756
15226
  if (parent === current) break;
14757
15227
  current = parent;
14758
15228
  }
@@ -14795,7 +15265,7 @@ function frameNumberFromFileName(name) {
14795
15265
  function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
14796
15266
  const result = [];
14797
15267
  for (const v of videos) {
14798
- const outputDir = join28(planDir, "video-frames", v.videoId);
15268
+ const outputDir = join29(planDir, "video-frames", v.videoId);
14799
15269
  if (!existsSync18(outputDir)) {
14800
15270
  throw new Error(
14801
15271
  `[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
@@ -14814,7 +15284,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
14814
15284
  if (!frameName) continue;
14815
15285
  const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
14816
15286
  const frameIndex = frameNumber === null ? i : frameNumber - 1;
14817
- framePaths.set(frameIndex, join28(outputDir, frameName));
15287
+ framePaths.set(frameIndex, join29(outputDir, frameName));
14818
15288
  }
14819
15289
  result.push({
14820
15290
  videoId: v.videoId,
@@ -14904,14 +15374,14 @@ async function extractDeferredVideoFramesForChunk(input) {
14904
15374
  const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
14905
15375
  return Promise.all(
14906
15376
  deferred.map(async (video) => {
14907
- const outputDir = join28(input.outputRoot, video.videoId);
15377
+ const outputDir = join29(input.outputRoot, video.videoId);
14908
15378
  const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
14909
15379
  if (indexes.length === 0) {
14910
15380
  return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
14911
15381
  }
14912
15382
  const range = video.deferredRange;
14913
15383
  const remoteUrl = remoteSources[range.sourcePath];
14914
- const sourcePath = remoteUrl ?? join28(input.planDir, ...range.sourcePath.split("/"));
15384
+ const sourcePath = remoteUrl ?? join29(input.planDir, ...range.sourcePath.split("/"));
14915
15385
  if (remoteUrl === void 0 && !existsSync18(sourcePath)) {
14916
15386
  throw new RenderChunkValidationError(
14917
15387
  MISSING_PLAN_ARTIFACT,
@@ -14960,12 +15430,12 @@ async function measurePlanSecondsPerFrame(input) {
14960
15430
  planVideos,
14961
15431
  frames: sampled,
14962
15432
  fps: fpsRatio,
14963
- outputRoot: join29(workDir, "frame-cost-video-frames"),
15433
+ outputRoot: join30(workDir, "frame-cost-video-frames"),
14964
15434
  cfg: input.cfg
14965
15435
  })
14966
15436
  ];
14967
15437
  const lookup = extracted.length > 0 ? createFrameLookupTable3(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
14968
- const compiledDir = join29(planDir, "compiled");
15438
+ const compiledDir = join30(planDir, "compiled");
14969
15439
  const fileServer = await createFileServer({
14970
15440
  projectDir: compiledDir,
14971
15441
  compiledDir,
@@ -14977,7 +15447,7 @@ async function measurePlanSecondsPerFrame(input) {
14977
15447
  try {
14978
15448
  session = await createCaptureSession9(
14979
15449
  fileServer.url,
14980
- join29(workDir, "frame-cost"),
15450
+ join30(workDir, "frame-cost"),
14981
15451
  buildChunkCaptureOptions({
14982
15452
  dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
14983
15453
  encoder: input,
@@ -15074,8 +15544,8 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
15074
15544
  }
15075
15545
 
15076
15546
  // src/services/render/stages/freezePlan.ts
15077
- import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync10, readdirSync as readdirSync7, writeFileSync as writeFileSync6 } from "node:fs";
15078
- import { join as join30, relative as relative3, resolve as resolve8 } from "node:path";
15547
+ import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync11, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
15548
+ import { join as join31, relative as relative3, resolve as resolve9 } from "node:path";
15079
15549
 
15080
15550
  // src/services/distributed/planProtocol.ts
15081
15551
  var PLAN_SCHEMA_VERSION = 1;
@@ -15173,12 +15643,12 @@ function readPlanProtocolV1(planJson, capabilities = DISTRIBUTED_RENDER_CAPABILI
15173
15643
  }
15174
15644
 
15175
15645
  // src/services/render/stages/planHash.ts
15176
- import { createHash as createHash6 } from "node:crypto";
15177
- import { closeSync as closeSync4, createReadStream as createReadStream3, openSync as openSync4, readSync as readSync3 } from "node:fs";
15646
+ import { createHash as createHash7 } from "node:crypto";
15647
+ import { closeSync as closeSync5, createReadStream as createReadStream3, openSync as openSync5, readSync as readSync4 } from "node:fs";
15178
15648
  var PLAN_HASH_SCHEMA_PREFIX = "hyperframes-plan-hash-v1\0";
15179
15649
  var FIELD_DELIMITER = Buffer.from([0]);
15180
15650
  function computePlanHash(input) {
15181
- const hash = createHash6("sha256");
15651
+ const hash = createHash7("sha256");
15182
15652
  hash.update(PLAN_HASH_SCHEMA_PREFIX, "utf8");
15183
15653
  hash.update(input.compositionHtml);
15184
15654
  hash.update(FIELD_DELIMITER);
@@ -15225,27 +15695,27 @@ function canonicalJsonStringify(value) {
15225
15695
  throw new TypeError(`canonicalJsonStringify: unsupported value type ${typeof value}`);
15226
15696
  }
15227
15697
  function sha256Hex(bytes) {
15228
- const h = createHash6("sha256");
15698
+ const h = createHash7("sha256");
15229
15699
  h.update(bytes);
15230
15700
  return h.digest("hex");
15231
15701
  }
15232
15702
  function sha256File(path) {
15233
- const hash = createHash6("sha256");
15703
+ const hash = createHash7("sha256");
15234
15704
  const buffer = Buffer.allocUnsafe(1024 * 1024);
15235
- const fd = openSync4(path, "r");
15705
+ const fd = openSync5(path, "r");
15236
15706
  try {
15237
15707
  let bytesRead = 0;
15238
15708
  do {
15239
- bytesRead = readSync3(fd, buffer, 0, buffer.byteLength, null);
15709
+ bytesRead = readSync4(fd, buffer, 0, buffer.byteLength, null);
15240
15710
  if (bytesRead > 0) hash.update(buffer.subarray(0, bytesRead));
15241
15711
  } while (bytesRead > 0);
15242
15712
  } finally {
15243
- closeSync4(fd);
15713
+ closeSync5(fd);
15244
15714
  }
15245
15715
  return hash.digest("hex");
15246
15716
  }
15247
15717
  async function sha256FileAsync(path) {
15248
- const hash = createHash6("sha256");
15718
+ const hash = createHash7("sha256");
15249
15719
  for await (const chunk of createReadStream3(path, { highWaterMark: 1024 * 1024 })) {
15250
15720
  hash.update(chunk);
15251
15721
  }
@@ -15289,11 +15759,11 @@ function stripUndefined(value) {
15289
15759
  }
15290
15760
  function listPlanFiles(planDir) {
15291
15761
  const results = [];
15292
- const rootResolved = resolve8(planDir);
15762
+ const rootResolved = resolve9(planDir);
15293
15763
  function walk(dir) {
15294
15764
  const entries = readdirSync7(dir, { withFileTypes: true });
15295
15765
  for (const entry of entries) {
15296
- const full = join30(dir, entry.name);
15766
+ const full = join31(dir, entry.name);
15297
15767
  if (entry.isDirectory()) {
15298
15768
  walk(full);
15299
15769
  } else if (entry.isFile()) {
@@ -15314,7 +15784,7 @@ function collectPlanAssetShas(planDir, digests) {
15314
15784
  const assets = [];
15315
15785
  for (const file of files) {
15316
15786
  if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
15317
- compositionHtml = readFileSync10(file.absolutePath);
15787
+ compositionHtml = readFileSync11(file.absolutePath);
15318
15788
  continue;
15319
15789
  }
15320
15790
  if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
@@ -15334,23 +15804,23 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
15334
15804
  function writePlanRemoteFiles(planDir, files) {
15335
15805
  if (files.length === 0) return;
15336
15806
  const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
15337
- writeFileSync6(join30(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
15807
+ writeFileSync7(join31(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
15338
15808
  }
15339
15809
  function readPlanRemoteFiles(planDir) {
15340
- const path = join30(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
15341
- return existsSync19(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
15810
+ const path = join31(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
15811
+ return existsSync19(path) ? JSON.parse(readFileSync11(path, "utf-8")) : [];
15342
15812
  }
15343
15813
  function recomputePlanHashFromPlanDir(planDir, digests) {
15344
- const planJsonPath = join30(planDir, "plan.json");
15345
- const encoderJsonPath = join30(planDir, "meta", "encoder.json");
15814
+ const planJsonPath = join31(planDir, "plan.json");
15815
+ const encoderJsonPath = join31(planDir, "meta", "encoder.json");
15346
15816
  if (!existsSync19(planJsonPath)) {
15347
15817
  throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
15348
15818
  }
15349
15819
  if (!existsSync19(encoderJsonPath)) {
15350
15820
  throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
15351
15821
  }
15352
- const planJson = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
15353
- const encoderConfigCanonicalJson = readFileSync10(encoderJsonPath, "utf-8");
15822
+ const planJson = JSON.parse(readFileSync11(planJsonPath, "utf-8"));
15823
+ const encoderConfigCanonicalJson = readFileSync11(encoderJsonPath, "utf-8");
15354
15824
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
15355
15825
  return computePlanHash({
15356
15826
  compositionHtml,
@@ -15379,18 +15849,18 @@ async function freezePlan(input) {
15379
15849
  if (!existsSync19(planDir)) {
15380
15850
  throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
15381
15851
  }
15382
- const metaDir = join30(planDir, "meta");
15383
- if (!existsSync19(metaDir)) mkdirSync10(metaDir, { recursive: true });
15384
- writeFileSync6(
15385
- join30(metaDir, "composition.json"),
15852
+ const metaDir = join31(planDir, "meta");
15853
+ if (!existsSync19(metaDir)) mkdirSync11(metaDir, { recursive: true });
15854
+ writeFileSync7(
15855
+ join31(metaDir, "composition.json"),
15386
15856
  `${JSON.stringify(composition, null, 2)}
15387
15857
  `,
15388
15858
  "utf-8"
15389
15859
  );
15390
15860
  const encoderForCanonical = stripUndefined(encoder);
15391
15861
  const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
15392
- writeFileSync6(join30(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
15393
- writeFileSync6(join30(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
15862
+ writeFileSync7(join31(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
15863
+ writeFileSync7(join31(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
15394
15864
  `, "utf-8");
15395
15865
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
15396
15866
  const planHash = computePlanHash({
@@ -15414,8 +15884,8 @@ async function freezePlan(input) {
15414
15884
  duration: durationSeconds,
15415
15885
  hasAudio
15416
15886
  };
15417
- const planJsonPath = join30(planDir, "plan.json");
15418
- writeFileSync6(planJsonPath, `${JSON.stringify(planJson, null, 2)}
15887
+ const planJsonPath = join31(planDir, "plan.json");
15888
+ writeFileSync7(planJsonPath, `${JSON.stringify(planJson, null, 2)}
15419
15889
  `, "utf-8");
15420
15890
  return { planJsonPath, planHash };
15421
15891
  }
@@ -15456,16 +15926,16 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
15456
15926
  }
15457
15927
 
15458
15928
  // src/services/distributed/chunkAudio.ts
15459
- import { closeSync as closeSync5, existsSync as existsSync20, ftruncateSync, mkdirSync as mkdirSync12, openSync as openSync5, readFileSync as readFileSync11 } from "node:fs";
15460
- import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
15461
- import { join as join32 } from "node:path";
15462
- import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
15929
+ import { closeSync as closeSync6, existsSync as existsSync20, ftruncateSync, mkdirSync as mkdirSync13, openSync as openSync6, readFileSync as readFileSync12 } from "node:fs";
15930
+ import { writeFileSync as writeFileSync8, writeSync } from "node:fs";
15931
+ import { join as join33 } from "node:path";
15932
+ import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
15463
15933
 
15464
15934
  // src/services/distributed/mp4Stitch.ts
15465
- import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync4, rmSync as rmSync12 } from "node:fs";
15935
+ import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync4, rmSync as rmSync11 } from "node:fs";
15466
15936
  import { open } from "node:fs/promises";
15467
- import { renameWithRetry as renameWithRetry5 } from "@hyperframes/engine";
15468
- import { dirname as dirname12, join as join31 } from "node:path";
15937
+ import { renameWithRetry as renameWithRetry6 } from "@hyperframes/engine";
15938
+ import { dirname as dirname13, join as join32 } from "node:path";
15469
15939
  import { finished, pipeline } from "node:stream/promises";
15470
15940
  function boxAt(src, off, at, limit) {
15471
15941
  let size = src.readUInt32BE(off);
@@ -15478,16 +15948,16 @@ function boxAt(src, off, at, limit) {
15478
15948
  return { type: src.toString("latin1", off + 4, off + 8), start: at, bodyStart, end: at + size };
15479
15949
  }
15480
15950
  function childBoxes(buf, parent) {
15481
- const boxes = [];
15482
- for (let at = parent.bodyStart; at < parent.end; at = boxes.at(-1).end) {
15483
- boxes.push(boxAt(buf, at, at, parent.end));
15951
+ const boxes2 = [];
15952
+ for (let at = parent.bodyStart; at < parent.end; at = boxes2.at(-1).end) {
15953
+ boxes2.push(boxAt(buf, at, at, parent.end));
15484
15954
  }
15485
- return boxes;
15955
+ return boxes2;
15486
15956
  }
15487
15957
  function findChild(buf, parent, type) {
15488
15958
  return childBoxes(buf, parent).find((box2) => box2.type === type);
15489
15959
  }
15490
- function child(buf, parent, type) {
15960
+ function child2(buf, parent, type) {
15491
15961
  const found = findChild(buf, parent, type);
15492
15962
  if (!found) throw new Error(`[mp4Stitch] ${parent.type} has no ${type}`);
15493
15963
  return found;
@@ -15539,7 +16009,7 @@ function rows(buf, table, columns, countAt = 4) {
15539
16009
  function readMediaTime(moov, trak) {
15540
16010
  const edts = findChild(moov, trak, "edts");
15541
16011
  if (!edts) return 0;
15542
- const elst = child(moov, edts, "elst");
16012
+ const elst = child2(moov, edts, "elst");
15543
16013
  const wide = moov[elst.bodyStart] === 1;
15544
16014
  const entries = moov.readUInt32BE(elst.bodyStart + 4);
15545
16015
  for (let i = 0, at = elst.bodyStart + 8; i < entries; i++, at += wide ? 20 : 12) {
@@ -15554,7 +16024,7 @@ function readSizes(moov, stsz) {
15554
16024
  if (!fixedSize) return rows(moov, stsz, 1, 8).map(([size]) => size);
15555
16025
  return Array(moov.readUInt32BE(stsz.bodyStart + 8)).fill(fixedSize);
15556
16026
  }
15557
- function readSync4(moov, stbl, sampleCount) {
16027
+ function readSync5(moov, stbl, sampleCount) {
15558
16028
  const stss = findChild(moov, stbl, "stss");
15559
16029
  if (!stss) return null;
15560
16030
  const sync = Array(sampleCount).fill(false);
@@ -15564,10 +16034,10 @@ function readSync4(moov, stbl, sampleCount) {
15564
16034
  function readChunkOffsets(moov, stbl) {
15565
16035
  const stco = findChild(moov, stbl, "stco");
15566
16036
  if (stco) return rows(moov, stco, 1).map(([offset]) => offset);
15567
- return rows(moov, child(moov, stbl, "co64"), 2).map(([hi, lo]) => hi * 2 ** 32 + lo);
16037
+ return rows(moov, child2(moov, stbl, "co64"), 2).map(([hi, lo]) => hi * 2 ** 32 + lo);
15568
16038
  }
15569
16039
  function readSamplesPerChunk(moov, stbl, chunkCount) {
15570
- const stsc = rows(moov, child(moov, stbl, "stsc"), 3);
16040
+ const stsc = rows(moov, child2(moov, stbl, "stsc"), 3);
15571
16041
  return stsc.flatMap(([firstChunk, perChunk, description], i) => {
15572
16042
  if (description !== 1) throw new Error("[mp4Stitch] chunk uses more than one sample entry");
15573
16043
  const nextFirst = stsc[i + 1]?.[0] ?? chunkCount + 1;
@@ -15575,28 +16045,28 @@ function readSamplesPerChunk(moov, stbl, chunkCount) {
15575
16045
  });
15576
16046
  }
15577
16047
  function readTrack(moov, trak) {
15578
- const mdia = child(moov, trak, "mdia");
15579
- const hdlr = child(moov, mdia, "hdlr");
15580
- const stbl = child(moov, child(moov, mdia, "minf"), "stbl");
16048
+ const mdia = child2(moov, trak, "mdia");
16049
+ const hdlr = child2(moov, mdia, "hdlr");
16050
+ const stbl = child2(moov, child2(moov, mdia, "minf"), "stbl");
15581
16051
  if (findChild(moov, stbl, "ctts")) throw new Error("[mp4Stitch] chunk has ctts (B-frames)");
15582
- const stts = rows(moov, child(moov, stbl, "stts"), 2);
16052
+ const stts = rows(moov, child2(moov, stbl, "stts"), 2);
15583
16053
  const durations = stts.flatMap(([n, delta]) => Array(n).fill(delta));
15584
- const sizes = readSizes(moov, child(moov, stbl, "stsz"));
16054
+ const sizes = readSizes(moov, child2(moov, stbl, "stsz"));
15585
16055
  const chunkOffsets = readChunkOffsets(moov, stbl);
15586
16056
  const samplesPerChunk = readSamplesPerChunk(moov, stbl, chunkOffsets.length);
15587
16057
  if (durations.length !== sizes.length || sum(samplesPerChunk) !== sizes.length) {
15588
16058
  throw new Error("[mp4Stitch] sample tables disagree on the sample count");
15589
16059
  }
15590
- const stsd = child(moov, stbl, "stsd");
16060
+ const stsd = child2(moov, stbl, "stsd");
15591
16061
  return {
15592
16062
  trak,
15593
16063
  handler: moov.toString("latin1", hdlr.bodyStart + 8, hdlr.bodyStart + 12),
15594
- timescale: timescaleOf(moov, child(moov, mdia, "mdhd")),
16064
+ timescale: timescaleOf(moov, child2(moov, mdia, "mdhd")),
15595
16065
  mediaTime: readMediaTime(moov, trak),
15596
16066
  stsd: moov.subarray(stsd.start, stsd.end),
15597
16067
  durations,
15598
16068
  sizes,
15599
- sync: readSync4(moov, stbl, sizes.length),
16069
+ sync: readSync5(moov, stbl, sizes.length),
15600
16070
  samplesPerChunk,
15601
16071
  chunkOffsets
15602
16072
  };
@@ -15737,9 +16207,9 @@ function buildStitchedHeader(layouts, audio) {
15737
16207
  }
15738
16208
  const template = layouts[0].moov;
15739
16209
  const root = boxAt(template, 0, 0, template.length);
15740
- const mvhd = child(template, root, "mvhd");
16210
+ const mvhd = child2(template, root, "mvhd");
15741
16211
  const movieTimescale = timescaleOf(template, mvhd);
15742
- const chunkTrackIds = plan2.slice(0, chunkTrackCount).map((track) => idOf(template, child(template, track.trak, "tkhd")));
16212
+ const chunkTrackIds = plan2.slice(0, chunkTrackCount).map((track) => idOf(template, child2(template, track.trak, "tkhd")));
15743
16213
  const audioTrackId = Math.max(...chunkTrackIds) + 1;
15744
16214
  const buildMoov = (dataStart2) => {
15745
16215
  const traks = plan2.map((track, t) => {
@@ -15829,18 +16299,18 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
15829
16299
  const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
15830
16300
  const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
15831
16301
  const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
15832
- mkdirSync11(dirname12(outputPath), { recursive: true });
16302
+ mkdirSync12(dirname13(outputPath), { recursive: true });
15833
16303
  const workDir = mkdtempSync4(`${outputPath}.stitch-`);
15834
- const out = createWriteStream(join31(workDir, "out.mp4"));
16304
+ const out = createWriteStream(join32(workDir, "out.mp4"));
15835
16305
  try {
15836
16306
  out.write(header);
15837
16307
  for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
15838
16308
  out.end();
15839
16309
  await finished(out);
15840
- renameWithRetry5(join31(workDir, "out.mp4"), outputPath);
16310
+ renameWithRetry6(join32(workDir, "out.mp4"), outputPath);
15841
16311
  } finally {
15842
16312
  out.destroy();
15843
- rmSync12(workDir, { recursive: true, force: true });
16313
+ rmSync11(workDir, { recursive: true, force: true });
15844
16314
  }
15845
16315
  }
15846
16316
 
@@ -15929,9 +16399,9 @@ async function writeChunkAudioSlices(input) {
15929
16399
  offset += track.sizes[offsets.length - 1];
15930
16400
  }
15931
16401
  });
15932
- const bytes = readFileSync11(input.audioPath);
16402
+ const bytes = readFileSync12(input.audioPath);
15933
16403
  const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
15934
- mkdirSync12(join32(input.planDir, SLICE_DIR), { recursive: true });
16404
+ mkdirSync13(join33(input.planDir, SLICE_DIR), { recursive: true });
15935
16405
  input.chunks.forEach((_, index) => {
15936
16406
  const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
15937
16407
  if (!cut) return;
@@ -15939,18 +16409,18 @@ async function writeChunkAudioSlices(input) {
15939
16409
  const start = offsets[cut.firstPacket];
15940
16410
  layout.partStarts[index] = start;
15941
16411
  const end = offsets[last] + track.sizes[last];
15942
- writeFileSync7(join32(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
16412
+ writeFileSync8(join33(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
15943
16413
  });
15944
16414
  const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
15945
- writeFileSync7(join32(input.planDir, SLICE_FRAME), Buffer.concat(frame));
15946
- writeFileSync7(join32(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
16415
+ writeFileSync8(join33(input.planDir, SLICE_FRAME), Buffer.concat(frame));
16416
+ writeFileSync8(join33(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
15947
16417
  }
15948
16418
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
15949
- const layoutPath = join32(planDir, SLICE_LAYOUT);
16419
+ const layoutPath = join33(planDir, SLICE_LAYOUT);
15950
16420
  if (!existsSync20(layoutPath)) return resolvePlanAudioPath(planDir);
15951
- const layout = JSON.parse(readFileSync11(layoutPath, "utf-8"));
15952
- const frame = readFileSync11(join32(planDir, SLICE_FRAME));
15953
- const fd = openSync5(scratchPath, "w");
16421
+ const layout = JSON.parse(readFileSync12(layoutPath, "utf-8"));
16422
+ const frame = readFileSync12(join33(planDir, SLICE_FRAME));
16423
+ const fd = openSync6(scratchPath, "w");
15954
16424
  try {
15955
16425
  writeSync(fd, frame, 0, layout.payloadStart, 0);
15956
16426
  writeSync(
@@ -15962,12 +16432,12 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
15962
16432
  );
15963
16433
  const start = layout.partStarts[chunkIndex];
15964
16434
  if (start !== void 0) {
15965
- const part = readFileSync11(join32(planDir, chunkAudioSlicePath(chunkIndex)));
16435
+ const part = readFileSync12(join33(planDir, chunkAudioSlicePath(chunkIndex)));
15966
16436
  writeSync(fd, part, 0, part.length, start);
15967
16437
  }
15968
16438
  ftruncateSync(fd, layout.sizeBytes);
15969
16439
  } finally {
15970
- closeSync5(fd);
16440
+ closeSync6(fd);
15971
16441
  }
15972
16442
  return scratchPath;
15973
16443
  }
@@ -15987,7 +16457,7 @@ async function muxChunkWithAudio(input) {
15987
16457
  const args = [...chunkMuxArgs(input, cut), "-c", "copy"];
15988
16458
  appendRenderProvenanceArgs(args, input.outputPath);
15989
16459
  args.push("-y", input.outputPath);
15990
- const result = await runFfmpeg6(args, { signal: input.signal, timeout: input.timeoutMs });
16460
+ const result = await runFfmpeg7(args, { signal: input.signal, timeout: input.timeoutMs });
15991
16461
  if (!result.success) {
15992
16462
  throw new Error(
15993
16463
  `[chunkAudio] chunk mux failed: ${describeFfmpegFailure3(result, input.timeoutMs)}`
@@ -16016,9 +16486,9 @@ async function muxChunkWithAudio(input) {
16016
16486
  }
16017
16487
 
16018
16488
  // src/services/distributed/planSize.ts
16019
- import { createHash as createHash7 } from "node:crypto";
16489
+ import { createHash as createHash8 } from "node:crypto";
16020
16490
  import { lstatSync as lstatSync2, readdirSync as readdirSync8 } from "node:fs";
16021
- import { extname as extname5, join as join33, relative as relative4 } from "node:path";
16491
+ import { extname as extname5, join as join34, relative as relative4 } from "node:path";
16022
16492
  var MAX_TOP_COMPONENTS = 10;
16023
16493
  var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
16024
16494
  ".aac",
@@ -16046,7 +16516,7 @@ var PLAN_ROOT_CATEGORY = {
16046
16516
  "plan.json": "metadata"
16047
16517
  };
16048
16518
  function hashComponent(value) {
16049
- return createHash7("sha256").update(value).digest("hex").slice(0, 12);
16519
+ return createHash8("sha256").update(value).digest("hex").slice(0, 12);
16050
16520
  }
16051
16521
  function classifyFile(segments, rootKind) {
16052
16522
  const rootName = segments[0] ?? "";
@@ -16130,7 +16600,7 @@ function walkRegularFiles(dir, visit) {
16130
16600
  return;
16131
16601
  }
16132
16602
  for (const entry of entries) {
16133
- const full = join33(dir, entry.name);
16603
+ const full = join34(dir, entry.name);
16134
16604
  if (entry.isDirectory()) {
16135
16605
  walkRegularFiles(full, visit);
16136
16606
  } else if (entry.isFile()) {
@@ -16342,13 +16812,13 @@ function assertPositiveInteger2(name, value) {
16342
16812
  }
16343
16813
  var FREEZE_OWNED_PLAN_FILES = [
16344
16814
  "plan.json",
16345
- join34("meta", "composition.json"),
16346
- join34("meta", "encoder.json"),
16347
- join34("meta", "chunks.json")
16815
+ join35("meta", "composition.json"),
16816
+ join35("meta", "encoder.json"),
16817
+ join35("meta", "chunks.json")
16348
16818
  ];
16349
16819
  function removeFreezeOwnedPlanFiles(planDir) {
16350
16820
  for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
16351
- rmSync13(join34(planDir, relativePath), { force: true });
16821
+ rmSync12(join35(planDir, relativePath), { force: true });
16352
16822
  }
16353
16823
  }
16354
16824
  function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
@@ -16408,6 +16878,9 @@ function buildLockedRenderConfig(input) {
16408
16878
  preset,
16409
16879
  crf: config.crf,
16410
16880
  bitrate: config.bitrate,
16881
+ maxBitrate: config.maxBitrate,
16882
+ bufferSize: config.bufferSize,
16883
+ keyframeIntervalFrames: config.keyframeIntervalSeconds === void 0 ? void 0 : keyframeIntervalToGopSize3({ num: config.fps, den: 1 }, config.keyframeIntervalSeconds),
16411
16884
  ...locksVp9CpuUsed,
16412
16885
  // GOP === chunkSize so every chunk's first frame is an IDR keyframe and
16413
16886
  // ffmpeg concat-copy round-trips losslessly.
@@ -16493,23 +16966,23 @@ function createVideoSourceShipper(input) {
16493
16966
  const known = shipped.get(srcPath);
16494
16967
  if (known) return known;
16495
16968
  const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
16496
- const current = stagedRel === null ? srcPath : join34(input.planDir, "video-frames", stagedRel);
16969
+ const current = stagedRel === null ? srcPath : join35(input.planDir, "video-frames", stagedRel);
16497
16970
  let result = null;
16498
16971
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
16499
16972
  const rel = pathInside(root, srcPath);
16500
- if (rel !== null && (existsSync21(join34(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
16973
+ if (rel !== null && (existsSync21(join35(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
16501
16974
  result = ["compiled", ...rel.split(sep2)].join("/");
16502
16975
  break;
16503
16976
  }
16504
16977
  }
16505
16978
  if (result === null) {
16506
16979
  result = `video-sources/${shipped.size}${extname6(current)}`;
16507
- const destination = join34(input.planDir, ...result.split("/"));
16508
- mkdirSync13(join34(input.planDir, "video-sources"), { recursive: true });
16980
+ const destination = join35(input.planDir, ...result.split("/"));
16981
+ mkdirSync14(join35(input.planDir, "video-sources"), { recursive: true });
16509
16982
  try {
16510
16983
  linkSync(current, destination);
16511
16984
  } catch {
16512
- copyFileSync5(current, destination);
16985
+ copyFileSync6(current, destination);
16513
16986
  }
16514
16987
  }
16515
16988
  shipped.set(srcPath, result);
@@ -16525,7 +16998,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
16525
16998
  useGpu: false,
16526
16999
  browserGpuMode: "software"
16527
17000
  });
16528
- if (!existsSync21(planDir)) mkdirSync13(planDir, { recursive: true });
17001
+ if (!existsSync21(planDir)) mkdirSync14(planDir, { recursive: true });
16529
17002
  const log = config.logger ?? defaultLogger;
16530
17003
  const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
16531
17004
  const abortSignal = config.abortSignal;
@@ -16554,15 +17027,15 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
16554
17027
  variables: config.variables
16555
17028
  });
16556
17029
  const entryFile = config.entryFile ?? "index.html";
16557
- const htmlPath = join34(projectDir, entryFile);
17030
+ const htmlPath = join35(projectDir, entryFile);
16558
17031
  if (!existsSync21(htmlPath)) {
16559
17032
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
16560
17033
  }
16561
- const workDir = join34(planDir, ".plan-work");
16562
- rmSync13(workDir, { recursive: true, force: true });
16563
- mkdirSync13(workDir, { recursive: true });
16564
- const compiledDir = join34(workDir, "compiled");
16565
- mkdirSync13(compiledDir, { recursive: true });
17034
+ const workDir = join35(planDir, ".plan-work");
17035
+ rmSync12(workDir, { recursive: true, force: true });
17036
+ mkdirSync14(workDir, { recursive: true });
17037
+ const compiledDir = join35(workDir, "compiled");
17038
+ mkdirSync14(compiledDir, { recursive: true });
16566
17039
  let stageStarted = Date.now();
16567
17040
  cpSync2(projectDir, compiledDir, {
16568
17041
  recursive: true,
@@ -16575,10 +17048,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
16575
17048
  }
16576
17049
  });
16577
17050
  logPlanStage(log, "copy", stageStarted);
16578
- const finalCompiledDir = join34(planDir, "compiled");
17051
+ const finalCompiledDir = join35(planDir, "compiled");
16579
17052
  const needsAlpha = outputNeedsAlpha(config.format);
16580
17053
  const remoteFiles = Object.entries(options.remoteFiles ?? {});
16581
- const remoteLocalPath = (rel) => join34(projectDir, ...rel.split("/"));
17054
+ const remoteLocalPath = (rel) => join35(projectDir, ...rel.split("/"));
16582
17055
  for (const [, file] of remoteFiles) {
16583
17056
  declareContentAddressedUrl(file.url, file.sha256);
16584
17057
  if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
@@ -16652,7 +17125,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
16652
17125
  validateDistributedDuration({
16653
17126
  duration: probeResult.duration,
16654
17127
  totalFrames,
16655
- fps: fpsToNumber10(job.config.fps)
17128
+ fps: fpsToNumber9(job.config.fps)
16656
17129
  });
16657
17130
  if (probeResult.fileServer) closeFileServerSafely(probeResult.fileServer, "plan", log);
16658
17131
  if (probeResult.probeSession) {
@@ -16764,18 +17237,18 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16764
17237
  let stageStarted = Date.now();
16765
17238
  const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
16766
17239
  logPlanStage(log, "extract", stageStarted);
16767
- const stagedVideoFrames = join34(compiledDir, "__hyperframes_video_frames");
16768
- const videoFramesDst = join34(planDir, "video-frames");
16769
- if (existsSync21(videoFramesDst)) rmSync13(videoFramesDst, { recursive: true, force: true });
16770
- const videoSourcesDir = join34(planDir, "video-sources");
16771
- rmSync13(videoSourcesDir, { recursive: true, force: true });
17240
+ const stagedVideoFrames = join35(compiledDir, "__hyperframes_video_frames");
17241
+ const videoFramesDst = join35(planDir, "video-frames");
17242
+ if (existsSync21(videoFramesDst)) rmSync12(videoFramesDst, { recursive: true, force: true });
17243
+ const videoSourcesDir = join35(planDir, "video-sources");
17244
+ rmSync12(videoSourcesDir, { recursive: true, force: true });
16772
17245
  if (existsSync21(stagedVideoFrames)) {
16773
- renameWithRetry6(stagedVideoFrames, videoFramesDst);
17246
+ renameWithRetry7(stagedVideoFrames, videoFramesDst);
16774
17247
  } else {
16775
- mkdirSync13(videoFramesDst, { recursive: true });
17248
+ mkdirSync14(videoFramesDst, { recursive: true });
16776
17249
  }
16777
- if (existsSync21(finalCompiledDir)) rmSync13(finalCompiledDir, { recursive: true, force: true });
16778
- renameWithRetry6(compiledDir, finalCompiledDir);
17250
+ if (existsSync21(finalCompiledDir)) rmSync12(finalCompiledDir, { recursive: true, force: true });
17251
+ renameWithRetry7(compiledDir, finalCompiledDir);
16779
17252
  const shipVideoSource = createVideoSourceShipper({
16780
17253
  planDir,
16781
17254
  projectDir,
@@ -16804,7 +17277,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16804
17277
  };
16805
17278
  })
16806
17279
  });
16807
- mkdirSync13(join34(planDir, "meta"), { recursive: true });
17280
+ mkdirSync14(join35(planDir, "meta"), { recursive: true });
16808
17281
  writePlanRemoteFiles(
16809
17282
  planDir,
16810
17283
  remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
@@ -16813,8 +17286,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16813
17286
  sizeBytes
16814
17287
  }))
16815
17288
  );
16816
- writeFileSync8(
16817
- join34(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
17289
+ writeFileSync9(
17290
+ join35(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
16818
17291
  JSON.stringify(planVideosJson, null, 2),
16819
17292
  "utf-8"
16820
17293
  );
@@ -16893,9 +17366,9 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16893
17366
  if (audioResult?.audioError) {
16894
17367
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
16895
17368
  }
16896
- const planAudioPath = join34(planDir, PLAN_AUDIO_RELATIVE_PATH);
17369
+ const planAudioPath = join35(planDir, PLAN_AUDIO_RELATIVE_PATH);
16897
17370
  if (audioResult?.hasAudio && existsSync21(audioResult.audioOutputPath)) {
16898
- renameWithRetry6(audioResult.audioOutputPath, planAudioPath);
17371
+ renameWithRetry7(audioResult.audioOutputPath, planAudioPath);
16899
17372
  }
16900
17373
  if (chunksCarryAudio && existsSync21(planAudioPath)) {
16901
17374
  if (options.sliceChunkAudio) {
@@ -16942,7 +17415,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
16942
17415
  format: config.format
16943
17416
  };
16944
17417
  try {
16945
- rmSync13(workDir, { recursive: true, force: true });
17418
+ rmSync12(workDir, { recursive: true, force: true });
16946
17419
  } catch (err) {
16947
17420
  log.warn("[plan] failed to remove temp work dir", {
16948
17421
  workDir,
@@ -17093,6 +17566,20 @@ function validateDistributedRenderConfig(config) {
17093
17566
  `must look like "10M" or "5000k"; got ${JSON.stringify(config.bitrate)}`
17094
17567
  );
17095
17568
  }
17569
+ try {
17570
+ validateDeliveryEncoderConfig({
17571
+ format: config.format,
17572
+ fps: { num: config.fps, den: 1 },
17573
+ keyframeIntervalSeconds: config.keyframeIntervalSeconds,
17574
+ maxBitrate: config.maxBitrate,
17575
+ bufferSize: config.bufferSize
17576
+ });
17577
+ } catch (error) {
17578
+ throw new InvalidConfigError(
17579
+ "config.maxBitrate",
17580
+ error instanceof Error ? error.message : String(error)
17581
+ );
17582
+ }
17096
17583
  if (config.chunkSize !== void 0) {
17097
17584
  if (!Number.isInteger(config.chunkSize) || config.chunkSize < 1) {
17098
17585
  throw new InvalidConfigError(
@@ -17272,6 +17759,12 @@ function assertOptionalInteger(options, field, min = 0) {
17272
17759
  throw new Error(`Render request ${field} must be an integer >= ${min}`);
17273
17760
  }
17274
17761
  }
17762
+ function assertOptionalPositiveNumber(options, field) {
17763
+ const value = options[field];
17764
+ if (value !== void 0 && !(typeof value === "number" && value > 0)) {
17765
+ throw new Error(`Render request ${field} must be a positive number`);
17766
+ }
17767
+ }
17275
17768
  function assertOptionalString(options, field) {
17276
17769
  if (options[field] !== void 0 && typeof options[field] !== "string") {
17277
17770
  throw new Error(`Render request ${field} must be a string`);
@@ -17319,10 +17812,11 @@ function assertRequestOptionScalars(options) {
17319
17812
  assertOptionalInteger(options, "workers", 1);
17320
17813
  assertOptionalInteger(options, "maxRenderMs");
17321
17814
  assertOptionalInteger(options, "crf");
17815
+ assertOptionalPositiveNumber(options, "keyframeIntervalSeconds");
17322
17816
  for (const field of ["useGpu", "debug", "outputResolutionAspectAgnostic"]) {
17323
17817
  assertOptionalBoolean(options, field);
17324
17818
  }
17325
- for (const field of ["entryFile", "videoBitrate"]) {
17819
+ for (const field of ["entryFile", "videoBitrate", "maxBitrate", "bufferSize"]) {
17326
17820
  assertOptionalString(options, field);
17327
17821
  }
17328
17822
  assertOptionalEnum(options, "strictness", ["strict", "best-effort"]);
@@ -17425,6 +17919,9 @@ function distributedConfigFromRequest(request, runtime = {}) {
17425
17919
  crf: options.crf,
17426
17920
  pageMotionBlur: options.pageMotionBlur,
17427
17921
  bitrate: options.videoBitrate,
17922
+ keyframeIntervalSeconds: options.keyframeIntervalSeconds,
17923
+ maxBitrate: options.maxBitrate,
17924
+ bufferSize: options.bufferSize,
17428
17925
  videoFrameFormat: options.videoFrameFormat,
17429
17926
  outputResolution: options.outputResolution,
17430
17927
  outputResolutionAspectAgnostic: options.outputResolutionAspectAgnostic,
@@ -17472,6 +17969,9 @@ function renderRequestFromDistributedConfig(input) {
17472
17969
  ...optionalProperty("crf", config.crf),
17473
17970
  ...optionalProperty("pageMotionBlur", config.pageMotionBlur),
17474
17971
  ...optionalProperty("videoBitrate", config.bitrate),
17972
+ ...optionalProperty("keyframeIntervalSeconds", config.keyframeIntervalSeconds),
17973
+ ...optionalProperty("maxBitrate", config.maxBitrate),
17974
+ ...optionalProperty("bufferSize", config.bufferSize),
17475
17975
  ...optionalProperty("videoFrameFormat", config.videoFrameFormat),
17476
17976
  ...optionalProperty("outputResolution", config.outputResolution),
17477
17977
  ...optionalProperty("outputResolutionAspectAgnostic", config.outputResolutionAspectAgnostic),
@@ -17513,14 +18013,14 @@ import {
17513
18013
  // src/server.ts
17514
18014
  import {
17515
18015
  existsSync as existsSync24,
17516
- mkdirSync as mkdirSync14,
17517
- statSync as statSync9,
18016
+ mkdirSync as mkdirSync15,
18017
+ statSync as statSync10,
17518
18018
  mkdtempSync as mkdtempSync5,
17519
- writeFileSync as writeFileSync9,
17520
- rmSync as rmSync14,
18019
+ writeFileSync as writeFileSync10,
18020
+ rmSync as rmSync13,
17521
18021
  createReadStream as createReadStream5
17522
18022
  } from "node:fs";
17523
- import { resolve as resolve10, dirname as dirname14, join as join37 } from "node:path";
18023
+ import { resolve as resolve11, dirname as dirname15, join as join38 } from "node:path";
17524
18024
  import { tmpdir as tmpdir3 } from "node:os";
17525
18025
  import { parseArgs } from "node:util";
17526
18026
  import crypto from "node:crypto";
@@ -17532,14 +18032,14 @@ import { fetchPublicHttpsText as fetchPublicHttpsText2 } from "@hyperframes/engi
17532
18032
  // src/services/hyperframeLint.ts
17533
18033
  import {
17534
18034
  existsSync as existsSync22,
17535
- readFileSync as readFileSync12,
17536
- statSync as statSync8,
17537
- openSync as openSync6,
18035
+ readFileSync as readFileSync13,
18036
+ statSync as statSync9,
18037
+ openSync as openSync7,
17538
18038
  fstatSync as fstatSync3,
17539
- closeSync as closeSync6,
18039
+ closeSync as closeSync7,
17540
18040
  constants as constants3
17541
18041
  } from "node:fs";
17542
- import { resolve as resolve9, join as join35, relative as relative6, isAbsolute as isAbsolute4, sep as sep3 } from "node:path";
18042
+ import { resolve as resolve10, join as join36, relative as relative6, isAbsolute as isAbsolute4, sep as sep3 } from "node:path";
17543
18043
  import { lintHyperframeHtml } from "@hyperframes/lint";
17544
18044
  function isStringRecord(value) {
17545
18045
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -17569,30 +18069,30 @@ function pickEntryFile(files, preferredEntryFile) {
17569
18069
  function readEntryHtml(filePath) {
17570
18070
  let fd;
17571
18071
  try {
17572
- fd = openSync6(filePath, constants3.O_RDONLY | constants3.O_NONBLOCK);
18072
+ fd = openSync7(filePath, constants3.O_RDONLY | constants3.O_NONBLOCK);
17573
18073
  } catch (error) {
17574
18074
  if (error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR"))
17575
18075
  return null;
17576
- if (!statSync8(filePath, { throwIfNoEntry: false })?.isFile()) return null;
18076
+ if (!statSync9(filePath, { throwIfNoEntry: false })?.isFile()) return null;
17577
18077
  throw error;
17578
18078
  }
17579
18079
  try {
17580
18080
  if (!fstatSync3(fd).isFile()) return null;
17581
- return readFileSync12(fd, "utf-8");
18081
+ return readFileSync13(fd, "utf-8");
17582
18082
  } finally {
17583
- closeSync6(fd);
18083
+ closeSync7(fd);
17584
18084
  }
17585
18085
  }
17586
18086
  function readProjectEntryFile(projectDir, preferredEntryFile) {
17587
- const absProjectDir = resolve9(projectDir);
17588
- if (!existsSync22(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
18087
+ const absProjectDir = resolve10(projectDir);
18088
+ if (!existsSync22(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
17589
18089
  return { error: `Project directory not found: ${absProjectDir}` };
17590
18090
  }
17591
18091
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
17592
18092
  (value) => typeof value === "string" && value.trim().length > 0
17593
18093
  );
17594
18094
  for (const entryFile of entryCandidates) {
17595
- const absoluteEntryPath = resolve9(absProjectDir, entryFile);
18095
+ const absoluteEntryPath = resolve10(absProjectDir, entryFile);
17596
18096
  const entryRelativePath = relative6(absProjectDir, absoluteEntryPath);
17597
18097
  if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${sep3}`) || isAbsolute4(entryRelativePath)) {
17598
18098
  return { error: `Entry file must stay inside project directory: ${entryFile}` };
@@ -17607,7 +18107,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
17607
18107
  }
17608
18108
  }
17609
18109
  return {
17610
- error: `No HTML entry file found in project directory: ${join35(absProjectDir, preferredEntryFile || "index.html")}`
18110
+ error: `No HTML entry file found in project directory: ${join36(absProjectDir, preferredEntryFile || "index.html")}`
17611
18111
  };
17612
18112
  }
17613
18113
  function prepareHyperframeLintBody(body) {
@@ -17653,7 +18153,7 @@ async function runHyperframeLint(prepared) {
17653
18153
  // src/services/healthWorker.ts
17654
18154
  import { Worker as Worker2 } from "node:worker_threads";
17655
18155
  import { fileURLToPath as fileURLToPath6 } from "node:url";
17656
- import { dirname as dirname13, join as join36 } from "node:path";
18156
+ import { dirname as dirname14, join as join37 } from "node:path";
17657
18157
  import { existsSync as existsSync23 } from "node:fs";
17658
18158
  var DEFAULT_HEALTH_PORT = 9848;
17659
18159
  async function startHealthWorker(options = {}) {
@@ -17672,12 +18172,12 @@ async function startHealthWorker(options = {}) {
17672
18172
  stdout: false,
17673
18173
  stderr: false
17674
18174
  });
17675
- await new Promise((resolve12, reject) => {
18175
+ await new Promise((resolve13, reject) => {
17676
18176
  const onMessage = (msg) => {
17677
18177
  if (msg?.type === "listening") {
17678
18178
  worker.off("error", onError);
17679
18179
  worker.off("message", onMessage);
17680
- resolve12();
18180
+ resolve13();
17681
18181
  } else if (msg?.type === "listen-error") {
17682
18182
  worker.off("error", onError);
17683
18183
  worker.off("message", onMessage);
@@ -17728,8 +18228,8 @@ function defaultLogger2() {
17728
18228
  };
17729
18229
  }
17730
18230
  function resolveWorkerEntry2() {
17731
- const here = dirname13(fileURLToPath6(import.meta.url));
17732
- const candidates = [join36(here, "healthWorkerThread.js"), join36(here, "healthWorkerThread.ts")];
18231
+ const here = dirname14(fileURLToPath6(import.meta.url));
18232
+ const candidates = [join37(here, "healthWorkerThread.js"), join37(here, "healthWorkerThread.ts")];
17733
18233
  for (const candidate of candidates) {
17734
18234
  if (existsSync23(candidate)) return candidate;
17735
18235
  }
@@ -17747,6 +18247,8 @@ var DEFAULT_SERVER_FPS = { num: 30, den: 1 };
17747
18247
  var SSE_KEEPALIVE_MS = 3e4;
17748
18248
  var SAFE_RENDER_ERROR_CODES = /* @__PURE__ */ new Set([
17749
18249
  "ASSET_MEDIA_TYPE_MISMATCH",
18250
+ "HEIF_IMAGE_CONVERSION_FAILED",
18251
+ "HEIF_TOOL_UNAVAILABLE",
17750
18252
  "NOT_MEDIA_PAYLOAD",
17751
18253
  "INVALID_VIDEO_METADATA",
17752
18254
  "VIDEO_SOURCE_UNRENDERABLE",
@@ -17821,7 +18323,7 @@ function parseRenderOptions(body) {
17821
18323
  const format = parseServerFormat(body.format);
17822
18324
  const outputDynamicRange = parseServerOutputDynamicRange(body.outputDynamicRange) ?? fromRenderHdrMode(parseLegacyServerHdrMode(body.hdrMode));
17823
18325
  const encoder = parseEncoderOptions(body, format ?? "mp4");
17824
- const { crf, videoBitrate, videoFrameFormat } = encoder.ok ? encoder.options : {};
18326
+ const { crf, videoBitrate, videoFrameFormat, keyframeIntervalSeconds, maxBitrate, bufferSize } = encoder.ok ? encoder.options : {};
17825
18327
  const { variables, outputResolution, outputResolutionAspectAgnostic } = parseRenderOverrides(body);
17826
18328
  return {
17827
18329
  outputPath,
@@ -17831,6 +18333,9 @@ function parseRenderOptions(body) {
17831
18333
  maxRenderMs,
17832
18334
  crf,
17833
18335
  videoBitrate,
18336
+ keyframeIntervalSeconds,
18337
+ maxBitrate,
18338
+ bufferSize,
17834
18339
  useGpu,
17835
18340
  debug,
17836
18341
  strictness,
@@ -17865,6 +18370,9 @@ function buildRenderJobConfig(input, outputPath, log) {
17865
18370
  maxRenderMs: input.maxRenderMs,
17866
18371
  crf: input.crf,
17867
18372
  videoBitrate: input.videoBitrate,
18373
+ keyframeIntervalSeconds: input.keyframeIntervalSeconds,
18374
+ maxBitrate: input.maxBitrate,
18375
+ bufferSize: input.bufferSize,
17868
18376
  useGpu: input.useGpu,
17869
18377
  debug: input.debug,
17870
18378
  strictness: input.strictness,
@@ -17882,8 +18390,8 @@ function buildRenderJobConfig(input, outputPath, log) {
17882
18390
  function resolvePreparedRenderOutput(prepared, rendersDir, log) {
17883
18391
  const { input, cleanupProjectDir } = prepared;
17884
18392
  const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
17885
- const outputDir = dirname14(absoluteOutputPath);
17886
- if (!existsSync24(outputDir)) mkdirSync14(outputDir, { recursive: true });
18393
+ const outputDir = dirname15(absoluteOutputPath);
18394
+ if (!existsSync24(outputDir)) mkdirSync15(outputDir, { recursive: true });
17887
18395
  return { input, cleanupProjectDir, absoluteOutputPath };
17888
18396
  }
17889
18397
  function validateRenderOverrides(body) {
@@ -17923,12 +18431,12 @@ var MAX_PREVIEW_HTML_BYTES = 20 * 1024 * 1024;
17923
18431
  function prepareProjectDirectory(projectDir, options) {
17924
18432
  const candidate = nonEmptyString(projectDir);
17925
18433
  if (!candidate) return null;
17926
- const absProjectDir = resolve10(candidate);
17927
- if (!existsSync24(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
18434
+ const absProjectDir = resolve11(candidate);
18435
+ if (!existsSync24(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
17928
18436
  return { error: `Project directory not found: ${absProjectDir}` };
17929
18437
  }
17930
18438
  const entry = options.entryFile || "index.html";
17931
- if (!existsSync24(resolve10(absProjectDir, entry))) {
18439
+ if (!existsSync24(resolve11(absProjectDir, entry))) {
17932
18440
  return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
17933
18441
  }
17934
18442
  return { prepared: { input: { projectDir: absProjectDir, ...options } } };
@@ -17949,8 +18457,8 @@ async function resolveInlineRenderHtml(body) {
17949
18457
  }
17950
18458
  function materializeInlineProject(html, options) {
17951
18459
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
17952
- const tempProjectDir = mkdtempSync5(join37(tempRoot, "producer-project-"));
17953
- writeFileSync9(join37(tempProjectDir, "index.html"), html, "utf-8");
18460
+ const tempProjectDir = mkdtempSync5(join38(tempRoot, "producer-project-"));
18461
+ writeFileSync10(join38(tempProjectDir, "index.html"), html, "utf-8");
17954
18462
  return {
17955
18463
  prepared: {
17956
18464
  input: { projectDir: tempProjectDir, ...options },
@@ -17975,7 +18483,7 @@ function resolveOutputPath(projectDir, outputCandidate, rendersDir, log) {
17975
18483
  try {
17976
18484
  return resolveRenderPaths(projectDir, outputCandidate, rendersDir).absoluteOutputPath;
17977
18485
  } catch (error) {
17978
- const fallbackPath = resolve10(rendersDir, `producer-fallback-${Date.now()}.mp4`);
18486
+ const fallbackPath = resolve11(rendersDir, `producer-fallback-${Date.now()}.mp4`);
17979
18487
  log.warn("Failed to resolve output path, using fallback", {
17980
18488
  fallback: fallbackPath,
17981
18489
  error: error instanceof Error ? error.message : String(error)
@@ -18011,7 +18519,7 @@ function createArtifactStore(ttlMs) {
18011
18519
  function cleanupTempDir(dir, log) {
18012
18520
  if (!dir) return;
18013
18521
  try {
18014
- rmSync14(dir, { recursive: true, force: true });
18522
+ rmSync13(dir, { recursive: true, force: true });
18015
18523
  } catch (error) {
18016
18524
  log.warn("Failed to cleanup temp project dir", {
18017
18525
  cleanupProjectDir: dir,
@@ -18020,7 +18528,7 @@ function cleanupTempDir(dir, log) {
18020
18528
  }
18021
18529
  }
18022
18530
  function outputFileSize(path) {
18023
- return existsSync24(path) ? statSync9(path).size : 0;
18531
+ return existsSync24(path) ? statSync10(path).size : 0;
18024
18532
  }
18025
18533
  function createBlockingProgressReporter(log, requestId) {
18026
18534
  let lastLoggedPct = -10;
@@ -18326,7 +18834,7 @@ function createRenderHandlers(options = {}) {
18326
18834
  store.delete(token);
18327
18835
  return c.json({ success: false, error: "Output artifact file missing" }, 404);
18328
18836
  }
18329
- const stats = statSync9(artifact.path);
18837
+ const stats = statSync10(artifact.path);
18330
18838
  return new Response(createReadStream5(artifact.path), {
18331
18839
  headers: {
18332
18840
  "content-type": "video/mp4",
@@ -18393,7 +18901,7 @@ function startServer(options = {}) {
18393
18901
  process.on("SIGINT", () => shutdown("SIGINT"));
18394
18902
  return server;
18395
18903
  }
18396
- var entryScript = process.argv[1] ? resolve10(process.argv[1]) : "";
18904
+ var entryScript = process.argv[1] ? resolve11(process.argv[1]) : "";
18397
18905
  var isPublicServerEntry = entryScript.endsWith("/public-server.js") || entryScript.endsWith("/src/server.ts");
18398
18906
  if (isPublicServerEntry) {
18399
18907
  const { values } = parseArgs({
@@ -18418,35 +18926,35 @@ import { quantizeTimeToFrame, MEDIA_VISUAL_STYLE_PROPERTIES } from "@hyperframes
18418
18926
 
18419
18927
  // src/services/distributed/planV2.ts
18420
18928
  import {
18421
- copyFileSync as copyFileSync7,
18929
+ copyFileSync as copyFileSync8,
18422
18930
  existsSync as existsSync26,
18423
18931
  lstatSync as lstatSync3,
18424
- mkdirSync as mkdirSync16,
18932
+ mkdirSync as mkdirSync17,
18425
18933
  mkdtempSync as mkdtempSync7,
18426
- readFileSync as readFileSync13,
18934
+ readFileSync as readFileSync14,
18427
18935
  readdirSync as readdirSync9,
18428
- rmSync as rmSync16,
18429
- statSync as statSync11,
18430
- writeFileSync as writeFileSync11
18936
+ rmSync as rmSync15,
18937
+ statSync as statSync12,
18938
+ writeFileSync as writeFileSync12
18431
18939
  } from "node:fs";
18432
18940
  import { tmpdir as tmpdir4 } from "node:os";
18433
- import { dirname as dirname16, isAbsolute as isAbsolute5, join as join40, relative as relative7, resolve as resolve11, sep as sep4 } from "node:path";
18941
+ import { dirname as dirname17, isAbsolute as isAbsolute5, join as join41, relative as relative7, resolve as resolve12, sep as sep4 } from "node:path";
18434
18942
  import { DISTRIBUTED_FPS as DISTRIBUTED_FPS2, isDistributedFps as isDistributedFps3 } from "@hyperframes/core";
18435
- import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
18943
+ import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
18436
18944
 
18437
18945
  // src/services/distributed/planV2Publisher.ts
18438
18946
  import {
18439
- copyFileSync as copyFileSync6,
18947
+ copyFileSync as copyFileSync7,
18440
18948
  existsSync as existsSync25,
18441
18949
  linkSync as linkSync2,
18442
- mkdirSync as mkdirSync15,
18950
+ mkdirSync as mkdirSync16,
18443
18951
  mkdtempSync as mkdtempSync6,
18444
- rmSync as rmSync15,
18445
- statSync as statSync10,
18446
- writeFileSync as writeFileSync10
18952
+ rmSync as rmSync14,
18953
+ statSync as statSync11,
18954
+ writeFileSync as writeFileSync11
18447
18955
  } from "node:fs";
18448
- import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
18449
- import { dirname as dirname15, join as join39 } from "node:path";
18956
+ import { renameWithRetry as renameWithRetry8 } from "@hyperframes/engine";
18957
+ import { dirname as dirname16, join as join40 } from "node:path";
18450
18958
 
18451
18959
  // src/services/distributed/planV2Errors.ts
18452
18960
  var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
@@ -18461,7 +18969,7 @@ var PlanV2IntegrityError = class extends Error {
18461
18969
  };
18462
18970
 
18463
18971
  // src/services/distributed/planV2Layout.ts
18464
- import { join as join38 } from "node:path";
18972
+ import { join as join39 } from "node:path";
18465
18973
  var SHA256_PATTERN = /^[0-9a-f]{64}$/;
18466
18974
  function assertPlanV2Sha256(value, field) {
18467
18975
  if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
@@ -18471,7 +18979,7 @@ function assertPlanV2Sha256(value, field) {
18471
18979
  }
18472
18980
  function planV2BlobPath(root, sha256) {
18473
18981
  const digest = assertPlanV2Sha256(sha256, "artifact sha256");
18474
- return join38(root, "artifacts", "sha256", digest.slice(0, 2), digest);
18982
+ return join39(root, "artifacts", "sha256", digest.slice(0, 2), digest);
18475
18983
  }
18476
18984
 
18477
18985
  // src/services/distributed/planV2Publisher.ts
@@ -18511,12 +19019,12 @@ var LocalPlanV2ArtifactPublisher = class {
18511
19019
  }
18512
19020
  this.destinationDir = destinationDir;
18513
19021
  this.#linkFile = options.linkFile ?? linkSync2;
18514
- mkdirSync15(dirname15(destinationDir), { recursive: true });
18515
- this.temporaryDir = mkdtempSync6(join39(dirname15(destinationDir), ".plan-v2-publish-"));
19022
+ mkdirSync16(dirname16(destinationDir), { recursive: true });
19023
+ this.temporaryDir = mkdtempSync6(join40(dirname16(destinationDir), ".plan-v2-publish-"));
18516
19024
  }
18517
19025
  async putBlob(blob) {
18518
19026
  const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
18519
- const sourceSize = statSync10(blob.sourcePath).size;
19027
+ const sourceSize = statSync11(blob.sourcePath).size;
18520
19028
  if (sourceSize !== blob.sizeBytes) {
18521
19029
  throw new PlanV2IntegrityError(
18522
19030
  `published blob size changed for ${digest}: expected ${blob.sizeBytes}, got ${sourceSize}`
@@ -18524,19 +19032,19 @@ var LocalPlanV2ArtifactPublisher = class {
18524
19032
  }
18525
19033
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
18526
19034
  if (existsSync25(destinationPath)) return;
18527
- mkdirSync15(dirname15(destinationPath), { recursive: true });
18528
- const stagingDir = mkdtempSync6(join39(dirname15(destinationPath), ".plan-v2-blob-"));
18529
- const temporaryPath = join39(stagingDir, "blob");
19035
+ mkdirSync16(dirname16(destinationPath), { recursive: true });
19036
+ const stagingDir = mkdtempSync6(join40(dirname16(destinationPath), ".plan-v2-blob-"));
19037
+ const temporaryPath = join40(stagingDir, "blob");
18530
19038
  try {
18531
19039
  try {
18532
19040
  this.#linkFile(blob.sourcePath, temporaryPath);
18533
19041
  } catch (error) {
18534
19042
  if (!canFallbackToCopy(error)) throw error;
18535
- copyFileSync6(blob.sourcePath, temporaryPath);
19043
+ copyFileSync7(blob.sourcePath, temporaryPath);
18536
19044
  }
18537
- renameWithRetry7(temporaryPath, destinationPath);
19045
+ renameWithRetry8(temporaryPath, destinationPath);
18538
19046
  } finally {
18539
- rmSync15(stagingDir, { recursive: true, force: true });
19047
+ rmSync14(stagingDir, { recursive: true, force: true });
18540
19048
  }
18541
19049
  }
18542
19050
  async commitManifest(manifestBytes) {
@@ -18547,13 +19055,13 @@ var LocalPlanV2ArtifactPublisher = class {
18547
19055
  );
18548
19056
  }
18549
19057
  }
18550
- writeFileSync10(join39(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
18551
- renameWithRetry7(this.temporaryDir, this.destinationDir);
19058
+ writeFileSync11(join40(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
19059
+ renameWithRetry8(this.temporaryDir, this.destinationDir);
18552
19060
  this.#committed = true;
18553
19061
  }
18554
19062
  async abort() {
18555
19063
  if (!this.#committed) {
18556
- rmSync15(this.temporaryDir, { recursive: true, force: true });
19064
+ rmSync14(this.temporaryDir, { recursive: true, force: true });
18557
19065
  }
18558
19066
  }
18559
19067
  };
@@ -18572,7 +19080,7 @@ function isNonNegativeIntegerArray(value) {
18572
19080
  return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
18573
19081
  }
18574
19082
  function readJsonFile(path, label) {
18575
- const contents = readFileSync13(path, "utf-8");
19083
+ const contents = readFileSync14(path, "utf-8");
18576
19084
  try {
18577
19085
  const parsed = JSON.parse(contents);
18578
19086
  return parsed;
@@ -18589,10 +19097,10 @@ function assertSafeRelativePath(path) {
18589
19097
  }
18590
19098
  function listFiles(root) {
18591
19099
  const files = [];
18592
- const rootResolved = resolve11(root);
19100
+ const rootResolved = resolve12(root);
18593
19101
  function walk(dir) {
18594
19102
  for (const entry of readdirSync9(dir, { withFileTypes: true })) {
18595
- const absolutePath = join40(dir, entry.name);
19103
+ const absolutePath = join41(dir, entry.name);
18596
19104
  if (entry.isDirectory()) {
18597
19105
  walk(absolutePath);
18598
19106
  } else if (entry.isFile()) {
@@ -18619,13 +19127,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
18619
19127
  }
18620
19128
  }
18621
19129
  function validateExtractionCacheCompleteSentinels(executionPlanDir) {
18622
- const videoRoot = join40(executionPlanDir, "video-frames");
19130
+ const videoRoot = join41(executionPlanDir, "video-frames");
18623
19131
  if (!existsSync26(videoRoot)) return;
18624
19132
  for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
18625
19133
  if (!videoEntry.isDirectory()) continue;
18626
- const videoDir = join40(videoRoot, videoEntry.name);
19134
+ const videoDir = join41(videoRoot, videoEntry.name);
18627
19135
  if (readdirSync9(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
18628
- const sentinelPath = join40(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
19136
+ const sentinelPath = join41(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
18629
19137
  assertValidExtractionCacheCompleteSentinel(sentinelPath);
18630
19138
  }
18631
19139
  }
@@ -18635,8 +19143,8 @@ function isExtractionCacheCompleteSentinelPath(path) {
18635
19143
  return segments.length === 3 && segments[0] === "video-frames" && segments[1] !== "" && segments[2] === EXTRACTION_CACHE_COMPLETE_SENTINEL;
18636
19144
  }
18637
19145
  function resolveExtractedVideoOutputDir(planDir, videoId) {
18638
- const videoRoot = resolve11(planDir, "video-frames");
18639
- const outputDir = resolve11(videoRoot, videoId);
19146
+ const videoRoot = resolve12(planDir, "video-frames");
19147
+ const outputDir = resolve12(videoRoot, videoId);
18640
19148
  if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${sep4}`)) {
18641
19149
  throw new PlanV2IntegrityError(`unsafe extracted video id: ${JSON.stringify(videoId)}`);
18642
19150
  }
@@ -18672,7 +19180,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
18672
19180
  if (video.deferredRange) {
18673
19181
  const framePaths2 = /* @__PURE__ */ new Map();
18674
19182
  for (let index = 0; index < video.totalFrames; index++) {
18675
- framePaths2.set(index, join40(outputDir, String(index)));
19183
+ framePaths2.set(index, join41(outputDir, String(index)));
18676
19184
  }
18677
19185
  return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
18678
19186
  }
@@ -18680,7 +19188,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
18680
19188
  const framePaths = /* @__PURE__ */ new Map();
18681
19189
  for (const frameName of frameNames) {
18682
19190
  if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
18683
- assertValidExtractionCacheCompleteSentinel(join40(outputDir, frameName));
19191
+ assertValidExtractionCacheCompleteSentinel(join41(outputDir, frameName));
18684
19192
  continue;
18685
19193
  }
18686
19194
  const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
@@ -18699,7 +19207,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
18699
19207
  `duplicate extracted frame index ${frameIndex} in ${outputDir}`
18700
19208
  );
18701
19209
  }
18702
- framePaths.set(frameIndex, join40(outputDir, frameName));
19210
+ framePaths.set(frameIndex, join41(outputDir, frameName));
18703
19211
  }
18704
19212
  return {
18705
19213
  ...video,
@@ -18720,11 +19228,11 @@ function parsePlanVideosJson2(value) {
18720
19228
  }
18721
19229
  }
18722
19230
  function materializeExtractedVideoDirectories(planDir) {
18723
- const videosPath = join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19231
+ const videosPath = join41(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18724
19232
  if (!existsSync26(videosPath)) return;
18725
19233
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
18726
19234
  for (const video of videos.extracted) {
18727
- mkdirSync16(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
19235
+ mkdirSync17(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
18728
19236
  }
18729
19237
  }
18730
19238
  function parseChunkSlices(value) {
@@ -18754,13 +19262,13 @@ function parseChunkSlices(value) {
18754
19262
  });
18755
19263
  }
18756
19264
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
18757
- const videoRoot = join40(executionPlanDir, "video-frames");
19265
+ const videoRoot = join41(executionPlanDir, "video-frames");
18758
19266
  const hasExtractedFrames = existsSync26(videoRoot) && listFiles(videoRoot).length > 0;
18759
- const videosPath = join40(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19267
+ const videosPath = join41(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18760
19268
  if (!existsSync26(videosPath)) {
18761
19269
  return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
18762
19270
  }
18763
- const chunksPath = join40(executionPlanDir, "meta", "chunks.json");
19271
+ const chunksPath = join41(executionPlanDir, "meta", "chunks.json");
18764
19272
  const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
18765
19273
  const chunks = readJsonFile(chunksPath, "meta/chunks.json");
18766
19274
  const parsedVideos = parsePlanVideosJson2(videos);
@@ -18797,7 +19305,7 @@ function collectChunkVideoDependencies(input) {
18797
19305
  for (let frame = chunk.startFrame; frame < chunk.endFrame; frame++) {
18798
19306
  const globalTime = frame * input.fpsDen / input.fpsNum;
18799
19307
  for (const [videoId, payload] of input.table.getActiveFramePayloads(globalTime)) {
18800
- const path = input.deferredSources.get(videoId) ?? relative7(resolve11(input.executionPlanDir), payload.framePath).split(sep4).join("/");
19308
+ const path = input.deferredSources.get(videoId) ?? relative7(resolve12(input.executionPlanDir), payload.framePath).split(sep4).join("/");
18801
19309
  assertSafeRelativePath(path);
18802
19310
  const indexes = mutable.get(path) ?? /* @__PURE__ */ new Set();
18803
19311
  indexes.add(chunk.index);
@@ -18815,21 +19323,21 @@ function computeManifestHash(manifest) {
18815
19323
  }
18816
19324
  function writeBlob(sourcePath, destinationPath) {
18817
19325
  if (existsSync26(destinationPath)) return;
18818
- mkdirSync16(dirname16(destinationPath), { recursive: true });
18819
- const temporaryDir = mkdtempSync7(join40(dirname16(destinationPath), ".plan-v2-blob-"));
18820
- const temporaryPath = join40(temporaryDir, "blob");
19326
+ mkdirSync17(dirname17(destinationPath), { recursive: true });
19327
+ const temporaryDir = mkdtempSync7(join41(dirname17(destinationPath), ".plan-v2-blob-"));
19328
+ const temporaryPath = join41(temporaryDir, "blob");
18821
19329
  try {
18822
- copyFileSync7(sourcePath, temporaryPath);
18823
- renameWithRetry8(temporaryPath, destinationPath);
19330
+ copyFileSync8(sourcePath, temporaryPath);
19331
+ renameWithRetry9(temporaryPath, destinationPath);
18824
19332
  } finally {
18825
- rmSync16(temporaryDir, { recursive: true, force: true });
19333
+ rmSync15(temporaryDir, { recursive: true, force: true });
18826
19334
  }
18827
19335
  }
18828
19336
  function publishBlobFor(absolutePath, digests) {
18829
19337
  return {
18830
19338
  sourcePath: absolutePath,
18831
19339
  sha256: planFileSha256(absolutePath, digests),
18832
- sizeBytes: statSync11(absolutePath).size
19340
+ sizeBytes: statSync12(absolutePath).size
18833
19341
  };
18834
19342
  }
18835
19343
  async function putBlobs(publisher, blobs) {
@@ -18853,7 +19361,7 @@ async function putTasks(tasks) {
18853
19361
  if (failure) throw failure.reason;
18854
19362
  }
18855
19363
  function buildPlanV2Publication(executionPlanDir, digests) {
18856
- const executionPlanPath = join40(executionPlanDir, "plan.json");
19364
+ const executionPlanPath = join41(executionPlanDir, "plan.json");
18857
19365
  if (!existsSync26(executionPlanPath)) {
18858
19366
  throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
18859
19367
  }
@@ -18881,7 +19389,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
18881
19389
  }
18882
19390
  validateExtractionCacheCompleteSentinels(executionPlanDir);
18883
19391
  const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
18884
- const encoder = readJsonFile(join40(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
19392
+ const encoder = readJsonFile(join41(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
18885
19393
  const files = listFiles(executionPlanDir);
18886
19394
  const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
18887
19395
  for (const file of files) {
@@ -18941,21 +19449,21 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
18941
19449
  if (publication.storedBlobs.length > 0) {
18942
19450
  throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
18943
19451
  }
18944
- mkdirSync16(dirname16(planV2Dir), { recursive: true });
18945
- const tempDir = mkdtempSync7(join40(dirname16(planV2Dir), ".plan-v2-build-"));
19452
+ mkdirSync17(dirname17(planV2Dir), { recursive: true });
19453
+ const tempDir = mkdtempSync7(join41(dirname17(planV2Dir), ".plan-v2-build-"));
18946
19454
  try {
18947
19455
  for (const blob of publication.blobs) {
18948
19456
  writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
18949
19457
  }
18950
- writeFileSync11(
18951
- join40(tempDir, "plan.json"),
19458
+ writeFileSync12(
19459
+ join41(tempDir, "plan.json"),
18952
19460
  canonicalJsonStringify(publication.manifest),
18953
19461
  "utf-8"
18954
19462
  );
18955
- renameWithRetry8(tempDir, planV2Dir);
19463
+ renameWithRetry9(tempDir, planV2Dir);
18956
19464
  return resultFromManifest(planV2Dir, publication.manifest);
18957
19465
  } catch (error) {
18958
- rmSync16(tempDir, { recursive: true, force: true });
19466
+ rmSync15(tempDir, { recursive: true, force: true });
18959
19467
  throw error;
18960
19468
  }
18961
19469
  }
@@ -18995,7 +19503,7 @@ async function publishPlanV2FromV1(executionPlanDir, publisher) {
18995
19503
  return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
18996
19504
  }
18997
19505
  async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
18998
- const stagingRoot = mkdtempSync7(join40(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
19506
+ const stagingRoot = mkdtempSync7(join41(options.stagingParentDir ?? tmpdir4(), ".execution-plan-"));
18999
19507
  const digests = /* @__PURE__ */ new Map();
19000
19508
  try {
19001
19509
  await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
@@ -19006,7 +19514,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
19006
19514
  // artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
19007
19515
  whileAudioMixes: () => putBlobs(
19008
19516
  publisher,
19009
- listFiles(join40(stagingRoot, "compiled")).map(
19517
+ listFiles(join41(stagingRoot, "compiled")).map(
19010
19518
  (file) => publishBlobFor(file.absolutePath, digests)
19011
19519
  )
19012
19520
  ),
@@ -19022,7 +19530,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
19022
19530
  }
19023
19531
  throw error;
19024
19532
  } finally {
19025
- rmSync16(stagingRoot, { recursive: true, force: true });
19533
+ rmSync15(stagingRoot, { recursive: true, force: true });
19026
19534
  }
19027
19535
  }
19028
19536
  async function planV2(projectDir, config, planV2Dir) {
@@ -19031,14 +19539,14 @@ async function planV2(projectDir, config, planV2Dir) {
19031
19539
  }
19032
19540
  const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
19033
19541
  const manifest = await planV2WithPublisher(projectDir, config, publisher, {
19034
- stagingParentDir: dirname16(planV2Dir)
19542
+ stagingParentDir: dirname17(planV2Dir)
19035
19543
  });
19036
19544
  return resultFromManifest(planV2Dir, manifest);
19037
19545
  }
19038
19546
  function resultFromManifest(planV2Dir, manifest) {
19039
19547
  return {
19040
19548
  planDir: planV2Dir,
19041
- manifestPath: join40(planV2Dir, "plan.json"),
19549
+ manifestPath: join41(planV2Dir, "plan.json"),
19042
19550
  planProtocol: PLAN_PROTOCOL_V2,
19043
19551
  planHash: manifest.planHash,
19044
19552
  sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
@@ -19174,7 +19682,7 @@ function parsePlanV2Manifest(value) {
19174
19682
  return parsed;
19175
19683
  }
19176
19684
  function readPlanV2Manifest(planV2Dir) {
19177
- const manifestPath = join40(planV2Dir, "plan.json");
19685
+ const manifestPath = join41(planV2Dir, "plan.json");
19178
19686
  if (!existsSync26(manifestPath)) {
19179
19687
  throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
19180
19688
  }
@@ -19197,7 +19705,7 @@ function verifyBlob(planV2Dir, artifact) {
19197
19705
  if (!existsSync26(sourcePath)) {
19198
19706
  throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
19199
19707
  }
19200
- const stats = statSync11(sourcePath);
19708
+ const stats = statSync12(sourcePath);
19201
19709
  if (!stats.isFile() || stats.size !== artifact.sizeBytes) {
19202
19710
  throw new PlanV2IntegrityError(`artifact size mismatch for ${artifact.path}`);
19203
19711
  }
@@ -19219,17 +19727,17 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
19219
19727
  artifact,
19220
19728
  sourcePath: verifyBlob(planV2Dir, artifact)
19221
19729
  }));
19222
- mkdirSync16(dirname16(destinationDir), { recursive: true });
19223
- const tempDir = mkdtempSync7(join40(dirname16(destinationDir), ".plan-v2-materialize-"));
19730
+ mkdirSync17(dirname17(destinationDir), { recursive: true });
19731
+ const tempDir = mkdtempSync7(join41(dirname17(destinationDir), ".plan-v2-materialize-"));
19224
19732
  try {
19225
19733
  for (const { artifact, sourcePath } of verified) {
19226
- const destinationPath = join40(tempDir, ...artifact.path.split("/"));
19227
- mkdirSync16(dirname16(destinationPath), { recursive: true });
19228
- copyFileSync7(sourcePath, destinationPath);
19734
+ const destinationPath = join41(tempDir, ...artifact.path.split("/"));
19735
+ mkdirSync17(dirname17(destinationPath), { recursive: true });
19736
+ copyFileSync8(sourcePath, destinationPath);
19229
19737
  }
19230
19738
  if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
19231
- writeFileSync11(
19232
- join40(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
19739
+ writeFileSync12(
19740
+ join41(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
19233
19741
  canonicalJsonStringify({
19234
19742
  manifest,
19235
19743
  target,
@@ -19237,9 +19745,9 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
19237
19745
  }),
19238
19746
  "utf-8"
19239
19747
  );
19240
- renameWithRetry8(tempDir, destinationDir);
19748
+ renameWithRetry9(tempDir, destinationDir);
19241
19749
  } catch (error) {
19242
- rmSync16(tempDir, { recursive: true, force: true });
19750
+ rmSync15(tempDir, { recursive: true, force: true });
19243
19751
  throw error;
19244
19752
  }
19245
19753
  return {
@@ -19252,7 +19760,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
19252
19760
  // Join the artifact's OWN name, not the current constant: a plan written
19253
19761
  // before the container change carries the legacy name, and materializing it
19254
19762
  // under the new one would point at a file that was never written.
19255
- audioPath: target.role === "assembler" ? audioArtifact ? join40(destinationDir, audioArtifact.path) : null : null
19763
+ audioPath: target.role === "assembler" ? audioArtifact ? join41(destinationDir, audioArtifact.path) : null : null
19256
19764
  };
19257
19765
  }
19258
19766
  function assertRemoteSources(remoteSources, artifacts) {
@@ -19271,7 +19779,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
19271
19779
  return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
19272
19780
  }
19273
19781
  function readValidatedPlanV2Materialization(planDir, expectedTarget) {
19274
- const markerPath = join40(planDir, PLAN_V2_MATERIALIZATION_MARKER);
19782
+ const markerPath = join41(planDir, PLAN_V2_MATERIALIZATION_MARKER);
19275
19783
  if (!existsSync26(markerPath)) return null;
19276
19784
  const marker = readJsonFile(markerPath, "materialization marker");
19277
19785
  if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
@@ -19289,8 +19797,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
19289
19797
  assertRemoteSources(remoteSources, required);
19290
19798
  for (const artifact of required) {
19291
19799
  if (Object.hasOwn(remoteSources, artifact.path)) continue;
19292
- const path = join40(planDir, ...artifact.path.split("/"));
19293
- if (!existsSync26(path) || statSync11(path).size !== artifact.sizeBytes) {
19800
+ const path = join41(planDir, ...artifact.path.split("/"));
19801
+ if (!existsSync26(path) || statSync12(path).size !== artifact.sizeBytes) {
19294
19802
  throw new PlanV2IntegrityError(
19295
19803
  `materialized artifact missing or truncated: ${artifact.path}`
19296
19804
  );
@@ -19303,33 +19811,34 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
19303
19811
  }
19304
19812
 
19305
19813
  // src/services/distributed/planV2Execution.ts
19306
- import { mkdtempSync as mkdtempSync9, rmSync as rmSync19 } from "node:fs";
19814
+ import { mkdtempSync as mkdtempSync9, rmSync as rmSync18 } from "node:fs";
19307
19815
  import { tmpdir as tmpdir5 } from "node:os";
19308
- import { join as join43 } from "node:path";
19816
+ import { join as join44 } from "node:path";
19309
19817
 
19310
19818
  // src/services/distributed/assemble.ts
19311
19819
  import {
19312
19820
  cpSync as cpSync3,
19313
19821
  existsSync as existsSync27,
19314
- mkdirSync as mkdirSync17,
19822
+ mkdirSync as mkdirSync18,
19315
19823
  mkdtempSync as mkdtempSync8,
19316
- readFileSync as readFileSync14,
19824
+ readFileSync as readFileSync15,
19317
19825
  readdirSync as readdirSync10,
19318
- rmSync as rmSync17,
19319
- statSync as statSync12,
19320
- writeFileSync as writeFileSync12
19826
+ rmSync as rmSync16,
19827
+ statSync as statSync13,
19828
+ writeFileSync as writeFileSync13
19321
19829
  } from "node:fs";
19322
- import { dirname as dirname17, join as join41 } from "node:path";
19830
+ import { dirname as dirname18, join as join42 } from "node:path";
19323
19831
  import {
19832
+ appendRateCapArgs as appendRateCapArgs2,
19324
19833
  appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
19325
19834
  describeFfmpegFailure as describeFfmpegFailure4,
19326
19835
  getEncoderPreset as getEncoderPreset5,
19327
19836
  MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME4,
19328
19837
  muxVideoWithAudio as muxVideoWithAudio2,
19329
19838
  resolveConfig as resolveConfig6,
19330
- runFfmpeg as runFfmpeg7,
19839
+ runFfmpeg as runFfmpeg8,
19331
19840
  appendSoftwareEncoderThreadsArg,
19332
- renameWithRetry as renameWithRetry9
19841
+ renameWithRetry as renameWithRetry10
19333
19842
  } from "@hyperframes/engine";
19334
19843
  import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
19335
19844
  function cfrRateControlArgs(locked) {
@@ -19355,13 +19864,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
19355
19864
  outputPath,
19356
19865
  durationMs: Date.now() - start,
19357
19866
  framesEncoded: plan2.totalFrames,
19358
- fileSize: statSync12(outputPath).size
19867
+ fileSize: statSync13(outputPath).size
19359
19868
  };
19360
19869
  }
19361
19870
  const format = plan2.dimensions.format;
19362
19871
  const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
19363
- if (!existsSync27(dirname17(outputPath))) {
19364
- mkdirSync17(dirname17(outputPath), { recursive: true });
19872
+ if (!existsSync27(dirname18(outputPath))) {
19873
+ mkdirSync18(dirname18(outputPath), { recursive: true });
19365
19874
  }
19366
19875
  const workDir = mkdtempSync8(`${outputPath}.assemble-work-`);
19367
19876
  const fps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
@@ -19383,10 +19892,10 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
19383
19892
  };
19384
19893
  try {
19385
19894
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
19386
- const finalPath = join41(workDir, `output.${format}`);
19895
+ const finalPath = join42(workDir, `output.${format}`);
19387
19896
  const audio = audioPath !== null && existsSync27(audioPath) ? audioPath : null;
19388
19897
  await writeJoined(ctx, videoSources, audio, finalPath);
19389
- renameWithRetry9(finalPath, outputPath);
19898
+ renameWithRetry10(finalPath, outputPath);
19390
19899
  log.info("[assemble] stage timings", {
19391
19900
  ...stageMs,
19392
19901
  chunks: chunkPaths.length,
@@ -19394,7 +19903,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
19394
19903
  });
19395
19904
  } finally {
19396
19905
  try {
19397
- rmSync17(workDir, { recursive: true, force: true });
19906
+ rmSync16(workDir, { recursive: true, force: true });
19398
19907
  } catch (err) {
19399
19908
  log.warn("[assemble] failed to remove work dir", {
19400
19909
  workDir,
@@ -19402,7 +19911,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
19402
19911
  });
19403
19912
  }
19404
19913
  }
19405
- const fileSize = existsSync27(outputPath) ? statSync12(outputPath).size : 0;
19914
+ const fileSize = existsSync27(outputPath) ? statSync13(outputPath).size : 0;
19406
19915
  return {
19407
19916
  outputPath,
19408
19917
  durationMs: Date.now() - start,
@@ -19411,18 +19920,18 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
19411
19920
  };
19412
19921
  }
19413
19922
  function readAssemblePlan(planDir, chunkPaths) {
19414
- const planJsonPath = join41(planDir, "plan.json");
19415
- const chunksJsonPath = join41(planDir, "meta", "chunks.json");
19923
+ const planJsonPath = join42(planDir, "plan.json");
19924
+ const chunksJsonPath = join42(planDir, "meta", "chunks.json");
19416
19925
  if (!existsSync27(planJsonPath)) {
19417
19926
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
19418
19927
  }
19419
- const plan2 = JSON.parse(readFileSync14(planJsonPath, "utf-8"));
19928
+ const plan2 = JSON.parse(readFileSync15(planJsonPath, "utf-8"));
19420
19929
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
19421
19930
  validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
19422
19931
  if (!existsSync27(chunksJsonPath)) {
19423
19932
  throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
19424
19933
  }
19425
- const chunks = JSON.parse(readFileSync14(chunksJsonPath, "utf-8"));
19934
+ const chunks = JSON.parse(readFileSync15(chunksJsonPath, "utf-8"));
19426
19935
  if (chunkPaths.length !== chunks.length) {
19427
19936
  throw new Error(
19428
19937
  `[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
@@ -19456,11 +19965,14 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
19456
19965
  "-r",
19457
19966
  fpsArg
19458
19967
  ];
19968
+ if (encoder.keyframeIntervalFrames !== void 0)
19969
+ args.push("-g", String(encoder.keyframeIntervalFrames));
19970
+ appendRateCapArgs2(args, encoder);
19459
19971
  appendSoftwareEncoderThreadsArg(args, "libx264");
19460
19972
  return args;
19461
19973
  }
19462
19974
  async function reencodeCfr(ctx, chunkPaths, encoder) {
19463
- const cfrOutputPath = join41(ctx.workDir, `cfr.${ctx.format}`);
19975
+ const cfrOutputPath = join42(ctx.workDir, `cfr.${ctx.format}`);
19464
19976
  const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
19465
19977
  appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
19466
19978
  cfrArgs.push("-y", cfrOutputPath);
@@ -19487,7 +19999,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
19487
19999
  );
19488
20000
  return;
19489
20001
  }
19490
- const paddedAudioPath = join41(ctx.workDir, "audio-padded.m4a");
20002
+ const paddedAudioPath = join42(ctx.workDir, "audio-padded.m4a");
19491
20003
  const padTrimResult = await ctx.timed(
19492
20004
  "audioMs",
19493
20005
  () => padOrTrimAudioToVideoFrameCount({
@@ -19527,9 +20039,9 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
19527
20039
  }
19528
20040
  }
19529
20041
  function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
19530
- const listPath = join41(workDir, "concat-list.txt");
20042
+ const listPath = join42(workDir, "concat-list.txt");
19531
20043
  const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
19532
- writeFileSync12(listPath, `${body}
20044
+ writeFileSync13(listPath, `${body}
19533
20045
  `, "utf-8");
19534
20046
  return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
19535
20047
  }
@@ -19544,7 +20056,7 @@ async function probeJoinedFrameInfo(videoPaths, fps, signal) {
19544
20056
  };
19545
20057
  }
19546
20058
  async function runAssembleFfmpeg(args, step, signal, timeout) {
19547
- const result = await runFfmpeg7(args, { signal, timeout });
20059
+ const result = await runFfmpeg8(args, { signal, timeout });
19548
20060
  if (!result.success) {
19549
20061
  throw encoderFailureError(`[assemble] ffmpeg ${step} failed`, {
19550
20062
  error: describeFfmpegFailure4(result, timeout),
@@ -19565,13 +20077,13 @@ function stitchesSeparateAudio(planDir) {
19565
20077
  return lockedAudio(planDir).separateAudio === true;
19566
20078
  }
19567
20079
  function planFormat(planDir) {
19568
- const plan2 = JSON.parse(readFileSync14(join41(planDir, "plan.json"), "utf-8"));
20080
+ const plan2 = JSON.parse(readFileSync15(join42(planDir, "plan.json"), "utf-8"));
19569
20081
  return plan2.dimensions.format;
19570
20082
  }
19571
20083
  function lockedAudio(planDir) {
19572
- const encoderJsonPath = join41(planDir, "meta", "encoder.json");
20084
+ const encoderJsonPath = join42(planDir, "meta", "encoder.json");
19573
20085
  if (!existsSync27(encoderJsonPath)) return {};
19574
- return JSON.parse(readFileSync14(encoderJsonPath, "utf-8"));
20086
+ return JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
19575
20087
  }
19576
20088
  function readCfrEncoder(planDir, format) {
19577
20089
  if (format !== "mp4") {
@@ -19579,11 +20091,11 @@ function readCfrEncoder(planDir, format) {
19579
20091
  `[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.`
19580
20092
  );
19581
20093
  }
19582
- const encoderJsonPath = join41(planDir, "meta", "encoder.json");
20094
+ const encoderJsonPath = join42(planDir, "meta", "encoder.json");
19583
20095
  if (!existsSync27(encoderJsonPath)) {
19584
20096
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
19585
20097
  }
19586
- const encoderJson = JSON.parse(readFileSync14(encoderJsonPath, "utf-8"));
20098
+ const encoderJson = JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
19587
20099
  if (encoderJson.encoder === "libx265-software") {
19588
20100
  throw new Error(
19589
20101
  `[assemble] cfr=true is not yet supported with codec: "h265". The cfr re-encode pass uses libx264 and would silently transcode the h265 chunks. Either disable cfr or render with codec: "h264".`
@@ -19592,11 +20104,11 @@ function readCfrEncoder(planDir, format) {
19592
20104
  return encoderJson;
19593
20105
  }
19594
20106
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
19595
- if (existsSync27(outputPath)) rmSync17(outputPath, { recursive: true, force: true });
19596
- mkdirSync17(outputPath, { recursive: true });
20107
+ if (existsSync27(outputPath)) rmSync16(outputPath, { recursive: true, force: true });
20108
+ mkdirSync18(outputPath, { recursive: true });
19597
20109
  let globalIdx = 0;
19598
20110
  for (const chunkDir of chunkPaths) {
19599
- if (!statSync12(chunkDir).isDirectory()) {
20111
+ if (!statSync13(chunkDir).isDirectory()) {
19600
20112
  throw new Error(
19601
20113
  `[assemble] png-sequence chunk must be a directory: ${chunkDir} (got a file)`
19602
20114
  );
@@ -19606,8 +20118,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
19606
20118
  throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
19607
20119
  }
19608
20120
  for (const frame of frames) {
19609
- const dst = join41(outputPath, formatExportFrameName(globalIdx, "png"));
19610
- cpSync3(join41(chunkDir, frame), dst);
20121
+ const dst = join42(outputPath, formatExportFrameName(globalIdx, "png"));
20122
+ cpSync3(join42(chunkDir, frame), dst);
19611
20123
  globalIdx += 1;
19612
20124
  }
19613
20125
  }
@@ -19617,13 +20129,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
19617
20129
  );
19618
20130
  }
19619
20131
  if (audioPath !== null && existsSync27(audioPath)) {
19620
- const sidecar = join41(outputPath, MIXED_AUDIO_FILENAME4);
20132
+ const sidecar = join42(outputPath, MIXED_AUDIO_FILENAME4);
19621
20133
  cpSync3(audioPath, sidecar);
19622
20134
  }
19623
20135
  let fileSize = 0;
19624
20136
  for (const name of readdirSync10(outputPath)) {
19625
20137
  try {
19626
- fileSize += statSync12(join41(outputPath, name)).size;
20138
+ fileSize += statSync13(join42(outputPath, name)).size;
19627
20139
  } catch {
19628
20140
  }
19629
20141
  }
@@ -19637,9 +20149,9 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
19637
20149
 
19638
20150
  // src/services/distributed/renderChunk.ts
19639
20151
  import { randomBytes } from "node:crypto";
19640
- import { existsSync as existsSync28, mkdirSync as mkdirSync18, readFileSync as readFileSync15, readdirSync as readdirSync11, rmSync as rmSync18, writeFileSync as writeFileSync13 } from "node:fs";
20152
+ import { existsSync as existsSync28, mkdirSync as mkdirSync19, readFileSync as readFileSync16, readdirSync as readdirSync11, rmSync as rmSync17, writeFileSync as writeFileSync14 } from "node:fs";
19641
20153
  import { cpus as cpus3 } from "node:os";
19642
- import { join as join42 } from "node:path";
20154
+ import { join as join43 } from "node:path";
19643
20155
  import {
19644
20156
  assertSwiftShader,
19645
20157
  BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
@@ -19758,10 +20270,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
19758
20270
  return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
19759
20271
  }
19760
20272
  function hashChunkOutput(outputPath, kind) {
19761
- if (kind === "file") return sha256Hex(readFileSync15(outputPath));
20273
+ if (kind === "file") return sha256Hex(readFileSync16(outputPath));
19762
20274
  const entries = readdirSync11(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
19763
20275
  const lines = entries.map(
19764
- (name) => `${name}\0${sha256Hex(readFileSync15(join42(outputPath, name)))}`
20276
+ (name) => `${name}\0${sha256Hex(readFileSync16(join43(outputPath, name)))}`
19765
20277
  );
19766
20278
  return sha256Hex(lines.join("\0"));
19767
20279
  }
@@ -19778,16 +20290,16 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
19778
20290
  async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19779
20291
  const start = Date.now();
19780
20292
  const log = defaultLogger;
19781
- const planJsonPath = join42(planDir, "plan.json");
19782
- const encoderJsonPath = join42(planDir, "meta", "encoder.json");
19783
- const chunksJsonPath = join42(planDir, "meta", "chunks.json");
20293
+ const planJsonPath = join43(planDir, "plan.json");
20294
+ const encoderJsonPath = join43(planDir, "meta", "encoder.json");
20295
+ const chunksJsonPath = join43(planDir, "meta", "chunks.json");
19784
20296
  if (!existsSync28(planJsonPath)) {
19785
20297
  throw new RenderChunkValidationError(
19786
20298
  MISSING_PLAN_ARTIFACT,
19787
20299
  `[renderChunk] planDir is missing required artifact: ${planJsonPath}`
19788
20300
  );
19789
20301
  }
19790
- const plan2 = JSON.parse(readFileSync15(planJsonPath, "utf-8"));
20302
+ const plan2 = JSON.parse(readFileSync16(planJsonPath, "utf-8"));
19791
20303
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
19792
20304
  const planHashStarted = Date.now();
19793
20305
  const v2Materialization = readValidatedPlanV2Materialization(planDir, {
@@ -19804,13 +20316,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19804
20316
  );
19805
20317
  }
19806
20318
  }
19807
- const encoder = JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
19808
- const chunks = JSON.parse(readFileSync15(chunksJsonPath, "utf-8"));
19809
- const videosJsonPath = join42(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
20319
+ const encoder = JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
20320
+ const chunks = JSON.parse(readFileSync16(chunksJsonPath, "utf-8"));
20321
+ const videosJsonPath = join43(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19810
20322
  let planVideos = null;
19811
20323
  if (existsSync28(videosJsonPath)) {
19812
20324
  try {
19813
- planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync15(videosJsonPath, "utf-8")));
20325
+ planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync16(videosJsonPath, "utf-8")));
19814
20326
  } catch (err) {
19815
20327
  if (err instanceof RenderChunkValidationError) throw err;
19816
20328
  throw new RenderChunkValidationError(
@@ -19838,7 +20350,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19838
20350
  }
19839
20351
  const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
19840
20352
  const framesInChunk = range.endFrame - range.startFrame;
19841
- const compiledDir = join42(planDir, "compiled");
20353
+ const compiledDir = join43(planDir, "compiled");
19842
20354
  if (!existsSync28(compiledDir)) {
19843
20355
  throw new RenderChunkValidationError(
19844
20356
  MISSING_PLAN_ARTIFACT,
@@ -19895,9 +20407,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19895
20407
  forceScreenshotExplicitlyOptedOut: !encoder.forceScreenshot
19896
20408
  };
19897
20409
  const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
19898
- mkdirSync18(workDir, { recursive: true });
19899
- const framesDir = join42(workDir, "captured-frames");
19900
- mkdirSync18(framesDir, { recursive: true });
20410
+ mkdirSync19(workDir, { recursive: true });
20411
+ const framesDir = join43(workDir, "captured-frames");
20412
+ mkdirSync19(framesDir, { recursive: true });
19901
20413
  const videoFramesDir = `${workDir}.video-frames`;
19902
20414
  const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
19903
20415
  const chunkFps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
@@ -19921,7 +20433,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19921
20433
  dimensions: plan2.dimensions,
19922
20434
  encoder,
19923
20435
  videoCount: planVideos?.videos.length ?? 0,
19924
- html: readFileSync15(join42(compiledDir, "index.html"), "utf-8"),
20436
+ html: readFileSync16(join43(compiledDir, "index.html"), "utf-8"),
19925
20437
  // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
19926
20438
  skipReadinessVideoIds: deferredVideos.filter(
19927
20439
  (video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
@@ -20050,10 +20562,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20050
20562
  const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
20051
20563
  const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
20052
20564
  if (isPngSequence) {
20053
- if (!existsSync28(outputChunkPath)) mkdirSync18(outputChunkPath, { recursive: true });
20565
+ if (!existsSync28(outputChunkPath)) mkdirSync19(outputChunkPath, { recursive: true });
20054
20566
  } else {
20055
- const outDir = join42(outputChunkPath, "..");
20056
- if (!existsSync28(outDir)) mkdirSync18(outDir, { recursive: true });
20567
+ const outDir = join43(outputChunkPath, "..");
20568
+ if (!existsSync28(outDir)) mkdirSync19(outDir, { recursive: true });
20057
20569
  }
20058
20570
  const encodeInput = {
20059
20571
  job,
@@ -20092,7 +20604,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20092
20604
  // first frame is an IDR keyframe and concat-copy at assemble time
20093
20605
  // round-trips losslessly.
20094
20606
  lockGopForChunkConcat: !isPngSequence,
20095
- gopSize: framesInChunk
20607
+ gopSize: chunkGopFrames(framesInChunk, encoder.keyframeIntervalFrames),
20608
+ maxBitrate: encoder.maxBitrate,
20609
+ bufferSize: encoder.bufferSize
20096
20610
  };
20097
20611
  const captureStarted = Date.now();
20098
20612
  captureMode = await runCaptureWithScreenshotFallback({
@@ -20164,8 +20678,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20164
20678
  return observedChunkCaptureMode(capturePerfs);
20165
20679
  },
20166
20680
  resetForScreenshotRetry: () => {
20167
- rmSync18(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
20168
- mkdirSync18(framesDir, { recursive: true });
20681
+ rmSync17(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
20682
+ mkdirSync19(framesDir, { recursive: true });
20169
20683
  capturePerfs.length = 0;
20170
20684
  captureAttempts.length = 0;
20171
20685
  job.framesRendered = 0;
@@ -20207,8 +20721,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20207
20721
  fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
20208
20722
  timeoutMs: cfg.ffmpegProcessTimeout
20209
20723
  });
20210
- rmSync18(videoOnlyPath, { force: true });
20211
- rmSync18(audioScratchPath, { force: true });
20724
+ rmSync17(videoOnlyPath, { force: true });
20725
+ rmSync17(audioScratchPath, { force: true });
20212
20726
  }
20213
20727
  encodeStageMs = Date.now() - encodeStarted;
20214
20728
  } finally {
@@ -20255,11 +20769,11 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20255
20769
  producerVersion: plan2.producerVersion,
20256
20770
  ffmpegVersion
20257
20771
  };
20258
- writeFileSync13(perfPath, `${JSON.stringify(perfPayload, null, 2)}
20772
+ writeFileSync14(perfPath, `${JSON.stringify(perfPayload, null, 2)}
20259
20773
  `, "utf-8");
20260
20774
  try {
20261
- rmSync18(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
20262
- rmSync18(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
20775
+ rmSync17(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
20776
+ rmSync17(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
20263
20777
  } catch (err) {
20264
20778
  log.warn("[renderChunk] failed to remove work dir", {
20265
20779
  workDir,
@@ -20288,18 +20802,18 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
20288
20802
 
20289
20803
  // src/services/distributed/planV2Execution.ts
20290
20804
  async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
20291
- const workRoot = mkdtempSync9(join43(tmpdir5(), "hf-plan-v2-chunk-"));
20292
- const materializedPlanDir = join43(workRoot, "plan");
20805
+ const workRoot = mkdtempSync9(join44(tmpdir5(), "hf-plan-v2-chunk-"));
20806
+ const materializedPlanDir = join44(workRoot, "plan");
20293
20807
  try {
20294
20808
  materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
20295
20809
  return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
20296
20810
  } finally {
20297
- rmSync19(workRoot, { recursive: true, force: true });
20811
+ rmSync18(workRoot, { recursive: true, force: true });
20298
20812
  }
20299
20813
  }
20300
20814
  async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
20301
- const workRoot = mkdtempSync9(join43(tmpdir5(), "hf-plan-v2-assembler-"));
20302
- const materializedPlanDir = join43(workRoot, "plan");
20815
+ const workRoot = mkdtempSync9(join44(tmpdir5(), "hf-plan-v2-assembler-"));
20816
+ const materializedPlanDir = join44(workRoot, "plan");
20303
20817
  try {
20304
20818
  const materialized = materializePlanV2Target(
20305
20819
  planV2Dir,
@@ -20314,7 +20828,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
20314
20828
  options
20315
20829
  );
20316
20830
  } finally {
20317
- rmSync19(workRoot, { recursive: true, force: true });
20831
+ rmSync18(workRoot, { recursive: true, force: true });
20318
20832
  }
20319
20833
  }
20320
20834
 
@@ -20322,7 +20836,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
20322
20836
  var READ_CHUNK_BYTES = 256 * 1024;
20323
20837
 
20324
20838
  // src/services/distributed/audioSidecar.ts
20325
- import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as runFfmpeg8 } from "@hyperframes/engine";
20839
+ import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as runFfmpeg9 } from "@hyperframes/engine";
20326
20840
  var AUDIO_SIDECAR_MIN_BYTES = 32 * 1024 * 1024;
20327
20841
 
20328
20842
  // src/services/distributed/probeFacts.ts
@@ -20386,6 +20900,7 @@ export {
20386
20900
  localizeRemoteImageSources,
20387
20901
  localizeRemoteMediaSources,
20388
20902
  materializePlanV2Target,
20903
+ mediaProfileMatchesElementType,
20389
20904
  normalizeErrorMessage,
20390
20905
  parseRenderRequest,
20391
20906
  plan,
@@ -20393,6 +20908,7 @@ export {
20393
20908
  planV2WithPublisher,
20394
20909
  prepareAnimatedGifInputs,
20395
20910
  prepareCaptureSessionForReuse4 as prepareCaptureSessionForReuse,
20911
+ prepareHeifImageInputs,
20396
20912
  prepareHyperframeLintBody,
20397
20913
  publishPlanV2FromExecutionPlan,
20398
20914
  publishPlanV2FromV1,