@hyperframes/producer 0.9.19 → 0.9.20

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 existsSync18,
11
+ existsSync as existsSync19,
12
12
  linkSync,
13
13
  mkdirSync as mkdirSync10,
14
14
  rmSync as rmSync7,
15
15
  writeFileSync as writeFileSync8
16
16
  } from "node:fs";
17
- import { extname as extname4, isAbsolute as isAbsolute3, join as join25, 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 sep3 } from "node:path";
18
18
  import {
19
19
  fpsToNumber as fpsToNumber9,
20
20
  HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
@@ -71,7 +71,7 @@ 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 existsSync13,
74
+ existsSync as existsSync14,
75
75
  mkdirSync as mkdirSync6,
76
76
  readdirSync as readdirSync3,
77
77
  rmSync as rmSync5,
@@ -887,7 +887,7 @@ function chunkGopFrames(framesInChunk, intervalFrames) {
887
887
  import {
888
888
  copyFileSync,
889
889
  cpSync,
890
- existsSync as existsSync3,
890
+ existsSync as existsSync4,
891
891
  mkdirSync,
892
892
  rmSync,
893
893
  symlinkSync,
@@ -907,7 +907,9 @@ import {
907
907
  relative as nodeRelative,
908
908
  isAbsolute as nodeIsAbsolute
909
909
  } from "node:path";
910
+ import { existsSync as existsSync3 } from "node:fs";
910
911
  import { fileURLToPath as fileURLToPath2 } from "node:url";
912
+ import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
911
913
  var DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? // fileURLToPath (not URL.pathname): on Windows .pathname is "/D:/..." which
912
914
  // resolves to a bogus renders dir.
913
915
  nodeResolve(fileURLToPath2(import.meta.url), "../../..", "renders");
@@ -938,6 +940,13 @@ function formatCaptureFrameName(index, ext) {
938
940
  function formatExportFrameName(index, ext) {
939
941
  return `frame_${String(index + 1).padStart(6, "0")}.${ext}`;
940
942
  }
943
+ function resolveCompositionSrc(projectDir, src) {
944
+ const rooted = (value) => nodeResolve(projectDir, value.replace(/^(?:\.?[\\/])+/, ""));
945
+ const decoded = rooted(decodedUrlPath(src));
946
+ if (!isPathInside2(decoded, projectDir)) return null;
947
+ const raw = rooted(src);
948
+ return !existsSync3(decoded) && isPathInside2(raw, projectDir) && existsSync3(raw) ? raw : decoded;
949
+ }
941
950
 
942
951
  // src/services/render/shared.ts
943
952
  var BROWSER_MEDIA_EPSILON = 1e-4;
@@ -1116,7 +1125,7 @@ var materializePathModule = {
1116
1125
  isAbsolute
1117
1126
  };
1118
1127
  var materializeFileSystem = {
1119
- existsSync: existsSync3,
1128
+ existsSync: existsSync4,
1120
1129
  mkdirSync,
1121
1130
  symlinkSync,
1122
1131
  cpSync,
@@ -1455,11 +1464,80 @@ function createStallClock(now = () => Date.now()) {
1455
1464
 
1456
1465
  // src/services/render/stages/captureStreamingStage.ts
1457
1466
  var DEFAULT_CAPTURE_STALL_MS = 6e4;
1467
+ var SESSION_INIT_STALL_MS = 3e5;
1468
+ var DE_STALL_POLL_MS = 5e3;
1469
+ var STALL_ADVICE = "Chrome stopped answering. If another app is keeping the GPU or CPU busy (a 3D render, a game, another export), let it finish or render one job at a time, then retry.";
1458
1470
  function resolveCaptureStallTimeoutMs() {
1459
1471
  const raw = process.env.HF_DE_STALL_MS ?? process.env.HF_DE_PARALLEL_STALL_MS;
1460
1472
  const parsed = raw ? Number(raw) : Number.NaN;
1461
1473
  return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CAPTURE_STALL_MS;
1462
1474
  }
1475
+ function captureModeLabel(mode) {
1476
+ if (mode === "drawelement") return "drawElement";
1477
+ if (mode === "beginframe") return "BeginFrame";
1478
+ return "screenshot";
1479
+ }
1480
+ var SequentialCaptureStallError = class extends Error {
1481
+ captureMode;
1482
+ constructor(session, stallTimeoutMs, frameIndex, totalFrames) {
1483
+ const step = session.captureStep ? ` while ${session.captureStep}` : "";
1484
+ super(
1485
+ `[Render] Sequential ${captureModeLabel(session.captureMode)} capture stalled: no frame progress for ${stallTimeoutMs}ms (stuck at frame ${frameIndex}/${totalFrames}${step}). ` + STALL_ADVICE
1486
+ );
1487
+ this.name = "SequentialCaptureStallError";
1488
+ this.captureMode = session.captureMode;
1489
+ }
1490
+ };
1491
+ function raceAgainstStall(promise, deadlineMs, input, signal) {
1492
+ return new Promise((resolve10, reject) => {
1493
+ const clock = createStallClock();
1494
+ const trip = () => reject(
1495
+ signal?.aborted ? new Error(
1496
+ `[Render] Sequential ${captureModeLabel(input.session.captureMode)} capture aborted while a capture call was in flight (frame ${input.frameIndex}/${input.totalFrames}).`
1497
+ ) : new SequentialCaptureStallError(
1498
+ input.session,
1499
+ input.stallTimeoutMs,
1500
+ input.frameIndex,
1501
+ input.totalFrames
1502
+ )
1503
+ );
1504
+ const deadline = Math.max(0, deadlineMs);
1505
+ let wait = Math.min(deadline, DE_STALL_POLL_MS);
1506
+ let timer;
1507
+ const look = () => {
1508
+ const idle = clock.idle(wait);
1509
+ if (idle >= deadline) return trip();
1510
+ wait = Math.min(deadline - idle, DE_STALL_POLL_MS);
1511
+ timer = setTimeout(look, wait);
1512
+ };
1513
+ timer = setTimeout(look, wait);
1514
+ promise.then(
1515
+ (value) => {
1516
+ clearTimeout(timer);
1517
+ resolve10(value);
1518
+ },
1519
+ (error) => {
1520
+ clearTimeout(timer);
1521
+ reject(error);
1522
+ }
1523
+ );
1524
+ });
1525
+ }
1526
+ function createSessionInitBudget(input) {
1527
+ const initStallMs = Math.max(SESSION_INIT_STALL_MS, resolveCaptureStallTimeoutMs());
1528
+ const setup = createStallClock();
1529
+ return (work, stalled) => raceAgainstStall(
1530
+ work,
1531
+ initStallMs - setup.idle(0),
1532
+ {
1533
+ session: stalled,
1534
+ frameIndex: input.frameIndex,
1535
+ totalFrames: input.totalFrames,
1536
+ stallTimeoutMs: initStallMs
1537
+ },
1538
+ input.abortSignal
1539
+ );
1540
+ }
1463
1541
 
1464
1542
  // src/services/render/captureCost.ts
1465
1543
  import { fpsToNumber as fpsToNumber4 } from "@hyperframes/core";
@@ -1674,7 +1752,7 @@ var ALLOWED_STRING_DATA_KEYS = /* @__PURE__ */ new Set([
1674
1752
  "stagePhase",
1675
1753
  // Full-fidelity fast-capture fallback trigger — the specific reason
1676
1754
  // drawElement gated off ("filter:blur", "filter:drop-shadow",
1677
- // "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", …).
1755
+ // "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", "webgl_unavailable", …).
1678
1756
  // Populated on the `capture_fallback_profile` checkpoint emitted by
1679
1757
  // `fallbackCaptureProfile.ts` when the operator opts into
1680
1758
  // `HF_PROFILE_FALLBACK_CAPTURE=true` so downstream metric consumers can
@@ -1993,17 +2071,17 @@ import {
1993
2071
  } from "@hyperframes/engine";
1994
2072
 
1995
2073
  // src/services/render/stages/compileStage.ts
1996
- import { existsSync as existsSync7, writeFileSync as writeFileSync3 } from "node:fs";
2074
+ import { existsSync as existsSync8, writeFileSync as writeFileSync3 } from "node:fs";
1997
2075
  import { join as join8 } from "node:path";
1998
2076
  import { suggestMatchingPreset } from "@hyperframes/core";
1999
2077
  import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
2000
2078
  import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
2001
2079
 
2002
2080
  // src/services/htmlCompiler.ts
2003
- import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
2081
+ import { decodedUrlPath as decodedUrlPath2 } from "@hyperframes/parsers/asset-paths";
2004
2082
  import { createHash as createHash4 } from "node:crypto";
2005
- import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
2006
- import { join as join7, dirname as dirname6, resolve as resolve6, basename as basename3, relative as relative2 } from "path";
2083
+ import { createReadStream as createReadStream2, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
2084
+ import { join as join7, dirname as dirname6, resolve as resolve6, basename as basename3, relative as relative2, sep as sep2 } from "path";
2007
2085
  import { parseHTML as parseHTML5 } from "linkedom";
2008
2086
  import {
2009
2087
  COMPOSITION_SOURCE_URL,
@@ -2249,7 +2327,7 @@ import {
2249
2327
 
2250
2328
  // src/services/animatedGifPrep.ts
2251
2329
  import { createHash as createHash2 } from "node:crypto";
2252
- import { copyFileSync as copyFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
2330
+ import { copyFileSync as copyFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
2253
2331
  import { dirname as dirname4, isAbsolute as isAbsolute2, join as join5, resolve as resolve4 } from "node:path";
2254
2332
  import { parseHTML as parseHTML3 } from "linkedom";
2255
2333
  import { parseAnimatedGifMetadata } from "@hyperframes/core";
@@ -2288,14 +2366,14 @@ function resolveLocalImageSourcePath(src, options) {
2288
2366
  const { basePath } = splitUrlSuffix(trimmed);
2289
2367
  const normalizedBase = normalizeRelPath(basePath);
2290
2368
  const mapped = options.sourceAssets?.get(trimmed) ?? options.sourceAssets?.get(basePath) ?? options.sourceAssets?.get(normalizedBase);
2291
- if (mapped && existsSync4(mapped)) return mapped;
2369
+ if (mapped && existsSync5(mapped)) return mapped;
2292
2370
  if (isHttpUrl(trimmed)) return null;
2293
2371
  const projectRelative = basePath.startsWith("/") ? basePath.slice(1) : basePath;
2294
2372
  const candidates = [
2295
2373
  isAbsolute2(basePath) ? basePath : resolve4(options.projectDir, projectRelative),
2296
2374
  resolve4(options.downloadDir, normalizedBase)
2297
2375
  ];
2298
- return candidates.find((candidate) => existsSync4(candidate)) ?? null;
2376
+ return candidates.find((candidate) => existsSync5(candidate)) ?? null;
2299
2377
  }
2300
2378
  function isUsableFile(path) {
2301
2379
  try {
@@ -2406,10 +2484,10 @@ async function runAnimatedGifTranscode(request) {
2406
2484
  throw result.error ?? new Error(`Animated GIF transcode failed (${result.exitCode}): ${result.stderr.slice(-500)}`);
2407
2485
  }
2408
2486
  async function ensurePreparedWebm(input) {
2409
- if (!existsSync4(dirname4(input.cachePath))) {
2487
+ if (!existsSync5(dirname4(input.cachePath))) {
2410
2488
  mkdirSync2(dirname4(input.cachePath), { recursive: true });
2411
2489
  }
2412
- if (!existsSync4(dirname4(input.outputPath))) {
2490
+ if (!existsSync5(dirname4(input.outputPath))) {
2413
2491
  mkdirSync2(dirname4(input.outputPath), { recursive: true });
2414
2492
  }
2415
2493
  if (!isUsableFile(input.cachePath)) {
@@ -2884,7 +2962,7 @@ import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-serve
2884
2962
  import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
2885
2963
 
2886
2964
  // src/services/assetMediaType.ts
2887
- import { existsSync as existsSync5 } from "node:fs";
2965
+ import { existsSync as existsSync6 } from "node:fs";
2888
2966
  import {
2889
2967
  fingerprintElementId,
2890
2968
  isNotMediaPayload,
@@ -2957,7 +3035,7 @@ async function preflightCompositionAssetMediaTypes(input) {
2957
3035
  input.projectDir,
2958
3036
  input.compiledDir
2959
3037
  );
2960
- if (!existsSync5(resolvedPath)) continue;
3038
+ if (!existsSync6(resolvedPath)) continue;
2961
3039
  byPath.set(resolvedPath, [...byPath.get(resolvedPath) ?? [], reference]);
2962
3040
  }
2963
3041
  const mismatches = [];
@@ -3045,6 +3123,14 @@ Check that each file referenced by data-composition-src contains valid HTML with
3045
3123
  this.problems = problems;
3046
3124
  }
3047
3125
  };
3126
+ function normalizeCompositionSrcs(html, projectDir) {
3127
+ return html.replace(/(\bdata-composition-src=)(["'])([^"']*)\2/g, (whole, name, quote, value) => {
3128
+ if (!value.includes("%") && !value.startsWith("/")) return whole;
3129
+ const file = resolveCompositionSrc(projectDir, value);
3130
+ if (file === null) return whole;
3131
+ return `${name}${quote}${relative2(projectDir, file).split(sep2).join("/")}${quote}`;
3132
+ });
3133
+ }
3048
3134
  function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */ new Set()) {
3049
3135
  const { document: document2 } = parseHTML5(html);
3050
3136
  const hosts = [...document2.querySelectorAll("[data-composition-src]")];
@@ -3053,7 +3139,11 @@ function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */
3053
3139
  const srcPath = el.getAttribute("data-composition-src");
3054
3140
  if (!srcPath) continue;
3055
3141
  if (isUnresolvedAssetPlaceholder(srcPath)) continue;
3056
- const filePath = resolve6(projectDir, srcPath);
3142
+ const filePath = resolveCompositionSrc(projectDir, srcPath);
3143
+ if (filePath === null) {
3144
+ problems.push({ srcPath, detail: "it is outside the project folder" });
3145
+ continue;
3146
+ }
3057
3147
  if (visited.has(filePath)) continue;
3058
3148
  const read = readProjectFile(filePath);
3059
3149
  if (read.kind === "missing") {
@@ -3199,7 +3289,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
3199
3289
  let filePath = src;
3200
3290
  if (isHttpUrl(src)) {
3201
3291
  const remoteDir = join7(downloadDir, REMOTE_MEDIA_SUBDIR);
3202
- if (!existsSync6(remoteDir)) mkdirSync4(remoteDir, { recursive: true });
3292
+ if (!existsSync7(remoteDir)) mkdirSync4(remoteDir, { recursive: true });
3203
3293
  try {
3204
3294
  filePath = await downloadToTemp(src, remoteDir, void 0, void 0, void 0, {
3205
3295
  onTelemetry: logRemoteDownloadTelemetry
@@ -3208,11 +3298,11 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
3208
3298
  return { duration: null, resolvedPath: src };
3209
3299
  }
3210
3300
  } else {
3211
- filePath = decodedUrlPath(src);
3301
+ filePath = decodedUrlPath2(src);
3212
3302
  if (!filePath.startsWith("/")) filePath = join7(baseDir, filePath);
3213
3303
  filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
3214
3304
  }
3215
- if (!isHttpUrl(filePath) && !existsSync6(filePath)) {
3305
+ if (!isHttpUrl(filePath) && !existsSync7(filePath)) {
3216
3306
  return { duration: null, resolvedPath: filePath };
3217
3307
  }
3218
3308
  const withSrcContext = (error) => {
@@ -3353,7 +3443,8 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
3353
3443
  for (const el of compEls) {
3354
3444
  const srcPath = el.getAttribute("data-composition-src");
3355
3445
  if (!srcPath) continue;
3356
- const filePath = resolve6(projectDir, srcPath);
3446
+ const filePath = resolveCompositionSrc(projectDir, srcPath);
3447
+ if (filePath === null) continue;
3357
3448
  if (visited.has(filePath)) {
3358
3449
  continue;
3359
3450
  }
@@ -3361,7 +3452,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
3361
3452
  if (read.kind !== "file") {
3362
3453
  continue;
3363
3454
  }
3364
- const rawSubHtml = read.text;
3455
+ const rawSubHtml = normalizeCompositionSrcs(read.text, projectDir);
3365
3456
  const nestedVisited = new Set(visited);
3366
3457
  nestedVisited.add(filePath);
3367
3458
  workItems.push({ srcPath, filePath, rawSubHtml, nestedVisited });
@@ -3549,8 +3640,9 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
3549
3640
  resolveHtml: (srcPath) => {
3550
3641
  let compHtml = subCompositions.get(srcPath) || null;
3551
3642
  if (!compHtml) {
3552
- const read = readProjectFile(resolve6(projectDir, srcPath));
3553
- if (read.kind === "file") {
3643
+ const filePath = resolveCompositionSrc(projectDir, srcPath);
3644
+ const read = filePath ? readProjectFile(filePath) : null;
3645
+ if (read?.kind === "file") {
3554
3646
  compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
3555
3647
  }
3556
3648
  }
@@ -3559,7 +3651,7 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
3559
3651
  parseHtml: (htmlStr) => parseHTML5(htmlStr).document,
3560
3652
  // Mirrors the preview bundler: a sub-composition's SIBLING assets resolve
3561
3653
  // against its own directory, project-root refs stay as authored.
3562
- assetExists: (path) => existsSync6(resolve6(projectDir, path)),
3654
+ assetExists: (path) => existsSync7(resolve6(projectDir, path)),
3563
3655
  // Preserve the authored root wrapper as a child of the host, matching
3564
3656
  // the preview bundler's shape (htmlBundler.ts's prepareFlattenedInnerRoot,
3565
3657
  // which the runtime compositionLoader mirrors with its own copy for the
@@ -3750,7 +3842,7 @@ function collectExternalAssets(html, projectDir) {
3750
3842
  if (isPathInside2(absPath, absProjectDir)) {
3751
3843
  return null;
3752
3844
  }
3753
- if (!existsSync6(absPath)) return null;
3845
+ if (!existsSync7(absPath)) return null;
3754
3846
  const safeKey = toExternalAssetKey(absPath);
3755
3847
  externalAssets.set(safeKey, absPath);
3756
3848
  return safeKey;
@@ -3800,7 +3892,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
3800
3892
  var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
3801
3893
  async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
3802
3894
  if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
3803
- if (!existsSync6(remoteDir)) mkdirSync4(remoteDir, { recursive: true });
3895
+ if (!existsSync7(remoteDir)) mkdirSync4(remoteDir, { recursive: true });
3804
3896
  const urlToLocal = /* @__PURE__ */ new Map();
3805
3897
  await Promise.all(
3806
3898
  [...urlSet].map(async (url) => {
@@ -3982,14 +4074,14 @@ function projectStylesheetFontFamilies(html, projectDir) {
3982
4074
  for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
3983
4075
  const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
3984
4076
  if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
3985
- const sheet = resolve6(projectDir, decodedUrlPath(href).replace(/^(?:\.?\/)+/, ""));
3986
- if (!isPathInside2(sheet, projectDir) || !existsSync6(sheet)) continue;
4077
+ const sheet = resolve6(projectDir, decodedUrlPath2(href).replace(/^(?:\.?\/)+/, ""));
4078
+ if (!isPathInside2(sheet, projectDir) || !existsSync7(sheet)) continue;
3987
4079
  for (const face of extractFontFaceBlocks(readFileSync4(sheet, "utf8"))) {
3988
4080
  const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
3989
4081
  const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
3990
- const path = decodedUrlPath(url);
4082
+ const path = decodedUrlPath2(url);
3991
4083
  const file = path.startsWith("/") ? resolve6(projectDir, `.${path}`) : resolve6(dirname6(sheet), path);
3992
- return isPathInside2(file, projectDir) && existsSync6(file);
4084
+ return isPathInside2(file, projectDir) && existsSync7(file);
3993
4085
  });
3994
4086
  if (family && shipsFile) families.push(family);
3995
4087
  }
@@ -4124,7 +4216,7 @@ function renderGsapDist() {
4124
4216
  }
4125
4217
  function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
4126
4218
  let gsapDist;
4127
- const exists = (path) => existsSync6(resolve6(projectDir, path));
4219
+ const exists = (path) => existsSync7(resolve6(projectDir, path));
4128
4220
  return html.replace(
4129
4221
  /(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
4130
4222
  (full, prefix, src, file, suffix) => {
@@ -4141,7 +4233,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
4141
4233
  const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
4142
4234
  if (!entryPath.includes("/")) return html;
4143
4235
  const { document: document2 } = parseHTML5(html);
4144
- const assetExists = (path) => existsSync6(resolve6(projectDir, path));
4236
+ const assetExists = (path) => existsSync7(resolve6(projectDir, path));
4145
4237
  rewriteAssetPaths(
4146
4238
  document2.querySelectorAll("[src], [href], [srcset]"),
4147
4239
  entryPath,
@@ -4163,7 +4255,10 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
4163
4255
  }
4164
4256
  async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
4165
4257
  const entryHtml = rebaseDirectEntryAssetPaths(readTextFile(htmlPath), projectDir, htmlPath);
4166
- const rawHtml = rewriteUnresolvableGsapToCdn(entryHtml, projectDir);
4258
+ const rawHtml = normalizeCompositionSrcs(
4259
+ rewriteUnresolvableGsapToCdn(entryHtml, projectDir),
4260
+ projectDir
4261
+ );
4167
4262
  assertSubCompositionsUsable(rawHtml, projectDir);
4168
4263
  const probes = /* @__PURE__ */ new Map();
4169
4264
  const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
@@ -4708,7 +4803,7 @@ function resolveEntryHtmlPath(input) {
4708
4803
  const rawEntry = readTextFile2(htmlPath);
4709
4804
  if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
4710
4805
  const projectIndexPath = join8(projectDir, "index.html");
4711
- if (!existsSync7(projectIndexPath)) {
4806
+ if (!existsSync8(projectIndexPath)) {
4712
4807
  throw new Error(
4713
4808
  `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
4714
4809
  );
@@ -5574,7 +5669,7 @@ function validateDistributedDuration(input) {
5574
5669
  }
5575
5670
 
5576
5671
  // src/services/render/stages/extractVideosStage.ts
5577
- import { existsSync as existsSync8 } from "node:fs";
5672
+ import { existsSync as existsSync9 } from "node:fs";
5578
5673
  import { join as join10 } from "node:path";
5579
5674
  import {
5580
5675
  classifyVideoExtractionError,
@@ -5849,7 +5944,7 @@ async function runExtractVideosStage(input) {
5849
5944
  const probeFailures = await Promise.all(
5850
5945
  composition.videos.map(async (v) => {
5851
5946
  const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
5852
- if (!existsSync8(videoPath)) return null;
5947
+ if (!existsSync9(videoPath)) return null;
5853
5948
  try {
5854
5949
  const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry2(() => extractMediaMetadata3(videoPath), {
5855
5950
  signal: abortSignal,
@@ -5887,7 +5982,7 @@ async function runExtractVideosStage(input) {
5887
5982
  const probed = await Promise.all(
5888
5983
  composition.images.map(async (img) => {
5889
5984
  const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
5890
- if (!existsSync8(imgPath)) return null;
5985
+ if (!existsSync9(imgPath)) return null;
5891
5986
  const colorSpace = await probeColorSpaceSafely(imgPath, log);
5892
5987
  if (isHdrColorSpace2(colorSpace)) {
5893
5988
  nativeHdrImageIds.add(img.id);
@@ -6007,7 +6102,7 @@ async function appendVideoAudioFromProbe(input) {
6007
6102
  videos.map(async (video) => {
6008
6103
  const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
6009
6104
  const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
6010
- if (!isHttpUrl2(readFrom) && !existsSync8(readFrom)) return null;
6105
+ if (!isHttpUrl2(readFrom) && !existsSync9(readFrom)) return null;
6011
6106
  const metadata = await extractMediaMetadata3(readFrom);
6012
6107
  const window2 = await resolveFinalFrameExtractionWindow2(
6013
6108
  readFrom,
@@ -6035,7 +6130,7 @@ import {
6035
6130
  } from "@hyperframes/engine";
6036
6131
 
6037
6132
  // src/services/render/stages/audioStage.ts
6038
- import { existsSync as existsSync10 } from "node:fs";
6133
+ import { existsSync as existsSync11 } from "node:fs";
6039
6134
  import { join as join13 } from "node:path";
6040
6135
  import {
6041
6136
  MIXED_AUDIO_FILENAME,
@@ -6046,7 +6141,7 @@ import {
6046
6141
  } from "@hyperframes/engine";
6047
6142
 
6048
6143
  // src/services/render/audioDeliveryLimit.ts
6049
- import { existsSync as existsSync9 } from "node:fs";
6144
+ import { existsSync as existsSync10 } from "node:fs";
6050
6145
  import { dirname as dirname8, join as join12 } from "node:path";
6051
6146
  import {
6052
6147
  applyAudioFxChain,
@@ -6555,7 +6650,7 @@ async function limitDeliveryPeaks(input, deps = {
6555
6650
  applyChain: applyAudioFxChain,
6556
6651
  encode: encodeAac2,
6557
6652
  rename: renameWithRetry6,
6558
- sourceExists: existsSync9
6653
+ sourceExists: existsSync10
6559
6654
  }) {
6560
6655
  const { audioPath, sourcePath, signal, timeoutMs } = input;
6561
6656
  if (!deps.sourceExists(sourcePath)) return { success: true };
@@ -6650,7 +6745,7 @@ async function runAudioStageScoped(input) {
6650
6745
  );
6651
6746
  audioLoweredDb = guard.loweredDb;
6652
6747
  input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
6653
- if (input.keepDeliverySource && !guard.sourceStale && existsSync10(pcmSourcePath)) {
6748
+ if (input.keepDeliverySource && !guard.sourceStale && existsSync11(pcmSourcePath)) {
6654
6749
  deliverySourcePath = pcmSourcePath;
6655
6750
  }
6656
6751
  }
@@ -6778,7 +6873,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
6778
6873
  }
6779
6874
 
6780
6875
  // src/services/render/stages/captureStage.ts
6781
- import { existsSync as existsSync11, readdirSync, statfsSync } from "node:fs";
6876
+ import { existsSync as existsSync12, readdirSync, statfsSync } from "node:fs";
6782
6877
  import { join as join14 } from "node:path";
6783
6878
  import {
6784
6879
  classifyCaptureFailure as classifyCaptureFailure2,
@@ -6830,7 +6925,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
6830
6925
  }
6831
6926
  function measureCaptureFrameBytes(workDir, framesDir) {
6832
6927
  let total = sampleDirectoryBytes(framesDir);
6833
- if (!existsSync11(workDir)) return total;
6928
+ if (!existsSync12(workDir)) return total;
6834
6929
  for (const attempt of readdirSync(workDir)) {
6835
6930
  if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
6836
6931
  total += sampleDirectoryBytes(join14(workDir, attempt));
@@ -7015,7 +7110,11 @@ async function runSequentialDiskCapture(input, captureOptions, captureCfg, check
7015
7110
  prepareCaptureSessionForReuse3(session, framesDir, videoInjector);
7016
7111
  probeSession = null;
7017
7112
  }
7018
- await ensureSessionReady(session);
7113
+ await ensureSessionReady(session, {
7114
+ frameIndex: resumeFrom,
7115
+ totalFrames,
7116
+ abortSignal: input.abortSignal
7117
+ });
7019
7118
  assertAttemptActive();
7020
7119
  lastBrowserConsole = session.browserConsoleBuffer;
7021
7120
  await captureSessionFrames(session, {
@@ -7067,13 +7166,13 @@ function planTransientResume(error, ctx) {
7067
7166
  if (!firstMissing) return null;
7068
7167
  return isTransientCaptureRetryEligible(failure.kind, missing, ctx.retriesUsed) ? firstMissing.startFrame : null;
7069
7168
  }
7070
- async function ensureSessionReady(session) {
7169
+ async function ensureSessionReady(session, budget) {
7071
7170
  if (!session.isInitialized) {
7072
7171
  await initializeSession5(session);
7073
7172
  return;
7074
7173
  }
7075
7174
  if (process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true") {
7076
- await completeDeferredDrawElementInit3(session);
7175
+ await createSessionInitBudget(budget)(completeDeferredDrawElementInit3(session), session);
7077
7176
  }
7078
7177
  }
7079
7178
  async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resumeFrom, checkDiskProjection }) {
@@ -7167,7 +7266,7 @@ import {
7167
7266
  // src/services/render/stages/encodeStage.ts
7168
7267
  import {
7169
7268
  copyFileSync as copyFileSync4,
7170
- existsSync as existsSync12,
7269
+ existsSync as existsSync13,
7171
7270
  mkdirSync as mkdirSync5,
7172
7271
  readdirSync as readdirSync2,
7173
7272
  readFileSync as readFileSync5,
@@ -7301,7 +7400,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
7301
7400
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
7302
7401
  if (failure) return failure;
7303
7402
  }
7304
- const fileSize = existsSync12(outputPath) ? statSync4(outputPath).size : 0;
7403
+ const fileSize = existsSync13(outputPath) ? statSync4(outputPath).size : 0;
7305
7404
  return {
7306
7405
  success: true,
7307
7406
  outputPath,
@@ -7462,7 +7561,7 @@ async function runEncodeStage(input) {
7462
7561
  const stage5Start = Date.now();
7463
7562
  if (isPngSequence) {
7464
7563
  const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
7465
- if (!existsSync12(outputPath)) mkdirSync5(outputPath, { recursive: true });
7564
+ if (!existsSync13(outputPath)) mkdirSync5(outputPath, { recursive: true });
7466
7565
  const captured = readdirSync2(framesDir).filter((name) => name.endsWith(".png")).sort();
7467
7566
  if (captured.length === 0) {
7468
7567
  throw new Error(
@@ -7473,7 +7572,7 @@ async function runEncodeStage(input) {
7473
7572
  const dst = join15(outputPath, formatExportFrameName(i, "png"));
7474
7573
  copyFileSync4(join15(framesDir, name), dst);
7475
7574
  });
7476
- if (hasAudio && audioOutputPath && existsSync12(audioOutputPath)) {
7575
+ if (hasAudio && audioOutputPath && existsSync13(audioOutputPath)) {
7477
7576
  copyFileSync4(audioOutputPath, join15(outputPath, MIXED_AUDIO_FILENAME2));
7478
7577
  log.info(
7479
7578
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
@@ -7636,7 +7735,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
7636
7735
  let rangeStart = null;
7637
7736
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
7638
7737
  const framePath = join16(framesDir, formatCaptureFrameName(frameIndex, frameExt));
7639
- const missing = !existsSync13(framePath) || statSync5(framePath).size <= 8;
7738
+ const missing = !existsSync14(framePath) || statSync5(framePath).size <= 8;
7640
7739
  if (missing && rangeStart === null) {
7641
7740
  rangeStart = frameIndex;
7642
7741
  } else if (!missing && rangeStart !== null) {
@@ -7726,7 +7825,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
7726
7825
  let captured = 0;
7727
7826
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
7728
7827
  const framePath = join16(framesDir, formatCaptureFrameName(frameIndex, frameExt));
7729
- if (existsSync13(framePath)) captured++;
7828
+ if (existsSync14(framePath)) captured++;
7730
7829
  }
7731
7830
  return captured;
7732
7831
  }
@@ -7953,7 +8052,7 @@ import {
7953
8052
  } from "@hyperframes/engine";
7954
8053
 
7955
8054
  // src/services/distributed/chunkFrames.ts
7956
- import { existsSync as existsSync15, readdirSync as readdirSync4 } from "node:fs";
8055
+ import { existsSync as existsSync16, readdirSync as readdirSync4 } from "node:fs";
7957
8056
  import { extname as extname2, join as join18 } from "node:path";
7958
8057
  import {
7959
8058
  compositionRequiresWebGpu as compositionRequiresWebGpu3,
@@ -7963,7 +8062,7 @@ import {
7963
8062
  // src/services/distributed/shared.ts
7964
8063
  import { execFile as execFileCallback } from "node:child_process";
7965
8064
  import { dirname as dirname11, join as join17 } from "node:path";
7966
- import { existsSync as existsSync14, readFileSync as readFileSync6 } from "node:fs";
8065
+ import { existsSync as existsSync15, readFileSync as readFileSync6 } from "node:fs";
7967
8066
  import { fileURLToPath as fileURLToPath3 } from "node:url";
7968
8067
  import { promisify as promisify2 } from "node:util";
7969
8068
  import {
@@ -7979,7 +8078,7 @@ function isPlanAudioArtifactPath(path) {
7979
8078
  function resolvePlanAudioPath(planDir) {
7980
8079
  for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
7981
8080
  const candidate = join17(planDir, name);
7982
- if (existsSync14(candidate)) return candidate;
8081
+ if (existsSync15(candidate)) return candidate;
7983
8082
  }
7984
8083
  return null;
7985
8084
  }
@@ -8240,7 +8339,7 @@ function readProducerVersion() {
8240
8339
  let current = startDir;
8241
8340
  for (let i = 0; i < 10; i++) {
8242
8341
  const candidate = join17(current, "package.json");
8243
- if (existsSync14(candidate)) {
8342
+ if (existsSync15(candidate)) {
8244
8343
  try {
8245
8344
  const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
8246
8345
  if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
@@ -8294,7 +8393,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
8294
8393
  const result = [];
8295
8394
  for (const v of videos) {
8296
8395
  const outputDir = join18(planDir, "video-frames", v.videoId);
8297
- if (!existsSync15(outputDir)) {
8396
+ if (!existsSync16(outputDir)) {
8298
8397
  throw new Error(
8299
8398
  `[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
8300
8399
  );
@@ -8393,7 +8492,7 @@ async function extractDeferredVideoFramesForChunk(input) {
8393
8492
  const range = planned.get(video.videoId).deferredRange;
8394
8493
  const remoteUrl = remoteSources[range.sourcePath];
8395
8494
  const sourcePath = remoteUrl ?? join18(input.planDir, ...range.sourcePath.split("/"));
8396
- if (remoteUrl === void 0 && !existsSync15(sourcePath)) {
8495
+ if (remoteUrl === void 0 && !existsSync16(sourcePath)) {
8397
8496
  throw new RenderChunkValidationError(
8398
8497
  MISSING_PLAN_ARTIFACT,
8399
8498
  `[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
@@ -8593,7 +8692,7 @@ async function readPlanReferenceBackground(input) {
8593
8692
  }
8594
8693
 
8595
8694
  // src/services/render/stages/freezePlan.ts
8596
- import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
8695
+ import { existsSync as existsSync17, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
8597
8696
  import { join as join21, relative as relative3, resolve as resolve8 } from "node:path";
8598
8697
  import {
8599
8698
  REFERENCE_BACKGROUND_FIELD,
@@ -8787,7 +8886,7 @@ function planFileSha256(absolutePath, digests) {
8787
8886
  return sha256;
8788
8887
  }
8789
8888
  async function prehashPlanFiles(dir, digests) {
8790
- if (!existsSync16(dir)) return;
8889
+ if (!existsSync17(dir)) return;
8791
8890
  for (const { absolutePath } of listPlanFiles(dir)) {
8792
8891
  if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
8793
8892
  }
@@ -8857,15 +8956,15 @@ function writePlanRemoteFiles(planDir, files) {
8857
8956
  }
8858
8957
  function readPlanRemoteFiles(planDir) {
8859
8958
  const path = join21(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
8860
- return existsSync16(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
8959
+ return existsSync17(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
8861
8960
  }
8862
8961
  function recomputePlanHashFromPlanDir(planDir, digests) {
8863
8962
  const planJsonPath = join21(planDir, "plan.json");
8864
8963
  const encoderJsonPath = join21(planDir, "meta", "encoder.json");
8865
- if (!existsSync16(planJsonPath)) {
8964
+ if (!existsSync17(planJsonPath)) {
8866
8965
  throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
8867
8966
  }
8868
- if (!existsSync16(encoderJsonPath)) {
8967
+ if (!existsSync17(encoderJsonPath)) {
8869
8968
  throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
8870
8969
  }
8871
8970
  const planJson = JSON.parse(readFileSync7(planJsonPath, "utf-8"));
@@ -8895,11 +8994,11 @@ async function freezePlan(input) {
8895
8994
  hasAudio,
8896
8995
  digests
8897
8996
  } = input;
8898
- if (!existsSync16(planDir)) {
8997
+ if (!existsSync17(planDir)) {
8899
8998
  throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
8900
8999
  }
8901
9000
  const metaDir = join21(planDir, "meta");
8902
- if (!existsSync16(metaDir)) mkdirSync7(metaDir, { recursive: true });
9001
+ if (!existsSync17(metaDir)) mkdirSync7(metaDir, { recursive: true });
8903
9002
  writeFileSync6(
8904
9003
  join21(metaDir, "composition.json"),
8905
9004
  `${JSON.stringify(composition, null, 2)}
@@ -8976,7 +9075,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
8976
9075
  }
8977
9076
 
8978
9077
  // src/services/distributed/chunkAudio.ts
8979
- import { closeSync as closeSync4, existsSync as existsSync17, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
9078
+ import { closeSync as closeSync4, existsSync as existsSync18, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
8980
9079
  import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
8981
9080
  import { join as join23 } from "node:path";
8982
9081
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
@@ -9467,7 +9566,7 @@ async function writeChunkAudioSlices(input) {
9467
9566
  }
9468
9567
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
9469
9568
  const layoutPath = join23(planDir, SLICE_LAYOUT);
9470
- if (!existsSync17(layoutPath)) return resolvePlanAudioPath(planDir);
9569
+ if (!existsSync18(layoutPath)) return resolvePlanAudioPath(planDir);
9471
9570
  const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
9472
9571
  const frame = readFileSync8(join23(planDir, SLICE_FRAME));
9473
9572
  const fd = openSync4(scratchPath, "w");
@@ -10017,7 +10116,7 @@ function pathInside(root, path) {
10017
10116
  function stableSourcePath(roots, srcPath) {
10018
10117
  for (const [root, prefix] of roots) {
10019
10118
  const rel = pathInside(root, srcPath);
10020
- if (rel !== null) return [prefix, ...rel.split(sep2)].filter(Boolean).join("/");
10119
+ if (rel !== null) return [prefix, ...rel.split(sep3)].filter(Boolean).join("/");
10021
10120
  }
10022
10121
  return srcPath;
10023
10122
  }
@@ -10031,8 +10130,8 @@ function createVideoSourceShipper(input) {
10031
10130
  let result = null;
10032
10131
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
10033
10132
  const rel = pathInside(root, srcPath);
10034
- if (rel !== null && (existsSync18(join25(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
10035
- result = ["compiled", ...rel.split(sep2)].join("/");
10133
+ if (rel !== null && (existsSync19(join25(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
10134
+ result = ["compiled", ...rel.split(sep3)].join("/");
10036
10135
  break;
10037
10136
  }
10038
10137
  }
@@ -10059,7 +10158,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
10059
10158
  useGpu: false,
10060
10159
  browserGpuMode: "software"
10061
10160
  });
10062
- if (!existsSync18(planDir)) mkdirSync10(planDir, { recursive: true });
10161
+ if (!existsSync19(planDir)) mkdirSync10(planDir, { recursive: true });
10063
10162
  const log = config.logger ?? defaultLogger;
10064
10163
  const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
10065
10164
  const abortSignal = config.abortSignal;
@@ -10089,7 +10188,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
10089
10188
  });
10090
10189
  const entryFile = config.entryFile ?? "index.html";
10091
10190
  const htmlPath = join25(projectDir, entryFile);
10092
- if (!existsSync18(htmlPath)) {
10191
+ if (!existsSync19(htmlPath)) {
10093
10192
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
10094
10193
  }
10095
10194
  const workDir = join25(planDir, ".plan-work");
@@ -10104,7 +10203,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
10104
10203
  filter: (src) => {
10105
10204
  const rel = relative5(projectDir, src);
10106
10205
  if (rel === "" || rel.startsWith("..")) return true;
10107
- const firstSegment = rel.split(sep2, 1)[0];
10206
+ const firstSegment = rel.split(sep3, 1)[0];
10108
10207
  return firstSegment === void 0 || !PLAN_PROJECT_DIR_SKIP_SEGMENTS.has(firstSegment);
10109
10208
  }
10110
10209
  });
@@ -10317,15 +10416,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10317
10416
  logPlanStage(log, "extract", stageStarted);
10318
10417
  const stagedVideoFrames = join25(compiledDir, "__hyperframes_video_frames");
10319
10418
  const videoFramesDst = join25(planDir, "video-frames");
10320
- if (existsSync18(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
10419
+ if (existsSync19(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
10321
10420
  const videoSourcesDir = join25(planDir, "video-sources");
10322
10421
  rmSync7(videoSourcesDir, { recursive: true, force: true });
10323
- if (existsSync18(stagedVideoFrames)) {
10422
+ if (existsSync19(stagedVideoFrames)) {
10324
10423
  renameWithRetry8(stagedVideoFrames, videoFramesDst);
10325
10424
  } else {
10326
10425
  mkdirSync10(videoFramesDst, { recursive: true });
10327
10426
  }
10328
- if (existsSync18(finalCompiledDir)) rmSync7(finalCompiledDir, { recursive: true, force: true });
10427
+ if (existsSync19(finalCompiledDir)) rmSync7(finalCompiledDir, { recursive: true, force: true });
10329
10428
  renameWithRetry8(compiledDir, finalCompiledDir);
10330
10429
  const shipVideoSource = createVideoSourceShipper({
10331
10430
  planDir,
@@ -10445,10 +10544,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
10445
10544
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
10446
10545
  }
10447
10546
  const planAudioPath = join25(planDir, PLAN_AUDIO_RELATIVE_PATH);
10448
- if (audioResult?.hasAudio && existsSync18(audioResult.audioOutputPath)) {
10547
+ if (audioResult?.hasAudio && existsSync19(audioResult.audioOutputPath)) {
10449
10548
  renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
10450
10549
  }
10451
- if (chunksCarryAudio && existsSync18(planAudioPath)) {
10550
+ if (chunksCarryAudio && existsSync19(planAudioPath)) {
10452
10551
  if (options.sliceChunkAudio) {
10453
10552
  stageStarted = Date.now();
10454
10553
  await writeChunkAudioSlices({
@@ -10561,7 +10660,7 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
10561
10660
  if (mixed.audioError) {
10562
10661
  applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
10563
10662
  }
10564
- if (!mixed.hasAudio || !existsSync18(mixed.audioOutputPath)) return { hasAudio: false };
10663
+ if (!mixed.hasAudio || !existsSync19(mixed.audioOutputPath)) return { hasAudio: false };
10565
10664
  renameWithRetry8(mixed.audioOutputPath, outputPath);
10566
10665
  return { hasAudio: true };
10567
10666
  } finally {
@@ -10576,7 +10675,7 @@ import {
10576
10675
  } from "@hyperframes/engine";
10577
10676
  import {
10578
10677
  copyFileSync as copyFileSync8,
10579
- existsSync as existsSync20,
10678
+ existsSync as existsSync21,
10580
10679
  lstatSync as lstatSync2,
10581
10680
  mkdirSync as mkdirSync12,
10582
10681
  mkdtempSync as mkdtempSync4,
@@ -10587,14 +10686,14 @@ import {
10587
10686
  writeFileSync as writeFileSync10
10588
10687
  } from "node:fs";
10589
10688
  import { tmpdir } from "node:os";
10590
- import { dirname as dirname14, isAbsolute as isAbsolute4, join as join28, relative as relative6, resolve as resolve9, sep as sep3 } from "node:path";
10689
+ import { dirname as dirname14, isAbsolute as isAbsolute4, join as join28, relative as relative6, resolve as resolve9, sep as sep4 } from "node:path";
10591
10690
  import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
10592
10691
  import { createFrameLookupTable as createFrameLookupTable5, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
10593
10692
 
10594
10693
  // src/services/distributed/planV2Publisher.ts
10595
10694
  import {
10596
10695
  copyFileSync as copyFileSync7,
10597
- existsSync as existsSync19,
10696
+ existsSync as existsSync20,
10598
10697
  linkSync as linkSync2,
10599
10698
  mkdirSync as mkdirSync11,
10600
10699
  mkdtempSync as mkdtempSync3,
@@ -10663,7 +10762,7 @@ var LocalPlanV2ArtifactPublisher = class {
10663
10762
  #linkFile;
10664
10763
  #committed = false;
10665
10764
  constructor(destinationDir, options = {}) {
10666
- if (existsSync19(destinationDir)) {
10765
+ if (existsSync20(destinationDir)) {
10667
10766
  throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
10668
10767
  }
10669
10768
  this.destinationDir = destinationDir;
@@ -10680,7 +10779,7 @@ var LocalPlanV2ArtifactPublisher = class {
10680
10779
  );
10681
10780
  }
10682
10781
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
10683
- if (existsSync19(destinationPath)) return;
10782
+ if (existsSync20(destinationPath)) return;
10684
10783
  mkdirSync11(dirname13(destinationPath), { recursive: true });
10685
10784
  const stagingDir = mkdtempSync3(join27(dirname13(destinationPath), ".plan-v2-blob-"));
10686
10785
  const temporaryPath = join27(stagingDir, "blob");
@@ -10698,7 +10797,7 @@ var LocalPlanV2ArtifactPublisher = class {
10698
10797
  }
10699
10798
  async commitManifest(manifestBytes) {
10700
10799
  for (const digest of new Set(manifestDigests(manifestBytes))) {
10701
- if (!existsSync19(planV2BlobPath(this.temporaryDir, digest))) {
10800
+ if (!existsSync20(planV2BlobPath(this.temporaryDir, digest))) {
10702
10801
  throw new PlanV2IntegrityError(
10703
10802
  `cannot commit manifest before referenced blob is durable: ${digest}`
10704
10803
  );
@@ -10754,7 +10853,7 @@ function listFiles(root) {
10754
10853
  walk(absolutePath);
10755
10854
  } else if (entry.isFile()) {
10756
10855
  files.push({
10757
- path: relative6(rootResolved, absolutePath).split(sep3).join("/"),
10856
+ path: relative6(rootResolved, absolutePath).split(sep4).join("/"),
10758
10857
  absolutePath
10759
10858
  });
10760
10859
  } else {
@@ -10777,7 +10876,7 @@ function assertValidExtractionCacheCompleteSentinel(path) {
10777
10876
  }
10778
10877
  function validateExtractionCacheCompleteSentinels(executionPlanDir) {
10779
10878
  const videoRoot = join28(executionPlanDir, "video-frames");
10780
- if (!existsSync20(videoRoot)) return;
10879
+ if (!existsSync21(videoRoot)) return;
10781
10880
  for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
10782
10881
  if (!videoEntry.isDirectory()) continue;
10783
10882
  const videoDir = join28(videoRoot, videoEntry.name);
@@ -10794,7 +10893,7 @@ function isExtractionCacheCompleteSentinelPath(path) {
10794
10893
  function resolveExtractedVideoOutputDir(planDir, videoId) {
10795
10894
  const videoRoot = resolve9(planDir, "video-frames");
10796
10895
  const outputDir = resolve9(videoRoot, videoId);
10797
- if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${sep3}`)) {
10896
+ if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${sep4}`)) {
10798
10897
  throw new PlanV2IntegrityError(`unsafe extracted video id: ${JSON.stringify(videoId)}`);
10799
10898
  }
10800
10899
  return outputDir;
@@ -10878,7 +10977,7 @@ function parsePlanVideosJson2(value) {
10878
10977
  }
10879
10978
  function materializeExtractedVideoDirectories(planDir) {
10880
10979
  const videosPath = join28(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10881
- if (!existsSync20(videosPath)) return;
10980
+ if (!existsSync21(videosPath)) return;
10882
10981
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
10883
10982
  for (const video of videos.extracted) {
10884
10983
  mkdirSync12(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
@@ -10912,9 +11011,9 @@ function parseChunkSlices(value) {
10912
11011
  }
10913
11012
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
10914
11013
  const videoRoot = join28(executionPlanDir, "video-frames");
10915
- const hasExtractedFrames = existsSync20(videoRoot) && listFiles(videoRoot).length > 0;
11014
+ const hasExtractedFrames = existsSync21(videoRoot) && listFiles(videoRoot).length > 0;
10916
11015
  const videosPath = join28(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
10917
- if (!existsSync20(videosPath)) {
11016
+ if (!existsSync21(videosPath)) {
10918
11017
  return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
10919
11018
  }
10920
11019
  const chunksPath = join28(executionPlanDir, "meta", "chunks.json");
@@ -10954,7 +11053,7 @@ function collectChunkVideoDependencies(input) {
10954
11053
  for (let frame = chunk.startFrame; frame < chunk.endFrame; frame++) {
10955
11054
  const globalTime = frame * input.fpsDen / input.fpsNum;
10956
11055
  for (const [videoId, payload] of input.table.getActiveFramePayloads(globalTime)) {
10957
- const path = input.deferredSources.get(videoId) ?? relative6(resolve9(input.executionPlanDir), payload.framePath).split(sep3).join("/");
11056
+ const path = input.deferredSources.get(videoId) ?? relative6(resolve9(input.executionPlanDir), payload.framePath).split(sep4).join("/");
10958
11057
  assertSafeRelativePath(path);
10959
11058
  const indexes = mutable.get(path) ?? /* @__PURE__ */ new Set();
10960
11059
  indexes.add(chunk.index);
@@ -10971,7 +11070,7 @@ function computeManifestHash(manifest) {
10971
11070
  return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
10972
11071
  }
10973
11072
  function writeBlob(sourcePath, destinationPath) {
10974
- if (existsSync20(destinationPath)) return;
11073
+ if (existsSync21(destinationPath)) return;
10975
11074
  mkdirSync12(dirname14(destinationPath), { recursive: true });
10976
11075
  const temporaryDir = mkdtempSync4(join28(dirname14(destinationPath), ".plan-v2-blob-"));
10977
11076
  const temporaryPath = join28(temporaryDir, "blob");
@@ -11011,7 +11110,7 @@ async function putTasks(tasks) {
11011
11110
  }
11012
11111
  function buildPlanV2Publication(executionPlanDir, digests) {
11013
11112
  const executionPlanPath = join28(executionPlanDir, "plan.json");
11014
- if (!existsSync20(executionPlanPath)) {
11113
+ if (!existsSync21(executionPlanPath)) {
11015
11114
  throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
11016
11115
  }
11017
11116
  const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
@@ -11091,7 +11190,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
11091
11190
  };
11092
11191
  }
11093
11192
  function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
11094
- if (existsSync20(planV2Dir)) {
11193
+ if (existsSync21(planV2Dir)) {
11095
11194
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
11096
11195
  }
11097
11196
  const publication = buildPlanV2Publication(executionPlanDir);
@@ -11177,7 +11276,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
11177
11276
  }
11178
11277
  }
11179
11278
  async function planV2(projectDir, config, planV2Dir) {
11180
- if (existsSync20(planV2Dir)) {
11279
+ if (existsSync21(planV2Dir)) {
11181
11280
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
11182
11281
  }
11183
11282
  const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
@@ -11331,7 +11430,7 @@ function parsePlanV2Manifest(value) {
11331
11430
  }
11332
11431
  function readPlanV2Manifest(planV2Dir) {
11333
11432
  const manifestPath = join28(planV2Dir, "plan.json");
11334
- if (!existsSync20(manifestPath)) {
11433
+ if (!existsSync21(manifestPath)) {
11335
11434
  throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
11336
11435
  }
11337
11436
  const value = readJsonFile(manifestPath, "v2 manifest");
@@ -11350,7 +11449,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
11350
11449
  }
11351
11450
  function verifyBlob(planV2Dir, artifact) {
11352
11451
  const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
11353
- if (!existsSync20(sourcePath)) {
11452
+ if (!existsSync21(sourcePath)) {
11354
11453
  throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
11355
11454
  }
11356
11455
  const stats = statSync7(sourcePath);
@@ -11363,7 +11462,7 @@ function verifyBlob(planV2Dir, artifact) {
11363
11462
  return sourcePath;
11364
11463
  }
11365
11464
  function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
11366
- if (existsSync20(destinationDir)) {
11465
+ if (existsSync21(destinationDir)) {
11367
11466
  throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
11368
11467
  }
11369
11468
  const manifest = readPlanV2Manifest(planV2Dir);
@@ -11428,7 +11527,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
11428
11527
  }
11429
11528
  function readValidatedPlanV2Materialization(planDir, expectedTarget) {
11430
11529
  const markerPath = join28(planDir, PLAN_V2_MATERIALIZATION_MARKER);
11431
- if (!existsSync20(markerPath)) return null;
11530
+ if (!existsSync21(markerPath)) return null;
11432
11531
  const marker = readJsonFile(markerPath, "materialization marker");
11433
11532
  if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
11434
11533
  throw new PlanV2IntegrityError("malformed materialization marker");
@@ -11446,7 +11545,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
11446
11545
  for (const artifact of required) {
11447
11546
  if (Object.hasOwn(remoteSources, artifact.path)) continue;
11448
11547
  const path = join28(planDir, ...artifact.path.split("/"));
11449
- if (!existsSync20(path) || statSync7(path).size !== artifact.sizeBytes) {
11548
+ if (!existsSync21(path) || statSync7(path).size !== artifact.sizeBytes) {
11450
11549
  throw new PlanV2IntegrityError(
11451
11550
  `materialized artifact missing or truncated: ${artifact.path}`
11452
11551
  );
@@ -11470,7 +11569,7 @@ import {
11470
11569
  } from "@hyperframes/engine";
11471
11570
  import {
11472
11571
  cpSync as cpSync3,
11473
- existsSync as existsSync21,
11572
+ existsSync as existsSync22,
11474
11573
  mkdirSync as mkdirSync13,
11475
11574
  mkdtempSync as mkdtempSync5,
11476
11575
  readFileSync as readFileSync10,
@@ -11525,7 +11624,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11525
11624
  }
11526
11625
  const format = plan.dimensions.format;
11527
11626
  const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
11528
- if (!existsSync21(dirname15(outputPath))) {
11627
+ if (!existsSync22(dirname15(outputPath))) {
11529
11628
  mkdirSync13(dirname15(outputPath), { recursive: true });
11530
11629
  }
11531
11630
  const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
@@ -11549,7 +11648,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11549
11648
  try {
11550
11649
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
11551
11650
  const finalPath = join29(workDir, `output.${format}`);
11552
- const audio = audioPath !== null && existsSync21(audioPath) ? audioPath : null;
11651
+ const audio = audioPath !== null && existsSync22(audioPath) ? audioPath : null;
11553
11652
  await writeJoined(ctx, videoSources, audio, finalPath);
11554
11653
  renameWithRetry11(finalPath, outputPath);
11555
11654
  log.info("[assemble] stage timings", {
@@ -11567,7 +11666,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11567
11666
  });
11568
11667
  }
11569
11668
  }
11570
- const fileSize = existsSync21(outputPath) ? statSync8(outputPath).size : 0;
11669
+ const fileSize = existsSync22(outputPath) ? statSync8(outputPath).size : 0;
11571
11670
  return {
11572
11671
  ...referenceBackgroundResult6(plan[REFERENCE_BACKGROUND_FIELD4]),
11573
11672
  outputPath,
@@ -11579,13 +11678,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
11579
11678
  function readAssemblePlan(planDir, chunkPaths) {
11580
11679
  const planJsonPath = join29(planDir, "plan.json");
11581
11680
  const chunksJsonPath = join29(planDir, "meta", "chunks.json");
11582
- if (!existsSync21(planJsonPath)) {
11681
+ if (!existsSync22(planJsonPath)) {
11583
11682
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
11584
11683
  }
11585
11684
  const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
11586
11685
  readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
11587
11686
  validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
11588
- if (!existsSync21(chunksJsonPath)) {
11687
+ if (!existsSync22(chunksJsonPath)) {
11589
11688
  throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
11590
11689
  }
11591
11690
  const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
@@ -11600,7 +11699,7 @@ function readAssemblePlan(planDir, chunkPaths) {
11600
11699
  }
11601
11700
  }
11602
11701
  for (const path of chunkPaths.flat()) {
11603
- if (!existsSync21(path)) {
11702
+ if (!existsSync22(path)) {
11604
11703
  throw new Error(`[assemble] chunk path does not exist: ${path}`);
11605
11704
  }
11606
11705
  }
@@ -11739,7 +11838,7 @@ function planFormat(planDir) {
11739
11838
  }
11740
11839
  function lockedAudio(planDir) {
11741
11840
  const encoderJsonPath = join29(planDir, "meta", "encoder.json");
11742
- if (!existsSync21(encoderJsonPath)) return {};
11841
+ if (!existsSync22(encoderJsonPath)) return {};
11743
11842
  return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
11744
11843
  }
11745
11844
  function readCfrEncoder(planDir, format) {
@@ -11749,7 +11848,7 @@ function readCfrEncoder(planDir, format) {
11749
11848
  );
11750
11849
  }
11751
11850
  const encoderJsonPath = join29(planDir, "meta", "encoder.json");
11752
- if (!existsSync21(encoderJsonPath)) {
11851
+ if (!existsSync22(encoderJsonPath)) {
11753
11852
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
11754
11853
  }
11755
11854
  const encoderJson = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
@@ -11761,7 +11860,7 @@ function readCfrEncoder(planDir, format) {
11761
11860
  return encoderJson;
11762
11861
  }
11763
11862
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
11764
- if (existsSync21(outputPath)) rmSync10(outputPath, { recursive: true, force: true });
11863
+ if (existsSync22(outputPath)) rmSync10(outputPath, { recursive: true, force: true });
11765
11864
  mkdirSync13(outputPath, { recursive: true });
11766
11865
  let globalIdx = 0;
11767
11866
  for (const chunkDir of chunkPaths) {
@@ -11785,7 +11884,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11785
11884
  `[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
11786
11885
  );
11787
11886
  }
11788
- if (audioPath !== null && existsSync21(audioPath)) {
11887
+ if (audioPath !== null && existsSync22(audioPath)) {
11789
11888
  const sidecar = join29(outputPath, MIXED_AUDIO_FILENAME4);
11790
11889
  cpSync3(audioPath, sidecar);
11791
11890
  }
@@ -11806,7 +11905,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11806
11905
 
11807
11906
  // src/services/distributed/renderChunk.ts
11808
11907
  import { randomBytes } from "node:crypto";
11809
- import { existsSync as existsSync22, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
11908
+ import { existsSync as existsSync23, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
11810
11909
  import { cpus } from "node:os";
11811
11910
  import { join as join30 } from "node:path";
11812
11911
  import {
@@ -11912,13 +12011,24 @@ async function runCaptureWithScreenshotFallback(input) {
11912
12011
  try {
11913
12012
  return await input.run(input.forceScreenshot);
11914
12013
  } catch (error) {
11915
- if (input.forceScreenshot || await input.canRetry?.() === false) throw error;
11916
- if (!shouldRetryChunkCaptureWithScreenshot(error)) throw error;
12014
+ if (input.forceScreenshot || await input.canRetry?.() === false)
12015
+ throw imageMissingIn(error) ?? error;
12016
+ if (!shouldRetryChunkCaptureWithScreenshot(error)) throw imageMissingIn(error) ?? error;
11917
12017
  input.onFallback?.(error);
11918
12018
  await input.resetForScreenshotRetry();
11919
- return await input.run(true);
12019
+ return await input.run(true).catch((retryError) => {
12020
+ throw imageMissingIn(retryError) ?? retryError;
12021
+ });
11920
12022
  }
11921
12023
  }
12024
+ function imageMissingIn(error) {
12025
+ let e = error;
12026
+ for (let depth = 0; depth < 5 && typeof e === "object" && e !== null; depth++) {
12027
+ if (e.name === "ImageMissingFromFrameError") return e;
12028
+ e = e.cause;
12029
+ }
12030
+ return void 0;
12031
+ }
11922
12032
  async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBeginFrameLiveness2) {
11923
12033
  if (session.launchCaptureMode !== "beginframe") return false;
11924
12034
  const timeoutMs = Number(process.env.PRODUCER_BEGINFRAME_PROBE_TIMEOUT_MS) > 0 ? Number(process.env.PRODUCER_BEGINFRAME_PROBE_TIMEOUT_MS) : 3e4;
@@ -11952,7 +12062,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11952
12062
  const planJsonPath = join30(planDir, "plan.json");
11953
12063
  const encoderJsonPath = join30(planDir, "meta", "encoder.json");
11954
12064
  const chunksJsonPath = join30(planDir, "meta", "chunks.json");
11955
- if (!existsSync22(planJsonPath)) {
12065
+ if (!existsSync23(planJsonPath)) {
11956
12066
  throw new RenderChunkValidationError(
11957
12067
  MISSING_PLAN_ARTIFACT,
11958
12068
  `[renderChunk] planDir is missing required artifact: ${planJsonPath}`
@@ -11968,7 +12078,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11968
12078
  const v2Manifest = v2Materialization?.manifest ?? null;
11969
12079
  const remoteSources = v2Materialization?.remoteSources ?? {};
11970
12080
  for (const required of [encoderJsonPath, chunksJsonPath]) {
11971
- if (!existsSync22(required)) {
12081
+ if (!existsSync23(required)) {
11972
12082
  throw new RenderChunkValidationError(
11973
12083
  MISSING_PLAN_ARTIFACT,
11974
12084
  `[renderChunk] planDir is missing required artifact: ${required}`
@@ -11979,7 +12089,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11979
12089
  const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
11980
12090
  const videosJsonPath = join30(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
11981
12091
  let planVideos = null;
11982
- if (existsSync22(videosJsonPath)) {
12092
+ if (existsSync23(videosJsonPath)) {
11983
12093
  try {
11984
12094
  planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync11(videosJsonPath, "utf-8")));
11985
12095
  } catch (err) {
@@ -12010,7 +12120,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
12010
12120
  const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
12011
12121
  const framesInChunk = range.endFrame - range.startFrame;
12012
12122
  const compiledDir = join30(planDir, "compiled");
12013
- if (!existsSync22(compiledDir)) {
12123
+ if (!existsSync23(compiledDir)) {
12014
12124
  throw new RenderChunkValidationError(
12015
12125
  MISSING_PLAN_ARTIFACT,
12016
12126
  `[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
@@ -12260,10 +12370,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
12260
12370
  const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
12261
12371
  const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
12262
12372
  if (isPngSequence) {
12263
- if (!existsSync22(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
12373
+ if (!existsSync23(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
12264
12374
  } else {
12265
12375
  const outDir = join30(outputChunkPath, "..");
12266
- if (!existsSync22(outDir)) mkdirSync14(outDir, { recursive: true });
12376
+ if (!existsSync23(outDir)) mkdirSync14(outDir, { recursive: true });
12267
12377
  }
12268
12378
  const encodeInput = {
12269
12379
  job,