@hyperframes/producer 0.8.180 → 0.8.181

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.
@@ -5709,14 +5709,84 @@ import { mkdtempSync as mkdtempSync2, renameSync as renameSync2, rmSync as rmSyn
5709
5709
  import { dirname as dirname6, join as join13 } from "node:path";
5710
5710
  import {
5711
5711
  DEFAULT_CONFIG,
5712
- describeFfmpegFailure,
5712
+ describeFfmpegFailure as describeFfmpegFailure2,
5713
5713
  extractAudioMetadata as extractAudioMetadata2,
5714
5714
  getFfprobeBinary,
5715
5715
  ManagedChildProcess,
5716
- runFfmpeg as runFfmpeg4,
5716
+ runFfmpeg as runFfmpeg5,
5717
5717
  trackChildProcess
5718
5718
  } from "@hyperframes/engine";
5719
5719
  import { redactKnownPaths, redactTelemetryString as redactTelemetryString3 } from "@hyperframes/core";
5720
+
5721
+ // src/services/render/audioTruePeak.ts
5722
+ import { aacSegmentCount, describeFfmpegFailure, runFfmpeg as runFfmpeg4 } from "@hyperframes/engine";
5723
+ var toDbfs = (token) => token.toLowerCase() === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
5724
+ function checkedDbfs(value) {
5725
+ if (Number.isNaN(value) || value === Number.POSITIVE_INFINITY) {
5726
+ throw new Error("[audioTruePeak] FFmpeg did not report AAC true peak");
5727
+ }
5728
+ return value;
5729
+ }
5730
+ async function scanTruePeak(audioPath, trim, timeoutMs, signal) {
5731
+ let firstBlock;
5732
+ let laterBlocks = Number.NEGATIVE_INFINITY;
5733
+ let partialLine = "";
5734
+ const readLines = (chunk) => {
5735
+ const lines = (partialLine + chunk).split(/\r|\n/);
5736
+ partialLine = lines.pop() ?? "";
5737
+ for (const line of lines) {
5738
+ const ftpk = /FTPK:([^d]*)dBFS/.exec(line)?.[1];
5739
+ if (!ftpk) continue;
5740
+ const block = Math.max(...ftpk.trim().split(/\s+/).map(toDbfs));
5741
+ if (firstBlock === void 0) firstBlock = block;
5742
+ else laterBlocks = Math.max(laterBlocks, block);
5743
+ }
5744
+ };
5745
+ const result = await runFfmpeg4(
5746
+ ["-i", audioPath, "-map", "0:a:0", "-vn", "-af", `${trim}ebur128=peak=true`, "-f", "null", "-"],
5747
+ { signal, timeout: timeoutMs, onStderr: readLines }
5748
+ );
5749
+ if (!result.success) {
5750
+ throw new Error(`[audioTruePeak] ${describeFfmpegFailure(result, timeoutMs)}`);
5751
+ }
5752
+ const summary = [...result.stderr.matchAll(/^\s*Peak:\s*([+-]?(?:[\d.]+|inf)) dBFS$/gim)];
5753
+ return {
5754
+ peak: checkedDbfs(toDbfs(summary.at(-1)?.[1] ?? "nan")),
5755
+ firstBlock: checkedDbfs(firstBlock ?? Number.NEGATIVE_INFINITY),
5756
+ laterBlocks: checkedDbfs(laterBlocks)
5757
+ };
5758
+ }
5759
+ async function measureTruePeakDbfs(audioPath, options) {
5760
+ const { timeoutMs, signal, segments } = options;
5761
+ const wholeFile = async () => (await scanTruePeak(audioPath, "", timeoutMs, signal)).peak;
5762
+ const block = 4800;
5763
+ const count = segments?.sampleRate === 48e3 ? aacSegmentCount(segments.durationSeconds) : 1;
5764
+ if (!segments || count === 1) return wholeFile();
5765
+ const cut = (k) => k * Math.floor(segments.durationSeconds * 10 / count) * block;
5766
+ const peers = new AbortController();
5767
+ const segmentSignal = signal ? AbortSignal.any([signal, peers.signal]) : peers.signal;
5768
+ const scans = await Promise.all(
5769
+ Array.from({ length: count }, (_, k) => {
5770
+ const start = k === 0 ? 0 : cut(k) - block;
5771
+ const end = k === count - 1 ? "" : `:end_sample=${cut(k + 1)}`;
5772
+ return scanTruePeak(
5773
+ audioPath,
5774
+ `atrim=start_sample=${start}${end},`,
5775
+ timeoutMs,
5776
+ segmentSignal
5777
+ );
5778
+ })
5779
+ ).catch((error) => {
5780
+ peers.abort();
5781
+ throw error;
5782
+ });
5783
+ const last = scans[count - 1];
5784
+ const counted = Math.max(scans[0].peak, ...scans.slice(1).map((scan) => scan.laterBlocks));
5785
+ if (last.peak > last.firstBlock || last.peak <= counted) return Math.max(counted, last.peak);
5786
+ return wholeFile();
5787
+ }
5788
+
5789
+ // src/services/render/audioPadTrim.ts
5720
5790
  var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
5721
5791
  var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
5722
5792
  var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
@@ -5816,7 +5886,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
5816
5886
  const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
5817
5887
  const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
5818
5888
  const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
5819
- const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg ? void 0 : (audioPath, signal) => defaultProbeAudioTruePeakDbfs(audioPath, timeoutMs, signal));
5889
+ const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
5820
5890
  const [videoResult, audioResult] = await Promise.allSettled([
5821
5891
  probeVideo(input.videoPath),
5822
5892
  probeAudio(input.audioPath, input.signal)
@@ -5925,7 +5995,11 @@ async function constrainAacTruePeak(input) {
5925
5995
  audioPath: input.audioPath,
5926
5996
  targetDurationSeconds: input.targetDurationSeconds,
5927
5997
  signal: input.signal,
5928
- probeTruePeak: (audioPath, signal) => defaultProbeAudioTruePeakDbfs(audioPath, timeoutMs, signal),
5998
+ probeTruePeak: async (audioPath, signal) => {
5999
+ const { sampleRate } = await extractAudioMetadata2(audioPath, { signal });
6000
+ const segments = { durationSeconds: input.targetDurationSeconds, sampleRate };
6001
+ return measureTruePeakDbfs(audioPath, { timeoutMs, signal, segments });
6002
+ },
5929
6003
  runner: (args) => defaultRunFfmpeg(args, timeoutMs, input.signal)
5930
6004
  });
5931
6005
  }
@@ -6062,28 +6136,12 @@ async function defaultProbeAudioInfo(audioPath, signal) {
6062
6136
  audioCodec: metadata.audioCodec
6063
6137
  };
6064
6138
  }
6065
- async function defaultProbeAudioTruePeakDbfs(audioPath, timeoutMs, signal) {
6066
- const result = await runFfmpeg4(
6067
- ["-i", audioPath, "-map", "0:a:0", "-vn", "-af", "ebur128=peak=true", "-f", "null", "-"],
6068
- { signal, timeout: timeoutMs }
6069
- );
6070
- if (!result.success) {
6071
- throw new Error(`[audioPadTrim] ${describeFfmpegFailure(result, timeoutMs)}`);
6072
- }
6073
- const matches = [...result.stderr.matchAll(/^\s*Peak:\s*([+-]?(?:[\d.]+|inf)) dBFS$/gim)];
6074
- const token = matches.at(-1)?.[1]?.toLowerCase();
6075
- const value = token === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
6076
- if (Number.isNaN(value) || value === Number.POSITIVE_INFINITY) {
6077
- throw new Error("[audioPadTrim] FFmpeg did not report AAC true peak");
6078
- }
6079
- return value;
6080
- }
6081
6139
  async function defaultRunFfmpeg(args, timeoutMs, signal) {
6082
- const result = await runFfmpeg4(args, { signal, timeout: timeoutMs });
6140
+ const result = await runFfmpeg5(args, { signal, timeout: timeoutMs });
6083
6141
  if (result.success) return { success: true };
6084
6142
  return {
6085
6143
  success: false,
6086
- error: `[audioPadTrim] ${describeFfmpegFailure(result, timeoutMs)}`,
6144
+ error: `[audioPadTrim] ${describeFfmpegFailure2(result, timeoutMs)}`,
6087
6145
  failureReason: result.failureReason
6088
6146
  };
6089
6147
  }
@@ -7375,7 +7433,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
7375
7433
  import { closeSync as closeSync4, existsSync as existsSync14, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync9 } from "node:fs";
7376
7434
  import { writeFileSync as writeFileSync5, writeSync } from "node:fs";
7377
7435
  import { join as join20 } from "node:path";
7378
- import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure2, runFfmpeg as runFfmpeg5 } from "@hyperframes/engine";
7436
+ import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
7379
7437
 
7380
7438
  // src/services/distributed/mp4Stitch.ts
7381
7439
  import {
@@ -7865,10 +7923,10 @@ async function muxChunkWithAudio(input) {
7865
7923
  const args = [...chunkMuxArgs(input, cut), "-c", "copy"];
7866
7924
  appendRenderProvenanceArgs(args, input.outputPath);
7867
7925
  args.push("-y", input.outputPath);
7868
- const result = await runFfmpeg5(args, { signal: input.signal, timeout: input.timeoutMs });
7926
+ const result = await runFfmpeg6(args, { signal: input.signal, timeout: input.timeoutMs });
7869
7927
  if (!result.success) {
7870
7928
  throw new Error(
7871
- `[chunkAudio] chunk mux failed: ${describeFfmpegFailure2(result, input.timeoutMs)}`
7929
+ `[chunkAudio] chunk mux failed: ${describeFfmpegFailure3(result, input.timeoutMs)}`
7872
7930
  );
7873
7931
  }
7874
7932
  const slice = input.chunks[input.chunkIndex];
@@ -9698,12 +9756,12 @@ import {
9698
9756
  import { dirname as dirname12, join as join26 } from "node:path";
9699
9757
  import {
9700
9758
  appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
9701
- describeFfmpegFailure as describeFfmpegFailure3,
9759
+ describeFfmpegFailure as describeFfmpegFailure4,
9702
9760
  getEncoderPreset as getEncoderPreset5,
9703
9761
  MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME4,
9704
9762
  muxVideoWithAudio as muxVideoWithAudio2,
9705
9763
  resolveConfig as resolveConfig4,
9706
- runFfmpeg as runFfmpeg6
9764
+ runFfmpeg as runFfmpeg7
9707
9765
  } from "@hyperframes/engine";
9708
9766
  import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
9709
9767
  function cfrRateControlArgs(locked) {
@@ -9912,10 +9970,10 @@ async function probeJoinedFrameInfo(videoPaths, fps, signal) {
9912
9970
  };
9913
9971
  }
9914
9972
  async function runAssembleFfmpeg(args, step, signal, timeout) {
9915
- const result = await runFfmpeg6(args, { signal, timeout });
9973
+ const result = await runFfmpeg7(args, { signal, timeout });
9916
9974
  if (!result.success) {
9917
9975
  throw encoderFailureError(`[assemble] ffmpeg ${step} failed`, {
9918
- error: describeFfmpegFailure3(result, timeout),
9976
+ error: describeFfmpegFailure4(result, timeout),
9919
9977
  failureReason: result.failureReason
9920
9978
  });
9921
9979
  }
package/dist/index.js CHANGED
@@ -10373,14 +10373,84 @@ import { mkdtempSync as mkdtempSync3, renameSync as renameSync4, rmSync as rmSyn
10373
10373
  import { dirname as dirname9, join as join25 } from "node:path";
10374
10374
  import {
10375
10375
  DEFAULT_CONFIG,
10376
- describeFfmpegFailure,
10376
+ describeFfmpegFailure as describeFfmpegFailure2,
10377
10377
  extractAudioMetadata as extractAudioMetadata2,
10378
10378
  getFfprobeBinary,
10379
10379
  ManagedChildProcess,
10380
- runFfmpeg as runFfmpeg4,
10380
+ runFfmpeg as runFfmpeg5,
10381
10381
  trackChildProcess
10382
10382
  } from "@hyperframes/engine";
10383
10383
  import { redactKnownPaths, redactTelemetryString as redactTelemetryString3 } from "@hyperframes/core";
10384
+
10385
+ // src/services/render/audioTruePeak.ts
10386
+ import { aacSegmentCount, describeFfmpegFailure, runFfmpeg as runFfmpeg4 } from "@hyperframes/engine";
10387
+ var toDbfs = (token) => token.toLowerCase() === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
10388
+ function checkedDbfs(value) {
10389
+ if (Number.isNaN(value) || value === Number.POSITIVE_INFINITY) {
10390
+ throw new Error("[audioTruePeak] FFmpeg did not report AAC true peak");
10391
+ }
10392
+ return value;
10393
+ }
10394
+ async function scanTruePeak(audioPath, trim, timeoutMs, signal) {
10395
+ let firstBlock;
10396
+ let laterBlocks = Number.NEGATIVE_INFINITY;
10397
+ let partialLine = "";
10398
+ const readLines = (chunk) => {
10399
+ const lines = (partialLine + chunk).split(/\r|\n/);
10400
+ partialLine = lines.pop() ?? "";
10401
+ for (const line of lines) {
10402
+ const ftpk = /FTPK:([^d]*)dBFS/.exec(line)?.[1];
10403
+ if (!ftpk) continue;
10404
+ const block = Math.max(...ftpk.trim().split(/\s+/).map(toDbfs));
10405
+ if (firstBlock === void 0) firstBlock = block;
10406
+ else laterBlocks = Math.max(laterBlocks, block);
10407
+ }
10408
+ };
10409
+ const result = await runFfmpeg4(
10410
+ ["-i", audioPath, "-map", "0:a:0", "-vn", "-af", `${trim}ebur128=peak=true`, "-f", "null", "-"],
10411
+ { signal, timeout: timeoutMs, onStderr: readLines }
10412
+ );
10413
+ if (!result.success) {
10414
+ throw new Error(`[audioTruePeak] ${describeFfmpegFailure(result, timeoutMs)}`);
10415
+ }
10416
+ const summary = [...result.stderr.matchAll(/^\s*Peak:\s*([+-]?(?:[\d.]+|inf)) dBFS$/gim)];
10417
+ return {
10418
+ peak: checkedDbfs(toDbfs(summary.at(-1)?.[1] ?? "nan")),
10419
+ firstBlock: checkedDbfs(firstBlock ?? Number.NEGATIVE_INFINITY),
10420
+ laterBlocks: checkedDbfs(laterBlocks)
10421
+ };
10422
+ }
10423
+ async function measureTruePeakDbfs(audioPath, options) {
10424
+ const { timeoutMs, signal, segments } = options;
10425
+ const wholeFile = async () => (await scanTruePeak(audioPath, "", timeoutMs, signal)).peak;
10426
+ const block = 4800;
10427
+ const count = segments?.sampleRate === 48e3 ? aacSegmentCount(segments.durationSeconds) : 1;
10428
+ if (!segments || count === 1) return wholeFile();
10429
+ const cut = (k) => k * Math.floor(segments.durationSeconds * 10 / count) * block;
10430
+ const peers = new AbortController();
10431
+ const segmentSignal = signal ? AbortSignal.any([signal, peers.signal]) : peers.signal;
10432
+ const scans = await Promise.all(
10433
+ Array.from({ length: count }, (_, k) => {
10434
+ const start = k === 0 ? 0 : cut(k) - block;
10435
+ const end = k === count - 1 ? "" : `:end_sample=${cut(k + 1)}`;
10436
+ return scanTruePeak(
10437
+ audioPath,
10438
+ `atrim=start_sample=${start}${end},`,
10439
+ timeoutMs,
10440
+ segmentSignal
10441
+ );
10442
+ })
10443
+ ).catch((error) => {
10444
+ peers.abort();
10445
+ throw error;
10446
+ });
10447
+ const last = scans[count - 1];
10448
+ const counted = Math.max(scans[0].peak, ...scans.slice(1).map((scan) => scan.laterBlocks));
10449
+ if (last.peak > last.firstBlock || last.peak <= counted) return Math.max(counted, last.peak);
10450
+ return wholeFile();
10451
+ }
10452
+
10453
+ // src/services/render/audioPadTrim.ts
10384
10454
  var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
10385
10455
  var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
10386
10456
  var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
@@ -10480,7 +10550,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
10480
10550
  const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
10481
10551
  const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
10482
10552
  const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
10483
- const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg ? void 0 : (audioPath, signal) => defaultProbeAudioTruePeakDbfs(audioPath, timeoutMs, signal));
10553
+ const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
10484
10554
  const [videoResult, audioResult] = await Promise.allSettled([
10485
10555
  probeVideo(input.videoPath),
10486
10556
  probeAudio(input.audioPath, input.signal)
@@ -10589,7 +10659,11 @@ async function constrainAacTruePeak(input) {
10589
10659
  audioPath: input.audioPath,
10590
10660
  targetDurationSeconds: input.targetDurationSeconds,
10591
10661
  signal: input.signal,
10592
- probeTruePeak: (audioPath, signal) => defaultProbeAudioTruePeakDbfs(audioPath, timeoutMs, signal),
10662
+ probeTruePeak: async (audioPath, signal) => {
10663
+ const { sampleRate } = await extractAudioMetadata2(audioPath, { signal });
10664
+ const segments = { durationSeconds: input.targetDurationSeconds, sampleRate };
10665
+ return measureTruePeakDbfs(audioPath, { timeoutMs, signal, segments });
10666
+ },
10593
10667
  runner: (args) => defaultRunFfmpeg(args, timeoutMs, input.signal)
10594
10668
  });
10595
10669
  }
@@ -10726,28 +10800,12 @@ async function defaultProbeAudioInfo(audioPath, signal) {
10726
10800
  audioCodec: metadata.audioCodec
10727
10801
  };
10728
10802
  }
10729
- async function defaultProbeAudioTruePeakDbfs(audioPath, timeoutMs, signal) {
10730
- const result = await runFfmpeg4(
10731
- ["-i", audioPath, "-map", "0:a:0", "-vn", "-af", "ebur128=peak=true", "-f", "null", "-"],
10732
- { signal, timeout: timeoutMs }
10733
- );
10734
- if (!result.success) {
10735
- throw new Error(`[audioPadTrim] ${describeFfmpegFailure(result, timeoutMs)}`);
10736
- }
10737
- const matches = [...result.stderr.matchAll(/^\s*Peak:\s*([+-]?(?:[\d.]+|inf)) dBFS$/gim)];
10738
- const token = matches.at(-1)?.[1]?.toLowerCase();
10739
- const value = token === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
10740
- if (Number.isNaN(value) || value === Number.POSITIVE_INFINITY) {
10741
- throw new Error("[audioPadTrim] FFmpeg did not report AAC true peak");
10742
- }
10743
- return value;
10744
- }
10745
10803
  async function defaultRunFfmpeg(args, timeoutMs, signal) {
10746
- const result = await runFfmpeg4(args, { signal, timeout: timeoutMs });
10804
+ const result = await runFfmpeg5(args, { signal, timeout: timeoutMs });
10747
10805
  if (result.success) return { success: true };
10748
10806
  return {
10749
10807
  success: false,
10750
- error: `[audioPadTrim] ${describeFfmpegFailure(result, timeoutMs)}`,
10808
+ error: `[audioPadTrim] ${describeFfmpegFailure2(result, timeoutMs)}`,
10751
10809
  failureReason: result.failureReason
10752
10810
  };
10753
10811
  }
@@ -14529,7 +14587,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
14529
14587
  import { closeSync as closeSync6, existsSync as existsSync20, ftruncateSync, mkdirSync as mkdirSync12, openSync as openSync5, readFileSync as readFileSync11 } from "node:fs";
14530
14588
  import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
14531
14589
  import { join as join32 } from "node:path";
14532
- import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure2, runFfmpeg as runFfmpeg5 } from "@hyperframes/engine";
14590
+ import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
14533
14591
 
14534
14592
  // src/services/distributed/mp4Stitch.ts
14535
14593
  import {
@@ -15019,10 +15077,10 @@ async function muxChunkWithAudio(input) {
15019
15077
  const args = [...chunkMuxArgs(input, cut), "-c", "copy"];
15020
15078
  appendRenderProvenanceArgs(args, input.outputPath);
15021
15079
  args.push("-y", input.outputPath);
15022
- const result = await runFfmpeg5(args, { signal: input.signal, timeout: input.timeoutMs });
15080
+ const result = await runFfmpeg6(args, { signal: input.signal, timeout: input.timeoutMs });
15023
15081
  if (!result.success) {
15024
15082
  throw new Error(
15025
- `[chunkAudio] chunk mux failed: ${describeFfmpegFailure2(result, input.timeoutMs)}`
15083
+ `[chunkAudio] chunk mux failed: ${describeFfmpegFailure3(result, input.timeoutMs)}`
15026
15084
  );
15027
15085
  }
15028
15086
  const slice = input.chunks[input.chunkIndex];
@@ -18233,12 +18291,12 @@ import {
18233
18291
  import { dirname as dirname17, join as join41 } from "node:path";
18234
18292
  import {
18235
18293
  appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
18236
- describeFfmpegFailure as describeFfmpegFailure3,
18294
+ describeFfmpegFailure as describeFfmpegFailure4,
18237
18295
  getEncoderPreset as getEncoderPreset5,
18238
18296
  MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME4,
18239
18297
  muxVideoWithAudio as muxVideoWithAudio2,
18240
18298
  resolveConfig as resolveConfig6,
18241
- runFfmpeg as runFfmpeg6
18299
+ runFfmpeg as runFfmpeg7
18242
18300
  } from "@hyperframes/engine";
18243
18301
  import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
18244
18302
  function cfrRateControlArgs(locked) {
@@ -18447,10 +18505,10 @@ async function probeJoinedFrameInfo(videoPaths, fps, signal) {
18447
18505
  };
18448
18506
  }
18449
18507
  async function runAssembleFfmpeg(args, step, signal, timeout) {
18450
- const result = await runFfmpeg6(args, { signal, timeout });
18508
+ const result = await runFfmpeg7(args, { signal, timeout });
18451
18509
  if (!result.success) {
18452
18510
  throw encoderFailureError(`[assemble] ffmpeg ${step} failed`, {
18453
- error: describeFfmpegFailure3(result, timeout),
18511
+ error: describeFfmpegFailure4(result, timeout),
18454
18512
  failureReason: result.failureReason
18455
18513
  });
18456
18514
  }
@@ -10356,14 +10356,84 @@ import { mkdtempSync as mkdtempSync3, renameSync as renameSync4, rmSync as rmSyn
10356
10356
  import { dirname as dirname9, join as join25 } from "node:path";
10357
10357
  import {
10358
10358
  DEFAULT_CONFIG,
10359
- describeFfmpegFailure,
10359
+ describeFfmpegFailure as describeFfmpegFailure2,
10360
10360
  extractAudioMetadata as extractAudioMetadata2,
10361
10361
  getFfprobeBinary,
10362
10362
  ManagedChildProcess,
10363
- runFfmpeg as runFfmpeg4,
10363
+ runFfmpeg as runFfmpeg5,
10364
10364
  trackChildProcess
10365
10365
  } from "@hyperframes/engine";
10366
10366
  import { redactKnownPaths, redactTelemetryString as redactTelemetryString3 } from "@hyperframes/core";
10367
+
10368
+ // src/services/render/audioTruePeak.ts
10369
+ import { aacSegmentCount, describeFfmpegFailure, runFfmpeg as runFfmpeg4 } from "@hyperframes/engine";
10370
+ var toDbfs = (token) => token.toLowerCase() === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
10371
+ function checkedDbfs(value) {
10372
+ if (Number.isNaN(value) || value === Number.POSITIVE_INFINITY) {
10373
+ throw new Error("[audioTruePeak] FFmpeg did not report AAC true peak");
10374
+ }
10375
+ return value;
10376
+ }
10377
+ async function scanTruePeak(audioPath, trim, timeoutMs, signal) {
10378
+ let firstBlock;
10379
+ let laterBlocks = Number.NEGATIVE_INFINITY;
10380
+ let partialLine = "";
10381
+ const readLines = (chunk) => {
10382
+ const lines = (partialLine + chunk).split(/\r|\n/);
10383
+ partialLine = lines.pop() ?? "";
10384
+ for (const line of lines) {
10385
+ const ftpk = /FTPK:([^d]*)dBFS/.exec(line)?.[1];
10386
+ if (!ftpk) continue;
10387
+ const block = Math.max(...ftpk.trim().split(/\s+/).map(toDbfs));
10388
+ if (firstBlock === void 0) firstBlock = block;
10389
+ else laterBlocks = Math.max(laterBlocks, block);
10390
+ }
10391
+ };
10392
+ const result = await runFfmpeg4(
10393
+ ["-i", audioPath, "-map", "0:a:0", "-vn", "-af", `${trim}ebur128=peak=true`, "-f", "null", "-"],
10394
+ { signal, timeout: timeoutMs, onStderr: readLines }
10395
+ );
10396
+ if (!result.success) {
10397
+ throw new Error(`[audioTruePeak] ${describeFfmpegFailure(result, timeoutMs)}`);
10398
+ }
10399
+ const summary = [...result.stderr.matchAll(/^\s*Peak:\s*([+-]?(?:[\d.]+|inf)) dBFS$/gim)];
10400
+ return {
10401
+ peak: checkedDbfs(toDbfs(summary.at(-1)?.[1] ?? "nan")),
10402
+ firstBlock: checkedDbfs(firstBlock ?? Number.NEGATIVE_INFINITY),
10403
+ laterBlocks: checkedDbfs(laterBlocks)
10404
+ };
10405
+ }
10406
+ async function measureTruePeakDbfs(audioPath, options) {
10407
+ const { timeoutMs, signal, segments } = options;
10408
+ const wholeFile = async () => (await scanTruePeak(audioPath, "", timeoutMs, signal)).peak;
10409
+ const block = 4800;
10410
+ const count = segments?.sampleRate === 48e3 ? aacSegmentCount(segments.durationSeconds) : 1;
10411
+ if (!segments || count === 1) return wholeFile();
10412
+ const cut = (k) => k * Math.floor(segments.durationSeconds * 10 / count) * block;
10413
+ const peers = new AbortController();
10414
+ const segmentSignal = signal ? AbortSignal.any([signal, peers.signal]) : peers.signal;
10415
+ const scans = await Promise.all(
10416
+ Array.from({ length: count }, (_, k) => {
10417
+ const start = k === 0 ? 0 : cut(k) - block;
10418
+ const end = k === count - 1 ? "" : `:end_sample=${cut(k + 1)}`;
10419
+ return scanTruePeak(
10420
+ audioPath,
10421
+ `atrim=start_sample=${start}${end},`,
10422
+ timeoutMs,
10423
+ segmentSignal
10424
+ );
10425
+ })
10426
+ ).catch((error) => {
10427
+ peers.abort();
10428
+ throw error;
10429
+ });
10430
+ const last = scans[count - 1];
10431
+ const counted = Math.max(scans[0].peak, ...scans.slice(1).map((scan) => scan.laterBlocks));
10432
+ if (last.peak > last.firstBlock || last.peak <= counted) return Math.max(counted, last.peak);
10433
+ return wholeFile();
10434
+ }
10435
+
10436
+ // src/services/render/audioPadTrim.ts
10367
10437
  var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
10368
10438
  var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
10369
10439
  var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
@@ -10463,7 +10533,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
10463
10533
  const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
10464
10534
  const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
10465
10535
  const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
10466
- const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg ? void 0 : (audioPath, signal) => defaultProbeAudioTruePeakDbfs(audioPath, timeoutMs, signal));
10536
+ const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
10467
10537
  const [videoResult, audioResult] = await Promise.allSettled([
10468
10538
  probeVideo(input.videoPath),
10469
10539
  probeAudio(input.audioPath, input.signal)
@@ -10699,28 +10769,12 @@ async function defaultProbeAudioInfo(audioPath, signal) {
10699
10769
  audioCodec: metadata.audioCodec
10700
10770
  };
10701
10771
  }
10702
- async function defaultProbeAudioTruePeakDbfs(audioPath, timeoutMs, signal) {
10703
- const result = await runFfmpeg4(
10704
- ["-i", audioPath, "-map", "0:a:0", "-vn", "-af", "ebur128=peak=true", "-f", "null", "-"],
10705
- { signal, timeout: timeoutMs }
10706
- );
10707
- if (!result.success) {
10708
- throw new Error(`[audioPadTrim] ${describeFfmpegFailure(result, timeoutMs)}`);
10709
- }
10710
- const matches = [...result.stderr.matchAll(/^\s*Peak:\s*([+-]?(?:[\d.]+|inf)) dBFS$/gim)];
10711
- const token = matches.at(-1)?.[1]?.toLowerCase();
10712
- const value = token === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
10713
- if (Number.isNaN(value) || value === Number.POSITIVE_INFINITY) {
10714
- throw new Error("[audioPadTrim] FFmpeg did not report AAC true peak");
10715
- }
10716
- return value;
10717
- }
10718
10772
  async function defaultRunFfmpeg(args, timeoutMs, signal) {
10719
- const result = await runFfmpeg4(args, { signal, timeout: timeoutMs });
10773
+ const result = await runFfmpeg5(args, { signal, timeout: timeoutMs });
10720
10774
  if (result.success) return { success: true };
10721
10775
  return {
10722
10776
  success: false,
10723
- error: `[audioPadTrim] ${describeFfmpegFailure(result, timeoutMs)}`,
10777
+ error: `[audioPadTrim] ${describeFfmpegFailure2(result, timeoutMs)}`,
10724
10778
  failureReason: result.failureReason
10725
10779
  };
10726
10780
  }
@@ -13853,7 +13907,7 @@ var DISTRIBUTED_RENDER_CAPABILITIES = Object.freeze({
13853
13907
  var FIELD_DELIMITER = Buffer.from([0]);
13854
13908
 
13855
13909
  // src/services/distributed/chunkAudio.ts
13856
- import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure2, runFfmpeg as runFfmpeg5 } from "@hyperframes/engine";
13910
+ import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg6 } from "@hyperframes/engine";
13857
13911
 
13858
13912
  // src/services/distributed/mp4Stitch.ts
13859
13913
  var SCAN_BYTES = 64 * 1024;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The true peak one `ebur128=peak=true` pass over the whole file reports, measured in parallel
3
+ * segments when the length is known and the rate is 48 kHz: the same digit, not an estimate.
4
+ */
5
+ export declare function measureTruePeakDbfs(audioPath: string, options: {
6
+ timeoutMs: number;
7
+ signal?: AbortSignal;
8
+ segments?: {
9
+ durationSeconds: number;
10
+ sampleRate: number;
11
+ };
12
+ }): Promise<number>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperframes/producer",
3
- "version": "0.8.180",
3
+ "version": "0.8.181",
4
4
  "description": "HTML-to-video rendering engine using Chrome's BeginFrame API",
5
5
  "repository": {
6
6
  "type": "git",
@@ -35,11 +35,11 @@
35
35
  "postcss": "^8.4.0",
36
36
  "puppeteer": "^25.2.1",
37
37
  "puppeteer-core": "^25.2.1",
38
- "@hyperframes/engine": "0.8.180",
39
- "@hyperframes/lint": "0.8.180",
40
- "@hyperframes/parsers": "0.8.180",
41
- "@hyperframes/core": "0.8.180",
42
- "@hyperframes/studio-server": "0.8.180"
38
+ "@hyperframes/core": "0.8.181",
39
+ "@hyperframes/lint": "0.8.181",
40
+ "@hyperframes/engine": "0.8.181",
41
+ "@hyperframes/parsers": "0.8.181",
42
+ "@hyperframes/studio-server": "0.8.181"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^25.0.10",
@@ -48,7 +48,7 @@
48
48
  "tsx": "^4.21.0",
49
49
  "typescript": "^5.7.2",
50
50
  "vitest": "^4.1.11",
51
- "@hyperframes/aws-lambda": "0.8.180"
51
+ "@hyperframes/aws-lambda": "0.8.181"
52
52
  },
53
53
  "engines": {
54
54
  "node": ">=22"