@hyperframes/producer 0.9.12 → 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.
@@ -1124,27 +1124,25 @@ var materializeFileSystem = {
1124
1124
  rmSync
1125
1125
  };
1126
1126
  var warnedSymlinkFallback = false;
1127
+ var copyCannotFix = (error) => ["ENOSPC", "EDQUOT", "EEXIST", "ENOENT"].includes(
1128
+ error?.code ?? ""
1129
+ );
1127
1130
  function tryWindowsFrameJunction(fileSystem, src, dest) {
1128
1131
  if (process.platform !== "win32") return false;
1129
1132
  try {
1130
1133
  fileSystem.symlinkSync(src, dest, "junction");
1131
1134
  return true;
1132
1135
  } catch (error) {
1133
- const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
1134
- if (["EPERM", "EACCES", "UNKNOWN", "EINVAL", "ENOSYS", "EOPNOTSUPP", "ENOTSUP"].includes(
1135
- code ?? ""
1136
- )) {
1137
- return false;
1138
- }
1139
- throw error;
1136
+ if (copyCannotFix(error)) throw error;
1137
+ return false;
1140
1138
  }
1141
1139
  }
1142
1140
  function linkOrCopyFrameDir(fileSystem, src, dest) {
1143
1141
  try {
1144
1142
  fileSystem.symlinkSync(src, dest, "dir");
1145
1143
  } catch (err) {
1144
+ if (copyCannotFix(err)) throw err;
1146
1145
  const code = err?.code;
1147
- if (code !== "EPERM" && code !== "EACCES" && code !== "UNKNOWN") throw err;
1148
1146
  if (tryWindowsFrameJunction(fileSystem, src, dest)) return;
1149
1147
  if (!warnedSymlinkFallback) {
1150
1148
  warnedSymlinkFallback = true;
@@ -1922,7 +1920,7 @@ import { suggestMatchingPreset } from "@hyperframes/core";
1922
1920
  // src/services/htmlCompiler.ts
1923
1921
  import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
1924
1922
  import { createHash as createHash4 } from "node:crypto";
1925
- import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
1923
+ import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
1926
1924
  import { join as join6, dirname as dirname5, resolve as resolve6, basename as basename3, relative as relative2 } from "path";
1927
1925
  import { parseHTML as parseHTML5 } from "linkedom";
1928
1926
  import {
@@ -3897,6 +3895,25 @@ ${fontFaceBlocks.join("\n")}
3897
3895
  }
3898
3896
  return result;
3899
3897
  }
3898
+ function projectStylesheetFontFamilies(html, projectDir) {
3899
+ const families = [];
3900
+ for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
3901
+ const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
3902
+ if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
3903
+ const sheet = resolve6(projectDir, decodedUrlPath(href).replace(/^(?:\.?\/)+/, ""));
3904
+ if (!isPathInside2(sheet, projectDir) || !existsSync6(sheet)) continue;
3905
+ for (const face of extractFontFaceBlocks(readFileSync4(sheet, "utf8"))) {
3906
+ const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
3907
+ const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
3908
+ const path = decodedUrlPath(url);
3909
+ const file = path.startsWith("/") ? resolve6(projectDir, `.${path}`) : resolve6(dirname5(sheet), path);
3910
+ return isPathInside2(file, projectDir) && existsSync6(file);
3911
+ });
3912
+ if (family && shipsFile) families.push(family);
3913
+ }
3914
+ }
3915
+ return families;
3916
+ }
3900
3917
  function collectFontFaceUrls(html) {
3901
3918
  const styleBlockRe = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
3902
3919
  const urlSet = /* @__PURE__ */ new Set();
@@ -3927,12 +3944,10 @@ function collectFontFaceUrls(html) {
3927
3944
  }
3928
3945
  return urlSet;
3929
3946
  }
3930
- async function localizeRemoteFontFaces(html, downloadDir) {
3931
- const processed = await inlineExternalFontStylesheets(html);
3932
- const urlSet = collectFontFaceUrls(processed);
3947
+ function localizeFontFaceUrls(html, downloadDir) {
3933
3948
  return downloadAndRewriteUrls(
3934
- urlSet,
3935
- processed,
3949
+ collectFontFaceUrls(html),
3950
+ html,
3936
3951
  join6(downloadDir, REMOTE_MEDIA_SUBDIR),
3937
3952
  "Remote font download failed",
3938
3953
  "Localized remote font face(s)",
@@ -4100,15 +4115,17 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
4100
4115
  const hasShaderTransitions = detectShaderTransitionUsage(sanitizedHtml);
4101
4116
  const usesThreeDTransforms = detectThreeDTransformUsage(sanitizedHtml);
4102
4117
  const hasAncestorBackgroundImage = detectAncestorBackgroundImage(sanitizedHtml);
4118
+ const fontSheetHtml = await inlineExternalFontStylesheets(
4119
+ promoteCssImportsToLinkTags(sanitizedHtml)
4120
+ );
4103
4121
  const normalizedFontHtml = normalizeSystemFontPrimaryFamilies(
4104
- injectTextRenderingRule(
4105
- coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml))
4106
- )
4122
+ injectTextRenderingRule(coalesceHeadStylesAndBodyScripts(fontSheetHtml))
4107
4123
  );
4108
4124
  const coalescedHtml = await injectDeterministicFontFaces(normalizedFontHtml, {
4109
4125
  logger: defaultLogger,
4110
4126
  failClosedFontFetch: options.failClosedFontFetch === true,
4111
4127
  allowSystemFontCapture: options.allowSystemFontCapture,
4128
+ projectFontFamilies: projectStylesheetFontFamilies(fontSheetHtml, projectDir),
4112
4129
  abortSignal: options.abortSignal
4113
4130
  });
4114
4131
  const assembledHtml = await inlineExternalScripts(coalescedHtml);
@@ -4134,7 +4151,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
4134
4151
  );
4135
4152
  const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
4136
4153
  const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
4137
- const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeRemoteFontFaces(htmlWithLocalBg, downloadDir);
4154
+ const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeFontFaceUrls(htmlWithLocalBg, downloadDir);
4138
4155
  const {
4139
4156
  html: htmlWithJpegs,
4140
4157
  preparedAssets: preparedHeifAssets,
@@ -5585,17 +5602,24 @@ var VideoExtractionStageError = class extends Error {
5585
5602
  publicMetadata;
5586
5603
  };
5587
5604
  function assertVideoExtractionSucceeded(result) {
5588
- throwIfEncoderInterrupted(result);
5605
+ throwIfHostFailure(result);
5589
5606
  const error = buildVideoExtractionStageError(result);
5590
5607
  if (error) throw error;
5591
5608
  }
5592
- function throwIfEncoderInterrupted(result) {
5609
+ function throwIfHostFailure(result) {
5593
5610
  const interrupted = result.errors.find((failure) => failure.kind === "external_interruption");
5594
- if (!interrupted) return;
5595
- throw encoderFailureError("Video frame extraction failed", {
5596
- error: String(interrupted.error),
5597
- failureReason: "external_interruption"
5598
- });
5611
+ if (interrupted) {
5612
+ throw encoderFailureError("Video frame extraction failed", {
5613
+ error: String(interrupted.error),
5614
+ failureReason: "external_interruption"
5615
+ });
5616
+ }
5617
+ const full = result.errors.filter((failure) => failure.kind === "disk_full").length;
5618
+ if (full === 0) return;
5619
+ throw Object.assign(
5620
+ new Error(`ENOSPC: no space left on device, extracting the frames of ${full} video source(s)`),
5621
+ { code: "ENOSPC" }
5622
+ );
5599
5623
  }
5600
5624
  function buildVideoExtractionStageError(result) {
5601
5625
  if (result.success && result.errors.length === 0) return null;
@@ -5810,7 +5834,7 @@ async function runExtractVideosStage(input) {
5810
5834
  );
5811
5835
  extractionResult.phaseBreakdown.transientRetries = (extractionResult.phaseBreakdown.transientRetries ?? 0) + hdrProbeTransientRetries;
5812
5836
  assertNotAborted();
5813
- throwIfEncoderInterrupted(extractionResult);
5837
+ throwIfHostFailure(extractionResult);
5814
5838
  failureToEnforce = applyVideoExtractionFailurePolicy(extractionResult, extractionPolicy, log);
5815
5839
  materializeExtractedFramesForCompiledDir(extractionResult.extracted, compiledDir, {
5816
5840
  materializeSymlinks
@@ -6965,7 +6989,7 @@ import {
6965
6989
  existsSync as existsSync10,
6966
6990
  mkdirSync as mkdirSync5,
6967
6991
  readdirSync as readdirSync2,
6968
- readFileSync as readFileSync4,
6992
+ readFileSync as readFileSync5,
6969
6993
  rmSync as rmSync4,
6970
6994
  statSync as statSync4,
6971
6995
  writeFileSync as writeFileSync3
@@ -7059,7 +7083,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
7059
7083
  const error = formatFfmpegError(result.exitCode, result.stderr);
7060
7084
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
7061
7085
  }
7062
- const gif = readFileSync4(outputPath);
7086
+ const gif = readFileSync5(outputPath);
7063
7087
  if (!clearGifFramesBeforeNext(gif)) {
7064
7088
  const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
7065
7089
  return gifEncodeFailure(startTime, outputPath, error);
@@ -7092,7 +7116,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
7092
7116
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
7093
7117
  }
7094
7118
  }
7095
- if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync4(outputPath)) !== false) {
7119
+ if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync5(outputPath)) !== false) {
7096
7120
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
7097
7121
  if (failure) return failure;
7098
7122
  }
@@ -7651,6 +7675,22 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
7651
7675
  }
7652
7676
  const remainingCount = countFrameRanges(remaining);
7653
7677
  const madeProgress = captureAttemptMadeProgress(frameCount, remainingCount);
7678
+ if (options.allowRetry && !madeProgress && (currentWorkers > 1 || initializationRetriesUsed === 0 && failure.kind === "protocol_timeout") && /\bNetwork\.enable timed out|Navigation timeout of \d+ ms exceeded/i.test(failure.message)) {
7679
+ initializationRetriesUsed++;
7680
+ const nextWorkers2 = getNextRetryWorkerCount(currentWorkers);
7681
+ options.log.warn(
7682
+ "[Render] Browser initialization timed out; retrying with fewer workers.",
7683
+ {
7684
+ fromWorkers: currentWorkers,
7685
+ toWorkers: nextWorkers2,
7686
+ error: failure.message
7687
+ }
7688
+ );
7689
+ currentWorkers = nextWorkers2;
7690
+ missingRanges = remaining;
7691
+ attempt++;
7692
+ continue;
7693
+ }
7654
7694
  if (options.allowRetry && isTransientCaptureRetryEligible(failure.kind, remaining, transientRetriesUsed)) {
7655
7695
  transientRetriesUsed++;
7656
7696
  options.log.warn(
@@ -7668,22 +7708,6 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
7668
7708
  pendingTransientRetry = true;
7669
7709
  continue;
7670
7710
  }
7671
- if (options.allowRetry && !madeProgress && initializationRetriesUsed === 0 && failure.kind === "protocol_timeout" && /\bNetwork\.enable timed out/i.test(failure.message)) {
7672
- initializationRetriesUsed++;
7673
- const nextWorkers2 = getNextRetryWorkerCount(currentWorkers);
7674
- options.log.warn(
7675
- "[Render] Browser initialization timed out; retrying once with fresh sessions.",
7676
- {
7677
- fromWorkers: currentWorkers,
7678
- toWorkers: nextWorkers2,
7679
- error: failure.message
7680
- }
7681
- );
7682
- currentWorkers = nextWorkers2;
7683
- missingRanges = remaining;
7684
- attempt++;
7685
- continue;
7686
- }
7687
7711
  if (!madeProgress) {
7688
7712
  options.log.warn(
7689
7713
  "[Render] Capture attempt made no forward progress; composition is likely structurally broken, not retrying.",
@@ -7764,7 +7788,7 @@ import {
7764
7788
  // src/services/distributed/shared.ts
7765
7789
  import { execFile as execFileCallback } from "node:child_process";
7766
7790
  import { dirname as dirname10, join as join16 } from "node:path";
7767
- import { existsSync as existsSync12, readFileSync as readFileSync5 } from "node:fs";
7791
+ import { existsSync as existsSync12, readFileSync as readFileSync6 } from "node:fs";
7768
7792
  import { fileURLToPath as fileURLToPath3 } from "node:url";
7769
7793
  import { promisify as promisify2 } from "node:util";
7770
7794
  import {
@@ -8043,7 +8067,7 @@ function readProducerVersion() {
8043
8067
  const candidate = join16(current, "package.json");
8044
8068
  if (existsSync12(candidate)) {
8045
8069
  try {
8046
- const pkg = JSON.parse(readFileSync5(candidate, "utf-8"));
8070
+ const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
8047
8071
  if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
8048
8072
  cachedProducerVersion = pkg.version;
8049
8073
  return pkg.version;
@@ -8429,7 +8453,7 @@ async function readPlanReferenceBackground(input) {
8429
8453
  }
8430
8454
 
8431
8455
  // src/services/render/stages/freezePlan.ts
8432
- import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync6, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
8456
+ import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
8433
8457
  import { join as join20, relative as relative3, resolve as resolve8 } from "node:path";
8434
8458
  import {
8435
8459
  REFERENCE_BACKGROUND_FIELD,
@@ -8669,7 +8693,7 @@ function collectPlanAssetShas(planDir, digests) {
8669
8693
  const assets = [];
8670
8694
  for (const file of files) {
8671
8695
  if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
8672
- compositionHtml = readFileSync6(file.absolutePath);
8696
+ compositionHtml = readFileSync7(file.absolutePath);
8673
8697
  continue;
8674
8698
  }
8675
8699
  if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
@@ -8693,7 +8717,7 @@ function writePlanRemoteFiles(planDir, files) {
8693
8717
  }
8694
8718
  function readPlanRemoteFiles(planDir) {
8695
8719
  const path = join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
8696
- return existsSync14(path) ? JSON.parse(readFileSync6(path, "utf-8")) : [];
8720
+ return existsSync14(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
8697
8721
  }
8698
8722
  function recomputePlanHashFromPlanDir(planDir, digests) {
8699
8723
  const planJsonPath = join20(planDir, "plan.json");
@@ -8704,8 +8728,8 @@ function recomputePlanHashFromPlanDir(planDir, digests) {
8704
8728
  if (!existsSync14(encoderJsonPath)) {
8705
8729
  throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
8706
8730
  }
8707
- const planJson = JSON.parse(readFileSync6(planJsonPath, "utf-8"));
8708
- const encoderConfigCanonicalJson = readFileSync6(encoderJsonPath, "utf-8");
8731
+ const planJson = JSON.parse(readFileSync7(planJsonPath, "utf-8"));
8732
+ const encoderConfigCanonicalJson = readFileSync7(encoderJsonPath, "utf-8");
8709
8733
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
8710
8734
  return computePlanHash({
8711
8735
  compositionHtml,
@@ -8812,7 +8836,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
8812
8836
  }
8813
8837
 
8814
8838
  // src/services/distributed/chunkAudio.ts
8815
- import { closeSync as closeSync4, existsSync as existsSync15, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync7 } from "node:fs";
8839
+ import { closeSync as closeSync4, existsSync as existsSync15, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
8816
8840
  import { writeFileSync as writeFileSync6, writeSync } from "node:fs";
8817
8841
  import { join as join22 } from "node:path";
8818
8842
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
@@ -9285,7 +9309,7 @@ async function writeChunkAudioSlices(input) {
9285
9309
  offset += track.sizes[offsets.length - 1];
9286
9310
  }
9287
9311
  });
9288
- const bytes = readFileSync7(input.audioPath);
9312
+ const bytes = readFileSync8(input.audioPath);
9289
9313
  const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
9290
9314
  mkdirSync9(join22(input.planDir, SLICE_DIR), { recursive: true });
9291
9315
  input.chunks.forEach((_, index) => {
@@ -9304,8 +9328,8 @@ async function writeChunkAudioSlices(input) {
9304
9328
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
9305
9329
  const layoutPath = join22(planDir, SLICE_LAYOUT);
9306
9330
  if (!existsSync15(layoutPath)) return resolvePlanAudioPath(planDir);
9307
- const layout = JSON.parse(readFileSync7(layoutPath, "utf-8"));
9308
- const frame = readFileSync7(join22(planDir, SLICE_FRAME));
9331
+ const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
9332
+ const frame = readFileSync8(join22(planDir, SLICE_FRAME));
9309
9333
  const fd = openSync4(scratchPath, "w");
9310
9334
  try {
9311
9335
  writeSync(fd, frame, 0, layout.payloadStart, 0);
@@ -9318,7 +9342,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
9318
9342
  );
9319
9343
  const start = layout.partStarts[chunkIndex];
9320
9344
  if (start !== void 0) {
9321
- const part = readFileSync7(join22(planDir, chunkAudioSlicePath(chunkIndex)));
9345
+ const part = readFileSync8(join22(planDir, chunkAudioSlicePath(chunkIndex)));
9322
9346
  writeSync(fd, part, 0, part.length, start);
9323
9347
  }
9324
9348
  ftruncateSync(fd, layout.sizeBytes);
@@ -10417,7 +10441,7 @@ import {
10417
10441
  lstatSync as lstatSync2,
10418
10442
  mkdirSync as mkdirSync12,
10419
10443
  mkdtempSync as mkdtempSync4,
10420
- readFileSync as readFileSync8,
10444
+ readFileSync as readFileSync9,
10421
10445
  readdirSync as readdirSync7,
10422
10446
  rmSync as rmSync9,
10423
10447
  statSync as statSync7,
@@ -10566,7 +10590,7 @@ function isNonNegativeIntegerArray(value) {
10566
10590
  return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
10567
10591
  }
10568
10592
  function readJsonFile(path, label) {
10569
- const contents = readFileSync8(path, "utf-8");
10593
+ const contents = readFileSync9(path, "utf-8");
10570
10594
  try {
10571
10595
  const parsed = JSON.parse(contents);
10572
10596
  return parsed;
@@ -11310,7 +11334,7 @@ import {
11310
11334
  existsSync as existsSync19,
11311
11335
  mkdirSync as mkdirSync13,
11312
11336
  mkdtempSync as mkdtempSync5,
11313
- readFileSync as readFileSync9,
11337
+ readFileSync as readFileSync10,
11314
11338
  readdirSync as readdirSync8,
11315
11339
  rmSync as rmSync10,
11316
11340
  statSync as statSync8,
@@ -11419,13 +11443,13 @@ function readAssemblePlan(planDir, chunkPaths) {
11419
11443
  if (!existsSync19(planJsonPath)) {
11420
11444
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
11421
11445
  }
11422
- const plan = JSON.parse(readFileSync9(planJsonPath, "utf-8"));
11446
+ const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
11423
11447
  readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
11424
11448
  validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
11425
11449
  if (!existsSync19(chunksJsonPath)) {
11426
11450
  throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
11427
11451
  }
11428
- const chunks = JSON.parse(readFileSync9(chunksJsonPath, "utf-8"));
11452
+ const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
11429
11453
  if (chunkPaths.length !== chunks.length) {
11430
11454
  throw new Error(
11431
11455
  `[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
@@ -11571,13 +11595,13 @@ function stitchesSeparateAudio(planDir) {
11571
11595
  return lockedAudio(planDir).separateAudio === true;
11572
11596
  }
11573
11597
  function planFormat(planDir) {
11574
- const plan = JSON.parse(readFileSync9(join28(planDir, "plan.json"), "utf-8"));
11598
+ const plan = JSON.parse(readFileSync10(join28(planDir, "plan.json"), "utf-8"));
11575
11599
  return plan.dimensions.format;
11576
11600
  }
11577
11601
  function lockedAudio(planDir) {
11578
11602
  const encoderJsonPath = join28(planDir, "meta", "encoder.json");
11579
11603
  if (!existsSync19(encoderJsonPath)) return {};
11580
- return JSON.parse(readFileSync9(encoderJsonPath, "utf-8"));
11604
+ return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
11581
11605
  }
11582
11606
  function readCfrEncoder(planDir, format) {
11583
11607
  if (format !== "mp4") {
@@ -11589,7 +11613,7 @@ function readCfrEncoder(planDir, format) {
11589
11613
  if (!existsSync19(encoderJsonPath)) {
11590
11614
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
11591
11615
  }
11592
- const encoderJson = JSON.parse(readFileSync9(encoderJsonPath, "utf-8"));
11616
+ const encoderJson = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
11593
11617
  if (encoderJson.encoder === "libx265-software") {
11594
11618
  throw new Error(
11595
11619
  `[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".`
@@ -11643,7 +11667,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11643
11667
 
11644
11668
  // src/services/distributed/renderChunk.ts
11645
11669
  import { randomBytes } from "node:crypto";
11646
- import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync10, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
11670
+ import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
11647
11671
  import { cpus } from "node:os";
11648
11672
  import { join as join29 } from "node:path";
11649
11673
  import {
@@ -11766,10 +11790,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
11766
11790
  return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
11767
11791
  }
11768
11792
  function hashChunkOutput(outputPath, kind) {
11769
- if (kind === "file") return sha256Hex(readFileSync10(outputPath));
11793
+ if (kind === "file") return sha256Hex(readFileSync11(outputPath));
11770
11794
  const entries = readdirSync9(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
11771
11795
  const lines = entries.map(
11772
- (name) => `${name}\0${sha256Hex(readFileSync10(join29(outputPath, name)))}`
11796
+ (name) => `${name}\0${sha256Hex(readFileSync11(join29(outputPath, name)))}`
11773
11797
  );
11774
11798
  return sha256Hex(lines.join("\0"));
11775
11799
  }
@@ -11795,7 +11819,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11795
11819
  `[renderChunk] planDir is missing required artifact: ${planJsonPath}`
11796
11820
  );
11797
11821
  }
11798
- const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
11822
+ const plan = JSON.parse(readFileSync11(planJsonPath, "utf-8"));
11799
11823
  readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
11800
11824
  const planHashStarted = Date.now();
11801
11825
  const v2Materialization = readValidatedPlanV2Materialization(planDir, {
@@ -11812,13 +11836,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11812
11836
  );
11813
11837
  }
11814
11838
  }
11815
- const encoder = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
11816
- const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
11839
+ const encoder = JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
11840
+ const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
11817
11841
  const videosJsonPath = join29(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
11818
11842
  let planVideos = null;
11819
11843
  if (existsSync20(videosJsonPath)) {
11820
11844
  try {
11821
- planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync10(videosJsonPath, "utf-8")));
11845
+ planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync11(videosJsonPath, "utf-8")));
11822
11846
  } catch (err) {
11823
11847
  if (err instanceof RenderChunkValidationError) throw err;
11824
11848
  throw new RenderChunkValidationError(
@@ -11929,7 +11953,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11929
11953
  dimensions: plan.dimensions,
11930
11954
  encoder,
11931
11955
  videoCount: planVideos?.videos.length ?? 0,
11932
- html: readFileSync10(join29(compiledDir, "index.html"), "utf-8"),
11956
+ html: readFileSync11(join29(compiledDir, "index.html"), "utf-8"),
11933
11957
  // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
11934
11958
  skipReadinessVideoIds: deferredVideos.filter(
11935
11959
  (video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "0.1.0",
3
3
  "buildId": "dev",
4
- "sha256": "0c9ef4248ee8b3a7da3fbab3793186b2ed6464b30e40f760bddc69cab5735de1",
4
+ "sha256": "fabca5558bf6c2986cf22d51b64beb3bfe533a577864888bfecd242f66363a73",
5
5
  "artifacts": {
6
6
  "iife": "hyperframe.runtime.iife.js",
7
7
  "esm": "hyperframe.runtime.mjs"