@hyperframes/producer 0.9.14 → 0.9.16

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,11 +8,11 @@ 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 existsSync18,
12
12
  linkSync,
13
13
  mkdirSync as mkdirSync10,
14
14
  rmSync as rmSync7,
15
- writeFileSync as writeFileSync7
15
+ writeFileSync as writeFileSync8
16
16
  } from "node:fs";
17
17
  import { extname as extname4, isAbsolute as isAbsolute3, join as join24, relative as relative5, sep as sep2 } from "node:path";
18
18
  import {
@@ -71,12 +71,12 @@ 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 existsSync13,
75
75
  mkdirSync as mkdirSync6,
76
76
  readdirSync as readdirSync3,
77
77
  rmSync as rmSync5,
78
78
  statSync as statSync5,
79
- writeFileSync as writeFileSync4,
79
+ writeFileSync as writeFileSync5,
80
80
  copyFileSync as copyFileSync5,
81
81
  appendFileSync
82
82
  } from "fs";
@@ -90,9 +90,7 @@ import {
90
90
  toFps as toFps2
91
91
  } from "@hyperframes/core";
92
92
  import { HF_AUDIO_GROUP_TAG } from "@hyperframes/core/audio-groups";
93
- import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
94
93
  import { HTML_BODY_CSS_HEIGHT_FIRST_RE, HTML_BODY_CSS_WIDTH_FIRST_RE } from "@hyperframes/parsers";
95
- import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
96
94
  import {
97
95
  extractMediaMetadata as extractMediaMetadata4,
98
96
  resolveConfig as resolveConfig2,
@@ -123,7 +121,8 @@ import {
123
121
  isDrawElementCaptureError,
124
122
  getDrawElementVerificationDetails,
125
123
  augmentProtocolTimeoutError,
126
- augmentPageNavigationTimeoutError
124
+ augmentPageNavigationTimeoutError,
125
+ displaySize
127
126
  } from "@hyperframes/engine";
128
127
  import {
129
128
  referenceBackgroundResult,
@@ -1914,8 +1913,11 @@ import {
1914
1913
  } from "@hyperframes/engine";
1915
1914
 
1916
1915
  // src/services/render/stages/compileStage.ts
1916
+ import { existsSync as existsSync7, writeFileSync as writeFileSync3 } from "node:fs";
1917
1917
  import { join as join7 } from "node:path";
1918
1918
  import { suggestMatchingPreset } from "@hyperframes/core";
1919
+ import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
1920
+ import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
1919
1921
 
1920
1922
  // src/services/htmlCompiler.ts
1921
1923
  import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
@@ -4620,11 +4622,36 @@ function adaptAspectAgnosticResolution(requested, aspectAgnostic, width, height,
4620
4622
  });
4621
4623
  return flipped;
4622
4624
  }
4625
+ function resolveEntryHtmlPath(input) {
4626
+ const { projectDir, workDir, entryFile, log } = input;
4627
+ const htmlPath = join7(projectDir, entryFile);
4628
+ const rawEntry = readTextFile2(htmlPath);
4629
+ if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
4630
+ const projectIndexPath = join7(projectDir, "index.html");
4631
+ if (!existsSync7(projectIndexPath)) {
4632
+ throw new Error(
4633
+ `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
4634
+ );
4635
+ }
4636
+ const standaloneHtml = extractStandaloneEntryFromIndex(
4637
+ readTextFile2(projectIndexPath),
4638
+ entryFile,
4639
+ rawEntry
4640
+ );
4641
+ if (!standaloneHtml) {
4642
+ throw new Error(
4643
+ `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
4644
+ );
4645
+ }
4646
+ const wrapperPath = join7(workDir, "standalone-entry.html");
4647
+ writeFileSync3(wrapperPath, standaloneHtml, "utf-8");
4648
+ log.info("Extracted standalone entry from index.html host context", { entryFile });
4649
+ return wrapperPath;
4650
+ }
4623
4651
  async function runCompileStage(input) {
4624
4652
  const {
4625
4653
  projectDir,
4626
4654
  workDir,
4627
- htmlPath,
4628
4655
  entryFile,
4629
4656
  job,
4630
4657
  cfg,
@@ -4635,6 +4662,7 @@ async function runCompileStage(input) {
4635
4662
  allowSystemFontCapture,
4636
4663
  abortSignal
4637
4664
  } = input;
4665
+ const htmlPath = resolveEntryHtmlPath({ projectDir, workDir, entryFile, log });
4638
4666
  const compileStart = Date.now();
4639
4667
  const compiled = await compileForRender(projectDir, htmlPath, join7(workDir, "downloads"), {
4640
4668
  log,
@@ -5466,7 +5494,7 @@ function validateDistributedDuration(input) {
5466
5494
  }
5467
5495
 
5468
5496
  // src/services/render/stages/extractVideosStage.ts
5469
- import { existsSync as existsSync7 } from "node:fs";
5497
+ import { existsSync as existsSync8 } from "node:fs";
5470
5498
  import { join as join9 } from "node:path";
5471
5499
  import {
5472
5500
  classifyVideoExtractionError,
@@ -5741,7 +5769,7 @@ async function runExtractVideosStage(input) {
5741
5769
  const probeFailures = await Promise.all(
5742
5770
  composition.videos.map(async (v) => {
5743
5771
  const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
5744
- if (!existsSync7(videoPath)) return null;
5772
+ if (!existsSync8(videoPath)) return null;
5745
5773
  try {
5746
5774
  const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry(() => extractMediaMetadata3(videoPath), {
5747
5775
  signal: abortSignal,
@@ -5779,7 +5807,7 @@ async function runExtractVideosStage(input) {
5779
5807
  const probed = await Promise.all(
5780
5808
  composition.images.map(async (img) => {
5781
5809
  const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
5782
- if (!existsSync7(imgPath)) return null;
5810
+ if (!existsSync8(imgPath)) return null;
5783
5811
  const colorSpace = await probeColorSpaceSafely(imgPath, log);
5784
5812
  if (isHdrColorSpace2(colorSpace)) {
5785
5813
  nativeHdrImageIds.add(img.id);
@@ -5899,7 +5927,7 @@ async function appendVideoAudioFromProbe(input) {
5899
5927
  videos.map(async (video) => {
5900
5928
  const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
5901
5929
  const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
5902
- if (!isHttpUrl2(readFrom) && !existsSync7(readFrom)) return null;
5930
+ if (!isHttpUrl2(readFrom) && !existsSync8(readFrom)) return null;
5903
5931
  const metadata = await extractMediaMetadata3(readFrom);
5904
5932
  const window2 = await resolveFinalFrameExtractionWindow2(
5905
5933
  readFrom,
@@ -5927,6 +5955,7 @@ import {
5927
5955
  } from "@hyperframes/engine";
5928
5956
 
5929
5957
  // src/services/render/stages/audioStage.ts
5958
+ import { existsSync as existsSync10 } from "node:fs";
5930
5959
  import { join as join12 } from "node:path";
5931
5960
  import {
5932
5961
  MIXED_AUDIO_FILENAME,
@@ -5937,7 +5966,7 @@ import {
5937
5966
  } from "@hyperframes/engine";
5938
5967
 
5939
5968
  // src/services/render/audioDeliveryLimit.ts
5940
- import { existsSync as existsSync8 } from "node:fs";
5969
+ import { existsSync as existsSync9 } from "node:fs";
5941
5970
  import { dirname as dirname7, join as join11 } from "node:path";
5942
5971
  import {
5943
5972
  applyAudioFxChain,
@@ -6060,8 +6089,9 @@ var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
6060
6089
  var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
6061
6090
  var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
6062
6091
  var MAX_TRUE_PEAK_CORRECTION_PASSES = 3;
6092
+ var gainFilter = (gainDb) => `volume=${gainDb.toFixed(3)}dB`;
6063
6093
  function aacTruePeakCorrectionFilter(targetDurationSeconds, attenuationDb) {
6064
- return `volume=${attenuationDb.toFixed(3)}dB,apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
6094
+ return `${gainFilter(attenuationDb)},apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
6065
6095
  }
6066
6096
  function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
6067
6097
  const delta = targetDurationSeconds - sourceDurationSeconds;
@@ -6099,7 +6129,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
6099
6129
  const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
6100
6130
  const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
6101
6131
  const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
6102
- const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal);
6132
+ const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath);
6103
6133
  const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg || input.encodeAac ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
6104
6134
  const [videoResult, audioResult] = await Promise.allSettled([
6105
6135
  probeVideo(input.videoPath),
@@ -6147,7 +6177,12 @@ async function padOrTrimAudioToVideoFrameCount(input) {
6147
6177
  );
6148
6178
  try {
6149
6179
  for (const step of plan.steps) {
6150
- const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(input.audioPath, input.outputPath, targetDurationSeconds, step.filter);
6180
+ const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
6181
+ input.sourcePath ?? input.audioPath,
6182
+ input.outputPath,
6183
+ targetDurationSeconds,
6184
+ step.filter
6185
+ );
6151
6186
  if (!ffmpegResult.success) {
6152
6187
  return {
6153
6188
  success: false,
@@ -6176,6 +6211,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
6176
6211
  if (probeTruePeak) {
6177
6212
  const correction = await enforceAacTruePeak({
6178
6213
  audioPath: input.outputPath,
6214
+ sourcePath: input.sourcePath,
6179
6215
  targetDurationSeconds,
6180
6216
  signal: input.signal,
6181
6217
  probeTruePeak,
@@ -6211,9 +6247,27 @@ async function constrainAacTruePeak(input) {
6211
6247
  targetDurationSeconds: input.targetDurationSeconds,
6212
6248
  signal: input.signal,
6213
6249
  probeTruePeak: input.probeTruePeakDbfs ?? ((audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal })),
6214
- encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
6250
+ encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath),
6251
+ rewriteSource: input.rewriteSource ?? ((sourcePath, outputPath, filter) => defaultRunFfmpeg(
6252
+ ["-i", sourcePath, "-af", filter, "-c:a", "pcm_f32le", "-y", outputPath],
6253
+ timeoutMs,
6254
+ input.signal
6255
+ ))
6215
6256
  });
6216
6257
  }
6258
+ async function cutSource(input, scratchDir, attenuationDb) {
6259
+ if (!input.sourcePath || !input.rewriteSource) return true;
6260
+ const cutPath = join10(scratchDir, "source.wav");
6261
+ try {
6262
+ const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
6263
+ if (!result.success) throw new Error(result.error);
6264
+ renameWithRetry5(cutPath, input.sourcePath, { copyFallback: true });
6265
+ return true;
6266
+ } catch {
6267
+ removeScratch(input.sourcePath);
6268
+ return false;
6269
+ }
6270
+ }
6217
6271
  async function enforceAacTruePeak(input) {
6218
6272
  let scratchDir;
6219
6273
  try {
@@ -6233,8 +6287,10 @@ async function enforceAacTruePeak(input) {
6233
6287
  }
6234
6288
  if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
6235
6289
  if (measuredPath !== correctedPath) return { success: true };
6290
+ const sourceCut = await cutSource(input, scratchDir, attenuationDb);
6236
6291
  renameWithRetry5(correctedPath, input.audioPath, { copyFallback: true });
6237
- return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
6292
+ const loweredDb = Number((-attenuationDb).toFixed(2));
6293
+ return sourceCut ? { success: true, loweredDb } : { success: true, loweredDb, sourceStale: true };
6238
6294
  }
6239
6295
  if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
6240
6296
  const headroomDb = AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB * 2 ** pass;
@@ -6244,7 +6300,7 @@ async function enforceAacTruePeak(input) {
6244
6300
  const referenceDbfs = Math.max(truePeakDbfs, (sourcePeakDbfs ?? -Infinity) + attenuationDb);
6245
6301
  attenuationDb += AAC_DELIVERY_TRUE_PEAK_DBFS - headroomDb - referenceDbfs;
6246
6302
  const result = await input.encode(
6247
- input.audioPath,
6303
+ input.sourcePath ?? input.audioPath,
6248
6304
  correctedPath,
6249
6305
  input.targetDurationSeconds,
6250
6306
  aacTruePeakCorrectionFilter(input.targetDurationSeconds, attenuationDb)
@@ -6417,7 +6473,7 @@ async function limitDeliveryPeaks(input, deps = {
6417
6473
  applyChain: applyAudioFxChain,
6418
6474
  encode: encodeAac2,
6419
6475
  rename: renameWithRetry6,
6420
- sourceExists: existsSync8
6476
+ sourceExists: existsSync9
6421
6477
  }) {
6422
6478
  const { audioPath, sourcePath, signal, timeoutMs } = input;
6423
6479
  if (!deps.sourceExists(sourcePath)) return { success: true };
@@ -6473,36 +6529,54 @@ async function runAudioStageScoped(input) {
6473
6529
  const stageStart = Date.now();
6474
6530
  const pcmSourcePath = join12(input.workDir, "audio-source.wav");
6475
6531
  const duration = input.totalFrames * input.fps.den / input.fps.num;
6532
+ let deliverySourcePath;
6476
6533
  try {
6477
6534
  const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
6478
6535
  let audioLoweredDb;
6479
6536
  if (result.hasAudio) {
6480
6537
  const guardStart = Date.now();
6481
- const limited = await limitDeliveryPeaks({
6482
- audioPath: result.audioOutputPath,
6483
- sourcePath: pcmSourcePath,
6484
- targetDurationSeconds: duration,
6485
- signal: input.abortSignal,
6486
- timeoutMs: input.ffmpegProcessTimeout,
6487
- warn: (message, details) => input.log?.warn?.(message, details)
6488
- });
6489
- if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
6490
- const peak = await constrainAacTruePeak({
6491
- audioPath: result.audioOutputPath,
6492
- sourcePath: pcmSourcePath,
6493
- targetDurationSeconds: duration,
6494
- signal: input.abortSignal,
6495
- timeoutMs: input.ffmpegProcessTimeout
6496
- });
6497
- if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
6498
- audioLoweredDb = peak.loweredDb;
6538
+ const guard = await guardDeliveryPeaks(
6539
+ input,
6540
+ result.audioOutputPath,
6541
+ pcmSourcePath,
6542
+ duration
6543
+ );
6544
+ audioLoweredDb = guard.loweredDb;
6499
6545
  input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
6546
+ if (input.keepDeliverySource && !guard.sourceStale && existsSync10(pcmSourcePath)) {
6547
+ deliverySourcePath = pcmSourcePath;
6548
+ }
6500
6549
  }
6501
- return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
6550
+ return {
6551
+ ...result,
6552
+ audioLoweredDb,
6553
+ deliverySourcePath,
6554
+ audioProcessMs: Date.now() - stageStart
6555
+ };
6502
6556
  } finally {
6503
- removeScratch2(pcmSourcePath);
6557
+ if (!deliverySourcePath) removeScratch2(pcmSourcePath);
6504
6558
  }
6505
6559
  }
6560
+ async function guardDeliveryPeaks(input, audioPath, sourcePath, duration) {
6561
+ const limited = await limitDeliveryPeaks({
6562
+ audioPath,
6563
+ sourcePath,
6564
+ targetDurationSeconds: duration,
6565
+ signal: input.abortSignal,
6566
+ timeoutMs: input.ffmpegProcessTimeout,
6567
+ warn: (message, details) => input.log?.warn?.(message, details)
6568
+ });
6569
+ if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
6570
+ const peak = await constrainAacTruePeak({
6571
+ audioPath,
6572
+ sourcePath,
6573
+ targetDurationSeconds: duration,
6574
+ signal: input.abortSignal,
6575
+ timeoutMs: input.ffmpegProcessTimeout
6576
+ });
6577
+ if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
6578
+ return { loweredDb: peak.loweredDb, sourceStale: peak.sourceStale };
6579
+ }
6506
6580
  async function mixAudioForDelivery(input, duration, pcmSourcePath) {
6507
6581
  const {
6508
6582
  projectDir,
@@ -6597,7 +6671,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
6597
6671
  }
6598
6672
 
6599
6673
  // src/services/render/stages/captureStage.ts
6600
- import { existsSync as existsSync9, readdirSync, statfsSync } from "node:fs";
6674
+ import { existsSync as existsSync11, readdirSync, statfsSync } from "node:fs";
6601
6675
  import { join as join13 } from "node:path";
6602
6676
  import {
6603
6677
  classifyCaptureFailure as classifyCaptureFailure2,
@@ -6649,7 +6723,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
6649
6723
  }
6650
6724
  function measureCaptureFrameBytes(workDir, framesDir) {
6651
6725
  let total = sampleDirectoryBytes(framesDir);
6652
- if (!existsSync9(workDir)) return total;
6726
+ if (!existsSync11(workDir)) return total;
6653
6727
  for (const attempt of readdirSync(workDir)) {
6654
6728
  if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
6655
6729
  total += sampleDirectoryBytes(join13(workDir, attempt));
@@ -6986,13 +7060,13 @@ import {
6986
7060
  // src/services/render/stages/encodeStage.ts
6987
7061
  import {
6988
7062
  copyFileSync as copyFileSync4,
6989
- existsSync as existsSync10,
7063
+ existsSync as existsSync12,
6990
7064
  mkdirSync as mkdirSync5,
6991
7065
  readdirSync as readdirSync2,
6992
7066
  readFileSync as readFileSync5,
6993
7067
  rmSync as rmSync4,
6994
7068
  statSync as statSync4,
6995
- writeFileSync as writeFileSync3
7069
+ writeFileSync as writeFileSync4
6996
7070
  } from "node:fs";
6997
7071
  import { readFile as readFile2 } from "node:fs/promises";
6998
7072
  import { dirname as dirname8, join as join14 } from "node:path";
@@ -7088,7 +7162,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
7088
7162
  const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
7089
7163
  return gifEncodeFailure(startTime, outputPath, error);
7090
7164
  }
7091
- writeFileSync3(outputPath, gif);
7165
+ writeFileSync4(outputPath, gif);
7092
7166
  return null;
7093
7167
  }
7094
7168
  async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
@@ -7120,7 +7194,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
7120
7194
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
7121
7195
  if (failure) return failure;
7122
7196
  }
7123
- const fileSize = existsSync10(outputPath) ? statSync4(outputPath).size : 0;
7197
+ const fileSize = existsSync12(outputPath) ? statSync4(outputPath).size : 0;
7124
7198
  return {
7125
7199
  success: true,
7126
7200
  outputPath,
@@ -7281,7 +7355,7 @@ async function runEncodeStage(input) {
7281
7355
  const stage5Start = Date.now();
7282
7356
  if (isPngSequence) {
7283
7357
  const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
7284
- if (!existsSync10(outputPath)) mkdirSync5(outputPath, { recursive: true });
7358
+ if (!existsSync12(outputPath)) mkdirSync5(outputPath, { recursive: true });
7285
7359
  const captured = readdirSync2(framesDir).filter((name) => name.endsWith(".png")).sort();
7286
7360
  if (captured.length === 0) {
7287
7361
  throw new Error(
@@ -7292,7 +7366,7 @@ async function runEncodeStage(input) {
7292
7366
  const dst = join14(outputPath, formatExportFrameName(i, "png"));
7293
7367
  copyFileSync4(join14(framesDir, name), dst);
7294
7368
  });
7295
- if (hasAudio && audioOutputPath && existsSync10(audioOutputPath)) {
7369
+ if (hasAudio && audioOutputPath && existsSync12(audioOutputPath)) {
7296
7370
  copyFileSync4(audioOutputPath, join14(outputPath, MIXED_AUDIO_FILENAME2));
7297
7371
  log.info(
7298
7372
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
@@ -7448,18 +7522,14 @@ function hasUsableVideoDimensions(metadata) {
7448
7522
  return Number.isFinite(metadata.width) && Number.isFinite(metadata.height) && metadata.width > 0 && metadata.height > 0;
7449
7523
  }
7450
7524
  function collectVideoMetadataHints(extractedVideos) {
7451
- return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({
7452
- id: video.videoId,
7453
- width: video.metadata.width,
7454
- height: video.metadata.height
7455
- })).sort((a, b) => a.id.localeCompare(b.id));
7525
+ return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({ id: video.videoId, ...displaySize(video.metadata) })).sort((a, b) => a.id.localeCompare(b.id));
7456
7526
  }
7457
7527
  function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
7458
7528
  const ranges = [];
7459
7529
  let rangeStart = null;
7460
7530
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
7461
7531
  const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
7462
- const missing = !existsSync11(framePath) || statSync5(framePath).size <= 8;
7532
+ const missing = !existsSync13(framePath) || statSync5(framePath).size <= 8;
7463
7533
  if (missing && rangeStart === null) {
7464
7534
  rangeStart = frameIndex;
7465
7535
  } else if (!missing && rangeStart !== null) {
@@ -7549,7 +7619,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
7549
7619
  let captured = 0;
7550
7620
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
7551
7621
  const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
7552
- if (existsSync11(framePath)) captured++;
7622
+ if (existsSync13(framePath)) captured++;
7553
7623
  }
7554
7624
  return captured;
7555
7625
  }
@@ -7776,7 +7846,7 @@ import {
7776
7846
  } from "@hyperframes/engine";
7777
7847
 
7778
7848
  // src/services/distributed/chunkFrames.ts
7779
- import { existsSync as existsSync13, readdirSync as readdirSync4 } from "node:fs";
7849
+ import { existsSync as existsSync15, readdirSync as readdirSync4 } from "node:fs";
7780
7850
  import { extname as extname2, join as join17 } from "node:path";
7781
7851
  import {
7782
7852
  compositionRequiresWebGpu as compositionRequiresWebGpu3,
@@ -7788,7 +7858,7 @@ import {
7788
7858
  // src/services/distributed/shared.ts
7789
7859
  import { execFile as execFileCallback } from "node:child_process";
7790
7860
  import { dirname as dirname10, join as join16 } from "node:path";
7791
- import { existsSync as existsSync12, readFileSync as readFileSync6 } from "node:fs";
7861
+ import { existsSync as existsSync14, readFileSync as readFileSync6 } from "node:fs";
7792
7862
  import { fileURLToPath as fileURLToPath3 } from "node:url";
7793
7863
  import { promisify as promisify2 } from "node:util";
7794
7864
  import {
@@ -7804,7 +7874,7 @@ function isPlanAudioArtifactPath(path) {
7804
7874
  function resolvePlanAudioPath(planDir) {
7805
7875
  for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
7806
7876
  const candidate = join16(planDir, name);
7807
- if (existsSync12(candidate)) return candidate;
7877
+ if (existsSync14(candidate)) return candidate;
7808
7878
  }
7809
7879
  return null;
7810
7880
  }
@@ -8065,7 +8135,7 @@ function readProducerVersion() {
8065
8135
  let current = startDir;
8066
8136
  for (let i = 0; i < 10; i++) {
8067
8137
  const candidate = join16(current, "package.json");
8068
- if (existsSync12(candidate)) {
8138
+ if (existsSync14(candidate)) {
8069
8139
  try {
8070
8140
  const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
8071
8141
  if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
@@ -8119,7 +8189,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
8119
8189
  const result = [];
8120
8190
  for (const v of videos) {
8121
8191
  const outputDir = join17(planDir, "video-frames", v.videoId);
8122
- if (!existsSync13(outputDir)) {
8192
+ if (!existsSync15(outputDir)) {
8123
8193
  throw new Error(
8124
8194
  `[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
8125
8195
  );
@@ -8235,7 +8305,7 @@ async function extractDeferredVideoFramesForChunk(input) {
8235
8305
  const range = video.deferredRange;
8236
8306
  const remoteUrl = remoteSources[range.sourcePath];
8237
8307
  const sourcePath = remoteUrl ?? join17(input.planDir, ...range.sourcePath.split("/"));
8238
- if (remoteUrl === void 0 && !existsSync13(sourcePath)) {
8308
+ if (remoteUrl === void 0 && !existsSync15(sourcePath)) {
8239
8309
  throw new RenderChunkValidationError(
8240
8310
  MISSING_PLAN_ARTIFACT,
8241
8311
  `[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
@@ -8453,7 +8523,7 @@ async function readPlanReferenceBackground(input) {
8453
8523
  }
8454
8524
 
8455
8525
  // src/services/render/stages/freezePlan.ts
8456
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
8526
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
8457
8527
  import { join as join20, relative as relative3, resolve as resolve8 } from "node:path";
8458
8528
  import {
8459
8529
  REFERENCE_BACKGROUND_FIELD,
@@ -8647,7 +8717,7 @@ function planFileSha256(absolutePath, digests) {
8647
8717
  return sha256;
8648
8718
  }
8649
8719
  async function prehashPlanFiles(dir, digests) {
8650
- if (!existsSync14(dir)) return;
8720
+ if (!existsSync16(dir)) return;
8651
8721
  for (const { absolutePath } of listPlanFiles(dir)) {
8652
8722
  if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
8653
8723
  }
@@ -8713,19 +8783,19 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
8713
8783
  function writePlanRemoteFiles(planDir, files) {
8714
8784
  if (files.length === 0) return;
8715
8785
  const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
8716
- writeFileSync5(join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
8786
+ writeFileSync6(join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
8717
8787
  }
8718
8788
  function readPlanRemoteFiles(planDir) {
8719
8789
  const path = join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
8720
- return existsSync14(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
8790
+ return existsSync16(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
8721
8791
  }
8722
8792
  function recomputePlanHashFromPlanDir(planDir, digests) {
8723
8793
  const planJsonPath = join20(planDir, "plan.json");
8724
8794
  const encoderJsonPath = join20(planDir, "meta", "encoder.json");
8725
- if (!existsSync14(planJsonPath)) {
8795
+ if (!existsSync16(planJsonPath)) {
8726
8796
  throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
8727
8797
  }
8728
- if (!existsSync14(encoderJsonPath)) {
8798
+ if (!existsSync16(encoderJsonPath)) {
8729
8799
  throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
8730
8800
  }
8731
8801
  const planJson = JSON.parse(readFileSync7(planJsonPath, "utf-8"));
@@ -8755,12 +8825,12 @@ async function freezePlan(input) {
8755
8825
  hasAudio,
8756
8826
  digests
8757
8827
  } = input;
8758
- if (!existsSync14(planDir)) {
8828
+ if (!existsSync16(planDir)) {
8759
8829
  throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
8760
8830
  }
8761
8831
  const metaDir = join20(planDir, "meta");
8762
- if (!existsSync14(metaDir)) mkdirSync7(metaDir, { recursive: true });
8763
- writeFileSync5(
8832
+ if (!existsSync16(metaDir)) mkdirSync7(metaDir, { recursive: true });
8833
+ writeFileSync6(
8764
8834
  join20(metaDir, "composition.json"),
8765
8835
  `${JSON.stringify(composition, null, 2)}
8766
8836
  `,
@@ -8768,8 +8838,8 @@ async function freezePlan(input) {
8768
8838
  );
8769
8839
  const encoderForCanonical = stripUndefined(encoder);
8770
8840
  const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
8771
- writeFileSync5(join20(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
8772
- writeFileSync5(join20(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
8841
+ writeFileSync6(join20(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
8842
+ writeFileSync6(join20(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
8773
8843
  `, "utf-8");
8774
8844
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
8775
8845
  const planHash = computePlanHash({
@@ -8795,7 +8865,7 @@ async function freezePlan(input) {
8795
8865
  hasAudio
8796
8866
  };
8797
8867
  const planJsonPath = join20(planDir, "plan.json");
8798
- writeFileSync5(planJsonPath, `${JSON.stringify(planJson, null, 2)}
8868
+ writeFileSync6(planJsonPath, `${JSON.stringify(planJson, null, 2)}
8799
8869
  `, "utf-8");
8800
8870
  return { planJsonPath, planHash };
8801
8871
  }
@@ -8836,8 +8906,8 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
8836
8906
  }
8837
8907
 
8838
8908
  // src/services/distributed/chunkAudio.ts
8839
- import { closeSync as closeSync4, existsSync as existsSync15, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
8840
- import { writeFileSync as writeFileSync6, writeSync } from "node:fs";
8909
+ import { closeSync as closeSync4, existsSync as existsSync17, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
8910
+ import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
8841
8911
  import { join as join22 } from "node:path";
8842
8912
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
8843
8913
 
@@ -9319,15 +9389,15 @@ async function writeChunkAudioSlices(input) {
9319
9389
  const start = offsets[cut.firstPacket];
9320
9390
  layout.partStarts[index] = start;
9321
9391
  const end = offsets[last] + track.sizes[last];
9322
- writeFileSync6(join22(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
9392
+ writeFileSync7(join22(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
9323
9393
  });
9324
9394
  const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
9325
- writeFileSync6(join22(input.planDir, SLICE_FRAME), Buffer.concat(frame));
9326
- writeFileSync6(join22(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
9395
+ writeFileSync7(join22(input.planDir, SLICE_FRAME), Buffer.concat(frame));
9396
+ writeFileSync7(join22(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
9327
9397
  }
9328
9398
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
9329
9399
  const layoutPath = join22(planDir, SLICE_LAYOUT);
9330
- if (!existsSync15(layoutPath)) return resolvePlanAudioPath(planDir);
9400
+ if (!existsSync17(layoutPath)) return resolvePlanAudioPath(planDir);
9331
9401
  const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
9332
9402
  const frame = readFileSync8(join22(planDir, SLICE_FRAME));
9333
9403
  const fd = openSync4(scratchPath, "w");
@@ -9891,7 +9961,7 @@ function createVideoSourceShipper(input) {
9891
9961
  let result = null;
9892
9962
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
9893
9963
  const rel = pathInside(root, srcPath);
9894
- if (rel !== null && (existsSync16(join24(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
9964
+ if (rel !== null && (existsSync18(join24(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
9895
9965
  result = ["compiled", ...rel.split(sep2)].join("/");
9896
9966
  break;
9897
9967
  }
@@ -9919,7 +9989,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
9919
9989
  useGpu: false,
9920
9990
  browserGpuMode: "software"
9921
9991
  });
9922
- if (!existsSync16(planDir)) mkdirSync10(planDir, { recursive: true });
9992
+ if (!existsSync18(planDir)) mkdirSync10(planDir, { recursive: true });
9923
9993
  const log = config.logger ?? defaultLogger;
9924
9994
  const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
9925
9995
  const abortSignal = config.abortSignal;
@@ -9949,7 +10019,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
9949
10019
  });
9950
10020
  const entryFile = config.entryFile ?? "index.html";
9951
10021
  const htmlPath = join24(projectDir, entryFile);
9952
- if (!existsSync16(htmlPath)) {
10022
+ if (!existsSync18(htmlPath)) {
9953
10023
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
9954
10024
  }
9955
10025
  const workDir = join24(planDir, ".plan-work");
@@ -9993,7 +10063,6 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
9993
10063
  urlByAbsentLocalPath,
9994
10064
  projectDir,
9995
10065
  workDir,
9996
- htmlPath,
9997
10066
  entryFile,
9998
10067
  job,
9999
10068
  cfg,
@@ -10178,15 +10247,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10178
10247
  logPlanStage(log, "extract", stageStarted);
10179
10248
  const stagedVideoFrames = join24(compiledDir, "__hyperframes_video_frames");
10180
10249
  const videoFramesDst = join24(planDir, "video-frames");
10181
- if (existsSync16(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
10250
+ if (existsSync18(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
10182
10251
  const videoSourcesDir = join24(planDir, "video-sources");
10183
10252
  rmSync7(videoSourcesDir, { recursive: true, force: true });
10184
- if (existsSync16(stagedVideoFrames)) {
10253
+ if (existsSync18(stagedVideoFrames)) {
10185
10254
  renameWithRetry8(stagedVideoFrames, videoFramesDst);
10186
10255
  } else {
10187
10256
  mkdirSync10(videoFramesDst, { recursive: true });
10188
10257
  }
10189
- if (existsSync16(finalCompiledDir)) rmSync7(finalCompiledDir, { recursive: true, force: true });
10258
+ if (existsSync18(finalCompiledDir)) rmSync7(finalCompiledDir, { recursive: true, force: true });
10190
10259
  renameWithRetry8(compiledDir, finalCompiledDir);
10191
10260
  const shipVideoSource = createVideoSourceShipper({
10192
10261
  planDir,
@@ -10225,7 +10294,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10225
10294
  sizeBytes
10226
10295
  }))
10227
10296
  );
10228
- writeFileSync7(
10297
+ writeFileSync8(
10229
10298
  join24(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
10230
10299
  JSON.stringify(planVideosJson, null, 2),
10231
10300
  "utf-8"
@@ -10306,10 +10375,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10306
10375
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
10307
10376
  }
10308
10377
  const planAudioPath = join24(planDir, PLAN_AUDIO_RELATIVE_PATH);
10309
- if (audioResult?.hasAudio && existsSync16(audioResult.audioOutputPath)) {
10378
+ if (audioResult?.hasAudio && existsSync18(audioResult.audioOutputPath)) {
10310
10379
  renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
10311
10380
  }
10312
- if (chunksCarryAudio && existsSync16(planAudioPath)) {
10381
+ if (chunksCarryAudio && existsSync18(planAudioPath)) {
10313
10382
  if (options.sliceChunkAudio) {
10314
10383
  stageStarted = Date.now();
10315
10384
  await writeChunkAudioSlices({
@@ -10422,7 +10491,7 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
10422
10491
  if (mixed.audioError) {
10423
10492
  applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
10424
10493
  }
10425
- if (!mixed.hasAudio || !existsSync16(mixed.audioOutputPath)) return { hasAudio: false };
10494
+ if (!mixed.hasAudio || !existsSync18(mixed.audioOutputPath)) return { hasAudio: false };
10426
10495
  renameWithRetry8(mixed.audioOutputPath, outputPath);
10427
10496
  return { hasAudio: true };
10428
10497
  } finally {
@@ -10437,7 +10506,7 @@ import {
10437
10506
  } from "@hyperframes/engine";
10438
10507
  import {
10439
10508
  copyFileSync as copyFileSync8,
10440
- existsSync as existsSync18,
10509
+ existsSync as existsSync20,
10441
10510
  lstatSync as lstatSync2,
10442
10511
  mkdirSync as mkdirSync12,
10443
10512
  mkdtempSync as mkdtempSync4,
@@ -10445,7 +10514,7 @@ import {
10445
10514
  readdirSync as readdirSync7,
10446
10515
  rmSync as rmSync9,
10447
10516
  statSync as statSync7,
10448
- writeFileSync as writeFileSync9
10517
+ writeFileSync as writeFileSync10
10449
10518
  } from "node:fs";
10450
10519
  import { tmpdir } from "node:os";
10451
10520
  import { dirname as dirname13, isAbsolute as isAbsolute4, join as join27, relative as relative6, resolve as resolve9, sep as sep3 } from "node:path";
@@ -10455,13 +10524,13 @@ import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as r
10455
10524
  // src/services/distributed/planV2Publisher.ts
10456
10525
  import {
10457
10526
  copyFileSync as copyFileSync7,
10458
- existsSync as existsSync17,
10527
+ existsSync as existsSync19,
10459
10528
  linkSync as linkSync2,
10460
10529
  mkdirSync as mkdirSync11,
10461
10530
  mkdtempSync as mkdtempSync3,
10462
10531
  rmSync as rmSync8,
10463
10532
  statSync as statSync6,
10464
- writeFileSync as writeFileSync8
10533
+ writeFileSync as writeFileSync9
10465
10534
  } from "node:fs";
10466
10535
  import { renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
10467
10536
  import { dirname as dirname12, join as join26 } from "node:path";
@@ -10524,7 +10593,7 @@ var LocalPlanV2ArtifactPublisher = class {
10524
10593
  #linkFile;
10525
10594
  #committed = false;
10526
10595
  constructor(destinationDir, options = {}) {
10527
- if (existsSync17(destinationDir)) {
10596
+ if (existsSync19(destinationDir)) {
10528
10597
  throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
10529
10598
  }
10530
10599
  this.destinationDir = destinationDir;
@@ -10541,7 +10610,7 @@ var LocalPlanV2ArtifactPublisher = class {
10541
10610
  );
10542
10611
  }
10543
10612
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
10544
- if (existsSync17(destinationPath)) return;
10613
+ if (existsSync19(destinationPath)) return;
10545
10614
  mkdirSync11(dirname12(destinationPath), { recursive: true });
10546
10615
  const stagingDir = mkdtempSync3(join26(dirname12(destinationPath), ".plan-v2-blob-"));
10547
10616
  const temporaryPath = join26(stagingDir, "blob");
@@ -10559,13 +10628,13 @@ var LocalPlanV2ArtifactPublisher = class {
10559
10628
  }
10560
10629
  async commitManifest(manifestBytes) {
10561
10630
  for (const digest of new Set(manifestDigests(manifestBytes))) {
10562
- if (!existsSync17(planV2BlobPath(this.temporaryDir, digest))) {
10631
+ if (!existsSync19(planV2BlobPath(this.temporaryDir, digest))) {
10563
10632
  throw new PlanV2IntegrityError(
10564
10633
  `cannot commit manifest before referenced blob is durable: ${digest}`
10565
10634
  );
10566
10635
  }
10567
10636
  }
10568
- writeFileSync8(join26(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
10637
+ writeFileSync9(join26(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
10569
10638
  renameWithRetry9(this.temporaryDir, this.destinationDir);
10570
10639
  this.#committed = true;
10571
10640
  }
@@ -10638,7 +10707,7 @@ function assertValidExtractionCacheCompleteSentinel(path) {
10638
10707
  }
10639
10708
  function validateExtractionCacheCompleteSentinels(executionPlanDir) {
10640
10709
  const videoRoot = join27(executionPlanDir, "video-frames");
10641
- if (!existsSync18(videoRoot)) return;
10710
+ if (!existsSync20(videoRoot)) return;
10642
10711
  for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
10643
10712
  if (!videoEntry.isDirectory()) continue;
10644
10713
  const videoDir = join27(videoRoot, videoEntry.name);
@@ -10739,7 +10808,7 @@ function parsePlanVideosJson2(value) {
10739
10808
  }
10740
10809
  function materializeExtractedVideoDirectories(planDir) {
10741
10810
  const videosPath = join27(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10742
- if (!existsSync18(videosPath)) return;
10811
+ if (!existsSync20(videosPath)) return;
10743
10812
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
10744
10813
  for (const video of videos.extracted) {
10745
10814
  mkdirSync12(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
@@ -10773,9 +10842,9 @@ function parseChunkSlices(value) {
10773
10842
  }
10774
10843
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
10775
10844
  const videoRoot = join27(executionPlanDir, "video-frames");
10776
- const hasExtractedFrames = existsSync18(videoRoot) && listFiles(videoRoot).length > 0;
10845
+ const hasExtractedFrames = existsSync20(videoRoot) && listFiles(videoRoot).length > 0;
10777
10846
  const videosPath = join27(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10778
- if (!existsSync18(videosPath)) {
10847
+ if (!existsSync20(videosPath)) {
10779
10848
  return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
10780
10849
  }
10781
10850
  const chunksPath = join27(executionPlanDir, "meta", "chunks.json");
@@ -10832,7 +10901,7 @@ function computeManifestHash(manifest) {
10832
10901
  return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
10833
10902
  }
10834
10903
  function writeBlob(sourcePath, destinationPath) {
10835
- if (existsSync18(destinationPath)) return;
10904
+ if (existsSync20(destinationPath)) return;
10836
10905
  mkdirSync12(dirname13(destinationPath), { recursive: true });
10837
10906
  const temporaryDir = mkdtempSync4(join27(dirname13(destinationPath), ".plan-v2-blob-"));
10838
10907
  const temporaryPath = join27(temporaryDir, "blob");
@@ -10872,7 +10941,7 @@ async function putTasks(tasks) {
10872
10941
  }
10873
10942
  function buildPlanV2Publication(executionPlanDir, digests) {
10874
10943
  const executionPlanPath = join27(executionPlanDir, "plan.json");
10875
- if (!existsSync18(executionPlanPath)) {
10944
+ if (!existsSync20(executionPlanPath)) {
10876
10945
  throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
10877
10946
  }
10878
10947
  const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
@@ -10952,7 +11021,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
10952
11021
  };
10953
11022
  }
10954
11023
  function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
10955
- if (existsSync18(planV2Dir)) {
11024
+ if (existsSync20(planV2Dir)) {
10956
11025
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
10957
11026
  }
10958
11027
  const publication = buildPlanV2Publication(executionPlanDir);
@@ -10965,7 +11034,7 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
10965
11034
  for (const blob of publication.blobs) {
10966
11035
  writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
10967
11036
  }
10968
- writeFileSync9(
11037
+ writeFileSync10(
10969
11038
  join27(tempDir, "plan.json"),
10970
11039
  canonicalJsonStringify(publication.manifest),
10971
11040
  "utf-8"
@@ -11038,7 +11107,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
11038
11107
  }
11039
11108
  }
11040
11109
  async function planV2(projectDir, config, planV2Dir) {
11041
- if (existsSync18(planV2Dir)) {
11110
+ if (existsSync20(planV2Dir)) {
11042
11111
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
11043
11112
  }
11044
11113
  const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
@@ -11192,7 +11261,7 @@ function parsePlanV2Manifest(value) {
11192
11261
  }
11193
11262
  function readPlanV2Manifest(planV2Dir) {
11194
11263
  const manifestPath = join27(planV2Dir, "plan.json");
11195
- if (!existsSync18(manifestPath)) {
11264
+ if (!existsSync20(manifestPath)) {
11196
11265
  throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
11197
11266
  }
11198
11267
  const value = readJsonFile(manifestPath, "v2 manifest");
@@ -11211,7 +11280,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
11211
11280
  }
11212
11281
  function verifyBlob(planV2Dir, artifact) {
11213
11282
  const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
11214
- if (!existsSync18(sourcePath)) {
11283
+ if (!existsSync20(sourcePath)) {
11215
11284
  throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
11216
11285
  }
11217
11286
  const stats = statSync7(sourcePath);
@@ -11224,7 +11293,7 @@ function verifyBlob(planV2Dir, artifact) {
11224
11293
  return sourcePath;
11225
11294
  }
11226
11295
  function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
11227
- if (existsSync18(destinationDir)) {
11296
+ if (existsSync20(destinationDir)) {
11228
11297
  throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
11229
11298
  }
11230
11299
  const manifest = readPlanV2Manifest(planV2Dir);
@@ -11245,7 +11314,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
11245
11314
  copyFileSync8(sourcePath, destinationPath);
11246
11315
  }
11247
11316
  if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
11248
- writeFileSync9(
11317
+ writeFileSync10(
11249
11318
  join27(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
11250
11319
  canonicalJsonStringify({
11251
11320
  manifest,
@@ -11289,7 +11358,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
11289
11358
  }
11290
11359
  function readValidatedPlanV2Materialization(planDir, expectedTarget) {
11291
11360
  const markerPath = join27(planDir, PLAN_V2_MATERIALIZATION_MARKER);
11292
- if (!existsSync18(markerPath)) return null;
11361
+ if (!existsSync20(markerPath)) return null;
11293
11362
  const marker = readJsonFile(markerPath, "materialization marker");
11294
11363
  if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
11295
11364
  throw new PlanV2IntegrityError("malformed materialization marker");
@@ -11307,7 +11376,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
11307
11376
  for (const artifact of required) {
11308
11377
  if (Object.hasOwn(remoteSources, artifact.path)) continue;
11309
11378
  const path = join27(planDir, ...artifact.path.split("/"));
11310
- if (!existsSync18(path) || statSync7(path).size !== artifact.sizeBytes) {
11379
+ if (!existsSync20(path) || statSync7(path).size !== artifact.sizeBytes) {
11311
11380
  throw new PlanV2IntegrityError(
11312
11381
  `materialized artifact missing or truncated: ${artifact.path}`
11313
11382
  );
@@ -11331,14 +11400,14 @@ import {
11331
11400
  } from "@hyperframes/engine";
11332
11401
  import {
11333
11402
  cpSync as cpSync3,
11334
- existsSync as existsSync19,
11403
+ existsSync as existsSync21,
11335
11404
  mkdirSync as mkdirSync13,
11336
11405
  mkdtempSync as mkdtempSync5,
11337
11406
  readFileSync as readFileSync10,
11338
11407
  readdirSync as readdirSync8,
11339
11408
  rmSync as rmSync10,
11340
11409
  statSync as statSync8,
11341
- writeFileSync as writeFileSync10
11410
+ writeFileSync as writeFileSync11
11342
11411
  } from "node:fs";
11343
11412
  import { dirname as dirname14, join as join28 } from "node:path";
11344
11413
  import {
@@ -11386,7 +11455,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11386
11455
  }
11387
11456
  const format = plan.dimensions.format;
11388
11457
  const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
11389
- if (!existsSync19(dirname14(outputPath))) {
11458
+ if (!existsSync21(dirname14(outputPath))) {
11390
11459
  mkdirSync13(dirname14(outputPath), { recursive: true });
11391
11460
  }
11392
11461
  const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
@@ -11410,7 +11479,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11410
11479
  try {
11411
11480
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
11412
11481
  const finalPath = join28(workDir, `output.${format}`);
11413
- const audio = audioPath !== null && existsSync19(audioPath) ? audioPath : null;
11482
+ const audio = audioPath !== null && existsSync21(audioPath) ? audioPath : null;
11414
11483
  await writeJoined(ctx, videoSources, audio, finalPath);
11415
11484
  renameWithRetry11(finalPath, outputPath);
11416
11485
  log.info("[assemble] stage timings", {
@@ -11428,7 +11497,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11428
11497
  });
11429
11498
  }
11430
11499
  }
11431
- const fileSize = existsSync19(outputPath) ? statSync8(outputPath).size : 0;
11500
+ const fileSize = existsSync21(outputPath) ? statSync8(outputPath).size : 0;
11432
11501
  return {
11433
11502
  ...referenceBackgroundResult6(plan[REFERENCE_BACKGROUND_FIELD4]),
11434
11503
  outputPath,
@@ -11440,13 +11509,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11440
11509
  function readAssemblePlan(planDir, chunkPaths) {
11441
11510
  const planJsonPath = join28(planDir, "plan.json");
11442
11511
  const chunksJsonPath = join28(planDir, "meta", "chunks.json");
11443
- if (!existsSync19(planJsonPath)) {
11512
+ if (!existsSync21(planJsonPath)) {
11444
11513
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
11445
11514
  }
11446
11515
  const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
11447
11516
  readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
11448
11517
  validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
11449
- if (!existsSync19(chunksJsonPath)) {
11518
+ if (!existsSync21(chunksJsonPath)) {
11450
11519
  throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
11451
11520
  }
11452
11521
  const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
@@ -11461,7 +11530,7 @@ function readAssemblePlan(planDir, chunkPaths) {
11461
11530
  }
11462
11531
  }
11463
11532
  for (const path of chunkPaths.flat()) {
11464
- if (!existsSync19(path)) {
11533
+ if (!existsSync21(path)) {
11465
11534
  throw new Error(`[assemble] chunk path does not exist: ${path}`);
11466
11535
  }
11467
11536
  }
@@ -11559,7 +11628,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
11559
11628
  function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
11560
11629
  const listPath = join28(workDir, "concat-list.txt");
11561
11630
  const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
11562
- writeFileSync10(listPath, `${body}
11631
+ writeFileSync11(listPath, `${body}
11563
11632
  `, "utf-8");
11564
11633
  return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
11565
11634
  }
@@ -11600,7 +11669,7 @@ function planFormat(planDir) {
11600
11669
  }
11601
11670
  function lockedAudio(planDir) {
11602
11671
  const encoderJsonPath = join28(planDir, "meta", "encoder.json");
11603
- if (!existsSync19(encoderJsonPath)) return {};
11672
+ if (!existsSync21(encoderJsonPath)) return {};
11604
11673
  return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
11605
11674
  }
11606
11675
  function readCfrEncoder(planDir, format) {
@@ -11610,7 +11679,7 @@ function readCfrEncoder(planDir, format) {
11610
11679
  );
11611
11680
  }
11612
11681
  const encoderJsonPath = join28(planDir, "meta", "encoder.json");
11613
- if (!existsSync19(encoderJsonPath)) {
11682
+ if (!existsSync21(encoderJsonPath)) {
11614
11683
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
11615
11684
  }
11616
11685
  const encoderJson = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
@@ -11622,7 +11691,7 @@ function readCfrEncoder(planDir, format) {
11622
11691
  return encoderJson;
11623
11692
  }
11624
11693
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
11625
- if (existsSync19(outputPath)) rmSync10(outputPath, { recursive: true, force: true });
11694
+ if (existsSync21(outputPath)) rmSync10(outputPath, { recursive: true, force: true });
11626
11695
  mkdirSync13(outputPath, { recursive: true });
11627
11696
  let globalIdx = 0;
11628
11697
  for (const chunkDir of chunkPaths) {
@@ -11646,7 +11715,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11646
11715
  `[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
11647
11716
  );
11648
11717
  }
11649
- if (audioPath !== null && existsSync19(audioPath)) {
11718
+ if (audioPath !== null && existsSync21(audioPath)) {
11650
11719
  const sidecar = join28(outputPath, MIXED_AUDIO_FILENAME4);
11651
11720
  cpSync3(audioPath, sidecar);
11652
11721
  }
@@ -11667,7 +11736,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11667
11736
 
11668
11737
  // src/services/distributed/renderChunk.ts
11669
11738
  import { randomBytes } from "node:crypto";
11670
- import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
11739
+ import { existsSync as existsSync22, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
11671
11740
  import { cpus } from "node:os";
11672
11741
  import { join as join29 } from "node:path";
11673
11742
  import {
@@ -11813,7 +11882,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11813
11882
  const planJsonPath = join29(planDir, "plan.json");
11814
11883
  const encoderJsonPath = join29(planDir, "meta", "encoder.json");
11815
11884
  const chunksJsonPath = join29(planDir, "meta", "chunks.json");
11816
- if (!existsSync20(planJsonPath)) {
11885
+ if (!existsSync22(planJsonPath)) {
11817
11886
  throw new RenderChunkValidationError(
11818
11887
  MISSING_PLAN_ARTIFACT,
11819
11888
  `[renderChunk] planDir is missing required artifact: ${planJsonPath}`
@@ -11829,7 +11898,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11829
11898
  const v2Manifest = v2Materialization?.manifest ?? null;
11830
11899
  const remoteSources = v2Materialization?.remoteSources ?? {};
11831
11900
  for (const required of [encoderJsonPath, chunksJsonPath]) {
11832
- if (!existsSync20(required)) {
11901
+ if (!existsSync22(required)) {
11833
11902
  throw new RenderChunkValidationError(
11834
11903
  MISSING_PLAN_ARTIFACT,
11835
11904
  `[renderChunk] planDir is missing required artifact: ${required}`
@@ -11840,7 +11909,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11840
11909
  const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
11841
11910
  const videosJsonPath = join29(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
11842
11911
  let planVideos = null;
11843
- if (existsSync20(videosJsonPath)) {
11912
+ if (existsSync22(videosJsonPath)) {
11844
11913
  try {
11845
11914
  planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync11(videosJsonPath, "utf-8")));
11846
11915
  } catch (err) {
@@ -11871,7 +11940,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11871
11940
  const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
11872
11941
  const framesInChunk = range.endFrame - range.startFrame;
11873
11942
  const compiledDir = join29(planDir, "compiled");
11874
- if (!existsSync20(compiledDir)) {
11943
+ if (!existsSync22(compiledDir)) {
11875
11944
  throw new RenderChunkValidationError(
11876
11945
  MISSING_PLAN_ARTIFACT,
11877
11946
  `[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
@@ -12121,10 +12190,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
12121
12190
  const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
12122
12191
  const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
12123
12192
  if (isPngSequence) {
12124
- if (!existsSync20(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
12193
+ if (!existsSync22(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
12125
12194
  } else {
12126
12195
  const outDir = join29(outputChunkPath, "..");
12127
- if (!existsSync20(outDir)) mkdirSync14(outDir, { recursive: true });
12196
+ if (!existsSync22(outDir)) mkdirSync14(outDir, { recursive: true });
12128
12197
  }
12129
12198
  const encodeInput = {
12130
12199
  job,
@@ -12334,7 +12403,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
12334
12403
  producerVersion: plan.producerVersion,
12335
12404
  ffmpegVersion
12336
12405
  };
12337
- writeFileSync11(perfPath, `${JSON.stringify(perfPayload, null, 2)}
12406
+ writeFileSync12(perfPath, `${JSON.stringify(perfPayload, null, 2)}
12338
12407
  `, "utf-8");
12339
12408
  try {
12340
12409
  rmSync11(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
@@ -12751,7 +12820,7 @@ var isMissing = (result) => result.error?.code === "ENOENT";
12751
12820
  var ffmpegIsMissing = async (signal) => isMissing(await runFfmpeg9(["-version"], { signal }));
12752
12821
 
12753
12822
  // src/services/distributed/probeFacts.ts
12754
- import { writeFileSync as writeFileSync12 } from "node:fs";
12823
+ import { writeFileSync as writeFileSync13 } from "node:fs";
12755
12824
  import { extname as extname6 } from "node:path";
12756
12825
  import { buildProbeFactsText } from "@hyperframes/engine";
12757
12826
  var PROBE_FACTS_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v", ".webm", ".mkv"]);
@@ -12760,7 +12829,7 @@ function wantsProbeFacts(path) {
12760
12829
  }
12761
12830
  async function buildProbeFacts(inputPath, outputPath, signal) {
12762
12831
  try {
12763
- writeFileSync12(outputPath, await buildProbeFactsText(inputPath, signal));
12832
+ writeFileSync13(outputPath, await buildProbeFactsText(inputPath, signal));
12764
12833
  return "built";
12765
12834
  } catch (error) {
12766
12835
  if (signal?.aborted) throw error;