@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.
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  // src/services/renderOrchestrator.ts
10
10
  import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
11
11
  import {
12
- existsSync as existsSync18,
12
+ existsSync as existsSync19,
13
13
  mkdirSync as mkdirSync10,
14
14
  readdirSync as readdirSync5,
15
15
  rmSync as rmSync10,
@@ -3741,6 +3741,7 @@ function createStallClock(now = () => Date.now()) {
3741
3741
 
3742
3742
  // src/services/render/stages/captureStreamingStage.ts
3743
3743
  var DEFAULT_CAPTURE_STALL_MS = 6e4;
3744
+ var SESSION_INIT_STALL_MS = 3e5;
3744
3745
  var DE_STALL_POLL_MS = 5e3;
3745
3746
  var STALL_ADVICE = "Chrome stopped answering. If another app is keeping the GPU or CPU busy (a 3D render, a game, another export), let it finish or render one job at a time, then retry.";
3746
3747
  function poolFailure(error, probeSession) {
@@ -3771,6 +3772,14 @@ function captureModeLabel(mode) {
3771
3772
  if (mode === "beginframe") return "BeginFrame";
3772
3773
  return "screenshot";
3773
3774
  }
3775
+ var OPENING_CAPTURE_SESSION = {
3776
+ captureMode: "screenshot",
3777
+ captureStep: "opening the capture session"
3778
+ };
3779
+ var OPENING_RISK_SESSION = {
3780
+ captureMode: "screenshot",
3781
+ captureStep: "opening the screenshot session for effect frames"
3782
+ };
3774
3783
  var SequentialCaptureStallError = class extends Error {
3775
3784
  captureMode;
3776
3785
  constructor(session, stallTimeoutMs, frameIndex, totalFrames) {
@@ -3920,7 +3929,12 @@ async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEnc
3920
3929
  const guard = createDrainFrameGuard({ log, stats, frameTime });
3921
3930
  const guardFrame = (item, buf) => item.trusted ? Promise.resolve(buf) : guard(session, item.idx, buf);
3922
3931
  const captureRisk = async (idx) => {
3923
- await risk.open();
3932
+ await raceAgainstStall(
3933
+ risk.open(),
3934
+ stallTimeoutMs - quiet.idle(0),
3935
+ { session: OPENING_RISK_SESSION, frameIndex: idx, totalFrames, stallTimeoutMs },
3936
+ abortSignal
3937
+ );
3924
3938
  quiet.reset();
3925
3939
  return raceAgainstStall(
3926
3940
  risk.capture(idx, frameTime(idx)),
@@ -4279,19 +4293,38 @@ async function runCaptureStreamingStage(input) {
4279
4293
  let resumes = 0;
4280
4294
  for (; ; ) {
4281
4295
  const videoInjector = createRenderVideoFrameInjector();
4282
- const session = probeSession ?? await createCaptureSession(
4283
- fileServer.url,
4284
- framesDir,
4285
- buildCaptureOptions(),
4286
- videoInjector,
4287
- captureCfg
4296
+ const initStallMs = Math.max(SESSION_INIT_STALL_MS, resolveCaptureStallTimeoutMs());
4297
+ const setup = createStallClock();
4298
+ const withinInitBudget = (work, stalled) => raceAgainstStall(
4299
+ work,
4300
+ initStallMs - setup.idle(0),
4301
+ { session: stalled, frameIndex: resume.from, totalFrames, stallTimeoutMs: initStallMs },
4302
+ abortSignal
4288
4303
  );
4304
+ const openSession = async () => {
4305
+ const opening = createCaptureSession(
4306
+ fileServer.url,
4307
+ framesDir,
4308
+ buildCaptureOptions(),
4309
+ videoInjector,
4310
+ captureCfg
4311
+ );
4312
+ try {
4313
+ return await withinInitBudget(opening, OPENING_CAPTURE_SESSION);
4314
+ } catch (error) {
4315
+ void opening.then(closeCaptureSession, () => {
4316
+ });
4317
+ throw error;
4318
+ }
4319
+ };
4320
+ const session = probeSession ?? await openSession();
4289
4321
  captureBeyondViewport = session.options.captureBeyondViewport;
4290
4322
  if (probeSession) {
4291
4323
  prepareCaptureSessionForReuse(session, framesDir, videoInjector);
4292
4324
  probeSession = null;
4293
4325
  }
4294
4326
  let riskSession = null;
4327
+ let attemptClosed = false;
4295
4328
  const openRiskSession = async () => {
4296
4329
  const opened = await createCaptureSession(
4297
4330
  fileServer.url,
@@ -4305,6 +4338,10 @@ async function runCaptureStreamingStage(input) {
4305
4338
  enableBrowserPool: false
4306
4339
  }
4307
4340
  );
4341
+ if (attemptClosed) {
4342
+ await closeCaptureSession(opened);
4343
+ throw new Error("[Render] Capture attempt ended while its screenshot session opened");
4344
+ }
4308
4345
  riskSession = opened;
4309
4346
  await initializeSession(opened);
4310
4347
  return opened;
@@ -4312,9 +4349,9 @@ async function runCaptureStreamingStage(input) {
4312
4349
  let resumed = false;
4313
4350
  try {
4314
4351
  if (!session.isInitialized) {
4315
- await initializeSession(session);
4352
+ await withinInitBudget(initializeSession(session), session);
4316
4353
  }
4317
- await completeDeferredDrawElementInit(session);
4354
+ await withinInitBudget(completeDeferredDrawElementInit(session), session);
4318
4355
  assertNotAborted();
4319
4356
  lastBrowserConsole = session.browserConsoleBuffer;
4320
4357
  if (session.workerEncodeEnabled) {
@@ -4397,6 +4434,7 @@ async function runCaptureStreamingStage(input) {
4397
4434
  resumes++;
4398
4435
  resumed = true;
4399
4436
  } finally {
4437
+ attemptClosed = true;
4400
4438
  lastBrowserConsole = session.browserConsoleBuffer;
4401
4439
  await closeCaptureSession(session);
4402
4440
  if (riskSession) await closeCaptureSession(riskSession);
@@ -5863,7 +5901,7 @@ import { suggestMatchingPreset } from "@hyperframes/core";
5863
5901
  // src/services/htmlCompiler.ts
5864
5902
  import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
5865
5903
  import { createHash as createHash6 } from "node:crypto";
5866
- import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
5904
+ import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7 } from "fs";
5867
5905
  import { join as join15, dirname as dirname7, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
5868
5906
  import { parseHTML as parseHTML5 } from "linkedom";
5869
5907
  import {
@@ -7838,6 +7876,25 @@ ${fontFaceBlocks.join("\n")}
7838
7876
  }
7839
7877
  return result;
7840
7878
  }
7879
+ function projectStylesheetFontFamilies(html, projectDir) {
7880
+ const families = [];
7881
+ for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
7882
+ const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
7883
+ if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
7884
+ const sheet = resolve7(projectDir, decodedUrlPath(href).replace(/^(?:\.?\/)+/, ""));
7885
+ if (!isPathInside2(sheet, projectDir) || !existsSync10(sheet)) continue;
7886
+ for (const face of extractFontFaceBlocks(readFileSync7(sheet, "utf8"))) {
7887
+ const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
7888
+ const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
7889
+ const path = decodedUrlPath(url);
7890
+ const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(dirname7(sheet), path);
7891
+ return isPathInside2(file, projectDir) && existsSync10(file);
7892
+ });
7893
+ if (family && shipsFile) families.push(family);
7894
+ }
7895
+ }
7896
+ return families;
7897
+ }
7841
7898
  function collectFontFaceUrls(html) {
7842
7899
  const styleBlockRe = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
7843
7900
  const urlSet = /* @__PURE__ */ new Set();
@@ -7869,11 +7926,12 @@ function collectFontFaceUrls(html) {
7869
7926
  return urlSet;
7870
7927
  }
7871
7928
  async function localizeRemoteFontFaces(html, downloadDir) {
7872
- const processed = await inlineExternalFontStylesheets(html);
7873
- const urlSet = collectFontFaceUrls(processed);
7929
+ return localizeFontFaceUrls(await inlineExternalFontStylesheets(html), downloadDir);
7930
+ }
7931
+ function localizeFontFaceUrls(html, downloadDir) {
7874
7932
  return downloadAndRewriteUrls(
7875
- urlSet,
7876
- processed,
7933
+ collectFontFaceUrls(html),
7934
+ html,
7877
7935
  join15(downloadDir, REMOTE_MEDIA_SUBDIR),
7878
7936
  "Remote font download failed",
7879
7937
  "Localized remote font face(s)",
@@ -8041,15 +8099,17 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
8041
8099
  const hasShaderTransitions = detectShaderTransitionUsage(sanitizedHtml);
8042
8100
  const usesThreeDTransforms = detectThreeDTransformUsage(sanitizedHtml);
8043
8101
  const hasAncestorBackgroundImage = detectAncestorBackgroundImage(sanitizedHtml);
8102
+ const fontSheetHtml = await inlineExternalFontStylesheets(
8103
+ promoteCssImportsToLinkTags(sanitizedHtml)
8104
+ );
8044
8105
  const normalizedFontHtml = normalizeSystemFontPrimaryFamilies(
8045
- injectTextRenderingRule(
8046
- coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml))
8047
- )
8106
+ injectTextRenderingRule(coalesceHeadStylesAndBodyScripts(fontSheetHtml))
8048
8107
  );
8049
8108
  const coalescedHtml = await injectDeterministicFontFaces(normalizedFontHtml, {
8050
8109
  logger: defaultLogger,
8051
8110
  failClosedFontFetch: options.failClosedFontFetch === true,
8052
8111
  allowSystemFontCapture: options.allowSystemFontCapture,
8112
+ projectFontFamilies: projectStylesheetFontFamilies(fontSheetHtml, projectDir),
8053
8113
  abortSignal: options.abortSignal
8054
8114
  });
8055
8115
  const assembledHtml = await inlineExternalScripts(coalescedHtml);
@@ -8075,7 +8135,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
8075
8135
  );
8076
8136
  const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
8077
8137
  const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
8078
- const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeRemoteFontFaces(htmlWithLocalBg, downloadDir);
8138
+ const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeFontFaceUrls(htmlWithLocalBg, downloadDir);
8079
8139
  const {
8080
8140
  html: htmlWithJpegs,
8081
8141
  preparedAssets: preparedHeifAssets,
@@ -9926,6 +9986,7 @@ async function planVideoLayerPassthrough(input) {
9926
9986
  }
9927
9987
 
9928
9988
  // src/services/render/stages/audioStage.ts
9989
+ import { existsSync as existsSync14 } from "node:fs";
9929
9990
  import { join as join21 } from "node:path";
9930
9991
  import {
9931
9992
  MIXED_AUDIO_FILENAME,
@@ -10059,8 +10120,9 @@ var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
10059
10120
  var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
10060
10121
  var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
10061
10122
  var MAX_TRUE_PEAK_CORRECTION_PASSES = 3;
10123
+ var gainFilter = (gainDb) => `volume=${gainDb.toFixed(3)}dB`;
10062
10124
  function aacTruePeakCorrectionFilter(targetDurationSeconds, attenuationDb) {
10063
- return `volume=${attenuationDb.toFixed(3)}dB,apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
10125
+ return `${gainFilter(attenuationDb)},apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
10064
10126
  }
10065
10127
  function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
10066
10128
  const delta = targetDurationSeconds - sourceDurationSeconds;
@@ -10098,7 +10160,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
10098
10160
  const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
10099
10161
  const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
10100
10162
  const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
10101
- const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal);
10163
+ const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath);
10102
10164
  const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg || input.encodeAac ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
10103
10165
  const [videoResult, audioResult] = await Promise.allSettled([
10104
10166
  probeVideo(input.videoPath),
@@ -10146,7 +10208,12 @@ async function padOrTrimAudioToVideoFrameCount(input) {
10146
10208
  );
10147
10209
  try {
10148
10210
  for (const step of plan2.steps) {
10149
- const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(input.audioPath, input.outputPath, targetDurationSeconds, step.filter);
10211
+ const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
10212
+ input.sourcePath ?? input.audioPath,
10213
+ input.outputPath,
10214
+ targetDurationSeconds,
10215
+ step.filter
10216
+ );
10150
10217
  if (!ffmpegResult.success) {
10151
10218
  return {
10152
10219
  success: false,
@@ -10175,6 +10242,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
10175
10242
  if (probeTruePeak) {
10176
10243
  const correction = await enforceAacTruePeak({
10177
10244
  audioPath: input.outputPath,
10245
+ sourcePath: input.sourcePath,
10178
10246
  targetDurationSeconds,
10179
10247
  signal: input.signal,
10180
10248
  probeTruePeak,
@@ -10210,9 +10278,27 @@ async function constrainAacTruePeak(input) {
10210
10278
  targetDurationSeconds: input.targetDurationSeconds,
10211
10279
  signal: input.signal,
10212
10280
  probeTruePeak: input.probeTruePeakDbfs ?? ((audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal })),
10213
- encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
10281
+ encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath),
10282
+ rewriteSource: input.rewriteSource ?? ((sourcePath, outputPath, filter) => defaultRunFfmpeg(
10283
+ ["-i", sourcePath, "-af", filter, "-c:a", "pcm_f32le", "-y", outputPath],
10284
+ timeoutMs,
10285
+ input.signal
10286
+ ))
10214
10287
  });
10215
10288
  }
10289
+ async function cutSource(input, scratchDir, attenuationDb) {
10290
+ if (!input.sourcePath || !input.rewriteSource) return true;
10291
+ const cutPath = join19(scratchDir, "source.wav");
10292
+ try {
10293
+ const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
10294
+ if (!result.success) throw new Error(result.error);
10295
+ renameWithRetry5(cutPath, input.sourcePath, { copyFallback: true });
10296
+ return true;
10297
+ } catch {
10298
+ removeScratch(input.sourcePath);
10299
+ return false;
10300
+ }
10301
+ }
10216
10302
  async function enforceAacTruePeak(input) {
10217
10303
  let scratchDir;
10218
10304
  try {
@@ -10232,8 +10318,10 @@ async function enforceAacTruePeak(input) {
10232
10318
  }
10233
10319
  if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
10234
10320
  if (measuredPath !== correctedPath) return { success: true };
10321
+ const sourceCut = await cutSource(input, scratchDir, attenuationDb);
10235
10322
  renameWithRetry5(correctedPath, input.audioPath, { copyFallback: true });
10236
- return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
10323
+ const loweredDb = Number((-attenuationDb).toFixed(2));
10324
+ return sourceCut ? { success: true, loweredDb } : { success: true, loweredDb, sourceStale: true };
10237
10325
  }
10238
10326
  if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
10239
10327
  const headroomDb = AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB * 2 ** pass;
@@ -10243,7 +10331,7 @@ async function enforceAacTruePeak(input) {
10243
10331
  const referenceDbfs = Math.max(truePeakDbfs, (sourcePeakDbfs ?? -Infinity) + attenuationDb);
10244
10332
  attenuationDb += AAC_DELIVERY_TRUE_PEAK_DBFS - headroomDb - referenceDbfs;
10245
10333
  const result = await input.encode(
10246
- input.audioPath,
10334
+ input.sourcePath ?? input.audioPath,
10247
10335
  correctedPath,
10248
10336
  input.targetDurationSeconds,
10249
10337
  aacTruePeakCorrectionFilter(input.targetDurationSeconds, attenuationDb)
@@ -10472,36 +10560,54 @@ async function runAudioStageScoped(input) {
10472
10560
  const stageStart = Date.now();
10473
10561
  const pcmSourcePath = join21(input.workDir, "audio-source.wav");
10474
10562
  const duration = input.totalFrames * input.fps.den / input.fps.num;
10563
+ let deliverySourcePath;
10475
10564
  try {
10476
10565
  const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
10477
10566
  let audioLoweredDb;
10478
10567
  if (result.hasAudio) {
10479
10568
  const guardStart = Date.now();
10480
- const limited = await limitDeliveryPeaks({
10481
- audioPath: result.audioOutputPath,
10482
- sourcePath: pcmSourcePath,
10483
- targetDurationSeconds: duration,
10484
- signal: input.abortSignal,
10485
- timeoutMs: input.ffmpegProcessTimeout,
10486
- warn: (message, details) => input.log?.warn?.(message, details)
10487
- });
10488
- if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
10489
- const peak = await constrainAacTruePeak({
10490
- audioPath: result.audioOutputPath,
10491
- sourcePath: pcmSourcePath,
10492
- targetDurationSeconds: duration,
10493
- signal: input.abortSignal,
10494
- timeoutMs: input.ffmpegProcessTimeout
10495
- });
10496
- if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
10497
- audioLoweredDb = peak.loweredDb;
10569
+ const guard = await guardDeliveryPeaks(
10570
+ input,
10571
+ result.audioOutputPath,
10572
+ pcmSourcePath,
10573
+ duration
10574
+ );
10575
+ audioLoweredDb = guard.loweredDb;
10498
10576
  input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
10577
+ if (input.keepDeliverySource && !guard.sourceStale && existsSync14(pcmSourcePath)) {
10578
+ deliverySourcePath = pcmSourcePath;
10579
+ }
10499
10580
  }
10500
- return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
10581
+ return {
10582
+ ...result,
10583
+ audioLoweredDb,
10584
+ deliverySourcePath,
10585
+ audioProcessMs: Date.now() - stageStart
10586
+ };
10501
10587
  } finally {
10502
- removeScratch2(pcmSourcePath);
10588
+ if (!deliverySourcePath) removeScratch2(pcmSourcePath);
10503
10589
  }
10504
10590
  }
10591
+ async function guardDeliveryPeaks(input, audioPath, sourcePath, duration) {
10592
+ const limited = await limitDeliveryPeaks({
10593
+ audioPath,
10594
+ sourcePath,
10595
+ targetDurationSeconds: duration,
10596
+ signal: input.abortSignal,
10597
+ timeoutMs: input.ffmpegProcessTimeout,
10598
+ warn: (message, details) => input.log?.warn?.(message, details)
10599
+ });
10600
+ if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
10601
+ const peak = await constrainAacTruePeak({
10602
+ audioPath,
10603
+ sourcePath,
10604
+ targetDurationSeconds: duration,
10605
+ signal: input.abortSignal,
10606
+ timeoutMs: input.ffmpegProcessTimeout
10607
+ });
10608
+ if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
10609
+ return { loweredDb: peak.loweredDb, sourceStale: peak.sourceStale };
10610
+ }
10505
10611
  async function mixAudioForDelivery(input, duration, pcmSourcePath) {
10506
10612
  const {
10507
10613
  projectDir,
@@ -10596,7 +10702,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
10596
10702
  }
10597
10703
 
10598
10704
  // src/services/render/stages/captureStage.ts
10599
- import { existsSync as existsSync14, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
10705
+ import { existsSync as existsSync15, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
10600
10706
  import { join as join22 } from "node:path";
10601
10707
  import {
10602
10708
  classifyCaptureFailure as classifyCaptureFailure2,
@@ -10667,7 +10773,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
10667
10773
  }
10668
10774
  function measureCaptureFrameBytes(workDir, framesDir) {
10669
10775
  let total = sampleDirectoryBytes(framesDir);
10670
- if (!existsSync14(workDir)) return total;
10776
+ if (!existsSync15(workDir)) return total;
10671
10777
  for (const attempt of readdirSync3(workDir)) {
10672
10778
  if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
10673
10779
  total += sampleDirectoryBytes(join22(workDir, attempt));
@@ -10970,7 +11076,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
10970
11076
  }
10971
11077
 
10972
11078
  // src/services/render/stages/captureHdrStage.ts
10973
- import { existsSync as existsSync16, mkdirSync as mkdirSync8 } from "node:fs";
11079
+ import { existsSync as existsSync17, mkdirSync as mkdirSync8 } from "node:fs";
10974
11080
  import { join as join26 } from "node:path";
10975
11081
  import {
10976
11082
  closeCaptureSession as closeCaptureSession7,
@@ -11216,7 +11322,7 @@ import { Worker } from "node:worker_threads";
11216
11322
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
11217
11323
  import { dirname as dirname10, join as join24 } from "node:path";
11218
11324
  import { createRequire } from "node:module";
11219
- import { existsSync as existsSync15 } from "node:fs";
11325
+ import { existsSync as existsSync16 } from "node:fs";
11220
11326
  import { cpus as cpus2 } from "node:os";
11221
11327
  function resolveWorkerEntry(explicit) {
11222
11328
  if (explicit && explicit.length > 0) {
@@ -11229,7 +11335,7 @@ function resolveWorkerEntry(explicit) {
11229
11335
  }
11230
11336
  const moduleDir = dirname10(fileURLToPath3(import.meta.url));
11231
11337
  const jsPath = join24(moduleDir, "shaderTransitionWorker.js");
11232
- if (existsSync15(jsPath)) return { path: jsPath, isTs: false };
11338
+ if (existsSync16(jsPath)) return { path: jsPath, isTs: false };
11233
11339
  const tsPath = join24(moduleDir, "shaderTransitionWorker.ts");
11234
11340
  return { path: tsPath, isTs: true };
11235
11341
  }
@@ -11790,7 +11896,7 @@ async function runCaptureHdrStage(input) {
11790
11896
  }
11791
11897
  const debugDumpEnabled = process.env.KEEP_TEMP === "1";
11792
11898
  const debugDumpDir = debugDumpEnabled ? join26(framesDir, "debug-composite") : null;
11793
- if (debugDumpDir && !existsSync16(debugDumpDir)) {
11899
+ if (debugDumpDir && !existsSync17(debugDumpDir)) {
11794
11900
  mkdirSync8(debugDumpDir, { recursive: true });
11795
11901
  }
11796
11902
  const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
@@ -11947,10 +12053,10 @@ async function runCaptureHdrStage(input) {
11947
12053
  // src/services/render/stages/encodeStage.ts
11948
12054
  import {
11949
12055
  copyFileSync as copyFileSync4,
11950
- existsSync as existsSync17,
12056
+ existsSync as existsSync18,
11951
12057
  mkdirSync as mkdirSync9,
11952
12058
  readdirSync as readdirSync4,
11953
- readFileSync as readFileSync7,
12059
+ readFileSync as readFileSync8,
11954
12060
  rmSync as rmSync9,
11955
12061
  statSync as statSync7,
11956
12062
  writeFileSync as writeFileSync5
@@ -12044,7 +12150,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
12044
12150
  const error = formatFfmpegError(result.exitCode, result.stderr);
12045
12151
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
12046
12152
  }
12047
- const gif = readFileSync7(outputPath);
12153
+ const gif = readFileSync8(outputPath);
12048
12154
  if (!clearGifFramesBeforeNext(gif)) {
12049
12155
  const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
12050
12156
  return gifEncodeFailure(startTime, outputPath, error);
@@ -12077,11 +12183,11 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
12077
12183
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
12078
12184
  }
12079
12185
  }
12080
- if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync7(outputPath)) !== false) {
12186
+ if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync8(outputPath)) !== false) {
12081
12187
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
12082
12188
  if (failure) return failure;
12083
12189
  }
12084
- const fileSize = existsSync17(outputPath) ? statSync7(outputPath).size : 0;
12190
+ const fileSize = existsSync18(outputPath) ? statSync7(outputPath).size : 0;
12085
12191
  return {
12086
12192
  success: true,
12087
12193
  outputPath,
@@ -12242,7 +12348,7 @@ async function runEncodeStage(input) {
12242
12348
  const stage5Start = Date.now();
12243
12349
  if (isPngSequence) {
12244
12350
  const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
12245
- if (!existsSync17(outputPath)) mkdirSync9(outputPath, { recursive: true });
12351
+ if (!existsSync18(outputPath)) mkdirSync9(outputPath, { recursive: true });
12246
12352
  const captured = readdirSync4(framesDir).filter((name) => name.endsWith(".png")).sort();
12247
12353
  if (captured.length === 0) {
12248
12354
  throw new Error(
@@ -12253,7 +12359,7 @@ async function runEncodeStage(input) {
12253
12359
  const dst = join27(outputPath, formatExportFrameName(i, "png"));
12254
12360
  copyFileSync4(join27(framesDir, name), dst);
12255
12361
  });
12256
- if (hasAudio && audioOutputPath && existsSync17(audioOutputPath)) {
12362
+ if (hasAudio && audioOutputPath && existsSync18(audioOutputPath)) {
12257
12363
  copyFileSync4(audioOutputPath, join27(outputPath, MIXED_AUDIO_FILENAME2));
12258
12364
  log.info(
12259
12365
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
@@ -12364,6 +12470,7 @@ async function assembleOutput(input, onSecondsWritten) {
12364
12470
  const normalizeResult = await padOrTrimAudioToVideoFrameCount({
12365
12471
  videoPath: videoOnlyPath,
12366
12472
  audioPath: audioOutputPath,
12473
+ sourcePath: input.audioPcmPath,
12367
12474
  outputPath: normalizedAudioPath,
12368
12475
  signal: abortSignal,
12369
12476
  timeoutMs: ffmpegProcessTimeout
@@ -12645,7 +12752,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
12645
12752
  let rangeStart = null;
12646
12753
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
12647
12754
  const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
12648
- const missing = !existsSync18(framePath) || statSync8(framePath).size <= 8;
12755
+ const missing = !existsSync19(framePath) || statSync8(framePath).size <= 8;
12649
12756
  if (missing && rangeStart === null) {
12650
12757
  rangeStart = frameIndex;
12651
12758
  } else if (!missing && rangeStart !== null) {
@@ -12770,7 +12877,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
12770
12877
  let captured = 0;
12771
12878
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
12772
12879
  const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
12773
- if (existsSync18(framePath)) captured++;
12880
+ if (existsSync19(framePath)) captured++;
12774
12881
  }
12775
12882
  return captured;
12776
12883
  }
@@ -13435,7 +13542,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
13435
13542
  validateDeliveryEncoderConfig(job.config);
13436
13543
  const debugDir = resolveRenderDebugDir();
13437
13544
  const outputDir = dirname12(outputPath);
13438
- if (!existsSync18(outputDir)) mkdirSync10(outputDir, { recursive: true });
13545
+ if (!existsSync19(outputDir)) mkdirSync10(outputDir, { recursive: true });
13439
13546
  const workDir = job.config.debug ? join28(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
13440
13547
  const pipelineStart = Date.now();
13441
13548
  const baseLog = job.config.logger ?? defaultLogger;
@@ -13577,7 +13684,7 @@ async function executeRenderPipeline(input) {
13577
13684
  job.startedAt = /* @__PURE__ */ new Date();
13578
13685
  assertNotAborted();
13579
13686
  assertConfiguredFfmpegBinariesExist();
13580
- if (!existsSync18(workDir)) mkdirSync10(workDir, { recursive: true });
13687
+ if (!existsSync19(workDir)) mkdirSync10(workDir, { recursive: true });
13581
13688
  if (job.config.debug) {
13582
13689
  log.info("[Render] Debug artifacts enabled", { workDir, logPath });
13583
13690
  }
@@ -13607,7 +13714,7 @@ async function executeRenderPipeline(input) {
13607
13714
  });
13608
13715
  const entryFile = job.config.entryFile || "index.html";
13609
13716
  let htmlPath = join28(projectDir, entryFile);
13610
- if (!existsSync18(htmlPath)) {
13717
+ if (!existsSync19(htmlPath)) {
13611
13718
  throw new Error(`Entry file not found: ${htmlPath}`);
13612
13719
  }
13613
13720
  assertNotAborted();
@@ -13615,7 +13722,7 @@ async function executeRenderPipeline(input) {
13615
13722
  if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
13616
13723
  const wrapperPath = join28(workDir, "standalone-entry.html");
13617
13724
  const projectIndexPath = join28(projectDir, "index.html");
13618
- if (!existsSync18(projectIndexPath)) {
13725
+ if (!existsSync19(projectIndexPath)) {
13619
13726
  throw new Error(
13620
13727
  `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
13621
13728
  );
@@ -13926,7 +14033,8 @@ async function executeRenderPipeline(input) {
13926
14033
  masterFxChain: composition.masterFxChain,
13927
14034
  abortSignal: executionSignal,
13928
14035
  assertNotAborted,
13929
- log
14036
+ log,
14037
+ keepDeliverySource: true
13930
14038
  })
13931
14039
  );
13932
14040
  pendingAudio = audioStage.catch(() => void 0);
@@ -13958,7 +14066,7 @@ async function executeRenderPipeline(input) {
13958
14066
  throw new Error("File server failed to initialize before frame capture");
13959
14067
  }
13960
14068
  const framesDir = join28(workDir, "captured-frames");
13961
- if (!existsSync18(framesDir)) mkdirSync10(framesDir, { recursive: true });
14069
+ if (!existsSync19(framesDir)) mkdirSync10(framesDir, { recursive: true });
13962
14070
  updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
13963
14071
  code: "start_browsers"
13964
14072
  });
@@ -14634,7 +14742,7 @@ async function executeRenderPipeline(input) {
14634
14742
  const effectiveQuality = job.config.crf ?? preset.quality;
14635
14743
  const effectiveBitrate = job.config.crf != null ? void 0 : job.config.videoBitrate;
14636
14744
  const audioResult = await audioStage;
14637
- const { audioOutputPath, hasAudio } = audioResult;
14745
+ const { audioOutputPath, deliverySourcePath, hasAudio } = audioResult;
14638
14746
  perfStages.audioProcessMs = audioResult.audioProcessMs;
14639
14747
  recordLimiterAttenuation(job, audioResult.audioLoweredDb);
14640
14748
  if (audioResult.audioError) {
@@ -15260,6 +15368,7 @@ async function executeRenderPipeline(input) {
15260
15368
  job,
15261
15369
  videoOnlyPath,
15262
15370
  audioOutputPath,
15371
+ audioPcmPath: deliverySourcePath,
15263
15372
  outputPath: stagedOutputPath,
15264
15373
  hasAudio,
15265
15374
  format: outputFormat,
@@ -15285,7 +15394,7 @@ async function executeRenderPipeline(input) {
15285
15394
  })
15286
15395
  );
15287
15396
  const totalElapsed = Date.now() - pipelineStart;
15288
- const tmpPeakBytes = existsSync18(workDir) ? sampleDirectoryBytes(workDir) : 0;
15397
+ const tmpPeakBytes = existsSync19(workDir) ? sampleDirectoryBytes(workDir) : 0;
15289
15398
  recordTransientRetryObservability();
15290
15399
  observability.checkpoint("pipeline", "artifact validated", { totalElapsedMs: totalElapsed });
15291
15400
  const observabilitySummary = observability.summary({
@@ -15366,7 +15475,7 @@ async function executeRenderPipeline(input) {
15366
15475
  }
15367
15476
  }
15368
15477
  if (job.config.debug) {
15369
- if (!isPngSequence && !isHls && existsSync18(stagedOutputPath)) {
15478
+ if (!isPngSequence && !isHls && existsSync19(stagedOutputPath)) {
15370
15479
  const debugOutput = join28(workDir, `output${videoExt}`);
15371
15480
  copyFileSync5(stagedOutputPath, debugOutput);
15372
15481
  }
@@ -15481,7 +15590,7 @@ import {
15481
15590
  REFERENCE_BACKGROUND_FIELD as REFERENCE_BACKGROUND_FIELD4,
15482
15591
  referenceBackgroundResult as referenceBackgroundResult6
15483
15592
  } from "@hyperframes/engine";
15484
- import { mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync7, readFileSync as readFileSync13, rmSync as rmSync16 } from "node:fs";
15593
+ import { mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync7, readFileSync as readFileSync14, rmSync as rmSync16 } from "node:fs";
15485
15594
  import { join as join42 } from "node:path";
15486
15595
  import { tmpdir as tmpdir4 } from "node:os";
15487
15596
 
@@ -15878,7 +15987,7 @@ import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframe
15878
15987
  import {
15879
15988
  copyFileSync as copyFileSync6,
15880
15989
  cpSync as cpSync2,
15881
- existsSync as existsSync23,
15990
+ existsSync as existsSync24,
15882
15991
  linkSync,
15883
15992
  mkdirSync as mkdirSync14,
15884
15993
  rmSync as rmSync12,
@@ -15909,7 +16018,7 @@ import {
15909
16018
  } from "@hyperframes/engine";
15910
16019
 
15911
16020
  // src/services/distributed/chunkFrames.ts
15912
- import { existsSync as existsSync20, readdirSync as readdirSync6 } from "node:fs";
16021
+ import { existsSync as existsSync21, readdirSync as readdirSync6 } from "node:fs";
15913
16022
  import { extname as extname4, join as join31 } from "node:path";
15914
16023
  import {
15915
16024
  compositionRequiresWebGpu as compositionRequiresWebGpu3,
@@ -15921,7 +16030,7 @@ import {
15921
16030
  // src/services/distributed/shared.ts
15922
16031
  import { execFile as execFileCallback } from "node:child_process";
15923
16032
  import { dirname as dirname14, join as join30 } from "node:path";
15924
- import { existsSync as existsSync19, readFileSync as readFileSync8 } from "node:fs";
16033
+ import { existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
15925
16034
  import { fileURLToPath as fileURLToPath5 } from "node:url";
15926
16035
  import { promisify as promisify2 } from "node:util";
15927
16036
  import {
@@ -15937,7 +16046,7 @@ function isPlanAudioArtifactPath(path) {
15937
16046
  function resolvePlanAudioPath(planDir) {
15938
16047
  for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
15939
16048
  const candidate = join30(planDir, name);
15940
- if (existsSync19(candidate)) return candidate;
16049
+ if (existsSync20(candidate)) return candidate;
15941
16050
  }
15942
16051
  return null;
15943
16052
  }
@@ -16198,9 +16307,9 @@ function readProducerVersion() {
16198
16307
  let current = startDir;
16199
16308
  for (let i = 0; i < 10; i++) {
16200
16309
  const candidate = join30(current, "package.json");
16201
- if (existsSync19(candidate)) {
16310
+ if (existsSync20(candidate)) {
16202
16311
  try {
16203
- const pkg = JSON.parse(readFileSync8(candidate, "utf-8"));
16312
+ const pkg = JSON.parse(readFileSync9(candidate, "utf-8"));
16204
16313
  if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
16205
16314
  cachedProducerVersion = pkg.version;
16206
16315
  return pkg.version;
@@ -16252,7 +16361,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
16252
16361
  const result = [];
16253
16362
  for (const v of videos) {
16254
16363
  const outputDir = join31(planDir, "video-frames", v.videoId);
16255
- if (!existsSync20(outputDir)) {
16364
+ if (!existsSync21(outputDir)) {
16256
16365
  throw new Error(
16257
16366
  `[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
16258
16367
  );
@@ -16368,7 +16477,7 @@ async function extractDeferredVideoFramesForChunk(input) {
16368
16477
  const range = video.deferredRange;
16369
16478
  const remoteUrl = remoteSources[range.sourcePath];
16370
16479
  const sourcePath = remoteUrl ?? join31(input.planDir, ...range.sourcePath.split("/"));
16371
- if (remoteUrl === void 0 && !existsSync20(sourcePath)) {
16480
+ if (remoteUrl === void 0 && !existsSync21(sourcePath)) {
16372
16481
  throw new RenderChunkValidationError(
16373
16482
  MISSING_PLAN_ARTIFACT,
16374
16483
  `[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
@@ -16586,7 +16695,7 @@ async function readPlanReferenceBackground(input) {
16586
16695
  }
16587
16696
 
16588
16697
  // src/services/render/stages/freezePlan.ts
16589
- import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync9, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
16698
+ import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync10, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
16590
16699
  import { join as join34, relative as relative3, resolve as resolve9 } from "node:path";
16591
16700
  import {
16592
16701
  REFERENCE_BACKGROUND_FIELD,
@@ -16780,7 +16889,7 @@ function planFileSha256(absolutePath, digests) {
16780
16889
  return sha256;
16781
16890
  }
16782
16891
  async function prehashPlanFiles(dir, digests) {
16783
- if (!existsSync21(dir)) return;
16892
+ if (!existsSync22(dir)) return;
16784
16893
  for (const { absolutePath } of listPlanFiles(dir)) {
16785
16894
  if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
16786
16895
  }
@@ -16826,7 +16935,7 @@ function collectPlanAssetShas(planDir, digests) {
16826
16935
  const assets = [];
16827
16936
  for (const file of files) {
16828
16937
  if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
16829
- compositionHtml = readFileSync9(file.absolutePath);
16938
+ compositionHtml = readFileSync10(file.absolutePath);
16830
16939
  continue;
16831
16940
  }
16832
16941
  if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
@@ -16850,19 +16959,19 @@ function writePlanRemoteFiles(planDir, files) {
16850
16959
  }
16851
16960
  function readPlanRemoteFiles(planDir) {
16852
16961
  const path = join34(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
16853
- return existsSync21(path) ? JSON.parse(readFileSync9(path, "utf-8")) : [];
16962
+ return existsSync22(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
16854
16963
  }
16855
16964
  function recomputePlanHashFromPlanDir(planDir, digests) {
16856
16965
  const planJsonPath = join34(planDir, "plan.json");
16857
16966
  const encoderJsonPath = join34(planDir, "meta", "encoder.json");
16858
- if (!existsSync21(planJsonPath)) {
16967
+ if (!existsSync22(planJsonPath)) {
16859
16968
  throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
16860
16969
  }
16861
- if (!existsSync21(encoderJsonPath)) {
16970
+ if (!existsSync22(encoderJsonPath)) {
16862
16971
  throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
16863
16972
  }
16864
- const planJson = JSON.parse(readFileSync9(planJsonPath, "utf-8"));
16865
- const encoderConfigCanonicalJson = readFileSync9(encoderJsonPath, "utf-8");
16973
+ const planJson = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
16974
+ const encoderConfigCanonicalJson = readFileSync10(encoderJsonPath, "utf-8");
16866
16975
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
16867
16976
  return computePlanHash({
16868
16977
  compositionHtml,
@@ -16888,11 +16997,11 @@ async function freezePlan(input) {
16888
16997
  hasAudio,
16889
16998
  digests
16890
16999
  } = input;
16891
- if (!existsSync21(planDir)) {
17000
+ if (!existsSync22(planDir)) {
16892
17001
  throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
16893
17002
  }
16894
17003
  const metaDir = join34(planDir, "meta");
16895
- if (!existsSync21(metaDir)) mkdirSync12(metaDir, { recursive: true });
17004
+ if (!existsSync22(metaDir)) mkdirSync12(metaDir, { recursive: true });
16896
17005
  writeFileSync7(
16897
17006
  join34(metaDir, "composition.json"),
16898
17007
  `${JSON.stringify(composition, null, 2)}
@@ -16969,7 +17078,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
16969
17078
  }
16970
17079
 
16971
17080
  // src/services/distributed/chunkAudio.ts
16972
- import { closeSync as closeSync6, existsSync as existsSync22, ftruncateSync, mkdirSync as mkdirSync13, openSync as openSync6, readFileSync as readFileSync10 } from "node:fs";
17081
+ import { closeSync as closeSync6, existsSync as existsSync23, ftruncateSync, mkdirSync as mkdirSync13, openSync as openSync6, readFileSync as readFileSync11 } from "node:fs";
16973
17082
  import { writeFileSync as writeFileSync8, writeSync } from "node:fs";
16974
17083
  import { join as join35 } from "node:path";
16975
17084
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
@@ -17057,7 +17166,7 @@ async function writeChunkAudioSlices(input) {
17057
17166
  offset += track.sizes[offsets.length - 1];
17058
17167
  }
17059
17168
  });
17060
- const bytes = readFileSync10(input.audioPath);
17169
+ const bytes = readFileSync11(input.audioPath);
17061
17170
  const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
17062
17171
  mkdirSync13(join35(input.planDir, SLICE_DIR), { recursive: true });
17063
17172
  input.chunks.forEach((_, index) => {
@@ -17075,9 +17184,9 @@ async function writeChunkAudioSlices(input) {
17075
17184
  }
17076
17185
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
17077
17186
  const layoutPath = join35(planDir, SLICE_LAYOUT);
17078
- if (!existsSync22(layoutPath)) return resolvePlanAudioPath(planDir);
17079
- const layout = JSON.parse(readFileSync10(layoutPath, "utf-8"));
17080
- const frame = readFileSync10(join35(planDir, SLICE_FRAME));
17187
+ if (!existsSync23(layoutPath)) return resolvePlanAudioPath(planDir);
17188
+ const layout = JSON.parse(readFileSync11(layoutPath, "utf-8"));
17189
+ const frame = readFileSync11(join35(planDir, SLICE_FRAME));
17081
17190
  const fd = openSync6(scratchPath, "w");
17082
17191
  try {
17083
17192
  writeSync(fd, frame, 0, layout.payloadStart, 0);
@@ -17090,7 +17199,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
17090
17199
  );
17091
17200
  const start = layout.partStarts[chunkIndex];
17092
17201
  if (start !== void 0) {
17093
- const part = readFileSync10(join35(planDir, chunkAudioSlicePath(chunkIndex)));
17202
+ const part = readFileSync11(join35(planDir, chunkAudioSlicePath(chunkIndex)));
17094
17203
  writeSync(fd, part, 0, part.length, start);
17095
17204
  }
17096
17205
  ftruncateSync(fd, layout.sizeBytes);
@@ -17635,7 +17744,7 @@ function createVideoSourceShipper(input) {
17635
17744
  let result = null;
17636
17745
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
17637
17746
  const rel = pathInside(root, srcPath);
17638
- if (rel !== null && (existsSync23(join37(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
17747
+ if (rel !== null && (existsSync24(join37(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
17639
17748
  result = ["compiled", ...rel.split(sep2)].join("/");
17640
17749
  break;
17641
17750
  }
@@ -17663,7 +17772,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
17663
17772
  useGpu: false,
17664
17773
  browserGpuMode: "software"
17665
17774
  });
17666
- if (!existsSync23(planDir)) mkdirSync14(planDir, { recursive: true });
17775
+ if (!existsSync24(planDir)) mkdirSync14(planDir, { recursive: true });
17667
17776
  const log = config.logger ?? defaultLogger;
17668
17777
  const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
17669
17778
  const abortSignal = config.abortSignal;
@@ -17693,7 +17802,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
17693
17802
  });
17694
17803
  const entryFile = config.entryFile ?? "index.html";
17695
17804
  const htmlPath = join37(projectDir, entryFile);
17696
- if (!existsSync23(htmlPath)) {
17805
+ if (!existsSync24(htmlPath)) {
17697
17806
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
17698
17807
  }
17699
17808
  const workDir = join37(planDir, ".plan-work");
@@ -17922,15 +18031,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
17922
18031
  logPlanStage(log, "extract", stageStarted);
17923
18032
  const stagedVideoFrames = join37(compiledDir, "__hyperframes_video_frames");
17924
18033
  const videoFramesDst = join37(planDir, "video-frames");
17925
- if (existsSync23(videoFramesDst)) rmSync12(videoFramesDst, { recursive: true, force: true });
18034
+ if (existsSync24(videoFramesDst)) rmSync12(videoFramesDst, { recursive: true, force: true });
17926
18035
  const videoSourcesDir = join37(planDir, "video-sources");
17927
18036
  rmSync12(videoSourcesDir, { recursive: true, force: true });
17928
- if (existsSync23(stagedVideoFrames)) {
18037
+ if (existsSync24(stagedVideoFrames)) {
17929
18038
  renameWithRetry8(stagedVideoFrames, videoFramesDst);
17930
18039
  } else {
17931
18040
  mkdirSync14(videoFramesDst, { recursive: true });
17932
18041
  }
17933
- if (existsSync23(finalCompiledDir)) rmSync12(finalCompiledDir, { recursive: true, force: true });
18042
+ if (existsSync24(finalCompiledDir)) rmSync12(finalCompiledDir, { recursive: true, force: true });
17934
18043
  renameWithRetry8(compiledDir, finalCompiledDir);
17935
18044
  const shipVideoSource = createVideoSourceShipper({
17936
18045
  planDir,
@@ -18050,10 +18159,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
18050
18159
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
18051
18160
  }
18052
18161
  const planAudioPath = join37(planDir, PLAN_AUDIO_RELATIVE_PATH);
18053
- if (audioResult?.hasAudio && existsSync23(audioResult.audioOutputPath)) {
18162
+ if (audioResult?.hasAudio && existsSync24(audioResult.audioOutputPath)) {
18054
18163
  renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
18055
18164
  }
18056
- if (chunksCarryAudio && existsSync23(planAudioPath)) {
18165
+ if (chunksCarryAudio && existsSync24(planAudioPath)) {
18057
18166
  if (options.sliceChunkAudio) {
18058
18167
  stageStarted = Date.now();
18059
18168
  await writeChunkAudioSlices({
@@ -18171,7 +18280,7 @@ async function plan(projectDir, config, planDir, options = {}) {
18171
18280
 
18172
18281
  // src/services/distributed/renderChunk.ts
18173
18282
  import { randomBytes } from "node:crypto";
18174
- import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync12, readdirSync as readdirSync10, rmSync as rmSync15, writeFileSync as writeFileSync12 } from "node:fs";
18283
+ import { existsSync as existsSync27, mkdirSync as mkdirSync17, readFileSync as readFileSync13, readdirSync as readdirSync10, rmSync as rmSync15, writeFileSync as writeFileSync12 } from "node:fs";
18175
18284
  import { cpus as cpus3 } from "node:os";
18176
18285
  import { join as join41 } from "node:path";
18177
18286
  import {
@@ -18198,11 +18307,11 @@ import {
18198
18307
  } from "@hyperframes/engine";
18199
18308
  import {
18200
18309
  copyFileSync as copyFileSync8,
18201
- existsSync as existsSync25,
18310
+ existsSync as existsSync26,
18202
18311
  lstatSync as lstatSync3,
18203
18312
  mkdirSync as mkdirSync16,
18204
18313
  mkdtempSync as mkdtempSync6,
18205
- readFileSync as readFileSync11,
18314
+ readFileSync as readFileSync12,
18206
18315
  readdirSync as readdirSync9,
18207
18316
  rmSync as rmSync14,
18208
18317
  statSync as statSync10,
@@ -18216,7 +18325,7 @@ import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as r
18216
18325
  // src/services/distributed/planV2Publisher.ts
18217
18326
  import {
18218
18327
  copyFileSync as copyFileSync7,
18219
- existsSync as existsSync24,
18328
+ existsSync as existsSync25,
18220
18329
  linkSync as linkSync2,
18221
18330
  mkdirSync as mkdirSync15,
18222
18331
  mkdtempSync as mkdtempSync5,
@@ -18285,7 +18394,7 @@ var LocalPlanV2ArtifactPublisher = class {
18285
18394
  #linkFile;
18286
18395
  #committed = false;
18287
18396
  constructor(destinationDir, options = {}) {
18288
- if (existsSync24(destinationDir)) {
18397
+ if (existsSync25(destinationDir)) {
18289
18398
  throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
18290
18399
  }
18291
18400
  this.destinationDir = destinationDir;
@@ -18302,7 +18411,7 @@ var LocalPlanV2ArtifactPublisher = class {
18302
18411
  );
18303
18412
  }
18304
18413
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
18305
- if (existsSync24(destinationPath)) return;
18414
+ if (existsSync25(destinationPath)) return;
18306
18415
  mkdirSync15(dirname15(destinationPath), { recursive: true });
18307
18416
  const stagingDir = mkdtempSync5(join39(dirname15(destinationPath), ".plan-v2-blob-"));
18308
18417
  const temporaryPath = join39(stagingDir, "blob");
@@ -18320,7 +18429,7 @@ var LocalPlanV2ArtifactPublisher = class {
18320
18429
  }
18321
18430
  async commitManifest(manifestBytes) {
18322
18431
  for (const digest of new Set(manifestDigests(manifestBytes))) {
18323
- if (!existsSync24(planV2BlobPath(this.temporaryDir, digest))) {
18432
+ if (!existsSync25(planV2BlobPath(this.temporaryDir, digest))) {
18324
18433
  throw new PlanV2IntegrityError(
18325
18434
  `cannot commit manifest before referenced blob is durable: ${digest}`
18326
18435
  );
@@ -18351,7 +18460,7 @@ function isNonNegativeIntegerArray(value) {
18351
18460
  return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
18352
18461
  }
18353
18462
  function readJsonFile(path, label) {
18354
- const contents = readFileSync11(path, "utf-8");
18463
+ const contents = readFileSync12(path, "utf-8");
18355
18464
  try {
18356
18465
  const parsed = JSON.parse(contents);
18357
18466
  return parsed;
@@ -18399,7 +18508,7 @@ function assertValidExtractionCacheCompleteSentinel(path) {
18399
18508
  }
18400
18509
  function validateExtractionCacheCompleteSentinels(executionPlanDir) {
18401
18510
  const videoRoot = join40(executionPlanDir, "video-frames");
18402
- if (!existsSync25(videoRoot)) return;
18511
+ if (!existsSync26(videoRoot)) return;
18403
18512
  for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
18404
18513
  if (!videoEntry.isDirectory()) continue;
18405
18514
  const videoDir = join40(videoRoot, videoEntry.name);
@@ -18500,7 +18609,7 @@ function parsePlanVideosJson2(value) {
18500
18609
  }
18501
18610
  function materializeExtractedVideoDirectories(planDir) {
18502
18611
  const videosPath = join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18503
- if (!existsSync25(videosPath)) return;
18612
+ if (!existsSync26(videosPath)) return;
18504
18613
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
18505
18614
  for (const video of videos.extracted) {
18506
18615
  mkdirSync16(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
@@ -18534,9 +18643,9 @@ function parseChunkSlices(value) {
18534
18643
  }
18535
18644
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
18536
18645
  const videoRoot = join40(executionPlanDir, "video-frames");
18537
- const hasExtractedFrames = existsSync25(videoRoot) && listFiles(videoRoot).length > 0;
18646
+ const hasExtractedFrames = existsSync26(videoRoot) && listFiles(videoRoot).length > 0;
18538
18647
  const videosPath = join40(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18539
- if (!existsSync25(videosPath)) {
18648
+ if (!existsSync26(videosPath)) {
18540
18649
  return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
18541
18650
  }
18542
18651
  const chunksPath = join40(executionPlanDir, "meta", "chunks.json");
@@ -18593,7 +18702,7 @@ function computeManifestHash(manifest) {
18593
18702
  return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
18594
18703
  }
18595
18704
  function writeBlob(sourcePath, destinationPath) {
18596
- if (existsSync25(destinationPath)) return;
18705
+ if (existsSync26(destinationPath)) return;
18597
18706
  mkdirSync16(dirname16(destinationPath), { recursive: true });
18598
18707
  const temporaryDir = mkdtempSync6(join40(dirname16(destinationPath), ".plan-v2-blob-"));
18599
18708
  const temporaryPath = join40(temporaryDir, "blob");
@@ -18633,7 +18742,7 @@ async function putTasks(tasks) {
18633
18742
  }
18634
18743
  function buildPlanV2Publication(executionPlanDir, digests) {
18635
18744
  const executionPlanPath = join40(executionPlanDir, "plan.json");
18636
- if (!existsSync25(executionPlanPath)) {
18745
+ if (!existsSync26(executionPlanPath)) {
18637
18746
  throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
18638
18747
  }
18639
18748
  const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
@@ -18713,7 +18822,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
18713
18822
  };
18714
18823
  }
18715
18824
  function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
18716
- if (existsSync25(planV2Dir)) {
18825
+ if (existsSync26(planV2Dir)) {
18717
18826
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
18718
18827
  }
18719
18828
  const publication = buildPlanV2Publication(executionPlanDir);
@@ -18799,7 +18908,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
18799
18908
  }
18800
18909
  }
18801
18910
  async function planV2(projectDir, config, planV2Dir) {
18802
- if (existsSync25(planV2Dir)) {
18911
+ if (existsSync26(planV2Dir)) {
18803
18912
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
18804
18913
  }
18805
18914
  const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
@@ -18953,7 +19062,7 @@ function parsePlanV2Manifest(value) {
18953
19062
  }
18954
19063
  function readPlanV2Manifest(planV2Dir) {
18955
19064
  const manifestPath = join40(planV2Dir, "plan.json");
18956
- if (!existsSync25(manifestPath)) {
19065
+ if (!existsSync26(manifestPath)) {
18957
19066
  throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
18958
19067
  }
18959
19068
  const value = readJsonFile(manifestPath, "v2 manifest");
@@ -18972,7 +19081,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
18972
19081
  }
18973
19082
  function verifyBlob(planV2Dir, artifact) {
18974
19083
  const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
18975
- if (!existsSync25(sourcePath)) {
19084
+ if (!existsSync26(sourcePath)) {
18976
19085
  throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
18977
19086
  }
18978
19087
  const stats = statSync10(sourcePath);
@@ -18985,7 +19094,7 @@ function verifyBlob(planV2Dir, artifact) {
18985
19094
  return sourcePath;
18986
19095
  }
18987
19096
  function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
18988
- if (existsSync25(destinationDir)) {
19097
+ if (existsSync26(destinationDir)) {
18989
19098
  throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
18990
19099
  }
18991
19100
  const manifest = readPlanV2Manifest(planV2Dir);
@@ -19050,7 +19159,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
19050
19159
  }
19051
19160
  function readValidatedPlanV2Materialization(planDir, expectedTarget) {
19052
19161
  const markerPath = join40(planDir, PLAN_V2_MATERIALIZATION_MARKER);
19053
- if (!existsSync25(markerPath)) return null;
19162
+ if (!existsSync26(markerPath)) return null;
19054
19163
  const marker = readJsonFile(markerPath, "materialization marker");
19055
19164
  if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
19056
19165
  throw new PlanV2IntegrityError("malformed materialization marker");
@@ -19068,7 +19177,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
19068
19177
  for (const artifact of required) {
19069
19178
  if (Object.hasOwn(remoteSources, artifact.path)) continue;
19070
19179
  const path = join40(planDir, ...artifact.path.split("/"));
19071
- if (!existsSync25(path) || statSync10(path).size !== artifact.sizeBytes) {
19180
+ if (!existsSync26(path) || statSync10(path).size !== artifact.sizeBytes) {
19072
19181
  throw new PlanV2IntegrityError(
19073
19182
  `materialized artifact missing or truncated: ${artifact.path}`
19074
19183
  );
@@ -19185,10 +19294,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
19185
19294
  return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
19186
19295
  }
19187
19296
  function hashChunkOutput(outputPath, kind) {
19188
- if (kind === "file") return sha256Hex(readFileSync12(outputPath));
19297
+ if (kind === "file") return sha256Hex(readFileSync13(outputPath));
19189
19298
  const entries = readdirSync10(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
19190
19299
  const lines = entries.map(
19191
- (name) => `${name}\0${sha256Hex(readFileSync12(join41(outputPath, name)))}`
19300
+ (name) => `${name}\0${sha256Hex(readFileSync13(join41(outputPath, name)))}`
19192
19301
  );
19193
19302
  return sha256Hex(lines.join("\0"));
19194
19303
  }
@@ -19208,13 +19317,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19208
19317
  const planJsonPath = join41(planDir, "plan.json");
19209
19318
  const encoderJsonPath = join41(planDir, "meta", "encoder.json");
19210
19319
  const chunksJsonPath = join41(planDir, "meta", "chunks.json");
19211
- if (!existsSync26(planJsonPath)) {
19320
+ if (!existsSync27(planJsonPath)) {
19212
19321
  throw new RenderChunkValidationError(
19213
19322
  MISSING_PLAN_ARTIFACT,
19214
19323
  `[renderChunk] planDir is missing required artifact: ${planJsonPath}`
19215
19324
  );
19216
19325
  }
19217
- const plan2 = JSON.parse(readFileSync12(planJsonPath, "utf-8"));
19326
+ const plan2 = JSON.parse(readFileSync13(planJsonPath, "utf-8"));
19218
19327
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
19219
19328
  const planHashStarted = Date.now();
19220
19329
  const v2Materialization = readValidatedPlanV2Materialization(planDir, {
@@ -19224,20 +19333,20 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19224
19333
  const v2Manifest = v2Materialization?.manifest ?? null;
19225
19334
  const remoteSources = v2Materialization?.remoteSources ?? {};
19226
19335
  for (const required of [encoderJsonPath, chunksJsonPath]) {
19227
- if (!existsSync26(required)) {
19336
+ if (!existsSync27(required)) {
19228
19337
  throw new RenderChunkValidationError(
19229
19338
  MISSING_PLAN_ARTIFACT,
19230
19339
  `[renderChunk] planDir is missing required artifact: ${required}`
19231
19340
  );
19232
19341
  }
19233
19342
  }
19234
- const encoder = JSON.parse(readFileSync12(encoderJsonPath, "utf-8"));
19235
- const chunks = JSON.parse(readFileSync12(chunksJsonPath, "utf-8"));
19343
+ const encoder = JSON.parse(readFileSync13(encoderJsonPath, "utf-8"));
19344
+ const chunks = JSON.parse(readFileSync13(chunksJsonPath, "utf-8"));
19236
19345
  const videosJsonPath = join41(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19237
19346
  let planVideos = null;
19238
- if (existsSync26(videosJsonPath)) {
19347
+ if (existsSync27(videosJsonPath)) {
19239
19348
  try {
19240
- planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync12(videosJsonPath, "utf-8")));
19349
+ planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync13(videosJsonPath, "utf-8")));
19241
19350
  } catch (err) {
19242
19351
  if (err instanceof RenderChunkValidationError) throw err;
19243
19352
  throw new RenderChunkValidationError(
@@ -19266,7 +19375,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19266
19375
  const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
19267
19376
  const framesInChunk = range.endFrame - range.startFrame;
19268
19377
  const compiledDir = join41(planDir, "compiled");
19269
- if (!existsSync26(compiledDir)) {
19378
+ if (!existsSync27(compiledDir)) {
19270
19379
  throw new RenderChunkValidationError(
19271
19380
  MISSING_PLAN_ARTIFACT,
19272
19381
  `[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
@@ -19348,7 +19457,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19348
19457
  dimensions: plan2.dimensions,
19349
19458
  encoder,
19350
19459
  videoCount: planVideos?.videos.length ?? 0,
19351
- html: readFileSync12(join41(compiledDir, "index.html"), "utf-8"),
19460
+ html: readFileSync13(join41(compiledDir, "index.html"), "utf-8"),
19352
19461
  // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
19353
19462
  skipReadinessVideoIds: deferredVideos.filter(
19354
19463
  (video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
@@ -19516,10 +19625,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19516
19625
  const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
19517
19626
  const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
19518
19627
  if (isPngSequence) {
19519
- if (!existsSync26(outputChunkPath)) mkdirSync17(outputChunkPath, { recursive: true });
19628
+ if (!existsSync27(outputChunkPath)) mkdirSync17(outputChunkPath, { recursive: true });
19520
19629
  } else {
19521
19630
  const outDir = join41(outputChunkPath, "..");
19522
- if (!existsSync26(outDir)) mkdirSync17(outDir, { recursive: true });
19631
+ if (!existsSync27(outDir)) mkdirSync17(outDir, { recursive: true });
19523
19632
  }
19524
19633
  const encodeInput = {
19525
19634
  job,
@@ -19810,7 +19919,7 @@ async function renderSpan(projectDir, config, span, outputPath) {
19810
19919
  planned.totalFrames
19811
19920
  );
19812
19921
  const slices = JSON.parse(
19813
- readFileSync13(join42(planDir, "meta", "chunks.json"), "utf-8")
19922
+ readFileSync14(join42(planDir, "meta", "chunks.json"), "utf-8")
19814
19923
  );
19815
19924
  const parts = [];
19816
19925
  for (const slice of slices) {
@@ -20419,7 +20528,7 @@ import {
20419
20528
 
20420
20529
  // src/server.ts
20421
20530
  import {
20422
- existsSync as existsSync29,
20531
+ existsSync as existsSync30,
20423
20532
  mkdirSync as mkdirSync19,
20424
20533
  statSync as statSync12,
20425
20534
  mkdtempSync as mkdtempSync8,
@@ -20442,8 +20551,8 @@ import {
20442
20551
 
20443
20552
  // src/services/hyperframeLint.ts
20444
20553
  import {
20445
- existsSync as existsSync27,
20446
- readFileSync as readFileSync14,
20554
+ existsSync as existsSync28,
20555
+ readFileSync as readFileSync15,
20447
20556
  statSync as statSync11,
20448
20557
  openSync as openSync7,
20449
20558
  fstatSync as fstatSync3,
@@ -20489,14 +20598,14 @@ function readEntryHtml(filePath) {
20489
20598
  }
20490
20599
  try {
20491
20600
  if (!fstatSync3(fd).isFile()) return null;
20492
- return readFileSync14(fd, "utf-8");
20601
+ return readFileSync15(fd, "utf-8");
20493
20602
  } finally {
20494
20603
  closeSync7(fd);
20495
20604
  }
20496
20605
  }
20497
20606
  function readProjectEntryFile(projectDir, preferredEntryFile) {
20498
20607
  const absProjectDir = resolve11(projectDir);
20499
- if (!existsSync27(absProjectDir) || !statSync11(absProjectDir).isDirectory()) {
20608
+ if (!existsSync28(absProjectDir) || !statSync11(absProjectDir).isDirectory()) {
20500
20609
  return { error: `Project directory not found: ${absProjectDir}` };
20501
20610
  }
20502
20611
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
@@ -20565,7 +20674,7 @@ async function runHyperframeLint(prepared) {
20565
20674
  import { Worker as Worker2 } from "node:worker_threads";
20566
20675
  import { fileURLToPath as fileURLToPath6 } from "node:url";
20567
20676
  import { dirname as dirname17, join as join44 } from "node:path";
20568
- import { existsSync as existsSync28 } from "node:fs";
20677
+ import { existsSync as existsSync29 } from "node:fs";
20569
20678
  var DEFAULT_HEALTH_PORT = 9848;
20570
20679
  async function startHealthWorker(options = {}) {
20571
20680
  const log = options.logger ?? defaultLogger2();
@@ -20642,7 +20751,7 @@ function resolveWorkerEntry2() {
20642
20751
  const here = dirname17(fileURLToPath6(import.meta.url));
20643
20752
  const candidates = [join44(here, "healthWorkerThread.js"), join44(here, "healthWorkerThread.ts")];
20644
20753
  for (const candidate of candidates) {
20645
- if (existsSync28(candidate)) return candidate;
20754
+ if (existsSync29(candidate)) return candidate;
20646
20755
  }
20647
20756
  return void 0;
20648
20757
  }
@@ -20788,7 +20897,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
20788
20897
  const { input, cleanupProjectDir } = prepared;
20789
20898
  const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
20790
20899
  const outputDir = dirname18(absoluteOutputPath);
20791
- if (!existsSync29(outputDir)) mkdirSync19(outputDir, { recursive: true });
20900
+ if (!existsSync30(outputDir)) mkdirSync19(outputDir, { recursive: true });
20792
20901
  return { input, cleanupProjectDir, absoluteOutputPath };
20793
20902
  }
20794
20903
  function validateRenderOverrides(body) {
@@ -20829,11 +20938,11 @@ function prepareProjectDirectory(projectDir, options) {
20829
20938
  const candidate = nonEmptyString(projectDir);
20830
20939
  if (!candidate) return null;
20831
20940
  const absProjectDir = resolve12(candidate);
20832
- if (!existsSync29(absProjectDir) || !statSync12(absProjectDir).isDirectory()) {
20941
+ if (!existsSync30(absProjectDir) || !statSync12(absProjectDir).isDirectory()) {
20833
20942
  return { error: `Project directory not found: ${absProjectDir}` };
20834
20943
  }
20835
20944
  const entry = options.entryFile || "index.html";
20836
- if (!existsSync29(resolve12(absProjectDir, entry))) {
20945
+ if (!existsSync30(resolve12(absProjectDir, entry))) {
20837
20946
  return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
20838
20947
  }
20839
20948
  return { prepared: { input: { projectDir: absProjectDir, ...options } } };
@@ -20925,7 +21034,7 @@ function cleanupTempDir(dir, log) {
20925
21034
  }
20926
21035
  }
20927
21036
  function outputFileSize(path) {
20928
- return existsSync29(path) ? statSync12(path).size : 0;
21037
+ return existsSync30(path) ? statSync12(path).size : 0;
20929
21038
  }
20930
21039
  function createBlockingProgressReporter(log, requestId) {
20931
21040
  let lastLoggedPct = -10;
@@ -21229,7 +21338,7 @@ function createRenderHandlers(options = {}) {
21229
21338
  if (!artifact) {
21230
21339
  return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
21231
21340
  }
21232
- if (!existsSync29(artifact.path)) {
21341
+ if (!existsSync30(artifact.path)) {
21233
21342
  store.delete(token);
21234
21343
  return c.json({ success: false, error: "Output artifact file missing" }, 404);
21235
21344
  }
@@ -21335,10 +21444,10 @@ import {
21335
21444
  } from "@hyperframes/engine";
21336
21445
  import {
21337
21446
  cpSync as cpSync3,
21338
- existsSync as existsSync30,
21447
+ existsSync as existsSync31,
21339
21448
  mkdirSync as mkdirSync20,
21340
21449
  mkdtempSync as mkdtempSync9,
21341
- readFileSync as readFileSync15,
21450
+ readFileSync as readFileSync16,
21342
21451
  readdirSync as readdirSync11,
21343
21452
  rmSync as rmSync18,
21344
21453
  statSync as statSync13,
@@ -21390,7 +21499,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21390
21499
  }
21391
21500
  const format = plan2.dimensions.format;
21392
21501
  const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
21393
- if (!existsSync30(dirname19(outputPath))) {
21502
+ if (!existsSync31(dirname19(outputPath))) {
21394
21503
  mkdirSync20(dirname19(outputPath), { recursive: true });
21395
21504
  }
21396
21505
  const workDir = mkdtempSync9(`${outputPath}.assemble-work-`);
@@ -21414,7 +21523,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21414
21523
  try {
21415
21524
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
21416
21525
  const finalPath = join46(workDir, `output.${format}`);
21417
- const audio = audioPath !== null && existsSync30(audioPath) ? audioPath : null;
21526
+ const audio = audioPath !== null && existsSync31(audioPath) ? audioPath : null;
21418
21527
  await writeJoined(ctx, videoSources, audio, finalPath);
21419
21528
  renameWithRetry11(finalPath, outputPath);
21420
21529
  log.info("[assemble] stage timings", {
@@ -21432,7 +21541,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21432
21541
  });
21433
21542
  }
21434
21543
  }
21435
- const fileSize = existsSync30(outputPath) ? statSync13(outputPath).size : 0;
21544
+ const fileSize = existsSync31(outputPath) ? statSync13(outputPath).size : 0;
21436
21545
  return {
21437
21546
  ...referenceBackgroundResult8(plan2[REFERENCE_BACKGROUND_FIELD6]),
21438
21547
  outputPath,
@@ -21444,16 +21553,16 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21444
21553
  function readAssemblePlan(planDir, chunkPaths) {
21445
21554
  const planJsonPath = join46(planDir, "plan.json");
21446
21555
  const chunksJsonPath = join46(planDir, "meta", "chunks.json");
21447
- if (!existsSync30(planJsonPath)) {
21556
+ if (!existsSync31(planJsonPath)) {
21448
21557
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
21449
21558
  }
21450
- const plan2 = JSON.parse(readFileSync15(planJsonPath, "utf-8"));
21559
+ const plan2 = JSON.parse(readFileSync16(planJsonPath, "utf-8"));
21451
21560
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
21452
21561
  validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
21453
- if (!existsSync30(chunksJsonPath)) {
21562
+ if (!existsSync31(chunksJsonPath)) {
21454
21563
  throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
21455
21564
  }
21456
- const chunks = JSON.parse(readFileSync15(chunksJsonPath, "utf-8"));
21565
+ const chunks = JSON.parse(readFileSync16(chunksJsonPath, "utf-8"));
21457
21566
  if (chunkPaths.length !== chunks.length) {
21458
21567
  throw new Error(
21459
21568
  `[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
@@ -21465,7 +21574,7 @@ function readAssemblePlan(planDir, chunkPaths) {
21465
21574
  }
21466
21575
  }
21467
21576
  for (const path of chunkPaths.flat()) {
21468
- if (!existsSync30(path)) {
21577
+ if (!existsSync31(path)) {
21469
21578
  throw new Error(`[assemble] chunk path does not exist: ${path}`);
21470
21579
  }
21471
21580
  }
@@ -21599,13 +21708,13 @@ function stitchesSeparateAudio(planDir) {
21599
21708
  return lockedAudio(planDir).separateAudio === true;
21600
21709
  }
21601
21710
  function planFormat(planDir) {
21602
- const plan2 = JSON.parse(readFileSync15(join46(planDir, "plan.json"), "utf-8"));
21711
+ const plan2 = JSON.parse(readFileSync16(join46(planDir, "plan.json"), "utf-8"));
21603
21712
  return plan2.dimensions.format;
21604
21713
  }
21605
21714
  function lockedAudio(planDir) {
21606
21715
  const encoderJsonPath = join46(planDir, "meta", "encoder.json");
21607
- if (!existsSync30(encoderJsonPath)) return {};
21608
- return JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
21716
+ if (!existsSync31(encoderJsonPath)) return {};
21717
+ return JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
21609
21718
  }
21610
21719
  function readCfrEncoder(planDir, format) {
21611
21720
  if (format !== "mp4") {
@@ -21614,10 +21723,10 @@ function readCfrEncoder(planDir, format) {
21614
21723
  );
21615
21724
  }
21616
21725
  const encoderJsonPath = join46(planDir, "meta", "encoder.json");
21617
- if (!existsSync30(encoderJsonPath)) {
21726
+ if (!existsSync31(encoderJsonPath)) {
21618
21727
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
21619
21728
  }
21620
- const encoderJson = JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
21729
+ const encoderJson = JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
21621
21730
  if (encoderJson.encoder === "libx265-software") {
21622
21731
  throw new Error(
21623
21732
  `[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".`
@@ -21626,7 +21735,7 @@ function readCfrEncoder(planDir, format) {
21626
21735
  return encoderJson;
21627
21736
  }
21628
21737
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
21629
- if (existsSync30(outputPath)) rmSync18(outputPath, { recursive: true, force: true });
21738
+ if (existsSync31(outputPath)) rmSync18(outputPath, { recursive: true, force: true });
21630
21739
  mkdirSync20(outputPath, { recursive: true });
21631
21740
  let globalIdx = 0;
21632
21741
  for (const chunkDir of chunkPaths) {
@@ -21650,7 +21759,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
21650
21759
  `[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
21651
21760
  );
21652
21761
  }
21653
- if (audioPath !== null && existsSync30(audioPath)) {
21762
+ if (audioPath !== null && existsSync31(audioPath)) {
21654
21763
  const sidecar = join46(outputPath, MIXED_AUDIO_FILENAME4);
21655
21764
  cpSync3(audioPath, sidecar);
21656
21765
  }