@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.
- package/dist/distributed.js +87 -29
- package/dist/index.js +87 -29
- package/dist/public-server.js +76 -22
- package/dist/services/render/audioTruePeak.d.ts +12 -0
- package/package.json +7 -7
package/dist/distributed.js
CHANGED
|
@@ -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
|
|
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) =>
|
|
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) =>
|
|
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
|
|
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] ${
|
|
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
|
|
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
|
|
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: ${
|
|
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
|
|
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
|
|
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
|
|
9973
|
+
const result = await runFfmpeg7(args, { signal, timeout });
|
|
9916
9974
|
if (!result.success) {
|
|
9917
9975
|
throw encoderFailureError(`[assemble] ffmpeg ${step} failed`, {
|
|
9918
|
-
error:
|
|
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
|
|
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) =>
|
|
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) =>
|
|
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
|
|
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] ${
|
|
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
|
|
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
|
|
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: ${
|
|
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
|
|
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
|
|
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
|
|
18508
|
+
const result = await runFfmpeg7(args, { signal, timeout });
|
|
18451
18509
|
if (!result.success) {
|
|
18452
18510
|
throw encoderFailureError(`[assemble] ffmpeg ${step} failed`, {
|
|
18453
|
-
error:
|
|
18511
|
+
error: describeFfmpegFailure4(result, timeout),
|
|
18454
18512
|
failureReason: result.failureReason
|
|
18455
18513
|
});
|
|
18456
18514
|
}
|
package/dist/public-server.js
CHANGED
|
@@ -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
|
|
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) =>
|
|
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
|
|
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] ${
|
|
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
|
|
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.
|
|
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/
|
|
39
|
-
"@hyperframes/lint": "0.8.
|
|
40
|
-
"@hyperframes/
|
|
41
|
-
"@hyperframes/
|
|
42
|
-
"@hyperframes/studio-server": "0.8.
|
|
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.
|
|
51
|
+
"@hyperframes/aws-lambda": "0.8.181"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=22"
|