@hyperframes/producer 0.9.15 → 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.
@@ -8,13 +8,13 @@ import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframe
8
8
  import {
9
9
  copyFileSync as copyFileSync6,
10
10
  cpSync as cpSync2,
11
- existsSync as existsSync17,
11
+ existsSync as existsSync18,
12
12
  linkSync,
13
13
  mkdirSync as mkdirSync10,
14
14
  rmSync as rmSync7,
15
- writeFileSync as writeFileSync7
15
+ writeFileSync as writeFileSync8
16
16
  } from "node:fs";
17
- 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
@@ -71,12 +71,12 @@ var defaultLogger = createConsoleLogger("info");
71
71
  // src/services/renderOrchestrator.ts
72
72
  import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
73
73
  import {
74
- existsSync as existsSync12,
74
+ existsSync as existsSync13,
75
75
  mkdirSync as mkdirSync6,
76
76
  readdirSync as readdirSync3,
77
77
  rmSync as rmSync5,
78
78
  statSync as statSync5,
79
- writeFileSync as writeFileSync4,
79
+ writeFileSync as writeFileSync5,
80
80
  copyFileSync as copyFileSync5,
81
81
  appendFileSync
82
82
  } from "fs";
@@ -90,16 +90,14 @@ import {
90
90
  toFps as toFps2
91
91
  } from "@hyperframes/core";
92
92
  import { HF_AUDIO_GROUP_TAG } from "@hyperframes/core/audio-groups";
93
- import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
94
93
  import { HTML_BODY_CSS_HEIGHT_FIRST_RE, HTML_BODY_CSS_WIDTH_FIRST_RE } from "@hyperframes/parsers";
95
- import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
96
94
  import {
97
95
  extractMediaMetadata as extractMediaMetadata4,
98
96
  resolveConfig as resolveConfig2,
99
97
  closeCaptureSession as closeCaptureSession8,
100
98
  createCaptureSession as createCaptureSession8,
101
99
  initializeSession as initializeSession8,
102
- createVideoFrameInjector,
100
+ createVideoFrameInjector as createVideoFrameInjector2,
103
101
  getEncoderPreset as getEncoderPreset3,
104
102
  distributeFrames as distributeFrames2,
105
103
  distributeFramesInterleaved as distributeFramesInterleaved2,
@@ -123,13 +121,14 @@ import {
123
121
  isDrawElementCaptureError,
124
122
  getDrawElementVerificationDetails,
125
123
  augmentProtocolTimeoutError,
126
- augmentPageNavigationTimeoutError
124
+ augmentPageNavigationTimeoutError,
125
+ displaySize
127
126
  } from "@hyperframes/engine";
128
127
  import {
129
128
  referenceBackgroundResult,
130
129
  createReferenceBackgroundRecorder
131
130
  } from "@hyperframes/engine";
132
- 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";
133
132
  import { randomUUID } from "crypto";
134
133
 
135
134
  // src/services/fileServer.ts
@@ -1319,6 +1318,7 @@ import {
1319
1318
  getCapturePerfSummary as getCapturePerfSummary2,
1320
1319
  initializeSession as initializeSession2,
1321
1320
  prepareCaptureSessionForReuse as prepareCaptureSessionForReuse2,
1321
+ resolveDeVerifyMinDb as resolveDeVerifyMinDb2,
1322
1322
  shouldDisableBrowserPoolForParallelWorker,
1323
1323
  spawnStreamingEncoder as spawnStreamingEncoder2
1324
1324
  } from "@hyperframes/engine";
@@ -1547,6 +1547,85 @@ async function measureCaptureCostFromSession(session, totalFrames, fps, log) {
1547
1547
  };
1548
1548
  }
1549
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
+
1550
1629
  // src/services/render/segmentManifest.ts
1551
1630
  import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
1552
1631
 
@@ -1914,14 +1993,17 @@ import {
1914
1993
  } from "@hyperframes/engine";
1915
1994
 
1916
1995
  // src/services/render/stages/compileStage.ts
1917
- import { join as join7 } from "node:path";
1996
+ import { existsSync as existsSync7, writeFileSync as writeFileSync3 } from "node:fs";
1997
+ import { join as join8 } from "node:path";
1918
1998
  import { suggestMatchingPreset } from "@hyperframes/core";
1999
+ import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
2000
+ import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
1919
2001
 
1920
2002
  // src/services/htmlCompiler.ts
1921
2003
  import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
1922
2004
  import { createHash as createHash4 } from "node:crypto";
1923
2005
  import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
1924
- 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";
1925
2007
  import { parseHTML as parseHTML5 } from "linkedom";
1926
2008
  import {
1927
2009
  COMPOSITION_SOURCE_URL,
@@ -2168,7 +2250,7 @@ import {
2168
2250
  // src/services/animatedGifPrep.ts
2169
2251
  import { createHash as createHash2 } from "node:crypto";
2170
2252
  import { copyFileSync as copyFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
2171
- 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";
2172
2254
  import { parseHTML as parseHTML3 } from "linkedom";
2173
2255
  import { parseAnimatedGifMetadata } from "@hyperframes/core";
2174
2256
  import { DEFAULT_VP9_CPU_USED, runFfmpeg as runFfmpeg2, renameWithRetry as renameWithRetry3 } from "@hyperframes/engine";
@@ -2324,11 +2406,11 @@ async function runAnimatedGifTranscode(request) {
2324
2406
  throw result.error ?? new Error(`Animated GIF transcode failed (${result.exitCode}): ${result.stderr.slice(-500)}`);
2325
2407
  }
2326
2408
  async function ensurePreparedWebm(input) {
2327
- if (!existsSync4(dirname3(input.cachePath))) {
2328
- mkdirSync2(dirname3(input.cachePath), { recursive: true });
2409
+ if (!existsSync4(dirname4(input.cachePath))) {
2410
+ mkdirSync2(dirname4(input.cachePath), { recursive: true });
2329
2411
  }
2330
- if (!existsSync4(dirname3(input.outputPath))) {
2331
- mkdirSync2(dirname3(input.outputPath), { recursive: true });
2412
+ if (!existsSync4(dirname4(input.outputPath))) {
2413
+ mkdirSync2(dirname4(input.outputPath), { recursive: true });
2332
2414
  }
2333
2415
  if (!isUsableFile(input.cachePath)) {
2334
2416
  const tmpPath = `${input.cachePath}.tmp-${process.pid}-${Date.now()}`;
@@ -2411,7 +2493,7 @@ function replaceImageWithVideo(input) {
2411
2493
  return video;
2412
2494
  }
2413
2495
  async function prepareAnimatedGifInputs(html, options) {
2414
- const outputDir = options.outputDir ?? join4(options.downloadDir, PREPARED_GIF_SUBDIR);
2496
+ const outputDir = options.outputDir ?? join5(options.downloadDir, PREPARED_GIF_SUBDIR);
2415
2497
  const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_GIF_SUBDIR);
2416
2498
  const cacheDir = options.cacheDir ?? outputDir;
2417
2499
  const { document: document2 } = parseHTML3(html);
@@ -2438,8 +2520,8 @@ async function prepareAnimatedGifInputs(html, options) {
2438
2520
  );
2439
2521
  const hash = computePreparedGifHash(bytes, loopIterations, padSeconds);
2440
2522
  const filename = `${CACHE_SCHEMA}-${hash.slice(0, 24)}.webm`;
2441
- const cachePath = join4(cacheDir, filename);
2442
- const outputPath = join4(outputDir, filename);
2523
+ const cachePath = join5(cacheDir, filename);
2524
+ const outputPath = join5(outputDir, filename);
2443
2525
  const outputSrc = `${outputSrcPrefix}/${filename}`;
2444
2526
  await ensurePreparedWebm({
2445
2527
  sourcePath,
@@ -2484,7 +2566,7 @@ import {
2484
2566
  statSync as statSync3,
2485
2567
  writeFileSync as writeFileSync2
2486
2568
  } from "node:fs";
2487
- 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";
2488
2570
  import { parseHTML as parseHTML4 } from "linkedom";
2489
2571
  import { renameWithRetry as renameWithRetry4, runFfmpeg as runFfmpeg3, runFfprobe } from "@hyperframes/engine";
2490
2572
 
@@ -2730,8 +2812,8 @@ function isUsableFile2(path) {
2730
2812
  }
2731
2813
  }
2732
2814
  async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
2733
- mkdirSync3(dirname4(cachePath), { recursive: true });
2734
- mkdirSync3(dirname4(outputPath), { recursive: true });
2815
+ mkdirSync3(dirname5(cachePath), { recursive: true });
2816
+ mkdirSync3(dirname5(outputPath), { recursive: true });
2735
2817
  if (!isUsableFile2(cachePath)) {
2736
2818
  const tmpPath = `${cachePath}.tmp-${process.pid}-${Date.now()}`;
2737
2819
  const inlinePath = `${tmpPath}.heic`;
@@ -2758,7 +2840,7 @@ async function ensurePreparedJpeg(source, cachePath, outputPath, options) {
2758
2840
  }
2759
2841
  }
2760
2842
  async function prepareHeifImageInputs(html, options) {
2761
- const outputDir = options.outputDir ?? join5(options.downloadDir, PREPARED_HEIF_SUBDIR);
2843
+ const outputDir = options.outputDir ?? join6(options.downloadDir, PREPARED_HEIF_SUBDIR);
2762
2844
  const outputSrcPrefix = normalizeRelPath(options.outputSrcPrefix ?? PREPARED_HEIF_SUBDIR);
2763
2845
  const cacheDir = options.cacheDir ?? outputDir;
2764
2846
  const preparedAssets = /* @__PURE__ */ new Map();
@@ -2779,7 +2861,7 @@ async function prepareHeifImageInputs(html, options) {
2779
2861
  await Promise.all(
2780
2862
  [...unique].map(
2781
2863
  ([filename, source]) => withMediaProbeSlot(
2782
- () => ensurePreparedJpeg(source, join5(cacheDir, filename), join5(outputDir, filename), options)
2864
+ () => ensurePreparedJpeg(source, join6(cacheDir, filename), join6(outputDir, filename), options)
2783
2865
  ).catch((error) => {
2784
2866
  if (!options.onFailure) throw error;
2785
2867
  failed.add(filename);
@@ -2791,8 +2873,8 @@ async function prepareHeifImageInputs(html, options) {
2791
2873
  if (failed.has(filename)) continue;
2792
2874
  const outputSrc = `${outputSrcPrefix}/${filename}`;
2793
2875
  img.setAttribute("src", outputSrc);
2794
- preparedAssets.set(outputSrc, join5(outputDir, filename));
2795
- 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) });
2796
2878
  }
2797
2879
  return { html: document2.toString(), preparedAssets, preparedImages };
2798
2880
  }
@@ -3116,7 +3198,7 @@ function detectShaderTransitionUsage(html) {
3116
3198
  async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath, probes) {
3117
3199
  let filePath = src;
3118
3200
  if (isHttpUrl(src)) {
3119
- const remoteDir = join6(downloadDir, REMOTE_MEDIA_SUBDIR);
3201
+ const remoteDir = join7(downloadDir, REMOTE_MEDIA_SUBDIR);
3120
3202
  if (!existsSync6(remoteDir)) mkdirSync4(remoteDir, { recursive: true });
3121
3203
  try {
3122
3204
  filePath = await downloadToTemp(src, remoteDir, void 0, void 0, void 0, {
@@ -3127,7 +3209,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
3127
3209
  }
3128
3210
  } else {
3129
3211
  filePath = decodedUrlPath(src);
3130
- if (!filePath.startsWith("/")) filePath = join6(baseDir, filePath);
3212
+ if (!filePath.startsWith("/")) filePath = join7(baseDir, filePath);
3131
3213
  filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
3132
3214
  }
3133
3215
  if (!isHttpUrl(filePath) && !existsSync6(filePath)) {
@@ -3288,7 +3370,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
3288
3370
  workItems.map(async (item) => {
3289
3371
  const { html: compiledSub } = await compileHtmlFile(
3290
3372
  rewriteUnresolvableGsapToCdn(item.rawSubHtml, projectDir, item.srcPath),
3291
- dirname5(item.filePath),
3373
+ dirname6(item.filePath),
3292
3374
  downloadDir,
3293
3375
  void 0,
3294
3376
  urlByAbsentLocalPath,
@@ -3765,7 +3847,7 @@ async function localizeRemoteMediaSources(html, downloadDir) {
3765
3847
  return downloadAndRewriteUrls(
3766
3848
  urlSet,
3767
3849
  html,
3768
- join6(downloadDir, REMOTE_MEDIA_SUBDIR),
3850
+ join7(downloadDir, REMOTE_MEDIA_SUBDIR),
3769
3851
  "Remote media download failed",
3770
3852
  "Localized remote media source(s)"
3771
3853
  );
@@ -3780,7 +3862,7 @@ async function localizeRemoteImageSources(html, downloadDir) {
3780
3862
  return downloadAndRewriteUrls(
3781
3863
  urlSet,
3782
3864
  html,
3783
- join6(downloadDir, REMOTE_MEDIA_SUBDIR),
3865
+ join7(downloadDir, REMOTE_MEDIA_SUBDIR),
3784
3866
  "Remote image download failed",
3785
3867
  "Localized remote image source(s)"
3786
3868
  );
@@ -3796,7 +3878,7 @@ async function localizeRemoteBackgroundImages(html, downloadDir) {
3796
3878
  return downloadAndRewriteUrls(
3797
3879
  urlSet,
3798
3880
  html,
3799
- join6(downloadDir, REMOTE_MEDIA_SUBDIR),
3881
+ join7(downloadDir, REMOTE_MEDIA_SUBDIR),
3800
3882
  "Remote background-image download failed",
3801
3883
  "Localized remote background-image(s)",
3802
3884
  // Quoted url('..')/url("..") are rewritten by downloadAndRewriteUrls' default
@@ -3906,7 +3988,7 @@ function projectStylesheetFontFamilies(html, projectDir) {
3906
3988
  const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
3907
3989
  const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
3908
3990
  const path = decodedUrlPath(url);
3909
- const file = path.startsWith("/") ? resolve6(projectDir, `.${path}`) : resolve6(dirname5(sheet), path);
3991
+ const file = path.startsWith("/") ? resolve6(projectDir, `.${path}`) : resolve6(dirname6(sheet), path);
3910
3992
  return isPathInside2(file, projectDir) && existsSync6(file);
3911
3993
  });
3912
3994
  if (family && shipsFile) families.push(family);
@@ -3948,7 +4030,7 @@ function localizeFontFaceUrls(html, downloadDir) {
3948
4030
  return downloadAndRewriteUrls(
3949
4031
  collectFontFaceUrls(html),
3950
4032
  html,
3951
- join6(downloadDir, REMOTE_MEDIA_SUBDIR),
4033
+ join7(downloadDir, REMOTE_MEDIA_SUBDIR),
3952
4034
  "Remote font download failed",
3953
4035
  "Localized remote font face(s)",
3954
4036
  (h, url, relPath) => h.replaceAll(`url(${url})`, `url("${relPath}")`)
@@ -4620,11 +4702,36 @@ function adaptAspectAgnosticResolution(requested, aspectAgnostic, width, height,
4620
4702
  });
4621
4703
  return flipped;
4622
4704
  }
4705
+ function resolveEntryHtmlPath(input) {
4706
+ const { projectDir, workDir, entryFile, log } = input;
4707
+ const htmlPath = join8(projectDir, entryFile);
4708
+ const rawEntry = readTextFile2(htmlPath);
4709
+ if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
4710
+ const projectIndexPath = join8(projectDir, "index.html");
4711
+ if (!existsSync7(projectIndexPath)) {
4712
+ throw new Error(
4713
+ `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
4714
+ );
4715
+ }
4716
+ const standaloneHtml = extractStandaloneEntryFromIndex(
4717
+ readTextFile2(projectIndexPath),
4718
+ entryFile,
4719
+ rawEntry
4720
+ );
4721
+ if (!standaloneHtml) {
4722
+ throw new Error(
4723
+ `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
4724
+ );
4725
+ }
4726
+ const wrapperPath = join8(workDir, "standalone-entry.html");
4727
+ writeFileSync3(wrapperPath, standaloneHtml, "utf-8");
4728
+ log.info("Extracted standalone entry from index.html host context", { entryFile });
4729
+ return wrapperPath;
4730
+ }
4623
4731
  async function runCompileStage(input) {
4624
4732
  const {
4625
4733
  projectDir,
4626
4734
  workDir,
4627
- htmlPath,
4628
4735
  entryFile,
4629
4736
  job,
4630
4737
  cfg,
@@ -4635,8 +4742,9 @@ async function runCompileStage(input) {
4635
4742
  allowSystemFontCapture,
4636
4743
  abortSignal
4637
4744
  } = input;
4745
+ const htmlPath = resolveEntryHtmlPath({ projectDir, workDir, entryFile, log });
4638
4746
  const compileStart = Date.now();
4639
- const compiled = await compileForRender(projectDir, htmlPath, join7(workDir, "downloads"), {
4747
+ const compiled = await compileForRender(projectDir, htmlPath, join8(workDir, "downloads"), {
4640
4748
  log,
4641
4749
  failClosedFontFetch: failClosedFontFetch === true,
4642
4750
  allowSystemFontCapture,
@@ -4644,7 +4752,7 @@ async function runCompileStage(input) {
4644
4752
  variables: input.variables,
4645
4753
  urlByAbsentLocalPath: input.urlByAbsentLocalPath,
4646
4754
  pageMotionBlur: input.pageMotionBlur,
4647
- animatedGifCacheDir: cfg.extractCacheDir ? join7(cfg.extractCacheDir, "animated-gif") : void 0,
4755
+ animatedGifCacheDir: cfg.extractCacheDir ? join8(cfg.extractCacheDir, "animated-gif") : void 0,
4648
4756
  ffmpegProcessTimeout: cfg.ffmpegProcessTimeout
4649
4757
  });
4650
4758
  assertNotAborted();
@@ -4701,7 +4809,7 @@ async function runCompileStage(input) {
4701
4809
  };
4702
4810
  await preflightCompositionAssetMediaTypes({
4703
4811
  projectDir,
4704
- compiledDir: join7(workDir, "compiled"),
4812
+ compiledDir: join8(workDir, "compiled"),
4705
4813
  composition,
4706
4814
  signal: abortSignal
4707
4815
  });
@@ -4747,7 +4855,7 @@ async function runCompileStage(input) {
4747
4855
 
4748
4856
  // src/services/render/stages/probeStage.ts
4749
4857
  import { createHash as createHash5 } from "node:crypto";
4750
- import { basename as basename4, join as join8 } from "node:path";
4858
+ import { basename as basename4, join as join9 } from "node:path";
4751
4859
  import { parseHTML as parseHTML6 } from "linkedom";
4752
4860
  import {
4753
4861
  closeCaptureSession as closeCaptureSession4,
@@ -4956,7 +5064,7 @@ async function runProbeStage(input) {
4956
5064
  });
4957
5065
  fileServer = await createFileServer({
4958
5066
  projectDir,
4959
- compiledDir: join8(workDir, "compiled"),
5067
+ compiledDir: join9(workDir, "compiled"),
4960
5068
  port: 0,
4961
5069
  preHeadScripts: [VIRTUAL_TIME_SHIM],
4962
5070
  fps: job.config.fps
@@ -4986,7 +5094,7 @@ async function runProbeStage(input) {
4986
5094
  log.info("Creating capture session...", { attempt, maxAttempts: PROBE_MAX_ATTEMPTS });
4987
5095
  probeSession = await createCaptureSession4(
4988
5096
  fileServer.url,
4989
- join8(workDir, "probe"),
5097
+ join9(workDir, "probe"),
4990
5098
  captureOpts,
4991
5099
  null,
4992
5100
  probeCfg
@@ -5071,7 +5179,7 @@ async function runProbeStage(input) {
5071
5179
  });
5072
5180
  probeSession = await createCaptureSession4(
5073
5181
  fileServer.url,
5074
- join8(workDir, "probe-screenshot"),
5182
+ join9(workDir, "probe-screenshot"),
5075
5183
  captureOpts,
5076
5184
  null,
5077
5185
  { ...probeCfg, forceScreenshot: true }
@@ -5109,7 +5217,7 @@ async function runProbeStage(input) {
5109
5217
  compiled,
5110
5218
  resolutions,
5111
5219
  projectDir,
5112
- join8(workDir, "downloads")
5220
+ join9(workDir, "downloads")
5113
5221
  );
5114
5222
  assertNotAborted();
5115
5223
  composition.videos = compiled.videos;
@@ -5300,7 +5408,7 @@ async function runProbeStage(input) {
5300
5408
  }
5301
5409
  await preflightCompositionAssetMediaTypes({
5302
5410
  projectDir,
5303
- compiledDir: join8(workDir, "compiled"),
5411
+ compiledDir: join9(workDir, "compiled"),
5304
5412
  composition,
5305
5413
  signal: abortSignal
5306
5414
  });
@@ -5466,11 +5574,11 @@ function validateDistributedDuration(input) {
5466
5574
  }
5467
5575
 
5468
5576
  // src/services/render/stages/extractVideosStage.ts
5469
- import { existsSync as existsSync7 } from "node:fs";
5470
- import { join as join9 } from "node:path";
5577
+ import { existsSync as existsSync8 } from "node:fs";
5578
+ import { join as join10 } from "node:path";
5471
5579
  import {
5472
5580
  classifyVideoExtractionError,
5473
- createFrameLookupTable,
5581
+ createFrameLookupTable as createFrameLookupTable3,
5474
5582
  detectTransfer,
5475
5583
  extractAllVideoFrames,
5476
5584
  extractMediaMetadata as extractMediaMetadata3,
@@ -5482,7 +5590,7 @@ import {
5482
5590
  resolveVideoExtractionWindow as resolveVideoExtractionWindow2,
5483
5591
  isHttpUrl as isHttpUrl2,
5484
5592
  resolveProjectRelativeSrc as resolveProjectRelativeSrc3,
5485
- runVideoExtractionWithRetry,
5593
+ runVideoExtractionWithRetry as runVideoExtractionWithRetry2,
5486
5594
  safeVideoExtractionSourceIdentity,
5487
5595
  videoAudioId as videoAudioId2
5488
5596
  } from "@hyperframes/engine";
@@ -5741,9 +5849,9 @@ async function runExtractVideosStage(input) {
5741
5849
  const probeFailures = await Promise.all(
5742
5850
  composition.videos.map(async (v) => {
5743
5851
  const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
5744
- if (!existsSync7(videoPath)) return null;
5852
+ if (!existsSync8(videoPath)) return null;
5745
5853
  try {
5746
- 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), {
5747
5855
  signal: abortSignal,
5748
5856
  maxTransientRetries: extractionPolicy.maxTransientRetries,
5749
5857
  onRetry: () => {
@@ -5779,7 +5887,7 @@ async function runExtractVideosStage(input) {
5779
5887
  const probed = await Promise.all(
5780
5888
  composition.images.map(async (img) => {
5781
5889
  const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
5782
- if (!existsSync7(imgPath)) return null;
5890
+ if (!existsSync8(imgPath)) return null;
5783
5891
  const colorSpace = await probeColorSpaceSafely(imgPath, log);
5784
5892
  if (isHdrColorSpace2(colorSpace)) {
5785
5893
  nativeHdrImageIds.add(img.id);
@@ -5813,14 +5921,14 @@ async function runExtractVideosStage(input) {
5813
5921
  // because short boundary counts can differ by one frame.
5814
5922
  {
5815
5923
  fps: job.config.fps,
5816
- outputDir: join9(compiledDir, "__hyperframes_video_frames"),
5924
+ outputDir: join10(compiledDir, "__hyperframes_video_frames"),
5817
5925
  format: job.config.videoFrameFormat ?? "auto",
5818
5926
  toneMapHdrToSdr: rendersSdrOnly(job.config, composition),
5819
5927
  timelineEnd: composition.duration,
5820
5928
  maxTransientRetries: extractionPolicy.maxTransientRetries,
5821
5929
  collectProbeFailures: extractionPolicy.failureMode === "enforce",
5822
5930
  deferRangeExtraction,
5823
- contentKeyedDirs: [join9(compiledDir, REMOTE_MEDIA_SUBDIR)],
5931
+ contentKeyedDirs: [join10(compiledDir, REMOTE_MEDIA_SUBDIR)],
5824
5932
  urlByAbsentLocalPath,
5825
5933
  onProgress: ({ planned = 0, done = 0 }) => {
5826
5934
  framesPlanned += planned;
@@ -5840,7 +5948,7 @@ async function runExtractVideosStage(input) {
5840
5948
  materializeSymlinks
5841
5949
  });
5842
5950
  if (extractionResult.extracted.length > 0) {
5843
- frameLookup = createFrameLookupTable(
5951
+ frameLookup = createFrameLookupTable3(
5844
5952
  composition.videos,
5845
5953
  extractionResult.extracted,
5846
5954
  resolveRenderFpsConfig(job.config.fps).value
@@ -5899,7 +6007,7 @@ async function appendVideoAudioFromProbe(input) {
5899
6007
  videos.map(async (video) => {
5900
6008
  const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
5901
6009
  const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
5902
- if (!isHttpUrl2(readFrom) && !existsSync7(readFrom)) return null;
6010
+ if (!isHttpUrl2(readFrom) && !existsSync8(readFrom)) return null;
5903
6011
  const metadata = await extractMediaMetadata3(readFrom);
5904
6012
  const window2 = await resolveFinalFrameExtractionWindow2(
5905
6013
  readFrom,
@@ -5927,8 +6035,8 @@ import {
5927
6035
  } from "@hyperframes/engine";
5928
6036
 
5929
6037
  // src/services/render/stages/audioStage.ts
5930
- import { existsSync as existsSync9 } from "node:fs";
5931
- import { join as join12 } from "node:path";
6038
+ import { existsSync as existsSync10 } from "node:fs";
6039
+ import { join as join13 } from "node:path";
5932
6040
  import {
5933
6041
  MIXED_AUDIO_FILENAME,
5934
6042
  audioFailureDiagnostics,
@@ -5938,8 +6046,8 @@ import {
5938
6046
  } from "@hyperframes/engine";
5939
6047
 
5940
6048
  // src/services/render/audioDeliveryLimit.ts
5941
- import { existsSync as existsSync8 } from "node:fs";
5942
- import { dirname as dirname7, join as join11 } from "node:path";
6049
+ import { existsSync as existsSync9 } from "node:fs";
6050
+ import { dirname as dirname8, join as join12 } from "node:path";
5943
6051
  import {
5944
6052
  applyAudioFxChain,
5945
6053
  describeFfmpegFailure as describeFfmpegFailure3,
@@ -5950,7 +6058,7 @@ import {
5950
6058
  // src/services/render/audioPadTrim.ts
5951
6059
  import { spawn } from "node:child_process";
5952
6060
  import { mkdtempSync } from "node:fs";
5953
- import { dirname as dirname6, join as join10 } from "node:path";
6061
+ import { dirname as dirname7, join as join11 } from "node:path";
5954
6062
  import {
5955
6063
  DEFAULT_CONFIG,
5956
6064
  describeFfmpegFailure as describeFfmpegFailure2,
@@ -6229,7 +6337,7 @@ async function constrainAacTruePeak(input) {
6229
6337
  }
6230
6338
  async function cutSource(input, scratchDir, attenuationDb) {
6231
6339
  if (!input.sourcePath || !input.rewriteSource) return true;
6232
- const cutPath = join10(scratchDir, "source.wav");
6340
+ const cutPath = join11(scratchDir, "source.wav");
6233
6341
  try {
6234
6342
  const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
6235
6343
  if (!result.success) throw new Error(result.error);
@@ -6243,8 +6351,8 @@ async function cutSource(input, scratchDir, attenuationDb) {
6243
6351
  async function enforceAacTruePeak(input) {
6244
6352
  let scratchDir;
6245
6353
  try {
6246
- scratchDir = mkdtempSync(join10(dirname6(input.audioPath), ".true-peak-"));
6247
- const correctedPath = join10(scratchDir, "audio.m4a");
6354
+ scratchDir = mkdtempSync(join11(dirname7(input.audioPath), ".true-peak-"));
6355
+ const correctedPath = join11(scratchDir, "audio.m4a");
6248
6356
  const measurements = [];
6249
6357
  let attenuationDb = 0;
6250
6358
  let sourcePeakDbfs;
@@ -6435,31 +6543,57 @@ async function runFfprobeJson(args, signal) {
6435
6543
  }
6436
6544
 
6437
6545
  // src/services/render/audioDeliveryLimit.ts
6438
- var LIMIT_CEILING_DBTP = AAC_DELIVERY_TRUE_PEAK_DBFS - 2;
6439
- 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) => ({
6440
6550
  version: 1,
6441
- nodes: [{ type: "truepeak", id: "delivery", params: { ceiling: LIMIT_CEILING_DBTP } }]
6442
- };
6551
+ nodes: [{ type: "truepeak", id: "delivery", params: { ceiling } }]
6552
+ });
6443
6553
  async function limitDeliveryPeaks(input, deps = {
6444
6554
  measure: measureTruePeakDbfs,
6445
6555
  applyChain: applyAudioFxChain,
6446
6556
  encode: encodeAac2,
6447
6557
  rename: renameWithRetry6,
6448
- sourceExists: existsSync8
6558
+ sourceExists: existsSync9
6449
6559
  }) {
6450
6560
  const { audioPath, sourcePath, signal, timeoutMs } = input;
6451
6561
  if (!deps.sourceExists(sourcePath)) return { success: true };
6452
6562
  const peak = await deps.measure(audioPath, { timeoutMs, signal });
6453
6563
  if (peak <= AAC_DELIVERY_TRUE_PEAK_DBFS) return { success: true };
6454
- 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;
6455
6586
  try {
6456
- await deps.applyChain(sourcePath, LIMIT_CHAIN, limitedPath, { trackId: "delivery", signal });
6587
+ await deps.applyChain(sourcePath, limitChain(ceiling), limitedPath, {
6588
+ trackId: "delivery",
6589
+ signal
6590
+ });
6457
6591
  } catch (error) {
6458
6592
  if (signal?.aborted) throw error;
6459
6593
  input.warn?.("[audio] true-peak limit failed, the whole-mix cut applies", {
6460
6594
  error: error instanceof Error ? error.message : String(error)
6461
6595
  });
6462
- return { success: true };
6596
+ return { limitFailed: true };
6463
6597
  }
6464
6598
  const encoded = await deps.encode(limitedPath, audioPath, input.targetDurationSeconds, {
6465
6599
  signal,
@@ -6467,13 +6601,14 @@ async function limitDeliveryPeaks(input, deps = {
6467
6601
  });
6468
6602
  if (!encoded.success) {
6469
6603
  return {
6470
- success: false,
6471
- error: `[audioDeliveryLimit] ${describeFfmpegFailure3(encoded, timeoutMs)}`,
6472
- failureReason: encoded.failureReason
6604
+ encodeFailure: {
6605
+ success: false,
6606
+ error: `[audioDeliveryLimit] ${describeFfmpegFailure3(encoded, timeoutMs)}`,
6607
+ failureReason: encoded.failureReason
6608
+ }
6473
6609
  };
6474
6610
  }
6475
- deps.rename(limitedPath, sourcePath, { copyFallback: true });
6476
- return { success: true };
6611
+ return { peak: await deps.measure(audioPath, { timeoutMs, signal }) };
6477
6612
  }
6478
6613
 
6479
6614
  // src/services/render/stages/audioStage.ts
@@ -6499,7 +6634,7 @@ function runAudioStage(input) {
6499
6634
  }
6500
6635
  async function runAudioStageScoped(input) {
6501
6636
  const stageStart = Date.now();
6502
- const pcmSourcePath = join12(input.workDir, "audio-source.wav");
6637
+ const pcmSourcePath = join13(input.workDir, "audio-source.wav");
6503
6638
  const duration = input.totalFrames * input.fps.den / input.fps.num;
6504
6639
  let deliverySourcePath;
6505
6640
  try {
@@ -6515,7 +6650,7 @@ async function runAudioStageScoped(input) {
6515
6650
  );
6516
6651
  audioLoweredDb = guard.loweredDb;
6517
6652
  input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
6518
- if (input.keepDeliverySource && !guard.sourceStale && existsSync9(pcmSourcePath)) {
6653
+ if (input.keepDeliverySource && !guard.sourceStale && existsSync10(pcmSourcePath)) {
6519
6654
  deliverySourcePath = pcmSourcePath;
6520
6655
  }
6521
6656
  }
@@ -6564,7 +6699,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
6564
6699
  assertNotAborted,
6565
6700
  log
6566
6701
  } = input;
6567
- const audioOutputPath = join12(workDir, MIXED_AUDIO_FILENAME);
6702
+ const audioOutputPath = join13(workDir, MIXED_AUDIO_FILENAME);
6568
6703
  let hasAudio = false;
6569
6704
  let audioError;
6570
6705
  let audioFailures;
@@ -6575,7 +6710,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
6575
6710
  audioResult = await processCompositionAudio(
6576
6711
  audios,
6577
6712
  projectDir,
6578
- join12(workDir, "audio-work"),
6713
+ join13(workDir, "audio-work"),
6579
6714
  audioOutputPath,
6580
6715
  duration,
6581
6716
  abortSignal,
@@ -6643,8 +6778,8 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
6643
6778
  }
6644
6779
 
6645
6780
  // src/services/render/stages/captureStage.ts
6646
- import { existsSync as existsSync10, readdirSync, statfsSync } from "node:fs";
6647
- import { join as join13 } from "node:path";
6781
+ import { existsSync as existsSync11, readdirSync, statfsSync } from "node:fs";
6782
+ import { join as join14 } from "node:path";
6648
6783
  import {
6649
6784
  classifyCaptureFailure as classifyCaptureFailure2,
6650
6785
  frameFileExtension,
@@ -6695,16 +6830,16 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
6695
6830
  }
6696
6831
  function measureCaptureFrameBytes(workDir, framesDir) {
6697
6832
  let total = sampleDirectoryBytes(framesDir);
6698
- if (!existsSync10(workDir)) return total;
6833
+ if (!existsSync11(workDir)) return total;
6699
6834
  for (const attempt of readdirSync(workDir)) {
6700
6835
  if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
6701
- total += sampleDirectoryBytes(join13(workDir, attempt));
6836
+ total += sampleDirectoryBytes(join14(workDir, attempt));
6702
6837
  continue;
6703
6838
  }
6704
6839
  if (!/^capture-attempt-\d+$/.test(attempt)) continue;
6705
- const attemptDir = join13(workDir, attempt);
6840
+ const attemptDir = join14(workDir, attempt);
6706
6841
  for (const name of readdirSync(attemptDir)) {
6707
- if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join13(attemptDir, name));
6842
+ if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join14(attemptDir, name));
6708
6843
  }
6709
6844
  }
6710
6845
  return total;
@@ -7032,16 +7167,16 @@ import {
7032
7167
  // src/services/render/stages/encodeStage.ts
7033
7168
  import {
7034
7169
  copyFileSync as copyFileSync4,
7035
- existsSync as existsSync11,
7170
+ existsSync as existsSync12,
7036
7171
  mkdirSync as mkdirSync5,
7037
7172
  readdirSync as readdirSync2,
7038
7173
  readFileSync as readFileSync5,
7039
7174
  rmSync as rmSync4,
7040
7175
  statSync as statSync4,
7041
- writeFileSync as writeFileSync3
7176
+ writeFileSync as writeFileSync4
7042
7177
  } from "node:fs";
7043
7178
  import { readFile as readFile2 } from "node:fs/promises";
7044
- import { dirname as dirname8, join as join14 } from "node:path";
7179
+ import { dirname as dirname9, join as join15 } from "node:path";
7045
7180
  import {
7046
7181
  encodeFramesChunkedConcat,
7047
7182
  encodeFramesFromDir,
@@ -7134,7 +7269,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
7134
7269
  const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
7135
7270
  return gifEncodeFailure(startTime, outputPath, error);
7136
7271
  }
7137
- writeFileSync3(outputPath, gif);
7272
+ writeFileSync4(outputPath, gif);
7138
7273
  return null;
7139
7274
  }
7140
7275
  async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
@@ -7166,7 +7301,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
7166
7301
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
7167
7302
  if (failure) return failure;
7168
7303
  }
7169
- const fileSize = existsSync11(outputPath) ? statSync4(outputPath).size : 0;
7304
+ const fileSize = existsSync12(outputPath) ? statSync4(outputPath).size : 0;
7170
7305
  return {
7171
7306
  success: true,
7172
7307
  outputPath,
@@ -7252,7 +7387,7 @@ function encodeWhileCapturing(input, totalFrames) {
7252
7387
  await new Promise((resolve10) => wake = resolve10);
7253
7388
  }
7254
7389
  if (signal.aborted) return;
7255
- const mergedPath = join14(input.framesDir, formatCaptureFrameName(next, "jpg"));
7390
+ const mergedPath = join15(input.framesDir, formatCaptureFrameName(next, "jpg"));
7256
7391
  const frame = await readLandedFrame(landed.get(next), mergedPath);
7257
7392
  landed.delete(next);
7258
7393
  if (!await stream.writeFrame(frame)) {
@@ -7327,7 +7462,7 @@ async function runEncodeStage(input) {
7327
7462
  const stage5Start = Date.now();
7328
7463
  if (isPngSequence) {
7329
7464
  const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
7330
- if (!existsSync11(outputPath)) mkdirSync5(outputPath, { recursive: true });
7465
+ if (!existsSync12(outputPath)) mkdirSync5(outputPath, { recursive: true });
7331
7466
  const captured = readdirSync2(framesDir).filter((name) => name.endsWith(".png")).sort();
7332
7467
  if (captured.length === 0) {
7333
7468
  throw new Error(
@@ -7335,11 +7470,11 @@ async function runEncodeStage(input) {
7335
7470
  );
7336
7471
  }
7337
7472
  captured.forEach((name, i) => {
7338
- const dst = join14(outputPath, formatExportFrameName(i, "png"));
7339
- copyFileSync4(join14(framesDir, name), dst);
7473
+ const dst = join15(outputPath, formatExportFrameName(i, "png"));
7474
+ copyFileSync4(join15(framesDir, name), dst);
7340
7475
  });
7341
- if (hasAudio && audioOutputPath && existsSync11(audioOutputPath)) {
7342
- copyFileSync4(audioOutputPath, join14(outputPath, MIXED_AUDIO_FILENAME2));
7476
+ if (hasAudio && audioOutputPath && existsSync12(audioOutputPath)) {
7477
+ copyFileSync4(audioOutputPath, join15(outputPath, MIXED_AUDIO_FILENAME2));
7343
7478
  log.info(
7344
7479
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
7345
7480
  );
@@ -7359,7 +7494,7 @@ async function runEncodeStage(input) {
7359
7494
  const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
7360
7495
  fps: job.config.fps,
7361
7496
  loop,
7362
- palettePath: join14(dirname8(videoOnlyPath), "gif-palette.png"),
7497
+ palettePath: join15(dirname9(videoOnlyPath), "gif-palette.png"),
7363
7498
  preserveAlpha: needsAlpha,
7364
7499
  signal: abortSignal,
7365
7500
  timeout: engineCfg.ffmpegEncodeTimeout
@@ -7418,7 +7553,7 @@ function sampleDirectoryBytes(dir) {
7418
7553
  continue;
7419
7554
  }
7420
7555
  for (const name of entries) {
7421
- const full = join15(current, name);
7556
+ const full = join16(current, name);
7422
7557
  try {
7423
7558
  const st = statSync5(full);
7424
7559
  if (st.isDirectory()) {
@@ -7494,18 +7629,14 @@ function hasUsableVideoDimensions(metadata) {
7494
7629
  return Number.isFinite(metadata.width) && Number.isFinite(metadata.height) && metadata.width > 0 && metadata.height > 0;
7495
7630
  }
7496
7631
  function collectVideoMetadataHints(extractedVideos) {
7497
- return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({
7498
- id: video.videoId,
7499
- width: video.metadata.width,
7500
- height: video.metadata.height
7501
- })).sort((a, b) => a.id.localeCompare(b.id));
7632
+ return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({ id: video.videoId, ...displaySize(video.metadata) })).sort((a, b) => a.id.localeCompare(b.id));
7502
7633
  }
7503
7634
  function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
7504
7635
  const ranges = [];
7505
7636
  let rangeStart = null;
7506
7637
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
7507
- const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
7508
- const missing = !existsSync12(framePath) || statSync5(framePath).size <= 8;
7638
+ const framePath = join16(framesDir, formatCaptureFrameName(frameIndex, frameExt));
7639
+ const missing = !existsSync13(framePath) || statSync5(framePath).size <= 8;
7509
7640
  if (missing && rangeStart === null) {
7510
7641
  rangeStart = frameIndex;
7511
7642
  } else if (!missing && rangeStart !== null) {
@@ -7521,7 +7652,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
7521
7652
  function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
7522
7653
  const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
7523
7654
  const batches = [];
7524
- 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}`);
7525
7656
  if (stride > 1) {
7526
7657
  const spans = /* @__PURE__ */ new Map();
7527
7658
  for (const range of ranges) {
@@ -7594,8 +7725,8 @@ function isRecoverableParallelCaptureError(error) {
7594
7725
  function countCapturedFrames(totalFrames, framesDir, frameExt) {
7595
7726
  let captured = 0;
7596
7727
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
7597
- const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
7598
- if (existsSync12(framePath)) captured++;
7728
+ const framePath = join16(framesDir, formatCaptureFrameName(frameIndex, frameExt));
7729
+ if (existsSync13(framePath)) captured++;
7599
7730
  }
7600
7731
  return captured;
7601
7732
  }
@@ -7620,7 +7751,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
7620
7751
  reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
7621
7752
  });
7622
7753
  pendingTransientRetry = false;
7623
- const attemptWorkDir = join15(options.workDir, `capture-attempt-${attempt}`);
7754
+ const attemptWorkDir = join16(options.workDir, `capture-attempt-${attempt}`);
7624
7755
  const batches = missingRanges ? buildMissingFrameRetryBatches(
7625
7756
  missingRanges,
7626
7757
  currentWorkers,
@@ -7813,28 +7944,26 @@ function explainStreamingEncodeGate(cfg, outputFormat, workerCount, durationSeco
7813
7944
  var DEFAULT_SEGMENTED_MIN_SECONDS = Number.POSITIVE_INFINITY;
7814
7945
 
7815
7946
  // src/services/distributed/planFrameCost.ts
7816
- import { join as join18 } from "node:path";
7947
+ import { join as join19 } from "node:path";
7817
7948
  import {
7818
7949
  closeCaptureSession as closeCaptureSession9,
7819
7950
  createCaptureSession as createCaptureSession9,
7820
- createFrameLookupTable as createFrameLookupTable3,
7951
+ createFrameLookupTable as createFrameLookupTable4,
7821
7952
  initializeSession as initializeSession9
7822
7953
  } from "@hyperframes/engine";
7823
7954
 
7824
7955
  // src/services/distributed/chunkFrames.ts
7825
- import { existsSync as existsSync14, readdirSync as readdirSync4 } from "node:fs";
7826
- import { extname as extname2, join as join17 } from "node:path";
7956
+ import { existsSync as existsSync15, readdirSync as readdirSync4 } from "node:fs";
7957
+ import { extname as extname2, join as join18 } from "node:path";
7827
7958
  import {
7828
7959
  compositionRequiresWebGpu as compositionRequiresWebGpu3,
7829
- createFrameLookupTable as createFrameLookupTable2,
7830
- createVideoFrameInjector as createVideoFrameInjector2,
7831
- extractVideoFramesRange
7960
+ createVideoFrameInjector as createVideoFrameInjector3
7832
7961
  } from "@hyperframes/engine";
7833
7962
 
7834
7963
  // src/services/distributed/shared.ts
7835
7964
  import { execFile as execFileCallback } from "node:child_process";
7836
- import { dirname as dirname10, join as join16 } from "node:path";
7837
- import { existsSync as existsSync13, readFileSync as readFileSync6 } from "node:fs";
7965
+ import { dirname as dirname11, join as join17 } from "node:path";
7966
+ import { existsSync as existsSync14, readFileSync as readFileSync6 } from "node:fs";
7838
7967
  import { fileURLToPath as fileURLToPath3 } from "node:url";
7839
7968
  import { promisify as promisify2 } from "node:util";
7840
7969
  import {
@@ -7849,8 +7978,8 @@ function isPlanAudioArtifactPath(path) {
7849
7978
  }
7850
7979
  function resolvePlanAudioPath(planDir) {
7851
7980
  for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
7852
- const candidate = join16(planDir, name);
7853
- if (existsSync13(candidate)) return candidate;
7981
+ const candidate = join17(planDir, name);
7982
+ if (existsSync14(candidate)) return candidate;
7854
7983
  }
7855
7984
  return null;
7856
7985
  }
@@ -8107,11 +8236,11 @@ function buildSyntheticRenderJob(input) {
8107
8236
  var cachedProducerVersion = null;
8108
8237
  function readProducerVersion() {
8109
8238
  if (cachedProducerVersion !== null) return cachedProducerVersion;
8110
- const startDir = dirname10(fileURLToPath3(import.meta.url));
8239
+ const startDir = dirname11(fileURLToPath3(import.meta.url));
8111
8240
  let current = startDir;
8112
8241
  for (let i = 0; i < 10; i++) {
8113
- const candidate = join16(current, "package.json");
8114
- if (existsSync13(candidate)) {
8242
+ const candidate = join17(current, "package.json");
8243
+ if (existsSync14(candidate)) {
8115
8244
  try {
8116
8245
  const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
8117
8246
  if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
@@ -8121,7 +8250,7 @@ function readProducerVersion() {
8121
8250
  } catch {
8122
8251
  }
8123
8252
  }
8124
- const parent = dirname10(current);
8253
+ const parent = dirname11(current);
8125
8254
  if (parent === current) break;
8126
8255
  current = parent;
8127
8256
  }
@@ -8146,11 +8275,11 @@ var RenderChunkValidationError = class extends Error {
8146
8275
  };
8147
8276
  function createChunkVideoFrameInjectorFactory(frameLookup, options) {
8148
8277
  if (!(frameLookup instanceof Promise))
8149
- return () => createVideoFrameInjector2(frameLookup, options);
8278
+ return () => createVideoFrameInjector3(frameLookup, options);
8150
8279
  return () => {
8151
8280
  let hook;
8152
8281
  return async (page, time, heldVideoTime) => {
8153
- hook ??= createVideoFrameInjector2(await frameLookup, options);
8282
+ hook ??= createVideoFrameInjector3(await frameLookup, options);
8154
8283
  await hook?.(page, time, heldVideoTime);
8155
8284
  };
8156
8285
  };
@@ -8164,8 +8293,8 @@ function frameNumberFromFileName(name) {
8164
8293
  function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
8165
8294
  const result = [];
8166
8295
  for (const v of videos) {
8167
- const outputDir = join17(planDir, "video-frames", v.videoId);
8168
- if (!existsSync14(outputDir)) {
8296
+ const outputDir = join18(planDir, "video-frames", v.videoId);
8297
+ if (!existsSync15(outputDir)) {
8169
8298
  throw new Error(
8170
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.`
8171
8300
  );
@@ -8183,7 +8312,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
8183
8312
  if (!frameName) continue;
8184
8313
  const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
8185
8314
  const frameIndex = frameNumber === null ? i : frameNumber - 1;
8186
- framePaths.set(frameIndex, join17(outputDir, frameName));
8315
+ framePaths.set(frameIndex, join18(outputDir, frameName));
8187
8316
  }
8188
8317
  result.push({
8189
8318
  videoId: v.videoId,
@@ -8200,15 +8329,6 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
8200
8329
  }
8201
8330
  return result;
8202
8331
  }
8203
- function contiguousRuns(sortedIndexes) {
8204
- const runs2 = [];
8205
- for (const index of sortedIndexes) {
8206
- const last = runs2[runs2.length - 1];
8207
- if (last && last.firstFrame + last.frames === index) last.frames++;
8208
- else runs2.push({ firstFrame: index, frames: 1 });
8209
- }
8210
- return runs2;
8211
- }
8212
8332
  function buildChunkCaptureOptions(input) {
8213
8333
  const { dimensions, encoder } = input;
8214
8334
  const videoCaptureBeyondViewport = resolveVideoCaptureBeyondViewport(input.videoCount);
@@ -8231,28 +8351,19 @@ function buildChunkCaptureOptions(input) {
8231
8351
  requiresWebGpu: compositionRequiresWebGpu3(input.html)
8232
8352
  };
8233
8353
  }
8234
- function shownDeferredFrameIndexes(planVideos, frames, fps) {
8235
- const shown = /* @__PURE__ */ new Map();
8236
- const deferred = planVideos.extracted.filter((video) => video.deferredRange);
8237
- if (deferred.length === 0) return shown;
8238
- const table = createFrameLookupTable2(
8239
- planVideos.videos,
8240
- deferred.map((video) => ({
8241
- ...video,
8242
- outputDir: "",
8243
- framePaths: new Map(Array.from({ length: video.totalFrames }, (_, i) => [i, String(i)]))
8244
- })),
8245
- 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
8246
8366
  );
8247
- for (const frame of frames) {
8248
- const globalTime = frame * fps.den / fps.num;
8249
- for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
8250
- const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
8251
- indexes.add(payload.frameIndex);
8252
- shown.set(videoId, indexes);
8253
- }
8254
- }
8255
- return shown;
8256
8367
  }
8257
8368
  async function extractDeferredVideoFramesForChunk(input) {
8258
8369
  const deferred = input.planVideos.extracted.filter((video) => video.deferredRange);
@@ -8269,49 +8380,32 @@ async function extractDeferredVideoFramesForChunk(input) {
8269
8380
  url: remoteSources[path].split("?")[0]
8270
8381
  });
8271
8382
  }
8272
- if (deferred.length === 0) return [];
8273
- const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
8274
- return Promise.all(
8275
- deferred.map(async (video) => {
8276
- const outputDir = join17(input.outputRoot, video.videoId);
8277
- const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
8278
- if (indexes.length === 0) {
8279
- return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
8280
- }
8281
- 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;
8282
8394
  const remoteUrl = remoteSources[range.sourcePath];
8283
- const sourcePath = remoteUrl ?? join17(input.planDir, ...range.sourcePath.split("/"));
8284
- if (remoteUrl === void 0 && !existsSync14(sourcePath)) {
8395
+ const sourcePath = remoteUrl ?? join18(input.planDir, ...range.sourcePath.split("/"));
8396
+ if (remoteUrl === void 0 && !existsSync15(sourcePath)) {
8285
8397
  throw new RenderChunkValidationError(
8286
8398
  MISSING_PLAN_ARTIFACT,
8287
8399
  `[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
8288
8400
  );
8289
8401
  }
8290
- const extracted = await extractVideoFramesRange(
8291
- sourcePath,
8292
- video.videoId,
8293
- range.startTime,
8294
- range.durationSeconds,
8295
- {
8296
- fps: input.fps,
8297
- outputDir: input.outputRoot,
8298
- format: range.format,
8299
- frameRanges: contiguousRuns(indexes),
8300
- shownBox: input.shownBoxes?.get(video.videoId)
8301
- },
8302
- void 0,
8303
- input.cfg
8304
- );
8305
- const missing = indexes.find((index) => !extracted.framePaths.has(index));
8306
- if (missing !== void 0) {
8307
- throw new RenderChunkValidationError(
8308
- INVALID_VIDEO_METADATA,
8309
- `[renderChunk] video ${JSON.stringify(video.videoId)} has no frame ${missing}; the plan expected ${video.totalFrames} frames from ${range.sourcePath}.`
8310
- );
8311
- }
8312
- return { ...video, outputDir, framePaths: extracted.framePaths, ownedByLookup: false };
8313
- })
8314
- );
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
+ });
8315
8409
  }
8316
8410
 
8317
8411
  // src/services/distributed/planFrameCost.ts
@@ -8329,12 +8423,12 @@ async function measurePlanSecondsPerFrame(input) {
8329
8423
  planVideos,
8330
8424
  frames: sampled,
8331
8425
  fps: fpsRatio,
8332
- outputRoot: join18(workDir, "frame-cost-video-frames"),
8426
+ outputRoot: join19(workDir, "frame-cost-video-frames"),
8333
8427
  cfg: input.cfg
8334
8428
  })
8335
8429
  ];
8336
- const lookup = extracted.length > 0 ? createFrameLookupTable3(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
8337
- 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");
8338
8432
  const fileServer = await createFileServer({
8339
8433
  projectDir: compiledDir,
8340
8434
  compiledDir,
@@ -8346,7 +8440,7 @@ async function measurePlanSecondsPerFrame(input) {
8346
8440
  try {
8347
8441
  session = await createCaptureSession9(
8348
8442
  fileServer.url,
8349
- join18(workDir, "frame-cost"),
8443
+ join19(workDir, "frame-cost"),
8350
8444
  buildChunkCaptureOptions({
8351
8445
  dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
8352
8446
  encoder: input,
@@ -8443,7 +8537,7 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
8443
8537
  }
8444
8538
 
8445
8539
  // src/services/render/planReferenceBackground.ts
8446
- import { join as join19 } from "node:path";
8540
+ import { join as join20 } from "node:path";
8447
8541
  import {
8448
8542
  closeCaptureSession as closeCaptureSession10,
8449
8543
  createCaptureSession as createCaptureSession10,
@@ -8456,14 +8550,14 @@ async function readPlanReferenceBackground(input) {
8456
8550
  try {
8457
8551
  server = await createFileServer({
8458
8552
  projectDir: input.projectDir,
8459
- compiledDir: join19(input.workDir, "compiled"),
8553
+ compiledDir: join20(input.workDir, "compiled"),
8460
8554
  port: 0,
8461
8555
  preHeadScripts: [VIRTUAL_TIME_SHIM],
8462
8556
  fps: input.options.fps
8463
8557
  });
8464
8558
  session = await createCaptureSession10(
8465
8559
  server.url,
8466
- join19(input.workDir, "reference"),
8560
+ join20(input.workDir, "reference"),
8467
8561
  {
8468
8562
  ...input.options,
8469
8563
  requiresWebGpu: false
@@ -8499,8 +8593,8 @@ async function readPlanReferenceBackground(input) {
8499
8593
  }
8500
8594
 
8501
8595
  // src/services/render/stages/freezePlan.ts
8502
- import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
8503
- import { join as join20, relative as relative3, resolve as resolve8 } from "node:path";
8596
+ import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
8597
+ import { join as join21, relative as relative3, resolve as resolve8 } from "node:path";
8504
8598
  import {
8505
8599
  REFERENCE_BACKGROUND_FIELD,
8506
8600
  referenceBackgroundResult as referenceBackgroundResult3
@@ -8693,7 +8787,7 @@ function planFileSha256(absolutePath, digests) {
8693
8787
  return sha256;
8694
8788
  }
8695
8789
  async function prehashPlanFiles(dir, digests) {
8696
- if (!existsSync15(dir)) return;
8790
+ if (!existsSync16(dir)) return;
8697
8791
  for (const { absolutePath } of listPlanFiles(dir)) {
8698
8792
  if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
8699
8793
  }
@@ -8718,7 +8812,7 @@ function listPlanFiles(planDir) {
8718
8812
  function walk(dir) {
8719
8813
  const entries = readdirSync5(dir, { withFileTypes: true });
8720
8814
  for (const entry of entries) {
8721
- const full = join20(dir, entry.name);
8815
+ const full = join21(dir, entry.name);
8722
8816
  if (entry.isDirectory()) {
8723
8817
  walk(full);
8724
8818
  } else if (entry.isFile()) {
@@ -8759,19 +8853,19 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
8759
8853
  function writePlanRemoteFiles(planDir, files) {
8760
8854
  if (files.length === 0) return;
8761
8855
  const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
8762
- writeFileSync5(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");
8763
8857
  }
8764
8858
  function readPlanRemoteFiles(planDir) {
8765
- const path = join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
8766
- return existsSync15(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
8859
+ const path = join21(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
8860
+ return existsSync16(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
8767
8861
  }
8768
8862
  function recomputePlanHashFromPlanDir(planDir, digests) {
8769
- const planJsonPath = join20(planDir, "plan.json");
8770
- const encoderJsonPath = join20(planDir, "meta", "encoder.json");
8771
- if (!existsSync15(planJsonPath)) {
8863
+ const planJsonPath = join21(planDir, "plan.json");
8864
+ const encoderJsonPath = join21(planDir, "meta", "encoder.json");
8865
+ if (!existsSync16(planJsonPath)) {
8772
8866
  throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
8773
8867
  }
8774
- if (!existsSync15(encoderJsonPath)) {
8868
+ if (!existsSync16(encoderJsonPath)) {
8775
8869
  throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
8776
8870
  }
8777
8871
  const planJson = JSON.parse(readFileSync7(planJsonPath, "utf-8"));
@@ -8801,21 +8895,21 @@ async function freezePlan(input) {
8801
8895
  hasAudio,
8802
8896
  digests
8803
8897
  } = input;
8804
- if (!existsSync15(planDir)) {
8898
+ if (!existsSync16(planDir)) {
8805
8899
  throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
8806
8900
  }
8807
- const metaDir = join20(planDir, "meta");
8808
- if (!existsSync15(metaDir)) mkdirSync7(metaDir, { recursive: true });
8809
- writeFileSync5(
8810
- join20(metaDir, "composition.json"),
8901
+ const metaDir = join21(planDir, "meta");
8902
+ if (!existsSync16(metaDir)) mkdirSync7(metaDir, { recursive: true });
8903
+ writeFileSync6(
8904
+ join21(metaDir, "composition.json"),
8811
8905
  `${JSON.stringify(composition, null, 2)}
8812
8906
  `,
8813
8907
  "utf-8"
8814
8908
  );
8815
8909
  const encoderForCanonical = stripUndefined(encoder);
8816
8910
  const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
8817
- writeFileSync5(join20(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
8818
- writeFileSync5(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)}
8819
8913
  `, "utf-8");
8820
8914
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
8821
8915
  const planHash = computePlanHash({
@@ -8840,8 +8934,8 @@ async function freezePlan(input) {
8840
8934
  duration: durationSeconds,
8841
8935
  hasAudio
8842
8936
  };
8843
- const planJsonPath = join20(planDir, "plan.json");
8844
- writeFileSync5(planJsonPath, `${JSON.stringify(planJson, null, 2)}
8937
+ const planJsonPath = join21(planDir, "plan.json");
8938
+ writeFileSync6(planJsonPath, `${JSON.stringify(planJson, null, 2)}
8845
8939
  `, "utf-8");
8846
8940
  return { planJsonPath, planHash };
8847
8941
  }
@@ -8882,16 +8976,16 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
8882
8976
  }
8883
8977
 
8884
8978
  // src/services/distributed/chunkAudio.ts
8885
- import { closeSync as closeSync4, existsSync as existsSync16, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
8886
- import { writeFileSync as writeFileSync6, writeSync } from "node:fs";
8887
- import { join as join22 } from "node:path";
8979
+ import { closeSync as closeSync4, existsSync as existsSync17, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
8980
+ import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
8981
+ import { join as join23 } from "node:path";
8888
8982
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
8889
8983
 
8890
8984
  // src/services/distributed/mp4Stitch.ts
8891
8985
  import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync2, rmSync as rmSync6 } from "node:fs";
8892
8986
  import { open } from "node:fs/promises";
8893
8987
  import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
8894
- import { dirname as dirname11, join as join21 } from "node:path";
8988
+ import { dirname as dirname12, join as join22 } from "node:path";
8895
8989
  import { finished, pipeline } from "node:stream/promises";
8896
8990
  function boxAt(src, off, at, limit) {
8897
8991
  let size = src.readUInt32BE(off);
@@ -9255,15 +9349,15 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
9255
9349
  const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
9256
9350
  const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
9257
9351
  const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
9258
- mkdirSync8(dirname11(outputPath), { recursive: true });
9352
+ mkdirSync8(dirname12(outputPath), { recursive: true });
9259
9353
  const workDir = mkdtempSync2(`${outputPath}.stitch-`);
9260
- const out = createWriteStream(join21(workDir, "out.mp4"));
9354
+ const out = createWriteStream(join22(workDir, "out.mp4"));
9261
9355
  try {
9262
9356
  out.write(header);
9263
9357
  for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
9264
9358
  out.end();
9265
9359
  await finished(out);
9266
- renameWithRetry7(join21(workDir, "out.mp4"), outputPath);
9360
+ renameWithRetry7(join22(workDir, "out.mp4"), outputPath);
9267
9361
  } finally {
9268
9362
  out.destroy();
9269
9363
  rmSync6(workDir, { recursive: true, force: true });
@@ -9357,7 +9451,7 @@ async function writeChunkAudioSlices(input) {
9357
9451
  });
9358
9452
  const bytes = readFileSync8(input.audioPath);
9359
9453
  const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
9360
- mkdirSync9(join22(input.planDir, SLICE_DIR), { recursive: true });
9454
+ mkdirSync9(join23(input.planDir, SLICE_DIR), { recursive: true });
9361
9455
  input.chunks.forEach((_, index) => {
9362
9456
  const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
9363
9457
  if (!cut) return;
@@ -9365,17 +9459,17 @@ async function writeChunkAudioSlices(input) {
9365
9459
  const start = offsets[cut.firstPacket];
9366
9460
  layout.partStarts[index] = start;
9367
9461
  const end = offsets[last] + track.sizes[last];
9368
- writeFileSync6(join22(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
9462
+ writeFileSync7(join23(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
9369
9463
  });
9370
9464
  const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
9371
- writeFileSync6(join22(input.planDir, SLICE_FRAME), Buffer.concat(frame));
9372
- writeFileSync6(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));
9373
9467
  }
9374
9468
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
9375
- const layoutPath = join22(planDir, SLICE_LAYOUT);
9376
- if (!existsSync16(layoutPath)) return resolvePlanAudioPath(planDir);
9469
+ const layoutPath = join23(planDir, SLICE_LAYOUT);
9470
+ if (!existsSync17(layoutPath)) return resolvePlanAudioPath(planDir);
9377
9471
  const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
9378
- const frame = readFileSync8(join22(planDir, SLICE_FRAME));
9472
+ const frame = readFileSync8(join23(planDir, SLICE_FRAME));
9379
9473
  const fd = openSync4(scratchPath, "w");
9380
9474
  try {
9381
9475
  writeSync(fd, frame, 0, layout.payloadStart, 0);
@@ -9388,7 +9482,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
9388
9482
  );
9389
9483
  const start = layout.partStarts[chunkIndex];
9390
9484
  if (start !== void 0) {
9391
- const part = readFileSync8(join22(planDir, chunkAudioSlicePath(chunkIndex)));
9485
+ const part = readFileSync8(join23(planDir, chunkAudioSlicePath(chunkIndex)));
9392
9486
  writeSync(fd, part, 0, part.length, start);
9393
9487
  }
9394
9488
  ftruncateSync(fd, layout.sizeBytes);
@@ -9444,7 +9538,7 @@ async function muxChunkWithAudio(input) {
9444
9538
  // src/services/distributed/planSize.ts
9445
9539
  import { createHash as createHash7 } from "node:crypto";
9446
9540
  import { lstatSync, readdirSync as readdirSync6 } from "node:fs";
9447
- 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";
9448
9542
  var MAX_TOP_COMPONENTS = 10;
9449
9543
  var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
9450
9544
  ".aac",
@@ -9556,7 +9650,7 @@ function walkRegularFiles(dir, visit) {
9556
9650
  return;
9557
9651
  }
9558
9652
  for (const entry of entries) {
9559
- const full = join23(dir, entry.name);
9653
+ const full = join24(dir, entry.name);
9560
9654
  if (entry.isDirectory()) {
9561
9655
  walkRegularFiles(full, visit);
9562
9656
  } else if (entry.isFile()) {
@@ -9772,13 +9866,13 @@ function assertPositiveInteger(name, value) {
9772
9866
  }
9773
9867
  var FREEZE_OWNED_PLAN_FILES = [
9774
9868
  "plan.json",
9775
- join24("meta", "composition.json"),
9776
- join24("meta", "encoder.json"),
9777
- join24("meta", "chunks.json")
9869
+ join25("meta", "composition.json"),
9870
+ join25("meta", "encoder.json"),
9871
+ join25("meta", "chunks.json")
9778
9872
  ];
9779
9873
  function removeFreezeOwnedPlanFiles(planDir) {
9780
9874
  for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
9781
- rmSync7(join24(planDir, relativePath), { force: true });
9875
+ rmSync7(join25(planDir, relativePath), { force: true });
9782
9876
  }
9783
9877
  }
9784
9878
  function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
@@ -9933,19 +10027,19 @@ function createVideoSourceShipper(input) {
9933
10027
  const known = shipped.get(srcPath);
9934
10028
  if (known) return known;
9935
10029
  const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
9936
- const current = stagedRel === null ? srcPath : join24(input.planDir, "video-frames", stagedRel);
10030
+ const current = stagedRel === null ? srcPath : join25(input.planDir, "video-frames", stagedRel);
9937
10031
  let result = null;
9938
10032
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
9939
10033
  const rel = pathInside(root, srcPath);
9940
- if (rel !== null && (existsSync17(join24(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
10034
+ if (rel !== null && (existsSync18(join25(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
9941
10035
  result = ["compiled", ...rel.split(sep2)].join("/");
9942
10036
  break;
9943
10037
  }
9944
10038
  }
9945
10039
  if (result === null) {
9946
10040
  result = `video-sources/${shipped.size}${extname4(current)}`;
9947
- const destination = join24(input.planDir, ...result.split("/"));
9948
- 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 });
9949
10043
  try {
9950
10044
  linkSync(current, destination);
9951
10045
  } catch {
@@ -9965,7 +10059,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
9965
10059
  useGpu: false,
9966
10060
  browserGpuMode: "software"
9967
10061
  });
9968
- if (!existsSync17(planDir)) mkdirSync10(planDir, { recursive: true });
10062
+ if (!existsSync18(planDir)) mkdirSync10(planDir, { recursive: true });
9969
10063
  const log = config.logger ?? defaultLogger;
9970
10064
  const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
9971
10065
  const abortSignal = config.abortSignal;
@@ -9994,14 +10088,14 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
9994
10088
  variables: config.variables
9995
10089
  });
9996
10090
  const entryFile = config.entryFile ?? "index.html";
9997
- const htmlPath = join24(projectDir, entryFile);
9998
- if (!existsSync17(htmlPath)) {
10091
+ const htmlPath = join25(projectDir, entryFile);
10092
+ if (!existsSync18(htmlPath)) {
9999
10093
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
10000
10094
  }
10001
- const workDir = join24(planDir, ".plan-work");
10095
+ const workDir = join25(planDir, ".plan-work");
10002
10096
  rmSync7(workDir, { recursive: true, force: true });
10003
10097
  mkdirSync10(workDir, { recursive: true });
10004
- const compiledDir = join24(workDir, "compiled");
10098
+ const compiledDir = join25(workDir, "compiled");
10005
10099
  mkdirSync10(compiledDir, { recursive: true });
10006
10100
  let stageStarted = Date.now();
10007
10101
  cpSync2(projectDir, compiledDir, {
@@ -10015,10 +10109,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
10015
10109
  }
10016
10110
  });
10017
10111
  logPlanStage(log, "copy", stageStarted);
10018
- const finalCompiledDir = join24(planDir, "compiled");
10112
+ const finalCompiledDir = join25(planDir, "compiled");
10019
10113
  const needsAlpha = outputNeedsAlpha(config.format);
10020
10114
  const remoteFiles = Object.entries(options.remoteFiles ?? {});
10021
- const remoteLocalPath = (rel) => join24(projectDir, ...rel.split("/"));
10115
+ const remoteLocalPath = (rel) => join25(projectDir, ...rel.split("/"));
10022
10116
  for (const [, file] of remoteFiles) {
10023
10117
  declareContentAddressedUrl(file.url, file.sha256);
10024
10118
  if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
@@ -10039,7 +10133,6 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
10039
10133
  urlByAbsentLocalPath,
10040
10134
  projectDir,
10041
10135
  workDir,
10042
- htmlPath,
10043
10136
  entryFile,
10044
10137
  job,
10045
10138
  cfg,
@@ -10222,17 +10315,17 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10222
10315
  let stageStarted = Date.now();
10223
10316
  const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
10224
10317
  logPlanStage(log, "extract", stageStarted);
10225
- const stagedVideoFrames = join24(compiledDir, "__hyperframes_video_frames");
10226
- const videoFramesDst = join24(planDir, "video-frames");
10227
- if (existsSync17(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
10228
- const videoSourcesDir = join24(planDir, "video-sources");
10318
+ const stagedVideoFrames = join25(compiledDir, "__hyperframes_video_frames");
10319
+ const videoFramesDst = join25(planDir, "video-frames");
10320
+ if (existsSync18(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
10321
+ const videoSourcesDir = join25(planDir, "video-sources");
10229
10322
  rmSync7(videoSourcesDir, { recursive: true, force: true });
10230
- if (existsSync17(stagedVideoFrames)) {
10323
+ if (existsSync18(stagedVideoFrames)) {
10231
10324
  renameWithRetry8(stagedVideoFrames, videoFramesDst);
10232
10325
  } else {
10233
10326
  mkdirSync10(videoFramesDst, { recursive: true });
10234
10327
  }
10235
- if (existsSync17(finalCompiledDir)) rmSync7(finalCompiledDir, { recursive: true, force: true });
10328
+ if (existsSync18(finalCompiledDir)) rmSync7(finalCompiledDir, { recursive: true, force: true });
10236
10329
  renameWithRetry8(compiledDir, finalCompiledDir);
10237
10330
  const shipVideoSource = createVideoSourceShipper({
10238
10331
  planDir,
@@ -10262,7 +10355,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10262
10355
  };
10263
10356
  })
10264
10357
  });
10265
- mkdirSync10(join24(planDir, "meta"), { recursive: true });
10358
+ mkdirSync10(join25(planDir, "meta"), { recursive: true });
10266
10359
  writePlanRemoteFiles(
10267
10360
  planDir,
10268
10361
  remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
@@ -10271,8 +10364,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10271
10364
  sizeBytes
10272
10365
  }))
10273
10366
  );
10274
- writeFileSync7(
10275
- join24(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
10367
+ writeFileSync8(
10368
+ join25(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
10276
10369
  JSON.stringify(planVideosJson, null, 2),
10277
10370
  "utf-8"
10278
10371
  );
@@ -10351,11 +10444,11 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10351
10444
  if (audioResult?.audioError) {
10352
10445
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
10353
10446
  }
10354
- const planAudioPath = join24(planDir, PLAN_AUDIO_RELATIVE_PATH);
10355
- if (audioResult?.hasAudio && existsSync17(audioResult.audioOutputPath)) {
10447
+ const planAudioPath = join25(planDir, PLAN_AUDIO_RELATIVE_PATH);
10448
+ if (audioResult?.hasAudio && existsSync18(audioResult.audioOutputPath)) {
10356
10449
  renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
10357
10450
  }
10358
- if (chunksCarryAudio && existsSync17(planAudioPath)) {
10451
+ if (chunksCarryAudio && existsSync18(planAudioPath)) {
10359
10452
  if (options.sliceChunkAudio) {
10360
10453
  stageStarted = Date.now();
10361
10454
  await writeChunkAudioSlices({
@@ -10468,7 +10561,7 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
10468
10561
  if (mixed.audioError) {
10469
10562
  applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
10470
10563
  }
10471
- if (!mixed.hasAudio || !existsSync17(mixed.audioOutputPath)) return { hasAudio: false };
10564
+ if (!mixed.hasAudio || !existsSync18(mixed.audioOutputPath)) return { hasAudio: false };
10472
10565
  renameWithRetry8(mixed.audioOutputPath, outputPath);
10473
10566
  return { hasAudio: true };
10474
10567
  } finally {
@@ -10483,7 +10576,7 @@ import {
10483
10576
  } from "@hyperframes/engine";
10484
10577
  import {
10485
10578
  copyFileSync as copyFileSync8,
10486
- existsSync as existsSync19,
10579
+ existsSync as existsSync20,
10487
10580
  lstatSync as lstatSync2,
10488
10581
  mkdirSync as mkdirSync12,
10489
10582
  mkdtempSync as mkdtempSync4,
@@ -10491,26 +10584,26 @@ import {
10491
10584
  readdirSync as readdirSync7,
10492
10585
  rmSync as rmSync9,
10493
10586
  statSync as statSync7,
10494
- writeFileSync as writeFileSync9
10587
+ writeFileSync as writeFileSync10
10495
10588
  } from "node:fs";
10496
10589
  import { tmpdir } from "node:os";
10497
- 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";
10498
10591
  import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
10499
- import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
10592
+ import { createFrameLookupTable as createFrameLookupTable5, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
10500
10593
 
10501
10594
  // src/services/distributed/planV2Publisher.ts
10502
10595
  import {
10503
10596
  copyFileSync as copyFileSync7,
10504
- existsSync as existsSync18,
10597
+ existsSync as existsSync19,
10505
10598
  linkSync as linkSync2,
10506
10599
  mkdirSync as mkdirSync11,
10507
10600
  mkdtempSync as mkdtempSync3,
10508
10601
  rmSync as rmSync8,
10509
10602
  statSync as statSync6,
10510
- writeFileSync as writeFileSync8
10603
+ writeFileSync as writeFileSync9
10511
10604
  } from "node:fs";
10512
10605
  import { renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
10513
- import { dirname as dirname12, join as join26 } from "node:path";
10606
+ import { dirname as dirname13, join as join27 } from "node:path";
10514
10607
 
10515
10608
  // src/services/distributed/planV2Errors.ts
10516
10609
  var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
@@ -10525,7 +10618,7 @@ var PlanV2IntegrityError = class extends Error {
10525
10618
  };
10526
10619
 
10527
10620
  // src/services/distributed/planV2Layout.ts
10528
- import { join as join25 } from "node:path";
10621
+ import { join as join26 } from "node:path";
10529
10622
  var SHA256_PATTERN = /^[0-9a-f]{64}$/;
10530
10623
  function assertPlanV2Sha256(value, field) {
10531
10624
  if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
@@ -10535,7 +10628,7 @@ function assertPlanV2Sha256(value, field) {
10535
10628
  }
10536
10629
  function planV2BlobPath(root, sha256) {
10537
10630
  const digest = assertPlanV2Sha256(sha256, "artifact sha256");
10538
- return join25(root, "artifacts", "sha256", digest.slice(0, 2), digest);
10631
+ return join26(root, "artifacts", "sha256", digest.slice(0, 2), digest);
10539
10632
  }
10540
10633
 
10541
10634
  // src/services/distributed/planV2Publisher.ts
@@ -10570,13 +10663,13 @@ var LocalPlanV2ArtifactPublisher = class {
10570
10663
  #linkFile;
10571
10664
  #committed = false;
10572
10665
  constructor(destinationDir, options = {}) {
10573
- if (existsSync18(destinationDir)) {
10666
+ if (existsSync19(destinationDir)) {
10574
10667
  throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
10575
10668
  }
10576
10669
  this.destinationDir = destinationDir;
10577
10670
  this.#linkFile = options.linkFile ?? linkSync2;
10578
- mkdirSync11(dirname12(destinationDir), { recursive: true });
10579
- 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-"));
10580
10673
  }
10581
10674
  async putBlob(blob) {
10582
10675
  const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
@@ -10587,10 +10680,10 @@ var LocalPlanV2ArtifactPublisher = class {
10587
10680
  );
10588
10681
  }
10589
10682
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
10590
- if (existsSync18(destinationPath)) return;
10591
- mkdirSync11(dirname12(destinationPath), { recursive: true });
10592
- const stagingDir = mkdtempSync3(join26(dirname12(destinationPath), ".plan-v2-blob-"));
10593
- const temporaryPath = join26(stagingDir, "blob");
10683
+ if (existsSync19(destinationPath)) return;
10684
+ mkdirSync11(dirname13(destinationPath), { recursive: true });
10685
+ const stagingDir = mkdtempSync3(join27(dirname13(destinationPath), ".plan-v2-blob-"));
10686
+ const temporaryPath = join27(stagingDir, "blob");
10594
10687
  try {
10595
10688
  try {
10596
10689
  this.#linkFile(blob.sourcePath, temporaryPath);
@@ -10605,13 +10698,13 @@ var LocalPlanV2ArtifactPublisher = class {
10605
10698
  }
10606
10699
  async commitManifest(manifestBytes) {
10607
10700
  for (const digest of new Set(manifestDigests(manifestBytes))) {
10608
- if (!existsSync18(planV2BlobPath(this.temporaryDir, digest))) {
10701
+ if (!existsSync19(planV2BlobPath(this.temporaryDir, digest))) {
10609
10702
  throw new PlanV2IntegrityError(
10610
10703
  `cannot commit manifest before referenced blob is durable: ${digest}`
10611
10704
  );
10612
10705
  }
10613
10706
  }
10614
- writeFileSync8(join26(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
10707
+ writeFileSync9(join27(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
10615
10708
  renameWithRetry9(this.temporaryDir, this.destinationDir);
10616
10709
  this.#committed = true;
10617
10710
  }
@@ -10656,7 +10749,7 @@ function listFiles(root) {
10656
10749
  const rootResolved = resolve9(root);
10657
10750
  function walk(dir) {
10658
10751
  for (const entry of readdirSync7(dir, { withFileTypes: true })) {
10659
- const absolutePath = join27(dir, entry.name);
10752
+ const absolutePath = join28(dir, entry.name);
10660
10753
  if (entry.isDirectory()) {
10661
10754
  walk(absolutePath);
10662
10755
  } else if (entry.isFile()) {
@@ -10683,13 +10776,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
10683
10776
  }
10684
10777
  }
10685
10778
  function validateExtractionCacheCompleteSentinels(executionPlanDir) {
10686
- const videoRoot = join27(executionPlanDir, "video-frames");
10687
- if (!existsSync19(videoRoot)) return;
10779
+ const videoRoot = join28(executionPlanDir, "video-frames");
10780
+ if (!existsSync20(videoRoot)) return;
10688
10781
  for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
10689
10782
  if (!videoEntry.isDirectory()) continue;
10690
- const videoDir = join27(videoRoot, videoEntry.name);
10783
+ const videoDir = join28(videoRoot, videoEntry.name);
10691
10784
  if (readdirSync7(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
10692
- const sentinelPath = join27(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
10785
+ const sentinelPath = join28(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
10693
10786
  assertValidExtractionCacheCompleteSentinel(sentinelPath);
10694
10787
  }
10695
10788
  }
@@ -10736,7 +10829,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
10736
10829
  if (video.deferredRange) {
10737
10830
  const framePaths2 = /* @__PURE__ */ new Map();
10738
10831
  for (let index = 0; index < video.totalFrames; index++) {
10739
- framePaths2.set(index, join27(outputDir, String(index)));
10832
+ framePaths2.set(index, join28(outputDir, String(index)));
10740
10833
  }
10741
10834
  return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
10742
10835
  }
@@ -10744,7 +10837,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
10744
10837
  const framePaths = /* @__PURE__ */ new Map();
10745
10838
  for (const frameName of frameNames) {
10746
10839
  if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
10747
- assertValidExtractionCacheCompleteSentinel(join27(outputDir, frameName));
10840
+ assertValidExtractionCacheCompleteSentinel(join28(outputDir, frameName));
10748
10841
  continue;
10749
10842
  }
10750
10843
  const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
@@ -10763,7 +10856,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
10763
10856
  `duplicate extracted frame index ${frameIndex} in ${outputDir}`
10764
10857
  );
10765
10858
  }
10766
- framePaths.set(frameIndex, join27(outputDir, frameName));
10859
+ framePaths.set(frameIndex, join28(outputDir, frameName));
10767
10860
  }
10768
10861
  return {
10769
10862
  ...video,
@@ -10784,8 +10877,8 @@ function parsePlanVideosJson2(value) {
10784
10877
  }
10785
10878
  }
10786
10879
  function materializeExtractedVideoDirectories(planDir) {
10787
- const videosPath = join27(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10788
- if (!existsSync19(videosPath)) return;
10880
+ const videosPath = join28(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10881
+ if (!existsSync20(videosPath)) return;
10789
10882
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
10790
10883
  for (const video of videos.extracted) {
10791
10884
  mkdirSync12(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
@@ -10818,13 +10911,13 @@ function parseChunkSlices(value) {
10818
10911
  });
10819
10912
  }
10820
10913
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
10821
- const videoRoot = join27(executionPlanDir, "video-frames");
10822
- const hasExtractedFrames = existsSync19(videoRoot) && listFiles(videoRoot).length > 0;
10823
- const videosPath = join27(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10824
- if (!existsSync19(videosPath)) {
10914
+ const videoRoot = join28(executionPlanDir, "video-frames");
10915
+ const hasExtractedFrames = existsSync20(videoRoot) && listFiles(videoRoot).length > 0;
10916
+ const videosPath = join28(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10917
+ if (!existsSync20(videosPath)) {
10825
10918
  return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
10826
10919
  }
10827
- const chunksPath = join27(executionPlanDir, "meta", "chunks.json");
10920
+ const chunksPath = join28(executionPlanDir, "meta", "chunks.json");
10828
10921
  const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
10829
10922
  const chunks = readJsonFile(chunksPath, "meta/chunks.json");
10830
10923
  const parsedVideos = parsePlanVideosJson2(videos);
@@ -10832,7 +10925,7 @@ function buildVideoChunkDependencies(executionPlanDir, dimensions) {
10832
10925
  const extracted = listVideoFramePaths(executionPlanDir, parsedVideos);
10833
10926
  const fpsNum = readPositiveInteger2(dimensions.fpsNum, "dimensions.fpsNum");
10834
10927
  const fpsDen = readPositiveInteger2(dimensions.fpsDen, "dimensions.fpsDen");
10835
- const table = createFrameLookupTable4(
10928
+ const table = createFrameLookupTable5(
10836
10929
  parsedVideos.videos,
10837
10930
  extracted,
10838
10931
  resolveRenderFpsConfig({ num: fpsNum, den: fpsDen }).value
@@ -10878,10 +10971,10 @@ function computeManifestHash(manifest) {
10878
10971
  return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
10879
10972
  }
10880
10973
  function writeBlob(sourcePath, destinationPath) {
10881
- if (existsSync19(destinationPath)) return;
10882
- mkdirSync12(dirname13(destinationPath), { recursive: true });
10883
- const temporaryDir = mkdtempSync4(join27(dirname13(destinationPath), ".plan-v2-blob-"));
10884
- const temporaryPath = join27(temporaryDir, "blob");
10974
+ if (existsSync20(destinationPath)) return;
10975
+ mkdirSync12(dirname14(destinationPath), { recursive: true });
10976
+ const temporaryDir = mkdtempSync4(join28(dirname14(destinationPath), ".plan-v2-blob-"));
10977
+ const temporaryPath = join28(temporaryDir, "blob");
10885
10978
  try {
10886
10979
  copyFileSync8(sourcePath, temporaryPath);
10887
10980
  renameWithRetry10(temporaryPath, destinationPath);
@@ -10917,8 +11010,8 @@ async function putTasks(tasks) {
10917
11010
  if (failure) throw failure.reason;
10918
11011
  }
10919
11012
  function buildPlanV2Publication(executionPlanDir, digests) {
10920
- const executionPlanPath = join27(executionPlanDir, "plan.json");
10921
- if (!existsSync19(executionPlanPath)) {
11013
+ const executionPlanPath = join28(executionPlanDir, "plan.json");
11014
+ if (!existsSync20(executionPlanPath)) {
10922
11015
  throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
10923
11016
  }
10924
11017
  const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
@@ -10945,7 +11038,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
10945
11038
  }
10946
11039
  validateExtractionCacheCompleteSentinels(executionPlanDir);
10947
11040
  const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
10948
- const encoder = readJsonFile(join27(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
11041
+ const encoder = readJsonFile(join28(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
10949
11042
  const files = listFiles(executionPlanDir);
10950
11043
  const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
10951
11044
  for (const file of files) {
@@ -10998,21 +11091,21 @@ function buildPlanV2Publication(executionPlanDir, digests) {
10998
11091
  };
10999
11092
  }
11000
11093
  function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
11001
- if (existsSync19(planV2Dir)) {
11094
+ if (existsSync20(planV2Dir)) {
11002
11095
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
11003
11096
  }
11004
11097
  const publication = buildPlanV2Publication(executionPlanDir);
11005
11098
  if (publication.storedBlobs.length > 0) {
11006
11099
  throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
11007
11100
  }
11008
- mkdirSync12(dirname13(planV2Dir), { recursive: true });
11009
- 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-"));
11010
11103
  try {
11011
11104
  for (const blob of publication.blobs) {
11012
11105
  writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
11013
11106
  }
11014
- writeFileSync9(
11015
- join27(tempDir, "plan.json"),
11107
+ writeFileSync10(
11108
+ join28(tempDir, "plan.json"),
11016
11109
  canonicalJsonStringify(publication.manifest),
11017
11110
  "utf-8"
11018
11111
  );
@@ -11053,7 +11146,7 @@ async function publishExecutionPlan(executionPlanDir, publisher, { digests, log
11053
11146
  }
11054
11147
  }
11055
11148
  async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
11056
- const stagingRoot = mkdtempSync4(join27(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
11149
+ const stagingRoot = mkdtempSync4(join28(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
11057
11150
  const digests = /* @__PURE__ */ new Map();
11058
11151
  try {
11059
11152
  await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
@@ -11064,7 +11157,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
11064
11157
  // artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
11065
11158
  whileAudioMixes: () => putBlobs(
11066
11159
  publisher,
11067
- listFiles(join27(stagingRoot, "compiled")).map(
11160
+ listFiles(join28(stagingRoot, "compiled")).map(
11068
11161
  (file) => publishBlobFor(file.absolutePath, digests)
11069
11162
  )
11070
11163
  ),
@@ -11084,12 +11177,12 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
11084
11177
  }
11085
11178
  }
11086
11179
  async function planV2(projectDir, config, planV2Dir) {
11087
- if (existsSync19(planV2Dir)) {
11180
+ if (existsSync20(planV2Dir)) {
11088
11181
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
11089
11182
  }
11090
11183
  const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
11091
11184
  const manifest = await planV2WithPublisher(projectDir, config, publisher, {
11092
- stagingParentDir: dirname13(planV2Dir)
11185
+ stagingParentDir: dirname14(planV2Dir)
11093
11186
  });
11094
11187
  return resultFromManifest(planV2Dir, manifest);
11095
11188
  }
@@ -11101,7 +11194,7 @@ function resultFromManifest(planV2Dir, manifest) {
11101
11194
  isRecord3(executionPlan) ? executionPlan[REFERENCE_BACKGROUND_FIELD3] : void 0
11102
11195
  ),
11103
11196
  planDir: planV2Dir,
11104
- manifestPath: join27(planV2Dir, "plan.json"),
11197
+ manifestPath: join28(planV2Dir, "plan.json"),
11105
11198
  planProtocol: PLAN_PROTOCOL_V2,
11106
11199
  planHash: manifest.planHash,
11107
11200
  sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
@@ -11237,8 +11330,8 @@ function parsePlanV2Manifest(value) {
11237
11330
  return parsed;
11238
11331
  }
11239
11332
  function readPlanV2Manifest(planV2Dir) {
11240
- const manifestPath = join27(planV2Dir, "plan.json");
11241
- if (!existsSync19(manifestPath)) {
11333
+ const manifestPath = join28(planV2Dir, "plan.json");
11334
+ if (!existsSync20(manifestPath)) {
11242
11335
  throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
11243
11336
  }
11244
11337
  const value = readJsonFile(manifestPath, "v2 manifest");
@@ -11257,7 +11350,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
11257
11350
  }
11258
11351
  function verifyBlob(planV2Dir, artifact) {
11259
11352
  const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
11260
- if (!existsSync19(sourcePath)) {
11353
+ if (!existsSync20(sourcePath)) {
11261
11354
  throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
11262
11355
  }
11263
11356
  const stats = statSync7(sourcePath);
@@ -11270,7 +11363,7 @@ function verifyBlob(planV2Dir, artifact) {
11270
11363
  return sourcePath;
11271
11364
  }
11272
11365
  function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
11273
- if (existsSync19(destinationDir)) {
11366
+ if (existsSync20(destinationDir)) {
11274
11367
  throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
11275
11368
  }
11276
11369
  const manifest = readPlanV2Manifest(planV2Dir);
@@ -11282,17 +11375,17 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
11282
11375
  artifact,
11283
11376
  sourcePath: verifyBlob(planV2Dir, artifact)
11284
11377
  }));
11285
- mkdirSync12(dirname13(destinationDir), { recursive: true });
11286
- 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-"));
11287
11380
  try {
11288
11381
  for (const { artifact, sourcePath } of verified) {
11289
- const destinationPath = join27(tempDir, ...artifact.path.split("/"));
11290
- mkdirSync12(dirname13(destinationPath), { recursive: true });
11382
+ const destinationPath = join28(tempDir, ...artifact.path.split("/"));
11383
+ mkdirSync12(dirname14(destinationPath), { recursive: true });
11291
11384
  copyFileSync8(sourcePath, destinationPath);
11292
11385
  }
11293
11386
  if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
11294
- writeFileSync9(
11295
- join27(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
11387
+ writeFileSync10(
11388
+ join28(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
11296
11389
  canonicalJsonStringify({
11297
11390
  manifest,
11298
11391
  target,
@@ -11315,7 +11408,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
11315
11408
  // Join the artifact's OWN name, not the current constant: a plan written
11316
11409
  // before the container change carries the legacy name, and materializing it
11317
11410
  // under the new one would point at a file that was never written.
11318
- audioPath: target.role === "assembler" ? audioArtifact ? join27(destinationDir, audioArtifact.path) : null : null
11411
+ audioPath: target.role === "assembler" ? audioArtifact ? join28(destinationDir, audioArtifact.path) : null : null
11319
11412
  };
11320
11413
  }
11321
11414
  function assertRemoteSources(remoteSources, artifacts) {
@@ -11334,8 +11427,8 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
11334
11427
  return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
11335
11428
  }
11336
11429
  function readValidatedPlanV2Materialization(planDir, expectedTarget) {
11337
- const markerPath = join27(planDir, PLAN_V2_MATERIALIZATION_MARKER);
11338
- if (!existsSync19(markerPath)) return null;
11430
+ const markerPath = join28(planDir, PLAN_V2_MATERIALIZATION_MARKER);
11431
+ if (!existsSync20(markerPath)) return null;
11339
11432
  const marker = readJsonFile(markerPath, "materialization marker");
11340
11433
  if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
11341
11434
  throw new PlanV2IntegrityError("malformed materialization marker");
@@ -11352,8 +11445,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
11352
11445
  assertRemoteSources(remoteSources, required);
11353
11446
  for (const artifact of required) {
11354
11447
  if (Object.hasOwn(remoteSources, artifact.path)) continue;
11355
- const path = join27(planDir, ...artifact.path.split("/"));
11356
- if (!existsSync19(path) || statSync7(path).size !== artifact.sizeBytes) {
11448
+ const path = join28(planDir, ...artifact.path.split("/"));
11449
+ if (!existsSync20(path) || statSync7(path).size !== artifact.sizeBytes) {
11357
11450
  throw new PlanV2IntegrityError(
11358
11451
  `materialized artifact missing or truncated: ${artifact.path}`
11359
11452
  );
@@ -11368,7 +11461,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
11368
11461
  // src/services/distributed/planV2Execution.ts
11369
11462
  import { mkdtempSync as mkdtempSync6, rmSync as rmSync12 } from "node:fs";
11370
11463
  import { tmpdir as tmpdir2 } from "node:os";
11371
- import { join as join30 } from "node:path";
11464
+ import { join as join31 } from "node:path";
11372
11465
 
11373
11466
  // src/services/distributed/assemble.ts
11374
11467
  import {
@@ -11377,16 +11470,16 @@ import {
11377
11470
  } from "@hyperframes/engine";
11378
11471
  import {
11379
11472
  cpSync as cpSync3,
11380
- existsSync as existsSync20,
11473
+ existsSync as existsSync21,
11381
11474
  mkdirSync as mkdirSync13,
11382
11475
  mkdtempSync as mkdtempSync5,
11383
11476
  readFileSync as readFileSync10,
11384
11477
  readdirSync as readdirSync8,
11385
11478
  rmSync as rmSync10,
11386
11479
  statSync as statSync8,
11387
- writeFileSync as writeFileSync10
11480
+ writeFileSync as writeFileSync11
11388
11481
  } from "node:fs";
11389
- import { dirname as dirname14, join as join28 } from "node:path";
11482
+ import { dirname as dirname15, join as join29 } from "node:path";
11390
11483
  import {
11391
11484
  appendRateCapArgs as appendRateCapArgs2,
11392
11485
  appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
@@ -11432,8 +11525,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11432
11525
  }
11433
11526
  const format = plan.dimensions.format;
11434
11527
  const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
11435
- if (!existsSync20(dirname14(outputPath))) {
11436
- mkdirSync13(dirname14(outputPath), { recursive: true });
11528
+ if (!existsSync21(dirname15(outputPath))) {
11529
+ mkdirSync13(dirname15(outputPath), { recursive: true });
11437
11530
  }
11438
11531
  const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
11439
11532
  const fps = { num: plan.dimensions.fpsNum, den: plan.dimensions.fpsDen };
@@ -11455,8 +11548,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11455
11548
  };
11456
11549
  try {
11457
11550
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
11458
- const finalPath = join28(workDir, `output.${format}`);
11459
- const audio = audioPath !== null && existsSync20(audioPath) ? audioPath : null;
11551
+ const finalPath = join29(workDir, `output.${format}`);
11552
+ const audio = audioPath !== null && existsSync21(audioPath) ? audioPath : null;
11460
11553
  await writeJoined(ctx, videoSources, audio, finalPath);
11461
11554
  renameWithRetry11(finalPath, outputPath);
11462
11555
  log.info("[assemble] stage timings", {
@@ -11474,7 +11567,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11474
11567
  });
11475
11568
  }
11476
11569
  }
11477
- const fileSize = existsSync20(outputPath) ? statSync8(outputPath).size : 0;
11570
+ const fileSize = existsSync21(outputPath) ? statSync8(outputPath).size : 0;
11478
11571
  return {
11479
11572
  ...referenceBackgroundResult6(plan[REFERENCE_BACKGROUND_FIELD4]),
11480
11573
  outputPath,
@@ -11484,15 +11577,15 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11484
11577
  };
11485
11578
  }
11486
11579
  function readAssemblePlan(planDir, chunkPaths) {
11487
- const planJsonPath = join28(planDir, "plan.json");
11488
- const chunksJsonPath = join28(planDir, "meta", "chunks.json");
11489
- if (!existsSync20(planJsonPath)) {
11580
+ const planJsonPath = join29(planDir, "plan.json");
11581
+ const chunksJsonPath = join29(planDir, "meta", "chunks.json");
11582
+ if (!existsSync21(planJsonPath)) {
11490
11583
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
11491
11584
  }
11492
11585
  const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
11493
11586
  readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
11494
11587
  validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
11495
- if (!existsSync20(chunksJsonPath)) {
11588
+ if (!existsSync21(chunksJsonPath)) {
11496
11589
  throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
11497
11590
  }
11498
11591
  const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
@@ -11507,7 +11600,7 @@ function readAssemblePlan(planDir, chunkPaths) {
11507
11600
  }
11508
11601
  }
11509
11602
  for (const path of chunkPaths.flat()) {
11510
- if (!existsSync20(path)) {
11603
+ if (!existsSync21(path)) {
11511
11604
  throw new Error(`[assemble] chunk path does not exist: ${path}`);
11512
11605
  }
11513
11606
  }
@@ -11536,7 +11629,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
11536
11629
  return args;
11537
11630
  }
11538
11631
  async function reencodeCfr(ctx, chunkPaths, encoder) {
11539
- const cfrOutputPath = join28(ctx.workDir, `cfr.${ctx.format}`);
11632
+ const cfrOutputPath = join29(ctx.workDir, `cfr.${ctx.format}`);
11540
11633
  const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
11541
11634
  appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
11542
11635
  cfrArgs.push("-y", cfrOutputPath);
@@ -11563,7 +11656,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
11563
11656
  );
11564
11657
  return;
11565
11658
  }
11566
- const paddedAudioPath = join28(ctx.workDir, "audio-padded.m4a");
11659
+ const paddedAudioPath = join29(ctx.workDir, "audio-padded.m4a");
11567
11660
  const padTrimResult = await ctx.timed(
11568
11661
  "audioMs",
11569
11662
  () => padOrTrimAudioToVideoFrameCount({
@@ -11603,9 +11696,9 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
11603
11696
  }
11604
11697
  }
11605
11698
  function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
11606
- const listPath = join28(workDir, "concat-list.txt");
11699
+ const listPath = join29(workDir, "concat-list.txt");
11607
11700
  const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
11608
- writeFileSync10(listPath, `${body}
11701
+ writeFileSync11(listPath, `${body}
11609
11702
  `, "utf-8");
11610
11703
  return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
11611
11704
  }
@@ -11641,12 +11734,12 @@ function stitchesSeparateAudio(planDir) {
11641
11734
  return lockedAudio(planDir).separateAudio === true;
11642
11735
  }
11643
11736
  function planFormat(planDir) {
11644
- const plan = JSON.parse(readFileSync10(join28(planDir, "plan.json"), "utf-8"));
11737
+ const plan = JSON.parse(readFileSync10(join29(planDir, "plan.json"), "utf-8"));
11645
11738
  return plan.dimensions.format;
11646
11739
  }
11647
11740
  function lockedAudio(planDir) {
11648
- const encoderJsonPath = join28(planDir, "meta", "encoder.json");
11649
- if (!existsSync20(encoderJsonPath)) return {};
11741
+ const encoderJsonPath = join29(planDir, "meta", "encoder.json");
11742
+ if (!existsSync21(encoderJsonPath)) return {};
11650
11743
  return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
11651
11744
  }
11652
11745
  function readCfrEncoder(planDir, format) {
@@ -11655,8 +11748,8 @@ function readCfrEncoder(planDir, format) {
11655
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.`
11656
11749
  );
11657
11750
  }
11658
- const encoderJsonPath = join28(planDir, "meta", "encoder.json");
11659
- if (!existsSync20(encoderJsonPath)) {
11751
+ const encoderJsonPath = join29(planDir, "meta", "encoder.json");
11752
+ if (!existsSync21(encoderJsonPath)) {
11660
11753
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
11661
11754
  }
11662
11755
  const encoderJson = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
@@ -11668,7 +11761,7 @@ function readCfrEncoder(planDir, format) {
11668
11761
  return encoderJson;
11669
11762
  }
11670
11763
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
11671
- if (existsSync20(outputPath)) rmSync10(outputPath, { recursive: true, force: true });
11764
+ if (existsSync21(outputPath)) rmSync10(outputPath, { recursive: true, force: true });
11672
11765
  mkdirSync13(outputPath, { recursive: true });
11673
11766
  let globalIdx = 0;
11674
11767
  for (const chunkDir of chunkPaths) {
@@ -11682,8 +11775,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11682
11775
  throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
11683
11776
  }
11684
11777
  for (const frame of frames) {
11685
- const dst = join28(outputPath, formatExportFrameName(globalIdx, "png"));
11686
- cpSync3(join28(chunkDir, frame), dst);
11778
+ const dst = join29(outputPath, formatExportFrameName(globalIdx, "png"));
11779
+ cpSync3(join29(chunkDir, frame), dst);
11687
11780
  globalIdx += 1;
11688
11781
  }
11689
11782
  }
@@ -11692,14 +11785,14 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11692
11785
  `[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
11693
11786
  );
11694
11787
  }
11695
- if (audioPath !== null && existsSync20(audioPath)) {
11696
- const sidecar = join28(outputPath, MIXED_AUDIO_FILENAME4);
11788
+ if (audioPath !== null && existsSync21(audioPath)) {
11789
+ const sidecar = join29(outputPath, MIXED_AUDIO_FILENAME4);
11697
11790
  cpSync3(audioPath, sidecar);
11698
11791
  }
11699
11792
  let fileSize = 0;
11700
11793
  for (const name of readdirSync8(outputPath)) {
11701
11794
  try {
11702
- fileSize += statSync8(join28(outputPath, name)).size;
11795
+ fileSize += statSync8(join29(outputPath, name)).size;
11703
11796
  } catch {
11704
11797
  }
11705
11798
  }
@@ -11713,9 +11806,9 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11713
11806
 
11714
11807
  // src/services/distributed/renderChunk.ts
11715
11808
  import { randomBytes } from "node:crypto";
11716
- import { existsSync as existsSync21, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
11809
+ import { existsSync as existsSync22, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
11717
11810
  import { cpus } from "node:os";
11718
- import { join as join29 } from "node:path";
11811
+ import { join as join30 } from "node:path";
11719
11812
  import {
11720
11813
  assertSwiftShader,
11721
11814
  BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
@@ -11723,7 +11816,7 @@ import {
11723
11816
  classifyCaptureFailure as classifyCaptureFailure4,
11724
11817
  closeCaptureSession as closeCaptureSession11,
11725
11818
  createCaptureSession as createCaptureSession11,
11726
- createFrameLookupTable as createFrameLookupTable5,
11819
+ createFrameLookupTable as createFrameLookupTable6,
11727
11820
  deriveBeginFrameProbeTimeTicks as deriveBeginFrameProbeTimeTicks2,
11728
11821
  getEncoderPreset as getEncoderPreset6,
11729
11822
  initializeSession as initializeSession10,
@@ -11839,7 +11932,7 @@ function hashChunkOutput(outputPath, kind) {
11839
11932
  if (kind === "file") return sha256Hex(readFileSync11(outputPath));
11840
11933
  const entries = readdirSync9(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
11841
11934
  const lines = entries.map(
11842
- (name) => `${name}\0${sha256Hex(readFileSync11(join29(outputPath, name)))}`
11935
+ (name) => `${name}\0${sha256Hex(readFileSync11(join30(outputPath, name)))}`
11843
11936
  );
11844
11937
  return sha256Hex(lines.join("\0"));
11845
11938
  }
@@ -11856,10 +11949,10 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
11856
11949
  async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11857
11950
  const start = Date.now();
11858
11951
  const log = defaultLogger;
11859
- const planJsonPath = join29(planDir, "plan.json");
11860
- const encoderJsonPath = join29(planDir, "meta", "encoder.json");
11861
- const chunksJsonPath = join29(planDir, "meta", "chunks.json");
11862
- if (!existsSync21(planJsonPath)) {
11952
+ const planJsonPath = join30(planDir, "plan.json");
11953
+ const encoderJsonPath = join30(planDir, "meta", "encoder.json");
11954
+ const chunksJsonPath = join30(planDir, "meta", "chunks.json");
11955
+ if (!existsSync22(planJsonPath)) {
11863
11956
  throw new RenderChunkValidationError(
11864
11957
  MISSING_PLAN_ARTIFACT,
11865
11958
  `[renderChunk] planDir is missing required artifact: ${planJsonPath}`
@@ -11875,7 +11968,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11875
11968
  const v2Manifest = v2Materialization?.manifest ?? null;
11876
11969
  const remoteSources = v2Materialization?.remoteSources ?? {};
11877
11970
  for (const required of [encoderJsonPath, chunksJsonPath]) {
11878
- if (!existsSync21(required)) {
11971
+ if (!existsSync22(required)) {
11879
11972
  throw new RenderChunkValidationError(
11880
11973
  MISSING_PLAN_ARTIFACT,
11881
11974
  `[renderChunk] planDir is missing required artifact: ${required}`
@@ -11884,9 +11977,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11884
11977
  }
11885
11978
  const encoder = JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
11886
11979
  const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
11887
- const videosJsonPath = join29(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
11980
+ const videosJsonPath = join30(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
11888
11981
  let planVideos = null;
11889
- if (existsSync21(videosJsonPath)) {
11982
+ if (existsSync22(videosJsonPath)) {
11890
11983
  try {
11891
11984
  planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync11(videosJsonPath, "utf-8")));
11892
11985
  } catch (err) {
@@ -11916,8 +12009,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11916
12009
  }
11917
12010
  const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
11918
12011
  const framesInChunk = range.endFrame - range.startFrame;
11919
- const compiledDir = join29(planDir, "compiled");
11920
- if (!existsSync21(compiledDir)) {
12012
+ const compiledDir = join30(planDir, "compiled");
12013
+ if (!existsSync22(compiledDir)) {
11921
12014
  throw new RenderChunkValidationError(
11922
12015
  MISSING_PLAN_ARTIFACT,
11923
12016
  `[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
@@ -11974,13 +12067,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11974
12067
  };
11975
12068
  const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
11976
12069
  mkdirSync14(workDir, { recursive: true });
11977
- const framesDir = join29(workDir, "captured-frames");
12070
+ const framesDir = join30(workDir, "captured-frames");
11978
12071
  mkdirSync14(framesDir, { recursive: true });
11979
12072
  const videoFramesDir = `${workDir}.video-frames`;
11980
12073
  const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
11981
12074
  const chunkFps = { num: plan.dimensions.fpsNum, den: plan.dimensions.fpsDen };
11982
12075
  const deferredVideos = planVideos?.extracted.filter((video) => video.deferredRange) ?? [];
11983
- const shownFrames = planVideos ? shownDeferredFrameIndexes(planVideos, chunkFrameNumbers, chunkFps) : /* @__PURE__ */ new Map();
12076
+ const shownFrames = planVideos ? shownPlanFrameIndexes(planVideos, chunkFrameNumbers, chunkFps) : /* @__PURE__ */ new Map();
11984
12077
  const remoteVideoIds = new Set(
11985
12078
  planVideos?.extracted.filter(
11986
12079
  (video) => video.deferredRange && Object.hasOwn(remoteSources, video.deferredRange.sourcePath)
@@ -11999,7 +12092,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11999
12092
  dimensions: plan.dimensions,
12000
12093
  encoder,
12001
12094
  videoCount: planVideos?.videos.length ?? 0,
12002
- html: readFileSync11(join29(compiledDir, "index.html"), "utf-8"),
12095
+ html: readFileSync11(join30(compiledDir, "index.html"), "utf-8"),
12003
12096
  // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
12004
12097
  skipReadinessVideoIds: deferredVideos.filter(
12005
12098
  (video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
@@ -12134,7 +12227,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
12134
12227
  deferredVideoFrames.catch(() => {
12135
12228
  });
12136
12229
  const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? deferredVideoFrames.then(
12137
- (deferred) => createFrameLookupTable5(
12230
+ (deferred) => createFrameLookupTable6(
12138
12231
  planVideos.videos,
12139
12232
  [
12140
12233
  ...rebuildExtractedFramesFromPlanDir(
@@ -12167,10 +12260,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
12167
12260
  const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
12168
12261
  const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
12169
12262
  if (isPngSequence) {
12170
- if (!existsSync21(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
12263
+ if (!existsSync22(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
12171
12264
  } else {
12172
- const outDir = join29(outputChunkPath, "..");
12173
- if (!existsSync21(outDir)) mkdirSync14(outDir, { recursive: true });
12265
+ const outDir = join30(outputChunkPath, "..");
12266
+ if (!existsSync22(outDir)) mkdirSync14(outDir, { recursive: true });
12174
12267
  }
12175
12268
  const encodeInput = {
12176
12269
  job,
@@ -12380,7 +12473,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
12380
12473
  producerVersion: plan.producerVersion,
12381
12474
  ffmpegVersion
12382
12475
  };
12383
- writeFileSync11(perfPath, `${JSON.stringify(perfPayload, null, 2)}
12476
+ writeFileSync12(perfPath, `${JSON.stringify(perfPayload, null, 2)}
12384
12477
  `, "utf-8");
12385
12478
  try {
12386
12479
  rmSync11(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
@@ -12413,8 +12506,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
12413
12506
 
12414
12507
  // src/services/distributed/planV2Execution.ts
12415
12508
  async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
12416
- const workRoot = mkdtempSync6(join30(tmpdir2(), "hf-plan-v2-chunk-"));
12417
- const materializedPlanDir = join30(workRoot, "plan");
12509
+ const workRoot = mkdtempSync6(join31(tmpdir2(), "hf-plan-v2-chunk-"));
12510
+ const materializedPlanDir = join31(workRoot, "plan");
12418
12511
  try {
12419
12512
  materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
12420
12513
  return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
@@ -12423,8 +12516,8 @@ async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
12423
12516
  }
12424
12517
  }
12425
12518
  async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
12426
- const workRoot = mkdtempSync6(join30(tmpdir2(), "hf-plan-v2-assembler-"));
12427
- const materializedPlanDir = join30(workRoot, "plan");
12519
+ const workRoot = mkdtempSync6(join31(tmpdir2(), "hf-plan-v2-assembler-"));
12520
+ const materializedPlanDir = join31(workRoot, "plan");
12428
12521
  try {
12429
12522
  const materialized = materializePlanV2Target(
12430
12523
  planV2Dir,
@@ -12716,7 +12809,7 @@ function validateIntDimension(field, value) {
12716
12809
  // src/services/distributed/projectHash.ts
12717
12810
  import { closeSync as closeSync5, openSync as openSync5, readdirSync as readdirSync10, readSync as readSync4, realpathSync as realpathSync2, statSync as statSync9 } from "node:fs";
12718
12811
  import { createHash as createHash8 } from "node:crypto";
12719
- import { join as join31 } from "node:path";
12812
+ import { join as join32 } from "node:path";
12720
12813
  var SITE_ID_SCHEME = "site-v2\0";
12721
12814
  var READ_CHUNK_BYTES = 256 * 1024;
12722
12815
  function listSiteFiles(projectDir) {
@@ -12725,7 +12818,7 @@ function listSiteFiles(projectDir) {
12725
12818
  function walk(dir, rel) {
12726
12819
  for (const name of readdirSync10(dir).sort()) {
12727
12820
  if (rel === "" && PLAN_PROJECT_DIR_SKIP_SEGMENTS.has(name)) continue;
12728
- const full = join31(dir, name);
12821
+ const full = join32(dir, name);
12729
12822
  const relPath = rel === "" ? name : `${rel}/${name}`;
12730
12823
  let stat;
12731
12824
  try {
@@ -12755,7 +12848,7 @@ function hashProjectDir(projectDir, files = listSiteFiles(projectDir)) {
12755
12848
  for (const rel of files) {
12756
12849
  hash.update(rel);
12757
12850
  hash.update("\0");
12758
- const fd = openSync5(join31(projectDir, rel), "r");
12851
+ const fd = openSync5(join32(projectDir, rel), "r");
12759
12852
  try {
12760
12853
  let read;
12761
12854
  while ((read = readSync4(fd, chunk, 0, chunk.length, null)) > 0) {
@@ -12797,7 +12890,7 @@ var isMissing = (result) => result.error?.code === "ENOENT";
12797
12890
  var ffmpegIsMissing = async (signal) => isMissing(await runFfmpeg9(["-version"], { signal }));
12798
12891
 
12799
12892
  // src/services/distributed/probeFacts.ts
12800
- import { writeFileSync as writeFileSync12 } from "node:fs";
12893
+ import { writeFileSync as writeFileSync13 } from "node:fs";
12801
12894
  import { extname as extname6 } from "node:path";
12802
12895
  import { buildProbeFactsText } from "@hyperframes/engine";
12803
12896
  var PROBE_FACTS_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v", ".webm", ".mkv"]);
@@ -12806,7 +12899,7 @@ function wantsProbeFacts(path) {
12806
12899
  }
12807
12900
  async function buildProbeFacts(inputPath, outputPath, signal) {
12808
12901
  try {
12809
- writeFileSync12(outputPath, await buildProbeFactsText(inputPath, signal));
12902
+ writeFileSync13(outputPath, await buildProbeFactsText(inputPath, signal));
12810
12903
  return "built";
12811
12904
  } catch (error) {
12812
12905
  if (signal?.aborted) throw error;