@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.
@@ -1920,7 +1920,7 @@ import { suggestMatchingPreset } from "@hyperframes/core";
1920
1920
  // src/services/htmlCompiler.ts
1921
1921
  import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
1922
1922
  import { createHash as createHash4 } from "node:crypto";
1923
- 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";
1924
1924
  import { join as join6, dirname as dirname5, resolve as resolve6, basename as basename3, relative as relative2 } from "path";
1925
1925
  import { parseHTML as parseHTML5 } from "linkedom";
1926
1926
  import {
@@ -3895,6 +3895,25 @@ ${fontFaceBlocks.join("\n")}
3895
3895
  }
3896
3896
  return result;
3897
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
+ }
3898
3917
  function collectFontFaceUrls(html) {
3899
3918
  const styleBlockRe = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
3900
3919
  const urlSet = /* @__PURE__ */ new Set();
@@ -3925,12 +3944,10 @@ function collectFontFaceUrls(html) {
3925
3944
  }
3926
3945
  return urlSet;
3927
3946
  }
3928
- async function localizeRemoteFontFaces(html, downloadDir) {
3929
- const processed = await inlineExternalFontStylesheets(html);
3930
- const urlSet = collectFontFaceUrls(processed);
3947
+ function localizeFontFaceUrls(html, downloadDir) {
3931
3948
  return downloadAndRewriteUrls(
3932
- urlSet,
3933
- processed,
3949
+ collectFontFaceUrls(html),
3950
+ html,
3934
3951
  join6(downloadDir, REMOTE_MEDIA_SUBDIR),
3935
3952
  "Remote font download failed",
3936
3953
  "Localized remote font face(s)",
@@ -4098,15 +4115,17 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
4098
4115
  const hasShaderTransitions = detectShaderTransitionUsage(sanitizedHtml);
4099
4116
  const usesThreeDTransforms = detectThreeDTransformUsage(sanitizedHtml);
4100
4117
  const hasAncestorBackgroundImage = detectAncestorBackgroundImage(sanitizedHtml);
4118
+ const fontSheetHtml = await inlineExternalFontStylesheets(
4119
+ promoteCssImportsToLinkTags(sanitizedHtml)
4120
+ );
4101
4121
  const normalizedFontHtml = normalizeSystemFontPrimaryFamilies(
4102
- injectTextRenderingRule(
4103
- coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml))
4104
- )
4122
+ injectTextRenderingRule(coalesceHeadStylesAndBodyScripts(fontSheetHtml))
4105
4123
  );
4106
4124
  const coalescedHtml = await injectDeterministicFontFaces(normalizedFontHtml, {
4107
4125
  logger: defaultLogger,
4108
4126
  failClosedFontFetch: options.failClosedFontFetch === true,
4109
4127
  allowSystemFontCapture: options.allowSystemFontCapture,
4128
+ projectFontFamilies: projectStylesheetFontFamilies(fontSheetHtml, projectDir),
4110
4129
  abortSignal: options.abortSignal
4111
4130
  });
4112
4131
  const assembledHtml = await inlineExternalScripts(coalescedHtml);
@@ -4132,7 +4151,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
4132
4151
  );
4133
4152
  const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
4134
4153
  const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
4135
- const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeRemoteFontFaces(htmlWithLocalBg, downloadDir);
4154
+ const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeFontFaceUrls(htmlWithLocalBg, downloadDir);
4136
4155
  const {
4137
4156
  html: htmlWithJpegs,
4138
4157
  preparedAssets: preparedHeifAssets,
@@ -6970,7 +6989,7 @@ import {
6970
6989
  existsSync as existsSync10,
6971
6990
  mkdirSync as mkdirSync5,
6972
6991
  readdirSync as readdirSync2,
6973
- readFileSync as readFileSync4,
6992
+ readFileSync as readFileSync5,
6974
6993
  rmSync as rmSync4,
6975
6994
  statSync as statSync4,
6976
6995
  writeFileSync as writeFileSync3
@@ -7064,7 +7083,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
7064
7083
  const error = formatFfmpegError(result.exitCode, result.stderr);
7065
7084
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
7066
7085
  }
7067
- const gif = readFileSync4(outputPath);
7086
+ const gif = readFileSync5(outputPath);
7068
7087
  if (!clearGifFramesBeforeNext(gif)) {
7069
7088
  const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
7070
7089
  return gifEncodeFailure(startTime, outputPath, error);
@@ -7097,7 +7116,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
7097
7116
  return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
7098
7117
  }
7099
7118
  }
7100
- if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync4(outputPath)) !== false) {
7119
+ if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync5(outputPath)) !== false) {
7101
7120
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
7102
7121
  if (failure) return failure;
7103
7122
  }
@@ -7769,7 +7788,7 @@ import {
7769
7788
  // src/services/distributed/shared.ts
7770
7789
  import { execFile as execFileCallback } from "node:child_process";
7771
7790
  import { dirname as dirname10, join as join16 } from "node:path";
7772
- import { existsSync as existsSync12, readFileSync as readFileSync5 } from "node:fs";
7791
+ import { existsSync as existsSync12, readFileSync as readFileSync6 } from "node:fs";
7773
7792
  import { fileURLToPath as fileURLToPath3 } from "node:url";
7774
7793
  import { promisify as promisify2 } from "node:util";
7775
7794
  import {
@@ -8048,7 +8067,7 @@ function readProducerVersion() {
8048
8067
  const candidate = join16(current, "package.json");
8049
8068
  if (existsSync12(candidate)) {
8050
8069
  try {
8051
- const pkg = JSON.parse(readFileSync5(candidate, "utf-8"));
8070
+ const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
8052
8071
  if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
8053
8072
  cachedProducerVersion = pkg.version;
8054
8073
  return pkg.version;
@@ -8434,7 +8453,7 @@ async function readPlanReferenceBackground(input) {
8434
8453
  }
8435
8454
 
8436
8455
  // src/services/render/stages/freezePlan.ts
8437
- 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";
8438
8457
  import { join as join20, relative as relative3, resolve as resolve8 } from "node:path";
8439
8458
  import {
8440
8459
  REFERENCE_BACKGROUND_FIELD,
@@ -8674,7 +8693,7 @@ function collectPlanAssetShas(planDir, digests) {
8674
8693
  const assets = [];
8675
8694
  for (const file of files) {
8676
8695
  if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
8677
- compositionHtml = readFileSync6(file.absolutePath);
8696
+ compositionHtml = readFileSync7(file.absolutePath);
8678
8697
  continue;
8679
8698
  }
8680
8699
  if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
@@ -8698,7 +8717,7 @@ function writePlanRemoteFiles(planDir, files) {
8698
8717
  }
8699
8718
  function readPlanRemoteFiles(planDir) {
8700
8719
  const path = join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
8701
- return existsSync14(path) ? JSON.parse(readFileSync6(path, "utf-8")) : [];
8720
+ return existsSync14(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
8702
8721
  }
8703
8722
  function recomputePlanHashFromPlanDir(planDir, digests) {
8704
8723
  const planJsonPath = join20(planDir, "plan.json");
@@ -8709,8 +8728,8 @@ function recomputePlanHashFromPlanDir(planDir, digests) {
8709
8728
  if (!existsSync14(encoderJsonPath)) {
8710
8729
  throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
8711
8730
  }
8712
- const planJson = JSON.parse(readFileSync6(planJsonPath, "utf-8"));
8713
- const encoderConfigCanonicalJson = readFileSync6(encoderJsonPath, "utf-8");
8731
+ const planJson = JSON.parse(readFileSync7(planJsonPath, "utf-8"));
8732
+ const encoderConfigCanonicalJson = readFileSync7(encoderJsonPath, "utf-8");
8714
8733
  const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
8715
8734
  return computePlanHash({
8716
8735
  compositionHtml,
@@ -8817,7 +8836,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
8817
8836
  }
8818
8837
 
8819
8838
  // src/services/distributed/chunkAudio.ts
8820
- 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";
8821
8840
  import { writeFileSync as writeFileSync6, writeSync } from "node:fs";
8822
8841
  import { join as join22 } from "node:path";
8823
8842
  import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
@@ -9290,7 +9309,7 @@ async function writeChunkAudioSlices(input) {
9290
9309
  offset += track.sizes[offsets.length - 1];
9291
9310
  }
9292
9311
  });
9293
- const bytes = readFileSync7(input.audioPath);
9312
+ const bytes = readFileSync8(input.audioPath);
9294
9313
  const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
9295
9314
  mkdirSync9(join22(input.planDir, SLICE_DIR), { recursive: true });
9296
9315
  input.chunks.forEach((_, index) => {
@@ -9309,8 +9328,8 @@ async function writeChunkAudioSlices(input) {
9309
9328
  function chunkAudioSource(planDir, chunkIndex, scratchPath) {
9310
9329
  const layoutPath = join22(planDir, SLICE_LAYOUT);
9311
9330
  if (!existsSync15(layoutPath)) return resolvePlanAudioPath(planDir);
9312
- const layout = JSON.parse(readFileSync7(layoutPath, "utf-8"));
9313
- const frame = readFileSync7(join22(planDir, SLICE_FRAME));
9331
+ const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
9332
+ const frame = readFileSync8(join22(planDir, SLICE_FRAME));
9314
9333
  const fd = openSync4(scratchPath, "w");
9315
9334
  try {
9316
9335
  writeSync(fd, frame, 0, layout.payloadStart, 0);
@@ -9323,7 +9342,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
9323
9342
  );
9324
9343
  const start = layout.partStarts[chunkIndex];
9325
9344
  if (start !== void 0) {
9326
- const part = readFileSync7(join22(planDir, chunkAudioSlicePath(chunkIndex)));
9345
+ const part = readFileSync8(join22(planDir, chunkAudioSlicePath(chunkIndex)));
9327
9346
  writeSync(fd, part, 0, part.length, start);
9328
9347
  }
9329
9348
  ftruncateSync(fd, layout.sizeBytes);
@@ -10422,7 +10441,7 @@ import {
10422
10441
  lstatSync as lstatSync2,
10423
10442
  mkdirSync as mkdirSync12,
10424
10443
  mkdtempSync as mkdtempSync4,
10425
- readFileSync as readFileSync8,
10444
+ readFileSync as readFileSync9,
10426
10445
  readdirSync as readdirSync7,
10427
10446
  rmSync as rmSync9,
10428
10447
  statSync as statSync7,
@@ -10571,7 +10590,7 @@ function isNonNegativeIntegerArray(value) {
10571
10590
  return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
10572
10591
  }
10573
10592
  function readJsonFile(path, label) {
10574
- const contents = readFileSync8(path, "utf-8");
10593
+ const contents = readFileSync9(path, "utf-8");
10575
10594
  try {
10576
10595
  const parsed = JSON.parse(contents);
10577
10596
  return parsed;
@@ -11315,7 +11334,7 @@ import {
11315
11334
  existsSync as existsSync19,
11316
11335
  mkdirSync as mkdirSync13,
11317
11336
  mkdtempSync as mkdtempSync5,
11318
- readFileSync as readFileSync9,
11337
+ readFileSync as readFileSync10,
11319
11338
  readdirSync as readdirSync8,
11320
11339
  rmSync as rmSync10,
11321
11340
  statSync as statSync8,
@@ -11424,13 +11443,13 @@ function readAssemblePlan(planDir, chunkPaths) {
11424
11443
  if (!existsSync19(planJsonPath)) {
11425
11444
  throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
11426
11445
  }
11427
- const plan = JSON.parse(readFileSync9(planJsonPath, "utf-8"));
11446
+ const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
11428
11447
  readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
11429
11448
  validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
11430
11449
  if (!existsSync19(chunksJsonPath)) {
11431
11450
  throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
11432
11451
  }
11433
- const chunks = JSON.parse(readFileSync9(chunksJsonPath, "utf-8"));
11452
+ const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
11434
11453
  if (chunkPaths.length !== chunks.length) {
11435
11454
  throw new Error(
11436
11455
  `[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
@@ -11576,13 +11595,13 @@ function stitchesSeparateAudio(planDir) {
11576
11595
  return lockedAudio(planDir).separateAudio === true;
11577
11596
  }
11578
11597
  function planFormat(planDir) {
11579
- const plan = JSON.parse(readFileSync9(join28(planDir, "plan.json"), "utf-8"));
11598
+ const plan = JSON.parse(readFileSync10(join28(planDir, "plan.json"), "utf-8"));
11580
11599
  return plan.dimensions.format;
11581
11600
  }
11582
11601
  function lockedAudio(planDir) {
11583
11602
  const encoderJsonPath = join28(planDir, "meta", "encoder.json");
11584
11603
  if (!existsSync19(encoderJsonPath)) return {};
11585
- return JSON.parse(readFileSync9(encoderJsonPath, "utf-8"));
11604
+ return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
11586
11605
  }
11587
11606
  function readCfrEncoder(planDir, format) {
11588
11607
  if (format !== "mp4") {
@@ -11594,7 +11613,7 @@ function readCfrEncoder(planDir, format) {
11594
11613
  if (!existsSync19(encoderJsonPath)) {
11595
11614
  throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
11596
11615
  }
11597
- const encoderJson = JSON.parse(readFileSync9(encoderJsonPath, "utf-8"));
11616
+ const encoderJson = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
11598
11617
  if (encoderJson.encoder === "libx265-software") {
11599
11618
  throw new Error(
11600
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".`
@@ -11648,7 +11667,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
11648
11667
 
11649
11668
  // src/services/distributed/renderChunk.ts
11650
11669
  import { randomBytes } from "node:crypto";
11651
- 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";
11652
11671
  import { cpus } from "node:os";
11653
11672
  import { join as join29 } from "node:path";
11654
11673
  import {
@@ -11771,10 +11790,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
11771
11790
  return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
11772
11791
  }
11773
11792
  function hashChunkOutput(outputPath, kind) {
11774
- if (kind === "file") return sha256Hex(readFileSync10(outputPath));
11793
+ if (kind === "file") return sha256Hex(readFileSync11(outputPath));
11775
11794
  const entries = readdirSync9(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
11776
11795
  const lines = entries.map(
11777
- (name) => `${name}\0${sha256Hex(readFileSync10(join29(outputPath, name)))}`
11796
+ (name) => `${name}\0${sha256Hex(readFileSync11(join29(outputPath, name)))}`
11778
11797
  );
11779
11798
  return sha256Hex(lines.join("\0"));
11780
11799
  }
@@ -11800,7 +11819,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11800
11819
  `[renderChunk] planDir is missing required artifact: ${planJsonPath}`
11801
11820
  );
11802
11821
  }
11803
- const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
11822
+ const plan = JSON.parse(readFileSync11(planJsonPath, "utf-8"));
11804
11823
  readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
11805
11824
  const planHashStarted = Date.now();
11806
11825
  const v2Materialization = readValidatedPlanV2Materialization(planDir, {
@@ -11817,13 +11836,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11817
11836
  );
11818
11837
  }
11819
11838
  }
11820
- const encoder = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
11821
- 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"));
11822
11841
  const videosJsonPath = join29(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
11823
11842
  let planVideos = null;
11824
11843
  if (existsSync20(videosJsonPath)) {
11825
11844
  try {
11826
- planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync10(videosJsonPath, "utf-8")));
11845
+ planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync11(videosJsonPath, "utf-8")));
11827
11846
  } catch (err) {
11828
11847
  if (err instanceof RenderChunkValidationError) throw err;
11829
11848
  throw new RenderChunkValidationError(
@@ -11934,7 +11953,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
11934
11953
  dimensions: plan.dimensions,
11935
11954
  encoder,
11936
11955
  videoCount: planVideos?.videos.length ?? 0,
11937
- html: readFileSync10(join29(compiledDir, "index.html"), "utf-8"),
11956
+ html: readFileSync11(join29(compiledDir, "index.html"), "utf-8"),
11938
11957
  // The plan ships a deferred source only to chunks that show it; don't wait on the rest.
11939
11958
  skipReadinessVideoIds: deferredVideos.filter(
11940
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"