@hyperframes/producer 0.9.16 → 0.9.17

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.
@@ -14,7 +14,7 @@ import {
14
14
  rmSync as rmSync7,
15
15
  writeFileSync as writeFileSync8
16
16
  } from "node:fs";
17
- import { extname as extname4, isAbsolute as isAbsolute3, join as join24, relative as relative5, sep as sep2 } from "node:path";
17
+ import { extname as extname4, isAbsolute as isAbsolute3, join as join25, relative as relative5, sep as sep2 } from "node:path";
18
18
  import {
19
19
  fpsToNumber as fpsToNumber9,
20
20
  HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
@@ -97,7 +97,7 @@ import {
97
97
  closeCaptureSession as closeCaptureSession8,
98
98
  createCaptureSession as createCaptureSession8,
99
99
  initializeSession as initializeSession8,
100
- createVideoFrameInjector,
100
+ createVideoFrameInjector as createVideoFrameInjector2,
101
101
  getEncoderPreset as getEncoderPreset3,
102
102
  distributeFrames as distributeFrames2,
103
103
  distributeFramesInterleaved as distributeFramesInterleaved2,
@@ -128,7 +128,7 @@ import {
128
128
  referenceBackgroundResult,
129
129
  createReferenceBackgroundRecorder
130
130
  } from "@hyperframes/engine";
131
- import { join as join15, dirname as dirname9, resolve as resolve7 } from "path";
131
+ import { join as join16, dirname as dirname10, resolve as resolve7 } from "path";
132
132
  import { randomUUID } from "crypto";
133
133
 
134
134
  // src/services/fileServer.ts
@@ -1318,6 +1318,7 @@ import {
1318
1318
  getCapturePerfSummary as getCapturePerfSummary2,
1319
1319
  initializeSession as initializeSession2,
1320
1320
  prepareCaptureSessionForReuse as prepareCaptureSessionForReuse2,
1321
+ resolveDeVerifyMinDb as resolveDeVerifyMinDb2,
1321
1322
  shouldDisableBrowserPoolForParallelWorker,
1322
1323
  spawnStreamingEncoder as spawnStreamingEncoder2
1323
1324
  } from "@hyperframes/engine";
@@ -1546,6 +1547,85 @@ async function measureCaptureCostFromSession(session, totalFrames, fps, log) {
1546
1547
  };
1547
1548
  }
1548
1549
 
1550
+ // src/services/render/deferredVideoFrames.ts
1551
+ import { dirname as dirname3, join as join4 } from "node:path";
1552
+ import {
1553
+ createFrameLookupTable,
1554
+ extractVideoFramesRange,
1555
+ runVideoExtractionWithRetry
1556
+ } from "@hyperframes/engine";
1557
+ function contiguousRuns(sortedIndexes) {
1558
+ const runs2 = [];
1559
+ for (const index of sortedIndexes) {
1560
+ const last = runs2[runs2.length - 1];
1561
+ if (last && last.firstFrame + last.frames === index) last.frames++;
1562
+ else runs2.push({ firstFrame: index, frames: 1 });
1563
+ }
1564
+ return runs2;
1565
+ }
1566
+ function shownDeferredFrameIndexes(source, frames, fps) {
1567
+ const shown = /* @__PURE__ */ new Map();
1568
+ const deferred = source.extracted.filter((video) => video.deferredRange);
1569
+ if (deferred.length === 0) return shown;
1570
+ const table = createFrameLookupTable(
1571
+ source.videos,
1572
+ deferred.map((video) => ({
1573
+ ...video,
1574
+ outputDir: "",
1575
+ framePaths: new Map(Array.from({ length: video.totalFrames }, (_, i) => [i, String(i)]))
1576
+ })),
1577
+ resolveRenderFpsConfig(fps).value
1578
+ );
1579
+ for (const frame of frames) {
1580
+ const globalTime = frame * fps.den / fps.num;
1581
+ for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
1582
+ const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
1583
+ indexes.add(payload.frameIndex);
1584
+ shown.set(videoId, indexes);
1585
+ }
1586
+ }
1587
+ return shown;
1588
+ }
1589
+ async function extractDeferredFrames(input) {
1590
+ const deferred = input.extracted.filter((video) => video.deferredRange);
1591
+ if (deferred.length === 0) return [];
1592
+ const shown = shownDeferredFrameIndexes(input, input.frames, input.fps);
1593
+ return Promise.all(
1594
+ deferred.map(async (video) => {
1595
+ const outputDir = join4(input.outputRoot, video.videoId);
1596
+ const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
1597
+ if (indexes.length === 0) {
1598
+ return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
1599
+ }
1600
+ const range = video.deferredRange;
1601
+ const extracted = await extractVideoFramesRange(
1602
+ input.sourceOf(video),
1603
+ video.videoId,
1604
+ range.startTime,
1605
+ range.durationSeconds,
1606
+ {
1607
+ fps: input.fps,
1608
+ outputDir: input.outputRoot,
1609
+ format: range.format,
1610
+ frameRanges: contiguousRuns(indexes),
1611
+ shownBox: input.shownBoxes?.get(video.videoId)
1612
+ },
1613
+ input.signal,
1614
+ input.cfg
1615
+ );
1616
+ const missing = indexes.find((index) => !extracted.framePaths.has(index));
1617
+ if (missing !== void 0) throw input.onMissingFrame(video, missing);
1618
+ return { ...video, outputDir, framePaths: extracted.framePaths, ownedByLookup: false };
1619
+ })
1620
+ );
1621
+ }
1622
+
1623
+ // src/services/render/segmentVideoFrames.ts
1624
+ import {
1625
+ createFrameLookupTable as createFrameLookupTable2,
1626
+ createVideoFrameInjector
1627
+ } from "@hyperframes/engine";
1628
+
1549
1629
  // src/services/render/segmentManifest.ts
1550
1630
  import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
1551
1631
 
@@ -1914,7 +1994,7 @@ import {
1914
1994
 
1915
1995
  // src/services/render/stages/compileStage.ts
1916
1996
  import { existsSync as existsSync7, writeFileSync as writeFileSync3 } from "node:fs";
1917
- import { join as join7 } from "node:path";
1997
+ import { join as join8 } from "node:path";
1918
1998
  import { suggestMatchingPreset } from "@hyperframes/core";
1919
1999
  import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
1920
2000
  import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
@@ -1923,7 +2003,7 @@ import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolu
1923
2003
  import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
1924
2004
  import { createHash as createHash4 } from "node:crypto";
1925
2005
  import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
1926
- import { join as join6, dirname as dirname5, resolve as resolve6, basename as basename3, relative as relative2 } from "path";
2006
+ import { join as join7, dirname as dirname6, resolve as resolve6, basename as basename3, relative as relative2 } from "path";
1927
2007
  import { parseHTML as parseHTML5 } from "linkedom";
1928
2008
  import {
1929
2009
  COMPOSITION_SOURCE_URL,
@@ -2170,7 +2250,7 @@ import {
2170
2250
  // src/services/animatedGifPrep.ts
2171
2251
  import { createHash as createHash2 } from "node:crypto";
2172
2252
  import { copyFileSync as copyFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
2173
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join4, resolve as resolve4 } from "node:path";
2253
+ import { dirname as dirname4, isAbsolute as isAbsolute2, join as join5, resolve as resolve4 } from "node:path";
2174
2254
  import { parseHTML as parseHTML3 } from "linkedom";
2175
2255
  import { parseAnimatedGifMetadata } from "@hyperframes/core";
2176
2256
  import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2, renameWithRetry as renameWithRetry3 } from "@hyperframes/engine";
@@ -2326,11 +2406,11 @@ async function runAnimatedGifTranscode(request) {
2326
2406
  throw result.error ?? new Error(`Animated GIF transcode failed (${result.exitCode}): ${result.stderr.slice(-500)}`);
2327
2407
  }
2328
2408
  async function ensurePreparedWebm(input) {
2329
- if (!existsSync4(dirname3(input.cachePath))) {
2330
- mkdirSync2(dirname3(input.cachePath), { recursive: true });
2409
+ if (!existsSync4(dirname4(input.cachePath))) {
2410
+ mkdirSync2(dirname4(input.cachePath), { recursive: true });
2331
2411
  }
2332
- if (!existsSync4(dirname3(input.outputPath))) {
2333
- mkdirSync2(dirname3(input.outputPath), { recursive: true });
2412
+ if (!existsSync4(dirname4(input.outputPath))) {
2413
+ mkdirSync2(dirname4(input.outputPath), { recursive: true });
2334
2414
  }
2335
2415
  if (!isUsableFile(input.cachePath)) {
2336
2416
  const tmpPath = `${input.cachePath}.tmp-${process.pid}-${Date.now()}`;
@@ -2413,7 +2493,7 @@ function replaceImageWithVideo(input) {
2413
2493
  return video;
2414
2494
  }
2415
2495
  async function prepareAnimatedGifInputs(html, options) {
2416
- const outputDir = options.outputDir ?? join4(options.downloadDir, PREPARED_GIF_SUBDIR);
2496
+ const outputDir = options.outputDir ?? join5(options.downloadDir, PREPARED_GIF_SUBDIR);
2417
2497
  const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_GIF_SUBDIR);
2418
2498
  const cacheDir = options.cacheDir ?? outputDir;
2419
2499
  const { document: document2 } = parseHTML3(html);
@@ -2440,8 +2520,8 @@ async function prepareAnimatedGifInputs(html, options) {
2440
2520
  );
2441
2521
  const hash = computePreparedGifHash(bytes, loopIterations, padSeconds);
2442
2522
  const filename = `${CACHE_SCHEMA}-${hash.slice(0, 24)}.webm`;
2443
- const cachePath = join4(cacheDir, filename);
2444
- const outputPath = join4(outputDir, filename);
2523
+ const cachePath = join5(cacheDir, filename);
2524
+ const outputPath = join5(outputDir, filename);
2445
2525
  const outputSrc = `${outputSrcPrefix}/${filename}`;
2446
2526
  await ensurePreparedWebm({
2447
2527
  sourcePath,
@@ -2486,7 +2566,7 @@ import {
2486
2566
  statSync as statSync3,
2487
2567
  writeFileSync as writeFileSync2
2488
2568
  } from "node:fs";
2489
- import { dirname as dirname4, join as join5, resolve as resolve5 } from "node:path";
2569
+ import { dirname as dirname5, join as join6, resolve as resolve5 } from "node:path";
2490
2570
  import { parseHTML as parseHTML4 } from "linkedom";
2491
2571
  import { renameWithRetry as renameWithRetry4, runFfmpeg as runFfmpeg3, runFfprobe } from "@hyperframes/engine";
2492
2572
 
@@ -2732,8 +2812,8 @@ function isUsableFile2(path) {
2732
2812
  }
2733
2813
  }
2734
2814
  async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
2735
- mkdirSync3(dirname4(cachePath), { recursive: true });
2736
- mkdirSync3(dirname4(outputPath), { recursive: true });
2815
+ mkdirSync3(dirname5(cachePath), { recursive: true });
2816
+ mkdirSync3(dirname5(outputPath), { recursive: true });
2737
2817
  if (!isUsableFile2(cachePath)) {
2738
2818
  const tmpPath = `${cachePath}.tmp-${process.pid}-${Date.now()}`;
2739
2819
  const inlinePath = `${tmpPath}.heic`;
@@ -2760,7 +2840,7 @@ async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
2760
2840
  }
2761
2841
  }
2762
2842
  async function prepareHeifImageInputs(html, options) {
2763
- const outputDir = options.outputDir ?? join5(options.downloadDir, PREPARED_HEIF_SUBDIR);
2843
+ const outputDir = options.outputDir ?? join6(options.downloadDir, PREPARED_HEIF_SUBDIR);
2764
2844
  const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_HEIF_SUBDIR);
2765
2845
  const cacheDir = options.cacheDir ?? outputDir;
2766
2846
  const preparedAssets = /* @__PURE__ */ new Map();
@@ -2781,7 +2861,7 @@ async function prepareHeifImageInputs(html, options) {
2781
2861
  await Promise.all(
2782
2862
  [...unique].map(
2783
2863
  ([filename, source]) => withMediaProbeSlot(
2784
- () => ensurePreparedJpeg(source, join5(cacheDir, filename), join5(outputDir, filename), options)
2864
+ () => ensurePreparedJpeg(source, join6(cacheDir, filename), join6(outputDir, filename), options)
2785
2865
  ).catch((error) => {
2786
2866
  if (!options.onFailure) throw error;
2787
2867
  failed.add(filename);
@@ -2793,8 +2873,8 @@ async function prepareHeifImageInputs(html, options) {
2793
2873
  if (failed.has(filename)) continue;
2794
2874
  const outputSrc = `${outputSrcPrefix}/${filename}`;
2795
2875
  img.setAttribute("src", outputSrc);
2796
- preparedAssets.set(outputSrc, join5(outputDir, filename));
2797
- preparedImages.push({ sourceSrc: src, outputSrc, outputPath: join5(outputDir, filename) });
2876
+ preparedAssets.set(outputSrc, join6(outputDir, filename));
2877
+ preparedImages.push({ sourceSrc: src, outputSrc, outputPath: join6(outputDir, filename) });
2798
2878
  }
2799
2879
  return { html: document2.toString(), preparedAssets, preparedImages };
2800
2880
  }
@@ -3118,7 +3198,7 @@ function detectShaderTransitionUsage(html) {
3118
3198
  async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath, probes) {
3119
3199
  let filePath = src;
3120
3200
  if (isHttpUrl(src)) {
3121
- const remoteDir = join6(downloadDir, REMOTE_MEDIA_SUBDIR);
3201
+ const remoteDir = join7(downloadDir, REMOTE_MEDIA_SUBDIR);
3122
3202
  if (!existsSync6(remoteDir)) mkdirSync4(remoteDir, { recursive: true });
3123
3203
  try {
3124
3204
  filePath = await downloadToTemp(src, remoteDir, void 0, void 0, void 0, {
@@ -3129,7 +3209,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
3129
3209
  }
3130
3210
  } else {
3131
3211
  filePath = decodedUrlPath(src);
3132
- if (!filePath.startsWith("/")) filePath = join6(baseDir, filePath);
3212
+ if (!filePath.startsWith("/")) filePath = join7(baseDir, filePath);
3133
3213
  filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
3134
3214
  }
3135
3215
  if (!isHttpUrl(filePath) && !existsSync6(filePath)) {
@@ -3290,7 +3370,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
3290
3370
  workItems.map(async (item) => {
3291
3371
  const { html: compiledSub } = await compileHtmlFile(
3292
3372
  rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
3293
- dirname5(item.filePath),
3373
+ dirname6(item.filePath),
3294
3374
  downloadDir,
3295
3375
  void 0,
3296
3376
  urlByAbsentLocalPath,
@@ -3767,7 +3847,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
3767
3847
  return downloadAndRewriteUrls(
3768
3848
  urlSet,
3769
3849
  html,
3770
- join6(downloadDir, REMOTE_MEDIA_SUBDIR),
3850
+ join7(downloadDir, REMOTE_MEDIA_SUBDIR),
3771
3851
  "Remote media download failed",
3772
3852
  "Localized remote media source(s)"
3773
3853
  );
@@ -3782,7 +3862,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
3782
3862
  return downloadAndRewriteUrls(
3783
3863
  urlSet,
3784
3864
  html,
3785
- join6(downloadDir, REMOTE_MEDIA_SUBDIR),
3865
+ join7(downloadDir, REMOTE_MEDIA_SUBDIR),
3786
3866
  "Remote image download failed",
3787
3867
  "Localized remote image source(s)"
3788
3868
  );
@@ -3798,7 +3878,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
3798
3878
  return downloadAndRewriteUrls(
3799
3879
  urlSet,
3800
3880
  html,
3801
- join6(downloadDir, REMOTE_MEDIA_SUBDIR),
3881
+ join7(downloadDir, REMOTE_MEDIA_SUBDIR),
3802
3882
  "Remote background-image download failed",
3803
3883
  "Localized remote background-image(s)",
3804
3884
  // Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
@@ -3908,7 +3988,7 @@ function projectStylesheetFontFamilies(html, projectDir) {
3908
3988
  const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
3909
3989
  const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
3910
3990
  const path = decodedUrlPath(url);
3911
- const file = path.startsWith("/") ? resolve6(projectDir, `.${path}`) : resolve6(dirname5(sheet), path);
3991
+ const file = path.startsWith("/") ? resolve6(projectDir, `.${path}`) : resolve6(dirname6(sheet), path);
3912
3992
  return isPathInside2(file, projectDir) && existsSync6(file);
3913
3993
  });
3914
3994
  if (family && shipsFile) families.push(family);
@@ -3950,7 +4030,7 @@ function localizeFontFaceUrls(html, downloadDir) {
3950
4030
  return downloadAndRewriteUrls(
3951
4031
  collectFontFaceUrls(html),
3952
4032
  html,
3953
- join6(downloadDir, REMOTE_MEDIA_SUBDIR),
4033
+ join7(downloadDir, REMOTE_MEDIA_SUBDIR),
3954
4034
  "Remote font download failed",
3955
4035
  "Localized remote font face(s)",
3956
4036
  (h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
@@ -4624,10 +4704,10 @@ function adaptAspectAgnosticResolution(requested, aspectAgnostic, width, height,
4624
4704
  }
4625
4705
  function resolveEntryHtmlPath(input) {
4626
4706
  const { projectDir, workDir, entryFile, log } = input;
4627
- const htmlPath = join7(projectDir, entryFile);
4707
+ const htmlPath = join8(projectDir, entryFile);
4628
4708
  const rawEntry = readTextFile2(htmlPath);
4629
4709
  if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
4630
- const projectIndexPath = join7(projectDir, "index.html");
4710
+ const projectIndexPath = join8(projectDir, "index.html");
4631
4711
  if (!existsSync7(projectIndexPath)) {
4632
4712
  throw new Error(
4633
4713
  `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
@@ -4643,7 +4723,7 @@ function resolveEntryHtmlPath(input) {
4643
4723
  `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
4644
4724
  );
4645
4725
  }
4646
- const wrapperPath = join7(workDir, "standalone-entry.html");
4726
+ const wrapperPath = join8(workDir, "standalone-entry.html");
4647
4727
  writeFileSync3(wrapperPath, standaloneHtml, "utf-8");
4648
4728
  log.info("Extracted standalone entry from index.html host context", { entryFile });
4649
4729
  return wrapperPath;
@@ -4664,7 +4744,7 @@ async function runCompileStage(input) {
4664
4744
  } = input;
4665
4745
  const htmlPath = resolveEntryHtmlPath({ projectDir, workDir, entryFile, log });
4666
4746
  const compileStart = Date.now();
4667
- const compiled = await compileForRender(projectDir, htmlPath, join7(workDir, "downloads"), {
4747
+ const compiled = await compileForRender(projectDir, htmlPath, join8(workDir, "downloads"), {
4668
4748
  log,
4669
4749
  failClosedFontFetch: failClosedFontFetch === true,
4670
4750
  allowSystemFontCapture,
@@ -4672,7 +4752,7 @@ async function runCompileStage(input) {
4672
4752
  variables: input.variables,
4673
4753
  urlByAbsentLocalPath: input.urlByAbsentLocalPath,
4674
4754
  pageMotionBlur: input.pageMotionBlur,
4675
- animatedGifCacheDir: cfg.extractCacheDir ? join7(cfg.extractCacheDir, "animated-gif") : void 0,
4755
+ animatedGifCacheDir: cfg.extractCacheDir ? join8(cfg.extractCacheDir, "animated-gif") : void 0,
4676
4756
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
4677
4757
  });
4678
4758
  assertNotAborted();
@@ -4729,7 +4809,7 @@ async function runCompileStage(input) {
4729
4809
  };
4730
4810
  await preflightCompositionAssetMediaTypes({
4731
4811
  projectDir,
4732
- compiledDir: join7(workDir, "compiled"),
4812
+ compiledDir: join8(workDir, "compiled"),
4733
4813
  composition,
4734
4814
  signal: abortSignal
4735
4815
  });
@@ -4775,7 +4855,7 @@ async function runCompileStage(input) {
4775
4855
 
4776
4856
  // src/services/render/stages/probeStage.ts
4777
4857
  import { createHash as createHash5 } from "node:crypto";
4778
- import { basename as basename4, join as join8 } from "node:path";
4858
+ import { basename as basename4, join as join9 } from "node:path";
4779
4859
  import { parseHTML as parseHTML6 } from "linkedom";
4780
4860
  import {
4781
4861
  closeCaptureSession as closeCaptureSession4,
@@ -4984,7 +5064,7 @@ async function runProbeStage(input) {
4984
5064
  });
4985
5065
  fileServer = await createFileServer({
4986
5066
  projectDir,
4987
- compiledDir: join8(workDir, "compiled"),
5067
+ compiledDir: join9(workDir, "compiled"),
4988
5068
  port: 0,
4989
5069
  preHeadScripts: [VIRTUAL_TIME_SHIM],
4990
5070
  fps: job.config.fps
@@ -5014,7 +5094,7 @@ async function runProbeStage(input) {
5014
5094
  log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
5015
5095
  probeSession = await createCaptureSession4(
5016
5096
  fileServer.url,
5017
- join8(workDir, "probe"),
5097
+ join9(workDir, "probe"),
5018
5098
  captureOpts,
5019
5099
  null,
5020
5100
  probeCfg
@@ -5099,7 +5179,7 @@ async function runProbeStage(input) {
5099
5179
  });
5100
5180
  probeSession = await createCaptureSession4(
5101
5181
  fileServer.url,
5102
- join8(workDir, "probe-screenshot"),
5182
+ join9(workDir, "probe-screenshot"),
5103
5183
  captureOpts,
5104
5184
  null,
5105
5185
  { ...probeCfg, forceScreenshot: true }
@@ -5137,7 +5217,7 @@ async function runProbeStage(input) {
5137
5217
  compiled,
5138
5218
  resolutions,
5139
5219
  projectDir,
5140
- join8(workDir, "downloads")
5220
+ join9(workDir, "downloads")
5141
5221
  );
5142
5222
  assertNotAborted();
5143
5223
  composition.videos = compiled.videos;
@@ -5328,7 +5408,7 @@ async function runProbeStage(input) {
5328
5408
  }
5329
5409
  await preflightCompositionAssetMediaTypes({
5330
5410
  projectDir,
5331
- compiledDir: join8(workDir, "compiled"),
5411
+ compiledDir: join9(workDir, "compiled"),
5332
5412
  composition,
5333
5413
  signal: abortSignal
5334
5414
  });
@@ -5495,10 +5575,10 @@ function validateDistributedDuration(input) {
5495
5575
 
5496
5576
  // src/services/render/stages/extractVideosStage.ts
5497
5577
  import { existsSync as existsSync8 } from "node:fs";
5498
- import { join as join9 } from "node:path";
5578
+ import { join as join10 } from "node:path";
5499
5579
  import {
5500
5580
  classifyVideoExtractionError,
5501
- createFrameLookupTable,
5581
+ createFrameLookupTable as createFrameLookupTable3,
5502
5582
  detectTransfer,
5503
5583
  extractAllVideoFrames,
5504
5584
  extractMediaMetadata as extractMediaMetadata3,
@@ -5510,7 +5590,7 @@ import {
5510
5590
  resolveVideoExtractionWindow as resolveVideoExtractionWindow2,
5511
5591
  isHttpUrl as isHttpUrl2,
5512
5592
  resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
5513
- runVideoExtractionWithRetry,
5593
+ runVideoExtractionWithRetry as runVideoExtractionWithRetry2,
5514
5594
  safeVideoExtractionSourceIdentity,
5515
5595
  videoAudioId as videoAudioId2
5516
5596
  } from "@hyperframes/engine";
@@ -5771,7 +5851,7 @@ async function runExtractVideosStage(input) {
5771
5851
  const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
5772
5852
  if (!existsSync8(videoPath)) return null;
5773
5853
  try {
5774
- const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry(() => extractMediaMetadata3(videoPath), {
5854
+ const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry2(() => extractMediaMetadata3(videoPath), {
5775
5855
  signal: abortSignal,
5776
5856
  maxTransientRetries: extractionPolicy.maxTransientRetries,
5777
5857
  onRetry: () => {
@@ -5841,14 +5921,14 @@ async function runExtractVideosStage(input) {
5841
5921
  // because short boundary counts can differ by one frame.
5842
5922
  {
5843
5923
  fps: job.config.fps,
5844
- outputDir: join9(compiledDir, "__hyperframes_video_frames"),
5924
+ outputDir: join10(compiledDir, "__hyperframes_video_frames"),
5845
5925
  format: job.config.videoFrameFormat ?? "auto",
5846
5926
  toneMapHdrToSdr: rendersSdrOnly(job.config, composition),
5847
5927
  timelineEnd: composition.duration,
5848
5928
  maxTransientRetries: extractionPolicy.maxTransientRetries,
5849
5929
  collectProbeFailures: extractionPolicy.failureMode === "enforce",
5850
5930
  deferRangeExtraction,
5851
- contentKeyedDirs: [join9(compiledDir, REMOTE_MEDIA_SUBDIR)],
5931
+ contentKeyedDirs: [join10(compiledDir, REMOTE_MEDIA_SUBDIR)],
5852
5932
  urlByAbsentLocalPath,
5853
5933
  onProgress: ({ planned = 0, done = 0 }) => {
5854
5934
  framesPlanned += planned;
@@ -5868,7 +5948,7 @@ async function runExtractVideosStage(input) {
5868
5948
  materializeSymlinks
5869
5949
  });
5870
5950
  if (extractionResult.extracted.length > 0) {
5871
- frameLookup = createFrameLookupTable(
5951
+ frameLookup = createFrameLookupTable3(
5872
5952
  composition.videos,
5873
5953
  extractionResult.extracted,
5874
5954
  resolveRenderFpsConfig(job.config.fps).value
@@ -5956,7 +6036,7 @@ import {
5956
6036
 
5957
6037
  // src/services/render/stages/audioStage.ts
5958
6038
  import { existsSync as existsSync10 } from "node:fs";
5959
- import { join as join12 } from "node:path";
6039
+ import { join as join13 } from "node:path";
5960
6040
  import {
5961
6041
  MIXED_AUDIO_FILENAME,
5962
6042
  audioFailureDiagnostics,
@@ -5967,7 +6047,7 @@ import {
5967
6047
 
5968
6048
  // src/services/render/audioDeliveryLimit.ts
5969
6049
  import { existsSync as existsSync9 } from "node:fs";
5970
- import { dirname as dirname7, join as join11 } from "node:path";
6050
+ import { dirname as dirname8, join as join12 } from "node:path";
5971
6051
  import {
5972
6052
  applyAudioFxChain,
5973
6053
  describeFfmpegFailure as describeFfmpegFailure3,
@@ -5978,7 +6058,7 @@ import {
5978
6058
  // src/services/render/audioPadTrim.ts
5979
6059
  import { spawn } from "node:child_process";
5980
6060
  import { mkdtempSync } from "node:fs";
5981
- import { dirname as dirname6, join as join10 } from "node:path";
6061
+ import { dirname as dirname7, join as join11 } from "node:path";
5982
6062
  import {
5983
6063
  DEFAULT_CONFIG,
5984
6064
  describeFfmpegFailure as describeFfmpegFailure2,
@@ -6257,7 +6337,7 @@ async function constrainAacTruePeak(input) {
6257
6337
  }
6258
6338
  async function cutSource(input, scratchDir, attenuationDb) {
6259
6339
  if (!input.sourcePath || !input.rewriteSource) return true;
6260
- const cutPath = join10(scratchDir, "source.wav");
6340
+ const cutPath = join11(scratchDir, "source.wav");
6261
6341
  try {
6262
6342
  const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
6263
6343
  if (!result.success) throw new Error(result.error);
@@ -6271,8 +6351,8 @@ async function cutSource(input, scratchDir, attenuationDb) {
6271
6351
  async function enforceAacTruePeak(input) {
6272
6352
  let scratchDir;
6273
6353
  try {
6274
- scratchDir = mkdtempSync(join10(dirname6(input.audioPath), ".true-peak-"));
6275
- const correctedPath = join10(scratchDir, "audio.m4a");
6354
+ scratchDir = mkdtempSync(join11(dirname7(input.audioPath), ".true-peak-"));
6355
+ const correctedPath = join11(scratchDir, "audio.m4a");
6276
6356
  const measurements = [];
6277
6357
  let attenuationDb = 0;
6278
6358
  let sourcePeakDbfs;
@@ -6463,11 +6543,13 @@ async function runFfprobeJson(args, signal) {
6463
6543
  }
6464
6544
 
6465
6545
  // src/services/render/audioDeliveryLimit.ts
6466
- var LIMIT_CEILING_DBTP = AAC_DELIVERY_TRUE_PEAK_DBFS - 2;
6467
- var LIMIT_CHAIN = {
6546
+ var LIMIT_PASSES = 3;
6547
+ var LOWEST_CEILING_DBTP = AAC_DELIVERY_TRUE_PEAK_DBFS - 2;
6548
+ var PASS_HEADROOM_DB = 0.05;
6549
+ var limitChain = (ceiling) => ({
6468
6550
  version: 1,
6469
- nodes: [{ type: "truepeak", id: "delivery", params: { ceiling: LIMIT_CEILING_DBTP } }]
6470
- };
6551
+ nodes: [{ type: "truepeak", id: "delivery", params: { ceiling } }]
6552
+ });
6471
6553
  async function limitDeliveryPeaks(input, deps = {
6472
6554
  measure: measureTruePeakDbfs,
6473
6555
  applyChain: applyAudioFxChain,
@@ -6479,15 +6561,39 @@ async function limitDeliveryPeaks(input, deps = {
6479
6561
  if (!deps.sourceExists(sourcePath)) return { success: true };
6480
6562
  const peak = await deps.measure(audioPath, { timeoutMs, signal });
6481
6563
  if (peak <= AAC_DELIVERY_TRUE_PEAK_DBFS) return { success: true };
6482
- const limitedPath = join11(dirname7(sourcePath), "audio-source-limited.wav");
6564
+ const alternating = ["a", "b"].map(
6565
+ (k) => join12(dirname8(sourcePath), `audio-source-limited-${k}.wav`)
6566
+ );
6567
+ let encodedFrom;
6568
+ let ceiling = AAC_DELIVERY_TRUE_PEAK_DBFS;
6569
+ for (let pass = 0; pass < LIMIT_PASSES; pass++) {
6570
+ const limitedPath = alternating[pass % 2];
6571
+ const result = await limitPass(input, deps, ceiling, limitedPath);
6572
+ if ("encodeFailure" in result) return result.encodeFailure;
6573
+ if ("limitFailed" in result) break;
6574
+ encodedFrom = limitedPath;
6575
+ if (result.peak <= AAC_DELIVERY_TRUE_PEAK_DBFS || ceiling <= LOWEST_CEILING_DBTP) break;
6576
+ ceiling = Math.max(
6577
+ ceiling - (result.peak - AAC_DELIVERY_TRUE_PEAK_DBFS) - PASS_HEADROOM_DB,
6578
+ LOWEST_CEILING_DBTP
6579
+ );
6580
+ }
6581
+ if (encodedFrom) deps.rename(encodedFrom, sourcePath, { copyFallback: true });
6582
+ return { success: true };
6583
+ }
6584
+ async function limitPass(input, deps, ceiling, limitedPath) {
6585
+ const { audioPath, sourcePath, signal, timeoutMs } = input;
6483
6586
  try {
6484
- await deps.applyChain(sourcePath, LIMIT_CHAIN, limitedPath, { trackId: "delivery", signal });
6587
+ await deps.applyChain(sourcePath, limitChain(ceiling), limitedPath, {
6588
+ trackId: "delivery",
6589
+ signal
6590
+ });
6485
6591
  } catch (error) {
6486
6592
  if (signal?.aborted) throw error;
6487
6593
  input.warn?.("[audio] true-peak limit failed, the whole-mix cut applies", {
6488
6594
  error: error instanceof Error ? error.message : String(error)
6489
6595
  });
6490
- return { success: true };
6596
+ return { limitFailed: true };
6491
6597
  }
6492
6598
  const encoded = await deps.encode(limitedPath, audioPath, input.targetDurationSeconds, {
6493
6599
  signal,
@@ -6495,13 +6601,14 @@ async function limitDeliveryPeaks(input, deps = {
6495
6601
  });
6496
6602
  if (!encoded.success) {
6497
6603
  return {
6498
- success: false,
6499
- error: `[audioDeliveryLimit] ${describeFfmpegFailure3(encoded, timeoutMs)}`,
6500
- failureReason: encoded.failureReason
6604
+ encodeFailure: {
6605
+ success: false,
6606
+ error: `[audioDeliveryLimit] ${describeFfmpegFailure3(encoded, timeoutMs)}`,
6607
+ failureReason: encoded.failureReason
6608
+ }
6501
6609
  };
6502
6610
  }
6503
- deps.rename(limitedPath, sourcePath, { copyFallback: true });
6504
- return { success: true };
6611
+ return { peak: await deps.measure(audioPath, { timeoutMs, signal }) };
6505
6612
  }
6506
6613
 
6507
6614
  // src/services/render/stages/audioStage.ts
@@ -6527,7 +6634,7 @@ function runAudioStage(input) {
6527
6634
  }
6528
6635
  async function runAudioStageScoped(input) {
6529
6636
  const stageStart = Date.now();
6530
- const pcmSourcePath = join12(input.workDir, "audio-source.wav");
6637
+ const pcmSourcePath = join13(input.workDir, "audio-source.wav");
6531
6638
  const duration = input.totalFrames * input.fps.den / input.fps.num;
6532
6639
  let deliverySourcePath;
6533
6640
  try {
@@ -6592,7 +6699,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
6592
6699
  assertNotAborted,
6593
6700
  log
6594
6701
  } = input;
6595
- const audioOutputPath = join12(workDir, MIXED_AUDIO_FILENAME);
6702
+ const audioOutputPath = join13(workDir, MIXED_AUDIO_FILENAME);
6596
6703
  let hasAudio = false;
6597
6704
  let audioError;
6598
6705
  let audioFailures;
@@ -6603,7 +6710,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
6603
6710
  audioResult = await processCompositionAudio(
6604
6711
  audios,
6605
6712
  projectDir,
6606
- join12(workDir, "audio-work"),
6713
+ join13(workDir, "audio-work"),
6607
6714
  audioOutputPath,
6608
6715
  duration,
6609
6716
  abortSignal,
@@ -6672,7 +6779,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
6672
6779
 
6673
6780
  // src/services/render/stages/captureStage.ts
6674
6781
  import { existsSync as existsSync11, readdirSync, statfsSync } from "node:fs";
6675
- import { join as join13 } from "node:path";
6782
+ import { join as join14 } from "node:path";
6676
6783
  import {
6677
6784
  classifyCaptureFailure as classifyCaptureFailure2,
6678
6785
  frameFileExtension,
@@ -6726,13 +6833,13 @@ function measureCaptureFrameBytes(workDir, framesDir) {
6726
6833
  if (!existsSync11(workDir)) return total;
6727
6834
  for (const attempt of readdirSync(workDir)) {
6728
6835
  if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
6729
- total += sampleDirectoryBytes(join13(workDir, attempt));
6836
+ total += sampleDirectoryBytes(join14(workDir, attempt));
6730
6837
  continue;
6731
6838
  }
6732
6839
  if (!/^capture-attempt-\d+$/.test(attempt)) continue;
6733
- const attemptDir = join13(workDir, attempt);
6840
+ const attemptDir = join14(workDir, attempt);
6734
6841
  for (const name of readdirSync(attemptDir)) {
6735
- if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join13(attemptDir, name));
6842
+ if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join14(attemptDir, name));
6736
6843
  }
6737
6844
  }
6738
6845
  return total;
@@ -7069,7 +7176,7 @@ import {
7069
7176
  writeFileSync as writeFileSync4
7070
7177
  } from "node:fs";
7071
7178
  import { readFile as readFile2 } from "node:fs/promises";
7072
- import { dirname as dirname8, join as join14 } from "node:path";
7179
+ import { dirname as dirname9, join as join15 } from "node:path";
7073
7180
  import {
7074
7181
  encodeFramesChunkedConcat,
7075
7182
  encodeFramesFromDir,
@@ -7280,7 +7387,7 @@ function encodeWhileCapturing(input, totalFrames) {
7280
7387
  await new Promise((resolve10) => wake = resolve10);
7281
7388
  }
7282
7389
  if (signal.aborted) return;
7283
- const mergedPath = join14(input.framesDir, formatCaptureFrameName(next, "jpg"));
7390
+ const mergedPath = join15(input.framesDir, formatCaptureFrameName(next, "jpg"));
7284
7391
  const frame = await readLandedFrame(landed.get(next), mergedPath);
7285
7392
  landed.delete(next);
7286
7393
  if (!await stream.writeFrame(frame)) {
@@ -7363,11 +7470,11 @@ async function runEncodeStage(input) {
7363
7470
  );
7364
7471
  }
7365
7472
  captured.forEach((name, i) => {
7366
- const dst = join14(outputPath, formatExportFrameName(i, "png"));
7367
- copyFileSync4(join14(framesDir, name), dst);
7473
+ const dst = join15(outputPath, formatExportFrameName(i, "png"));
7474
+ copyFileSync4(join15(framesDir, name), dst);
7368
7475
  });
7369
7476
  if (hasAudio && audioOutputPath && existsSync12(audioOutputPath)) {
7370
- copyFileSync4(audioOutputPath, join14(outputPath, MIXED_AUDIO_FILENAME2));
7477
+ copyFileSync4(audioOutputPath, join15(outputPath, MIXED_AUDIO_FILENAME2));
7371
7478
  log.info(
7372
7479
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
7373
7480
  );
@@ -7387,7 +7494,7 @@ async function runEncodeStage(input) {
7387
7494
  const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
7388
7495
  fps: job.config.fps,
7389
7496
  loop,
7390
- palettePath: join14(dirname8(videoOnlyPath), "gif-palette.png"),
7497
+ palettePath: join15(dirname9(videoOnlyPath), "gif-palette.png"),
7391
7498
  preserveAlpha: needsAlpha,
7392
7499
  signal: abortSignal,
7393
7500
  timeout: engineCfg.ffmpegEncodeTimeout
@@ -7446,7 +7553,7 @@ function sampleDirectoryBytes(dir) {
7446
7553
  continue;
7447
7554
  }
7448
7555
  for (const name of entries) {
7449
- const full = join15(current, name);
7556
+ const full = join16(current, name);
7450
7557
  try {
7451
7558
  const st = statSync5(full);
7452
7559
  if (st.isDirectory()) {
@@ -7528,7 +7635,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
7528
7635
  const ranges = [];
7529
7636
  let rangeStart = null;
7530
7637
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
7531
- const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
7638
+ const framePath = join16(framesDir, formatCaptureFrameName(frameIndex, frameExt));
7532
7639
  const missing = !existsSync13(framePath) || statSync5(framePath).size <= 8;
7533
7640
  if (missing && rangeStart === null) {
7534
7641
  rangeStart = frameIndex;
@@ -7545,7 +7652,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
7545
7652
  function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
7546
7653
  const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
7547
7654
  const batches = [];
7548
- const outputDirFor = (batchIndex, workerId) => join15(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
7655
+ const outputDirFor = (batchIndex, workerId) => join16(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
7549
7656
  if (stride > 1) {
7550
7657
  const spans = /* @__PURE__ */ new Map();
7551
7658
  for (const range of ranges) {
@@ -7618,7 +7725,7 @@ function isRecoverableParallelCaptureError(error) {
7618
7725
  function countCapturedFrames(totalFrames, framesDir, frameExt) {
7619
7726
  let captured = 0;
7620
7727
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
7621
- const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
7728
+ const framePath = join16(framesDir, formatCaptureFrameName(frameIndex, frameExt));
7622
7729
  if (existsSync13(framePath)) captured++;
7623
7730
  }
7624
7731
  return captured;
@@ -7644,7 +7751,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
7644
7751
  reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
7645
7752
  });
7646
7753
  pendingTransientRetry = false;
7647
- const attemptWorkDir = join15(options.workDir, `capture-attempt-${attempt}`);
7754
+ const attemptWorkDir = join16(options.workDir, `capture-attempt-${attempt}`);
7648
7755
  const batches = missingRanges ? buildMissingFrameRetryBatches(
7649
7756
  missingRanges,
7650
7757
  currentWorkers,
@@ -7837,27 +7944,25 @@ function explainStreamingEncodeGate(cfg, outputFormat, workerCount, durationSeco
7837
7944
  var DEFAULT_SEGMENTED_MIN_SECONDS = Number.POSITIVE_INFINITY;
7838
7945
 
7839
7946
  // src/services/distributed/planFrameCost.ts
7840
- import { join as join18 } from "node:path";
7947
+ import { join as join19 } from "node:path";
7841
7948
  import {
7842
7949
  closeCaptureSession as closeCaptureSession9,
7843
7950
  createCaptureSession as createCaptureSession9,
7844
- createFrameLookupTable as createFrameLookupTable3,
7951
+ createFrameLookupTable as createFrameLookupTable4,
7845
7952
  initializeSession as initializeSession9
7846
7953
  } from "@hyperframes/engine";
7847
7954
 
7848
7955
  // src/services/distributed/chunkFrames.ts
7849
7956
  import { existsSync as existsSync15, readdirSync as readdirSync4 } from "node:fs";
7850
- import { extname as extname2, join as join17 } from "node:path";
7957
+ import { extname as extname2, join as join18 } from "node:path";
7851
7958
  import {
7852
7959
  compositionRequiresWebGpu as compositionRequiresWebGpu3,
7853
- createFrameLookupTable as createFrameLookupTable2,
7854
- createVideoFrameInjector as createVideoFrameInjector2,
7855
- extractVideoFramesRange
7960
+ createVideoFrameInjector as createVideoFrameInjector3
7856
7961
  } from "@hyperframes/engine";
7857
7962
 
7858
7963
  // src/services/distributed/shared.ts
7859
7964
  import { execFile as execFileCallback } from "node:child_process";
7860
- import { dirname as dirname10, join as join16 } from "node:path";
7965
+ import { dirname as dirname11, join as join17 } from "node:path";
7861
7966
  import { existsSync as existsSync14, readFileSync as readFileSync6 } from "node:fs";
7862
7967
  import { fileURLToPath as fileURLToPath3 } from "node:url";
7863
7968
  import { promisify as promisify2 } from "node:util";
@@ -7873,7 +7978,7 @@ function isPlanAudioArtifactPath(path) {
7873
7978
  }
7874
7979
  function resolvePlanAudioPath(planDir) {
7875
7980
  for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
7876
- const candidate = join16(planDir, name);
7981
+ const candidate = join17(planDir, name);
7877
7982
  if (existsSync14(candidate)) return candidate;
7878
7983
  }
7879
7984
  return null;
@@ -8131,10 +8236,10 @@ function buildSyntheticRenderJob(input) {
8131
8236
  var cachedProducerVersion = null;
8132
8237
  function readProducerVersion() {
8133
8238
  if (cachedProducerVersion !== null) return cachedProducerVersion;
8134
- const startDir = dirname10(fileURLToPath3(import.meta.url));
8239
+ const startDir = dirname11(fileURLToPath3(import.meta.url));
8135
8240
  let current = startDir;
8136
8241
  for (let i = 0; i < 10; i++) {
8137
- const candidate = join16(current, "package.json");
8242
+ const candidate = join17(current, "package.json");
8138
8243
  if (existsSync14(candidate)) {
8139
8244
  try {
8140
8245
  const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
@@ -8145,7 +8250,7 @@ function readProducerVersion() {
8145
8250
  } catch {
8146
8251
  }
8147
8252
  }
8148
- const parent = dirname10(current);
8253
+ const parent = dirname11(current);
8149
8254
  if (parent === current) break;
8150
8255
  current = parent;
8151
8256
  }
@@ -8170,11 +8275,11 @@ var RenderChunkValidationError = class extends Error {
8170
8275
  };
8171
8276
  function createChunkVideoFrameInjectorFactory(frameLookup, options) {
8172
8277
  if (!(frameLookup instanceof Promise))
8173
- return () => createVideoFrameInjector2(frameLookup, options);
8278
+ return () => createVideoFrameInjector3(frameLookup, options);
8174
8279
  return () => {
8175
8280
  let hook;
8176
8281
  return async (page, time, heldVideoTime) => {
8177
- hook ??= createVideoFrameInjector2(await frameLookup, options);
8282
+ hook ??= createVideoFrameInjector3(await frameLookup, options);
8178
8283
  await hook?.(page, time, heldVideoTime);
8179
8284
  };
8180
8285
  };
@@ -8188,7 +8293,7 @@ function frameNumberFromFileName(name) {
8188
8293
  function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
8189
8294
  const result = [];
8190
8295
  for (const v of videos) {
8191
- const outputDir = join17(planDir, "video-frames", v.videoId);
8296
+ const outputDir = join18(planDir, "video-frames", v.videoId);
8192
8297
  if (!existsSync15(outputDir)) {
8193
8298
  throw new Error(
8194
8299
  `[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
@@ -8207,7 +8312,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
8207
8312
  if (!frameName) continue;
8208
8313
  const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
8209
8314
  const frameIndex = frameNumber === null ? i : frameNumber - 1;
8210
- framePaths.set(frameIndex, join17(outputDir, frameName));
8315
+ framePaths.set(frameIndex, join18(outputDir, frameName));
8211
8316
  }
8212
8317
  result.push({
8213
8318
  videoId: v.videoId,
@@ -8224,15 +8329,6 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
8224
8329
  }
8225
8330
  return result;
8226
8331
  }
8227
- function contiguousRuns(sortedIndexes) {
8228
- const runs2 = [];
8229
- for (const index of sortedIndexes) {
8230
- const last = runs2[runs2.length - 1];
8231
- if (last && last.firstFrame + last.frames === index) last.frames++;
8232
- else runs2.push({ firstFrame: index, frames: 1 });
8233
- }
8234
- return runs2;
8235
- }
8236
8332
  function buildChunkCaptureOptions(input) {
8237
8333
  const { dimensions, encoder } = input;
8238
8334
  const videoCaptureBeyondViewport = resolveVideoCaptureBeyondViewport(input.videoCount);
@@ -8255,28 +8351,19 @@ function buildChunkCaptureOptions(input) {
8255
8351
  requiresWebGpu: compositionRequiresWebGpu3(input.html)
8256
8352
  };
8257
8353
  }
8258
- function shownDeferredFrameIndexes(planVideos, frames, fps) {
8259
- const shown = /* @__PURE__ */ new Map();
8260
- const deferred = planVideos.extracted.filter((video) => video.deferredRange);
8261
- if (deferred.length === 0) return shown;
8262
- const table = createFrameLookupTable2(
8263
- planVideos.videos,
8264
- deferred.map((video) => ({
8265
- ...video,
8266
- outputDir: "",
8267
- framePaths: new Map(Array.from({ length: video.totalFrames }, (_, i) => [i, String(i)]))
8268
- })),
8269
- resolveRenderFpsConfig(fps).value
8354
+ function planExtractedFrames(planVideos) {
8355
+ return planVideos.extracted.map((video) => ({
8356
+ ...video,
8357
+ outputDir: "",
8358
+ framePaths: /* @__PURE__ */ new Map()
8359
+ }));
8360
+ }
8361
+ function shownPlanFrameIndexes(planVideos, frames, fps) {
8362
+ return shownDeferredFrameIndexes(
8363
+ { videos: planVideos.videos, extracted: planExtractedFrames(planVideos) },
8364
+ frames,
8365
+ fps
8270
8366
  );
8271
- for (const frame of frames) {
8272
- const globalTime = frame * fps.den / fps.num;
8273
- for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
8274
- const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
8275
- indexes.add(payload.frameIndex);
8276
- shown.set(videoId, indexes);
8277
- }
8278
- }
8279
- return shown;
8280
8367
  }
8281
8368
  async function extractDeferredVideoFramesForChunk(input) {
8282
8369
  const deferred = input.planVideos.extracted.filter((video) => video.deferredRange);
@@ -8293,49 +8380,32 @@ async function extractDeferredVideoFramesForChunk(input) {
8293
8380
  url: remoteSources[path].split("?")[0]
8294
8381
  });
8295
8382
  }
8296
- if (deferred.length === 0) return [];
8297
- const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
8298
- return Promise.all(
8299
- deferred.map(async (video) => {
8300
- const outputDir = join17(input.outputRoot, video.videoId);
8301
- const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
8302
- if (indexes.length === 0) {
8303
- return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
8304
- }
8305
- const range = video.deferredRange;
8383
+ const planned = new Map(input.planVideos.extracted.map((video) => [video.videoId, video]));
8384
+ return extractDeferredFrames({
8385
+ videos: input.planVideos.videos,
8386
+ extracted: planExtractedFrames(input.planVideos),
8387
+ frames: input.frames,
8388
+ fps: input.fps,
8389
+ outputRoot: input.outputRoot,
8390
+ cfg: input.cfg,
8391
+ shownBoxes: input.shownBoxes,
8392
+ sourceOf: (video) => {
8393
+ const range = planned.get(video.videoId).deferredRange;
8306
8394
  const remoteUrl = remoteSources[range.sourcePath];
8307
- const sourcePath = remoteUrl ?? join17(input.planDir, ...range.sourcePath.split("/"));
8395
+ const sourcePath = remoteUrl ?? join18(input.planDir, ...range.sourcePath.split("/"));
8308
8396
  if (remoteUrl === void 0 && !existsSync15(sourcePath)) {
8309
8397
  throw new RenderChunkValidationError(
8310
8398
  MISSING_PLAN_ARTIFACT,
8311
8399
  `[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
8312
8400
  );
8313
8401
  }
8314
- const extracted = await extractVideoFramesRange(
8315
- sourcePath,
8316
- video.videoId,
8317
- range.startTime,
8318
- range.durationSeconds,
8319
- {
8320
- fps: input.fps,
8321
- outputDir: input.outputRoot,
8322
- format: range.format,
8323
- frameRanges: contiguousRuns(indexes),
8324
- shownBox: input.shownBoxes?.get(video.videoId)
8325
- },
8326
- void 0,
8327
- input.cfg
8328
- );
8329
- const missing = indexes.find((index) => !extracted.framePaths.has(index));
8330
- if (missing !== void 0) {
8331
- throw new RenderChunkValidationError(
8332
- INVALID_VIDEO_METADATA,
8333
- `[renderChunk] video ${JSON.stringify(video.videoId)} has no frame ${missing}; the plan expected ${video.totalFrames} frames from ${range.sourcePath}.`
8334
- );
8335
- }
8336
- return { ...video, outputDir, framePaths: extracted.framePaths, ownedByLookup: false };
8337
- })
8338
- );
8402
+ return sourcePath;
8403
+ },
8404
+ onMissingFrame: (video, index) => new RenderChunkValidationError(
8405
+ INVALID_VIDEO_METADATA,
8406
+ `[renderChunk] video ${JSON.stringify(video.videoId)} has no frame ${index}; the plan expected ${video.totalFrames} frames from ${planned.get(video.videoId).deferredRange.sourcePath}.`
8407
+ )
8408
+ });
8339
8409
  }
8340
8410
 
8341
8411
  // src/services/distributed/planFrameCost.ts
@@ -8353,12 +8423,12 @@ async function measurePlanSecondsPerFrame(input) {
8353
8423
  planVideos,
8354
8424
  frames: sampled,
8355
8425
  fps: fpsRatio,
8356
- outputRoot: join18(workDir, "frame-cost-video-frames"),
8426
+ outputRoot: join19(workDir, "frame-cost-video-frames"),
8357
8427
  cfg: input.cfg
8358
8428
  })
8359
8429
  ];
8360
- const lookup = extracted.length > 0 ? createFrameLookupTable3(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
8361
- const compiledDir = join18(planDir, "compiled");
8430
+ const lookup = extracted.length > 0 ? createFrameLookupTable4(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
8431
+ const compiledDir = join19(planDir, "compiled");
8362
8432
  const fileServer = await createFileServer({
8363
8433
  projectDir: compiledDir,
8364
8434
  compiledDir,
@@ -8370,7 +8440,7 @@ async function measurePlanSecondsPerFrame(input) {
8370
8440
  try {
8371
8441
  session = await createCaptureSession9(
8372
8442
  fileServer.url,
8373
- join18(workDir, "frame-cost"),
8443
+ join19(workDir, "frame-cost"),
8374
8444
  buildChunkCaptureOptions({
8375
8445
  dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
8376
8446
  encoder: input,
@@ -8467,7 +8537,7 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
8467
8537
  }
8468
8538
 
8469
8539
  // src/services/render/planReferenceBackground.ts
8470
- import { join as join19 } from "node:path";
8540
+ import { join as join20 } from "node:path";
8471
8541
  import {
8472
8542
  closeCaptureSession as closeCaptureSession10,
8473
8543
  createCaptureSession as createCaptureSession10,
@@ -8480,14 +8550,14 @@ async function readPlanReferenceBackground(input) {
8480
8550
  try {
8481
8551
  server = await createFileServer({
8482
8552
  projectDir: input.projectDir,
8483
- compiledDir: join19(input.workDir, "compiled"),
8553
+ compiledDir: join20(input.workDir, "compiled"),
8484
8554
  port: 0,
8485
8555
  preHeadScripts: [VIRTUAL_TIME_SHIM],
8486
8556
  fps: input.options.fps
8487
8557
  });
8488
8558
  session = await createCaptureSession10(
8489
8559
  server.url,
8490
- join19(input.workDir, "reference"),
8560
+ join20(input.workDir, "reference"),
8491
8561
  {
8492
8562
  ...input.options,
8493
8563
  requiresWebGpu: false
@@ -8524,7 +8594,7 @@ async function readPlanReferenceBackground(input) {
8524
8594
 
8525
8595
  // src/services/render/stages/freezePlan.ts
8526
8596
  import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
8527
- import { join as join20, relative as relative3, resolve as resolve8 } from "node:path";
8597
+ import { join as join21, relative as relative3, resolve as resolve8 } from "node:path";
8528
8598
  import {
8529
8599
  REFERENCE_BACKGROUND_FIELD,
8530
8600
  referenceBackgroundResult as referenceBackgroundResult3
@@ -8742,7 +8812,7 @@ function listPlanFiles(planDir) {
8742
8812
  function walk(dir) {
8743
8813
  const entries = readdirSync5(dir, { withFileTypes: true });
8744
8814
  for (const entry of entries) {
8745
- const full = join20(dir, entry.name);
8815
+ const full = join21(dir, entry.name);
8746
8816
  if (entry.isDirectory()) {
8747
8817
  walk(full);
8748
8818
  } else if (entry.isFile()) {
@@ -8783,15 +8853,15 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
8783
8853
  function writePlanRemoteFiles(planDir, files) {
8784
8854
  if (files.length === 0) return;
8785
8855
  const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
8786
- writeFileSync6(join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
8856
+ writeFileSync6(join21(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
8787
8857
  }
8788
8858
  function readPlanRemoteFiles(planDir) {
8789
- const path = join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
8859
+ const path = join21(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
8790
8860
  return existsSync16(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
8791
8861
  }
8792
8862
  function recomputePlanHashFromPlanDir(planDir, digests) {
8793
- const planJsonPath = join20(planDir, "plan.json");
8794
- const encoderJsonPath = join20(planDir, "meta", "encoder.json");
8863
+ const planJsonPath = join21(planDir, "plan.json");
8864
+ const encoderJsonPath = join21(planDir, "meta", "encoder.json");
8795
8865
  if (!existsSync16(planJsonPath)) {
8796
8866
  throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
8797
8867
  }
@@ -8828,18 +8898,18 @@ async function freezePlan(input) {
8828
8898
  if (!existsSync16(planDir)) {
8829
8899
  throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
8830
8900
  }
8831
- const metaDir = join20(planDir, "meta");
8901
+ const metaDir = join21(planDir, "meta");
8832
8902
  if (!existsSync16(metaDir)) mkdirSync7(metaDir, { recursive: true });
8833
8903
  writeFileSync6(
8834
- join20(metaDir, "composition.json"),
8904
+ join21(metaDir, "composition.json"),
8835
8905
  `${JSON.stringify(composition, null, 2)}
8836
8906
  `,
8837
8907
  "utf-8"
8838
8908
  );
8839
8909
  const encoderForCanonical = stripUndefined(encoder);
8840
8910
  const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
8841
- writeFileSync6(join20(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
8842
- writeFileSync6(join20(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
8911
+ writeFileSync6(join21(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
8912
+ writeFileSync6(join21(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
8843
8913
  `, "utf-8");
8844
8914
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
8845
8915
  const planHash = computePlanHash({
@@ -8864,7 +8934,7 @@ async function freezePlan(input) {
8864
8934
  duration: durationSeconds,
8865
8935
  hasAudio
8866
8936
  };
8867
- const planJsonPath = join20(planDir, "plan.json");
8937
+ const planJsonPath = join21(planDir, "plan.json");
8868
8938
  writeFileSync6(planJsonPath, `${JSON.stringify(planJson, null, 2)}
8869
8939
  `, "utf-8");
8870
8940
  return { planJsonPath, planHash };
@@ -8908,14 +8978,14 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
8908
8978
  // src/services/distributed/chunkAudio.ts
8909
8979
  import { closeSync as closeSync4, existsSync as existsSync17, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
8910
8980
  import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
8911
- import { join as join22 } from "node:path";
8981
+ import { join as join23 } from "node:path";
8912
8982
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
8913
8983
 
8914
8984
  // src/services/distributed/mp4Stitch.ts
8915
8985
  import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync2, rmSync as rmSync6 } from "node:fs";
8916
8986
  import { open } from "node:fs/promises";
8917
8987
  import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
8918
- import { dirname as dirname11, join as join21 } from "node:path";
8988
+ import { dirname as dirname12, join as join22 } from "node:path";
8919
8989
  import { finished, pipeline } from "node:stream/promises";
8920
8990
  function boxAt(src, off, at, limit) {
8921
8991
  let size = src.readUInt32BE(off);
@@ -9279,15 +9349,15 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
9279
9349
  const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
9280
9350
  const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
9281
9351
  const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
9282
- mkdirSync8(dirname11(outputPath), { recursive: true });
9352
+ mkdirSync8(dirname12(outputPath), { recursive: true });
9283
9353
  const workDir = mkdtempSync2(`${outputPath}.stitch-`);
9284
- const out = createWriteStream(join21(workDir, "out.mp4"));
9354
+ const out = createWriteStream(join22(workDir, "out.mp4"));
9285
9355
  try {
9286
9356
  out.write(header);
9287
9357
  for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
9288
9358
  out.end();
9289
9359
  await finished(out);
9290
- renameWithRetry7(join21(workDir, "out.mp4"), outputPath);
9360
+ renameWithRetry7(join22(workDir, "out.mp4"), outputPath);
9291
9361
  } finally {
9292
9362
  out.destroy();
9293
9363
  rmSync6(workDir, { recursive: true, force: true });
@@ -9381,7 +9451,7 @@ async function writeChunkAudioSlices(input) {
9381
9451
  });
9382
9452
  const bytes = readFileSync8(input.audioPath);
9383
9453
  const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
9384
- mkdirSync9(join22(input.planDir, SLICE_DIR), { recursive: true });
9454
+ mkdirSync9(join23(input.planDir, SLICE_DIR), { recursive: true });
9385
9455
  input.chunks.forEach((_, index) => {
9386
9456
  const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
9387
9457
  if (!cut) return;
@@ -9389,17 +9459,17 @@ async function writeChunkAudioSlices(input) {
9389
9459
  const start = offsets[cut.firstPacket];
9390
9460
  layout.partStarts[index] = start;
9391
9461
  const end = offsets[last] + track.sizes[last];
9392
- writeFileSync7(join22(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
9462
+ writeFileSync7(join23(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
9393
9463
  });
9394
9464
  const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
9395
- writeFileSync7(join22(input.planDir, SLICE_FRAME), Buffer.concat(frame));
9396
- writeFileSync7(join22(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
9465
+ writeFileSync7(join23(input.planDir, SLICE_FRAME), Buffer.concat(frame));
9466
+ writeFileSync7(join23(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
9397
9467
  }
9398
9468
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
9399
- const layoutPath = join22(planDir, SLICE_LAYOUT);
9469
+ const layoutPath = join23(planDir, SLICE_LAYOUT);
9400
9470
  if (!existsSync17(layoutPath)) return resolvePlanAudioPath(planDir);
9401
9471
  const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
9402
- const frame = readFileSync8(join22(planDir, SLICE_FRAME));
9472
+ const frame = readFileSync8(join23(planDir, SLICE_FRAME));
9403
9473
  const fd = openSync4(scratchPath, "w");
9404
9474
  try {
9405
9475
  writeSync(fd, frame, 0, layout.payloadStart, 0);
@@ -9412,7 +9482,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
9412
9482
  );
9413
9483
  const start = layout.partStarts[chunkIndex];
9414
9484
  if (start !== void 0) {
9415
- const part = readFileSync8(join22(planDir, chunkAudioSlicePath(chunkIndex)));
9485
+ const part = readFileSync8(join23(planDir, chunkAudioSlicePath(chunkIndex)));
9416
9486
  writeSync(fd, part, 0, part.length, start);
9417
9487
  }
9418
9488
  ftruncateSync(fd, layout.sizeBytes);
@@ -9468,7 +9538,7 @@ async function muxChunkWithAudio(input) {
9468
9538
  // src/services/distributed/planSize.ts
9469
9539
  import { createHash as createHash7 } from "node:crypto";
9470
9540
  import { lstatSync, readdirSync as readdirSync6 } from "node:fs";
9471
- import { extname as extname3, join as join23, relative as relative4 } from "node:path";
9541
+ import { extname as extname3, join as join24, relative as relative4 } from "node:path";
9472
9542
  var MAX_TOP_COMPONENTS = 10;
9473
9543
  var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
9474
9544
  ".aac",
@@ -9580,7 +9650,7 @@ function walkRegularFiles(dir, visit) {
9580
9650
  return;
9581
9651
  }
9582
9652
  for (const entry of entries) {
9583
- const full = join23(dir, entry.name);
9653
+ const full = join24(dir, entry.name);
9584
9654
  if (entry.isDirectory()) {
9585
9655
  walkRegularFiles(full, visit);
9586
9656
  } else if (entry.isFile()) {
@@ -9796,13 +9866,13 @@ function assertPositiveInteger(name, value) {
9796
9866
  }
9797
9867
  var FREEZE_OWNED_PLAN_FILES = [
9798
9868
  "plan.json",
9799
- join24("meta", "composition.json"),
9800
- join24("meta", "encoder.json"),
9801
- join24("meta", "chunks.json")
9869
+ join25("meta", "composition.json"),
9870
+ join25("meta", "encoder.json"),
9871
+ join25("meta", "chunks.json")
9802
9872
  ];
9803
9873
  function removeFreezeOwnedPlanFiles(planDir) {
9804
9874
  for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
9805
- rmSync7(join24(planDir, relativePath), { force: true });
9875
+ rmSync7(join25(planDir, relativePath), { force: true });
9806
9876
  }
9807
9877
  }
9808
9878
  function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
@@ -9957,19 +10027,19 @@ function createVideoSourceShipper(input) {
9957
10027
  const known = shipped.get(srcPath);
9958
10028
  if (known) return known;
9959
10029
  const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
9960
- const current = stagedRel === null ? srcPath : join24(input.planDir, "video-frames", stagedRel);
10030
+ const current = stagedRel === null ? srcPath : join25(input.planDir, "video-frames", stagedRel);
9961
10031
  let result = null;
9962
10032
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
9963
10033
  const rel = pathInside(root, srcPath);
9964
- if (rel !== null && (existsSync18(join24(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
10034
+ if (rel !== null && (existsSync18(join25(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
9965
10035
  result = ["compiled", ...rel.split(sep2)].join("/");
9966
10036
  break;
9967
10037
  }
9968
10038
  }
9969
10039
  if (result === null) {
9970
10040
  result = `video-sources/${shipped.size}${extname4(current)}`;
9971
- const destination = join24(input.planDir, ...result.split("/"));
9972
- mkdirSync10(join24(input.planDir, "video-sources"), { recursive: true });
10041
+ const destination = join25(input.planDir, ...result.split("/"));
10042
+ mkdirSync10(join25(input.planDir, "video-sources"), { recursive: true });
9973
10043
  try {
9974
10044
  linkSync(current, destination);
9975
10045
  } catch {
@@ -10018,14 +10088,14 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
10018
10088
  variables: config.variables
10019
10089
  });
10020
10090
  const entryFile = config.entryFile ?? "index.html";
10021
- const htmlPath = join24(projectDir, entryFile);
10091
+ const htmlPath = join25(projectDir, entryFile);
10022
10092
  if (!existsSync18(htmlPath)) {
10023
10093
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
10024
10094
  }
10025
- const workDir = join24(planDir, ".plan-work");
10095
+ const workDir = join25(planDir, ".plan-work");
10026
10096
  rmSync7(workDir, { recursive: true, force: true });
10027
10097
  mkdirSync10(workDir, { recursive: true });
10028
- const compiledDir = join24(workDir, "compiled");
10098
+ const compiledDir = join25(workDir, "compiled");
10029
10099
  mkdirSync10(compiledDir, { recursive: true });
10030
10100
  let stageStarted = Date.now();
10031
10101
  cpSync2(projectDir, compiledDir, {
@@ -10039,10 +10109,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
10039
10109
  }
10040
10110
  });
10041
10111
  logPlanStage(log, "copy", stageStarted);
10042
- const finalCompiledDir = join24(planDir, "compiled");
10112
+ const finalCompiledDir = join25(planDir, "compiled");
10043
10113
  const needsAlpha = outputNeedsAlpha(config.format);
10044
10114
  const remoteFiles = Object.entries(options.remoteFiles ?? {});
10045
- const remoteLocalPath = (rel) => join24(projectDir, ...rel.split("/"));
10115
+ const remoteLocalPath = (rel) => join25(projectDir, ...rel.split("/"));
10046
10116
  for (const [, file] of remoteFiles) {
10047
10117
  declareContentAddressedUrl(file.url, file.sha256);
10048
10118
  if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
@@ -10245,10 +10315,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10245
10315
  let stageStarted = Date.now();
10246
10316
  const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
10247
10317
  logPlanStage(log, "extract", stageStarted);
10248
- const stagedVideoFrames = join24(compiledDir, "__hyperframes_video_frames");
10249
- const videoFramesDst = join24(planDir, "video-frames");
10318
+ const stagedVideoFrames = join25(compiledDir, "__hyperframes_video_frames");
10319
+ const videoFramesDst = join25(planDir, "video-frames");
10250
10320
  if (existsSync18(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
10251
- const videoSourcesDir = join24(planDir, "video-sources");
10321
+ const videoSourcesDir = join25(planDir, "video-sources");
10252
10322
  rmSync7(videoSourcesDir, { recursive: true, force: true });
10253
10323
  if (existsSync18(stagedVideoFrames)) {
10254
10324
  renameWithRetry8(stagedVideoFrames, videoFramesDst);
@@ -10285,7 +10355,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10285
10355
  };
10286
10356
  })
10287
10357
  });
10288
- mkdirSync10(join24(planDir, "meta"), { recursive: true });
10358
+ mkdirSync10(join25(planDir, "meta"), { recursive: true });
10289
10359
  writePlanRemoteFiles(
10290
10360
  planDir,
10291
10361
  remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
@@ -10295,7 +10365,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10295
10365
  }))
10296
10366
  );
10297
10367
  writeFileSync8(
10298
- join24(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
10368
+ join25(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
10299
10369
  JSON.stringify(planVideosJson, null, 2),
10300
10370
  "utf-8"
10301
10371
  );
@@ -10374,7 +10444,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10374
10444
  if (audioResult?.audioError) {
10375
10445
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
10376
10446
  }
10377
- const planAudioPath = join24(planDir, PLAN_AUDIO_RELATIVE_PATH);
10447
+ const planAudioPath = join25(planDir, PLAN_AUDIO_RELATIVE_PATH);
10378
10448
  if (audioResult?.hasAudio && existsSync18(audioResult.audioOutputPath)) {
10379
10449
  renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
10380
10450
  }
@@ -10517,9 +10587,9 @@ import {
10517
10587
  writeFileSync as writeFileSync10
10518
10588
  } from "node:fs";
10519
10589
  import { tmpdir } from "node:os";
10520
- import { dirname as dirname13, isAbsolute as isAbsolute4, join as join27, relative as relative6, resolve as resolve9, sep as sep3 } from "node:path";
10590
+ import { dirname as dirname14, isAbsolute as isAbsolute4, join as join28, relative as relative6, resolve as resolve9, sep as sep3 } from "node:path";
10521
10591
  import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
10522
- import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
10592
+ import { createFrameLookupTable as createFrameLookupTable5, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
10523
10593
 
10524
10594
  // src/services/distributed/planV2Publisher.ts
10525
10595
  import {
@@ -10533,7 +10603,7 @@ import {
10533
10603
  writeFileSync as writeFileSync9
10534
10604
  } from "node:fs";
10535
10605
  import { renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
10536
- import { dirname as dirname12, join as join26 } from "node:path";
10606
+ import { dirname as dirname13, join as join27 } from "node:path";
10537
10607
 
10538
10608
  // src/services/distributed/planV2Errors.ts
10539
10609
  var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
@@ -10548,7 +10618,7 @@ var PlanV2IntegrityError = class extends Error {
10548
10618
  };
10549
10619
 
10550
10620
  // src/services/distributed/planV2Layout.ts
10551
- import { join as join25 } from "node:path";
10621
+ import { join as join26 } from "node:path";
10552
10622
  var SHA256_PATTERN = /^[0-9a-f]{64}$/;
10553
10623
  function assertPlanV2Sha256(value, field) {
10554
10624
  if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
@@ -10558,7 +10628,7 @@ function assertPlanV2Sha256(value, field) {
10558
10628
  }
10559
10629
  function planV2BlobPath(root, sha256) {
10560
10630
  const digest = assertPlanV2Sha256(sha256, "artifact sha256");
10561
- return join25(root, "artifacts", "sha256", digest.slice(0, 2), digest);
10631
+ return join26(root, "artifacts", "sha256", digest.slice(0, 2), digest);
10562
10632
  }
10563
10633
 
10564
10634
  // src/services/distributed/planV2Publisher.ts
@@ -10598,8 +10668,8 @@ var LocalPlanV2ArtifactPublisher = class {
10598
10668
  }
10599
10669
  this.destinationDir = destinationDir;
10600
10670
  this.#linkFile = options.linkFile ?? linkSync2;
10601
- mkdirSync11(dirname12(destinationDir), { recursive: true });
10602
- this.temporaryDir = mkdtempSync3(join26(dirname12(destinationDir), ".plan-v2-publish-"));
10671
+ mkdirSync11(dirname13(destinationDir), { recursive: true });
10672
+ this.temporaryDir = mkdtempSync3(join27(dirname13(destinationDir), ".plan-v2-publish-"));
10603
10673
  }
10604
10674
  async putBlob(blob) {
10605
10675
  const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
@@ -10611,9 +10681,9 @@ var LocalPlanV2ArtifactPublisher = class {
10611
10681
  }
10612
10682
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
10613
10683
  if (existsSync19(destinationPath)) return;
10614
- mkdirSync11(dirname12(destinationPath), { recursive: true });
10615
- const stagingDir = mkdtempSync3(join26(dirname12(destinationPath), ".plan-v2-blob-"));
10616
- const temporaryPath = join26(stagingDir, "blob");
10684
+ mkdirSync11(dirname13(destinationPath), { recursive: true });
10685
+ const stagingDir = mkdtempSync3(join27(dirname13(destinationPath), ".plan-v2-blob-"));
10686
+ const temporaryPath = join27(stagingDir, "blob");
10617
10687
  try {
10618
10688
  try {
10619
10689
  this.#linkFile(blob.sourcePath, temporaryPath);
@@ -10634,7 +10704,7 @@ var LocalPlanV2ArtifactPublisher = class {
10634
10704
  );
10635
10705
  }
10636
10706
  }
10637
- writeFileSync9(join26(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
10707
+ writeFileSync9(join27(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
10638
10708
  renameWithRetry9(this.temporaryDir, this.destinationDir);
10639
10709
  this.#committed = true;
10640
10710
  }
@@ -10679,7 +10749,7 @@ function listFiles(root) {
10679
10749
  const rootResolved = resolve9(root);
10680
10750
  function walk(dir) {
10681
10751
  for (const entry of readdirSync7(dir, { withFileTypes: true })) {
10682
- const absolutePath = join27(dir, entry.name);
10752
+ const absolutePath = join28(dir, entry.name);
10683
10753
  if (entry.isDirectory()) {
10684
10754
  walk(absolutePath);
10685
10755
  } else if (entry.isFile()) {
@@ -10706,13 +10776,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
10706
10776
  }
10707
10777
  }
10708
10778
  function validateExtractionCacheCompleteSentinels(executionPlanDir) {
10709
- const videoRoot = join27(executionPlanDir, "video-frames");
10779
+ const videoRoot = join28(executionPlanDir, "video-frames");
10710
10780
  if (!existsSync20(videoRoot)) return;
10711
10781
  for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
10712
10782
  if (!videoEntry.isDirectory()) continue;
10713
- const videoDir = join27(videoRoot, videoEntry.name);
10783
+ const videoDir = join28(videoRoot, videoEntry.name);
10714
10784
  if (readdirSync7(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
10715
- const sentinelPath = join27(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
10785
+ const sentinelPath = join28(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
10716
10786
  assertValidExtractionCacheCompleteSentinel(sentinelPath);
10717
10787
  }
10718
10788
  }
@@ -10759,7 +10829,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
10759
10829
  if (video.deferredRange) {
10760
10830
  const framePaths2 = /* @__PURE__ */ new Map();
10761
10831
  for (let index = 0; index < video.totalFrames; index++) {
10762
- framePaths2.set(index, join27(outputDir, String(index)));
10832
+ framePaths2.set(index, join28(outputDir, String(index)));
10763
10833
  }
10764
10834
  return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
10765
10835
  }
@@ -10767,7 +10837,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
10767
10837
  const framePaths = /* @__PURE__ */ new Map();
10768
10838
  for (const frameName of frameNames) {
10769
10839
  if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
10770
- assertValidExtractionCacheCompleteSentinel(join27(outputDir, frameName));
10840
+ assertValidExtractionCacheCompleteSentinel(join28(outputDir, frameName));
10771
10841
  continue;
10772
10842
  }
10773
10843
  const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
@@ -10786,7 +10856,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
10786
10856
  `duplicate extracted frame index ${frameIndex} in ${outputDir}`
10787
10857
  );
10788
10858
  }
10789
- framePaths.set(frameIndex, join27(outputDir, frameName));
10859
+ framePaths.set(frameIndex, join28(outputDir, frameName));
10790
10860
  }
10791
10861
  return {
10792
10862
  ...video,
@@ -10807,7 +10877,7 @@ function parsePlanVideosJson2(value) {
10807
10877
  }
10808
10878
  }
10809
10879
  function materializeExtractedVideoDirectories(planDir) {
10810
- const videosPath = join27(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10880
+ const videosPath = join28(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10811
10881
  if (!existsSync20(videosPath)) return;
10812
10882
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
10813
10883
  for (const video of videos.extracted) {
@@ -10841,13 +10911,13 @@ function parseChunkSlices(value) {
10841
10911
  });
10842
10912
  }
10843
10913
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
10844
- const videoRoot = join27(executionPlanDir, "video-frames");
10914
+ const videoRoot = join28(executionPlanDir, "video-frames");
10845
10915
  const hasExtractedFrames = existsSync20(videoRoot) && listFiles(videoRoot).length > 0;
10846
- const videosPath = join27(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10916
+ const videosPath = join28(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10847
10917
  if (!existsSync20(videosPath)) {
10848
10918
  return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
10849
10919
  }
10850
- const chunksPath = join27(executionPlanDir, "meta", "chunks.json");
10920
+ const chunksPath = join28(executionPlanDir, "meta", "chunks.json");
10851
10921
  const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
10852
10922
  const chunks = readJsonFile(chunksPath, "meta/chunks.json");
10853
10923
  const parsedVideos = parsePlanVideosJson2(videos);
@@ -10855,7 +10925,7 @@ function buildVideoChunkDependencies(executionPlanDir, dimensions) {
10855
10925
  const extracted = listVideoFramePaths(executionPlanDir, parsedVideos);
10856
10926
  const fpsNum = readPositiveInteger2(dimensions.fpsNum, "dimensions.fpsNum");
10857
10927
  const fpsDen = readPositiveInteger2(dimensions.fpsDen, "dimensions.fpsDen");
10858
- const table = createFrameLookupTable4(
10928
+ const table = createFrameLookupTable5(
10859
10929
  parsedVideos.videos,
10860
10930
  extracted,
10861
10931
  resolveRenderFpsConfig({ num: fpsNum, den: fpsDen }).value
@@ -10902,9 +10972,9 @@ function computeManifestHash(manifest) {
10902
10972
  }
10903
10973
  function writeBlob(sourcePath, destinationPath) {
10904
10974
  if (existsSync20(destinationPath)) return;
10905
- mkdirSync12(dirname13(destinationPath), { recursive: true });
10906
- const temporaryDir = mkdtempSync4(join27(dirname13(destinationPath), ".plan-v2-blob-"));
10907
- const temporaryPath = join27(temporaryDir, "blob");
10975
+ mkdirSync12(dirname14(destinationPath), { recursive: true });
10976
+ const temporaryDir = mkdtempSync4(join28(dirname14(destinationPath), ".plan-v2-blob-"));
10977
+ const temporaryPath = join28(temporaryDir, "blob");
10908
10978
  try {
10909
10979
  copyFileSync8(sourcePath, temporaryPath);
10910
10980
  renameWithRetry10(temporaryPath, destinationPath);
@@ -10940,7 +11010,7 @@ async function putTasks(tasks) {
10940
11010
  if (failure) throw failure.reason;
10941
11011
  }
10942
11012
  function buildPlanV2Publication(executionPlanDir, digests) {
10943
- const executionPlanPath = join27(executionPlanDir, "plan.json");
11013
+ const executionPlanPath = join28(executionPlanDir, "plan.json");
10944
11014
  if (!existsSync20(executionPlanPath)) {
10945
11015
  throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
10946
11016
  }
@@ -10968,7 +11038,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
10968
11038
  }
10969
11039
  validateExtractionCacheCompleteSentinels(executionPlanDir);
10970
11040
  const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
10971
- const encoder = readJsonFile(join27(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
11041
+ const encoder = readJsonFile(join28(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
10972
11042
  const files = listFiles(executionPlanDir);
10973
11043
  const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
10974
11044
  for (const file of files) {
@@ -11028,14 +11098,14 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
11028
11098
  if (publication.storedBlobs.length > 0) {
11029
11099
  throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
11030
11100
  }
11031
- mkdirSync12(dirname13(planV2Dir), { recursive: true });
11032
- const tempDir = mkdtempSync4(join27(dirname13(planV2Dir), ".plan-v2-build-"));
11101
+ mkdirSync12(dirname14(planV2Dir), { recursive: true });
11102
+ const tempDir = mkdtempSync4(join28(dirname14(planV2Dir), ".plan-v2-build-"));
11033
11103
  try {
11034
11104
  for (const blob of publication.blobs) {
11035
11105
  writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
11036
11106
  }
11037
11107
  writeFileSync10(
11038
- join27(tempDir, "plan.json"),
11108
+ join28(tempDir, "plan.json"),
11039
11109
  canonicalJsonStringify(publication.manifest),
11040
11110
  "utf-8"
11041
11111
  );
@@ -11076,7 +11146,7 @@ async function publishExecutionPlan(executionPlanDir, publisher, { digests, log
11076
11146
  }
11077
11147
  }
11078
11148
  async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
11079
- const stagingRoot = mkdtempSync4(join27(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
11149
+ const stagingRoot = mkdtempSync4(join28(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
11080
11150
  const digests = /* @__PURE__ */ new Map();
11081
11151
  try {
11082
11152
  await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
@@ -11087,7 +11157,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
11087
11157
  // artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
11088
11158
  whileAudioMixes: () => putBlobs(
11089
11159
  publisher,
11090
- listFiles(join27(stagingRoot, "compiled")).map(
11160
+ listFiles(join28(stagingRoot, "compiled")).map(
11091
11161
  (file) => publishBlobFor(file.absolutePath, digests)
11092
11162
  )
11093
11163
  ),
@@ -11112,7 +11182,7 @@ async function planV2(projectDir, config, planV2Dir) {
11112
11182
  }
11113
11183
  const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
11114
11184
  const manifest = await planV2WithPublisher(projectDir, config, publisher, {
11115
- stagingParentDir: dirname13(planV2Dir)
11185
+ stagingParentDir: dirname14(planV2Dir)
11116
11186
  });
11117
11187
  return resultFromManifest(planV2Dir, manifest);
11118
11188
  }
@@ -11124,7 +11194,7 @@ function resultFromManifest(planV2Dir, manifest) {
11124
11194
  isRecord3(executionPlan) ? executionPlan[REFERENCE_BACKGROUND_FIELD3] : void 0
11125
11195
  ),
11126
11196
  planDir: planV2Dir,
11127
- manifestPath: join27(planV2Dir, "plan.json"),
11197
+ manifestPath: join28(planV2Dir, "plan.json"),
11128
11198
  planProtocol: PLAN_PROTOCOL_V2,
11129
11199
  planHash: manifest.planHash,
11130
11200
  sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
@@ -11260,7 +11330,7 @@ function parsePlanV2Manifest(value) {
11260
11330
  return parsed;
11261
11331
  }
11262
11332
  function readPlanV2Manifest(planV2Dir) {
11263
- const manifestPath = join27(planV2Dir, "plan.json");
11333
+ const manifestPath = join28(planV2Dir, "plan.json");
11264
11334
  if (!existsSync20(manifestPath)) {
11265
11335
  throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
11266
11336
  }
@@ -11305,17 +11375,17 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
11305
11375
  artifact,
11306
11376
  sourcePath: verifyBlob(planV2Dir, artifact)
11307
11377
  }));
11308
- mkdirSync12(dirname13(destinationDir), { recursive: true });
11309
- const tempDir = mkdtempSync4(join27(dirname13(destinationDir), ".plan-v2-materialize-"));
11378
+ mkdirSync12(dirname14(destinationDir), { recursive: true });
11379
+ const tempDir = mkdtempSync4(join28(dirname14(destinationDir), ".plan-v2-materialize-"));
11310
11380
  try {
11311
11381
  for (const { artifact, sourcePath } of verified) {
11312
- const destinationPath = join27(tempDir, ...artifact.path.split("/"));
11313
- mkdirSync12(dirname13(destinationPath), { recursive: true });
11382
+ const destinationPath = join28(tempDir, ...artifact.path.split("/"));
11383
+ mkdirSync12(dirname14(destinationPath), { recursive: true });
11314
11384
  copyFileSync8(sourcePath, destinationPath);
11315
11385
  }
11316
11386
  if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
11317
11387
  writeFileSync10(
11318
- join27(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
11388
+ join28(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
11319
11389
  canonicalJsonStringify({
11320
11390
  manifest,
11321
11391
  target,
@@ -11338,7 +11408,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
11338
11408
  // Join the artifact's OWN name, not the current constant: a plan written
11339
11409
  // before the container change carries the legacy name, and materializing it
11340
11410
  // under the new one would point at a file that was never written.
11341
- audioPath: target.role === "assembler" ? audioArtifact ? join27(destinationDir, audioArtifact.path) : null : null
11411
+ audioPath: target.role === "assembler" ? audioArtifact ? join28(destinationDir, audioArtifact.path) : null : null
11342
11412
  };
11343
11413
  }
11344
11414
  function assertRemoteSources(remoteSources, artifacts) {
@@ -11357,7 +11427,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
11357
11427
  return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
11358
11428
  }
11359
11429
  function readValidatedPlanV2Materialization(planDir, expectedTarget) {
11360
- const markerPath = join27(planDir, PLAN_V2_MATERIALIZATION_MARKER);
11430
+ const markerPath = join28(planDir, PLAN_V2_MATERIALIZATION_MARKER);
11361
11431
  if (!existsSync20(markerPath)) return null;
11362
11432
  const marker = readJsonFile(markerPath, "materialization marker");
11363
11433
  if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
@@ -11375,7 +11445,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
11375
11445
  assertRemoteSources(remoteSources, required);
11376
11446
  for (const artifact of required) {
11377
11447
  if (Object.hasOwn(remoteSources, artifact.path)) continue;
11378
- const path = join27(planDir, ...artifact.path.split("/"));
11448
+ const path = join28(planDir, ...artifact.path.split("/"));
11379
11449
  if (!existsSync20(path) || statSync7(path).size !== artifact.sizeBytes) {
11380
11450
  throw new PlanV2IntegrityError(
11381
11451
  `materialized artifact missing or truncated: ${artifact.path}`
@@ -11391,7 +11461,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
11391
11461
  // src/services/distributed/planV2Execution.ts
11392
11462
  import { mkdtempSync as mkdtempSync6, rmSync as rmSync12 } from "node:fs";
11393
11463
  import { tmpdir as tmpdir2 } from "node:os";
11394
- import { join as join30 } from "node:path";
11464
+ import { join as join31 } from "node:path";
11395
11465
 
11396
11466
  // src/services/distributed/assemble.ts
11397
11467
  import {
@@ -11409,7 +11479,7 @@ import {
11409
11479
  statSync as statSync8,
11410
11480
  writeFileSync as writeFileSync11
11411
11481
  } from "node:fs";
11412
- import { dirname as dirname14, join as join28 } from "node:path";
11482
+ import { dirname as dirname15, join as join29 } from "node:path";
11413
11483
  import {
11414
11484
  appendRateCapArgs as appendRateCapArgs2,
11415
11485
  appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
@@ -11455,8 +11525,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11455
11525
  }
11456
11526
  const format = plan.dimensions.format;
11457
11527
  const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
11458
- if (!existsSync21(dirname14(outputPath))) {
11459
- mkdirSync13(dirname14(outputPath), { recursive: true });
11528
+ if (!existsSync21(dirname15(outputPath))) {
11529
+ mkdirSync13(dirname15(outputPath), { recursive: true });
11460
11530
  }
11461
11531
  const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
11462
11532
  const fps = { num: plan.dimensions.fpsNum, den: plan.dimensions.fpsDen };
@@ -11478,7 +11548,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11478
11548
  };
11479
11549
  try {
11480
11550
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
11481
- const finalPath = join28(workDir, `output.${format}`);
11551
+ const finalPath = join29(workDir, `output.${format}`);
11482
11552
  const audio = audioPath !== null && existsSync21(audioPath) ? audioPath : null;
11483
11553
  await writeJoined(ctx, videoSources, audio, finalPath);
11484
11554
  renameWithRetry11(finalPath, outputPath);
@@ -11507,8 +11577,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11507
11577
  };
11508
11578
  }
11509
11579
  function readAssemblePlan(planDir, chunkPaths) {
11510
- const planJsonPath = join28(planDir, "plan.json");
11511
- const chunksJsonPath = join28(planDir, "meta", "chunks.json");
11580
+ const planJsonPath = join29(planDir, "plan.json");
11581
+ const chunksJsonPath = join29(planDir, "meta", "chunks.json");
11512
11582
  if (!existsSync21(planJsonPath)) {
11513
11583
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
11514
11584
  }
@@ -11559,7 +11629,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
11559
11629
  return args;
11560
11630
  }
11561
11631
  async function reencodeCfr(ctx, chunkPaths, encoder) {
11562
- const cfrOutputPath = join28(ctx.workDir, `cfr.${ctx.format}`);
11632
+ const cfrOutputPath = join29(ctx.workDir, `cfr.${ctx.format}`);
11563
11633
  const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
11564
11634
  appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
11565
11635
  cfrArgs.push("-y", cfrOutputPath);
@@ -11586,7 +11656,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
11586
11656
  );
11587
11657
  return;
11588
11658
  }
11589
- const paddedAudioPath = join28(ctx.workDir, "audio-padded.m4a");
11659
+ const paddedAudioPath = join29(ctx.workDir, "audio-padded.m4a");
11590
11660
  const padTrimResult = await ctx.timed(
11591
11661
  "audioMs",
11592
11662
  () => padOrTrimAudioToVideoFrameCount({
@@ -11626,7 +11696,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
11626
11696
  }
11627
11697
  }
11628
11698
  function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
11629
- const listPath = join28(workDir, "concat-list.txt");
11699
+ const listPath = join29(workDir, "concat-list.txt");
11630
11700
  const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
11631
11701
  writeFileSync11(listPath, `${body}
11632
11702
  `, "utf-8");
@@ -11664,11 +11734,11 @@ function stitchesSeparateAudio(planDir) {
11664
11734
  return lockedAudio(planDir).separateAudio === true;
11665
11735
  }
11666
11736
  function planFormat(planDir) {
11667
- const plan = JSON.parse(readFileSync10(join28(planDir, "plan.json"), "utf-8"));
11737
+ const plan = JSON.parse(readFileSync10(join29(planDir, "plan.json"), "utf-8"));
11668
11738
  return plan.dimensions.format;
11669
11739
  }
11670
11740
  function lockedAudio(planDir) {
11671
- const encoderJsonPath = join28(planDir, "meta", "encoder.json");
11741
+ const encoderJsonPath = join29(planDir, "meta", "encoder.json");
11672
11742
  if (!existsSync21(encoderJsonPath)) return {};
11673
11743
  return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
11674
11744
  }
@@ -11678,7 +11748,7 @@ function readCfrEncoder(planDir, format) {
11678
11748
  `[assemble] cfr=true is only supported for format="mp4" (got "${format}"). Stream-copy paths for webm and mov already produce exact avg_frame_rate; cfr re-encode is not needed.`
11679
11749
  );
11680
11750
  }
11681
- const encoderJsonPath = join28(planDir, "meta", "encoder.json");
11751
+ const encoderJsonPath = join29(planDir, "meta", "encoder.json");
11682
11752
  if (!existsSync21(encoderJsonPath)) {
11683
11753
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
11684
11754
  }
@@ -11705,8 +11775,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11705
11775
  throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
11706
11776
  }
11707
11777
  for (const frame of frames) {
11708
- const dst = join28(outputPath, formatExportFrameName(globalIdx, "png"));
11709
- cpSync3(join28(chunkDir, frame), dst);
11778
+ const dst = join29(outputPath, formatExportFrameName(globalIdx, "png"));
11779
+ cpSync3(join29(chunkDir, frame), dst);
11710
11780
  globalIdx += 1;
11711
11781
  }
11712
11782
  }
@@ -11716,13 +11786,13 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11716
11786
  );
11717
11787
  }
11718
11788
  if (audioPath !== null && existsSync21(audioPath)) {
11719
- const sidecar = join28(outputPath, MIXED_AUDIO_FILENAME4);
11789
+ const sidecar = join29(outputPath, MIXED_AUDIO_FILENAME4);
11720
11790
  cpSync3(audioPath, sidecar);
11721
11791
  }
11722
11792
  let fileSize = 0;
11723
11793
  for (const name of readdirSync8(outputPath)) {
11724
11794
  try {
11725
- fileSize += statSync8(join28(outputPath, name)).size;
11795
+ fileSize += statSync8(join29(outputPath, name)).size;
11726
11796
  } catch {
11727
11797
  }
11728
11798
  }
@@ -11738,7 +11808,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11738
11808
  import { randomBytes } from "node:crypto";
11739
11809
  import { existsSync as existsSync22, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
11740
11810
  import { cpus } from "node:os";
11741
- import { join as join29 } from "node:path";
11811
+ import { join as join30 } from "node:path";
11742
11812
  import {
11743
11813
  assertSwiftShader,
11744
11814
  BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
@@ -11746,7 +11816,7 @@ import {
11746
11816
  classifyCaptureFailure as classifyCaptureFailure4,
11747
11817
  closeCaptureSession as closeCaptureSession11,
11748
11818
  createCaptureSession as createCaptureSession11,
11749
- createFrameLookupTable as createFrameLookupTable5,
11819
+ createFrameLookupTable as createFrameLookupTable6,
11750
11820
  deriveBeginFrameProbeTimeTicks as deriveBeginFrameProbeTimeTicks2,
11751
11821
  getEncoderPreset as getEncoderPreset6,
11752
11822
  initializeSession as initializeSession10,
@@ -11862,7 +11932,7 @@ function hashChunkOutput(outputPath, kind) {
11862
11932
  if (kind === "file") return sha256Hex(readFileSync11(outputPath));
11863
11933
  const entries = readdirSync9(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
11864
11934
  const lines = entries.map(
11865
- (name) => `${name}\0${sha256Hex(readFileSync11(join29(outputPath, name)))}`
11935
+ (name) => `${name}\0${sha256Hex(readFileSync11(join30(outputPath, name)))}`
11866
11936
  );
11867
11937
  return sha256Hex(lines.join("\0"));
11868
11938
  }
@@ -11879,9 +11949,9 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
11879
11949
  async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11880
11950
  const start = Date.now();
11881
11951
  const log = defaultLogger;
11882
- const planJsonPath = join29(planDir, "plan.json");
11883
- const encoderJsonPath = join29(planDir, "meta", "encoder.json");
11884
- const chunksJsonPath = join29(planDir, "meta", "chunks.json");
11952
+ const planJsonPath = join30(planDir, "plan.json");
11953
+ const encoderJsonPath = join30(planDir, "meta", "encoder.json");
11954
+ const chunksJsonPath = join30(planDir, "meta", "chunks.json");
11885
11955
  if (!existsSync22(planJsonPath)) {
11886
11956
  throw new RenderChunkValidationError(
11887
11957
  MISSING_PLAN_ARTIFACT,
@@ -11907,7 +11977,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11907
11977
  }
11908
11978
  const encoder = JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
11909
11979
  const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
11910
- const videosJsonPath = join29(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
11980
+ const videosJsonPath = join30(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
11911
11981
  let planVideos = null;
11912
11982
  if (existsSync22(videosJsonPath)) {
11913
11983
  try {
@@ -11939,7 +12009,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11939
12009
  }
11940
12010
  const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
11941
12011
  const framesInChunk = range.endFrame - range.startFrame;
11942
- const compiledDir = join29(planDir, "compiled");
12012
+ const compiledDir = join30(planDir, "compiled");
11943
12013
  if (!existsSync22(compiledDir)) {
11944
12014
  throw new RenderChunkValidationError(
11945
12015
  MISSING_PLAN_ARTIFACT,
@@ -11997,13 +12067,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11997
12067
  };
11998
12068
  const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
11999
12069
  mkdirSync14(workDir, { recursive: true });
12000
- const framesDir = join29(workDir, "captured-frames");
12070
+ const framesDir = join30(workDir, "captured-frames");
12001
12071
  mkdirSync14(framesDir, { recursive: true });
12002
12072
  const videoFramesDir = `${workDir}.video-frames`;
12003
12073
  const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
12004
12074
  const chunkFps = { num: plan.dimensions.fpsNum, den: plan.dimensions.fpsDen };
12005
12075
  const deferredVideos = planVideos?.extracted.filter((video) => video.deferredRange) ?? [];
12006
- const shownFrames = planVideos ? shownDeferredFrameIndexes(planVideos, chunkFrameNumbers, chunkFps) : /* @__PURE__ */ new Map();
12076
+ const shownFrames = planVideos ? shownPlanFrameIndexes(planVideos, chunkFrameNumbers, chunkFps) : /* @__PURE__ */ new Map();
12007
12077
  const remoteVideoIds = new Set(
12008
12078
  planVideos?.extracted.filter(
12009
12079
  (video) => video.deferredRange && Object.hasOwn(remoteSources, video.deferredRange.sourcePath)
@@ -12022,7 +12092,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
12022
12092
  dimensions: plan.dimensions,
12023
12093
  encoder,
12024
12094
  videoCount: planVideos?.videos.length ?? 0,
12025
- html: readFileSync11(join29(compiledDir, "index.html"), "utf-8"),
12095
+ html: readFileSync11(join30(compiledDir, "index.html"), "utf-8"),
12026
12096
  // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
12027
12097
  skipReadinessVideoIds: deferredVideos.filter(
12028
12098
  (video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
@@ -12157,7 +12227,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
12157
12227
  deferredVideoFrames.catch(() => {
12158
12228
  });
12159
12229
  const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? deferredVideoFrames.then(
12160
- (deferred) => createFrameLookupTable5(
12230
+ (deferred) => createFrameLookupTable6(
12161
12231
  planVideos.videos,
12162
12232
  [
12163
12233
  ...rebuildExtractedFramesFromPlanDir(
@@ -12192,7 +12262,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
12192
12262
  if (isPngSequence) {
12193
12263
  if (!existsSync22(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
12194
12264
  } else {
12195
- const outDir = join29(outputChunkPath, "..");
12265
+ const outDir = join30(outputChunkPath, "..");
12196
12266
  if (!existsSync22(outDir)) mkdirSync14(outDir, { recursive: true });
12197
12267
  }
12198
12268
  const encodeInput = {
@@ -12436,8 +12506,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
12436
12506
 
12437
12507
  // src/services/distributed/planV2Execution.ts
12438
12508
  async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
12439
- const workRoot = mkdtempSync6(join30(tmpdir2(), "hf-plan-v2-chunk-"));
12440
- const materializedPlanDir = join30(workRoot, "plan");
12509
+ const workRoot = mkdtempSync6(join31(tmpdir2(), "hf-plan-v2-chunk-"));
12510
+ const materializedPlanDir = join31(workRoot, "plan");
12441
12511
  try {
12442
12512
  materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
12443
12513
  return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
@@ -12446,8 +12516,8 @@ async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
12446
12516
  }
12447
12517
  }
12448
12518
  async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
12449
- const workRoot = mkdtempSync6(join30(tmpdir2(), "hf-plan-v2-assembler-"));
12450
- const materializedPlanDir = join30(workRoot, "plan");
12519
+ const workRoot = mkdtempSync6(join31(tmpdir2(), "hf-plan-v2-assembler-"));
12520
+ const materializedPlanDir = join31(workRoot, "plan");
12451
12521
  try {
12452
12522
  const materialized = materializePlanV2Target(
12453
12523
  planV2Dir,
@@ -12739,7 +12809,7 @@ function validateIntDimension(field, value) {
12739
12809
  // src/services/distributed/projectHash.ts
12740
12810
  import { closeSync as closeSync5, openSync as openSync5, readdirSync as readdirSync10, readSync as readSync4, realpathSync as realpathSync2, statSync as statSync9 } from "node:fs";
12741
12811
  import { createHash as createHash8 } from "node:crypto";
12742
- import { join as join31 } from "node:path";
12812
+ import { join as join32 } from "node:path";
12743
12813
  var SITE_ID_SCHEME = "site-v2\0";
12744
12814
  var READ_CHUNK_BYTES = 256 * 1024;
12745
12815
  function listSiteFiles(projectDir) {
@@ -12748,7 +12818,7 @@ function listSiteFiles(projectDir) {
12748
12818
  function walk(dir, rel) {
12749
12819
  for (const name of readdirSync10(dir).sort()) {
12750
12820
  if (rel === "" && PLAN_PROJECT_DIR_SKIP_SEGMENTS.has(name)) continue;
12751
- const full = join31(dir, name);
12821
+ const full = join32(dir, name);
12752
12822
  const relPath = rel === "" ? name : `${rel}/${name}`;
12753
12823
  let stat;
12754
12824
  try {
@@ -12778,7 +12848,7 @@ function hashProjectDir(projectDir, files = listSiteFiles(projectDir)) {
12778
12848
  for (const rel of files) {
12779
12849
  hash.update(rel);
12780
12850
  hash.update("\0");
12781
- const fd = openSync5(join31(projectDir, rel), "r");
12851
+ const fd = openSync5(join32(projectDir, rel), "r");
12782
12852
  try {
12783
12853
  let read;
12784
12854
  while ((read = readSync4(fd, chunk, 0, chunk.length, null)) > 0) {