@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.
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  // src/services/renderOrchestrator.ts
10
10
  import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
11
11
  import {
12
- existsSync as existsSync20,
12
+ existsSync as existsSync21,
13
13
  mkdirSync as mkdirSync12,
14
14
  readdirSync as readdirSync6,
15
15
  rmSync as rmSync12,
@@ -918,7 +918,7 @@ function resolveDeliveryEncoderOptions(config) {
918
918
  import {
919
919
  copyFileSync,
920
920
  cpSync,
921
- existsSync as existsSync3,
921
+ existsSync as existsSync4,
922
922
  mkdirSync,
923
923
  rmSync,
924
924
  symlinkSync,
@@ -938,7 +938,9 @@ import {
938
938
  relative as nodeRelative,
939
939
  isAbsolute as nodeIsAbsolute
940
940
  } from "node:path";
941
+ import { existsSync as existsSync3 } from "node:fs";
941
942
  import { fileURLToPath as fileURLToPath2 } from "node:url";
943
+ import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
942
944
  var DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? // fileURLToPath (not URL.pathname): on Windows .pathname is "/D:/..." which
943
945
  // resolves to a bogus renders dir.
944
946
  nodeResolve(fileURLToPath2(import.meta.url), "../../..", "renders");
@@ -976,6 +978,13 @@ function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS
976
978
  const absoluteOutputPath = nodeResolve(resolvedOutputPath);
977
979
  return { absoluteProjectDir, absoluteOutputPath };
978
980
  }
981
+ function resolveCompositionSrc(projectDir, src) {
982
+ const rooted = (value) => nodeResolve(projectDir, value.replace(/^(?:\.?[\\/])+/, ""));
983
+ const decoded = rooted(decodedUrlPath(src));
984
+ if (!isPathInside2(decoded, projectDir)) return null;
985
+ const raw = rooted(src);
986
+ return !existsSync3(decoded) && isPathInside2(raw, projectDir) && existsSync3(raw) ? raw : decoded;
987
+ }
979
988
 
980
989
  // src/services/render/shared.ts
981
990
  var BROWSER_MEDIA_EPSILON = 1e-4;
@@ -1179,7 +1188,7 @@ var materializePathModule = {
1179
1188
  isAbsolute
1180
1189
  };
1181
1190
  var materializeFileSystem = {
1182
- existsSync: existsSync3,
1191
+ existsSync: existsSync4,
1183
1192
  mkdirSync,
1184
1193
  symlinkSync,
1185
1194
  cpSync,
@@ -1481,7 +1490,7 @@ var RenderExecutionContext = class {
1481
1490
  };
1482
1491
 
1483
1492
  // src/services/render/artifactTransaction.ts
1484
- import { closeSync as closeSync2, existsSync as existsSync5, fstatSync as fstatSync2, openSync as openSync2, readSync, readdirSync as readdirSync2, rmSync as rmSync3 } from "node:fs";
1493
+ import { closeSync as closeSync2, existsSync as existsSync6, fstatSync as fstatSync2, openSync as openSync2, readSync, readdirSync as readdirSync2, rmSync as rmSync3 } from "node:fs";
1485
1494
  import { basename as basename3, dirname as dirname4, extname as extname2, join as join5, resolve as resolve4 } from "node:path";
1486
1495
  import { HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST, renameWithRetry } from "@hyperframes/engine";
1487
1496
 
@@ -1494,7 +1503,7 @@ import {
1494
1503
 
1495
1504
  // src/services/render/renderDirOwner.ts
1496
1505
  import {
1497
- existsSync as existsSync4,
1506
+ existsSync as existsSync5,
1498
1507
  lstatSync,
1499
1508
  mkdtempSync,
1500
1509
  readFileSync as readFileSync2,
@@ -1570,7 +1579,7 @@ function newestWriteMs(path) {
1570
1579
  }
1571
1580
  function isAbandoned(dir, ownerlessIdleMs, now) {
1572
1581
  try {
1573
- if (!lstatSync(dir).isDirectory() || existsSync4(join4(dir, TRANSACTION_BACKUP))) return false;
1582
+ if (!lstatSync(dir).isDirectory() || existsSync5(join4(dir, TRANSACTION_BACKUP))) return false;
1574
1583
  const owner = ownerState(dir);
1575
1584
  if (owner === "gone") return true;
1576
1585
  return owner === "none" && ownerlessIdleMs !== void 0 && now - newestWriteMs(dir) >= ownerlessIdleMs;
@@ -1602,7 +1611,7 @@ function createRenderWorkDir(prefix, outputDir) {
1602
1611
 
1603
1612
  // src/services/render/artifactTransaction.ts
1604
1613
  var defaultFileSystem = {
1605
- existsSync: existsSync5,
1614
+ existsSync: existsSync6,
1606
1615
  renameSync: (source, destination) => renameWithRetry(source, destination),
1607
1616
  rmSync: rmSync3
1608
1617
  };
@@ -1756,7 +1765,7 @@ var ArtifactTransaction = class {
1756
1765
  async assertDirectoryExpectation(expected, files) {
1757
1766
  for (const required of expected.requiredFiles ?? []) {
1758
1767
  const path = join5(this.stagingPath, required);
1759
- if (!existsSync5(path)) {
1768
+ if (!existsSync6(path)) {
1760
1769
  throw new Error(`Render artifact directory is missing ${required}: ${this.stagingPath}`);
1761
1770
  }
1762
1771
  assertReadableNonEmptyFile(path);
@@ -2005,7 +2014,7 @@ function replanAfterFailure(plan2, failure) {
2005
2014
  }
2006
2015
 
2007
2016
  // src/services/render/stages/captureSegmentedStage.ts
2008
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, rmSync as rmSync6, statSync as statSync3 } from "node:fs";
2017
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, rmSync as rmSync6, statSync as statSync3 } from "node:fs";
2009
2018
  import { join as join10 } from "node:path";
2010
2019
  import {
2011
2020
  captureFrameToBuffer as captureFrameToBuffer2,
@@ -2302,6 +2311,19 @@ function aggregateBeginFrameReuse(perfs) {
2302
2311
  if (noDamageFrames + hasDamageFrames === 0) return void 0;
2303
2312
  return { noDamageFrames, hasDamageFrames };
2304
2313
  }
2314
+ function aggregateMotionBlur(perfs) {
2315
+ const ran = perfs.flatMap((p) => p.motionBlur ?? []).filter((m) => m.tier || m.failedFrames > 0);
2316
+ const first = ran[0];
2317
+ if (!first) return void 0;
2318
+ return {
2319
+ tier: first.tier ?? ran.find((m) => m.tier)?.tier ?? null,
2320
+ reasonCode: first.reasonCode,
2321
+ movers: first.movers,
2322
+ failedFrames: ran.reduce((sum2, m) => sum2 + m.failedFrames, 0),
2323
+ readEvents: ran.some((m) => m.readEvents),
2324
+ fingerprint: ran.find((m) => m.fingerprint)?.fingerprint ?? null
2325
+ };
2326
+ }
2305
2327
  function buildRenderPerfSummary(input) {
2306
2328
  return {
2307
2329
  renderId: input.job.id,
@@ -2352,6 +2374,7 @@ function buildRenderPerfSummary(input) {
2352
2374
  staticDedup: aggregateDedup(input.dedupPerfs),
2353
2375
  chromeMemory: aggregateChromeMemory(input.dedupPerfs),
2354
2376
  beginFrameReuse: aggregateBeginFrameReuse(input.dedupPerfs),
2377
+ motionBlur: aggregateMotionBlur(input.dedupPerfs),
2355
2378
  drawElement: aggregateDrawElement(
2356
2379
  input.dedupPerfs,
2357
2380
  input.drawElement ?? { selfVerifyFallback: false }
@@ -3826,6 +3849,21 @@ function raceAgainstStall(promise, deadlineMs, input, signal) {
3826
3849
  );
3827
3850
  });
3828
3851
  }
3852
+ function createSessionInitBudget(input) {
3853
+ const initStallMs = Math.max(SESSION_INIT_STALL_MS, resolveCaptureStallTimeoutMs());
3854
+ const setup = createStallClock();
3855
+ return (work, stalled) => raceAgainstStall(
3856
+ work,
3857
+ initStallMs - setup.idle(0),
3858
+ {
3859
+ session: stalled,
3860
+ frameIndex: input.frameIndex,
3861
+ totalFrames: input.totalFrames,
3862
+ stallTimeoutMs: initStallMs
3863
+ },
3864
+ input.abortSignal
3865
+ );
3866
+ }
3829
3867
  function createDrainFrameGuard(args) {
3830
3868
  const { log, stats, frameTime } = args;
3831
3869
  const verifyMinDb = resolveDeVerifyMinDb();
@@ -4298,14 +4336,11 @@ async function runCaptureStreamingStage(input) {
4298
4336
  let resumes = 0;
4299
4337
  for (; ; ) {
4300
4338
  const videoInjector = createRenderVideoFrameInjector();
4301
- const initStallMs = Math.max(SESSION_INIT_STALL_MS, resolveCaptureStallTimeoutMs());
4302
- const setup = createStallClock();
4303
- const withinInitBudget = (work, stalled) => raceAgainstStall(
4304
- work,
4305
- initStallMs - setup.idle(0),
4306
- { session: stalled, frameIndex: resume.from, totalFrames, stallTimeoutMs: initStallMs },
4339
+ const withinInitBudget = createSessionInitBudget({
4340
+ frameIndex: resume.from,
4341
+ totalFrames,
4307
4342
  abortSignal
4308
- );
4343
+ });
4309
4344
  const openSession = async () => {
4310
4345
  const opening = createCaptureSession(
4311
4346
  fileServer.url,
@@ -4583,7 +4618,7 @@ async function captureSegmentToEncoder(ctx, segment) {
4583
4618
  path: segmentPath,
4584
4619
  // Recorded so resume can prove the file on disk is the one that
4585
4620
  // finished; absent only in unit tests, whose fake encoder writes none.
4586
- bytes: existsSync6(segmentPath) ? statSync3(segmentPath).size : 0
4621
+ bytes: existsSync7(segmentPath) ? statSync3(segmentPath).size : 0
4587
4622
  });
4588
4623
  return encodeResult;
4589
4624
  } finally {
@@ -4613,14 +4648,29 @@ function defaultSessionFactory(input) {
4613
4648
  });
4614
4649
  probeSession = null;
4615
4650
  }
4616
- const session = probeSession ?? await openSegmentedSession(input, injector);
4651
+ const withinInitBudget = createSessionInitBudget({
4652
+ frameIndex: input.job.framesRendered ?? 0,
4653
+ totalFrames: input.totalFrames,
4654
+ abortSignal: input.abortSignal
4655
+ });
4656
+ const openSession = async () => {
4657
+ const opening = openSegmentedSession(input, injector);
4658
+ try {
4659
+ return await withinInitBudget(opening, OPENING_CAPTURE_SESSION);
4660
+ } catch (error) {
4661
+ void opening.then(closeCaptureSession2, () => {
4662
+ });
4663
+ throw error;
4664
+ }
4665
+ };
4666
+ const session = probeSession ?? await openSession();
4617
4667
  if (probeSession) {
4618
4668
  openSegmentedSessionReuse(input, probeSession, injector);
4619
4669
  probeSession = null;
4620
4670
  }
4621
4671
  try {
4622
- if (!session.isInitialized) await initializeSession2(session);
4623
- await completeDeferredDrawElementInit2(session);
4672
+ if (!session.isInitialized) await withinInitBudget(initializeSession2(session), session);
4673
+ await withinInitBudget(completeDeferredDrawElementInit2(session), session);
4624
4674
  } catch (error) {
4625
4675
  await closeCaptureSession2(session).catch(() => {
4626
4676
  });
@@ -5552,7 +5602,7 @@ function trackSegmentFramePeak(frames, rootDir, onPeak) {
5552
5602
 
5553
5603
  // src/services/render/segmentManifest.ts
5554
5604
  import { createHash as createHash2 } from "node:crypto";
5555
- import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync3 } from "node:fs";
5605
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync3 } from "node:fs";
5556
5606
  import { join as join15 } from "node:path";
5557
5607
  import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
5558
5608
  var MANIFEST_FILENAME = "segments.json";
@@ -5600,7 +5650,7 @@ function isManifest(value) {
5600
5650
  }
5601
5651
  function readSegmentManifest(segmentDir) {
5602
5652
  const path = join15(segmentDir, MANIFEST_FILENAME);
5603
- if (!existsSync7(path)) return null;
5653
+ if (!existsSync8(path)) return null;
5604
5654
  try {
5605
5655
  const parsed = JSON.parse(readFileSync4(path, "utf-8"));
5606
5656
  return isManifest(parsed) ? parsed : null;
@@ -5629,7 +5679,7 @@ async function validateCompletedSegments(manifest, expectedHash, probeFrames) {
5629
5679
  const ok = /* @__PURE__ */ new Set();
5630
5680
  if (manifest.planHash !== expectedHash) return ok;
5631
5681
  for (const entry of manifest.completed) {
5632
- if (!existsSync7(entry.path)) continue;
5682
+ if (!existsSync8(entry.path)) continue;
5633
5683
  const size = statSync5(entry.path).size;
5634
5684
  if (size <= 0 || size !== entry.bytes) continue;
5635
5685
  const frames = await probeFrames(entry.path);
@@ -5769,7 +5819,7 @@ var ALLOWED_STRING_DATA_KEYS = /* @__PURE__ */ new Set([
5769
5819
  "stagePhase",
5770
5820
  // Full-fidelity fast-capture fallback trigger — the specific reason
5771
5821
  // drawElement gated off ("filter:blur", "filter:drop-shadow",
5772
- // "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", …).
5822
+ // "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", "webgl_unavailable", …).
5773
5823
  // Populated on the `capture_fallback_profile` checkpoint emitted by
5774
5824
  // `fallbackCaptureProfile.ts` when the operator opts into
5775
5825
  // `HF_PROFILE_FALLBACK_CAPTURE=true` so downstream metric consumers can
@@ -6208,17 +6258,17 @@ function countAuthoredTimedClips(html) {
6208
6258
  }
6209
6259
 
6210
6260
  // src/services/render/stages/compileStage.ts
6211
- import { existsSync as existsSync11, writeFileSync as writeFileSync5 } from "node:fs";
6261
+ import { existsSync as existsSync12, writeFileSync as writeFileSync5 } from "node:fs";
6212
6262
  import { join as join19 } from "node:path";
6213
6263
  import { suggestMatchingPreset } from "@hyperframes/core";
6214
6264
  import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
6215
6265
  import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
6216
6266
 
6217
6267
  // src/services/htmlCompiler.ts
6218
- import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
6268
+ import { decodedUrlPath as decodedUrlPath2 } from "@hyperframes/parsers/asset-paths";
6219
6269
  import { createHash as createHash6 } from "node:crypto";
6220
- import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync9, readFileSync as readFileSync7 } from "fs";
6221
- import { join as join18, dirname as dirname8, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
6270
+ import { createReadStream as createReadStream2, existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync7 } from "fs";
6271
+ import { join as join18, dirname as dirname8, resolve as resolve7, basename as basename4, relative as relative2, sep as sep2 } from "path";
6222
6272
  import { parseHTML as parseHTML5 } from "linkedom";
6223
6273
  import {
6224
6274
  COMPOSITION_SOURCE_URL,
@@ -6464,7 +6514,7 @@ import {
6464
6514
 
6465
6515
  // src/services/animatedGifPrep.ts
6466
6516
  import { createHash as createHash4 } from "node:crypto";
6467
- import { copyFileSync as copyFileSync2, existsSync as existsSync8, mkdirSync as mkdirSync7, readFileSync as readFileSync5, rmSync as rmSync9, statSync as statSync6 } from "node:fs";
6517
+ import { copyFileSync as copyFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync5, rmSync as rmSync9, statSync as statSync6 } from "node:fs";
6468
6518
  import { dirname as dirname6, isAbsolute as isAbsolute2, join as join16, resolve as resolve5 } from "node:path";
6469
6519
  import { parseHTML as parseHTML3 } from "linkedom";
6470
6520
  import { parseAnimatedGifMetadata } from "@hyperframes/core";
@@ -6503,14 +6553,14 @@ function resolveLocalImageSourcePath(src, options) {
6503
6553
  const { basePath } = splitUrlSuffix(trimmed);
6504
6554
  const normalizedBase = normalizeRelPath(basePath);
6505
6555
  const mapped = options.sourceAssets?.get(trimmed) ?? options.sourceAssets?.get(basePath) ?? options.sourceAssets?.get(normalizedBase);
6506
- if (mapped && existsSync8(mapped)) return mapped;
6556
+ if (mapped && existsSync9(mapped)) return mapped;
6507
6557
  if (isHttpUrl(trimmed)) return null;
6508
6558
  const projectRelative = basePath.startsWith("/") ? basePath.slice(1) : basePath;
6509
6559
  const candidates = [
6510
6560
  isAbsolute2(basePath) ? basePath : resolve5(options.projectDir, projectRelative),
6511
6561
  resolve5(options.downloadDir, normalizedBase)
6512
6562
  ];
6513
- return candidates.find((candidate) => existsSync8(candidate)) ?? null;
6563
+ return candidates.find((candidate) => existsSync9(candidate)) ?? null;
6514
6564
  }
6515
6565
  function isUsableFile(path) {
6516
6566
  try {
@@ -6621,10 +6671,10 @@ async function runAnimatedGifTranscode(request) {
6621
6671
  throw result.error ?? new Error(`Animated GIF transcode failed (${result.exitCode}): ${result.stderr.slice(-500)}`);
6622
6672
  }
6623
6673
  async function ensurePreparedWebm(input) {
6624
- if (!existsSync8(dirname6(input.cachePath))) {
6674
+ if (!existsSync9(dirname6(input.cachePath))) {
6625
6675
  mkdirSync7(dirname6(input.cachePath), { recursive: true });
6626
6676
  }
6627
- if (!existsSync8(dirname6(input.outputPath))) {
6677
+ if (!existsSync9(dirname6(input.outputPath))) {
6628
6678
  mkdirSync7(dirname6(input.outputPath), { recursive: true });
6629
6679
  }
6630
6680
  if (!isUsableFile(input.cachePath)) {
@@ -7099,7 +7149,7 @@ import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-serve
7099
7149
  import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
7100
7150
 
7101
7151
  // src/services/assetMediaType.ts
7102
- import { existsSync as existsSync9 } from "node:fs";
7152
+ import { existsSync as existsSync10 } from "node:fs";
7103
7153
  import {
7104
7154
  fingerprintElementId,
7105
7155
  isNotMediaPayload,
@@ -7172,7 +7222,7 @@ async function preflightCompositionAssetMediaTypes(input) {
7172
7222
  input.projectDir,
7173
7223
  input.compiledDir
7174
7224
  );
7175
- if (!existsSync9(resolvedPath)) continue;
7225
+ if (!existsSync10(resolvedPath)) continue;
7176
7226
  byPath.set(resolvedPath, [...byPath.get(resolvedPath) ?? [], reference]);
7177
7227
  }
7178
7228
  const mismatches = [];
@@ -7260,6 +7310,14 @@ Check that each file referenced by data-composition-src contains valid HTML with
7260
7310
  this.problems = problems;
7261
7311
  }
7262
7312
  };
7313
+ function normalizeCompositionSrcs(html, projectDir) {
7314
+ return html.replace(/(\bdata-composition-src=)(["'])([^"']*)\2/g, (whole, name, quote, value) => {
7315
+ if (!value.includes("%") && !value.startsWith("/")) return whole;
7316
+ const file = resolveCompositionSrc(projectDir, value);
7317
+ if (file === null) return whole;
7318
+ return `${name}${quote}${relative2(projectDir, file).split(sep2).join("/")}${quote}`;
7319
+ });
7320
+ }
7263
7321
  function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */ new Set()) {
7264
7322
  const { document: document2 } = parseHTML5(html);
7265
7323
  const hosts = [...document2.querySelectorAll("[data-composition-src]")];
@@ -7268,7 +7326,11 @@ function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */
7268
7326
  const srcPath = el.getAttribute("data-composition-src");
7269
7327
  if (!srcPath) continue;
7270
7328
  if (isUnresolvedAssetPlaceholder(srcPath)) continue;
7271
- const filePath = resolve7(projectDir, srcPath);
7329
+ const filePath = resolveCompositionSrc(projectDir, srcPath);
7330
+ if (filePath === null) {
7331
+ problems.push({ srcPath, detail: "it is outside the project folder" });
7332
+ continue;
7333
+ }
7272
7334
  if (visited.has(filePath)) continue;
7273
7335
  const read = readProjectFile(filePath);
7274
7336
  if (read.kind === "missing") {
@@ -7414,7 +7476,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
7414
7476
  let filePath = src;
7415
7477
  if (isHttpUrl(src)) {
7416
7478
  const remoteDir = join18(downloadDir, REMOTE_MEDIA_SUBDIR);
7417
- if (!existsSync10(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
7479
+ if (!existsSync11(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
7418
7480
  try {
7419
7481
  filePath = await downloadToTemp(src, remoteDir, void 0, void 0, void 0, {
7420
7482
  onTelemetry: logRemoteDownloadTelemetry
@@ -7423,11 +7485,11 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
7423
7485
  return { duration: null, resolvedPath: src };
7424
7486
  }
7425
7487
  } else {
7426
- filePath = decodedUrlPath(src);
7488
+ filePath = decodedUrlPath2(src);
7427
7489
  if (!filePath.startsWith("/")) filePath = join18(baseDir, filePath);
7428
7490
  filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
7429
7491
  }
7430
- if (!isHttpUrl(filePath) && !existsSync10(filePath)) {
7492
+ if (!isHttpUrl(filePath) && !existsSync11(filePath)) {
7431
7493
  return { duration: null, resolvedPath: filePath };
7432
7494
  }
7433
7495
  const withSrcContext = (error) => {
@@ -7568,7 +7630,8 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
7568
7630
  for (const el of compEls) {
7569
7631
  const srcPath = el.getAttribute("data-composition-src");
7570
7632
  if (!srcPath) continue;
7571
- const filePath = resolve7(projectDir, srcPath);
7633
+ const filePath = resolveCompositionSrc(projectDir, srcPath);
7634
+ if (filePath === null) continue;
7572
7635
  if (visited.has(filePath)) {
7573
7636
  continue;
7574
7637
  }
@@ -7576,7 +7639,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
7576
7639
  if (read.kind !== "file") {
7577
7640
  continue;
7578
7641
  }
7579
- const rawSubHtml = read.text;
7642
+ const rawSubHtml = normalizeCompositionSrcs(read.text, projectDir);
7580
7643
  const nestedVisited = new Set(visited);
7581
7644
  nestedVisited.add(filePath);
7582
7645
  workItems.push({ srcPath, filePath, rawSubHtml, nestedVisited });
@@ -7764,8 +7827,9 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
7764
7827
  resolveHtml: (srcPath) => {
7765
7828
  let compHtml = subCompositions.get(srcPath) || null;
7766
7829
  if (!compHtml) {
7767
- const read = readProjectFile(resolve7(projectDir, srcPath));
7768
- if (read.kind === "file") {
7830
+ const filePath = resolveCompositionSrc(projectDir, srcPath);
7831
+ const read = filePath ? readProjectFile(filePath) : null;
7832
+ if (read?.kind === "file") {
7769
7833
  compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
7770
7834
  }
7771
7835
  }
@@ -7774,7 +7838,7 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
7774
7838
  parseHtml: (htmlStr) => parseHTML5(htmlStr).document,
7775
7839
  // Mirrors the preview bundler: a sub-composition's SIBLING assets resolve
7776
7840
  // against its own directory, project-root refs stay as authored.
7777
- assetExists: (path) => existsSync10(resolve7(projectDir, path)),
7841
+ assetExists: (path) => existsSync11(resolve7(projectDir, path)),
7778
7842
  // Preserve the authored root wrapper as a child of the host, matching
7779
7843
  // the preview bundler's shape (htmlBundler.ts's prepareFlattenedInnerRoot,
7780
7844
  // which the runtime compositionLoader mirrors with its own copy for the
@@ -7965,7 +8029,7 @@ function collectExternalAssets(html, projectDir) {
7965
8029
  if (isPathInside2(absPath, absProjectDir)) {
7966
8030
  return null;
7967
8031
  }
7968
- if (!existsSync10(absPath)) return null;
8032
+ if (!existsSync11(absPath)) return null;
7969
8033
  const safeKey = toExternalAssetKey(absPath);
7970
8034
  externalAssets.set(safeKey, absPath);
7971
8035
  return safeKey;
@@ -8015,7 +8079,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
8015
8079
  var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
8016
8080
  async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
8017
8081
  if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
8018
- if (!existsSync10(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
8082
+ if (!existsSync11(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
8019
8083
  const urlToLocal = /* @__PURE__ */ new Map();
8020
8084
  await Promise.all(
8021
8085
  [...urlSet].map(async (url) => {
@@ -8197,14 +8261,14 @@ function projectStylesheetFontFamilies(html, projectDir) {
8197
8261
  for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
8198
8262
  const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
8199
8263
  if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
8200
- const sheet = resolve7(projectDir, decodedUrlPath(href).replace(/^(?:\.?\/)+/, ""));
8201
- if (!isPathInside2(sheet, projectDir) || !existsSync10(sheet)) continue;
8264
+ const sheet = resolve7(projectDir, decodedUrlPath2(href).replace(/^(?:\.?\/)+/, ""));
8265
+ if (!isPathInside2(sheet, projectDir) || !existsSync11(sheet)) continue;
8202
8266
  for (const face of extractFontFaceBlocks(readFileSync7(sheet, "utf8"))) {
8203
8267
  const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
8204
8268
  const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
8205
- const path = decodedUrlPath(url);
8269
+ const path = decodedUrlPath2(url);
8206
8270
  const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(dirname8(sheet), path);
8207
- return isPathInside2(file, projectDir) && existsSync10(file);
8271
+ return isPathInside2(file, projectDir) && existsSync11(file);
8208
8272
  });
8209
8273
  if (family && shipsFile) families.push(family);
8210
8274
  }
@@ -8342,7 +8406,7 @@ function renderGsapDist() {
8342
8406
  }
8343
8407
  function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
8344
8408
  let gsapDist;
8345
- const exists = (path) => existsSync10(resolve7(projectDir, path));
8409
+ const exists = (path) => existsSync11(resolve7(projectDir, path));
8346
8410
  return html.replace(
8347
8411
  /(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
8348
8412
  (full, prefix, src, file, suffix) => {
@@ -8359,7 +8423,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
8359
8423
  const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
8360
8424
  if (!entryPath.includes("/")) return html;
8361
8425
  const { document: document2 } = parseHTML5(html);
8362
- const assetExists = (path) => existsSync10(resolve7(projectDir, path));
8426
+ const assetExists = (path) => existsSync11(resolve7(projectDir, path));
8363
8427
  rewriteAssetPaths(
8364
8428
  document2.querySelectorAll("[src], [href], [srcset]"),
8365
8429
  entryPath,
@@ -8381,7 +8445,10 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
8381
8445
  }
8382
8446
  async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
8383
8447
  const entryHtml = rebaseDirectEntryAssetPaths(readTextFile(htmlPath), projectDir, htmlPath);
8384
- const rawHtml = rewriteUnresolvableGsapToCdn(entryHtml, projectDir);
8448
+ const rawHtml = normalizeCompositionSrcs(
8449
+ rewriteUnresolvableGsapToCdn(entryHtml, projectDir),
8450
+ projectDir
8451
+ );
8385
8452
  assertSubCompositionsUsable(rawHtml, projectDir);
8386
8453
  const probes = /* @__PURE__ */ new Map();
8387
8454
  const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
@@ -8926,7 +8993,7 @@ function resolveEntryHtmlPath(input) {
8926
8993
  const rawEntry = readTextFile2(htmlPath);
8927
8994
  if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
8928
8995
  const projectIndexPath = join19(projectDir, "index.html");
8929
- if (!existsSync11(projectIndexPath)) {
8996
+ if (!existsSync12(projectIndexPath)) {
8930
8997
  throw new Error(
8931
8998
  `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
8932
8999
  );
@@ -9792,7 +9859,7 @@ function validateDistributedDuration(input) {
9792
9859
  }
9793
9860
 
9794
9861
  // src/services/render/stages/extractVideosStage.ts
9795
- import { existsSync as existsSync12 } from "node:fs";
9862
+ import { existsSync as existsSync13 } from "node:fs";
9796
9863
  import { join as join21 } from "node:path";
9797
9864
  import {
9798
9865
  classifyVideoExtractionError,
@@ -10067,7 +10134,7 @@ async function runExtractVideosStage(input) {
10067
10134
  const probeFailures = await Promise.all(
10068
10135
  composition.videos.map(async (v) => {
10069
10136
  const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
10070
- if (!existsSync12(videoPath)) return null;
10137
+ if (!existsSync13(videoPath)) return null;
10071
10138
  try {
10072
10139
  const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry2(() => extractMediaMetadata3(videoPath), {
10073
10140
  signal: abortSignal,
@@ -10105,7 +10172,7 @@ async function runExtractVideosStage(input) {
10105
10172
  const probed = await Promise.all(
10106
10173
  composition.images.map(async (img) => {
10107
10174
  const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
10108
- if (!existsSync12(imgPath)) return null;
10175
+ if (!existsSync13(imgPath)) return null;
10109
10176
  const colorSpace = await probeColorSpaceSafely(imgPath, log);
10110
10177
  if (isHdrColorSpace2(colorSpace)) {
10111
10178
  nativeHdrImageIds.add(img.id);
@@ -10225,7 +10292,7 @@ async function appendVideoAudioFromProbe(input) {
10225
10292
  videos.map(async (video) => {
10226
10293
  const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
10227
10294
  const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
10228
- if (!isHttpUrl2(readFrom) && !existsSync12(readFrom)) return null;
10295
+ if (!isHttpUrl2(readFrom) && !existsSync13(readFrom)) return null;
10229
10296
  const metadata = await extractMediaMetadata3(readFrom);
10230
10297
  const window2 = await resolveFinalFrameExtractionWindow2(
10231
10298
  readFrom,
@@ -10245,7 +10312,7 @@ async function appendVideoAudioFromProbe(input) {
10245
10312
  }
10246
10313
 
10247
10314
  // src/services/render/videoLayerPassthroughPlan.ts
10248
- import { existsSync as existsSync13 } from "node:fs";
10315
+ import { existsSync as existsSync14 } from "node:fs";
10249
10316
  import {
10250
10317
  buildVideoLayerInput,
10251
10318
  isHttpUrl as isHttpUrl3,
@@ -10313,7 +10380,7 @@ async function planVideoLayerPassthrough(input) {
10313
10380
  const { video } = q;
10314
10381
  if (isHttpUrl3(video.src)) return { reason: "remote_source" };
10315
10382
  const path = resolveProjectRelativeSrc4(video.src, input.projectDir, input.compiledDir);
10316
- if (!existsSync13(path)) return { reason: "source_missing" };
10383
+ if (!existsSync14(path)) return { reason: "source_missing" };
10317
10384
  const layout = await probeVideoLayout(input.page, video.id, input.canvas, input.duration);
10318
10385
  if ("reason" in layout) return layout;
10319
10386
  const layer = await buildVideoLayerInput(
@@ -10328,7 +10395,7 @@ async function planVideoLayerPassthrough(input) {
10328
10395
  }
10329
10396
 
10330
10397
  // src/services/render/stages/audioStage.ts
10331
- import { existsSync as existsSync15 } from "node:fs";
10398
+ import { existsSync as existsSync16 } from "node:fs";
10332
10399
  import { join as join24 } from "node:path";
10333
10400
  import {
10334
10401
  MIXED_AUDIO_FILENAME,
@@ -10339,7 +10406,7 @@ import {
10339
10406
  } from "@hyperframes/engine";
10340
10407
 
10341
10408
  // src/services/render/audioDeliveryLimit.ts
10342
- import { existsSync as existsSync14 } from "node:fs";
10409
+ import { existsSync as existsSync15 } from "node:fs";
10343
10410
  import { dirname as dirname10, join as join23 } from "node:path";
10344
10411
  import {
10345
10412
  applyAudioFxChain,
@@ -10848,7 +10915,7 @@ async function limitDeliveryPeaks(input, deps = {
10848
10915
  applyChain: applyAudioFxChain,
10849
10916
  encode: encodeAac2,
10850
10917
  rename: renameWithRetry6,
10851
- sourceExists: existsSync14
10918
+ sourceExists: existsSync15
10852
10919
  }) {
10853
10920
  const { audioPath, sourcePath, signal, timeoutMs } = input;
10854
10921
  if (!deps.sourceExists(sourcePath)) return { success: true };
@@ -10943,7 +11010,7 @@ async function runAudioStageScoped(input) {
10943
11010
  );
10944
11011
  audioLoweredDb = guard.loweredDb;
10945
11012
  input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
10946
- if (input.keepDeliverySource && !guard.sourceStale && existsSync15(pcmSourcePath)) {
11013
+ if (input.keepDeliverySource && !guard.sourceStale && existsSync16(pcmSourcePath)) {
10947
11014
  deliverySourcePath = pcmSourcePath;
10948
11015
  }
10949
11016
  }
@@ -11071,7 +11138,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
11071
11138
  }
11072
11139
 
11073
11140
  // src/services/render/stages/captureStage.ts
11074
- import { existsSync as existsSync16, readdirSync as readdirSync4, statfsSync as statfsSync2 } from "node:fs";
11141
+ import { existsSync as existsSync17, readdirSync as readdirSync4, statfsSync as statfsSync2 } from "node:fs";
11075
11142
  import { join as join25 } from "node:path";
11076
11143
  import {
11077
11144
  classifyCaptureFailure as classifyCaptureFailure2,
@@ -11142,7 +11209,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
11142
11209
  }
11143
11210
  function measureCaptureFrameBytes(workDir, framesDir) {
11144
11211
  let total = sampleDirectoryBytes(framesDir);
11145
- if (!existsSync16(workDir)) return total;
11212
+ if (!existsSync17(workDir)) return total;
11146
11213
  for (const attempt of readdirSync4(workDir)) {
11147
11214
  if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
11148
11215
  total += sampleDirectoryBytes(join25(workDir, attempt));
@@ -11327,7 +11394,11 @@ async function runSequentialDiskCapture(input, captureOptions, captureCfg, check
11327
11394
  prepareCaptureSessionForReuse3(session, framesDir, videoInjector);
11328
11395
  probeSession = null;
11329
11396
  }
11330
- await ensureSessionReady(session);
11397
+ await ensureSessionReady(session, {
11398
+ frameIndex: resumeFrom,
11399
+ totalFrames,
11400
+ abortSignal: input.abortSignal
11401
+ });
11331
11402
  assertAttemptActive();
11332
11403
  lastBrowserConsole = session.browserConsoleBuffer;
11333
11404
  await captureSessionFrames(session, {
@@ -11379,13 +11450,13 @@ function planTransientResume(error, ctx) {
11379
11450
  if (!firstMissing) return null;
11380
11451
  return isTransientCaptureRetryEligible(failure.kind, missing, ctx.retriesUsed) ? firstMissing.startFrame : null;
11381
11452
  }
11382
- async function ensureSessionReady(session) {
11453
+ async function ensureSessionReady(session, budget) {
11383
11454
  if (!session.isInitialized) {
11384
11455
  await initializeSession5(session);
11385
11456
  return;
11386
11457
  }
11387
11458
  if (process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true") {
11388
- await completeDeferredDrawElementInit3(session);
11459
+ await createSessionInitBudget(budget)(completeDeferredDrawElementInit3(session), session);
11389
11460
  }
11390
11461
  }
11391
11462
  async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resumeFrom, checkDiskProjection }) {
@@ -11445,7 +11516,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
11445
11516
  }
11446
11517
 
11447
11518
  // src/services/render/stages/captureHdrStage.ts
11448
- import { existsSync as existsSync18, mkdirSync as mkdirSync10 } from "node:fs";
11519
+ import { existsSync as existsSync19, mkdirSync as mkdirSync10 } from "node:fs";
11449
11520
  import { join as join29 } from "node:path";
11450
11521
  import {
11451
11522
  closeCaptureSession as closeCaptureSession7,
@@ -11691,7 +11762,7 @@ import { Worker } from "node:worker_threads";
11691
11762
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
11692
11763
  import { dirname as dirname11, join as join27 } from "node:path";
11693
11764
  import { createRequire } from "node:module";
11694
- import { existsSync as existsSync17 } from "node:fs";
11765
+ import { existsSync as existsSync18 } from "node:fs";
11695
11766
  import { cpus as cpus2 } from "node:os";
11696
11767
  function resolveWorkerEntry(explicit) {
11697
11768
  if (explicit && explicit.length > 0) {
@@ -11704,7 +11775,7 @@ function resolveWorkerEntry(explicit) {
11704
11775
  }
11705
11776
  const moduleDir = dirname11(fileURLToPath3(import.meta.url));
11706
11777
  const jsPath = join27(moduleDir, "shaderTransitionWorker.js");
11707
- if (existsSync17(jsPath)) return { path: jsPath, isTs: false };
11778
+ if (existsSync18(jsPath)) return { path: jsPath, isTs: false };
11708
11779
  const tsPath = join27(moduleDir, "shaderTransitionWorker.ts");
11709
11780
  return { path: tsPath, isTs: true };
11710
11781
  }
@@ -12265,7 +12336,7 @@ async function runCaptureHdrStage(input) {
12265
12336
  }
12266
12337
  const debugDumpEnabled = process.env.KEEP_TEMP === "1";
12267
12338
  const debugDumpDir = debugDumpEnabled ? join29(framesDir, "debug-composite") : null;
12268
- if (debugDumpDir && !existsSync18(debugDumpDir)) {
12339
+ if (debugDumpDir && !existsSync19(debugDumpDir)) {
12269
12340
  mkdirSync10(debugDumpDir, { recursive: true });
12270
12341
  }
12271
12342
  const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
@@ -12422,7 +12493,7 @@ async function runCaptureHdrStage(input) {
12422
12493
  // src/services/render/stages/encodeStage.ts
12423
12494
  import {
12424
12495
  copyFileSync as copyFileSync4,
12425
- existsSync as existsSync19,
12496
+ existsSync as existsSync20,
12426
12497
  mkdirSync as mkdirSync11,
12427
12498
  readdirSync as readdirSync5,
12428
12499
  readFileSync as readFileSync8,
@@ -12556,7 +12627,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
12556
12627
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
12557
12628
  if (failure) return failure;
12558
12629
  }
12559
- const fileSize = existsSync19(outputPath) ? statSync8(outputPath).size : 0;
12630
+ const fileSize = existsSync20(outputPath) ? statSync8(outputPath).size : 0;
12560
12631
  return {
12561
12632
  success: true,
12562
12633
  outputPath,
@@ -12717,7 +12788,7 @@ async function runEncodeStage(input) {
12717
12788
  const stage5Start = Date.now();
12718
12789
  if (isPngSequence) {
12719
12790
  const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
12720
- if (!existsSync19(outputPath)) mkdirSync11(outputPath, { recursive: true });
12791
+ if (!existsSync20(outputPath)) mkdirSync11(outputPath, { recursive: true });
12721
12792
  const captured = readdirSync5(framesDir).filter((name) => name.endsWith(".png")).sort();
12722
12793
  if (captured.length === 0) {
12723
12794
  throw new Error(
@@ -12728,7 +12799,7 @@ async function runEncodeStage(input) {
12728
12799
  const dst = join30(outputPath, formatExportFrameName(i, "png"));
12729
12800
  copyFileSync4(join30(framesDir, name), dst);
12730
12801
  });
12731
- if (hasAudio && audioOutputPath && existsSync19(audioOutputPath)) {
12802
+ if (hasAudio && audioOutputPath && existsSync20(audioOutputPath)) {
12732
12803
  copyFileSync4(audioOutputPath, join30(outputPath, MIXED_AUDIO_FILENAME2));
12733
12804
  log.info(
12734
12805
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
@@ -13117,7 +13188,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
13117
13188
  let rangeStart = null;
13118
13189
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
13119
13190
  const framePath = join31(framesDir, formatCaptureFrameName(frameIndex, frameExt));
13120
- const missing = !existsSync20(framePath) || statSync9(framePath).size <= 8;
13191
+ const missing = !existsSync21(framePath) || statSync9(framePath).size <= 8;
13121
13192
  if (missing && rangeStart === null) {
13122
13193
  rangeStart = frameIndex;
13123
13194
  } else if (!missing && rangeStart !== null) {
@@ -13242,7 +13313,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
13242
13313
  let captured = 0;
13243
13314
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
13244
13315
  const framePath = join31(framesDir, formatCaptureFrameName(frameIndex, frameExt));
13245
- if (existsSync20(framePath)) captured++;
13316
+ if (existsSync21(framePath)) captured++;
13246
13317
  }
13247
13318
  return captured;
13248
13319
  }
@@ -13907,7 +13978,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
13907
13978
  validateDeliveryEncoderConfig(job.config);
13908
13979
  const debugDir = resolveRenderDebugDir();
13909
13980
  const outputDir = dirname13(outputPath);
13910
- if (!existsSync20(outputDir)) mkdirSync12(outputDir, { recursive: true });
13981
+ if (!existsSync21(outputDir)) mkdirSync12(outputDir, { recursive: true });
13911
13982
  const workDir = job.config.debug ? join31(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
13912
13983
  const pipelineStart = Date.now();
13913
13984
  const baseLog = job.config.logger ?? defaultLogger;
@@ -14049,7 +14120,7 @@ async function executeRenderPipeline(input) {
14049
14120
  job.startedAt = /* @__PURE__ */ new Date();
14050
14121
  assertNotAborted();
14051
14122
  assertConfiguredFfmpegBinariesExist();
14052
- if (!existsSync20(workDir)) mkdirSync12(workDir, { recursive: true });
14123
+ if (!existsSync21(workDir)) mkdirSync12(workDir, { recursive: true });
14053
14124
  if (job.config.debug) {
14054
14125
  log.info("[Render] Debug artifacts enabled", { workDir, logPath });
14055
14126
  }
@@ -14079,7 +14150,7 @@ async function executeRenderPipeline(input) {
14079
14150
  });
14080
14151
  const entryFile = job.config.entryFile || "index.html";
14081
14152
  const htmlPath = join31(projectDir, entryFile);
14082
- if (!existsSync20(htmlPath)) {
14153
+ if (!existsSync21(htmlPath)) {
14083
14154
  throw new Error(`Entry file not found: ${htmlPath}`);
14084
14155
  }
14085
14156
  assertNotAborted();
@@ -14394,9 +14465,11 @@ async function executeRenderPipeline(input) {
14394
14465
  );
14395
14466
  pendingAudio = audioStage.catch(() => void 0);
14396
14467
  const stage4Start = Date.now();
14397
- updateJobStatus(job, "rendering", "Starting frame capture", 25, onProgress, {
14468
+ const EFFECT_SCAN_STAGE = "Checking effects";
14469
+ const startingFrameCapture = () => updateJobStatus(job, "rendering", "Starting frame capture", 25, onProgress, {
14398
14470
  code: "start_browsers"
14399
14471
  });
14472
+ startingFrameCapture();
14400
14473
  if (!fileServer) {
14401
14474
  const fileServerStart = observability.stageStart("file_server", { reused: false });
14402
14475
  try {
@@ -14421,7 +14494,7 @@ async function executeRenderPipeline(input) {
14421
14494
  throw new Error("File server failed to initialize before frame capture");
14422
14495
  }
14423
14496
  const framesDir = join31(workDir, "captured-frames");
14424
- if (!existsSync20(framesDir)) mkdirSync12(framesDir, { recursive: true });
14497
+ if (!existsSync21(framesDir)) mkdirSync12(framesDir, { recursive: true });
14425
14498
  updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
14426
14499
  code: "start_browsers"
14427
14500
  });
@@ -14429,6 +14502,7 @@ async function executeRenderPipeline(input) {
14429
14502
  chromePath: resolveHeadlessShellPath(cfg),
14430
14503
  browserTimeout: cfg.browserTimeout
14431
14504
  });
14505
+ startingFrameCapture();
14432
14506
  captureForceScreenshot = applyConcreteGpuScreenshotClamp(
14433
14507
  captureForceScreenshot,
14434
14508
  resolvedBrowserGpuMode,
@@ -14477,10 +14551,13 @@ async function executeRenderPipeline(input) {
14477
14551
  onEffectScanProgress: (scanned, total) => updateJobStatus(
14478
14552
  job,
14479
14553
  "rendering",
14480
- `Checking effects, ${scanned} of ${total} frames`,
14554
+ `${EFFECT_SCAN_STAGE}, ${scanned} of ${total} frames`,
14481
14555
  25,
14482
14556
  onProgress
14483
14557
  ),
14558
+ onEffectScanEnd: () => {
14559
+ if (job.currentStage.startsWith(EFFECT_SCAN_STAGE)) startingFrameCapture();
14560
+ },
14484
14561
  requiresWebGpu: compositionRequiresWebGpu2(compiled.html),
14485
14562
  // Live route for Chrome memory (spec §5 Phase −1). The aggregate route
14486
14563
  // through CapturePerfSummary only exists on success; a mid-capture
@@ -14748,6 +14825,7 @@ async function executeRenderPipeline(input) {
14748
14825
  } finally {
14749
14826
  calibrationFrames?.release();
14750
14827
  }
14828
+ startingFrameCapture();
14751
14829
  captureCalibration = outcome.calibration;
14752
14830
  calibrationEvaluateTimedOut = outcome.calibrationEvaluateTimedOut;
14753
14831
  captureForceScreenshot = outcome.forceScreenshot;
@@ -15806,7 +15884,7 @@ async function executeRenderPipeline(input) {
15806
15884
  })
15807
15885
  );
15808
15886
  const totalElapsed = Date.now() - pipelineStart;
15809
- const tmpPeakBytes = existsSync20(workDir) ? sampleDirectoryBytes(workDir) : 0;
15887
+ const tmpPeakBytes = existsSync21(workDir) ? sampleDirectoryBytes(workDir) : 0;
15810
15888
  recordTransientRetryObservability();
15811
15889
  observability.checkpoint("pipeline", "artifact validated", { totalElapsedMs: totalElapsed });
15812
15890
  const observabilitySummary = observability.summary({
@@ -15887,7 +15965,7 @@ async function executeRenderPipeline(input) {
15887
15965
  }
15888
15966
  }
15889
15967
  if (job.config.debug) {
15890
- if (!isPngSequence && !isHls && existsSync20(stagedOutputPath)) {
15968
+ if (!isPngSequence && !isHls && existsSync21(stagedOutputPath)) {
15891
15969
  const debugOutput = join31(workDir, `output${videoExt}`);
15892
15970
  copyFileSync5(stagedOutputPath, debugOutput);
15893
15971
  }
@@ -16399,13 +16477,13 @@ import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframe
16399
16477
  import {
16400
16478
  copyFileSync as copyFileSync6,
16401
16479
  cpSync as cpSync2,
16402
- existsSync as existsSync25,
16480
+ existsSync as existsSync26,
16403
16481
  linkSync,
16404
16482
  mkdirSync as mkdirSync16,
16405
16483
  rmSync as rmSync14,
16406
16484
  writeFileSync as writeFileSync10
16407
16485
  } from "node:fs";
16408
- import { extname as extname6, isAbsolute as isAbsolute3, join as join40, relative as relative5, sep as sep2 } from "node:path";
16486
+ import { extname as extname6, isAbsolute as isAbsolute3, join as join40, relative as relative5, sep as sep3 } from "node:path";
16409
16487
  import {
16410
16488
  fpsToNumber as fpsToNumber9,
16411
16489
  HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
@@ -16430,7 +16508,7 @@ import {
16430
16508
  } from "@hyperframes/engine";
16431
16509
 
16432
16510
  // src/services/distributed/chunkFrames.ts
16433
- import { existsSync as existsSync22, readdirSync as readdirSync7 } from "node:fs";
16511
+ import { existsSync as existsSync23, readdirSync as readdirSync7 } from "node:fs";
16434
16512
  import { extname as extname4, join as join34 } from "node:path";
16435
16513
  import {
16436
16514
  compositionRequiresWebGpu as compositionRequiresWebGpu3,
@@ -16440,7 +16518,7 @@ import {
16440
16518
  // src/services/distributed/shared.ts
16441
16519
  import { execFile as execFileCallback } from "node:child_process";
16442
16520
  import { dirname as dirname15, join as join33 } from "node:path";
16443
- import { existsSync as existsSync21, readFileSync as readFileSync9 } from "node:fs";
16521
+ import { existsSync as existsSync22, readFileSync as readFileSync9 } from "node:fs";
16444
16522
  import { fileURLToPath as fileURLToPath5 } from "node:url";
16445
16523
  import { promisify as promisify2 } from "node:util";
16446
16524
  import {
@@ -16456,7 +16534,7 @@ function isPlanAudioArtifactPath(path) {
16456
16534
  function resolvePlanAudioPath(planDir) {
16457
16535
  for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
16458
16536
  const candidate = join33(planDir, name);
16459
- if (existsSync21(candidate)) return candidate;
16537
+ if (existsSync22(candidate)) return candidate;
16460
16538
  }
16461
16539
  return null;
16462
16540
  }
@@ -16717,7 +16795,7 @@ function readProducerVersion() {
16717
16795
  let current = startDir;
16718
16796
  for (let i = 0; i < 10; i++) {
16719
16797
  const candidate = join33(current, "package.json");
16720
- if (existsSync21(candidate)) {
16798
+ if (existsSync22(candidate)) {
16721
16799
  try {
16722
16800
  const pkg = JSON.parse(readFileSync9(candidate, "utf-8"));
16723
16801
  if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
@@ -16771,7 +16849,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
16771
16849
  const result = [];
16772
16850
  for (const v of videos) {
16773
16851
  const outputDir = join34(planDir, "video-frames", v.videoId);
16774
- if (!existsSync22(outputDir)) {
16852
+ if (!existsSync23(outputDir)) {
16775
16853
  throw new Error(
16776
16854
  `[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
16777
16855
  );
@@ -16870,7 +16948,7 @@ async function extractDeferredVideoFramesForChunk(input) {
16870
16948
  const range = planned.get(video.videoId).deferredRange;
16871
16949
  const remoteUrl = remoteSources[range.sourcePath];
16872
16950
  const sourcePath = remoteUrl ?? join34(input.planDir, ...range.sourcePath.split("/"));
16873
- if (remoteUrl === void 0 && !existsSync22(sourcePath)) {
16951
+ if (remoteUrl === void 0 && !existsSync23(sourcePath)) {
16874
16952
  throw new RenderChunkValidationError(
16875
16953
  MISSING_PLAN_ARTIFACT,
16876
16954
  `[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
@@ -17070,7 +17148,7 @@ async function readPlanReferenceBackground(input) {
17070
17148
  }
17071
17149
 
17072
17150
  // src/services/render/stages/freezePlan.ts
17073
- import { existsSync as existsSync23, mkdirSync as mkdirSync14, readFileSync as readFileSync10, readdirSync as readdirSync8, writeFileSync as writeFileSync8 } from "node:fs";
17151
+ import { existsSync as existsSync24, mkdirSync as mkdirSync14, readFileSync as readFileSync10, readdirSync as readdirSync8, writeFileSync as writeFileSync8 } from "node:fs";
17074
17152
  import { join as join37, relative as relative3, resolve as resolve9 } from "node:path";
17075
17153
  import {
17076
17154
  REFERENCE_BACKGROUND_FIELD,
@@ -17264,7 +17342,7 @@ function planFileSha256(absolutePath, digests) {
17264
17342
  return sha256;
17265
17343
  }
17266
17344
  async function prehashPlanFiles(dir, digests) {
17267
- if (!existsSync23(dir)) return;
17345
+ if (!existsSync24(dir)) return;
17268
17346
  for (const { absolutePath } of listPlanFiles(dir)) {
17269
17347
  if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
17270
17348
  }
@@ -17334,15 +17412,15 @@ function writePlanRemoteFiles(planDir, files) {
17334
17412
  }
17335
17413
  function readPlanRemoteFiles(planDir) {
17336
17414
  const path = join37(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
17337
- return existsSync23(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
17415
+ return existsSync24(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
17338
17416
  }
17339
17417
  function recomputePlanHashFromPlanDir(planDir, digests) {
17340
17418
  const planJsonPath = join37(planDir, "plan.json");
17341
17419
  const encoderJsonPath = join37(planDir, "meta", "encoder.json");
17342
- if (!existsSync23(planJsonPath)) {
17420
+ if (!existsSync24(planJsonPath)) {
17343
17421
  throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
17344
17422
  }
17345
- if (!existsSync23(encoderJsonPath)) {
17423
+ if (!existsSync24(encoderJsonPath)) {
17346
17424
  throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
17347
17425
  }
17348
17426
  const planJson = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
@@ -17372,11 +17450,11 @@ async function freezePlan(input) {
17372
17450
  hasAudio,
17373
17451
  digests
17374
17452
  } = input;
17375
- if (!existsSync23(planDir)) {
17453
+ if (!existsSync24(planDir)) {
17376
17454
  throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
17377
17455
  }
17378
17456
  const metaDir = join37(planDir, "meta");
17379
- if (!existsSync23(metaDir)) mkdirSync14(metaDir, { recursive: true });
17457
+ if (!existsSync24(metaDir)) mkdirSync14(metaDir, { recursive: true });
17380
17458
  writeFileSync8(
17381
17459
  join37(metaDir, "composition.json"),
17382
17460
  `${JSON.stringify(composition, null, 2)}
@@ -17453,7 +17531,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
17453
17531
  }
17454
17532
 
17455
17533
  // src/services/distributed/chunkAudio.ts
17456
- import { closeSync as closeSync6, existsSync as existsSync24, ftruncateSync, mkdirSync as mkdirSync15, openSync as openSync6, readFileSync as readFileSync11 } from "node:fs";
17534
+ import { closeSync as closeSync6, existsSync as existsSync25, ftruncateSync, mkdirSync as mkdirSync15, openSync as openSync6, readFileSync as readFileSync11 } from "node:fs";
17457
17535
  import { writeFileSync as writeFileSync9, writeSync } from "node:fs";
17458
17536
  import { join as join38 } from "node:path";
17459
17537
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
@@ -17559,7 +17637,7 @@ async function writeChunkAudioSlices(input) {
17559
17637
  }
17560
17638
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
17561
17639
  const layoutPath = join38(planDir, SLICE_LAYOUT);
17562
- if (!existsSync24(layoutPath)) return resolvePlanAudioPath(planDir);
17640
+ if (!existsSync25(layoutPath)) return resolvePlanAudioPath(planDir);
17563
17641
  const layout = JSON.parse(readFileSync11(layoutPath, "utf-8"));
17564
17642
  const frame = readFileSync11(join38(planDir, SLICE_FRAME));
17565
17643
  const fd = openSync6(scratchPath, "w");
@@ -18105,7 +18183,7 @@ function pathInside(root, path) {
18105
18183
  function stableSourcePath(roots, srcPath) {
18106
18184
  for (const [root, prefix] of roots) {
18107
18185
  const rel = pathInside(root, srcPath);
18108
- if (rel !== null) return [prefix, ...rel.split(sep2)].filter(Boolean).join("/");
18186
+ if (rel !== null) return [prefix, ...rel.split(sep3)].filter(Boolean).join("/");
18109
18187
  }
18110
18188
  return srcPath;
18111
18189
  }
@@ -18119,8 +18197,8 @@ function createVideoSourceShipper(input) {
18119
18197
  let result = null;
18120
18198
  for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
18121
18199
  const rel = pathInside(root, srcPath);
18122
- if (rel !== null && (existsSync25(join40(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
18123
- result = ["compiled", ...rel.split(sep2)].join("/");
18200
+ if (rel !== null && (existsSync26(join40(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
18201
+ result = ["compiled", ...rel.split(sep3)].join("/");
18124
18202
  break;
18125
18203
  }
18126
18204
  }
@@ -18147,7 +18225,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
18147
18225
  useGpu: false,
18148
18226
  browserGpuMode: "software"
18149
18227
  });
18150
- if (!existsSync25(planDir)) mkdirSync16(planDir, { recursive: true });
18228
+ if (!existsSync26(planDir)) mkdirSync16(planDir, { recursive: true });
18151
18229
  const log = config.logger ?? defaultLogger;
18152
18230
  const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
18153
18231
  const abortSignal = config.abortSignal;
@@ -18177,7 +18255,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
18177
18255
  });
18178
18256
  const entryFile = config.entryFile ?? "index.html";
18179
18257
  const htmlPath = join40(projectDir, entryFile);
18180
- if (!existsSync25(htmlPath)) {
18258
+ if (!existsSync26(htmlPath)) {
18181
18259
  throw new Error(`[plan] entry file not found: ${htmlPath}`);
18182
18260
  }
18183
18261
  const workDir = join40(planDir, ".plan-work");
@@ -18192,7 +18270,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
18192
18270
  filter: (src) => {
18193
18271
  const rel = relative5(projectDir, src);
18194
18272
  if (rel === "" || rel.startsWith("..")) return true;
18195
- const firstSegment = rel.split(sep2, 1)[0];
18273
+ const firstSegment = rel.split(sep3, 1)[0];
18196
18274
  return firstSegment === void 0 || !PLAN_PROJECT_DIR_SKIP_SEGMENTS.has(firstSegment);
18197
18275
  }
18198
18276
  });
@@ -18405,15 +18483,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
18405
18483
  logPlanStage(log, "extract", stageStarted);
18406
18484
  const stagedVideoFrames = join40(compiledDir, "__hyperframes_video_frames");
18407
18485
  const videoFramesDst = join40(planDir, "video-frames");
18408
- if (existsSync25(videoFramesDst)) rmSync14(videoFramesDst, { recursive: true, force: true });
18486
+ if (existsSync26(videoFramesDst)) rmSync14(videoFramesDst, { recursive: true, force: true });
18409
18487
  const videoSourcesDir = join40(planDir, "video-sources");
18410
18488
  rmSync14(videoSourcesDir, { recursive: true, force: true });
18411
- if (existsSync25(stagedVideoFrames)) {
18489
+ if (existsSync26(stagedVideoFrames)) {
18412
18490
  renameWithRetry8(stagedVideoFrames, videoFramesDst);
18413
18491
  } else {
18414
18492
  mkdirSync16(videoFramesDst, { recursive: true });
18415
18493
  }
18416
- if (existsSync25(finalCompiledDir)) rmSync14(finalCompiledDir, { recursive: true, force: true });
18494
+ if (existsSync26(finalCompiledDir)) rmSync14(finalCompiledDir, { recursive: true, force: true });
18417
18495
  renameWithRetry8(compiledDir, finalCompiledDir);
18418
18496
  const shipVideoSource = createVideoSourceShipper({
18419
18497
  planDir,
@@ -18533,10 +18611,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
18533
18611
  applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
18534
18612
  }
18535
18613
  const planAudioPath = join40(planDir, PLAN_AUDIO_RELATIVE_PATH);
18536
- if (audioResult?.hasAudio && existsSync25(audioResult.audioOutputPath)) {
18614
+ if (audioResult?.hasAudio && existsSync26(audioResult.audioOutputPath)) {
18537
18615
  renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
18538
18616
  }
18539
- if (chunksCarryAudio && existsSync25(planAudioPath)) {
18617
+ if (chunksCarryAudio && existsSync26(planAudioPath)) {
18540
18618
  if (options.sliceChunkAudio) {
18541
18619
  stageStarted = Date.now();
18542
18620
  await writeChunkAudioSlices({
@@ -18654,7 +18732,7 @@ async function plan(projectDir, config, planDir, options = {}) {
18654
18732
 
18655
18733
  // src/services/distributed/renderChunk.ts
18656
18734
  import { randomBytes } from "node:crypto";
18657
- import { existsSync as existsSync28, mkdirSync as mkdirSync19, readFileSync as readFileSync13, readdirSync as readdirSync11, rmSync as rmSync17, writeFileSync as writeFileSync13 } from "node:fs";
18735
+ import { existsSync as existsSync29, mkdirSync as mkdirSync19, readFileSync as readFileSync13, readdirSync as readdirSync11, rmSync as rmSync17, writeFileSync as writeFileSync13 } from "node:fs";
18658
18736
  import { cpus as cpus3 } from "node:os";
18659
18737
  import { join as join44 } from "node:path";
18660
18738
  import {
@@ -18681,7 +18759,7 @@ import {
18681
18759
  } from "@hyperframes/engine";
18682
18760
  import {
18683
18761
  copyFileSync as copyFileSync8,
18684
- existsSync as existsSync27,
18762
+ existsSync as existsSync28,
18685
18763
  lstatSync as lstatSync3,
18686
18764
  mkdirSync as mkdirSync18,
18687
18765
  mkdtempSync as mkdtempSync6,
@@ -18692,14 +18770,14 @@ import {
18692
18770
  writeFileSync as writeFileSync12
18693
18771
  } from "node:fs";
18694
18772
  import { tmpdir as tmpdir3 } from "node:os";
18695
- import { dirname as dirname17, isAbsolute as isAbsolute4, join as join43, relative as relative6, resolve as resolve10, sep as sep3 } from "node:path";
18773
+ import { dirname as dirname17, isAbsolute as isAbsolute4, join as join43, relative as relative6, resolve as resolve10, sep as sep4 } from "node:path";
18696
18774
  import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
18697
18775
  import { createFrameLookupTable as createFrameLookupTable5, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
18698
18776
 
18699
18777
  // src/services/distributed/planV2Publisher.ts
18700
18778
  import {
18701
18779
  copyFileSync as copyFileSync7,
18702
- existsSync as existsSync26,
18780
+ existsSync as existsSync27,
18703
18781
  linkSync as linkSync2,
18704
18782
  mkdirSync as mkdirSync17,
18705
18783
  mkdtempSync as mkdtempSync5,
@@ -18768,7 +18846,7 @@ var LocalPlanV2ArtifactPublisher = class {
18768
18846
  #linkFile;
18769
18847
  #committed = false;
18770
18848
  constructor(destinationDir, options = {}) {
18771
- if (existsSync26(destinationDir)) {
18849
+ if (existsSync27(destinationDir)) {
18772
18850
  throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
18773
18851
  }
18774
18852
  this.destinationDir = destinationDir;
@@ -18785,7 +18863,7 @@ var LocalPlanV2ArtifactPublisher = class {
18785
18863
  );
18786
18864
  }
18787
18865
  const destinationPath = planV2BlobPath(this.temporaryDir, digest);
18788
- if (existsSync26(destinationPath)) return;
18866
+ if (existsSync27(destinationPath)) return;
18789
18867
  mkdirSync17(dirname16(destinationPath), { recursive: true });
18790
18868
  const stagingDir = mkdtempSync5(join42(dirname16(destinationPath), ".plan-v2-blob-"));
18791
18869
  const temporaryPath = join42(stagingDir, "blob");
@@ -18803,7 +18881,7 @@ var LocalPlanV2ArtifactPublisher = class {
18803
18881
  }
18804
18882
  async commitManifest(manifestBytes) {
18805
18883
  for (const digest of new Set(manifestDigests(manifestBytes))) {
18806
- if (!existsSync26(planV2BlobPath(this.temporaryDir, digest))) {
18884
+ if (!existsSync27(planV2BlobPath(this.temporaryDir, digest))) {
18807
18885
  throw new PlanV2IntegrityError(
18808
18886
  `cannot commit manifest before referenced blob is durable: ${digest}`
18809
18887
  );
@@ -18859,7 +18937,7 @@ function listFiles(root) {
18859
18937
  walk(absolutePath);
18860
18938
  } else if (entry.isFile()) {
18861
18939
  files.push({
18862
- path: relative6(rootResolved, absolutePath).split(sep3).join("/"),
18940
+ path: relative6(rootResolved, absolutePath).split(sep4).join("/"),
18863
18941
  absolutePath
18864
18942
  });
18865
18943
  } else {
@@ -18882,7 +18960,7 @@ function assertValidExtractionCacheCompleteSentinel(path) {
18882
18960
  }
18883
18961
  function validateExtractionCacheCompleteSentinels(executionPlanDir) {
18884
18962
  const videoRoot = join43(executionPlanDir, "video-frames");
18885
- if (!existsSync27(videoRoot)) return;
18963
+ if (!existsSync28(videoRoot)) return;
18886
18964
  for (const videoEntry of readdirSync10(videoRoot, { withFileTypes: true })) {
18887
18965
  if (!videoEntry.isDirectory()) continue;
18888
18966
  const videoDir = join43(videoRoot, videoEntry.name);
@@ -18899,7 +18977,7 @@ function isExtractionCacheCompleteSentinelPath(path) {
18899
18977
  function resolveExtractedVideoOutputDir(planDir, videoId) {
18900
18978
  const videoRoot = resolve10(planDir, "video-frames");
18901
18979
  const outputDir = resolve10(videoRoot, videoId);
18902
- if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${sep3}`)) {
18980
+ if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${sep4}`)) {
18903
18981
  throw new PlanV2IntegrityError(`unsafe extracted video id: ${JSON.stringify(videoId)}`);
18904
18982
  }
18905
18983
  return outputDir;
@@ -18983,7 +19061,7 @@ function parsePlanVideosJson2(value) {
18983
19061
  }
18984
19062
  function materializeExtractedVideoDirectories(planDir) {
18985
19063
  const videosPath = join43(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
18986
- if (!existsSync27(videosPath)) return;
19064
+ if (!existsSync28(videosPath)) return;
18987
19065
  const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
18988
19066
  for (const video of videos.extracted) {
18989
19067
  mkdirSync18(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
@@ -19017,9 +19095,9 @@ function parseChunkSlices(value) {
19017
19095
  }
19018
19096
  function buildVideoChunkDependencies(executionPlanDir, dimensions) {
19019
19097
  const videoRoot = join43(executionPlanDir, "video-frames");
19020
- const hasExtractedFrames = existsSync27(videoRoot) && listFiles(videoRoot).length > 0;
19098
+ const hasExtractedFrames = existsSync28(videoRoot) && listFiles(videoRoot).length > 0;
19021
19099
  const videosPath = join43(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19022
- if (!existsSync27(videosPath)) {
19100
+ if (!existsSync28(videosPath)) {
19023
19101
  return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
19024
19102
  }
19025
19103
  const chunksPath = join43(executionPlanDir, "meta", "chunks.json");
@@ -19059,7 +19137,7 @@ function collectChunkVideoDependencies(input) {
19059
19137
  for (let frame = chunk.startFrame; frame < chunk.endFrame; frame++) {
19060
19138
  const globalTime = frame * input.fpsDen / input.fpsNum;
19061
19139
  for (const [videoId, payload] of input.table.getActiveFramePayloads(globalTime)) {
19062
- const path = input.deferredSources.get(videoId) ?? relative6(resolve10(input.executionPlanDir), payload.framePath).split(sep3).join("/");
19140
+ const path = input.deferredSources.get(videoId) ?? relative6(resolve10(input.executionPlanDir), payload.framePath).split(sep4).join("/");
19063
19141
  assertSafeRelativePath(path);
19064
19142
  const indexes = mutable.get(path) ?? /* @__PURE__ */ new Set();
19065
19143
  indexes.add(chunk.index);
@@ -19076,7 +19154,7 @@ function computeManifestHash(manifest) {
19076
19154
  return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
19077
19155
  }
19078
19156
  function writeBlob(sourcePath, destinationPath) {
19079
- if (existsSync27(destinationPath)) return;
19157
+ if (existsSync28(destinationPath)) return;
19080
19158
  mkdirSync18(dirname17(destinationPath), { recursive: true });
19081
19159
  const temporaryDir = mkdtempSync6(join43(dirname17(destinationPath), ".plan-v2-blob-"));
19082
19160
  const temporaryPath = join43(temporaryDir, "blob");
@@ -19116,7 +19194,7 @@ async function putTasks(tasks) {
19116
19194
  }
19117
19195
  function buildPlanV2Publication(executionPlanDir, digests) {
19118
19196
  const executionPlanPath = join43(executionPlanDir, "plan.json");
19119
- if (!existsSync27(executionPlanPath)) {
19197
+ if (!existsSync28(executionPlanPath)) {
19120
19198
  throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
19121
19199
  }
19122
19200
  const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
@@ -19196,7 +19274,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
19196
19274
  };
19197
19275
  }
19198
19276
  function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
19199
- if (existsSync27(planV2Dir)) {
19277
+ if (existsSync28(planV2Dir)) {
19200
19278
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
19201
19279
  }
19202
19280
  const publication = buildPlanV2Publication(executionPlanDir);
@@ -19282,7 +19360,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
19282
19360
  }
19283
19361
  }
19284
19362
  async function planV2(projectDir, config, planV2Dir) {
19285
- if (existsSync27(planV2Dir)) {
19363
+ if (existsSync28(planV2Dir)) {
19286
19364
  throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
19287
19365
  }
19288
19366
  const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
@@ -19436,7 +19514,7 @@ function parsePlanV2Manifest(value) {
19436
19514
  }
19437
19515
  function readPlanV2Manifest(planV2Dir) {
19438
19516
  const manifestPath = join43(planV2Dir, "plan.json");
19439
- if (!existsSync27(manifestPath)) {
19517
+ if (!existsSync28(manifestPath)) {
19440
19518
  throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
19441
19519
  }
19442
19520
  const value = readJsonFile(manifestPath, "v2 manifest");
@@ -19455,7 +19533,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
19455
19533
  }
19456
19534
  function verifyBlob(planV2Dir, artifact) {
19457
19535
  const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
19458
- if (!existsSync27(sourcePath)) {
19536
+ if (!existsSync28(sourcePath)) {
19459
19537
  throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
19460
19538
  }
19461
19539
  const stats = statSync11(sourcePath);
@@ -19468,7 +19546,7 @@ function verifyBlob(planV2Dir, artifact) {
19468
19546
  return sourcePath;
19469
19547
  }
19470
19548
  function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
19471
- if (existsSync27(destinationDir)) {
19549
+ if (existsSync28(destinationDir)) {
19472
19550
  throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
19473
19551
  }
19474
19552
  const manifest = readPlanV2Manifest(planV2Dir);
@@ -19533,7 +19611,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
19533
19611
  }
19534
19612
  function readValidatedPlanV2Materialization(planDir, expectedTarget) {
19535
19613
  const markerPath = join43(planDir, PLAN_V2_MATERIALIZATION_MARKER);
19536
- if (!existsSync27(markerPath)) return null;
19614
+ if (!existsSync28(markerPath)) return null;
19537
19615
  const marker = readJsonFile(markerPath, "materialization marker");
19538
19616
  if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
19539
19617
  throw new PlanV2IntegrityError("malformed materialization marker");
@@ -19551,7 +19629,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
19551
19629
  for (const artifact of required) {
19552
19630
  if (Object.hasOwn(remoteSources, artifact.path)) continue;
19553
19631
  const path = join43(planDir, ...artifact.path.split("/"));
19554
- if (!existsSync27(path) || statSync11(path).size !== artifact.sizeBytes) {
19632
+ if (!existsSync28(path) || statSync11(path).size !== artifact.sizeBytes) {
19555
19633
  throw new PlanV2IntegrityError(
19556
19634
  `materialized artifact missing or truncated: ${artifact.path}`
19557
19635
  );
@@ -19651,12 +19729,23 @@ async function runCaptureWithScreenshotFallback(input) {
19651
19729
  try {
19652
19730
  return await input.run(input.forceScreenshot);
19653
19731
  } catch (error) {
19654
- if (input.forceScreenshot || await input.canRetry?.() === false) throw error;
19655
- if (!shouldRetryChunkCaptureWithScreenshot(error)) throw error;
19732
+ if (input.forceScreenshot || await input.canRetry?.() === false)
19733
+ throw imageMissingIn(error) ?? error;
19734
+ if (!shouldRetryChunkCaptureWithScreenshot(error)) throw imageMissingIn(error) ?? error;
19656
19735
  input.onFallback?.(error);
19657
19736
  await input.resetForScreenshotRetry();
19658
- return await input.run(true);
19737
+ return await input.run(true).catch((retryError) => {
19738
+ throw imageMissingIn(retryError) ?? retryError;
19739
+ });
19740
+ }
19741
+ }
19742
+ function imageMissingIn(error) {
19743
+ let e = error;
19744
+ for (let depth = 0; depth < 5 && typeof e === "object" && e !== null; depth++) {
19745
+ if (e.name === "ImageMissingFromFrameError") return e;
19746
+ e = e.cause;
19659
19747
  }
19748
+ return void 0;
19660
19749
  }
19661
19750
  async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBeginFrameLiveness2) {
19662
19751
  if (session.launchCaptureMode !== "beginframe") return false;
@@ -19691,7 +19780,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19691
19780
  const planJsonPath = join44(planDir, "plan.json");
19692
19781
  const encoderJsonPath = join44(planDir, "meta", "encoder.json");
19693
19782
  const chunksJsonPath = join44(planDir, "meta", "chunks.json");
19694
- if (!existsSync28(planJsonPath)) {
19783
+ if (!existsSync29(planJsonPath)) {
19695
19784
  throw new RenderChunkValidationError(
19696
19785
  MISSING_PLAN_ARTIFACT,
19697
19786
  `[renderChunk] planDir is missing required artifact: ${planJsonPath}`
@@ -19707,7 +19796,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19707
19796
  const v2Manifest = v2Materialization?.manifest ?? null;
19708
19797
  const remoteSources = v2Materialization?.remoteSources ?? {};
19709
19798
  for (const required of [encoderJsonPath, chunksJsonPath]) {
19710
- if (!existsSync28(required)) {
19799
+ if (!existsSync29(required)) {
19711
19800
  throw new RenderChunkValidationError(
19712
19801
  MISSING_PLAN_ARTIFACT,
19713
19802
  `[renderChunk] planDir is missing required artifact: ${required}`
@@ -19718,7 +19807,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19718
19807
  const chunks = JSON.parse(readFileSync13(chunksJsonPath, "utf-8"));
19719
19808
  const videosJsonPath = join44(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19720
19809
  let planVideos = null;
19721
- if (existsSync28(videosJsonPath)) {
19810
+ if (existsSync29(videosJsonPath)) {
19722
19811
  try {
19723
19812
  planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync13(videosJsonPath, "utf-8")));
19724
19813
  } catch (err) {
@@ -19749,7 +19838,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19749
19838
  const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
19750
19839
  const framesInChunk = range.endFrame - range.startFrame;
19751
19840
  const compiledDir = join44(planDir, "compiled");
19752
- if (!existsSync28(compiledDir)) {
19841
+ if (!existsSync29(compiledDir)) {
19753
19842
  throw new RenderChunkValidationError(
19754
19843
  MISSING_PLAN_ARTIFACT,
19755
19844
  `[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
@@ -19999,10 +20088,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19999
20088
  const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
20000
20089
  const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
20001
20090
  if (isPngSequence) {
20002
- if (!existsSync28(outputChunkPath)) mkdirSync19(outputChunkPath, { recursive: true });
20091
+ if (!existsSync29(outputChunkPath)) mkdirSync19(outputChunkPath, { recursive: true });
20003
20092
  } else {
20004
20093
  const outDir = join44(outputChunkPath, "..");
20005
- if (!existsSync28(outDir)) mkdirSync19(outDir, { recursive: true });
20094
+ if (!existsSync29(outDir)) mkdirSync19(outDir, { recursive: true });
20006
20095
  }
20007
20096
  const encodeInput = {
20008
20097
  job,
@@ -20902,7 +20991,7 @@ import {
20902
20991
 
20903
20992
  // src/server.ts
20904
20993
  import {
20905
- existsSync as existsSync31,
20994
+ existsSync as existsSync32,
20906
20995
  mkdirSync as mkdirSync21,
20907
20996
  statSync as statSync13,
20908
20997
  mkdtempSync as mkdtempSync8,
@@ -20925,7 +21014,7 @@ import {
20925
21014
 
20926
21015
  // src/services/hyperframeLint.ts
20927
21016
  import {
20928
- existsSync as existsSync29,
21017
+ existsSync as existsSync30,
20929
21018
  readFileSync as readFileSync15,
20930
21019
  statSync as statSync12,
20931
21020
  openSync as openSync7,
@@ -20933,7 +21022,7 @@ import {
20933
21022
  closeSync as closeSync7,
20934
21023
  constants as constants3
20935
21024
  } from "node:fs";
20936
- import { resolve as resolve11, join as join46, relative as relative7, isAbsolute as isAbsolute5, sep as sep4 } from "node:path";
21025
+ import { resolve as resolve11, join as join46, relative as relative7, isAbsolute as isAbsolute5, sep as sep5 } from "node:path";
20937
21026
  import { lintHyperframeHtml } from "@hyperframes/lint";
20938
21027
  function isStringRecord(value) {
20939
21028
  if (!value || typeof value !== "object" || Array.isArray(value)) {
@@ -20979,7 +21068,7 @@ function readEntryHtml(filePath) {
20979
21068
  }
20980
21069
  function readProjectEntryFile(projectDir, preferredEntryFile) {
20981
21070
  const absProjectDir = resolve11(projectDir);
20982
- if (!existsSync29(absProjectDir) || !statSync12(absProjectDir).isDirectory()) {
21071
+ if (!existsSync30(absProjectDir) || !statSync12(absProjectDir).isDirectory()) {
20983
21072
  return { error: `Project directory not found: ${absProjectDir}` };
20984
21073
  }
20985
21074
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
@@ -20988,7 +21077,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
20988
21077
  for (const entryFile of entryCandidates) {
20989
21078
  const absoluteEntryPath = resolve11(absProjectDir, entryFile);
20990
21079
  const entryRelativePath = relative7(absProjectDir, absoluteEntryPath);
20991
- if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${sep4}`) || isAbsolute5(entryRelativePath)) {
21080
+ if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${sep5}`) || isAbsolute5(entryRelativePath)) {
20992
21081
  return { error: `Entry file must stay inside project directory: ${entryFile}` };
20993
21082
  }
20994
21083
  const html = readEntryHtml(absoluteEntryPath);
@@ -21048,7 +21137,7 @@ async function runHyperframeLint(prepared) {
21048
21137
  import { Worker as Worker2 } from "node:worker_threads";
21049
21138
  import { fileURLToPath as fileURLToPath6 } from "node:url";
21050
21139
  import { dirname as dirname18, join as join47 } from "node:path";
21051
- import { existsSync as existsSync30 } from "node:fs";
21140
+ import { existsSync as existsSync31 } from "node:fs";
21052
21141
  var DEFAULT_HEALTH_PORT = 9848;
21053
21142
  async function startHealthWorker(options = {}) {
21054
21143
  const log = options.logger ?? defaultLogger2();
@@ -21125,7 +21214,7 @@ function resolveWorkerEntry2() {
21125
21214
  const here = dirname18(fileURLToPath6(import.meta.url));
21126
21215
  const candidates = [join47(here, "healthWorkerThread.js"), join47(here, "healthWorkerThread.ts")];
21127
21216
  for (const candidate of candidates) {
21128
- if (existsSync30(candidate)) return candidate;
21217
+ if (existsSync31(candidate)) return candidate;
21129
21218
  }
21130
21219
  return void 0;
21131
21220
  }
@@ -21271,7 +21360,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
21271
21360
  const { input, cleanupProjectDir } = prepared;
21272
21361
  const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
21273
21362
  const outputDir = dirname19(absoluteOutputPath);
21274
- if (!existsSync31(outputDir)) mkdirSync21(outputDir, { recursive: true });
21363
+ if (!existsSync32(outputDir)) mkdirSync21(outputDir, { recursive: true });
21275
21364
  return { input, cleanupProjectDir, absoluteOutputPath };
21276
21365
  }
21277
21366
  function validateRenderOverrides(body) {
@@ -21312,11 +21401,11 @@ function prepareProjectDirectory(projectDir, options) {
21312
21401
  const candidate = nonEmptyString(projectDir);
21313
21402
  if (!candidate) return null;
21314
21403
  const absProjectDir = resolve12(candidate);
21315
- if (!existsSync31(absProjectDir) || !statSync13(absProjectDir).isDirectory()) {
21404
+ if (!existsSync32(absProjectDir) || !statSync13(absProjectDir).isDirectory()) {
21316
21405
  return { error: `Project directory not found: ${absProjectDir}` };
21317
21406
  }
21318
21407
  const entry = options.entryFile || "index.html";
21319
- if (!existsSync31(resolve12(absProjectDir, entry))) {
21408
+ if (!existsSync32(resolve12(absProjectDir, entry))) {
21320
21409
  return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
21321
21410
  }
21322
21411
  return { prepared: { input: { projectDir: absProjectDir, ...options } } };
@@ -21408,7 +21497,7 @@ function cleanupTempDir(dir, log) {
21408
21497
  }
21409
21498
  }
21410
21499
  function outputFileSize(path) {
21411
- return existsSync31(path) ? statSync13(path).size : 0;
21500
+ return existsSync32(path) ? statSync13(path).size : 0;
21412
21501
  }
21413
21502
  function createBlockingProgressReporter(log, requestId) {
21414
21503
  let lastLoggedPct = -10;
@@ -21712,7 +21801,7 @@ function createRenderHandlers(options = {}) {
21712
21801
  if (!artifact) {
21713
21802
  return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
21714
21803
  }
21715
- if (!existsSync31(artifact.path)) {
21804
+ if (!existsSync32(artifact.path)) {
21716
21805
  store.delete(token);
21717
21806
  return c.json({ success: false, error: "Output artifact file missing" }, 404);
21718
21807
  }
@@ -21818,7 +21907,7 @@ import {
21818
21907
  } from "@hyperframes/engine";
21819
21908
  import {
21820
21909
  cpSync as cpSync3,
21821
- existsSync as existsSync32,
21910
+ existsSync as existsSync33,
21822
21911
  mkdirSync as mkdirSync22,
21823
21912
  mkdtempSync as mkdtempSync9,
21824
21913
  readFileSync as readFileSync16,
@@ -21873,7 +21962,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21873
21962
  }
21874
21963
  const format = plan2.dimensions.format;
21875
21964
  const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
21876
- if (!existsSync32(dirname20(outputPath))) {
21965
+ if (!existsSync33(dirname20(outputPath))) {
21877
21966
  mkdirSync22(dirname20(outputPath), { recursive: true });
21878
21967
  }
21879
21968
  const workDir = mkdtempSync9(`${outputPath}.assemble-work-`);
@@ -21897,7 +21986,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21897
21986
  try {
21898
21987
  const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
21899
21988
  const finalPath = join49(workDir, `output.${format}`);
21900
- const audio = audioPath !== null && existsSync32(audioPath) ? audioPath : null;
21989
+ const audio = audioPath !== null && existsSync33(audioPath) ? audioPath : null;
21901
21990
  await writeJoined(ctx, videoSources, audio, finalPath);
21902
21991
  renameWithRetry11(finalPath, outputPath);
21903
21992
  log.info("[assemble] stage timings", {
@@ -21915,7 +22004,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21915
22004
  });
21916
22005
  }
21917
22006
  }
21918
- const fileSize = existsSync32(outputPath) ? statSync14(outputPath).size : 0;
22007
+ const fileSize = existsSync33(outputPath) ? statSync14(outputPath).size : 0;
21919
22008
  return {
21920
22009
  ...referenceBackgroundResult8(plan2[REFERENCE_BACKGROUND_FIELD6]),
21921
22010
  outputPath,
@@ -21927,13 +22016,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
21927
22016
  function readAssemblePlan(planDir, chunkPaths) {
21928
22017
  const planJsonPath = join49(planDir, "plan.json");
21929
22018
  const chunksJsonPath = join49(planDir, "meta", "chunks.json");
21930
- if (!existsSync32(planJsonPath)) {
22019
+ if (!existsSync33(planJsonPath)) {
21931
22020
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
21932
22021
  }
21933
22022
  const plan2 = JSON.parse(readFileSync16(planJsonPath, "utf-8"));
21934
22023
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
21935
22024
  validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
21936
- if (!existsSync32(chunksJsonPath)) {
22025
+ if (!existsSync33(chunksJsonPath)) {
21937
22026
  throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
21938
22027
  }
21939
22028
  const chunks = JSON.parse(readFileSync16(chunksJsonPath, "utf-8"));
@@ -21948,7 +22037,7 @@ function readAssemblePlan(planDir, chunkPaths) {
21948
22037
  }
21949
22038
  }
21950
22039
  for (const path of chunkPaths.flat()) {
21951
- if (!existsSync32(path)) {
22040
+ if (!existsSync33(path)) {
21952
22041
  throw new Error(`[assemble] chunk path does not exist: ${path}`);
21953
22042
  }
21954
22043
  }
@@ -22087,7 +22176,7 @@ function planFormat(planDir) {
22087
22176
  }
22088
22177
  function lockedAudio(planDir) {
22089
22178
  const encoderJsonPath = join49(planDir, "meta", "encoder.json");
22090
- if (!existsSync32(encoderJsonPath)) return {};
22179
+ if (!existsSync33(encoderJsonPath)) return {};
22091
22180
  return JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
22092
22181
  }
22093
22182
  function readCfrEncoder(planDir, format) {
@@ -22097,7 +22186,7 @@ function readCfrEncoder(planDir, format) {
22097
22186
  );
22098
22187
  }
22099
22188
  const encoderJsonPath = join49(planDir, "meta", "encoder.json");
22100
- if (!existsSync32(encoderJsonPath)) {
22189
+ if (!existsSync33(encoderJsonPath)) {
22101
22190
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
22102
22191
  }
22103
22192
  const encoderJson = JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
@@ -22109,7 +22198,7 @@ function readCfrEncoder(planDir, format) {
22109
22198
  return encoderJson;
22110
22199
  }
22111
22200
  function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
22112
- if (existsSync32(outputPath)) rmSync20(outputPath, { recursive: true, force: true });
22201
+ if (existsSync33(outputPath)) rmSync20(outputPath, { recursive: true, force: true });
22113
22202
  mkdirSync22(outputPath, { recursive: true });
22114
22203
  let globalIdx = 0;
22115
22204
  for (const chunkDir of chunkPaths) {
@@ -22133,7 +22222,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
22133
22222
  `[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
22134
22223
  );
22135
22224
  }
22136
- if (audioPath !== null && existsSync32(audioPath)) {
22225
+ if (audioPath !== null && existsSync33(audioPath)) {
22137
22226
  const sidecar = join49(outputPath, MIXED_AUDIO_FILENAME4);
22138
22227
  cpSync3(audioPath, sidecar);
22139
22228
  }