@hyperframes/producer 0.9.14 → 0.9.16

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.
@@ -2,11 +2,11 @@
2
2
 
3
3
  // src/server.ts
4
4
  import {
5
- existsSync as existsSync21,
5
+ existsSync as existsSync23,
6
6
  mkdirSync as mkdirSync11,
7
7
  statSync as statSync10,
8
8
  mkdtempSync as mkdtempSync4,
9
- writeFileSync as writeFileSync7,
9
+ writeFileSync as writeFileSync8,
10
10
  rmSync as rmSync11,
11
11
  createReadStream as createReadStream3
12
12
  } from "node:fs";
@@ -26,12 +26,12 @@ import {
26
26
  // src/services/renderOrchestrator.ts
27
27
  import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
28
28
  import {
29
- existsSync as existsSync18,
29
+ existsSync as existsSync20,
30
30
  mkdirSync as mkdirSync10,
31
31
  readdirSync as readdirSync5,
32
32
  rmSync as rmSync10,
33
33
  statSync as statSync8,
34
- writeFileSync as writeFileSync6,
34
+ writeFileSync as writeFileSync7,
35
35
  copyFileSync as copyFileSync5,
36
36
  appendFileSync
37
37
  } from "fs";
@@ -46,9 +46,7 @@ import {
46
46
  toFps as toFps2
47
47
  } from "@hyperframes/core";
48
48
  import { HF_AUDIO_GROUP_TAG } from "@hyperframes/core/audio-groups";
49
- import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
50
49
  import { HTML_BODY_CSS_HEIGHT_FIRST_RE, HTML_BODY_CSS_WIDTH_FIRST_RE } from "@hyperframes/parsers";
51
- import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
52
50
  import {
53
51
  extractMediaMetadata as extractMediaMetadata4,
54
52
  resolveConfig as resolveConfig2,
@@ -79,7 +77,8 @@ import {
79
77
  isDrawElementCaptureError,
80
78
  getDrawElementVerificationDetails,
81
79
  augmentProtocolTimeoutError,
82
- augmentPageNavigationTimeoutError
80
+ augmentPageNavigationTimeoutError,
81
+ displaySize
83
82
  } from "@hyperframes/engine";
84
83
  import {
85
84
  referenceBackgroundResult,
@@ -5908,8 +5907,11 @@ function countAuthoredTimedClips(html) {
5908
5907
  }
5909
5908
 
5910
5909
  // src/services/render/stages/compileStage.ts
5910
+ import { existsSync as existsSync11, writeFileSync as writeFileSync5 } from "node:fs";
5911
5911
  import { join as join16 } from "node:path";
5912
5912
  import { suggestMatchingPreset } from "@hyperframes/core";
5913
+ import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
5914
+ import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
5913
5915
 
5914
5916
  // src/services/htmlCompiler.ts
5915
5917
  import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
@@ -8614,11 +8616,36 @@ function adaptAspectAgnosticResolution(requested, aspectAgnostic, width, height,
8614
8616
  });
8615
8617
  return flipped;
8616
8618
  }
8619
+ function resolveEntryHtmlPath(input) {
8620
+ const { projectDir, workDir, entryFile, log } = input;
8621
+ const htmlPath = join16(projectDir, entryFile);
8622
+ const rawEntry = readTextFile2(htmlPath);
8623
+ if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
8624
+ const projectIndexPath = join16(projectDir, "index.html");
8625
+ if (!existsSync11(projectIndexPath)) {
8626
+ throw new Error(
8627
+ `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
8628
+ );
8629
+ }
8630
+ const standaloneHtml = extractStandaloneEntryFromIndex(
8631
+ readTextFile2(projectIndexPath),
8632
+ entryFile,
8633
+ rawEntry
8634
+ );
8635
+ if (!standaloneHtml) {
8636
+ throw new Error(
8637
+ `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
8638
+ );
8639
+ }
8640
+ const wrapperPath = join16(workDir, "standalone-entry.html");
8641
+ writeFileSync5(wrapperPath, standaloneHtml, "utf-8");
8642
+ log.info("Extracted standalone entry from index.html host context", { entryFile });
8643
+ return wrapperPath;
8644
+ }
8617
8645
  async function runCompileStage(input) {
8618
8646
  const {
8619
8647
  projectDir,
8620
8648
  workDir,
8621
- htmlPath,
8622
8649
  entryFile,
8623
8650
  job,
8624
8651
  cfg,
@@ -8629,6 +8656,7 @@ async function runCompileStage(input) {
8629
8656
  allowSystemFontCapture,
8630
8657
  abortSignal
8631
8658
  } = input;
8659
+ const htmlPath = resolveEntryHtmlPath({ projectDir, workDir, entryFile, log });
8632
8660
  const compileStart = Date.now();
8633
8661
  const compiled = await compileForRender(projectDir, htmlPath, join16(workDir, "downloads"), {
8634
8662
  log,
@@ -9428,7 +9456,7 @@ function validateRenderDuration(input) {
9428
9456
  }
9429
9457
 
9430
9458
  // src/services/render/stages/extractVideosStage.ts
9431
- import { existsSync as existsSync11 } from "node:fs";
9459
+ import { existsSync as existsSync12 } from "node:fs";
9432
9460
  import { join as join18 } from "node:path";
9433
9461
  import {
9434
9462
  classifyVideoExtractionError,
@@ -9698,7 +9726,7 @@ async function runExtractVideosStage(input) {
9698
9726
  const probeFailures = await Promise.all(
9699
9727
  composition.videos.map(async (v) => {
9700
9728
  const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
9701
- if (!existsSync11(videoPath)) return null;
9729
+ if (!existsSync12(videoPath)) return null;
9702
9730
  try {
9703
9731
  const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry(() => extractMediaMetadata3(videoPath), {
9704
9732
  signal: abortSignal,
@@ -9736,7 +9764,7 @@ async function runExtractVideosStage(input) {
9736
9764
  const probed = await Promise.all(
9737
9765
  composition.images.map(async (img) => {
9738
9766
  const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
9739
- if (!existsSync11(imgPath)) return null;
9767
+ if (!existsSync12(imgPath)) return null;
9740
9768
  const colorSpace = await probeColorSpaceSafely(imgPath, log);
9741
9769
  if (isHdrColorSpace2(colorSpace)) {
9742
9770
  nativeHdrImageIds.add(img.id);
@@ -9856,7 +9884,7 @@ async function appendVideoAudioFromProbe(input) {
9856
9884
  videos.map(async (video) => {
9857
9885
  const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
9858
9886
  const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
9859
- if (!isHttpUrl2(readFrom) && !existsSync11(readFrom)) return null;
9887
+ if (!isHttpUrl2(readFrom) && !existsSync12(readFrom)) return null;
9860
9888
  const metadata = await extractMediaMetadata3(readFrom);
9861
9889
  const window2 = await resolveFinalFrameExtractionWindow2(
9862
9890
  readFrom,
@@ -9876,7 +9904,7 @@ async function appendVideoAudioFromProbe(input) {
9876
9904
  }
9877
9905
 
9878
9906
  // src/services/render/videoLayerPassthroughPlan.ts
9879
- import { existsSync as existsSync12 } from "node:fs";
9907
+ import { existsSync as existsSync13 } from "node:fs";
9880
9908
  import {
9881
9909
  buildVideoLayerInput,
9882
9910
  isHttpUrl as isHttpUrl3,
@@ -9944,7 +9972,7 @@ async function planVideoLayerPassthrough(input) {
9944
9972
  const { video } = q;
9945
9973
  if (isHttpUrl3(video.src)) return { reason: "remote_source" };
9946
9974
  const path = resolveProjectRelativeSrc4(video.src, input.projectDir, input.compiledDir);
9947
- if (!existsSync12(path)) return { reason: "source_missing" };
9975
+ if (!existsSync13(path)) return { reason: "source_missing" };
9948
9976
  const layout = await probeVideoLayout(input.page, video.id, input.canvas, input.duration);
9949
9977
  if ("reason" in layout) return layout;
9950
9978
  const layer = await buildVideoLayerInput(
@@ -9959,6 +9987,7 @@ async function planVideoLayerPassthrough(input) {
9959
9987
  }
9960
9988
 
9961
9989
  // src/services/render/stages/audioStage.ts
9990
+ import { existsSync as existsSync15 } from "node:fs";
9962
9991
  import { join as join21 } from "node:path";
9963
9992
  import {
9964
9993
  MIXED_AUDIO_FILENAME,
@@ -9969,7 +9998,7 @@ import {
9969
9998
  } from "@hyperframes/engine";
9970
9999
 
9971
10000
  // src/services/render/audioDeliveryLimit.ts
9972
- import { existsSync as existsSync13 } from "node:fs";
10001
+ import { existsSync as existsSync14 } from "node:fs";
9973
10002
  import { dirname as dirname9, join as join20 } from "node:path";
9974
10003
  import {
9975
10004
  applyAudioFxChain,
@@ -10092,8 +10121,9 @@ var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
10092
10121
  var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
10093
10122
  var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
10094
10123
  var MAX_TRUE_PEAK_CORRECTION_PASSES = 3;
10124
+ var gainFilter = (gainDb) => `volume=${gainDb.toFixed(3)}dB`;
10095
10125
  function aacTruePeakCorrectionFilter(targetDurationSeconds, attenuationDb) {
10096
- return `volume=${attenuationDb.toFixed(3)}dB,apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
10126
+ return `${gainFilter(attenuationDb)},apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
10097
10127
  }
10098
10128
  function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
10099
10129
  const delta = targetDurationSeconds - sourceDurationSeconds;
@@ -10131,7 +10161,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
10131
10161
  const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
10132
10162
  const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
10133
10163
  const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
10134
- const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal);
10164
+ const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath);
10135
10165
  const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg || input.encodeAac ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
10136
10166
  const [videoResult, audioResult] = await Promise.allSettled([
10137
10167
  probeVideo(input.videoPath),
@@ -10179,7 +10209,12 @@ async function padOrTrimAudioToVideoFrameCount(input) {
10179
10209
  );
10180
10210
  try {
10181
10211
  for (const step of plan.steps) {
10182
- const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(input.audioPath, input.outputPath, targetDurationSeconds, step.filter);
10212
+ const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
10213
+ input.sourcePath ?? input.audioPath,
10214
+ input.outputPath,
10215
+ targetDurationSeconds,
10216
+ step.filter
10217
+ );
10183
10218
  if (!ffmpegResult.success) {
10184
10219
  return {
10185
10220
  success: false,
@@ -10208,6 +10243,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
10208
10243
  if (probeTruePeak) {
10209
10244
  const correction = await enforceAacTruePeak({
10210
10245
  audioPath: input.outputPath,
10246
+ sourcePath: input.sourcePath,
10211
10247
  targetDurationSeconds,
10212
10248
  signal: input.signal,
10213
10249
  probeTruePeak,
@@ -10243,9 +10279,27 @@ async function constrainAacTruePeak(input) {
10243
10279
  targetDurationSeconds: input.targetDurationSeconds,
10244
10280
  signal: input.signal,
10245
10281
  probeTruePeak: input.probeTruePeakDbfs ?? ((audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal })),
10246
- encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
10282
+ encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath),
10283
+ rewriteSource: input.rewriteSource ?? ((sourcePath, outputPath, filter) => defaultRunFfmpeg(
10284
+ ["-i", sourcePath, "-af", filter, "-c:a", "pcm_f32le", "-y", outputPath],
10285
+ timeoutMs,
10286
+ input.signal
10287
+ ))
10247
10288
  });
10248
10289
  }
10290
+ async function cutSource(input, scratchDir, attenuationDb) {
10291
+ if (!input.sourcePath || !input.rewriteSource) return true;
10292
+ const cutPath = join19(scratchDir, "source.wav");
10293
+ try {
10294
+ const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
10295
+ if (!result.success) throw new Error(result.error);
10296
+ renameWithRetry5(cutPath, input.sourcePath, { copyFallback: true });
10297
+ return true;
10298
+ } catch {
10299
+ removeScratch(input.sourcePath);
10300
+ return false;
10301
+ }
10302
+ }
10249
10303
  async function enforceAacTruePeak(input) {
10250
10304
  let scratchDir;
10251
10305
  try {
@@ -10265,8 +10319,10 @@ async function enforceAacTruePeak(input) {
10265
10319
  }
10266
10320
  if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
10267
10321
  if (measuredPath !== correctedPath) return { success: true };
10322
+ const sourceCut = await cutSource(input, scratchDir, attenuationDb);
10268
10323
  renameWithRetry5(correctedPath, input.audioPath, { copyFallback: true });
10269
- return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
10324
+ const loweredDb = Number((-attenuationDb).toFixed(2));
10325
+ return sourceCut ? { success: true, loweredDb } : { success: true, loweredDb, sourceStale: true };
10270
10326
  }
10271
10327
  if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
10272
10328
  const headroomDb = AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB * 2 ** pass;
@@ -10276,7 +10332,7 @@ async function enforceAacTruePeak(input) {
10276
10332
  const referenceDbfs = Math.max(truePeakDbfs, (sourcePeakDbfs ?? -Infinity) + attenuationDb);
10277
10333
  attenuationDb += AAC_DELIVERY_TRUE_PEAK_DBFS - headroomDb - referenceDbfs;
10278
10334
  const result = await input.encode(
10279
- input.audioPath,
10335
+ input.sourcePath ?? input.audioPath,
10280
10336
  correctedPath,
10281
10337
  input.targetDurationSeconds,
10282
10338
  aacTruePeakCorrectionFilter(input.targetDurationSeconds, attenuationDb)
@@ -10449,7 +10505,7 @@ async function limitDeliveryPeaks(input, deps = {
10449
10505
  applyChain: applyAudioFxChain,
10450
10506
  encode: encodeAac2,
10451
10507
  rename: renameWithRetry6,
10452
- sourceExists: existsSync13
10508
+ sourceExists: existsSync14
10453
10509
  }) {
10454
10510
  const { audioPath, sourcePath, signal, timeoutMs } = input;
10455
10511
  if (!deps.sourceExists(sourcePath)) return { success: true };
@@ -10505,36 +10561,54 @@ async function runAudioStageScoped(input) {
10505
10561
  const stageStart = Date.now();
10506
10562
  const pcmSourcePath = join21(input.workDir, "audio-source.wav");
10507
10563
  const duration = input.totalFrames * input.fps.den / input.fps.num;
10564
+ let deliverySourcePath;
10508
10565
  try {
10509
10566
  const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
10510
10567
  let audioLoweredDb;
10511
10568
  if (result.hasAudio) {
10512
10569
  const guardStart = Date.now();
10513
- const limited = await limitDeliveryPeaks({
10514
- audioPath: result.audioOutputPath,
10515
- sourcePath: pcmSourcePath,
10516
- targetDurationSeconds: duration,
10517
- signal: input.abortSignal,
10518
- timeoutMs: input.ffmpegProcessTimeout,
10519
- warn: (message, details) => input.log?.warn?.(message, details)
10520
- });
10521
- if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
10522
- const peak = await constrainAacTruePeak({
10523
- audioPath: result.audioOutputPath,
10524
- sourcePath: pcmSourcePath,
10525
- targetDurationSeconds: duration,
10526
- signal: input.abortSignal,
10527
- timeoutMs: input.ffmpegProcessTimeout
10528
- });
10529
- if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
10530
- audioLoweredDb = peak.loweredDb;
10570
+ const guard = await guardDeliveryPeaks(
10571
+ input,
10572
+ result.audioOutputPath,
10573
+ pcmSourcePath,
10574
+ duration
10575
+ );
10576
+ audioLoweredDb = guard.loweredDb;
10531
10577
  input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
10578
+ if (input.keepDeliverySource && !guard.sourceStale && existsSync15(pcmSourcePath)) {
10579
+ deliverySourcePath = pcmSourcePath;
10580
+ }
10532
10581
  }
10533
- return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
10582
+ return {
10583
+ ...result,
10584
+ audioLoweredDb,
10585
+ deliverySourcePath,
10586
+ audioProcessMs: Date.now() - stageStart
10587
+ };
10534
10588
  } finally {
10535
- removeScratch2(pcmSourcePath);
10589
+ if (!deliverySourcePath) removeScratch2(pcmSourcePath);
10536
10590
  }
10537
10591
  }
10592
+ async function guardDeliveryPeaks(input, audioPath, sourcePath, duration) {
10593
+ const limited = await limitDeliveryPeaks({
10594
+ audioPath,
10595
+ sourcePath,
10596
+ targetDurationSeconds: duration,
10597
+ signal: input.abortSignal,
10598
+ timeoutMs: input.ffmpegProcessTimeout,
10599
+ warn: (message, details) => input.log?.warn?.(message, details)
10600
+ });
10601
+ if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
10602
+ const peak = await constrainAacTruePeak({
10603
+ audioPath,
10604
+ sourcePath,
10605
+ targetDurationSeconds: duration,
10606
+ signal: input.abortSignal,
10607
+ timeoutMs: input.ffmpegProcessTimeout
10608
+ });
10609
+ if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
10610
+ return { loweredDb: peak.loweredDb, sourceStale: peak.sourceStale };
10611
+ }
10538
10612
  async function mixAudioForDelivery(input, duration, pcmSourcePath) {
10539
10613
  const {
10540
10614
  projectDir,
@@ -10629,7 +10703,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
10629
10703
  }
10630
10704
 
10631
10705
  // src/services/render/stages/captureStage.ts
10632
- import { existsSync as existsSync14, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
10706
+ import { existsSync as existsSync16, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
10633
10707
  import { join as join22 } from "node:path";
10634
10708
  import {
10635
10709
  classifyCaptureFailure as classifyCaptureFailure2,
@@ -10700,7 +10774,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
10700
10774
  }
10701
10775
  function measureCaptureFrameBytes(workDir, framesDir) {
10702
10776
  let total = sampleDirectoryBytes(framesDir);
10703
- if (!existsSync14(workDir)) return total;
10777
+ if (!existsSync16(workDir)) return total;
10704
10778
  for (const attempt of readdirSync3(workDir)) {
10705
10779
  if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
10706
10780
  total += sampleDirectoryBytes(join22(workDir, attempt));
@@ -11003,7 +11077,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
11003
11077
  }
11004
11078
 
11005
11079
  // src/services/render/stages/captureHdrStage.ts
11006
- import { existsSync as existsSync16, mkdirSync as mkdirSync8 } from "node:fs";
11080
+ import { existsSync as existsSync18, mkdirSync as mkdirSync8 } from "node:fs";
11007
11081
  import { join as join26 } from "node:path";
11008
11082
  import {
11009
11083
  closeCaptureSession as closeCaptureSession7,
@@ -11249,7 +11323,7 @@ import { Worker } from "node:worker_threads";
11249
11323
  import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
11250
11324
  import { dirname as dirname10, join as join24 } from "node:path";
11251
11325
  import { createRequire } from "node:module";
11252
- import { existsSync as existsSync15 } from "node:fs";
11326
+ import { existsSync as existsSync17 } from "node:fs";
11253
11327
  import { cpus as cpus2 } from "node:os";
11254
11328
  function resolveWorkerEntry(explicit) {
11255
11329
  if (explicit && explicit.length > 0) {
@@ -11262,7 +11336,7 @@ function resolveWorkerEntry(explicit) {
11262
11336
  }
11263
11337
  const moduleDir = dirname10(fileURLToPath3(import.meta.url));
11264
11338
  const jsPath = join24(moduleDir, "shaderTransitionWorker.js");
11265
- if (existsSync15(jsPath)) return { path: jsPath, isTs: false };
11339
+ if (existsSync17(jsPath)) return { path: jsPath, isTs: false };
11266
11340
  const tsPath = join24(moduleDir, "shaderTransitionWorker.ts");
11267
11341
  return { path: tsPath, isTs: true };
11268
11342
  }
@@ -11823,7 +11897,7 @@ async function runCaptureHdrStage(input) {
11823
11897
  }
11824
11898
  const debugDumpEnabled = process.env.KEEP_TEMP === "1";
11825
11899
  const debugDumpDir = debugDumpEnabled ? join26(framesDir, "debug-composite") : null;
11826
- if (debugDumpDir && !existsSync16(debugDumpDir)) {
11900
+ if (debugDumpDir && !existsSync18(debugDumpDir)) {
11827
11901
  mkdirSync8(debugDumpDir, { recursive: true });
11828
11902
  }
11829
11903
  const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
@@ -11980,13 +12054,13 @@ async function runCaptureHdrStage(input) {
11980
12054
  // src/services/render/stages/encodeStage.ts
11981
12055
  import {
11982
12056
  copyFileSync as copyFileSync4,
11983
- existsSync as existsSync17,
12057
+ existsSync as existsSync19,
11984
12058
  mkdirSync as mkdirSync9,
11985
12059
  readdirSync as readdirSync4,
11986
12060
  readFileSync as readFileSync8,
11987
12061
  rmSync as rmSync9,
11988
12062
  statSync as statSync7,
11989
- writeFileSync as writeFileSync5
12063
+ writeFileSync as writeFileSync6
11990
12064
  } from "node:fs";
11991
12065
  import { readFile as readFile2 } from "node:fs/promises";
11992
12066
  import { dirname as dirname11, join as join27 } from "node:path";
@@ -12082,7 +12156,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
12082
12156
  const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
12083
12157
  return gifEncodeFailure(startTime, outputPath, error);
12084
12158
  }
12085
- writeFileSync5(outputPath, gif);
12159
+ writeFileSync6(outputPath, gif);
12086
12160
  return null;
12087
12161
  }
12088
12162
  async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
@@ -12114,7 +12188,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
12114
12188
  const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
12115
12189
  if (failure) return failure;
12116
12190
  }
12117
- const fileSize = existsSync17(outputPath) ? statSync7(outputPath).size : 0;
12191
+ const fileSize = existsSync19(outputPath) ? statSync7(outputPath).size : 0;
12118
12192
  return {
12119
12193
  success: true,
12120
12194
  outputPath,
@@ -12275,7 +12349,7 @@ async function runEncodeStage(input) {
12275
12349
  const stage5Start = Date.now();
12276
12350
  if (isPngSequence) {
12277
12351
  const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
12278
- if (!existsSync17(outputPath)) mkdirSync9(outputPath, { recursive: true });
12352
+ if (!existsSync19(outputPath)) mkdirSync9(outputPath, { recursive: true });
12279
12353
  const captured = readdirSync4(framesDir).filter((name) => name.endsWith(".png")).sort();
12280
12354
  if (captured.length === 0) {
12281
12355
  throw new Error(
@@ -12286,7 +12360,7 @@ async function runEncodeStage(input) {
12286
12360
  const dst = join27(outputPath, formatExportFrameName(i, "png"));
12287
12361
  copyFileSync4(join27(framesDir, name), dst);
12288
12362
  });
12289
- if (hasAudio && audioOutputPath && existsSync17(audioOutputPath)) {
12363
+ if (hasAudio && audioOutputPath && existsSync19(audioOutputPath)) {
12290
12364
  copyFileSync4(audioOutputPath, join27(outputPath, MIXED_AUDIO_FILENAME2));
12291
12365
  log.info(
12292
12366
  `[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
@@ -12397,6 +12471,7 @@ async function assembleOutput(input, onSecondsWritten) {
12397
12471
  const normalizeResult = await padOrTrimAudioToVideoFrameCount({
12398
12472
  videoPath: videoOnlyPath,
12399
12473
  audioPath: audioOutputPath,
12474
+ sourcePath: input.audioPcmPath,
12400
12475
  outputPath: normalizedAudioPath,
12401
12476
  signal: abortSignal,
12402
12477
  timeoutMs: ffmpegProcessTimeout
@@ -12667,18 +12742,14 @@ function hasUsableVideoDimensions(metadata) {
12667
12742
  return Number.isFinite(metadata.width) && Number.isFinite(metadata.height) && metadata.width > 0 && metadata.height > 0;
12668
12743
  }
12669
12744
  function collectVideoMetadataHints(extractedVideos) {
12670
- return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({
12671
- id: video.videoId,
12672
- width: video.metadata.width,
12673
- height: video.metadata.height
12674
- })).sort((a, b) => a.id.localeCompare(b.id));
12745
+ return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({ id: video.videoId, ...displaySize(video.metadata) })).sort((a, b) => a.id.localeCompare(b.id));
12675
12746
  }
12676
12747
  function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
12677
12748
  const ranges = [];
12678
12749
  let rangeStart = null;
12679
12750
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
12680
12751
  const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
12681
- const missing = !existsSync18(framePath) || statSync8(framePath).size <= 8;
12752
+ const missing = !existsSync20(framePath) || statSync8(framePath).size <= 8;
12682
12753
  if (missing && rangeStart === null) {
12683
12754
  rangeStart = frameIndex;
12684
12755
  } else if (!missing && rangeStart !== null) {
@@ -12803,7 +12874,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
12803
12874
  let captured = 0;
12804
12875
  for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
12805
12876
  const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
12806
- if (existsSync18(framePath)) captured++;
12877
+ if (existsSync20(framePath)) captured++;
12807
12878
  }
12808
12879
  return captured;
12809
12880
  }
@@ -13468,7 +13539,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
13468
13539
  validateDeliveryEncoderConfig(job.config);
13469
13540
  const debugDir = resolveRenderDebugDir();
13470
13541
  const outputDir = dirname12(outputPath);
13471
- if (!existsSync18(outputDir)) mkdirSync10(outputDir, { recursive: true });
13542
+ if (!existsSync20(outputDir)) mkdirSync10(outputDir, { recursive: true });
13472
13543
  const workDir = job.config.debug ? join28(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
13473
13544
  const pipelineStart = Date.now();
13474
13545
  const baseLog = job.config.logger ?? defaultLogger;
@@ -13610,7 +13681,7 @@ async function executeRenderPipeline(input) {
13610
13681
  job.startedAt = /* @__PURE__ */ new Date();
13611
13682
  assertNotAborted();
13612
13683
  assertConfiguredFfmpegBinariesExist();
13613
- if (!existsSync18(workDir)) mkdirSync10(workDir, { recursive: true });
13684
+ if (!existsSync20(workDir)) mkdirSync10(workDir, { recursive: true });
13614
13685
  if (job.config.debug) {
13615
13686
  log.info("[Render] Debug artifacts enabled", { workDir, logPath });
13616
13687
  }
@@ -13639,36 +13710,11 @@ async function executeRenderPipeline(input) {
13639
13710
  requestedWorkers: job.config.workers ?? "auto"
13640
13711
  });
13641
13712
  const entryFile = job.config.entryFile || "index.html";
13642
- let htmlPath = join28(projectDir, entryFile);
13643
- if (!existsSync18(htmlPath)) {
13713
+ const htmlPath = join28(projectDir, entryFile);
13714
+ if (!existsSync20(htmlPath)) {
13644
13715
  throw new Error(`Entry file not found: ${htmlPath}`);
13645
13716
  }
13646
13717
  assertNotAborted();
13647
- const rawEntry = readTextFile2(htmlPath);
13648
- if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
13649
- const wrapperPath = join28(workDir, "standalone-entry.html");
13650
- const projectIndexPath = join28(projectDir, "index.html");
13651
- if (!existsSync18(projectIndexPath)) {
13652
- throw new Error(
13653
- `Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
13654
- );
13655
- }
13656
- const standaloneHtml = extractStandaloneEntryFromIndex(
13657
- readTextFile2(projectIndexPath),
13658
- entryFile,
13659
- rawEntry
13660
- );
13661
- if (!standaloneHtml) {
13662
- throw new Error(
13663
- `Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
13664
- );
13665
- }
13666
- writeFileSync6(wrapperPath, standaloneHtml, "utf-8");
13667
- htmlPath = wrapperPath;
13668
- log.info("Extracted standalone entry from index.html host context", {
13669
- entryFile
13670
- });
13671
- }
13672
13718
  const stage1Start = Date.now();
13673
13719
  updateJobStatus(job, "preprocessing", "Compiling composition", 5, onProgress, {
13674
13720
  code: "compile"
@@ -13680,7 +13726,6 @@ async function executeRenderPipeline(input) {
13680
13726
  () => runCompileStage({
13681
13727
  projectDir,
13682
13728
  workDir,
13683
- htmlPath,
13684
13729
  entryFile,
13685
13730
  job,
13686
13731
  cfg,
@@ -13959,7 +14004,8 @@ async function executeRenderPipeline(input) {
13959
14004
  masterFxChain: composition.masterFxChain,
13960
14005
  abortSignal: executionSignal,
13961
14006
  assertNotAborted,
13962
- log
14007
+ log,
14008
+ keepDeliverySource: true
13963
14009
  })
13964
14010
  );
13965
14011
  pendingAudio = audioStage.catch(() => void 0);
@@ -13991,7 +14037,7 @@ async function executeRenderPipeline(input) {
13991
14037
  throw new Error("File server failed to initialize before frame capture");
13992
14038
  }
13993
14039
  const framesDir = join28(workDir, "captured-frames");
13994
- if (!existsSync18(framesDir)) mkdirSync10(framesDir, { recursive: true });
14040
+ if (!existsSync20(framesDir)) mkdirSync10(framesDir, { recursive: true });
13995
14041
  updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
13996
14042
  code: "start_browsers"
13997
14043
  });
@@ -14667,7 +14713,7 @@ async function executeRenderPipeline(input) {
14667
14713
  const effectiveQuality = job.config.crf ?? preset.quality;
14668
14714
  const effectiveBitrate = job.config.crf != null ? void 0 : job.config.videoBitrate;
14669
14715
  const audioResult = await audioStage;
14670
- const { audioOutputPath, hasAudio } = audioResult;
14716
+ const { audioOutputPath, deliverySourcePath, hasAudio } = audioResult;
14671
14717
  perfStages.audioProcessMs = audioResult.audioProcessMs;
14672
14718
  recordLimiterAttenuation(job, audioResult.audioLoweredDb);
14673
14719
  if (audioResult.audioError) {
@@ -15293,6 +15339,7 @@ async function executeRenderPipeline(input) {
15293
15339
  job,
15294
15340
  videoOnlyPath,
15295
15341
  audioOutputPath,
15342
+ audioPcmPath: deliverySourcePath,
15296
15343
  outputPath: stagedOutputPath,
15297
15344
  hasAudio,
15298
15345
  format: outputFormat,
@@ -15318,7 +15365,7 @@ async function executeRenderPipeline(input) {
15318
15365
  })
15319
15366
  );
15320
15367
  const totalElapsed = Date.now() - pipelineStart;
15321
- const tmpPeakBytes = existsSync18(workDir) ? sampleDirectoryBytes(workDir) : 0;
15368
+ const tmpPeakBytes = existsSync20(workDir) ? sampleDirectoryBytes(workDir) : 0;
15322
15369
  recordTransientRetryObservability();
15323
15370
  observability.checkpoint("pipeline", "artifact validated", { totalElapsedMs: totalElapsed });
15324
15371
  const observabilitySummary = observability.summary({
@@ -15390,7 +15437,7 @@ async function executeRenderPipeline(input) {
15390
15437
  job.perfSummary = perfSummary;
15391
15438
  if (job.config.debug) {
15392
15439
  try {
15393
- writeFileSync6(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
15440
+ writeFileSync7(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
15394
15441
  } catch (err) {
15395
15442
  log.debug("Failed to write perf summary", {
15396
15443
  perfOutputPath,
@@ -15399,7 +15446,7 @@ async function executeRenderPipeline(input) {
15399
15446
  }
15400
15447
  }
15401
15448
  if (job.config.debug) {
15402
- if (!isPngSequence && !isHls && existsSync18(stagedOutputPath)) {
15449
+ if (!isPngSequence && !isHls && existsSync20(stagedOutputPath)) {
15403
15450
  const debugOutput = join28(workDir, `output${videoExt}`);
15404
15451
  copyFileSync5(stagedOutputPath, debugOutput);
15405
15452
  }
@@ -15511,7 +15558,7 @@ async function executeRenderPipeline(input) {
15511
15558
 
15512
15559
  // src/services/hyperframeLint.ts
15513
15560
  import {
15514
- existsSync as existsSync19,
15561
+ existsSync as existsSync21,
15515
15562
  readFileSync as readFileSync9,
15516
15563
  statSync as statSync9,
15517
15564
  openSync as openSync5,
@@ -15565,7 +15612,7 @@ function readEntryHtml(filePath) {
15565
15612
  }
15566
15613
  function readProjectEntryFile(projectDir, preferredEntryFile) {
15567
15614
  const absProjectDir = resolve9(projectDir);
15568
- if (!existsSync19(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
15615
+ if (!existsSync21(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
15569
15616
  return { error: `Project directory not found: ${absProjectDir}` };
15570
15617
  }
15571
15618
  const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
@@ -15634,7 +15681,7 @@ async function runHyperframeLint(prepared) {
15634
15681
  import { Worker as Worker2 } from "node:worker_threads";
15635
15682
  import { fileURLToPath as fileURLToPath5 } from "node:url";
15636
15683
  import { dirname as dirname13, join as join30 } from "node:path";
15637
- import { existsSync as existsSync20 } from "node:fs";
15684
+ import { existsSync as existsSync22 } from "node:fs";
15638
15685
  var DEFAULT_HEALTH_PORT = 9848;
15639
15686
  async function startHealthWorker(options = {}) {
15640
15687
  const log = options.logger ?? defaultLogger2();
@@ -15711,7 +15758,7 @@ function resolveWorkerEntry2() {
15711
15758
  const here = dirname13(fileURLToPath5(import.meta.url));
15712
15759
  const candidates = [join30(here, "healthWorkerThread.js"), join30(here, "healthWorkerThread.ts")];
15713
15760
  for (const candidate of candidates) {
15714
- if (existsSync20(candidate)) return candidate;
15761
+ if (existsSync22(candidate)) return candidate;
15715
15762
  }
15716
15763
  return void 0;
15717
15764
  }
@@ -16261,7 +16308,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
16261
16308
  const { input, cleanupProjectDir } = prepared;
16262
16309
  const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
16263
16310
  const outputDir = dirname14(absoluteOutputPath);
16264
- if (!existsSync21(outputDir)) mkdirSync11(outputDir, { recursive: true });
16311
+ if (!existsSync23(outputDir)) mkdirSync11(outputDir, { recursive: true });
16265
16312
  return { input, cleanupProjectDir, absoluteOutputPath };
16266
16313
  }
16267
16314
  function validateRenderOverrides(body) {
@@ -16302,11 +16349,11 @@ function prepareProjectDirectory(projectDir, options) {
16302
16349
  const candidate = nonEmptyString(projectDir);
16303
16350
  if (!candidate) return null;
16304
16351
  const absProjectDir = resolve10(candidate);
16305
- if (!existsSync21(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
16352
+ if (!existsSync23(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
16306
16353
  return { error: `Project directory not found: ${absProjectDir}` };
16307
16354
  }
16308
16355
  const entry = options.entryFile || "index.html";
16309
- if (!existsSync21(resolve10(absProjectDir, entry))) {
16356
+ if (!existsSync23(resolve10(absProjectDir, entry))) {
16310
16357
  return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
16311
16358
  }
16312
16359
  return { prepared: { input: { projectDir: absProjectDir, ...options } } };
@@ -16328,7 +16375,7 @@ async function resolveInlineRenderHtml(body) {
16328
16375
  function materializeInlineProject(html, options) {
16329
16376
  const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
16330
16377
  const tempProjectDir = mkdtempSync4(join32(tempRoot, "producer-project-"));
16331
- writeFileSync7(join32(tempProjectDir, "index.html"), html, "utf-8");
16378
+ writeFileSync8(join32(tempProjectDir, "index.html"), html, "utf-8");
16332
16379
  return {
16333
16380
  prepared: {
16334
16381
  input: { projectDir: tempProjectDir, ...options },
@@ -16398,7 +16445,7 @@ function cleanupTempDir(dir, log) {
16398
16445
  }
16399
16446
  }
16400
16447
  function outputFileSize(path) {
16401
- return existsSync21(path) ? statSync10(path).size : 0;
16448
+ return existsSync23(path) ? statSync10(path).size : 0;
16402
16449
  }
16403
16450
  function createBlockingProgressReporter(log, requestId) {
16404
16451
  let lastLoggedPct = -10;
@@ -16702,7 +16749,7 @@ function createRenderHandlers(options = {}) {
16702
16749
  if (!artifact) {
16703
16750
  return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
16704
16751
  }
16705
- if (!existsSync21(artifact.path)) {
16752
+ if (!existsSync23(artifact.path)) {
16706
16753
  store.delete(token);
16707
16754
  return c.json({ success: false, error: "Output artifact file missing" }, 404);
16708
16755
  }