@hyperframes/producer 0.9.13 → 0.9.14

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
@@ -3741,6 +3741,7 @@ function createStallClock(now = () => Date.now()) {
3741
3741
 
3742
3742
  // src/services/render/stages/captureStreamingStage.ts
3743
3743
  var DEFAULT_CAPTURE_STALL_MS = 6e4;
3744
+ var SESSION_INIT_STALL_MS = 3e5;
3744
3745
  var DE_STALL_POLL_MS = 5e3;
3745
3746
  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.";
3746
3747
  function poolFailure(error, probeSession) {
@@ -3771,6 +3772,14 @@ function captureModeLabel(mode) {
3771
3772
  if (mode === "beginframe") return "BeginFrame";
3772
3773
  return "screenshot";
3773
3774
  }
3775
+ var OPENING_CAPTURE_SESSION = {
3776
+ captureMode: "screenshot",
3777
+ captureStep: "opening the capture session"
3778
+ };
3779
+ var OPENING_RISK_SESSION = {
3780
+ captureMode: "screenshot",
3781
+ captureStep: "opening the screenshot session for effect frames"
3782
+ };
3774
3783
  var SequentialCaptureStallError = class extends Error {
3775
3784
  captureMode;
3776
3785
  constructor(session, stallTimeoutMs, frameIndex, totalFrames) {
@@ -3920,7 +3929,12 @@ async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEnc
3920
3929
  const guard = createDrainFrameGuard({ log, stats, frameTime });
3921
3930
  const guardFrame = (item, buf) => item.trusted ? Promise.resolve(buf) : guard(session, item.idx, buf);
3922
3931
  const captureRisk = async (idx) => {
3923
- await risk.open();
3932
+ await raceAgainstStall(
3933
+ risk.open(),
3934
+ stallTimeoutMs - quiet.idle(0),
3935
+ { session: OPENING_RISK_SESSION, frameIndex: idx, totalFrames, stallTimeoutMs },
3936
+ abortSignal
3937
+ );
3924
3938
  quiet.reset();
3925
3939
  return raceAgainstStall(
3926
3940
  risk.capture(idx, frameTime(idx)),
@@ -4279,19 +4293,38 @@ async function runCaptureStreamingStage(input) {
4279
4293
  let resumes = 0;
4280
4294
  for (; ; ) {
4281
4295
  const videoInjector = createRenderVideoFrameInjector();
4282
- const session = probeSession ?? await createCaptureSession(
4283
- fileServer.url,
4284
- framesDir,
4285
- buildCaptureOptions(),
4286
- videoInjector,
4287
- captureCfg
4296
+ const initStallMs = Math.max(SESSION_INIT_STALL_MS, resolveCaptureStallTimeoutMs());
4297
+ const setup = createStallClock();
4298
+ const withinInitBudget = (work, stalled) => raceAgainstStall(
4299
+ work,
4300
+ initStallMs - setup.idle(0),
4301
+ { session: stalled, frameIndex: resume.from, totalFrames, stallTimeoutMs: initStallMs },
4302
+ abortSignal
4288
4303
  );
4304
+ const openSession = async () => {
4305
+ const opening = createCaptureSession(
4306
+ fileServer.url,
4307
+ framesDir,
4308
+ buildCaptureOptions(),
4309
+ videoInjector,
4310
+ captureCfg
4311
+ );
4312
+ try {
4313
+ return await withinInitBudget(opening, OPENING_CAPTURE_SESSION);
4314
+ } catch (error) {
4315
+ void opening.then(closeCaptureSession, () => {
4316
+ });
4317
+ throw error;
4318
+ }
4319
+ };
4320
+ const session = probeSession ?? await openSession();
4289
4321
  captureBeyondViewport = session.options.captureBeyondViewport;
4290
4322
  if (probeSession) {
4291
4323
  prepareCaptureSessionForReuse(session, framesDir, videoInjector);
4292
4324
  probeSession = null;
4293
4325
  }
4294
4326
  let riskSession = null;
4327
+ let attemptClosed = false;
4295
4328
  const openRiskSession = async () => {
4296
4329
  const opened = await createCaptureSession(
4297
4330
  fileServer.url,
@@ -4305,6 +4338,10 @@ async function runCaptureStreamingStage(input) {
4305
4338
  enableBrowserPool: false
4306
4339
  }
4307
4340
  );
4341
+ if (attemptClosed) {
4342
+ await closeCaptureSession(opened);
4343
+ throw new Error("[Render] Capture attempt ended while its screenshot session opened");
4344
+ }
4308
4345
  riskSession = opened;
4309
4346
  await initializeSession(opened);
4310
4347
  return opened;
@@ -4312,9 +4349,9 @@ async function runCaptureStreamingStage(input) {
4312
4349
  let resumed = false;
4313
4350
  try {
4314
4351
  if (!session.isInitialized) {
4315
- await initializeSession(session);
4352
+ await withinInitBudget(initializeSession(session), session);
4316
4353
  }
4317
- await completeDeferredDrawElementInit(session);
4354
+ await withinInitBudget(completeDeferredDrawElementInit(session), session);
4318
4355
  assertNotAborted();
4319
4356
  lastBrowserConsole = session.browserConsoleBuffer;
4320
4357
  if (session.workerEncodeEnabled) {
@@ -4397,6 +4434,7 @@ async function runCaptureStreamingStage(input) {
4397
4434
  resumes++;
4398
4435
  resumed = true;
4399
4436
  } finally {
4437
+ attemptClosed = true;
4400
4438
  lastBrowserConsole = session.browserConsoleBuffer;
4401
4439
  await closeCaptureSession(session);
4402
4440
  if (riskSession) await closeCaptureSession(riskSession);
@@ -5863,7 +5901,7 @@ import { suggestMatchingPreset } from "@hyperframes/core";
5863
5901
  // src/services/htmlCompiler.ts
5864
5902
  import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
5865
5903
  import { createHash as createHash6 } from "node:crypto";
5866
- import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
5904
+ import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7 } from "fs";
5867
5905
  import { join as join15, dirname as dirname7, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
5868
5906
  import { parseHTML as parseHTML5 } from "linkedom";
5869
5907
  import {
@@ -7838,6 +7876,25 @@ ${fontFaceBlocks.join("\n")}
7838
7876
  }
7839
7877
  return result;
7840
7878
  }
7879
+ function projectStylesheetFontFamilies(html, projectDir) {
7880
+ const families = [];
7881
+ for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
7882
+ const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
7883
+ if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
7884
+ const sheet = resolve7(projectDir, decodedUrlPath(href).replace(/^(?:\.?\/)+/, ""));
7885
+ if (!isPathInside2(sheet, projectDir) || !existsSync10(sheet)) continue;
7886
+ for (const face of extractFontFaceBlocks(readFileSync7(sheet, "utf8"))) {
7887
+ const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
7888
+ const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
7889
+ const path = decodedUrlPath(url);
7890
+ const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(dirname7(sheet), path);
7891
+ return isPathInside2(file, projectDir) && existsSync10(file);
7892
+ });
7893
+ if (family && shipsFile) families.push(family);
7894
+ }
7895
+ }
7896
+ return families;
7897
+ }
7841
7898
  function collectFontFaceUrls(html) {
7842
7899
  const styleBlockRe = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
7843
7900
  const urlSet = /* @__PURE__ */ new Set();
@@ -7869,11 +7926,12 @@ function collectFontFaceUrls(html) {
7869
7926
  return urlSet;
7870
7927
  }
7871
7928
  async function localizeRemoteFontFaces(html, downloadDir) {
7872
- const processed = await inlineExternalFontStylesheets(html);
7873
- const urlSet = collectFontFaceUrls(processed);
7929
+ return localizeFontFaceUrls(await inlineExternalFontStylesheets(html), downloadDir);
7930
+ }
7931
+ function localizeFontFaceUrls(html, downloadDir) {
7874
7932
  return downloadAndRewriteUrls(
7875
- urlSet,
7876
- processed,
7933
+ collectFontFaceUrls(html),
7934
+ html,
7877
7935
  join15(downloadDir, REMOTE_MEDIA_SUBDIR),
7878
7936
  "Remote font download failed",
7879
7937
  "Localized remote font face(s)",
@@ -8041,15 +8099,17 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
8041
8099
  const hasShaderTransitions = detectShaderTransitionUsage(sanitizedHtml);
8042
8100
  const usesThreeDTransforms = detectThreeDTransformUsage(sanitizedHtml);
8043
8101
  const hasAncestorBackgroundImage = detectAncestorBackgroundImage(sanitizedHtml);
8102
+ const fontSheetHtml = await inlineExternalFontStylesheets(
8103
+ promoteCssImportsToLinkTags(sanitizedHtml)
8104
+ );
8044
8105
  const normalizedFontHtml = normalizeSystemFontPrimaryFamilies(
8045
- injectTextRenderingRule(
8046
- coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml))
8047
- )
8106
+ injectTextRenderingRule(coalesceHeadStylesAndBodyScripts(fontSheetHtml))
8048
8107
  );
8049
8108
  const coalescedHtml = await injectDeterministicFontFaces(normalizedFontHtml, {
8050
8109
  logger: defaultLogger,
8051
8110
  failClosedFontFetch: options.failClosedFontFetch === true,
8052
8111
  allowSystemFontCapture: options.allowSystemFontCapture,
8112
+ projectFontFamilies: projectStylesheetFontFamilies(fontSheetHtml, projectDir),
8053
8113
  abortSignal: options.abortSignal
8054
8114
  });
8055
8115
  const assembledHtml = await inlineExternalScripts(coalescedHtml);
@@ -8075,7 +8135,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
8075
8135
  );
8076
8136
  const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
8077
8137
  const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
8078
- const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeRemoteFontFaces(htmlWithLocalBg, downloadDir);
8138
+ const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeFontFaceUrls(htmlWithLocalBg, downloadDir);
8079
8139
  const {
8080
8140
  html: htmlWithJpegs,
8081
8141
  preparedAssets: preparedHeifAssets,
@@ -11950,7 +12010,7 @@ import {
11950
12010
  existsSync as existsSync17,
11951
12011
  mkdirSync as mkdirSync9,
11952
12012
  readdirSync as readdirSync4,
11953
- readFileSync as readFileSync7,
12013
+ readFileSync as readFileSync8,
11954
12014
  rmSync as rmSync9,
11955
12015
  statSync as statSync7,
11956
12016
  writeFileSync as writeFileSync5
@@ -12044,7 +12104,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
12044
12104
  const error = formatFfmpegError(result.exitCode, result.stderr);
12045
12105
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
12046
12106
  }
12047
- const gif = readFileSync7(outputPath);
12107
+ const gif = readFileSync8(outputPath);
12048
12108
  if (!clearGifFramesBeforeNext(gif)) {
12049
12109
  const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
12050
12110
  return gifEncodeFailure(startTime, outputPath, error);
@@ -12077,7 +12137,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
12077
12137
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
12078
12138
  }
12079
12139
  }
12080
- if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync7(outputPath)) !== false) {
12140
+ if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync8(outputPath)) !== false) {
12081
12141
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
12082
12142
  if (failure) return failure;
12083
12143
  }
@@ -15481,7 +15541,7 @@ import {
15481
15541
  REFERENCE_BACKGROUND_FIELD as REFERENCE_BACKGROUND_FIELD4,
15482
15542
  referenceBackgroundResult as referenceBackgroundResult6
15483
15543
  } from "@hyperframes/engine";
15484
- import { mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync7, readFileSync as readFileSync13, rmSync as rmSync16 } from "node:fs";
15544
+ import { mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync7, readFileSync as readFileSync14, rmSync as rmSync16 } from "node:fs";
15485
15545
  import { join as join42 } from "node:path";
15486
15546
  import { tmpdir as tmpdir4 } from "node:os";
15487
15547
 
@@ -15921,7 +15981,7 @@ import {
15921
15981
  // src/services/distributed/shared.ts
15922
15982
  import { execFile as execFileCallback } from "node:child_process";
15923
15983
  import { dirname as dirname14, join as join30 } from "node:path";
15924
- import { existsSync as existsSync19, readFileSync as readFileSync8 } from "node:fs";
15984
+ import { existsSync as existsSync19, readFileSync as readFileSync9 } from "node:fs";
15925
15985
  import { fileURLToPath as fileURLToPath5 } from "node:url";
15926
15986
  import { promisify as promisify2 } from "node:util";
15927
15987
  import {
@@ -16200,7 +16260,7 @@ function readProducerVersion() {
16200
16260
  const candidate = join30(current, "package.json");
16201
16261
  if (existsSync19(candidate)) {
16202
16262
  try {
16203
- const pkg = JSON.parse(readFileSync8(candidate, "utf-8"));
16263
+ const pkg = JSON.parse(readFileSync9(candidate, "utf-8"));
16204
16264
  if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
16205
16265
  cachedProducerVersion = pkg.version;
16206
16266
  return pkg.version;
@@ -16586,7 +16646,7 @@ async function readPlanReferenceBackground(input) {
16586
16646
  }
16587
16647
 
16588
16648
  // src/services/render/stages/freezePlan.ts
16589
- import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync9, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
16649
+ import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync10, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
16590
16650
  import { join as join34, relative as relative3, resolve as resolve9 } from "node:path";
16591
16651
  import {
16592
16652
  REFERENCE_BACKGROUND_FIELD,
@@ -16826,7 +16886,7 @@ function collectPlanAssetShas(planDir, digests) {
16826
16886
  const assets = [];
16827
16887
  for (const file of files) {
16828
16888
  if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
16829
- compositionHtml = readFileSync9(file.absolutePath);
16889
+ compositionHtml = readFileSync10(file.absolutePath);
16830
16890
  continue;
16831
16891
  }
16832
16892
  if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
@@ -16850,7 +16910,7 @@ function writePlanRemoteFiles(planDir, files) {
16850
16910
  }
16851
16911
  function readPlanRemoteFiles(planDir) {
16852
16912
  const path = join34(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
16853
- return existsSync21(path) ? JSON.parse(readFileSync9(path, "utf-8")) : [];
16913
+ return existsSync21(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
16854
16914
  }
16855
16915
  function recomputePlanHashFromPlanDir(planDir, digests) {
16856
16916
  const planJsonPath = join34(planDir, "plan.json");
@@ -16861,8 +16921,8 @@ function recomputePlanHashFromPlanDir(planDir, digests) {
16861
16921
  if (!existsSync21(encoderJsonPath)) {
16862
16922
  throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
16863
16923
  }
16864
- const planJson = JSON.parse(readFileSync9(planJsonPath, "utf-8"));
16865
- const encoderConfigCanonicalJson = readFileSync9(encoderJsonPath, "utf-8");
16924
+ const planJson = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
16925
+ const encoderConfigCanonicalJson = readFileSync10(encoderJsonPath, "utf-8");
16866
16926
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
16867
16927
  return computePlanHash({
16868
16928
  compositionHtml,
@@ -16969,7 +17029,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
16969
17029
  }
16970
17030
 
16971
17031
  // src/services/distributed/chunkAudio.ts
16972
- import { closeSync as closeSync6, existsSync as existsSync22, ftruncateSync, mkdirSync as mkdirSync13, openSync as openSync6, readFileSync as readFileSync10 } from "node:fs";
17032
+ import { closeSync as closeSync6, existsSync as existsSync22, ftruncateSync, mkdirSync as mkdirSync13, openSync as openSync6, readFileSync as readFileSync11 } from "node:fs";
16973
17033
  import { writeFileSync as writeFileSync8, writeSync } from "node:fs";
16974
17034
  import { join as join35 } from "node:path";
16975
17035
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
@@ -17057,7 +17117,7 @@ async function writeChunkAudioSlices(input) {
17057
17117
  offset += track.sizes[offsets.length - 1];
17058
17118
  }
17059
17119
  });
17060
- const bytes = readFileSync10(input.audioPath);
17120
+ const bytes = readFileSync11(input.audioPath);
17061
17121
  const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
17062
17122
  mkdirSync13(join35(input.planDir, SLICE_DIR), { recursive: true });
17063
17123
  input.chunks.forEach((_, index) => {
@@ -17076,8 +17136,8 @@ async function writeChunkAudioSlices(input) {
17076
17136
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
17077
17137
  const layoutPath = join35(planDir, SLICE_LAYOUT);
17078
17138
  if (!existsSync22(layoutPath)) return resolvePlanAudioPath(planDir);
17079
- const layout = JSON.parse(readFileSync10(layoutPath, "utf-8"));
17080
- const frame = readFileSync10(join35(planDir, SLICE_FRAME));
17139
+ const layout = JSON.parse(readFileSync11(layoutPath, "utf-8"));
17140
+ const frame = readFileSync11(join35(planDir, SLICE_FRAME));
17081
17141
  const fd = openSync6(scratchPath, "w");
17082
17142
  try {
17083
17143
  writeSync(fd, frame, 0, layout.payloadStart, 0);
@@ -17090,7 +17150,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
17090
17150
  );
17091
17151
  const start = layout.partStarts[chunkIndex];
17092
17152
  if (start !== void 0) {
17093
- const part = readFileSync10(join35(planDir, chunkAudioSlicePath(chunkIndex)));
17153
+ const part = readFileSync11(join35(planDir, chunkAudioSlicePath(chunkIndex)));
17094
17154
  writeSync(fd, part, 0, part.length, start);
17095
17155
  }
17096
17156
  ftruncateSync(fd, layout.sizeBytes);
@@ -18171,7 +18231,7 @@ async function plan(projectDir, config, planDir, options = {}) {
18171
18231
 
18172
18232
  // src/services/distributed/renderChunk.ts
18173
18233
  import { randomBytes } from "node:crypto";
18174
- import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync12, readdirSync as readdirSync10, rmSync as rmSync15, writeFileSync as writeFileSync12 } from "node:fs";
18234
+ import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync13, readdirSync as readdirSync10, rmSync as rmSync15, writeFileSync as writeFileSync12 } from "node:fs";
18175
18235
  import { cpus as cpus3 } from "node:os";
18176
18236
  import { join as join41 } from "node:path";
18177
18237
  import {
@@ -18202,7 +18262,7 @@ import {
18202
18262
  lstatSync as lstatSync3,
18203
18263
  mkdirSync as mkdirSync16,
18204
18264
  mkdtempSync as mkdtempSync6,
18205
- readFileSync as readFileSync11,
18265
+ readFileSync as readFileSync12,
18206
18266
  readdirSync as readdirSync9,
18207
18267
  rmSync as rmSync14,
18208
18268
  statSync as statSync10,
@@ -18351,7 +18411,7 @@ function isNonNegativeIntegerArray(value) {
18351
18411
  return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
18352
18412
  }
18353
18413
  function readJsonFile(path, label) {
18354
- const contents = readFileSync11(path, "utf-8");
18414
+ const contents = readFileSync12(path, "utf-8");
18355
18415
  try {
18356
18416
  const parsed = JSON.parse(contents);
18357
18417
  return parsed;
@@ -19185,10 +19245,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
19185
19245
  return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
19186
19246
  }
19187
19247
  function hashChunkOutput(outputPath, kind) {
19188
- if (kind === "file") return sha256Hex(readFileSync12(outputPath));
19248
+ if (kind === "file") return sha256Hex(readFileSync13(outputPath));
19189
19249
  const entries = readdirSync10(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
19190
19250
  const lines = entries.map(
19191
- (name) => `${name}\0${sha256Hex(readFileSync12(join41(outputPath, name)))}`
19251
+ (name) => `${name}\0${sha256Hex(readFileSync13(join41(outputPath, name)))}`
19192
19252
  );
19193
19253
  return sha256Hex(lines.join("\0"));
19194
19254
  }
@@ -19214,7 +19274,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19214
19274
  `[renderChunk] planDir is missing required artifact: ${planJsonPath}`
19215
19275
  );
19216
19276
  }
19217
- const plan2 = JSON.parse(readFileSync12(planJsonPath, "utf-8"));
19277
+ const plan2 = JSON.parse(readFileSync13(planJsonPath, "utf-8"));
19218
19278
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
19219
19279
  const planHashStarted = Date.now();
19220
19280
  const v2Materialization = readValidatedPlanV2Materialization(planDir, {
@@ -19231,13 +19291,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19231
19291
  );
19232
19292
  }
19233
19293
  }
19234
- const encoder = JSON.parse(readFileSync12(encoderJsonPath, "utf-8"));
19235
- const chunks = JSON.parse(readFileSync12(chunksJsonPath, "utf-8"));
19294
+ const encoder = JSON.parse(readFileSync13(encoderJsonPath, "utf-8"));
19295
+ const chunks = JSON.parse(readFileSync13(chunksJsonPath, "utf-8"));
19236
19296
  const videosJsonPath = join41(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
19237
19297
  let planVideos = null;
19238
19298
  if (existsSync26(videosJsonPath)) {
19239
19299
  try {
19240
- planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync12(videosJsonPath, "utf-8")));
19300
+ planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync13(videosJsonPath, "utf-8")));
19241
19301
  } catch (err) {
19242
19302
  if (err instanceof RenderChunkValidationError) throw err;
19243
19303
  throw new RenderChunkValidationError(
@@ -19348,7 +19408,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
19348
19408
  dimensions: plan2.dimensions,
19349
19409
  encoder,
19350
19410
  videoCount: planVideos?.videos.length ?? 0,
19351
- html: readFileSync12(join41(compiledDir, "index.html"), "utf-8"),
19411
+ html: readFileSync13(join41(compiledDir, "index.html"), "utf-8"),
19352
19412
  // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
19353
19413
  skipReadinessVideoIds: deferredVideos.filter(
19354
19414
  (video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
@@ -19810,7 +19870,7 @@ async function renderSpan(projectDir, config, span, outputPath) {
19810
19870
  planned.totalFrames
19811
19871
  );
19812
19872
  const slices = JSON.parse(
19813
- readFileSync13(join42(planDir, "meta", "chunks.json"), "utf-8")
19873
+ readFileSync14(join42(planDir, "meta", "chunks.json"), "utf-8")
19814
19874
  );
19815
19875
  const parts = [];
19816
19876
  for (const slice of slices) {
@@ -20443,7 +20503,7 @@ import {
20443
20503
  // src/services/hyperframeLint.ts
20444
20504
  import {
20445
20505
  existsSync as existsSync27,
20446
- readFileSync as readFileSync14,
20506
+ readFileSync as readFileSync15,
20447
20507
  statSync as statSync11,
20448
20508
  openSync as openSync7,
20449
20509
  fstatSync as fstatSync3,
@@ -20489,7 +20549,7 @@ function readEntryHtml(filePath) {
20489
20549
  }
20490
20550
  try {
20491
20551
  if (!fstatSync3(fd).isFile()) return null;
20492
- return readFileSync14(fd, "utf-8");
20552
+ return readFileSync15(fd, "utf-8");
20493
20553
  } finally {
20494
20554
  closeSync7(fd);
20495
20555
  }
@@ -21338,7 +21398,7 @@ import {
21338
21398
  existsSync as existsSync30,
21339
21399
  mkdirSync as mkdirSync20,
21340
21400
  mkdtempSync as mkdtempSync9,
21341
- readFileSync as readFileSync15,
21401
+ readFileSync as readFileSync16,
21342
21402
  readdirSync as readdirSync11,
21343
21403
  rmSync as rmSync18,
21344
21404
  statSync as statSync13,
@@ -21447,13 +21507,13 @@ function readAssemblePlan(planDir, chunkPaths) {
21447
21507
  if (!existsSync30(planJsonPath)) {
21448
21508
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
21449
21509
  }
21450
- const plan2 = JSON.parse(readFileSync15(planJsonPath, "utf-8"));
21510
+ const plan2 = JSON.parse(readFileSync16(planJsonPath, "utf-8"));
21451
21511
  readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
21452
21512
  validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
21453
21513
  if (!existsSync30(chunksJsonPath)) {
21454
21514
  throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
21455
21515
  }
21456
- const chunks = JSON.parse(readFileSync15(chunksJsonPath, "utf-8"));
21516
+ const chunks = JSON.parse(readFileSync16(chunksJsonPath, "utf-8"));
21457
21517
  if (chunkPaths.length !== chunks.length) {
21458
21518
  throw new Error(
21459
21519
  `[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
@@ -21599,13 +21659,13 @@ function stitchesSeparateAudio(planDir) {
21599
21659
  return lockedAudio(planDir).separateAudio === true;
21600
21660
  }
21601
21661
  function planFormat(planDir) {
21602
- const plan2 = JSON.parse(readFileSync15(join46(planDir, "plan.json"), "utf-8"));
21662
+ const plan2 = JSON.parse(readFileSync16(join46(planDir, "plan.json"), "utf-8"));
21603
21663
  return plan2.dimensions.format;
21604
21664
  }
21605
21665
  function lockedAudio(planDir) {
21606
21666
  const encoderJsonPath = join46(planDir, "meta", "encoder.json");
21607
21667
  if (!existsSync30(encoderJsonPath)) return {};
21608
- return JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
21668
+ return JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
21609
21669
  }
21610
21670
  function readCfrEncoder(planDir, format) {
21611
21671
  if (format !== "mp4") {
@@ -21617,7 +21677,7 @@ function readCfrEncoder(planDir, format) {
21617
21677
  if (!existsSync30(encoderJsonPath)) {
21618
21678
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
21619
21679
  }
21620
- const encoderJson = JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
21680
+ const encoderJson = JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
21621
21681
  if (encoderJson.encoder === "libx265-software") {
21622
21682
  throw new Error(
21623
21683
  `[assemble] cfr=true is not yet supported with codec: "h265". The cfr re-encode pass uses libx264 and would silently transcode the h265 chunks. Either disable cfr or render with codec: "h264".`