@hyperframes/producer 0.8.198 → 0.8.200

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.
@@ -5,8 +5,8 @@ import {
5
5
  cpSync as cpSync2,
6
6
  existsSync as existsSync15,
7
7
  linkSync,
8
- mkdirSync as mkdirSync10,
9
- rmSync as rmSync9,
8
+ mkdirSync as mkdirSync9,
9
+ rmSync as rmSync8,
10
10
  writeFileSync as writeFileSync6
11
11
  } from "node:fs";
12
12
  import { extname as extname4, isAbsolute as isAbsolute3, join as join21, relative as relative5, sep as sep2 } from "node:path";
@@ -66,10 +66,10 @@ var defaultLogger = createConsoleLogger("info");
66
66
  import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
67
67
  import {
68
68
  existsSync as existsSync10,
69
- mkdirSync as mkdirSync6,
70
- readFileSync as readFileSync6,
69
+ mkdirSync as mkdirSync5,
70
+ readFileSync as readFileSync5,
71
71
  readdirSync as readdirSync3,
72
- rmSync as rmSync7,
72
+ rmSync as rmSync6,
73
73
  statSync as statSync4,
74
74
  writeFileSync as writeFileSync3,
75
75
  copyFileSync as copyFileSync4,
@@ -1297,17 +1297,6 @@ import {
1297
1297
  } from "@hyperframes/engine";
1298
1298
 
1299
1299
  // src/services/render/stages/captureHdrResources.ts
1300
- import {
1301
- closeSync as closeSync2,
1302
- constants as constants2,
1303
- fstatSync as fstatSync2,
1304
- mkdirSync as mkdirSync2,
1305
- mkdtempSync,
1306
- openSync as openSync2,
1307
- readFileSync as readFileSync2,
1308
- rmSync as rmSync2,
1309
- statfsSync
1310
- } from "node:fs";
1311
1300
  import {
1312
1301
  decodePngToRgb48le,
1313
1302
  extractMediaMetadata as extractMediaMetadata2,
@@ -1321,8 +1310,8 @@ import {
1321
1310
  runFfmpeg
1322
1311
  } from "@hyperframes/engine";
1323
1312
  import { fpsToFfmpegArg, fpsToNumber as fpsToNumber4 } from "@hyperframes/core";
1324
- var NO_FOLLOW_FLAG = constants2.O_NOFOLLOW ?? 0;
1325
1313
  var BYTES_PER_MIB = 1024 * 1024;
1314
+ var DEFAULT_HDR_WINDOW_BYTES = 512 * BYTES_PER_MIB;
1326
1315
 
1327
1316
  // src/services/render/stages/captureStreamingStage.ts
1328
1317
  import {
@@ -1476,7 +1465,7 @@ import { suggestMatchingPreset } from "@hyperframes/core";
1476
1465
  // src/services/htmlCompiler.ts
1477
1466
  import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
1478
1467
  import { createHash as createHash3 } from "node:crypto";
1479
- import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
1468
+ import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync3 } from "fs";
1480
1469
  import { join as join5, dirname as dirname4, resolve as resolve5, basename as basename3, relative as relative2 } from "path";
1481
1470
  import { parseHTML as parseHTML4 } from "linkedom";
1482
1471
  import {
@@ -1704,7 +1693,7 @@ import {
1704
1693
 
1705
1694
  // src/services/animatedGifPrep.ts
1706
1695
  import { createHash as createHash2 } from "node:crypto";
1707
- import { copyFileSync as copyFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, rmSync as rmSync3, statSync as statSync2 } from "node:fs";
1696
+ import { copyFileSync as copyFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
1708
1697
  import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4, resolve as resolve4 } from "node:path";
1709
1698
  import { parseHTML as parseHTML3 } from "linkedom";
1710
1699
  import { parseAnimatedGifMetadata } from "@hyperframes/core";
@@ -1862,10 +1851,10 @@ async function runAnimatedGifTranscode(request) {
1862
1851
  }
1863
1852
  async function ensurePreparedWebm(input) {
1864
1853
  if (!existsSync4(dirname3(input.cachePath))) {
1865
- mkdirSync3(dirname3(input.cachePath), { recursive: true });
1854
+ mkdirSync2(dirname3(input.cachePath), { recursive: true });
1866
1855
  }
1867
1856
  if (!existsSync4(dirname3(input.outputPath))) {
1868
- mkdirSync3(dirname3(input.outputPath), { recursive: true });
1857
+ mkdirSync2(dirname3(input.outputPath), { recursive: true });
1869
1858
  }
1870
1859
  if (!isUsableFile(input.cachePath)) {
1871
1860
  const tmpPath = `${input.cachePath}.tmp-${process.pid}-${Date.now()}`;
@@ -1889,10 +1878,10 @@ async function ensurePreparedWebm(input) {
1889
1878
  if (!isUsableFile(input.cachePath)) {
1890
1879
  renameWithRetry3(tmpPath, input.cachePath);
1891
1880
  } else {
1892
- rmSync3(tmpPath, { force: true });
1881
+ rmSync2(tmpPath, { force: true });
1893
1882
  }
1894
1883
  } catch (error) {
1895
- rmSync3(tmpPath, { force: true });
1884
+ rmSync2(tmpPath, { force: true });
1896
1885
  throw error;
1897
1886
  }
1898
1887
  }
@@ -1962,7 +1951,7 @@ async function prepareAnimatedGifInputs(html, options) {
1962
1951
  if (!hasGifExtension(src)) continue;
1963
1952
  const sourcePath = resolveGifSourcePath(src, options);
1964
1953
  if (!sourcePath) continue;
1965
- const bytes = readFileSync3(sourcePath);
1954
+ const bytes = readFileSync2(sourcePath);
1966
1955
  const metadata = parseAnimatedGifMetadata(bytes);
1967
1956
  if (!metadata?.animated) continue;
1968
1957
  const loop = resolveLoop(metadata, readLoopOverride(img));
@@ -2322,10 +2311,6 @@ var THREE_D_CONTEXT_PATTERN = /transform-style\s*:\s*preserve-3d|backface-visibi
2322
2311
  function detectThreeDTransformUsage(html) {
2323
2312
  return THREE_D_CONTEXT_PATTERN.test(html);
2324
2313
  }
2325
- var MIX_BLEND_MODE_PATTERN = /mix-blend-mode\s*:/i;
2326
- function detectMixBlendModeUsage(html) {
2327
- return MIX_BLEND_MODE_PATTERN.test(html);
2328
- }
2329
2314
  var BACKGROUND_IMAGE_DECL_PATTERN = /(?:^|;|\{)\s*background(?:-image)?\s*:[^;}]*(?:\bgradient\s*\(|url\s*\()/i;
2330
2315
  function detectAncestorBackgroundImage(html) {
2331
2316
  const { document: document2 } = parseHTML4(html);
@@ -2375,7 +2360,7 @@ function detectShaderTransitionUsage(html) {
2375
2360
  async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath) {
2376
2361
  let filePath = src;
2377
2362
  if (isHttpUrl(src)) {
2378
- if (!existsSync6(downloadDir)) mkdirSync4(downloadDir, { recursive: true });
2363
+ if (!existsSync6(downloadDir)) mkdirSync3(downloadDir, { recursive: true });
2379
2364
  try {
2380
2365
  filePath = await downloadToTemp(src, downloadDir, void 0, void 0, void 0, {
2381
2366
  onTelemetry: logRemoteDownloadTelemetry
@@ -2958,7 +2943,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
2958
2943
  var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
2959
2944
  async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
2960
2945
  if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
2961
- if (!existsSync6(remoteDir)) mkdirSync4(remoteDir, { recursive: true });
2946
+ if (!existsSync6(remoteDir)) mkdirSync3(remoteDir, { recursive: true });
2962
2947
  const urlToLocal = /* @__PURE__ */ new Map();
2963
2948
  await Promise.all(
2964
2949
  [...urlSet].map(async (url) => {
@@ -3304,7 +3289,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
3304
3289
  }
3305
3290
  async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
3306
3291
  const entryHtml = rebaseDirectEntryAssetPaths(
3307
- readFileSync4(htmlPath, "utf-8"),
3292
+ readFileSync3(htmlPath, "utf-8"),
3308
3293
  projectDir,
3309
3294
  htmlPath
3310
3295
  );
@@ -3338,7 +3323,6 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
3338
3323
  const renderModeHints = detectRenderModeHints(sanitizedHtml, options.pageMotionBlur);
3339
3324
  const hasShaderTransitions = detectShaderTransitionUsage(sanitizedHtml);
3340
3325
  const usesThreeDTransforms = detectThreeDTransformUsage(sanitizedHtml);
3341
- const usesMixBlendMode = detectMixBlendModeUsage(sanitizedHtml);
3342
3326
  const hasAncestorBackgroundImage = detectAncestorBackgroundImage(sanitizedHtml);
3343
3327
  const normalizedFontHtml = normalizeSystemFontPrimaryFamilies(
3344
3328
  injectTextRenderingRule(
@@ -3452,7 +3436,6 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
3452
3436
  renderModeHints,
3453
3437
  hasShaderTransitions,
3454
3438
  usesThreeDTransforms,
3455
- usesMixBlendMode,
3456
3439
  hasAncestorBackgroundImage
3457
3440
  };
3458
3441
  }
@@ -3859,13 +3842,6 @@ async function runCompileStage(input) {
3859
3842
  "[Render] Fast capture: composition uses a CSS 3D rendering context (perspective / preserve-3d / backface-visibility), so disabling drawElementImage for this render. Capture uses the platform's baseline route."
3860
3843
  );
3861
3844
  }
3862
- if (cfg.useDrawElement && process.env.HF_FAST_CAPTURE_BLEND !== "true" && compiled.usesMixBlendMode) {
3863
- cfg.useDrawElement = false;
3864
- deCompileGate = "mix_blend_mode";
3865
- log.info(
3866
- "[Render] Fast capture: composition uses mix-blend-mode, so disabling drawElementImage for this render. Capture uses the platform's baseline route."
3867
- );
3868
- }
3869
3845
  if (cfg.useDrawElement && process.env.HF_FAST_CAPTURE_ANCESTOR_BG !== "true" && compiled.hasAncestorBackgroundImage) {
3870
3846
  cfg.useDrawElement = false;
3871
3847
  deCompileGate = "ancestor_background_image";
@@ -5073,7 +5049,7 @@ async function appendVideoAudioFromProbe(input) {
5073
5049
 
5074
5050
  // src/services/render/stages/audioStage.ts
5075
5051
  import { join as join10 } from "node:path";
5076
- import { rmSync as rmSync5 } from "node:fs";
5052
+ import { rmSync as rmSync4 } from "node:fs";
5077
5053
  import {
5078
5054
  MIXED_AUDIO_FILENAME,
5079
5055
  audioFailureDiagnostics,
@@ -5082,7 +5058,7 @@ import {
5082
5058
 
5083
5059
  // src/services/render/audioPadTrim.ts
5084
5060
  import { spawn } from "node:child_process";
5085
- import { mkdtempSync as mkdtempSync2, rmSync as rmSync4 } from "node:fs";
5061
+ import { mkdtempSync, rmSync as rmSync3 } from "node:fs";
5086
5062
  import { dirname as dirname5, join as join9 } from "node:path";
5087
5063
  import {
5088
5064
  DEFAULT_CONFIG,
@@ -5286,7 +5262,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
5286
5262
  error: `audioPadTrim: failed to materialize ${plan2.operation}: ${err instanceof Error ? err.message : String(err)}`
5287
5263
  };
5288
5264
  } finally {
5289
- for (const path of plan2.cleanupPaths) rmSync4(path, { force: true });
5265
+ for (const path of plan2.cleanupPaths) rmSync3(path, { force: true });
5290
5266
  }
5291
5267
  let loweredDb = 0;
5292
5268
  if (probeTruePeak) {
@@ -5323,19 +5299,21 @@ async function constrainAacTruePeak(input) {
5323
5299
  const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
5324
5300
  return enforceAacTruePeak({
5325
5301
  audioPath: input.audioPath,
5302
+ sourcePath: input.sourcePath,
5326
5303
  targetDurationSeconds: input.targetDurationSeconds,
5327
5304
  signal: input.signal,
5328
- probeTruePeak: (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }),
5329
- encode: defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
5305
+ probeTruePeak: input.probeTruePeakDbfs ?? ((audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal })),
5306
+ encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
5330
5307
  });
5331
5308
  }
5332
5309
  async function enforceAacTruePeak(input) {
5333
5310
  let scratchDir;
5334
5311
  try {
5335
- scratchDir = mkdtempSync2(join9(dirname5(input.audioPath), ".true-peak-"));
5312
+ scratchDir = mkdtempSync(join9(dirname5(input.audioPath), ".true-peak-"));
5336
5313
  const correctedPath = join9(scratchDir, "audio.m4a");
5337
5314
  const measurements = [];
5338
5315
  let attenuationDb = 0;
5316
+ let sourcePeakDbfs;
5339
5317
  let measuredPath = input.audioPath;
5340
5318
  for (let pass = 0; pass <= MAX_TRUE_PEAK_CORRECTION_PASSES; pass += 1) {
5341
5319
  const truePeakDbfs = await input.probeTruePeak(measuredPath, input.signal);
@@ -5352,7 +5330,11 @@ async function enforceAacTruePeak(input) {
5352
5330
  }
5353
5331
  if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
5354
5332
  const headroomDb = AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB * 2 ** pass;
5355
- attenuationDb += AAC_DELIVERY_TRUE_PEAK_DBFS - headroomDb - truePeakDbfs;
5333
+ if (input.sourcePath && sourcePeakDbfs === void 0) {
5334
+ sourcePeakDbfs = await input.probeTruePeak(input.sourcePath, input.signal);
5335
+ }
5336
+ const referenceDbfs = Math.max(truePeakDbfs, (sourcePeakDbfs ?? -Infinity) + attenuationDb);
5337
+ attenuationDb += AAC_DELIVERY_TRUE_PEAK_DBFS - headroomDb - referenceDbfs;
5356
5338
  const result = await input.encode(
5357
5339
  input.audioPath,
5358
5340
  correctedPath,
@@ -5384,7 +5366,7 @@ async function enforceAacTruePeak(input) {
5384
5366
  )}`
5385
5367
  };
5386
5368
  } finally {
5387
- if (scratchDir) rmSync4(scratchDir, { recursive: true, force: true });
5369
+ if (scratchDir) rmSync3(scratchDir, { recursive: true, force: true });
5388
5370
  }
5389
5371
  }
5390
5372
  function failResult(outputPath, target, source, error) {
@@ -5551,7 +5533,7 @@ async function runAudioStage(input) {
5551
5533
  }
5552
5534
  return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
5553
5535
  } finally {
5554
- rmSync5(pcmSourcePath, { force: true });
5536
+ rmSync4(pcmSourcePath, { force: true });
5555
5537
  }
5556
5538
  }
5557
5539
  async function mixAudioForDelivery(input, duration, pcmSourcePath) {
@@ -5648,7 +5630,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
5648
5630
  }
5649
5631
 
5650
5632
  // src/services/render/stages/captureStage.ts
5651
- import { existsSync as existsSync8, readdirSync, statfsSync as statfsSync2 } from "node:fs";
5633
+ import { existsSync as existsSync8, readdirSync, statfsSync } from "node:fs";
5652
5634
  import { join as join11 } from "node:path";
5653
5635
  import {
5654
5636
  classifyCaptureFailure as classifyCaptureFailure2,
@@ -5670,7 +5652,7 @@ function shouldAllowAdaptiveCaptureRetry(workerCount, _explicitlyConfigured) {
5670
5652
  var DISK_PROJECTION_SAMPLE_FRAMES = 10;
5671
5653
  function freeDiskBytesAt(path) {
5672
5654
  try {
5673
- const stat = statfsSync2(path);
5655
+ const stat = statfsSync(path);
5674
5656
  return stat.bavail * stat.bsize;
5675
5657
  } catch {
5676
5658
  return null;
@@ -5769,7 +5751,7 @@ async function runCaptureStage(input) {
5769
5751
  );
5770
5752
  }
5771
5753
  }
5772
- const captureOptions = buildCaptureOptions();
5754
+ const captureOptions = { ...buildCaptureOptions(), deFrameFallback: false };
5773
5755
  const remedy = diskCaptureRemedy(cfg, job.config.format ?? "mp4", job.duration ?? 0, workerCount);
5774
5756
  const checkDiskProjection = createDiskCaptureProjection({
5775
5757
  framesDir,
@@ -6030,10 +6012,10 @@ import {
6030
6012
  import {
6031
6013
  copyFileSync as copyFileSync3,
6032
6014
  existsSync as existsSync9,
6033
- mkdirSync as mkdirSync5,
6015
+ mkdirSync as mkdirSync4,
6034
6016
  readdirSync as readdirSync2,
6035
- readFileSync as readFileSync5,
6036
- rmSync as rmSync6,
6017
+ readFileSync as readFileSync4,
6018
+ rmSync as rmSync5,
6037
6019
  statSync as statSync3,
6038
6020
  writeFileSync as writeFileSync2
6039
6021
  } from "node:fs";
@@ -6126,7 +6108,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
6126
6108
  const error = formatFfmpegError(result.exitCode, result.stderr);
6127
6109
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
6128
6110
  }
6129
- const gif = readFileSync5(outputPath);
6111
+ const gif = readFileSync4(outputPath);
6130
6112
  if (!clearGifFramesBeforeNext(gif)) {
6131
6113
  const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
6132
6114
  return gifEncodeFailure(startTime, outputPath, error);
@@ -6159,7 +6141,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
6159
6141
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
6160
6142
  }
6161
6143
  }
6162
- if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync5(outputPath)) !== false) {
6144
+ if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync4(outputPath)) !== false) {
6163
6145
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
6164
6146
  if (failure) return failure;
6165
6147
  }
@@ -6172,7 +6154,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
6172
6154
  fileSize
6173
6155
  };
6174
6156
  } finally {
6175
- rmSync6(input.palettePath, { force: true });
6157
+ rmSync5(input.palettePath, { force: true });
6176
6158
  }
6177
6159
  }
6178
6160
  function startEncodeProgress(job, stage, onProgress) {
@@ -6322,7 +6304,7 @@ async function runEncodeStage(input) {
6322
6304
  const stage5Start = Date.now();
6323
6305
  if (isPngSequence) {
6324
6306
  const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
6325
- if (!existsSync9(outputPath)) mkdirSync5(outputPath, { recursive: true });
6307
+ if (!existsSync9(outputPath)) mkdirSync4(outputPath, { recursive: true });
6326
6308
  const captured = readdirSync2(framesDir).filter((name) => name.endsWith(".png")).sort();
6327
6309
  if (captured.length === 0) {
6328
6310
  throw new Error(
@@ -6778,7 +6760,7 @@ import {
6778
6760
  // src/services/distributed/shared.ts
6779
6761
  import { execFile as execFileCallback } from "node:child_process";
6780
6762
  import { dirname as dirname8, join as join14 } from "node:path";
6781
- import { existsSync as existsSync11, readFileSync as readFileSync7 } from "node:fs";
6763
+ import { existsSync as existsSync11, readFileSync as readFileSync6 } from "node:fs";
6782
6764
  import { fileURLToPath as fileURLToPath3 } from "node:url";
6783
6765
  import { promisify as promisify2 } from "node:util";
6784
6766
  import {
@@ -7056,7 +7038,7 @@ function readProducerVersion() {
7056
7038
  const candidate = join14(current, "package.json");
7057
7039
  if (existsSync11(candidate)) {
7058
7040
  try {
7059
- const pkg = JSON.parse(readFileSync7(candidate, "utf-8"));
7041
+ const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
7060
7042
  if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
7061
7043
  cachedProducerVersion = pkg.version;
7062
7044
  return pkg.version;
@@ -7347,6 +7329,18 @@ function planFrameWeights(input) {
7347
7329
  }
7348
7330
  return weights;
7349
7331
  }
7332
+ function withChunkCaptureCost(slices, weights) {
7333
+ const lightest = weights.reduce((least, weight) => Math.min(least, weight), Infinity);
7334
+ return slices.map((slice) => {
7335
+ let sum2 = 0;
7336
+ for (let frame = slice.startFrame; frame < slice.endFrame; frame++) sum2 += weights[frame];
7337
+ const cost = Math.round(sum2 / (slice.endFrame - slice.startFrame) / lightest * 100) / 100;
7338
+ return cost > 1 ? { ...slice, captureCost: cost } : slice;
7339
+ });
7340
+ }
7341
+ function effectiveChunkFrames(frames, captureCost = 1) {
7342
+ return Math.round(frames * captureCost);
7343
+ }
7350
7344
  function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
7351
7345
  if (isUniformFrameCost(weights)) return null;
7352
7346
  const totalFrames = weights.length;
@@ -7374,7 +7368,7 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
7374
7368
  }
7375
7369
 
7376
7370
  // src/services/render/stages/freezePlan.ts
7377
- import { existsSync as existsSync13, mkdirSync as mkdirSync7, readFileSync as readFileSync8, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
7371
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync4 } from "node:fs";
7378
7372
  import { join as join17, relative as relative3, resolve as resolve7 } from "node:path";
7379
7373
 
7380
7374
  // src/services/distributed/planProtocol.ts
@@ -7474,7 +7468,7 @@ function readPlanProtocolV1(planJson, capabilities = DISTRIBUTED_RENDER_CAPABILI
7474
7468
 
7475
7469
  // src/services/render/stages/planHash.ts
7476
7470
  import { createHash as createHash4 } from "node:crypto";
7477
- import { closeSync as closeSync3, createReadStream as createReadStream3, openSync as openSync3, readSync } from "node:fs";
7471
+ import { closeSync as closeSync2, createReadStream as createReadStream3, openSync as openSync2, readSync } from "node:fs";
7478
7472
  var PLAN_HASH_SCHEMA_PREFIX = "hyperframes-plan-hash-v1\0";
7479
7473
  var FIELD_DELIMITER = Buffer.from([0]);
7480
7474
  function computePlanHash(input) {
@@ -7532,7 +7526,7 @@ function sha256Hex(bytes) {
7532
7526
  function sha256File(path) {
7533
7527
  const hash = createHash4("sha256");
7534
7528
  const buffer = Buffer.allocUnsafe(1024 * 1024);
7535
- const fd = openSync3(path, "r");
7529
+ const fd = openSync2(path, "r");
7536
7530
  try {
7537
7531
  let bytesRead = 0;
7538
7532
  do {
@@ -7540,7 +7534,7 @@ function sha256File(path) {
7540
7534
  if (bytesRead > 0) hash.update(buffer.subarray(0, bytesRead));
7541
7535
  } while (bytesRead > 0);
7542
7536
  } finally {
7543
- closeSync3(fd);
7537
+ closeSync2(fd);
7544
7538
  }
7545
7539
  return hash.digest("hex");
7546
7540
  }
@@ -7614,7 +7608,7 @@ function collectPlanAssetShas(planDir, digests) {
7614
7608
  const assets = [];
7615
7609
  for (const file of files) {
7616
7610
  if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
7617
- compositionHtml = readFileSync8(file.absolutePath);
7611
+ compositionHtml = readFileSync7(file.absolutePath);
7618
7612
  continue;
7619
7613
  }
7620
7614
  if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
@@ -7638,7 +7632,7 @@ function writePlanRemoteFiles(planDir, files) {
7638
7632
  }
7639
7633
  function readPlanRemoteFiles(planDir) {
7640
7634
  const path = join17(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
7641
- return existsSync13(path) ? JSON.parse(readFileSync8(path, "utf-8")) : [];
7635
+ return existsSync13(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
7642
7636
  }
7643
7637
  function recomputePlanHashFromPlanDir(planDir, digests) {
7644
7638
  const planJsonPath = join17(planDir, "plan.json");
@@ -7649,8 +7643,8 @@ function recomputePlanHashFromPlanDir(planDir, digests) {
7649
7643
  if (!existsSync13(encoderJsonPath)) {
7650
7644
  throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
7651
7645
  }
7652
- const planJson = JSON.parse(readFileSync8(planJsonPath, "utf-8"));
7653
- const encoderConfigCanonicalJson = readFileSync8(encoderJsonPath, "utf-8");
7646
+ const planJson = JSON.parse(readFileSync7(planJsonPath, "utf-8"));
7647
+ const encoderConfigCanonicalJson = readFileSync7(encoderJsonPath, "utf-8");
7654
7648
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
7655
7649
  return computePlanHash({
7656
7650
  compositionHtml,
@@ -7680,7 +7674,7 @@ async function freezePlan(input) {
7680
7674
  throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
7681
7675
  }
7682
7676
  const metaDir = join17(planDir, "meta");
7683
- if (!existsSync13(metaDir)) mkdirSync7(metaDir, { recursive: true });
7677
+ if (!existsSync13(metaDir)) mkdirSync6(metaDir, { recursive: true });
7684
7678
  writeFileSync4(
7685
7679
  join17(metaDir, "composition.json"),
7686
7680
  `${JSON.stringify(composition, null, 2)}
@@ -7756,13 +7750,13 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
7756
7750
  }
7757
7751
 
7758
7752
  // src/services/distributed/chunkAudio.ts
7759
- import { closeSync as closeSync4, existsSync as existsSync14, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync9 } from "node:fs";
7753
+ import { closeSync as closeSync3, existsSync as existsSync14, ftruncateSync, mkdirSync as mkdirSync8, openSync as openSync3, readFileSync as readFileSync8 } from "node:fs";
7760
7754
  import { writeFileSync as writeFileSync5, writeSync } from "node:fs";
7761
7755
  import { join as join19 } from "node:path";
7762
7756
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
7763
7757
 
7764
7758
  // src/services/distributed/mp4Stitch.ts
7765
- import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync3, rmSync as rmSync8 } from "node:fs";
7759
+ import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync7, mkdtempSync as mkdtempSync2, rmSync as rmSync7 } from "node:fs";
7766
7760
  import { open } from "node:fs/promises";
7767
7761
  import { renameWithRetry as renameWithRetry5 } from "@hyperframes/engine";
7768
7762
  import { dirname as dirname9, join as join18 } from "node:path";
@@ -8129,8 +8123,8 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
8129
8123
  const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
8130
8124
  const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
8131
8125
  const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
8132
- mkdirSync8(dirname9(outputPath), { recursive: true });
8133
- const workDir = mkdtempSync3(`${outputPath}.stitch-`);
8126
+ mkdirSync7(dirname9(outputPath), { recursive: true });
8127
+ const workDir = mkdtempSync2(`${outputPath}.stitch-`);
8134
8128
  const out = createWriteStream(join18(workDir, "out.mp4"));
8135
8129
  try {
8136
8130
  out.write(header);
@@ -8140,7 +8134,7 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
8140
8134
  renameWithRetry5(join18(workDir, "out.mp4"), outputPath);
8141
8135
  } finally {
8142
8136
  out.destroy();
8143
- rmSync8(workDir, { recursive: true, force: true });
8137
+ rmSync7(workDir, { recursive: true, force: true });
8144
8138
  }
8145
8139
  }
8146
8140
 
@@ -8229,9 +8223,9 @@ async function writeChunkAudioSlices(input) {
8229
8223
  offset += track.sizes[offsets.length - 1];
8230
8224
  }
8231
8225
  });
8232
- const bytes = readFileSync9(input.audioPath);
8226
+ const bytes = readFileSync8(input.audioPath);
8233
8227
  const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
8234
- mkdirSync9(join19(input.planDir, SLICE_DIR), { recursive: true });
8228
+ mkdirSync8(join19(input.planDir, SLICE_DIR), { recursive: true });
8235
8229
  input.chunks.forEach((_, index) => {
8236
8230
  const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
8237
8231
  if (!cut) return;
@@ -8248,9 +8242,9 @@ async function writeChunkAudioSlices(input) {
8248
8242
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
8249
8243
  const layoutPath = join19(planDir, SLICE_LAYOUT);
8250
8244
  if (!existsSync14(layoutPath)) return resolvePlanAudioPath(planDir);
8251
- const layout = JSON.parse(readFileSync9(layoutPath, "utf-8"));
8252
- const frame = readFileSync9(join19(planDir, SLICE_FRAME));
8253
- const fd = openSync4(scratchPath, "w");
8245
+ const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
8246
+ const frame = readFileSync8(join19(planDir, SLICE_FRAME));
8247
+ const fd = openSync3(scratchPath, "w");
8254
8248
  try {
8255
8249
  writeSync(fd, frame, 0, layout.payloadStart, 0);
8256
8250
  writeSync(
@@ -8262,12 +8256,12 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
8262
8256
  );
8263
8257
  const start = layout.partStarts[chunkIndex];
8264
8258
  if (start !== void 0) {
8265
- const part = readFileSync9(join19(planDir, chunkAudioSlicePath(chunkIndex)));
8259
+ const part = readFileSync8(join19(planDir, chunkAudioSlicePath(chunkIndex)));
8266
8260
  writeSync(fd, part, 0, part.length, start);
8267
8261
  }
8268
8262
  ftruncateSync(fd, layout.sizeBytes);
8269
8263
  } finally {
8270
- closeSync4(fd);
8264
+ closeSync3(fd);
8271
8265
  }
8272
8266
  return scratchPath;
8273
8267
  }
@@ -8652,7 +8646,7 @@ var FREEZE_OWNED_PLAN_FILES = [
8652
8646
  ];
8653
8647
  function removeFreezeOwnedPlanFiles(planDir) {
8654
8648
  for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
8655
- rmSync9(join21(planDir, relativePath), { force: true });
8649
+ rmSync8(join21(planDir, relativePath), { force: true });
8656
8650
  }
8657
8651
  }
8658
8652
  function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
@@ -8671,7 +8665,10 @@ function buildPlanChunkSlices(frameWeights, config, chunkCount, effectiveChunkSi
8671
8665
  MIN_CHUNK_SIZE,
8672
8666
  Math.min(config.targetChunkFrames ?? MAX_CHUNK_SIZE, MAX_CHUNK_SIZE, maxFramesPerChunk)
8673
8667
  ) : null;
8674
- return weighted ?? buildChunkSlices(frameWeights.length, chunkCount, effectiveChunkSize);
8668
+ return withChunkCaptureCost(
8669
+ weighted ?? buildChunkSlices(frameWeights.length, chunkCount, effectiveChunkSize),
8670
+ frameWeights
8671
+ );
8675
8672
  }
8676
8673
  async function readFontSnapshotSha() {
8677
8674
  const module = await import("@hyperframes/core/fonts/embedded-data");
@@ -8806,7 +8803,7 @@ function createVideoSourceShipper(input) {
8806
8803
  if (result === null) {
8807
8804
  result = `video-sources/${shipped.size}${extname4(current)}`;
8808
8805
  const destination = join21(input.planDir, ...result.split("/"));
8809
- mkdirSync10(join21(input.planDir, "video-sources"), { recursive: true });
8806
+ mkdirSync9(join21(input.planDir, "video-sources"), { recursive: true });
8810
8807
  try {
8811
8808
  linkSync(current, destination);
8812
8809
  } catch {
@@ -8826,7 +8823,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
8826
8823
  useGpu: false,
8827
8824
  browserGpuMode: "software"
8828
8825
  });
8829
- if (!existsSync15(planDir)) mkdirSync10(planDir, { recursive: true });
8826
+ if (!existsSync15(planDir)) mkdirSync9(planDir, { recursive: true });
8830
8827
  const log = config.logger ?? defaultLogger;
8831
8828
  const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
8832
8829
  const abortSignal = config.abortSignal;
@@ -8860,10 +8857,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
8860
8857
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
8861
8858
  }
8862
8859
  const workDir = join21(planDir, ".plan-work");
8863
- rmSync9(workDir, { recursive: true, force: true });
8864
- mkdirSync10(workDir, { recursive: true });
8860
+ rmSync8(workDir, { recursive: true, force: true });
8861
+ mkdirSync9(workDir, { recursive: true });
8865
8862
  const compiledDir = join21(workDir, "compiled");
8866
- mkdirSync10(compiledDir, { recursive: true });
8863
+ mkdirSync9(compiledDir, { recursive: true });
8867
8864
  let stageStarted = Date.now();
8868
8865
  cpSync2(projectDir, compiledDir, {
8869
8866
  recursive: true,
@@ -9067,15 +9064,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
9067
9064
  logPlanStage(log, "extract", stageStarted);
9068
9065
  const stagedVideoFrames = join21(compiledDir, "__hyperframes_video_frames");
9069
9066
  const videoFramesDst = join21(planDir, "video-frames");
9070
- if (existsSync15(videoFramesDst)) rmSync9(videoFramesDst, { recursive: true, force: true });
9067
+ if (existsSync15(videoFramesDst)) rmSync8(videoFramesDst, { recursive: true, force: true });
9071
9068
  const videoSourcesDir = join21(planDir, "video-sources");
9072
- rmSync9(videoSourcesDir, { recursive: true, force: true });
9069
+ rmSync8(videoSourcesDir, { recursive: true, force: true });
9073
9070
  if (existsSync15(stagedVideoFrames)) {
9074
9071
  renameWithRetry6(stagedVideoFrames, videoFramesDst);
9075
9072
  } else {
9076
- mkdirSync10(videoFramesDst, { recursive: true });
9073
+ mkdirSync9(videoFramesDst, { recursive: true });
9077
9074
  }
9078
- if (existsSync15(finalCompiledDir)) rmSync9(finalCompiledDir, { recursive: true, force: true });
9075
+ if (existsSync15(finalCompiledDir)) rmSync8(finalCompiledDir, { recursive: true, force: true });
9079
9076
  renameWithRetry6(compiledDir, finalCompiledDir);
9080
9077
  const shipVideoSource = createVideoSourceShipper({
9081
9078
  planDir,
@@ -9105,7 +9102,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
9105
9102
  };
9106
9103
  })
9107
9104
  });
9108
- mkdirSync10(join21(planDir, "meta"), { recursive: true });
9105
+ mkdirSync9(join21(planDir, "meta"), { recursive: true });
9109
9106
  writePlanRemoteFiles(
9110
9107
  planDir,
9111
9108
  remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
@@ -9243,7 +9240,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
9243
9240
  format: config.format
9244
9241
  };
9245
9242
  try {
9246
- rmSync9(workDir, { recursive: true, force: true });
9243
+ rmSync8(workDir, { recursive: true, force: true });
9247
9244
  } catch (err) {
9248
9245
  log.warn("[plan] failed to remove temp work dir", {
9249
9246
  workDir,
@@ -9313,7 +9310,7 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
9313
9310
  renameWithRetry6(mixed.audioOutputPath, outputPath);
9314
9311
  return { hasAudio: true };
9315
9312
  } finally {
9316
- rmSync9(c.workDir, { recursive: true, force: true });
9313
+ rmSync8(c.workDir, { recursive: true, force: true });
9317
9314
  }
9318
9315
  }
9319
9316
  async function plan(projectDir, config, planDir, options = {}) {
@@ -9338,11 +9335,11 @@ import {
9338
9335
  copyFileSync as copyFileSync7,
9339
9336
  existsSync as existsSync17,
9340
9337
  lstatSync as lstatSync2,
9341
- mkdirSync as mkdirSync12,
9342
- mkdtempSync as mkdtempSync5,
9343
- readFileSync as readFileSync10,
9338
+ mkdirSync as mkdirSync11,
9339
+ mkdtempSync as mkdtempSync4,
9340
+ readFileSync as readFileSync9,
9344
9341
  readdirSync as readdirSync7,
9345
- rmSync as rmSync11,
9342
+ rmSync as rmSync10,
9346
9343
  statSync as statSync6,
9347
9344
  writeFileSync as writeFileSync8
9348
9345
  } from "node:fs";
@@ -9356,9 +9353,9 @@ import {
9356
9353
  copyFileSync as copyFileSync6,
9357
9354
  existsSync as existsSync16,
9358
9355
  linkSync as linkSync2,
9359
- mkdirSync as mkdirSync11,
9360
- mkdtempSync as mkdtempSync4,
9361
- rmSync as rmSync10,
9356
+ mkdirSync as mkdirSync10,
9357
+ mkdtempSync as mkdtempSync3,
9358
+ rmSync as rmSync9,
9362
9359
  statSync as statSync5,
9363
9360
  writeFileSync as writeFileSync7
9364
9361
  } from "node:fs";
@@ -9428,8 +9425,8 @@ var LocalPlanV2ArtifactPublisher = class {
9428
9425
  }
9429
9426
  this.destinationDir = destinationDir;
9430
9427
  this.#linkFile = options.linkFile ?? linkSync2;
9431
- mkdirSync11(dirname10(destinationDir), { recursive: true });
9432
- this.temporaryDir = mkdtempSync4(join23(dirname10(destinationDir), ".plan-v2-publish-"));
9428
+ mkdirSync10(dirname10(destinationDir), { recursive: true });
9429
+ this.temporaryDir = mkdtempSync3(join23(dirname10(destinationDir), ".plan-v2-publish-"));
9433
9430
  }
9434
9431
  async putBlob(blob) {
9435
9432
  const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
@@ -9441,8 +9438,8 @@ var LocalPlanV2ArtifactPublisher = class {
9441
9438
  }
9442
9439
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
9443
9440
  if (existsSync16(destinationPath)) return;
9444
- mkdirSync11(dirname10(destinationPath), { recursive: true });
9445
- const stagingDir = mkdtempSync4(join23(dirname10(destinationPath), ".plan-v2-blob-"));
9441
+ mkdirSync10(dirname10(destinationPath), { recursive: true });
9442
+ const stagingDir = mkdtempSync3(join23(dirname10(destinationPath), ".plan-v2-blob-"));
9446
9443
  const temporaryPath = join23(stagingDir, "blob");
9447
9444
  try {
9448
9445
  try {
@@ -9453,7 +9450,7 @@ var LocalPlanV2ArtifactPublisher = class {
9453
9450
  }
9454
9451
  renameWithRetry7(temporaryPath, destinationPath);
9455
9452
  } finally {
9456
- rmSync10(stagingDir, { recursive: true, force: true });
9453
+ rmSync9(stagingDir, { recursive: true, force: true });
9457
9454
  }
9458
9455
  }
9459
9456
  async commitManifest(manifestBytes) {
@@ -9470,7 +9467,7 @@ var LocalPlanV2ArtifactPublisher = class {
9470
9467
  }
9471
9468
  async abort() {
9472
9469
  if (!this.#committed) {
9473
- rmSync10(this.temporaryDir, { recursive: true, force: true });
9470
+ rmSync9(this.temporaryDir, { recursive: true, force: true });
9474
9471
  }
9475
9472
  }
9476
9473
  };
@@ -9489,7 +9486,7 @@ function isNonNegativeIntegerArray(value) {
9489
9486
  return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
9490
9487
  }
9491
9488
  function readJsonFile(path, label) {
9492
- const contents = readFileSync10(path, "utf-8");
9489
+ const contents = readFileSync9(path, "utf-8");
9493
9490
  try {
9494
9491
  const parsed = JSON.parse(contents);
9495
9492
  return parsed;
@@ -9641,7 +9638,7 @@ function materializeExtractedVideoDirectories(planDir) {
9641
9638
  if (!existsSync17(videosPath)) return;
9642
9639
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
9643
9640
  for (const video of videos.extracted) {
9644
- mkdirSync12(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
9641
+ mkdirSync11(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
9645
9642
  }
9646
9643
  }
9647
9644
  function parseChunkSlices(value) {
@@ -9662,7 +9659,12 @@ function parseChunkSlices(value) {
9662
9659
  throw new PlanV2IntegrityError(`meta/chunks.json contains duplicate chunk index ${index}`);
9663
9660
  }
9664
9661
  indexes.add(index);
9665
- return { index, startFrame, endFrame };
9662
+ if (chunk.captureCost === void 0) return { index, startFrame, endFrame };
9663
+ const captureCost = chunk.captureCost;
9664
+ if (typeof captureCost !== "number" || !Number.isFinite(captureCost) || captureCost <= 1) {
9665
+ throw new PlanV2IntegrityError(`${field}.captureCost must be a number above 1`);
9666
+ }
9667
+ return { index, startFrame, endFrame, captureCost };
9666
9668
  });
9667
9669
  }
9668
9670
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
@@ -9727,14 +9729,14 @@ function computeManifestHash(manifest) {
9727
9729
  }
9728
9730
  function writeBlob(sourcePath, destinationPath) {
9729
9731
  if (existsSync17(destinationPath)) return;
9730
- mkdirSync12(dirname11(destinationPath), { recursive: true });
9731
- const temporaryDir = mkdtempSync5(join24(dirname11(destinationPath), ".plan-v2-blob-"));
9732
+ mkdirSync11(dirname11(destinationPath), { recursive: true });
9733
+ const temporaryDir = mkdtempSync4(join24(dirname11(destinationPath), ".plan-v2-blob-"));
9732
9734
  const temporaryPath = join24(temporaryDir, "blob");
9733
9735
  try {
9734
9736
  copyFileSync7(sourcePath, temporaryPath);
9735
9737
  renameWithRetry8(temporaryPath, destinationPath);
9736
9738
  } finally {
9737
- rmSync11(temporaryDir, { recursive: true, force: true });
9739
+ rmSync10(temporaryDir, { recursive: true, force: true });
9738
9740
  }
9739
9741
  }
9740
9742
  function publishBlobFor(absolutePath, digests) {
@@ -9853,8 +9855,8 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
9853
9855
  if (publication.storedBlobs.length > 0) {
9854
9856
  throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
9855
9857
  }
9856
- mkdirSync12(dirname11(planV2Dir), { recursive: true });
9857
- const tempDir = mkdtempSync5(join24(dirname11(planV2Dir), ".plan-v2-build-"));
9858
+ mkdirSync11(dirname11(planV2Dir), { recursive: true });
9859
+ const tempDir = mkdtempSync4(join24(dirname11(planV2Dir), ".plan-v2-build-"));
9858
9860
  try {
9859
9861
  for (const blob of publication.blobs) {
9860
9862
  writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
@@ -9867,7 +9869,7 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
9867
9869
  renameWithRetry8(tempDir, planV2Dir);
9868
9870
  return resultFromManifest(planV2Dir, publication.manifest);
9869
9871
  } catch (error) {
9870
- rmSync11(tempDir, { recursive: true, force: true });
9872
+ rmSync10(tempDir, { recursive: true, force: true });
9871
9873
  throw error;
9872
9874
  }
9873
9875
  }
@@ -9907,7 +9909,7 @@ async function publishPlanV2FromV1(executionPlanDir, publisher) {
9907
9909
  return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
9908
9910
  }
9909
9911
  async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
9910
- const stagingRoot = mkdtempSync5(join24(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
9912
+ const stagingRoot = mkdtempSync4(join24(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
9911
9913
  const digests = /* @__PURE__ */ new Map();
9912
9914
  try {
9913
9915
  await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
@@ -9934,7 +9936,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
9934
9936
  }
9935
9937
  throw error;
9936
9938
  } finally {
9937
- rmSync11(stagingRoot, { recursive: true, force: true });
9939
+ rmSync10(stagingRoot, { recursive: true, force: true });
9938
9940
  }
9939
9941
  }
9940
9942
  async function planV2(projectDir, config, planV2Dir) {
@@ -10131,12 +10133,12 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
10131
10133
  artifact,
10132
10134
  sourcePath: verifyBlob(planV2Dir, artifact)
10133
10135
  }));
10134
- mkdirSync12(dirname11(destinationDir), { recursive: true });
10135
- const tempDir = mkdtempSync5(join24(dirname11(destinationDir), ".plan-v2-materialize-"));
10136
+ mkdirSync11(dirname11(destinationDir), { recursive: true });
10137
+ const tempDir = mkdtempSync4(join24(dirname11(destinationDir), ".plan-v2-materialize-"));
10136
10138
  try {
10137
10139
  for (const { artifact, sourcePath } of verified) {
10138
10140
  const destinationPath = join24(tempDir, ...artifact.path.split("/"));
10139
- mkdirSync12(dirname11(destinationPath), { recursive: true });
10141
+ mkdirSync11(dirname11(destinationPath), { recursive: true });
10140
10142
  copyFileSync7(sourcePath, destinationPath);
10141
10143
  }
10142
10144
  if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
@@ -10151,7 +10153,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
10151
10153
  );
10152
10154
  renameWithRetry8(tempDir, destinationDir);
10153
10155
  } catch (error) {
10154
- rmSync11(tempDir, { recursive: true, force: true });
10156
+ rmSync10(tempDir, { recursive: true, force: true });
10155
10157
  throw error;
10156
10158
  }
10157
10159
  return {
@@ -10215,7 +10217,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
10215
10217
  }
10216
10218
 
10217
10219
  // src/services/distributed/planV2Execution.ts
10218
- import { mkdtempSync as mkdtempSync7, rmSync as rmSync14 } from "node:fs";
10220
+ import { mkdtempSync as mkdtempSync6, rmSync as rmSync13 } from "node:fs";
10219
10221
  import { tmpdir as tmpdir2 } from "node:os";
10220
10222
  import { join as join27 } from "node:path";
10221
10223
 
@@ -10223,11 +10225,11 @@ import { join as join27 } from "node:path";
10223
10225
  import {
10224
10226
  cpSync as cpSync3,
10225
10227
  existsSync as existsSync18,
10226
- mkdirSync as mkdirSync13,
10227
- mkdtempSync as mkdtempSync6,
10228
- readFileSync as readFileSync11,
10228
+ mkdirSync as mkdirSync12,
10229
+ mkdtempSync as mkdtempSync5,
10230
+ readFileSync as readFileSync10,
10229
10231
  readdirSync as readdirSync8,
10230
- rmSync as rmSync12,
10232
+ rmSync as rmSync11,
10231
10233
  statSync as statSync7,
10232
10234
  writeFileSync as writeFileSync9
10233
10235
  } from "node:fs";
@@ -10273,9 +10275,9 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
10273
10275
  const format = plan2.dimensions.format;
10274
10276
  const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
10275
10277
  if (!existsSync18(dirname12(outputPath))) {
10276
- mkdirSync13(dirname12(outputPath), { recursive: true });
10278
+ mkdirSync12(dirname12(outputPath), { recursive: true });
10277
10279
  }
10278
- const workDir = mkdtempSync6(`${outputPath}.assemble-work-`);
10280
+ const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
10279
10281
  const fps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
10280
10282
  const stageMs = {};
10281
10283
  const ctx = {
@@ -10306,7 +10308,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
10306
10308
  });
10307
10309
  } finally {
10308
10310
  try {
10309
- rmSync12(workDir, { recursive: true, force: true });
10311
+ rmSync11(workDir, { recursive: true, force: true });
10310
10312
  } catch (err) {
10311
10313
  log.warn("[assemble] failed to remove work dir", {
10312
10314
  workDir,
@@ -10328,13 +10330,13 @@ function readAssemblePlan(planDir, chunkPaths) {
10328
10330
  if (!existsSync18(planJsonPath)) {
10329
10331
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
10330
10332
  }
10331
- const plan2 = JSON.parse(readFileSync11(planJsonPath, "utf-8"));
10333
+ const plan2 = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
10332
10334
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
10333
10335
  validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
10334
10336
  if (!existsSync18(chunksJsonPath)) {
10335
10337
  throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
10336
10338
  }
10337
- const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
10339
+ const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
10338
10340
  if (chunkPaths.length !== chunks.length) {
10339
10341
  throw new Error(
10340
10342
  `[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
@@ -10477,13 +10479,13 @@ function stitchesSeparateAudio(planDir) {
10477
10479
  return lockedAudio(planDir).separateAudio === true;
10478
10480
  }
10479
10481
  function planFormat(planDir) {
10480
- const plan2 = JSON.parse(readFileSync11(join25(planDir, "plan.json"), "utf-8"));
10482
+ const plan2 = JSON.parse(readFileSync10(join25(planDir, "plan.json"), "utf-8"));
10481
10483
  return plan2.dimensions.format;
10482
10484
  }
10483
10485
  function lockedAudio(planDir) {
10484
10486
  const encoderJsonPath = join25(planDir, "meta", "encoder.json");
10485
10487
  if (!existsSync18(encoderJsonPath)) return {};
10486
- return JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
10488
+ return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
10487
10489
  }
10488
10490
  function readCfrEncoder(planDir, format) {
10489
10491
  if (format !== "mp4") {
@@ -10495,7 +10497,7 @@ function readCfrEncoder(planDir, format) {
10495
10497
  if (!existsSync18(encoderJsonPath)) {
10496
10498
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
10497
10499
  }
10498
- const encoderJson = JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
10500
+ const encoderJson = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
10499
10501
  if (encoderJson.encoder === "libx265-software") {
10500
10502
  throw new Error(
10501
10503
  `[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".`
@@ -10504,8 +10506,8 @@ function readCfrEncoder(planDir, format) {
10504
10506
  return encoderJson;
10505
10507
  }
10506
10508
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
10507
- if (existsSync18(outputPath)) rmSync12(outputPath, { recursive: true, force: true });
10508
- mkdirSync13(outputPath, { recursive: true });
10509
+ if (existsSync18(outputPath)) rmSync11(outputPath, { recursive: true, force: true });
10510
+ mkdirSync12(outputPath, { recursive: true });
10509
10511
  let globalIdx = 0;
10510
10512
  for (const chunkDir of chunkPaths) {
10511
10513
  if (!statSync7(chunkDir).isDirectory()) {
@@ -10549,7 +10551,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
10549
10551
 
10550
10552
  // src/services/distributed/renderChunk.ts
10551
10553
  import { randomBytes } from "node:crypto";
10552
- import { existsSync as existsSync19, mkdirSync as mkdirSync14, readFileSync as readFileSync12, readdirSync as readdirSync9, rmSync as rmSync13, writeFileSync as writeFileSync10 } from "node:fs";
10554
+ import { existsSync as existsSync19, mkdirSync as mkdirSync13, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync12, writeFileSync as writeFileSync10 } from "node:fs";
10553
10555
  import { cpus } from "node:os";
10554
10556
  import { join as join26 } from "node:path";
10555
10557
  import {
@@ -10667,10 +10669,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
10667
10669
  return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
10668
10670
  }
10669
10671
  function hashChunkOutput(outputPath, kind) {
10670
- if (kind === "file") return sha256Hex(readFileSync12(outputPath));
10672
+ if (kind === "file") return sha256Hex(readFileSync11(outputPath));
10671
10673
  const entries = readdirSync9(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
10672
10674
  const lines = entries.map(
10673
- (name) => `${name}\0${sha256Hex(readFileSync12(join26(outputPath, name)))}`
10675
+ (name) => `${name}\0${sha256Hex(readFileSync11(join26(outputPath, name)))}`
10674
10676
  );
10675
10677
  return sha256Hex(lines.join("\0"));
10676
10678
  }
@@ -10696,7 +10698,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
10696
10698
  `[renderChunk] planDir is missing required artifact: ${planJsonPath}`
10697
10699
  );
10698
10700
  }
10699
- const plan2 = JSON.parse(readFileSync12(planJsonPath, "utf-8"));
10701
+ const plan2 = JSON.parse(readFileSync11(planJsonPath, "utf-8"));
10700
10702
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
10701
10703
  const planHashStarted = Date.now();
10702
10704
  const v2Materialization = readValidatedPlanV2Materialization(planDir, {
@@ -10713,13 +10715,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
10713
10715
  );
10714
10716
  }
10715
10717
  }
10716
- const encoder = JSON.parse(readFileSync12(encoderJsonPath, "utf-8"));
10717
- const chunks = JSON.parse(readFileSync12(chunksJsonPath, "utf-8"));
10718
+ const encoder = JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
10719
+ const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
10718
10720
  const videosJsonPath = join26(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10719
10721
  let planVideos = null;
10720
10722
  if (existsSync19(videosJsonPath)) {
10721
10723
  try {
10722
- planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync12(videosJsonPath, "utf-8")));
10724
+ planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync11(videosJsonPath, "utf-8")));
10723
10725
  } catch (err) {
10724
10726
  if (err instanceof RenderChunkValidationError) throw err;
10725
10727
  throw new RenderChunkValidationError(
@@ -10804,9 +10806,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
10804
10806
  forceScreenshotExplicitlyOptedOut: !encoder.forceScreenshot
10805
10807
  };
10806
10808
  const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
10807
- mkdirSync14(workDir, { recursive: true });
10809
+ mkdirSync13(workDir, { recursive: true });
10808
10810
  const framesDir = join26(workDir, "captured-frames");
10809
- mkdirSync14(framesDir, { recursive: true });
10811
+ mkdirSync13(framesDir, { recursive: true });
10810
10812
  const videoFramesDir = `${workDir}.video-frames`;
10811
10813
  const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
10812
10814
  const chunkFps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
@@ -10830,7 +10832,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
10830
10832
  dimensions: plan2.dimensions,
10831
10833
  encoder,
10832
10834
  videoCount: planVideos?.videos.length ?? 0,
10833
- html: readFileSync12(join26(compiledDir, "index.html"), "utf-8"),
10835
+ html: readFileSync11(join26(compiledDir, "index.html"), "utf-8"),
10834
10836
  // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
10835
10837
  skipReadinessVideoIds: deferredVideos.filter(
10836
10838
  (video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
@@ -10839,15 +10841,17 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
10839
10841
  deferredVideos.filter((video) => remoteVideoIds.has(video.videoId))
10840
10842
  )
10841
10843
  });
10844
+ const captureFrames = effectiveChunkFrames(framesInChunk, slice.captureCost);
10842
10845
  const chunkWorkerCount = resolveChunkCaptureWorkers(
10843
- framesInChunk,
10844
- calculateOptimalWorkers2(framesInChunk, void 0, cfg),
10846
+ captureFrames,
10847
+ calculateOptimalWorkers2(captureFrames, void 0, cfg),
10845
10848
  options?.maxCaptureWorkers
10846
10849
  );
10847
10850
  let session = null;
10848
10851
  let outputKind;
10849
10852
  let framesEncoded = 0;
10850
10853
  let sessionBootMs = 0;
10854
+ let videoExtractMs = 0;
10851
10855
  let captureStageMs = 0;
10852
10856
  let encodeStageMs = 0;
10853
10857
  let captureMode;
@@ -10913,6 +10917,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
10913
10917
  session = null;
10914
10918
  }
10915
10919
  }
10920
+ const extractStarted = Date.now();
10916
10921
  const deferredVideoFrames = planVideos ? extractDeferredVideoFramesForChunk({
10917
10922
  planDir,
10918
10923
  planVideos,
@@ -10922,6 +10927,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
10922
10927
  cfg,
10923
10928
  remoteSources,
10924
10929
  shownBoxes
10930
+ }).then((frames) => {
10931
+ videoExtractMs = Date.now() - extractStarted;
10932
+ return frames;
10925
10933
  }) : Promise.resolve([]);
10926
10934
  extraction = deferredVideoFrames;
10927
10935
  const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? deferredVideoFrames.then(
@@ -10953,10 +10961,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
10953
10961
  const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
10954
10962
  const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
10955
10963
  if (isPngSequence) {
10956
- if (!existsSync19(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
10964
+ if (!existsSync19(outputChunkPath)) mkdirSync13(outputChunkPath, { recursive: true });
10957
10965
  } else {
10958
10966
  const outDir = join26(outputChunkPath, "..");
10959
- if (!existsSync19(outDir)) mkdirSync14(outDir, { recursive: true });
10967
+ if (!existsSync19(outDir)) mkdirSync13(outDir, { recursive: true });
10960
10968
  }
10961
10969
  const encodeInput = {
10962
10970
  job,
@@ -11059,8 +11067,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11059
11067
  return observedChunkCaptureMode(capturePerfs);
11060
11068
  },
11061
11069
  resetForScreenshotRetry: () => {
11062
- rmSync13(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
11063
- mkdirSync14(framesDir, { recursive: true });
11070
+ rmSync12(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
11071
+ mkdirSync13(framesDir, { recursive: true });
11064
11072
  capturePerfs.length = 0;
11065
11073
  captureAttempts.length = 0;
11066
11074
  job.framesRendered = 0;
@@ -11102,8 +11110,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11102
11110
  fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
11103
11111
  timeoutMs: cfg.ffmpegProcessTimeout
11104
11112
  });
11105
- rmSync13(videoOnlyPath, { force: true });
11106
- rmSync13(audioScratchPath, { force: true });
11113
+ rmSync12(videoOnlyPath, { force: true });
11114
+ rmSync12(audioScratchPath, { force: true });
11107
11115
  }
11108
11116
  encodeStageMs = Date.now() - encodeStarted;
11109
11117
  } finally {
@@ -11139,6 +11147,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11139
11147
  sessionBootMs,
11140
11148
  captureStageMs,
11141
11149
  encodeStageMs,
11150
+ // Wall time of the chunk's own video frame extraction; it overlaps captureStageMs, so do not add them.
11151
+ videoExtractMs,
11142
11152
  workers: chunkWorkerCount,
11143
11153
  captureMode,
11144
11154
  // Device-pixel box each video's frames were extracted to cover; a video not listed kept full size.
@@ -11151,8 +11161,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11151
11161
  writeFileSync10(perfPath, `${JSON.stringify(perfPayload, null, 2)}
11152
11162
  `, "utf-8");
11153
11163
  try {
11154
- rmSync13(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
11155
- rmSync13(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
11164
+ rmSync12(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
11165
+ rmSync12(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
11156
11166
  } catch (err) {
11157
11167
  log.warn("[renderChunk] failed to remove work dir", {
11158
11168
  workDir,
@@ -11181,17 +11191,17 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11181
11191
 
11182
11192
  // src/services/distributed/planV2Execution.ts
11183
11193
  async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
11184
- const workRoot = mkdtempSync7(join27(tmpdir2(), "hf-plan-v2-chunk-"));
11194
+ const workRoot = mkdtempSync6(join27(tmpdir2(), "hf-plan-v2-chunk-"));
11185
11195
  const materializedPlanDir = join27(workRoot, "plan");
11186
11196
  try {
11187
11197
  materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
11188
11198
  return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
11189
11199
  } finally {
11190
- rmSync14(workRoot, { recursive: true, force: true });
11200
+ rmSync13(workRoot, { recursive: true, force: true });
11191
11201
  }
11192
11202
  }
11193
11203
  async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
11194
- const workRoot = mkdtempSync7(join27(tmpdir2(), "hf-plan-v2-assembler-"));
11204
+ const workRoot = mkdtempSync6(join27(tmpdir2(), "hf-plan-v2-assembler-"));
11195
11205
  const materializedPlanDir = join27(workRoot, "plan");
11196
11206
  try {
11197
11207
  const materialized = materializePlanV2Target(
@@ -11207,7 +11217,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
11207
11217
  options
11208
11218
  );
11209
11219
  } finally {
11210
- rmSync14(workRoot, { recursive: true, force: true });
11220
+ rmSync13(workRoot, { recursive: true, force: true });
11211
11221
  }
11212
11222
  }
11213
11223
 
@@ -11456,7 +11466,7 @@ function validateIntDimension(field, value) {
11456
11466
  }
11457
11467
 
11458
11468
  // src/services/distributed/projectHash.ts
11459
- import { closeSync as closeSync5, openSync as openSync5, readdirSync as readdirSync10, readSync as readSync3, realpathSync as realpathSync2, statSync as statSync8 } from "node:fs";
11469
+ import { closeSync as closeSync4, openSync as openSync4, readdirSync as readdirSync10, readSync as readSync3, realpathSync as realpathSync2, statSync as statSync8 } from "node:fs";
11460
11470
  import { createHash as createHash6 } from "node:crypto";
11461
11471
  import { join as join28 } from "node:path";
11462
11472
  var SITE_ID_SCHEME = "site-v2\0";
@@ -11497,14 +11507,14 @@ function hashProjectDir(projectDir, files = listSiteFiles(projectDir)) {
11497
11507
  for (const rel of files) {
11498
11508
  hash.update(rel);
11499
11509
  hash.update("\0");
11500
- const fd = openSync5(join28(projectDir, rel), "r");
11510
+ const fd = openSync4(join28(projectDir, rel), "r");
11501
11511
  try {
11502
11512
  let read;
11503
11513
  while ((read = readSync3(fd, chunk, 0, chunk.length, null)) > 0) {
11504
11514
  hash.update(chunk.subarray(0, read));
11505
11515
  }
11506
11516
  } finally {
11507
- closeSync5(fd);
11517
+ closeSync4(fd);
11508
11518
  }
11509
11519
  }
11510
11520
  return hash.digest("hex").slice(0, 16);