@hyperframes/producer 0.9.13 → 0.9.15

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.
@@ -8,7 +8,7 @@ import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframe
8
8
  import {
9
9
  copyFileSync as copyFileSync6,
10
10
  cpSync as cpSync2,
11
- existsSync as existsSync16,
11
+ existsSync as existsSync17,
12
12
  linkSync,
13
13
  mkdirSync as mkdirSync10,
14
14
  rmSync as rmSync7,
@@ -71,7 +71,7 @@ var defaultLogger = createConsoleLogger("info");
71
71
  // src/services/renderOrchestrator.ts
72
72
  import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
73
73
  import {
74
- existsSync as existsSync11,
74
+ existsSync as existsSync12,
75
75
  mkdirSync as mkdirSync6,
76
76
  readdirSync as readdirSync3,
77
77
  rmSync as rmSync5,
@@ -1920,7 +1920,7 @@ import { suggestMatchingPreset } from "@hyperframes/core";
1920
1920
  // src/services/htmlCompiler.ts
1921
1921
  import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
1922
1922
  import { createHash as createHash4 } from "node:crypto";
1923
- import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
1923
+ import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
1924
1924
  import { join as join6, dirname as dirname5, resolve as resolve6, basename as basename3, relative as relative2 } from "path";
1925
1925
  import { parseHTML as parseHTML5 } from "linkedom";
1926
1926
  import {
@@ -3895,6 +3895,25 @@ ${fontFaceBlocks.join("\n")}
3895
3895
  }
3896
3896
  return result;
3897
3897
  }
3898
+ function projectStylesheetFontFamilies(html, projectDir) {
3899
+ const families = [];
3900
+ for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
3901
+ const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
3902
+ if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
3903
+ const sheet = resolve6(projectDir, decodedUrlPath(href).replace(/^(?:\.?\/)+/, ""));
3904
+ if (!isPathInside2(sheet, projectDir) || !existsSync6(sheet)) continue;
3905
+ for (const face of extractFontFaceBlocks(readFileSync4(sheet, "utf8"))) {
3906
+ const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
3907
+ const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
3908
+ const path = decodedUrlPath(url);
3909
+ const file = path.startsWith("/") ? resolve6(projectDir, `.${path}`) : resolve6(dirname5(sheet), path);
3910
+ return isPathInside2(file, projectDir) && existsSync6(file);
3911
+ });
3912
+ if (family && shipsFile) families.push(family);
3913
+ }
3914
+ }
3915
+ return families;
3916
+ }
3898
3917
  function collectFontFaceUrls(html) {
3899
3918
  const styleBlockRe = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
3900
3919
  const urlSet = /* @__PURE__ */ new Set();
@@ -3925,12 +3944,10 @@ function collectFontFaceUrls(html) {
3925
3944
  }
3926
3945
  return urlSet;
3927
3946
  }
3928
- async function localizeRemoteFontFaces(html, downloadDir) {
3929
- const processed = await inlineExternalFontStylesheets(html);
3930
- const urlSet = collectFontFaceUrls(processed);
3947
+ function localizeFontFaceUrls(html, downloadDir) {
3931
3948
  return downloadAndRewriteUrls(
3932
- urlSet,
3933
- processed,
3949
+ collectFontFaceUrls(html),
3950
+ html,
3934
3951
  join6(downloadDir, REMOTE_MEDIA_SUBDIR),
3935
3952
  "Remote font download failed",
3936
3953
  "Localized remote font face(s)",
@@ -4098,15 +4115,17 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
4098
4115
  const hasShaderTransitions = detectShaderTransitionUsage(sanitizedHtml);
4099
4116
  const usesThreeDTransforms = detectThreeDTransformUsage(sanitizedHtml);
4100
4117
  const hasAncestorBackgroundImage = detectAncestorBackgroundImage(sanitizedHtml);
4118
+ const fontSheetHtml = await inlineExternalFontStylesheets(
4119
+ promoteCssImportsToLinkTags(sanitizedHtml)
4120
+ );
4101
4121
  const normalizedFontHtml = normalizeSystemFontPrimaryFamilies(
4102
- injectTextRenderingRule(
4103
- coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml))
4104
- )
4122
+ injectTextRenderingRule(coalesceHeadStylesAndBodyScripts(fontSheetHtml))
4105
4123
  );
4106
4124
  const coalescedHtml = await injectDeterministicFontFaces(normalizedFontHtml, {
4107
4125
  logger: defaultLogger,
4108
4126
  failClosedFontFetch: options.failClosedFontFetch === true,
4109
4127
  allowSystemFontCapture: options.allowSystemFontCapture,
4128
+ projectFontFamilies: projectStylesheetFontFamilies(fontSheetHtml, projectDir),
4110
4129
  abortSignal: options.abortSignal
4111
4130
  });
4112
4131
  const assembledHtml = await inlineExternalScripts(coalescedHtml);
@@ -4132,7 +4151,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
4132
4151
  );
4133
4152
  const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
4134
4153
  const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
4135
- const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeRemoteFontFaces(htmlWithLocalBg, downloadDir);
4154
+ const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeFontFaceUrls(htmlWithLocalBg, downloadDir);
4136
4155
  const {
4137
4156
  html: htmlWithJpegs,
4138
4157
  preparedAssets: preparedHeifAssets,
@@ -5908,6 +5927,7 @@ import {
5908
5927
  } from "@hyperframes/engine";
5909
5928
 
5910
5929
  // src/services/render/stages/audioStage.ts
5930
+ import { existsSync as existsSync9 } from "node:fs";
5911
5931
  import { join as join12 } from "node:path";
5912
5932
  import {
5913
5933
  MIXED_AUDIO_FILENAME,
@@ -6041,8 +6061,9 @@ var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
6041
6061
  var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
6042
6062
  var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
6043
6063
  var MAX_TRUE_PEAK_CORRECTION_PASSES = 3;
6064
+ var gainFilter = (gainDb) => `volume=${gainDb.toFixed(3)}dB`;
6044
6065
  function aacTruePeakCorrectionFilter(targetDurationSeconds, attenuationDb) {
6045
- return `volume=${attenuationDb.toFixed(3)}dB,apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
6066
+ return `${gainFilter(attenuationDb)},apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
6046
6067
  }
6047
6068
  function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
6048
6069
  const delta = targetDurationSeconds - sourceDurationSeconds;
@@ -6080,7 +6101,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
6080
6101
  const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
6081
6102
  const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
6082
6103
  const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
6083
- const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal);
6104
+ const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath);
6084
6105
  const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg || input.encodeAac ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
6085
6106
  const [videoResult, audioResult] = await Promise.allSettled([
6086
6107
  probeVideo(input.videoPath),
@@ -6128,7 +6149,12 @@ async function padOrTrimAudioToVideoFrameCount(input) {
6128
6149
  );
6129
6150
  try {
6130
6151
  for (const step of plan.steps) {
6131
- const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(input.audioPath, input.outputPath, targetDurationSeconds, step.filter);
6152
+ const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
6153
+ input.sourcePath ?? input.audioPath,
6154
+ input.outputPath,
6155
+ targetDurationSeconds,
6156
+ step.filter
6157
+ );
6132
6158
  if (!ffmpegResult.success) {
6133
6159
  return {
6134
6160
  success: false,
@@ -6157,6 +6183,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
6157
6183
  if (probeTruePeak) {
6158
6184
  const correction = await enforceAacTruePeak({
6159
6185
  audioPath: input.outputPath,
6186
+ sourcePath: input.sourcePath,
6160
6187
  targetDurationSeconds,
6161
6188
  signal: input.signal,
6162
6189
  probeTruePeak,
@@ -6192,9 +6219,27 @@ async function constrainAacTruePeak(input) {
6192
6219
  targetDurationSeconds: input.targetDurationSeconds,
6193
6220
  signal: input.signal,
6194
6221
  probeTruePeak: input.probeTruePeakDbfs ?? ((audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal })),
6195
- encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
6222
+ encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath),
6223
+ rewriteSource: input.rewriteSource ?? ((sourcePath, outputPath, filter) => defaultRunFfmpeg(
6224
+ ["-i", sourcePath, "-af", filter, "-c:a", "pcm_f32le", "-y", outputPath],
6225
+ timeoutMs,
6226
+ input.signal
6227
+ ))
6196
6228
  });
6197
6229
  }
6230
+ async function cutSource(input, scratchDir, attenuationDb) {
6231
+ if (!input.sourcePath || !input.rewriteSource) return true;
6232
+ const cutPath = join10(scratchDir, "source.wav");
6233
+ try {
6234
+ const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
6235
+ if (!result.success) throw new Error(result.error);
6236
+ renameWithRetry5(cutPath, input.sourcePath, { copyFallback: true });
6237
+ return true;
6238
+ } catch {
6239
+ removeScratch(input.sourcePath);
6240
+ return false;
6241
+ }
6242
+ }
6198
6243
  async function enforceAacTruePeak(input) {
6199
6244
  let scratchDir;
6200
6245
  try {
@@ -6214,8 +6259,10 @@ async function enforceAacTruePeak(input) {
6214
6259
  }
6215
6260
  if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
6216
6261
  if (measuredPath !== correctedPath) return { success: true };
6262
+ const sourceCut = await cutSource(input, scratchDir, attenuationDb);
6217
6263
  renameWithRetry5(correctedPath, input.audioPath, { copyFallback: true });
6218
- return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
6264
+ const loweredDb = Number((-attenuationDb).toFixed(2));
6265
+ return sourceCut ? { success: true, loweredDb } : { success: true, loweredDb, sourceStale: true };
6219
6266
  }
6220
6267
  if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
6221
6268
  const headroomDb = AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB * 2 ** pass;
@@ -6225,7 +6272,7 @@ async function enforceAacTruePeak(input) {
6225
6272
  const referenceDbfs = Math.max(truePeakDbfs, (sourcePeakDbfs ?? -Infinity) + attenuationDb);
6226
6273
  attenuationDb += AAC_DELIVERY_TRUE_PEAK_DBFS - headroomDb - referenceDbfs;
6227
6274
  const result = await input.encode(
6228
- input.audioPath,
6275
+ input.sourcePath ?? input.audioPath,
6229
6276
  correctedPath,
6230
6277
  input.targetDurationSeconds,
6231
6278
  aacTruePeakCorrectionFilter(input.targetDurationSeconds, attenuationDb)
@@ -6454,36 +6501,54 @@ async function runAudioStageScoped(input) {
6454
6501
  const stageStart = Date.now();
6455
6502
  const pcmSourcePath = join12(input.workDir, "audio-source.wav");
6456
6503
  const duration = input.totalFrames * input.fps.den / input.fps.num;
6504
+ let deliverySourcePath;
6457
6505
  try {
6458
6506
  const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
6459
6507
  let audioLoweredDb;
6460
6508
  if (result.hasAudio) {
6461
6509
  const guardStart = Date.now();
6462
- const limited = await limitDeliveryPeaks({
6463
- audioPath: result.audioOutputPath,
6464
- sourcePath: pcmSourcePath,
6465
- targetDurationSeconds: duration,
6466
- signal: input.abortSignal,
6467
- timeoutMs: input.ffmpegProcessTimeout,
6468
- warn: (message, details) => input.log?.warn?.(message, details)
6469
- });
6470
- if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
6471
- const peak = await constrainAacTruePeak({
6472
- audioPath: result.audioOutputPath,
6473
- sourcePath: pcmSourcePath,
6474
- targetDurationSeconds: duration,
6475
- signal: input.abortSignal,
6476
- timeoutMs: input.ffmpegProcessTimeout
6477
- });
6478
- if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
6479
- audioLoweredDb = peak.loweredDb;
6510
+ const guard = await guardDeliveryPeaks(
6511
+ input,
6512
+ result.audioOutputPath,
6513
+ pcmSourcePath,
6514
+ duration
6515
+ );
6516
+ audioLoweredDb = guard.loweredDb;
6480
6517
  input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
6518
+ if (input.keepDeliverySource && !guard.sourceStale && existsSync9(pcmSourcePath)) {
6519
+ deliverySourcePath = pcmSourcePath;
6520
+ }
6481
6521
  }
6482
- return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
6522
+ return {
6523
+ ...result,
6524
+ audioLoweredDb,
6525
+ deliverySourcePath,
6526
+ audioProcessMs: Date.now() - stageStart
6527
+ };
6483
6528
  } finally {
6484
- removeScratch2(pcmSourcePath);
6529
+ if (!deliverySourcePath) removeScratch2(pcmSourcePath);
6485
6530
  }
6486
6531
  }
6532
+ async function guardDeliveryPeaks(input, audioPath, sourcePath, duration) {
6533
+ const limited = await limitDeliveryPeaks({
6534
+ audioPath,
6535
+ sourcePath,
6536
+ targetDurationSeconds: duration,
6537
+ signal: input.abortSignal,
6538
+ timeoutMs: input.ffmpegProcessTimeout,
6539
+ warn: (message, details) => input.log?.warn?.(message, details)
6540
+ });
6541
+ if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
6542
+ const peak = await constrainAacTruePeak({
6543
+ audioPath,
6544
+ sourcePath,
6545
+ targetDurationSeconds: duration,
6546
+ signal: input.abortSignal,
6547
+ timeoutMs: input.ffmpegProcessTimeout
6548
+ });
6549
+ if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
6550
+ return { loweredDb: peak.loweredDb, sourceStale: peak.sourceStale };
6551
+ }
6487
6552
  async function mixAudioForDelivery(input, duration, pcmSourcePath) {
6488
6553
  const {
6489
6554
  projectDir,
@@ -6578,7 +6643,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
6578
6643
  }
6579
6644
 
6580
6645
  // src/services/render/stages/captureStage.ts
6581
- import { existsSync as existsSync9, readdirSync, statfsSync } from "node:fs";
6646
+ import { existsSync as existsSync10, readdirSync, statfsSync } from "node:fs";
6582
6647
  import { join as join13 } from "node:path";
6583
6648
  import {
6584
6649
  classifyCaptureFailure as classifyCaptureFailure2,
@@ -6630,7 +6695,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
6630
6695
  }
6631
6696
  function measureCaptureFrameBytes(workDir, framesDir) {
6632
6697
  let total = sampleDirectoryBytes(framesDir);
6633
- if (!existsSync9(workDir)) return total;
6698
+ if (!existsSync10(workDir)) return total;
6634
6699
  for (const attempt of readdirSync(workDir)) {
6635
6700
  if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
6636
6701
  total += sampleDirectoryBytes(join13(workDir, attempt));
@@ -6967,10 +7032,10 @@ import {
6967
7032
  // src/services/render/stages/encodeStage.ts
6968
7033
  import {
6969
7034
  copyFileSync as copyFileSync4,
6970
- existsSync as existsSync10,
7035
+ existsSync as existsSync11,
6971
7036
  mkdirSync as mkdirSync5,
6972
7037
  readdirSync as readdirSync2,
6973
- readFileSync as readFileSync4,
7038
+ readFileSync as readFileSync5,
6974
7039
  rmSync as rmSync4,
6975
7040
  statSync as statSync4,
6976
7041
  writeFileSync as writeFileSync3
@@ -7064,7 +7129,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
7064
7129
  const error = formatFfmpegError(result.exitCode, result.stderr);
7065
7130
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
7066
7131
  }
7067
- const gif = readFileSync4(outputPath);
7132
+ const gif = readFileSync5(outputPath);
7068
7133
  if (!clearGifFramesBeforeNext(gif)) {
7069
7134
  const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
7070
7135
  return gifEncodeFailure(startTime, outputPath, error);
@@ -7097,11 +7162,11 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
7097
7162
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
7098
7163
  }
7099
7164
  }
7100
- if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync4(outputPath)) !== false) {
7165
+ if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync5(outputPath)) !== false) {
7101
7166
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
7102
7167
  if (failure) return failure;
7103
7168
  }
7104
- const fileSize = existsSync10(outputPath) ? statSync4(outputPath).size : 0;
7169
+ const fileSize = existsSync11(outputPath) ? statSync4(outputPath).size : 0;
7105
7170
  return {
7106
7171
  success: true,
7107
7172
  outputPath,
@@ -7262,7 +7327,7 @@ async function runEncodeStage(input) {
7262
7327
  const stage5Start = Date.now();
7263
7328
  if (isPngSequence) {
7264
7329
  const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
7265
- if (!existsSync10(outputPath)) mkdirSync5(outputPath, { recursive: true });
7330
+ if (!existsSync11(outputPath)) mkdirSync5(outputPath, { recursive: true });
7266
7331
  const captured = readdirSync2(framesDir).filter((name) => name.endsWith(".png")).sort();
7267
7332
  if (captured.length === 0) {
7268
7333
  throw new Error(
@@ -7273,7 +7338,7 @@ async function runEncodeStage(input) {
7273
7338
  const dst = join14(outputPath, formatExportFrameName(i, "png"));
7274
7339
  copyFileSync4(join14(framesDir, name), dst);
7275
7340
  });
7276
- if (hasAudio && audioOutputPath && existsSync10(audioOutputPath)) {
7341
+ if (hasAudio && audioOutputPath && existsSync11(audioOutputPath)) {
7277
7342
  copyFileSync4(audioOutputPath, join14(outputPath, MIXED_AUDIO_FILENAME2));
7278
7343
  log.info(
7279
7344
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
@@ -7440,7 +7505,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
7440
7505
  let rangeStart = null;
7441
7506
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
7442
7507
  const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
7443
- const missing = !existsSync11(framePath) || statSync5(framePath).size <= 8;
7508
+ const missing = !existsSync12(framePath) || statSync5(framePath).size <= 8;
7444
7509
  if (missing && rangeStart === null) {
7445
7510
  rangeStart = frameIndex;
7446
7511
  } else if (!missing && rangeStart !== null) {
@@ -7530,7 +7595,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
7530
7595
  let captured = 0;
7531
7596
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
7532
7597
  const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
7533
- if (existsSync11(framePath)) captured++;
7598
+ if (existsSync12(framePath)) captured++;
7534
7599
  }
7535
7600
  return captured;
7536
7601
  }
@@ -7757,7 +7822,7 @@ import {
7757
7822
  } from "@hyperframes/engine";
7758
7823
 
7759
7824
  // src/services/distributed/chunkFrames.ts
7760
- import { existsSync as existsSync13, readdirSync as readdirSync4 } from "node:fs";
7825
+ import { existsSync as existsSync14, readdirSync as readdirSync4 } from "node:fs";
7761
7826
  import { extname as extname2, join as join17 } from "node:path";
7762
7827
  import {
7763
7828
  compositionRequiresWebGpu as compositionRequiresWebGpu3,
@@ -7769,7 +7834,7 @@ import {
7769
7834
  // src/services/distributed/shared.ts
7770
7835
  import { execFile as execFileCallback } from "node:child_process";
7771
7836
  import { dirname as dirname10, join as join16 } from "node:path";
7772
- import { existsSync as existsSync12, readFileSync as readFileSync5 } from "node:fs";
7837
+ import { existsSync as existsSync13, readFileSync as readFileSync6 } from "node:fs";
7773
7838
  import { fileURLToPath as fileURLToPath3 } from "node:url";
7774
7839
  import { promisify as promisify2 } from "node:util";
7775
7840
  import {
@@ -7785,7 +7850,7 @@ function isPlanAudioArtifactPath(path) {
7785
7850
  function resolvePlanAudioPath(planDir) {
7786
7851
  for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
7787
7852
  const candidate = join16(planDir, name);
7788
- if (existsSync12(candidate)) return candidate;
7853
+ if (existsSync13(candidate)) return candidate;
7789
7854
  }
7790
7855
  return null;
7791
7856
  }
@@ -8046,9 +8111,9 @@ function readProducerVersion() {
8046
8111
  let current = startDir;
8047
8112
  for (let i = 0; i < 10; i++) {
8048
8113
  const candidate = join16(current, "package.json");
8049
- if (existsSync12(candidate)) {
8114
+ if (existsSync13(candidate)) {
8050
8115
  try {
8051
- const pkg = JSON.parse(readFileSync5(candidate, "utf-8"));
8116
+ const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
8052
8117
  if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
8053
8118
  cachedProducerVersion = pkg.version;
8054
8119
  return pkg.version;
@@ -8100,7 +8165,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
8100
8165
  const result = [];
8101
8166
  for (const v of videos) {
8102
8167
  const outputDir = join17(planDir, "video-frames", v.videoId);
8103
- if (!existsSync13(outputDir)) {
8168
+ if (!existsSync14(outputDir)) {
8104
8169
  throw new Error(
8105
8170
  `[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
8106
8171
  );
@@ -8216,7 +8281,7 @@ async function extractDeferredVideoFramesForChunk(input) {
8216
8281
  const range = video.deferredRange;
8217
8282
  const remoteUrl = remoteSources[range.sourcePath];
8218
8283
  const sourcePath = remoteUrl ?? join17(input.planDir, ...range.sourcePath.split("/"));
8219
- if (remoteUrl === void 0 && !existsSync13(sourcePath)) {
8284
+ if (remoteUrl === void 0 && !existsSync14(sourcePath)) {
8220
8285
  throw new RenderChunkValidationError(
8221
8286
  MISSING_PLAN_ARTIFACT,
8222
8287
  `[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
@@ -8434,7 +8499,7 @@ async function readPlanReferenceBackground(input) {
8434
8499
  }
8435
8500
 
8436
8501
  // src/services/render/stages/freezePlan.ts
8437
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync6, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
8502
+ import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
8438
8503
  import { join as join20, relative as relative3, resolve as resolve8 } from "node:path";
8439
8504
  import {
8440
8505
  REFERENCE_BACKGROUND_FIELD,
@@ -8628,7 +8693,7 @@ function planFileSha256(absolutePath, digests) {
8628
8693
  return sha256;
8629
8694
  }
8630
8695
  async function prehashPlanFiles(dir, digests) {
8631
- if (!existsSync14(dir)) return;
8696
+ if (!existsSync15(dir)) return;
8632
8697
  for (const { absolutePath } of listPlanFiles(dir)) {
8633
8698
  if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
8634
8699
  }
@@ -8674,7 +8739,7 @@ function collectPlanAssetShas(planDir, digests) {
8674
8739
  const assets = [];
8675
8740
  for (const file of files) {
8676
8741
  if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
8677
- compositionHtml = readFileSync6(file.absolutePath);
8742
+ compositionHtml = readFileSync7(file.absolutePath);
8678
8743
  continue;
8679
8744
  }
8680
8745
  if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
@@ -8698,19 +8763,19 @@ function writePlanRemoteFiles(planDir, files) {
8698
8763
  }
8699
8764
  function readPlanRemoteFiles(planDir) {
8700
8765
  const path = join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
8701
- return existsSync14(path) ? JSON.parse(readFileSync6(path, "utf-8")) : [];
8766
+ return existsSync15(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
8702
8767
  }
8703
8768
  function recomputePlanHashFromPlanDir(planDir, digests) {
8704
8769
  const planJsonPath = join20(planDir, "plan.json");
8705
8770
  const encoderJsonPath = join20(planDir, "meta", "encoder.json");
8706
- if (!existsSync14(planJsonPath)) {
8771
+ if (!existsSync15(planJsonPath)) {
8707
8772
  throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
8708
8773
  }
8709
- if (!existsSync14(encoderJsonPath)) {
8774
+ if (!existsSync15(encoderJsonPath)) {
8710
8775
  throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
8711
8776
  }
8712
- const planJson = JSON.parse(readFileSync6(planJsonPath, "utf-8"));
8713
- const encoderConfigCanonicalJson = readFileSync6(encoderJsonPath, "utf-8");
8777
+ const planJson = JSON.parse(readFileSync7(planJsonPath, "utf-8"));
8778
+ const encoderConfigCanonicalJson = readFileSync7(encoderJsonPath, "utf-8");
8714
8779
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
8715
8780
  return computePlanHash({
8716
8781
  compositionHtml,
@@ -8736,11 +8801,11 @@ async function freezePlan(input) {
8736
8801
  hasAudio,
8737
8802
  digests
8738
8803
  } = input;
8739
- if (!existsSync14(planDir)) {
8804
+ if (!existsSync15(planDir)) {
8740
8805
  throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
8741
8806
  }
8742
8807
  const metaDir = join20(planDir, "meta");
8743
- if (!existsSync14(metaDir)) mkdirSync7(metaDir, { recursive: true });
8808
+ if (!existsSync15(metaDir)) mkdirSync7(metaDir, { recursive: true });
8744
8809
  writeFileSync5(
8745
8810
  join20(metaDir, "composition.json"),
8746
8811
  `${JSON.stringify(composition, null, 2)}
@@ -8817,7 +8882,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
8817
8882
  }
8818
8883
 
8819
8884
  // src/services/distributed/chunkAudio.ts
8820
- import { closeSync as closeSync4, existsSync as existsSync15, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync7 } from "node:fs";
8885
+ import { closeSync as closeSync4, existsSync as existsSync16, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
8821
8886
  import { writeFileSync as writeFileSync6, writeSync } from "node:fs";
8822
8887
  import { join as join22 } from "node:path";
8823
8888
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
@@ -9290,7 +9355,7 @@ async function writeChunkAudioSlices(input) {
9290
9355
  offset += track.sizes[offsets.length - 1];
9291
9356
  }
9292
9357
  });
9293
- const bytes = readFileSync7(input.audioPath);
9358
+ const bytes = readFileSync8(input.audioPath);
9294
9359
  const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
9295
9360
  mkdirSync9(join22(input.planDir, SLICE_DIR), { recursive: true });
9296
9361
  input.chunks.forEach((_, index) => {
@@ -9308,9 +9373,9 @@ async function writeChunkAudioSlices(input) {
9308
9373
  }
9309
9374
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
9310
9375
  const layoutPath = join22(planDir, SLICE_LAYOUT);
9311
- if (!existsSync15(layoutPath)) return resolvePlanAudioPath(planDir);
9312
- const layout = JSON.parse(readFileSync7(layoutPath, "utf-8"));
9313
- const frame = readFileSync7(join22(planDir, SLICE_FRAME));
9376
+ if (!existsSync16(layoutPath)) return resolvePlanAudioPath(planDir);
9377
+ const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
9378
+ const frame = readFileSync8(join22(planDir, SLICE_FRAME));
9314
9379
  const fd = openSync4(scratchPath, "w");
9315
9380
  try {
9316
9381
  writeSync(fd, frame, 0, layout.payloadStart, 0);
@@ -9323,7 +9388,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
9323
9388
  );
9324
9389
  const start = layout.partStarts[chunkIndex];
9325
9390
  if (start !== void 0) {
9326
- const part = readFileSync7(join22(planDir, chunkAudioSlicePath(chunkIndex)));
9391
+ const part = readFileSync8(join22(planDir, chunkAudioSlicePath(chunkIndex)));
9327
9392
  writeSync(fd, part, 0, part.length, start);
9328
9393
  }
9329
9394
  ftruncateSync(fd, layout.sizeBytes);
@@ -9872,7 +9937,7 @@ function createVideoSourceShipper(input) {
9872
9937
  let result = null;
9873
9938
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
9874
9939
  const rel = pathInside(root, srcPath);
9875
- if (rel !== null && (existsSync16(join24(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
9940
+ if (rel !== null && (existsSync17(join24(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
9876
9941
  result = ["compiled", ...rel.split(sep2)].join("/");
9877
9942
  break;
9878
9943
  }
@@ -9900,7 +9965,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
9900
9965
  useGpu: false,
9901
9966
  browserGpuMode: "software"
9902
9967
  });
9903
- if (!existsSync16(planDir)) mkdirSync10(planDir, { recursive: true });
9968
+ if (!existsSync17(planDir)) mkdirSync10(planDir, { recursive: true });
9904
9969
  const log = config.logger ?? defaultLogger;
9905
9970
  const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
9906
9971
  const abortSignal = config.abortSignal;
@@ -9930,7 +9995,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
9930
9995
  });
9931
9996
  const entryFile = config.entryFile ?? "index.html";
9932
9997
  const htmlPath = join24(projectDir, entryFile);
9933
- if (!existsSync16(htmlPath)) {
9998
+ if (!existsSync17(htmlPath)) {
9934
9999
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
9935
10000
  }
9936
10001
  const workDir = join24(planDir, ".plan-work");
@@ -10159,15 +10224,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10159
10224
  logPlanStage(log, "extract", stageStarted);
10160
10225
  const stagedVideoFrames = join24(compiledDir, "__hyperframes_video_frames");
10161
10226
  const videoFramesDst = join24(planDir, "video-frames");
10162
- if (existsSync16(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
10227
+ if (existsSync17(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
10163
10228
  const videoSourcesDir = join24(planDir, "video-sources");
10164
10229
  rmSync7(videoSourcesDir, { recursive: true, force: true });
10165
- if (existsSync16(stagedVideoFrames)) {
10230
+ if (existsSync17(stagedVideoFrames)) {
10166
10231
  renameWithRetry8(stagedVideoFrames, videoFramesDst);
10167
10232
  } else {
10168
10233
  mkdirSync10(videoFramesDst, { recursive: true });
10169
10234
  }
10170
- if (existsSync16(finalCompiledDir)) rmSync7(finalCompiledDir, { recursive: true, force: true });
10235
+ if (existsSync17(finalCompiledDir)) rmSync7(finalCompiledDir, { recursive: true, force: true });
10171
10236
  renameWithRetry8(compiledDir, finalCompiledDir);
10172
10237
  const shipVideoSource = createVideoSourceShipper({
10173
10238
  planDir,
@@ -10287,10 +10352,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10287
10352
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
10288
10353
  }
10289
10354
  const planAudioPath = join24(planDir, PLAN_AUDIO_RELATIVE_PATH);
10290
- if (audioResult?.hasAudio && existsSync16(audioResult.audioOutputPath)) {
10355
+ if (audioResult?.hasAudio && existsSync17(audioResult.audioOutputPath)) {
10291
10356
  renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
10292
10357
  }
10293
- if (chunksCarryAudio && existsSync16(planAudioPath)) {
10358
+ if (chunksCarryAudio && existsSync17(planAudioPath)) {
10294
10359
  if (options.sliceChunkAudio) {
10295
10360
  stageStarted = Date.now();
10296
10361
  await writeChunkAudioSlices({
@@ -10403,7 +10468,7 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
10403
10468
  if (mixed.audioError) {
10404
10469
  applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
10405
10470
  }
10406
- if (!mixed.hasAudio || !existsSync16(mixed.audioOutputPath)) return { hasAudio: false };
10471
+ if (!mixed.hasAudio || !existsSync17(mixed.audioOutputPath)) return { hasAudio: false };
10407
10472
  renameWithRetry8(mixed.audioOutputPath, outputPath);
10408
10473
  return { hasAudio: true };
10409
10474
  } finally {
@@ -10418,11 +10483,11 @@ import {
10418
10483
  } from "@hyperframes/engine";
10419
10484
  import {
10420
10485
  copyFileSync as copyFileSync8,
10421
- existsSync as existsSync18,
10486
+ existsSync as existsSync19,
10422
10487
  lstatSync as lstatSync2,
10423
10488
  mkdirSync as mkdirSync12,
10424
10489
  mkdtempSync as mkdtempSync4,
10425
- readFileSync as readFileSync8,
10490
+ readFileSync as readFileSync9,
10426
10491
  readdirSync as readdirSync7,
10427
10492
  rmSync as rmSync9,
10428
10493
  statSync as statSync7,
@@ -10436,7 +10501,7 @@ import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as r
10436
10501
  // src/services/distributed/planV2Publisher.ts
10437
10502
  import {
10438
10503
  copyFileSync as copyFileSync7,
10439
- existsSync as existsSync17,
10504
+ existsSync as existsSync18,
10440
10505
  linkSync as linkSync2,
10441
10506
  mkdirSync as mkdirSync11,
10442
10507
  mkdtempSync as mkdtempSync3,
@@ -10505,7 +10570,7 @@ var LocalPlanV2ArtifactPublisher = class {
10505
10570
  #linkFile;
10506
10571
  #committed = false;
10507
10572
  constructor(destinationDir, options = {}) {
10508
- if (existsSync17(destinationDir)) {
10573
+ if (existsSync18(destinationDir)) {
10509
10574
  throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
10510
10575
  }
10511
10576
  this.destinationDir = destinationDir;
@@ -10522,7 +10587,7 @@ var LocalPlanV2ArtifactPublisher = class {
10522
10587
  );
10523
10588
  }
10524
10589
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
10525
- if (existsSync17(destinationPath)) return;
10590
+ if (existsSync18(destinationPath)) return;
10526
10591
  mkdirSync11(dirname12(destinationPath), { recursive: true });
10527
10592
  const stagingDir = mkdtempSync3(join26(dirname12(destinationPath), ".plan-v2-blob-"));
10528
10593
  const temporaryPath = join26(stagingDir, "blob");
@@ -10540,7 +10605,7 @@ var LocalPlanV2ArtifactPublisher = class {
10540
10605
  }
10541
10606
  async commitManifest(manifestBytes) {
10542
10607
  for (const digest of new Set(manifestDigests(manifestBytes))) {
10543
- if (!existsSync17(planV2BlobPath(this.temporaryDir, digest))) {
10608
+ if (!existsSync18(planV2BlobPath(this.temporaryDir, digest))) {
10544
10609
  throw new PlanV2IntegrityError(
10545
10610
  `cannot commit manifest before referenced blob is durable: ${digest}`
10546
10611
  );
@@ -10571,7 +10636,7 @@ function isNonNegativeIntegerArray(value) {
10571
10636
  return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
10572
10637
  }
10573
10638
  function readJsonFile(path, label) {
10574
- const contents = readFileSync8(path, "utf-8");
10639
+ const contents = readFileSync9(path, "utf-8");
10575
10640
  try {
10576
10641
  const parsed = JSON.parse(contents);
10577
10642
  return parsed;
@@ -10619,7 +10684,7 @@ function assertValidExtractionCacheCompleteSentinel(path) {
10619
10684
  }
10620
10685
  function validateExtractionCacheCompleteSentinels(executionPlanDir) {
10621
10686
  const videoRoot = join27(executionPlanDir, "video-frames");
10622
- if (!existsSync18(videoRoot)) return;
10687
+ if (!existsSync19(videoRoot)) return;
10623
10688
  for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
10624
10689
  if (!videoEntry.isDirectory()) continue;
10625
10690
  const videoDir = join27(videoRoot, videoEntry.name);
@@ -10720,7 +10785,7 @@ function parsePlanVideosJson2(value) {
10720
10785
  }
10721
10786
  function materializeExtractedVideoDirectories(planDir) {
10722
10787
  const videosPath = join27(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10723
- if (!existsSync18(videosPath)) return;
10788
+ if (!existsSync19(videosPath)) return;
10724
10789
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
10725
10790
  for (const video of videos.extracted) {
10726
10791
  mkdirSync12(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
@@ -10754,9 +10819,9 @@ function parseChunkSlices(value) {
10754
10819
  }
10755
10820
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
10756
10821
  const videoRoot = join27(executionPlanDir, "video-frames");
10757
- const hasExtractedFrames = existsSync18(videoRoot) && listFiles(videoRoot).length > 0;
10822
+ const hasExtractedFrames = existsSync19(videoRoot) && listFiles(videoRoot).length > 0;
10758
10823
  const videosPath = join27(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10759
- if (!existsSync18(videosPath)) {
10824
+ if (!existsSync19(videosPath)) {
10760
10825
  return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
10761
10826
  }
10762
10827
  const chunksPath = join27(executionPlanDir, "meta", "chunks.json");
@@ -10813,7 +10878,7 @@ function computeManifestHash(manifest) {
10813
10878
  return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
10814
10879
  }
10815
10880
  function writeBlob(sourcePath, destinationPath) {
10816
- if (existsSync18(destinationPath)) return;
10881
+ if (existsSync19(destinationPath)) return;
10817
10882
  mkdirSync12(dirname13(destinationPath), { recursive: true });
10818
10883
  const temporaryDir = mkdtempSync4(join27(dirname13(destinationPath), ".plan-v2-blob-"));
10819
10884
  const temporaryPath = join27(temporaryDir, "blob");
@@ -10853,7 +10918,7 @@ async function putTasks(tasks) {
10853
10918
  }
10854
10919
  function buildPlanV2Publication(executionPlanDir, digests) {
10855
10920
  const executionPlanPath = join27(executionPlanDir, "plan.json");
10856
- if (!existsSync18(executionPlanPath)) {
10921
+ if (!existsSync19(executionPlanPath)) {
10857
10922
  throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
10858
10923
  }
10859
10924
  const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
@@ -10933,7 +10998,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
10933
10998
  };
10934
10999
  }
10935
11000
  function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
10936
- if (existsSync18(planV2Dir)) {
11001
+ if (existsSync19(planV2Dir)) {
10937
11002
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
10938
11003
  }
10939
11004
  const publication = buildPlanV2Publication(executionPlanDir);
@@ -11019,7 +11084,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
11019
11084
  }
11020
11085
  }
11021
11086
  async function planV2(projectDir, config, planV2Dir) {
11022
- if (existsSync18(planV2Dir)) {
11087
+ if (existsSync19(planV2Dir)) {
11023
11088
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
11024
11089
  }
11025
11090
  const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
@@ -11173,7 +11238,7 @@ function parsePlanV2Manifest(value) {
11173
11238
  }
11174
11239
  function readPlanV2Manifest(planV2Dir) {
11175
11240
  const manifestPath = join27(planV2Dir, "plan.json");
11176
- if (!existsSync18(manifestPath)) {
11241
+ if (!existsSync19(manifestPath)) {
11177
11242
  throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
11178
11243
  }
11179
11244
  const value = readJsonFile(manifestPath, "v2 manifest");
@@ -11192,7 +11257,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
11192
11257
  }
11193
11258
  function verifyBlob(planV2Dir, artifact) {
11194
11259
  const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
11195
- if (!existsSync18(sourcePath)) {
11260
+ if (!existsSync19(sourcePath)) {
11196
11261
  throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
11197
11262
  }
11198
11263
  const stats = statSync7(sourcePath);
@@ -11205,7 +11270,7 @@ function verifyBlob(planV2Dir, artifact) {
11205
11270
  return sourcePath;
11206
11271
  }
11207
11272
  function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
11208
- if (existsSync18(destinationDir)) {
11273
+ if (existsSync19(destinationDir)) {
11209
11274
  throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
11210
11275
  }
11211
11276
  const manifest = readPlanV2Manifest(planV2Dir);
@@ -11270,7 +11335,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
11270
11335
  }
11271
11336
  function readValidatedPlanV2Materialization(planDir, expectedTarget) {
11272
11337
  const markerPath = join27(planDir, PLAN_V2_MATERIALIZATION_MARKER);
11273
- if (!existsSync18(markerPath)) return null;
11338
+ if (!existsSync19(markerPath)) return null;
11274
11339
  const marker = readJsonFile(markerPath, "materialization marker");
11275
11340
  if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
11276
11341
  throw new PlanV2IntegrityError("malformed materialization marker");
@@ -11288,7 +11353,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
11288
11353
  for (const artifact of required) {
11289
11354
  if (Object.hasOwn(remoteSources, artifact.path)) continue;
11290
11355
  const path = join27(planDir, ...artifact.path.split("/"));
11291
- if (!existsSync18(path) || statSync7(path).size !== artifact.sizeBytes) {
11356
+ if (!existsSync19(path) || statSync7(path).size !== artifact.sizeBytes) {
11292
11357
  throw new PlanV2IntegrityError(
11293
11358
  `materialized artifact missing or truncated: ${artifact.path}`
11294
11359
  );
@@ -11312,10 +11377,10 @@ import {
11312
11377
  } from "@hyperframes/engine";
11313
11378
  import {
11314
11379
  cpSync as cpSync3,
11315
- existsSync as existsSync19,
11380
+ existsSync as existsSync20,
11316
11381
  mkdirSync as mkdirSync13,
11317
11382
  mkdtempSync as mkdtempSync5,
11318
- readFileSync as readFileSync9,
11383
+ readFileSync as readFileSync10,
11319
11384
  readdirSync as readdirSync8,
11320
11385
  rmSync as rmSync10,
11321
11386
  statSync as statSync8,
@@ -11367,7 +11432,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11367
11432
  }
11368
11433
  const format = plan.dimensions.format;
11369
11434
  const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
11370
- if (!existsSync19(dirname14(outputPath))) {
11435
+ if (!existsSync20(dirname14(outputPath))) {
11371
11436
  mkdirSync13(dirname14(outputPath), { recursive: true });
11372
11437
  }
11373
11438
  const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
@@ -11391,7 +11456,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11391
11456
  try {
11392
11457
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
11393
11458
  const finalPath = join28(workDir, `output.${format}`);
11394
- const audio = audioPath !== null && existsSync19(audioPath) ? audioPath : null;
11459
+ const audio = audioPath !== null && existsSync20(audioPath) ? audioPath : null;
11395
11460
  await writeJoined(ctx, videoSources, audio, finalPath);
11396
11461
  renameWithRetry11(finalPath, outputPath);
11397
11462
  log.info("[assemble] stage timings", {
@@ -11409,7 +11474,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11409
11474
  });
11410
11475
  }
11411
11476
  }
11412
- const fileSize = existsSync19(outputPath) ? statSync8(outputPath).size : 0;
11477
+ const fileSize = existsSync20(outputPath) ? statSync8(outputPath).size : 0;
11413
11478
  return {
11414
11479
  ...referenceBackgroundResult6(plan[REFERENCE_BACKGROUND_FIELD4]),
11415
11480
  outputPath,
@@ -11421,16 +11486,16 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11421
11486
  function readAssemblePlan(planDir, chunkPaths) {
11422
11487
  const planJsonPath = join28(planDir, "plan.json");
11423
11488
  const chunksJsonPath = join28(planDir, "meta", "chunks.json");
11424
- if (!existsSync19(planJsonPath)) {
11489
+ if (!existsSync20(planJsonPath)) {
11425
11490
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
11426
11491
  }
11427
- const plan = JSON.parse(readFileSync9(planJsonPath, "utf-8"));
11492
+ const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
11428
11493
  readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
11429
11494
  validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
11430
- if (!existsSync19(chunksJsonPath)) {
11495
+ if (!existsSync20(chunksJsonPath)) {
11431
11496
  throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
11432
11497
  }
11433
- const chunks = JSON.parse(readFileSync9(chunksJsonPath, "utf-8"));
11498
+ const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
11434
11499
  if (chunkPaths.length !== chunks.length) {
11435
11500
  throw new Error(
11436
11501
  `[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
@@ -11442,7 +11507,7 @@ function readAssemblePlan(planDir, chunkPaths) {
11442
11507
  }
11443
11508
  }
11444
11509
  for (const path of chunkPaths.flat()) {
11445
- if (!existsSync19(path)) {
11510
+ if (!existsSync20(path)) {
11446
11511
  throw new Error(`[assemble] chunk path does not exist: ${path}`);
11447
11512
  }
11448
11513
  }
@@ -11576,13 +11641,13 @@ function stitchesSeparateAudio(planDir) {
11576
11641
  return lockedAudio(planDir).separateAudio === true;
11577
11642
  }
11578
11643
  function planFormat(planDir) {
11579
- const plan = JSON.parse(readFileSync9(join28(planDir, "plan.json"), "utf-8"));
11644
+ const plan = JSON.parse(readFileSync10(join28(planDir, "plan.json"), "utf-8"));
11580
11645
  return plan.dimensions.format;
11581
11646
  }
11582
11647
  function lockedAudio(planDir) {
11583
11648
  const encoderJsonPath = join28(planDir, "meta", "encoder.json");
11584
- if (!existsSync19(encoderJsonPath)) return {};
11585
- return JSON.parse(readFileSync9(encoderJsonPath, "utf-8"));
11649
+ if (!existsSync20(encoderJsonPath)) return {};
11650
+ return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
11586
11651
  }
11587
11652
  function readCfrEncoder(planDir, format) {
11588
11653
  if (format !== "mp4") {
@@ -11591,10 +11656,10 @@ function readCfrEncoder(planDir, format) {
11591
11656
  );
11592
11657
  }
11593
11658
  const encoderJsonPath = join28(planDir, "meta", "encoder.json");
11594
- if (!existsSync19(encoderJsonPath)) {
11659
+ if (!existsSync20(encoderJsonPath)) {
11595
11660
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
11596
11661
  }
11597
- const encoderJson = JSON.parse(readFileSync9(encoderJsonPath, "utf-8"));
11662
+ const encoderJson = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
11598
11663
  if (encoderJson.encoder === "libx265-software") {
11599
11664
  throw new Error(
11600
11665
  `[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".`
@@ -11603,7 +11668,7 @@ function readCfrEncoder(planDir, format) {
11603
11668
  return encoderJson;
11604
11669
  }
11605
11670
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
11606
- if (existsSync19(outputPath)) rmSync10(outputPath, { recursive: true, force: true });
11671
+ if (existsSync20(outputPath)) rmSync10(outputPath, { recursive: true, force: true });
11607
11672
  mkdirSync13(outputPath, { recursive: true });
11608
11673
  let globalIdx = 0;
11609
11674
  for (const chunkDir of chunkPaths) {
@@ -11627,7 +11692,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11627
11692
  `[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
11628
11693
  );
11629
11694
  }
11630
- if (audioPath !== null && existsSync19(audioPath)) {
11695
+ if (audioPath !== null && existsSync20(audioPath)) {
11631
11696
  const sidecar = join28(outputPath, MIXED_AUDIO_FILENAME4);
11632
11697
  cpSync3(audioPath, sidecar);
11633
11698
  }
@@ -11648,7 +11713,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11648
11713
 
11649
11714
  // src/services/distributed/renderChunk.ts
11650
11715
  import { randomBytes } from "node:crypto";
11651
- import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync10, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
11716
+ import { existsSync as existsSync21, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
11652
11717
  import { cpus } from "node:os";
11653
11718
  import { join as join29 } from "node:path";
11654
11719
  import {
@@ -11771,10 +11836,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
11771
11836
  return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
11772
11837
  }
11773
11838
  function hashChunkOutput(outputPath, kind) {
11774
- if (kind === "file") return sha256Hex(readFileSync10(outputPath));
11839
+ if (kind === "file") return sha256Hex(readFileSync11(outputPath));
11775
11840
  const entries = readdirSync9(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
11776
11841
  const lines = entries.map(
11777
- (name) => `${name}\0${sha256Hex(readFileSync10(join29(outputPath, name)))}`
11842
+ (name) => `${name}\0${sha256Hex(readFileSync11(join29(outputPath, name)))}`
11778
11843
  );
11779
11844
  return sha256Hex(lines.join("\0"));
11780
11845
  }
@@ -11794,13 +11859,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11794
11859
  const planJsonPath = join29(planDir, "plan.json");
11795
11860
  const encoderJsonPath = join29(planDir, "meta", "encoder.json");
11796
11861
  const chunksJsonPath = join29(planDir, "meta", "chunks.json");
11797
- if (!existsSync20(planJsonPath)) {
11862
+ if (!existsSync21(planJsonPath)) {
11798
11863
  throw new RenderChunkValidationError(
11799
11864
  MISSING_PLAN_ARTIFACT,
11800
11865
  `[renderChunk] planDir is missing required artifact: ${planJsonPath}`
11801
11866
  );
11802
11867
  }
11803
- const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
11868
+ const plan = JSON.parse(readFileSync11(planJsonPath, "utf-8"));
11804
11869
  readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
11805
11870
  const planHashStarted = Date.now();
11806
11871
  const v2Materialization = readValidatedPlanV2Materialization(planDir, {
@@ -11810,20 +11875,20 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11810
11875
  const v2Manifest = v2Materialization?.manifest ?? null;
11811
11876
  const remoteSources = v2Materialization?.remoteSources ?? {};
11812
11877
  for (const required of [encoderJsonPath, chunksJsonPath]) {
11813
- if (!existsSync20(required)) {
11878
+ if (!existsSync21(required)) {
11814
11879
  throw new RenderChunkValidationError(
11815
11880
  MISSING_PLAN_ARTIFACT,
11816
11881
  `[renderChunk] planDir is missing required artifact: ${required}`
11817
11882
  );
11818
11883
  }
11819
11884
  }
11820
- const encoder = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
11821
- const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
11885
+ const encoder = JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
11886
+ const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
11822
11887
  const videosJsonPath = join29(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
11823
11888
  let planVideos = null;
11824
- if (existsSync20(videosJsonPath)) {
11889
+ if (existsSync21(videosJsonPath)) {
11825
11890
  try {
11826
- planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync10(videosJsonPath, "utf-8")));
11891
+ planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync11(videosJsonPath, "utf-8")));
11827
11892
  } catch (err) {
11828
11893
  if (err instanceof RenderChunkValidationError) throw err;
11829
11894
  throw new RenderChunkValidationError(
@@ -11852,7 +11917,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11852
11917
  const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
11853
11918
  const framesInChunk = range.endFrame - range.startFrame;
11854
11919
  const compiledDir = join29(planDir, "compiled");
11855
- if (!existsSync20(compiledDir)) {
11920
+ if (!existsSync21(compiledDir)) {
11856
11921
  throw new RenderChunkValidationError(
11857
11922
  MISSING_PLAN_ARTIFACT,
11858
11923
  `[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
@@ -11934,7 +11999,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11934
11999
  dimensions: plan.dimensions,
11935
12000
  encoder,
11936
12001
  videoCount: planVideos?.videos.length ?? 0,
11937
- html: readFileSync10(join29(compiledDir, "index.html"), "utf-8"),
12002
+ html: readFileSync11(join29(compiledDir, "index.html"), "utf-8"),
11938
12003
  // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
11939
12004
  skipReadinessVideoIds: deferredVideos.filter(
11940
12005
  (video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
@@ -12102,10 +12167,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
12102
12167
  const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
12103
12168
  const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
12104
12169
  if (isPngSequence) {
12105
- if (!existsSync20(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
12170
+ if (!existsSync21(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
12106
12171
  } else {
12107
12172
  const outDir = join29(outputChunkPath, "..");
12108
- if (!existsSync20(outDir)) mkdirSync14(outDir, { recursive: true });
12173
+ if (!existsSync21(outDir)) mkdirSync14(outDir, { recursive: true });
12109
12174
  }
12110
12175
  const encodeInput = {
12111
12176
  job,