@hyperframes/producer 0.8.189 → 0.8.191

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.
@@ -39,6 +39,7 @@ export { muxChunkWithAudio } from "./services/distributed/chunkAudio.js";
39
39
  export { buildStitchedHeader, readMp4Layout, type Mp4Layout, type ReadRange, } from "./services/distributed/mp4Stitch.js";
40
40
  export { InvalidConfigError, type SerializableDistributedRenderConfig, validateDistributedRenderConfig, validateVariablesPayload, } from "./services/distributed/renderConfigValidation.js";
41
41
  export { hashProjectDir, listSiteFiles } from "./services/distributed/projectHash.js";
42
+ export { AUDIO_SIDECAR_MIN_BYTES, buildAudioSidecar, wantsAudioSidecar, type AudioSidecarOutcome, } from "./services/distributed/audioSidecar.js";
42
43
  export { CURRENT_PLAN_PROTOCOL, DISTRIBUTED_RENDER_CAPABILITIES, getDistributedRenderCapabilities, PLAN_ARTIFACT_LAYOUT, PLAN_HASH_SCHEMA, PLAN_PROTOCOL_V1, PLAN_PROTOCOL_V2, PLAN_PROTOCOL_UNSUPPORTED, PLAN_SCHEMA_VERSION, PLAN_V2_ARTIFACT_LAYOUT, PLAN_V2_HASH_SCHEMA, PLAN_V2_SCHEMA_VERSION, PlanProtocolUnsupportedError, readPlanProtocol, readPlanProtocolV1, type DistributedRenderCapabilities, type PlanProtocolConsumerCapabilities, type PlanProtocolDescriptor, type PlanProtocolV1Descriptor, type PlanProtocolV2Descriptor, type SupportedPlanProtocolDescriptor, } from "./services/distributed/planProtocol.js";
43
44
  export { PlanVideosMetadataError, type DistributedFormat } from "./services/distributed/shared.js";
44
45
  export { isPlanAudioArtifactPath, PLAN_AUDIO_LEGACY_RELATIVE_PATH, PLAN_AUDIO_RELATIVE_PATH, resolvePlanAudioPath, } from "./services/distributed/shared.js";
@@ -1097,6 +1097,14 @@ function materializeExtractedFramesForCompiledDir(extracted, compiledDir, option
1097
1097
  }
1098
1098
  }
1099
1099
 
1100
+ // src/services/render/frameCacheBudget.ts
1101
+ function frameCacheOptions(cfg, workers) {
1102
+ return {
1103
+ frameDataUriCacheLimit: cfg.frameDataUriCacheLimit,
1104
+ frameDataUriCacheBytesLimitMb: Math.floor(cfg.frameDataUriCacheBytesLimitMb / (workers || 1))
1105
+ };
1106
+ }
1107
+
1100
1108
  // src/utils/errorMessage.ts
1101
1109
  function normalizeErrorMessage(error) {
1102
1110
  if (error instanceof Error) return error.message;
@@ -3387,7 +3395,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
3387
3395
  );
3388
3396
  if (analysis.isProblematic) {
3389
3397
  defaultLogger.warn(
3390
- `[Compiler] WARNING: Video "${video.id}" has sparse keyframes (max interval: ${analysis.maxIntervalSeconds}s). This causes seek failures and frame freezing. Re-encode with: ${reencode}`
3398
+ `[Compiler] WARNING: Video "${video.id}" has sparse keyframes (max interval: ${analysis.maxIntervalSeconds}s). Long GOPs can make preview seeking slower; rendering is unaffected. If preview seeking is slow, re-encode with: ${reencode}`
3391
3399
  );
3392
3400
  }
3393
3401
  if (metadata.isVFR) {
@@ -5438,6 +5446,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
5438
5446
  audios,
5439
5447
  masterFxChain,
5440
5448
  urlByAbsentLocalPath,
5449
+ audioSidecarUrlByLocalPath,
5441
5450
  abortSignal,
5442
5451
  assertNotAborted,
5443
5452
  log
@@ -5461,6 +5470,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
5461
5470
  audioGain,
5462
5471
  masterFxChain,
5463
5472
  urlByAbsentLocalPath,
5473
+ audioSidecarUrlByLocalPath,
5464
5474
  keepEncoderInputAt: pcmSourcePath
5465
5475
  },
5466
5476
  compiledDir
@@ -6847,12 +6857,13 @@ var RenderChunkValidationError = class extends Error {
6847
6857
  this.code = code;
6848
6858
  }
6849
6859
  };
6850
- function createChunkVideoFrameInjectorFactory(frameLookup) {
6851
- if (!(frameLookup instanceof Promise)) return () => createVideoFrameInjector2(frameLookup);
6860
+ function createChunkVideoFrameInjectorFactory(frameLookup, options) {
6861
+ if (!(frameLookup instanceof Promise))
6862
+ return () => createVideoFrameInjector2(frameLookup, options);
6852
6863
  return () => {
6853
6864
  let hook;
6854
6865
  return async (page, time, heldVideoTime) => {
6855
- hook ??= createVideoFrameInjector2(await frameLookup);
6866
+ hook ??= createVideoFrameInjector2(await frameLookup, options);
6856
6867
  await hook?.(page, time, heldVideoTime);
6857
6868
  };
6858
6869
  };
@@ -8574,6 +8585,11 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
8574
8585
  const localPathByUrl = new Map(
8575
8586
  remoteFiles.map(([rel, file]) => [file.url, remoteLocalPath(rel)])
8576
8587
  );
8588
+ const audioSidecarUrlByLocalPath = new Map(
8589
+ remoteFiles.flatMap(
8590
+ ([rel, file]) => file.audioSidecarUrl ? [[remoteLocalPath(rel), file.audioSidecarUrl]] : []
8591
+ )
8592
+ );
8577
8593
  stageStarted = Date.now();
8578
8594
  const compileResult = await runCompileStage({
8579
8595
  urlByAbsentLocalPath,
@@ -8658,6 +8674,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
8658
8674
  finalCompiledDir,
8659
8675
  remoteFiles,
8660
8676
  urlByAbsentLocalPath,
8677
+ audioSidecarUrlByLocalPath,
8661
8678
  localPathByUrl,
8662
8679
  compileResult,
8663
8680
  compiled,
@@ -8675,6 +8692,7 @@ function mixPlanAudio(c, compiledDir) {
8675
8692
  workDir: c.workDir,
8676
8693
  compiledDir,
8677
8694
  urlByAbsentLocalPath: c.urlByAbsentLocalPath,
8695
+ audioSidecarUrlByLocalPath: c.audioSidecarUrlByLocalPath,
8678
8696
  totalFrames: c.totalFrames,
8679
8697
  fps: c.job.config.fps,
8680
8698
  ffmpegProcessTimeout: c.cfg.ffmpegProcessTimeout,
@@ -10283,6 +10301,22 @@ async function createVerifiedDistributedCaptureSession(serverUrl, framesDir, cap
10283
10301
  function isCaptureMode(value) {
10284
10302
  return value === "beginframe" || value === "screenshot" || value === "drawelement";
10285
10303
  }
10304
+ var MixedCaptureModesError = class extends Error {
10305
+ constructor(modes) {
10306
+ super(`[renderChunk] capture workers reported inconsistent modes: ${modes.join(",")}`);
10307
+ this.modes = modes;
10308
+ this.name = "MixedCaptureModesError";
10309
+ }
10310
+ };
10311
+ function observedChunkCaptureMode(perfs) {
10312
+ const modes = [...new Set(perfs.map((perf) => perf.captureMode))];
10313
+ if (modes.length > 1) throw new MixedCaptureModesError(modes);
10314
+ const [mode] = modes;
10315
+ if (!mode || !isCaptureMode(mode)) {
10316
+ throw new Error(`[renderChunk] capture workers reported an invalid mode: ${mode ?? "<none>"}`);
10317
+ }
10318
+ return mode;
10319
+ }
10286
10320
  function shouldRetryChunkCaptureWithScreenshot(error) {
10287
10321
  const failure = classifyCaptureFailure4(error);
10288
10322
  if (failure.kind === "cancelled" || failure.kind === "memory_exhaustion") return false;
@@ -10580,7 +10614,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
10580
10614
  ) : null;
10581
10615
  videoFrameLookup?.catch(() => {
10582
10616
  });
10583
- const createChunkVideoFrameInjector = createChunkVideoFrameInjectorFactory(videoFrameLookup);
10617
+ const createChunkVideoFrameInjector = createChunkVideoFrameInjectorFactory(
10618
+ videoFrameLookup,
10619
+ frameCacheOptions(cfg, chunkWorkerCount)
10620
+ );
10584
10621
  const captureStarted = Date.now();
10585
10622
  captureMode = await runCaptureWithScreenshotFallback({
10586
10623
  forceScreenshot: forceScreenshotForChunk,
@@ -10634,17 +10671,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
10634
10671
  },
10635
10672
  frameRange: range
10636
10673
  });
10637
- const observedModes = new Set(capturePerfs.map((perf) => perf.captureMode));
10638
- if (observedModes.size !== 1 || ![...observedModes].every(isCaptureMode)) {
10639
- throw new Error(
10640
- `[renderChunk] capture workers reported invalid or inconsistent modes: ${[...observedModes].join(",") || "<none>"}`
10641
- );
10642
- }
10643
- const [observedMode] = observedModes;
10644
- if (!observedMode || !isCaptureMode(observedMode)) {
10645
- throw new Error("[renderChunk] capture completed without an observed mode");
10646
- }
10647
- return observedMode;
10674
+ return observedChunkCaptureMode(capturePerfs);
10648
10675
  },
10649
10676
  resetForScreenshotRetry: () => {
10650
10677
  rmSync13(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
@@ -11146,7 +11173,36 @@ function hashProjectDir(projectDir, files = listSiteFiles(projectDir)) {
11146
11173
  }
11147
11174
  return hash.digest("hex").slice(0, 16);
11148
11175
  }
11176
+
11177
+ // src/services/distributed/audioSidecar.ts
11178
+ import { extractAudioMetadata as extractAudioMetadata4, runFfmpeg as runFfmpeg8 } from "@hyperframes/engine";
11179
+ import { extname as extname5 } from "node:path";
11180
+ var AUDIO_SIDECAR_MIN_BYTES = 32 * 1024 * 1024;
11181
+ var AUDIO_SIDECAR_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v"]);
11182
+ function wantsAudioSidecar(path, sizeBytes) {
11183
+ return sizeBytes >= AUDIO_SIDECAR_MIN_BYTES && AUDIO_SIDECAR_SOURCE_EXTENSIONS.has(extname5(path).toLowerCase());
11184
+ }
11185
+ async function buildAudioSidecar(inputPath, outputPath, signal) {
11186
+ const audio = await extractAudioMetadata4(inputPath, { signal }).catch(() => null);
11187
+ if (!audio) return await ffmpegIsMissing(signal) ? "unavailable" : "none";
11188
+ if (audio.audioStreamCount !== 1 || (audio.audioStreamLeadSeconds ?? 0) > 0) return "none";
11189
+ const result = await runFfmpeg8(
11190
+ ["-v", "error", "-y", "-i", inputPath, "-map", "0:a:0", "-vn", "-c:a", "copy"].concat([
11191
+ "-movflags",
11192
+ "+faststart",
11193
+ "-f",
11194
+ "mp4",
11195
+ outputPath
11196
+ ]),
11197
+ { signal }
11198
+ );
11199
+ if (result.success) return "built";
11200
+ return isMissing(result) ? "unavailable" : "none";
11201
+ }
11202
+ var isMissing = (result) => result.error?.code === "ENOENT";
11203
+ var ffmpegIsMissing = async (signal) => isMissing(await runFfmpeg8(["-version"], { signal }));
11149
11204
  export {
11205
+ AUDIO_SIDECAR_MIN_BYTES,
11150
11206
  CHUNK_EXCEEDS_WORKER_TIMEOUT,
11151
11207
  CHUNK_FRAME_RANGE_INVALID,
11152
11208
  CURRENT_PLAN_PROTOCOL,
@@ -11189,6 +11245,7 @@ export {
11189
11245
  applyRuntimeEnvSnapshot,
11190
11246
  assemble,
11191
11247
  assembleV2,
11248
+ buildAudioSidecar,
11192
11249
  buildChunkSlices,
11193
11250
  buildPlanAudio,
11194
11251
  buildStitchedHeader,
@@ -11224,5 +11281,6 @@ export {
11224
11281
  stitchesSeparateAudio,
11225
11282
  validateDistributedRenderConfig,
11226
11283
  validatePlanV2MaterializedTarget,
11227
- validateVariablesPayload
11284
+ validateVariablesPayload,
11285
+ wantsAudioSidecar
11228
11286
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "0.1.0",
3
3
  "buildId": "dev",
4
- "sha256": "eee27ae31219b3ee97526ca8d8716b921e7f5bce461e1d62f1ff2a49eb26ea67",
4
+ "sha256": "cd5f930c1b25aea193005449687af10c1b706fc870e51d1ff160dd46751d1581",
5
5
  "artifacts": {
6
6
  "iife": "hyperframe.runtime.iife.js",
7
7
  "esm": "hyperframe.runtime.mjs"