@hyperframes/producer 0.8.181 → 0.8.183
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.d.ts +2 -2
- package/dist/distributed.js +411 -220
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +17 -17
- package/dist/index.js +433 -238
- package/dist/public-server.js +155 -105
- package/dist/services/distributed/assemble.d.ts +2 -1
- package/dist/services/distributed/chunkFrames.d.ts +6 -1
- package/dist/services/distributed/mp4Stitch.d.ts +4 -4
- package/dist/services/distributed/plan.d.ts +11 -0
- package/dist/services/distributed/planV2.d.ts +1 -0
- package/dist/services/distributed/projectHash.d.ts +1 -4
- package/dist/services/render/artifactTransaction.d.ts +7 -0
- package/dist/services/render/audioPadTrim.d.ts +20 -15
- package/dist/services/render/audioTruePeak.d.ts +1 -5
- package/dist/services/render/perfSummary.d.ts +2 -0
- package/dist/services/render/stages/audioStage.d.ts +2 -0
- package/dist/services/render/stages/captureStage.d.ts +9 -3
- package/dist/services/render/stages/freezePlan.d.ts +1 -0
- package/dist/services/renderOrchestrator.d.ts +2 -2
- package/package.json +7 -7
package/dist/distributed.js
CHANGED
|
@@ -89,6 +89,8 @@ import {
|
|
|
89
89
|
extractMediaMetadata as extractMediaMetadata4,
|
|
90
90
|
resolveConfig as resolveConfig2,
|
|
91
91
|
closeCaptureSession as closeCaptureSession8,
|
|
92
|
+
createCaptureSession as createCaptureSession8,
|
|
93
|
+
initializeSession as initializeSession8,
|
|
92
94
|
createVideoFrameInjector,
|
|
93
95
|
getEncoderPreset as getEncoderPreset3,
|
|
94
96
|
distributeFrames as distributeFrames2,
|
|
@@ -4955,6 +4957,7 @@ async function runAudioStage(input) {
|
|
|
4955
4957
|
audios,
|
|
4956
4958
|
masterFxChain,
|
|
4957
4959
|
urlByAbsentLocalPath,
|
|
4960
|
+
keepEncoderInputAt,
|
|
4958
4961
|
abortSignal,
|
|
4959
4962
|
assertNotAborted,
|
|
4960
4963
|
log
|
|
@@ -4974,7 +4977,13 @@ async function runAudioStage(input) {
|
|
|
4974
4977
|
audioOutputPath,
|
|
4975
4978
|
duration,
|
|
4976
4979
|
abortSignal,
|
|
4977
|
-
{
|
|
4980
|
+
{
|
|
4981
|
+
ffmpegProcessTimeout,
|
|
4982
|
+
audioGain,
|
|
4983
|
+
masterFxChain,
|
|
4984
|
+
urlByAbsentLocalPath,
|
|
4985
|
+
keepEncoderInputAt
|
|
4986
|
+
},
|
|
4978
4987
|
compiledDir
|
|
4979
4988
|
);
|
|
4980
4989
|
} catch (err) {
|
|
@@ -5088,13 +5097,29 @@ function inspectDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, free
|
|
|
5088
5097
|
}
|
|
5089
5098
|
return { available: false, estimatedBytes, freeBytes };
|
|
5090
5099
|
}
|
|
5091
|
-
function
|
|
5100
|
+
function diskCaptureRemedy(cfg, outputFormat, durationSeconds, workerCount) {
|
|
5101
|
+
const oneWorker = explainStreamingEncodeGate(cfg, outputFormat, 1, durationSeconds);
|
|
5102
|
+
if (workerCount > 1 && oneWorker.enabled) {
|
|
5103
|
+
return `This render stores frames because it captures with ${workerCount} workers. Free up disk space, or re-run this render with --workers 1 to stream frames into the encoder instead. One worker is slower, so keep the default worker count for renders that fit on disk.`;
|
|
5104
|
+
}
|
|
5105
|
+
switch (oneWorker.reason) {
|
|
5106
|
+
case "format_excluded":
|
|
5107
|
+
return `${outputFormat} output stores every frame at any worker count. Free up disk space or render to mp4.`;
|
|
5108
|
+
case "disabled_by_config":
|
|
5109
|
+
return "PRODUCER_ENABLE_STREAMING_ENCODE=false sends every render to disk. Free up disk space or unset it.";
|
|
5110
|
+
case "duration_cap":
|
|
5111
|
+
return "The streaming duration cap (PRODUCER_STREAMING_ENCODE_DURATION_CAP_ENABLED=true) sends renders longer than PRODUCER_STREAMING_ENCODE_MAX_DURATION_SECONDS to disk at any worker count. Free up disk space, unset the cap, or use --low-memory-mode, which streams past it on one worker.";
|
|
5112
|
+
default:
|
|
5113
|
+
return "Free up disk space.";
|
|
5114
|
+
}
|
|
5115
|
+
}
|
|
5116
|
+
function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remedy) {
|
|
5092
5117
|
const basisNote = basis === "measured" ? ` (measured from the first ${DISK_PROJECTION_SAMPLE_FRAMES} frames)` : "";
|
|
5093
5118
|
return new Error(
|
|
5094
|
-
`Disk capture may need ~${(needBytes / 1e6).toFixed(1)} MB of temporary frame storage${basisNote}, but only ${(freeBytes / 1e6).toFixed(1)} MB is free at ${framesDir}. Disk capture stores every frame as an image file (JPEG, or PNG for alpha).
|
|
5119
|
+
`Disk capture may need ~${(needBytes / 1e6).toFixed(1)} MB of temporary frame storage${basisNote}, but only ${(freeBytes / 1e6).toFixed(1)} MB is free at ${framesDir}. Disk capture stores every frame as an image file (JPEG, or PNG for alpha). ${remedy}`
|
|
5095
5120
|
);
|
|
5096
5121
|
}
|
|
5097
|
-
function assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, freeDiskBytes) {
|
|
5122
|
+
function assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, remedy, freeDiskBytes) {
|
|
5098
5123
|
const headroom = inspectDiskCaptureHeadroom(
|
|
5099
5124
|
framesDir,
|
|
5100
5125
|
totalFrames,
|
|
@@ -5106,7 +5131,8 @@ function assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, freeD
|
|
|
5106
5131
|
headroom.estimatedBytes,
|
|
5107
5132
|
headroom.freeBytes,
|
|
5108
5133
|
framesDir,
|
|
5109
|
-
"estimate"
|
|
5134
|
+
"estimate",
|
|
5135
|
+
remedy
|
|
5110
5136
|
);
|
|
5111
5137
|
}
|
|
5112
5138
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
@@ -5126,7 +5152,7 @@ function measureCaptureFrameBytes(workDir, framesDir) {
|
|
|
5126
5152
|
return total;
|
|
5127
5153
|
}
|
|
5128
5154
|
function createDiskCaptureProjection(input) {
|
|
5129
|
-
const { framesDir, totalFrames, measureBytes, freeDiskBytes = freeDiskBytesAt } = input;
|
|
5155
|
+
const { framesDir, totalFrames, measureBytes, remedy, freeDiskBytes = freeDiskBytesAt } = input;
|
|
5130
5156
|
let checked = false;
|
|
5131
5157
|
return (capturedFrames) => {
|
|
5132
5158
|
if (checked || capturedFrames < DISK_PROJECTION_SAMPLE_FRAMES) return;
|
|
@@ -5138,7 +5164,7 @@ function createDiskCaptureProjection(input) {
|
|
|
5138
5164
|
if (remaining <= 0 || freeBytes === null) return;
|
|
5139
5165
|
const needBytes = Math.ceil(measured / capturedFrames * remaining);
|
|
5140
5166
|
if (needBytes > freeBytes * 0.9) {
|
|
5141
|
-
throw diskCaptureShortfallError(needBytes, freeBytes, framesDir, "measured");
|
|
5167
|
+
throw diskCaptureShortfallError(needBytes, freeBytes, framesDir, "measured", remedy);
|
|
5142
5168
|
}
|
|
5143
5169
|
};
|
|
5144
5170
|
}
|
|
@@ -5180,11 +5206,13 @@ async function runCaptureStage(input) {
|
|
|
5180
5206
|
}
|
|
5181
5207
|
}
|
|
5182
5208
|
const captureOptions = buildCaptureOptions();
|
|
5183
|
-
|
|
5209
|
+
const remedy = diskCaptureRemedy(cfg, job.config.format ?? "mp4", job.duration ?? 0, workerCount);
|
|
5210
|
+
assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, remedy);
|
|
5184
5211
|
const checkDiskProjection = createDiskCaptureProjection({
|
|
5185
5212
|
framesDir,
|
|
5186
5213
|
totalFrames,
|
|
5187
|
-
measureBytes: () => measureCaptureFrameBytes(workDir, framesDir)
|
|
5214
|
+
measureBytes: () => measureCaptureFrameBytes(workDir, framesDir),
|
|
5215
|
+
remedy
|
|
5188
5216
|
});
|
|
5189
5217
|
if (workerCount > 1) {
|
|
5190
5218
|
const attempts = await executeDiskCaptureWithAdaptiveRetry({
|
|
@@ -5459,8 +5487,10 @@ function fpsToFfmpegArg2(fps) {
|
|
|
5459
5487
|
}
|
|
5460
5488
|
var KEEP_FILTER_GRAPH_WHEN_FRAMES_DROP_ALPHA = ["-reinit_filter", "0"];
|
|
5461
5489
|
var WRITE_EVERY_FRAME_WHOLE = ["-gifflags", "0"];
|
|
5490
|
+
var FAIL_ON_UNREADABLE_FRAME_INSTEAD_OF_ENDING_THERE = ["-xerror"];
|
|
5462
5491
|
function framesInput(input, fpsArg) {
|
|
5463
5492
|
return [
|
|
5493
|
+
...FAIL_ON_UNREADABLE_FRAME_INSTEAD_OF_ENDING_THERE,
|
|
5464
5494
|
"-framerate",
|
|
5465
5495
|
fpsArg,
|
|
5466
5496
|
...KEEP_FILTER_GRAPH_WHEN_FRAMES_DROP_ALPHA,
|
|
@@ -5710,7 +5740,8 @@ import { dirname as dirname6, join as join13 } from "node:path";
|
|
|
5710
5740
|
import {
|
|
5711
5741
|
DEFAULT_CONFIG,
|
|
5712
5742
|
describeFfmpegFailure as describeFfmpegFailure2,
|
|
5713
|
-
|
|
5743
|
+
encodeAac,
|
|
5744
|
+
extractAudioMetadata as extractAudioMetadata3,
|
|
5714
5745
|
getFfprobeBinary,
|
|
5715
5746
|
ManagedChildProcess,
|
|
5716
5747
|
runFfmpeg as runFfmpeg5,
|
|
@@ -5719,7 +5750,12 @@ import {
|
|
|
5719
5750
|
import { redactKnownPaths, redactTelemetryString as redactTelemetryString3 } from "@hyperframes/core";
|
|
5720
5751
|
|
|
5721
5752
|
// src/services/render/audioTruePeak.ts
|
|
5722
|
-
import {
|
|
5753
|
+
import {
|
|
5754
|
+
aacSegmentCount,
|
|
5755
|
+
describeFfmpegFailure,
|
|
5756
|
+
extractAudioMetadata as extractAudioMetadata2,
|
|
5757
|
+
runFfmpeg as runFfmpeg4
|
|
5758
|
+
} from "@hyperframes/engine";
|
|
5723
5759
|
var toDbfs = (token) => token.toLowerCase() === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
|
|
5724
5760
|
function checkedDbfs(value) {
|
|
5725
5761
|
if (Number.isNaN(value) || value === Number.POSITIVE_INFINITY) {
|
|
@@ -5757,12 +5793,13 @@ async function scanTruePeak(audioPath, trim, timeoutMs, signal) {
|
|
|
5757
5793
|
};
|
|
5758
5794
|
}
|
|
5759
5795
|
async function measureTruePeakDbfs(audioPath, options) {
|
|
5760
|
-
const { timeoutMs, signal
|
|
5796
|
+
const { timeoutMs, signal } = options;
|
|
5761
5797
|
const wholeFile = async () => (await scanTruePeak(audioPath, "", timeoutMs, signal)).peak;
|
|
5798
|
+
const { durationSeconds, sampleRate } = await extractAudioMetadata2(audioPath, { signal });
|
|
5762
5799
|
const block = 4800;
|
|
5763
|
-
const count =
|
|
5764
|
-
if (
|
|
5765
|
-
const cut = (k) => k * Math.floor(
|
|
5800
|
+
const count = sampleRate === 48e3 && Number.isFinite(durationSeconds) ? aacSegmentCount(durationSeconds) : 1;
|
|
5801
|
+
if (count === 1) return wholeFile();
|
|
5802
|
+
const cut = (k) => k * Math.floor(durationSeconds * 10 / count) * block;
|
|
5766
5803
|
const peers = new AbortController();
|
|
5767
5804
|
const segmentSignal = signal ? AbortSignal.any([signal, peers.signal]) : peers.signal;
|
|
5768
5805
|
const scans = await Promise.all(
|
|
@@ -5791,24 +5828,8 @@ var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
|
|
|
5791
5828
|
var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
|
|
5792
5829
|
var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
|
|
5793
5830
|
var MAX_TRUE_PEAK_CORRECTION_PASSES = 3;
|
|
5794
|
-
function
|
|
5795
|
-
return
|
|
5796
|
-
"-i",
|
|
5797
|
-
inputPath,
|
|
5798
|
-
"-map",
|
|
5799
|
-
"0:a:0",
|
|
5800
|
-
"-vn",
|
|
5801
|
-
"-af",
|
|
5802
|
-
`volume=${attenuationDb.toFixed(3)}dB`,
|
|
5803
|
-
"-t",
|
|
5804
|
-
formatSeconds(targetDurationSeconds),
|
|
5805
|
-
"-c:a",
|
|
5806
|
-
"aac",
|
|
5807
|
-
"-b:a",
|
|
5808
|
-
"192k",
|
|
5809
|
-
"-y",
|
|
5810
|
-
outputPath
|
|
5811
|
-
];
|
|
5831
|
+
function aacTruePeakCorrectionFilter(targetDurationSeconds, attenuationDb) {
|
|
5832
|
+
return `volume=${attenuationDb.toFixed(3)}dB,apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
|
|
5812
5833
|
}
|
|
5813
5834
|
function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
|
|
5814
5835
|
const delta = targetDurationSeconds - sourceDurationSeconds;
|
|
@@ -5823,53 +5844,13 @@ function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, tar
|
|
|
5823
5844
|
if (delta > 0) {
|
|
5824
5845
|
return {
|
|
5825
5846
|
operation: "pad",
|
|
5826
|
-
steps: [
|
|
5827
|
-
{
|
|
5828
|
-
kind: "normalize",
|
|
5829
|
-
args: [
|
|
5830
|
-
"-i",
|
|
5831
|
-
audioPath,
|
|
5832
|
-
"-af",
|
|
5833
|
-
`apad,atrim=0:${targetSec}`,
|
|
5834
|
-
"-t",
|
|
5835
|
-
targetSec,
|
|
5836
|
-
"-c:a",
|
|
5837
|
-
"aac",
|
|
5838
|
-
"-b:a",
|
|
5839
|
-
"192k",
|
|
5840
|
-
"-y",
|
|
5841
|
-
outputPath
|
|
5842
|
-
]
|
|
5843
|
-
}
|
|
5844
|
-
],
|
|
5847
|
+
steps: [{ kind: "normalize", filter: `apad,atrim=0:${targetSec}` }],
|
|
5845
5848
|
cleanupPaths: []
|
|
5846
5849
|
};
|
|
5847
5850
|
}
|
|
5848
5851
|
return {
|
|
5849
5852
|
operation: "trim",
|
|
5850
|
-
steps: [
|
|
5851
|
-
{
|
|
5852
|
-
kind: "trim",
|
|
5853
|
-
args: [
|
|
5854
|
-
"-i",
|
|
5855
|
-
audioPath,
|
|
5856
|
-
"-af",
|
|
5857
|
-
`atrim=duration=${targetSec},asetpts=PTS-STARTPTS`,
|
|
5858
|
-
// `atrim` limits decoded samples but does not cap muxer timestamps
|
|
5859
|
-
// introduced by encoder delay/flush. The output duration contract
|
|
5860
|
-
// is enforced at the container boundary as well; without `-t`, a
|
|
5861
|
-
// long primed source can retain its original tail after re-encode.
|
|
5862
|
-
"-t",
|
|
5863
|
-
targetSec,
|
|
5864
|
-
"-c:a",
|
|
5865
|
-
"aac",
|
|
5866
|
-
"-b:a",
|
|
5867
|
-
"192k",
|
|
5868
|
-
"-y",
|
|
5869
|
-
outputPath
|
|
5870
|
-
]
|
|
5871
|
-
}
|
|
5872
|
-
],
|
|
5853
|
+
steps: [{ kind: "trim", filter: `atrim=duration=${targetSec},asetpts=PTS-STARTPTS` }],
|
|
5873
5854
|
cleanupPaths: []
|
|
5874
5855
|
};
|
|
5875
5856
|
}
|
|
@@ -5886,7 +5867,8 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
5886
5867
|
const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
|
|
5887
5868
|
const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
|
|
5888
5869
|
const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
|
|
5889
|
-
const
|
|
5870
|
+
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal);
|
|
5871
|
+
const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg || input.encodeAac ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
|
|
5890
5872
|
const [videoResult, audioResult] = await Promise.allSettled([
|
|
5891
5873
|
probeVideo(input.videoPath),
|
|
5892
5874
|
probeAudio(input.audioPath, input.signal)
|
|
@@ -5933,7 +5915,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
5933
5915
|
);
|
|
5934
5916
|
try {
|
|
5935
5917
|
for (const step of plan2.steps) {
|
|
5936
|
-
const ffmpegResult = await runner(step.args);
|
|
5918
|
+
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(input.audioPath, input.outputPath, targetDurationSeconds, step.filter);
|
|
5937
5919
|
if (!ffmpegResult.success) {
|
|
5938
5920
|
return {
|
|
5939
5921
|
success: false,
|
|
@@ -5965,7 +5947,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
5965
5947
|
targetDurationSeconds,
|
|
5966
5948
|
signal: input.signal,
|
|
5967
5949
|
probeTruePeak,
|
|
5968
|
-
|
|
5950
|
+
encode
|
|
5969
5951
|
});
|
|
5970
5952
|
if (!correction.success) {
|
|
5971
5953
|
return {
|
|
@@ -5995,12 +5977,8 @@ async function constrainAacTruePeak(input) {
|
|
|
5995
5977
|
audioPath: input.audioPath,
|
|
5996
5978
|
targetDurationSeconds: input.targetDurationSeconds,
|
|
5997
5979
|
signal: input.signal,
|
|
5998
|
-
probeTruePeak:
|
|
5999
|
-
|
|
6000
|
-
const segments = { durationSeconds: input.targetDurationSeconds, sampleRate };
|
|
6001
|
-
return measureTruePeakDbfs(audioPath, { timeoutMs, signal, segments });
|
|
6002
|
-
},
|
|
6003
|
-
runner: (args) => defaultRunFfmpeg(args, timeoutMs, input.signal)
|
|
5980
|
+
probeTruePeak: (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }),
|
|
5981
|
+
encode: defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
|
|
6004
5982
|
});
|
|
6005
5983
|
}
|
|
6006
5984
|
async function enforceAacTruePeak(input) {
|
|
@@ -6027,13 +6005,11 @@ async function enforceAacTruePeak(input) {
|
|
|
6027
6005
|
}
|
|
6028
6006
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
6029
6007
|
attenuationDb += correctionTargetDbfs - truePeakDbfs;
|
|
6030
|
-
const result = await input.
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
attenuationDb
|
|
6036
|
-
)
|
|
6008
|
+
const result = await input.encode(
|
|
6009
|
+
input.audioPath,
|
|
6010
|
+
correctedPath,
|
|
6011
|
+
input.targetDurationSeconds,
|
|
6012
|
+
aacTruePeakCorrectionFilter(input.targetDurationSeconds, attenuationDb)
|
|
6037
6013
|
);
|
|
6038
6014
|
if (!result.success) {
|
|
6039
6015
|
return {
|
|
@@ -6128,7 +6104,7 @@ function parseFrameRate(rate) {
|
|
|
6128
6104
|
return { fpsNum, fpsDen };
|
|
6129
6105
|
}
|
|
6130
6106
|
async function defaultProbeAudioInfo(audioPath, signal) {
|
|
6131
|
-
const metadata = await
|
|
6107
|
+
const metadata = await extractAudioMetadata3(audioPath, { signal });
|
|
6132
6108
|
return {
|
|
6133
6109
|
durationSeconds: metadata.durationSeconds,
|
|
6134
6110
|
sampleRate: metadata.sampleRate,
|
|
@@ -6137,7 +6113,18 @@ async function defaultProbeAudioInfo(audioPath, signal) {
|
|
|
6137
6113
|
};
|
|
6138
6114
|
}
|
|
6139
6115
|
async function defaultRunFfmpeg(args, timeoutMs, signal) {
|
|
6140
|
-
|
|
6116
|
+
return toStepResult(await runFfmpeg5(args, { signal, timeout: timeoutMs }), timeoutMs);
|
|
6117
|
+
}
|
|
6118
|
+
function defaultAacEncoder(timeoutMs, signal, pcmSourcePath) {
|
|
6119
|
+
return async (inputPath, outputPath, durationSeconds, filter) => {
|
|
6120
|
+
const options = pcmSourcePath ? { signal, timeout: timeoutMs, filter } : { signal, timeout: timeoutMs, filter, segments: 1 };
|
|
6121
|
+
return toStepResult(
|
|
6122
|
+
await encodeAac(pcmSourcePath ?? inputPath, outputPath, durationSeconds, options),
|
|
6123
|
+
timeoutMs
|
|
6124
|
+
);
|
|
6125
|
+
};
|
|
6126
|
+
}
|
|
6127
|
+
function toStepResult(result, timeoutMs) {
|
|
6141
6128
|
if (result.success) return { success: true };
|
|
6142
6129
|
return {
|
|
6143
6130
|
success: false,
|
|
@@ -6513,15 +6500,33 @@ function createRenderJob(config) {
|
|
|
6513
6500
|
warnings: []
|
|
6514
6501
|
};
|
|
6515
6502
|
}
|
|
6503
|
+
function explainStreamingEncodeGate(cfg, outputFormat, workerCount, durationSeconds, forceParallelStream = false) {
|
|
6504
|
+
if (!cfg.enableStreamingEncode) return { enabled: false, reason: "disabled_by_config" };
|
|
6505
|
+
if (outputFormat === "png-sequence" || outputFormat === "gif") {
|
|
6506
|
+
return { enabled: false, reason: "format_excluded" };
|
|
6507
|
+
}
|
|
6508
|
+
if (!Number.isFinite(durationSeconds) || durationSeconds <= 0) {
|
|
6509
|
+
return { enabled: false, reason: "invalid_duration" };
|
|
6510
|
+
}
|
|
6511
|
+
const overCap = cfg.streamingEncodeDurationCapEnabled === true && durationSeconds > cfg.streamingEncodeMaxDurationSeconds;
|
|
6512
|
+
if (overCap && !cfg.lowMemoryMode) return { enabled: false, reason: "duration_cap" };
|
|
6513
|
+
if (forceParallelStream || process.env.HF_DE_PARALLEL_STREAM === "true") {
|
|
6514
|
+
return { enabled: true, reason: "parallel_forced" };
|
|
6515
|
+
}
|
|
6516
|
+
if (workerCount === 1) {
|
|
6517
|
+
return { enabled: true, reason: overCap ? "low_memory_mode" : "single_worker" };
|
|
6518
|
+
}
|
|
6519
|
+
return { enabled: false, reason: "multi_worker" };
|
|
6520
|
+
}
|
|
6516
6521
|
var DEFAULT_SEGMENTED_MIN_SECONDS = Number.POSITIVE_INFINITY;
|
|
6517
6522
|
|
|
6518
6523
|
// src/services/distributed/planFrameCost.ts
|
|
6519
6524
|
import { join as join17 } from "node:path";
|
|
6520
6525
|
import {
|
|
6521
6526
|
closeCaptureSession as closeCaptureSession9,
|
|
6522
|
-
createCaptureSession as
|
|
6527
|
+
createCaptureSession as createCaptureSession9,
|
|
6523
6528
|
createFrameLookupTable as createFrameLookupTable3,
|
|
6524
|
-
initializeSession as
|
|
6529
|
+
initializeSession as initializeSession9
|
|
6525
6530
|
} from "@hyperframes/engine";
|
|
6526
6531
|
|
|
6527
6532
|
// src/services/distributed/chunkFrames.ts
|
|
@@ -6919,6 +6924,29 @@ function buildChunkCaptureOptions(input) {
|
|
|
6919
6924
|
requiresWebGpu: compositionRequiresWebGpu3(input.html)
|
|
6920
6925
|
};
|
|
6921
6926
|
}
|
|
6927
|
+
function shownDeferredFrameIndexes(planVideos, frames, fps) {
|
|
6928
|
+
const shown = /* @__PURE__ */ new Map();
|
|
6929
|
+
const deferred = planVideos.extracted.filter((video) => video.deferredRange);
|
|
6930
|
+
if (deferred.length === 0) return shown;
|
|
6931
|
+
const table = createFrameLookupTable2(
|
|
6932
|
+
planVideos.videos,
|
|
6933
|
+
deferred.map((video) => ({
|
|
6934
|
+
...video,
|
|
6935
|
+
outputDir: "",
|
|
6936
|
+
framePaths: new Map(Array.from({ length: video.totalFrames }, (_, i) => [i, String(i)]))
|
|
6937
|
+
})),
|
|
6938
|
+
resolveRenderFpsConfig(fps).value
|
|
6939
|
+
);
|
|
6940
|
+
for (const frame of frames) {
|
|
6941
|
+
const globalTime = frame * fps.den / fps.num;
|
|
6942
|
+
for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
|
|
6943
|
+
const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
|
|
6944
|
+
indexes.add(payload.frameIndex);
|
|
6945
|
+
shown.set(videoId, indexes);
|
|
6946
|
+
}
|
|
6947
|
+
}
|
|
6948
|
+
return shown;
|
|
6949
|
+
}
|
|
6922
6950
|
async function extractDeferredVideoFramesForChunk(input) {
|
|
6923
6951
|
const deferred = input.planVideos.extracted.filter((video) => video.deferredRange);
|
|
6924
6952
|
const remoteSources = input.remoteSources ?? {};
|
|
@@ -6935,24 +6963,7 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
6935
6963
|
});
|
|
6936
6964
|
}
|
|
6937
6965
|
if (deferred.length === 0) return [];
|
|
6938
|
-
const
|
|
6939
|
-
input.planVideos.videos,
|
|
6940
|
-
deferred.map((video) => ({
|
|
6941
|
-
...video,
|
|
6942
|
-
outputDir: input.outputRoot,
|
|
6943
|
-
framePaths: new Map(Array.from({ length: video.totalFrames }, (_, i) => [i, String(i)]))
|
|
6944
|
-
})),
|
|
6945
|
-
resolveRenderFpsConfig(input.fps).value
|
|
6946
|
-
);
|
|
6947
|
-
const shown = /* @__PURE__ */ new Map();
|
|
6948
|
-
for (const frame of input.frames) {
|
|
6949
|
-
const globalTime = frame * input.fps.den / input.fps.num;
|
|
6950
|
-
for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
|
|
6951
|
-
const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
|
|
6952
|
-
indexes.add(payload.frameIndex);
|
|
6953
|
-
shown.set(videoId, indexes);
|
|
6954
|
-
}
|
|
6955
|
-
}
|
|
6966
|
+
const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
|
|
6956
6967
|
return Promise.all(
|
|
6957
6968
|
deferred.map(async (video) => {
|
|
6958
6969
|
const outputDir = join16(input.outputRoot, video.videoId);
|
|
@@ -6978,7 +6989,8 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
6978
6989
|
fps: input.fps,
|
|
6979
6990
|
outputDir: input.outputRoot,
|
|
6980
6991
|
format: range.format,
|
|
6981
|
-
frameRanges: contiguousRuns(indexes)
|
|
6992
|
+
frameRanges: contiguousRuns(indexes),
|
|
6993
|
+
shownBox: input.shownBoxes?.get(video.videoId)
|
|
6982
6994
|
},
|
|
6983
6995
|
void 0,
|
|
6984
6996
|
input.cfg
|
|
@@ -7025,7 +7037,7 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
7025
7037
|
});
|
|
7026
7038
|
let session = null;
|
|
7027
7039
|
try {
|
|
7028
|
-
session = await
|
|
7040
|
+
session = await createCaptureSession9(
|
|
7029
7041
|
fileServer.url,
|
|
7030
7042
|
join17(workDir, "frame-cost"),
|
|
7031
7043
|
buildChunkCaptureOptions({
|
|
@@ -7037,7 +7049,7 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
7037
7049
|
createChunkVideoFrameInjectorFactory(lookup)(),
|
|
7038
7050
|
createCaptureCalibrationConfig(input.cfg)
|
|
7039
7051
|
);
|
|
7040
|
-
await
|
|
7052
|
+
await initializeSession9(session);
|
|
7041
7053
|
const { estimate } = await measureCaptureCostFromSession(session, input.totalFrames, fps, log);
|
|
7042
7054
|
return (estimate.p95Ms ?? 0) / 1e3;
|
|
7043
7055
|
} finally {
|
|
@@ -7631,6 +7643,20 @@ function withDuration(buf, target, duration) {
|
|
|
7631
7643
|
function timescaleOf(buf, header) {
|
|
7632
7644
|
return buf.readUInt32BE(header.bodyStart + (buf[header.bodyStart] === 1 ? 20 : 12));
|
|
7633
7645
|
}
|
|
7646
|
+
var TRACK_ID_AT = { tkhd: [12, 20], mvhd: [96, 108] };
|
|
7647
|
+
function idOf(buf, target) {
|
|
7648
|
+
return buf.readUInt32BE(
|
|
7649
|
+
target.bodyStart + TRACK_ID_AT[target.type][buf[target.bodyStart] === 1 ? 1 : 0]
|
|
7650
|
+
);
|
|
7651
|
+
}
|
|
7652
|
+
function withId(boxBytes, type, id) {
|
|
7653
|
+
const header = boxAt(boxBytes, 0, 0, boxBytes.length);
|
|
7654
|
+
boxBytes.writeUInt32BE(
|
|
7655
|
+
id,
|
|
7656
|
+
header.bodyStart + TRACK_ID_AT[type][boxBytes[header.bodyStart] === 1 ? 1 : 0]
|
|
7657
|
+
);
|
|
7658
|
+
return boxBytes;
|
|
7659
|
+
}
|
|
7634
7660
|
function buildStbl(stsd, track) {
|
|
7635
7661
|
const stts = runs(track.durations);
|
|
7636
7662
|
const stsc = [];
|
|
@@ -7672,24 +7698,48 @@ function planTracksOf(tracks, plan2, index) {
|
|
|
7672
7698
|
}
|
|
7673
7699
|
return matched;
|
|
7674
7700
|
}
|
|
7675
|
-
function
|
|
7676
|
-
const
|
|
7677
|
-
const
|
|
7701
|
+
function sourceTracks(layouts, audio) {
|
|
7702
|
+
const chunkPlan = readTracks(layouts[0].moov);
|
|
7703
|
+
const audioTracks = audio ? readTracks(audio.moov) : [];
|
|
7704
|
+
if (audio && (audioTracks.length !== 1 || audioTracks[0].handler !== "soun")) {
|
|
7705
|
+
throw new Error("[mp4Stitch] the separate audio file must hold one audio track");
|
|
7706
|
+
}
|
|
7707
|
+
const plan2 = [...chunkPlan, ...audioTracks];
|
|
7708
|
+
const chunks = layouts.map((layout, i) => {
|
|
7709
|
+
const tracks = readTracks(layout.moov);
|
|
7710
|
+
if (audio && tracks.some((track) => track.handler === "soun")) {
|
|
7711
|
+
throw new Error(`[mp4Stitch] chunk ${i} carries its own audio`);
|
|
7712
|
+
}
|
|
7713
|
+
return planTracksOf(tracks, plan2, i);
|
|
7714
|
+
});
|
|
7715
|
+
return {
|
|
7716
|
+
plan: plan2,
|
|
7717
|
+
chunkTrackCount: chunkPlan.length,
|
|
7718
|
+
sources: audio ? [[...chunkPlan.map(() => null), audioTracks[0]], ...chunks] : chunks
|
|
7719
|
+
};
|
|
7720
|
+
}
|
|
7721
|
+
function buildStitchedHeader(layouts, audio) {
|
|
7722
|
+
const { plan: plan2, chunkTrackCount, sources } = sourceTracks(layouts, audio);
|
|
7723
|
+
const sourceLayouts = audio ? [audio, ...layouts] : layouts;
|
|
7678
7724
|
const payloadBases = [];
|
|
7679
7725
|
let payloadBytes = 0;
|
|
7680
|
-
for (const layout of
|
|
7726
|
+
for (const layout of sourceLayouts) {
|
|
7681
7727
|
payloadBases.push(payloadBytes);
|
|
7682
7728
|
payloadBytes += layout.payloadEnd - layout.payloadStart;
|
|
7683
7729
|
}
|
|
7684
7730
|
const template = layouts[0].moov;
|
|
7685
7731
|
const root = boxAt(template, 0, 0, template.length);
|
|
7686
|
-
const
|
|
7732
|
+
const mvhd = child(template, root, "mvhd");
|
|
7733
|
+
const movieTimescale = timescaleOf(template, mvhd);
|
|
7734
|
+
const chunkTrackIds = plan2.slice(0, chunkTrackCount).map((track) => idOf(template, child(template, track.trak, "tkhd")));
|
|
7735
|
+
const audioTrackId = Math.max(...chunkTrackIds) + 1;
|
|
7687
7736
|
const buildMoov = (dataStart2) => {
|
|
7688
7737
|
const traks = plan2.map((track, t) => {
|
|
7689
|
-
const parts =
|
|
7738
|
+
const parts = sources.flatMap((tracks, chunk) => {
|
|
7690
7739
|
const part = tracks[t];
|
|
7691
7740
|
return part ? [{ ...part, chunk }] : [];
|
|
7692
7741
|
});
|
|
7742
|
+
const moov = t < chunkTrackCount ? template : audio.moov;
|
|
7693
7743
|
const merged = {
|
|
7694
7744
|
durations: parts.flatMap((part) => part.durations),
|
|
7695
7745
|
sizes: parts.flatMap((part) => part.sizes),
|
|
@@ -7698,9 +7748,9 @@ function buildStitchedHeader(layouts) {
|
|
|
7698
7748
|
chunkOffsets: parts.flatMap(
|
|
7699
7749
|
(part) => part.chunkOffsets.map((offset) => {
|
|
7700
7750
|
const i = part.chunk;
|
|
7701
|
-
const { payloadStart, payloadEnd } =
|
|
7751
|
+
const { payloadStart, payloadEnd } = sourceLayouts[i];
|
|
7702
7752
|
if (offset < payloadStart || offset >= payloadEnd) {
|
|
7703
|
-
throw new Error(`[mp4Stitch]
|
|
7753
|
+
throw new Error(`[mp4Stitch] source ${i} has samples outside its mdat`);
|
|
7704
7754
|
}
|
|
7705
7755
|
return offset - payloadStart + payloadBases[i] + dataStart2;
|
|
7706
7756
|
})
|
|
@@ -7716,20 +7766,25 @@ function buildStitchedHeader(layouts) {
|
|
|
7716
7766
|
elst.writeBigInt64BE(BigInt(track.mediaTime), 12);
|
|
7717
7767
|
elst.writeUInt32BE(65536, 20);
|
|
7718
7768
|
const edts = track.mediaTime ? box("edts", fullBox("elst", 1, elst)) : Buffer.alloc(0);
|
|
7719
|
-
const
|
|
7720
|
-
|
|
7769
|
+
const tkhdOf = (tkhd) => {
|
|
7770
|
+
const timed = withDuration(moov, tkhd, shown);
|
|
7771
|
+
return t < chunkTrackCount ? timed : withId(timed, "tkhd", audioTrackId);
|
|
7772
|
+
};
|
|
7773
|
+
const trak = rebuild(moov, track.trak, {
|
|
7774
|
+
tkhd: (tkhd) => Buffer.concat([tkhdOf(tkhd), edts]),
|
|
7721
7775
|
edts: () => Buffer.alloc(0),
|
|
7722
|
-
mdia: (mdia) => rebuild(
|
|
7723
|
-
mdhd: (mdhd) => withDuration(
|
|
7724
|
-
minf: (minf) => rebuild(
|
|
7776
|
+
mdia: (mdia) => rebuild(moov, mdia, {
|
|
7777
|
+
mdhd: (mdhd) => withDuration(moov, mdhd, mediaDuration),
|
|
7778
|
+
minf: (minf) => rebuild(moov, minf, { stbl: () => buildStbl(track.stsd, merged) })
|
|
7725
7779
|
})
|
|
7726
7780
|
});
|
|
7727
7781
|
return { trak, shown };
|
|
7728
7782
|
});
|
|
7729
7783
|
let next = 0;
|
|
7784
|
+
const movieHeader = withDuration(template, mvhd, Math.max(...traks.map((t) => t.shown)));
|
|
7730
7785
|
return rebuild(template, root, {
|
|
7731
|
-
mvhd: (
|
|
7732
|
-
trak: () => traks[next
|
|
7786
|
+
mvhd: () => audio ? withId(movieHeader, "mvhd", audioTrackId + 1) : movieHeader,
|
|
7787
|
+
trak: () => ++next < chunkTrackCount ? traks[next - 1].trak : Buffer.concat(traks.slice(next - 1).map((t) => t.trak))
|
|
7733
7788
|
});
|
|
7734
7789
|
};
|
|
7735
7790
|
const ftyp = layouts[0].ftyp;
|
|
@@ -7762,15 +7817,16 @@ async function copyPayload(path, layout, index, out) {
|
|
|
7762
7817
|
);
|
|
7763
7818
|
}
|
|
7764
7819
|
}
|
|
7765
|
-
async function writeStitchedMp4(chunkPaths, outputPath) {
|
|
7766
|
-
const
|
|
7767
|
-
const
|
|
7820
|
+
async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
7821
|
+
const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
|
|
7822
|
+
const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
|
|
7823
|
+
const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
|
|
7768
7824
|
mkdirSync8(dirname9(outputPath), { recursive: true });
|
|
7769
7825
|
const workDir = mkdtempSync3(`${outputPath}.stitch-`);
|
|
7770
7826
|
const out = createWriteStream(join19(workDir, "out.mp4"));
|
|
7771
7827
|
try {
|
|
7772
7828
|
out.write(header);
|
|
7773
|
-
for (const [i, path] of
|
|
7829
|
+
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
7774
7830
|
out.end();
|
|
7775
7831
|
await finished(out);
|
|
7776
7832
|
renameSync3(join19(workDir, "out.mp4"), outputPath);
|
|
@@ -8345,7 +8401,8 @@ function buildLockedRenderConfig(input) {
|
|
|
8345
8401
|
chunkCount: input.chunkCount,
|
|
8346
8402
|
runtimeEnv: input.runtimeEnv,
|
|
8347
8403
|
variables: config.variables,
|
|
8348
|
-
...input.chunksCarryAudio ? { chunksCarryAudio: true } : {}
|
|
8404
|
+
...input.chunksCarryAudio ? { chunksCarryAudio: true } : {},
|
|
8405
|
+
...input.separateAudio ? { separateAudio: true } : {}
|
|
8349
8406
|
};
|
|
8350
8407
|
}
|
|
8351
8408
|
function carriesChunkAudio(config, options) {
|
|
@@ -8440,8 +8497,10 @@ function createVideoSourceShipper(input) {
|
|
|
8440
8497
|
return result;
|
|
8441
8498
|
};
|
|
8442
8499
|
}
|
|
8443
|
-
|
|
8444
|
-
|
|
8500
|
+
function plansSeparateAudio(config, options) {
|
|
8501
|
+
return options.deferAudio === true && config.format === "mp4" && config.cfr !== true;
|
|
8502
|
+
}
|
|
8503
|
+
async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
8445
8504
|
rejectUnsupportedDistributedFormat(config);
|
|
8446
8505
|
validateNoGpuEncode({
|
|
8447
8506
|
useGpu: false,
|
|
@@ -8577,6 +8636,100 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
8577
8636
|
});
|
|
8578
8637
|
}
|
|
8579
8638
|
}
|
|
8639
|
+
return {
|
|
8640
|
+
projectDir,
|
|
8641
|
+
log,
|
|
8642
|
+
sizeLimitBytes,
|
|
8643
|
+
abortSignal,
|
|
8644
|
+
assertNotAborted,
|
|
8645
|
+
cfg,
|
|
8646
|
+
job,
|
|
8647
|
+
workDir,
|
|
8648
|
+
compiledDir,
|
|
8649
|
+
finalCompiledDir,
|
|
8650
|
+
remoteFiles,
|
|
8651
|
+
urlByAbsentLocalPath,
|
|
8652
|
+
localPathByUrl,
|
|
8653
|
+
compileResult,
|
|
8654
|
+
compiled,
|
|
8655
|
+
composition,
|
|
8656
|
+
deviceScaleFactor,
|
|
8657
|
+
forceScreenshot,
|
|
8658
|
+
width,
|
|
8659
|
+
height,
|
|
8660
|
+
probeResult,
|
|
8661
|
+
totalFrames
|
|
8662
|
+
};
|
|
8663
|
+
}
|
|
8664
|
+
function mixPlanAudio(c, compiledDir, seconds, pcmPath) {
|
|
8665
|
+
return runAudioStage({
|
|
8666
|
+
projectDir: c.projectDir,
|
|
8667
|
+
workDir: c.workDir,
|
|
8668
|
+
compiledDir,
|
|
8669
|
+
urlByAbsentLocalPath: c.urlByAbsentLocalPath,
|
|
8670
|
+
duration: seconds,
|
|
8671
|
+
ffmpegProcessTimeout: c.cfg.ffmpegProcessTimeout,
|
|
8672
|
+
audioGain: c.cfg.audioGain,
|
|
8673
|
+
audios: c.composition.audios,
|
|
8674
|
+
masterFxChain: c.composition.masterFxChain,
|
|
8675
|
+
...pcmPath ? { keepEncoderInputAt: pcmPath } : {},
|
|
8676
|
+
abortSignal: c.abortSignal,
|
|
8677
|
+
assertNotAborted: c.assertNotAborted
|
|
8678
|
+
});
|
|
8679
|
+
}
|
|
8680
|
+
async function extractPlanVideos(c, deferRangeExtraction) {
|
|
8681
|
+
const extractResult = await runExtractVideosStage({
|
|
8682
|
+
projectDir: c.projectDir,
|
|
8683
|
+
compiledDir: c.compiledDir,
|
|
8684
|
+
job: c.job,
|
|
8685
|
+
cfg: c.cfg,
|
|
8686
|
+
composition: c.composition,
|
|
8687
|
+
abortSignal: c.abortSignal,
|
|
8688
|
+
assertNotAborted: c.assertNotAborted,
|
|
8689
|
+
materializeSymlinks: true,
|
|
8690
|
+
deferRangeExtraction,
|
|
8691
|
+
urlByAbsentLocalPath: c.urlByAbsentLocalPath
|
|
8692
|
+
});
|
|
8693
|
+
if (extractResult.failureToEnforce) throw extractResult.failureToEnforce;
|
|
8694
|
+
if (extractResult.extractionResult) {
|
|
8695
|
+
assertVideoExtractionSucceeded(extractResult.extractionResult);
|
|
8696
|
+
}
|
|
8697
|
+
return extractResult;
|
|
8698
|
+
}
|
|
8699
|
+
async function holdPlanAudioPeak(c, audioPath, pcmPath, seconds) {
|
|
8700
|
+
const peak = await constrainAacTruePeak({
|
|
8701
|
+
audioPath,
|
|
8702
|
+
...existsSync15(pcmPath) ? { sourcePath: pcmPath } : {},
|
|
8703
|
+
targetDurationSeconds: seconds,
|
|
8704
|
+
signal: c.abortSignal,
|
|
8705
|
+
timeoutMs: c.cfg.ffmpegProcessTimeout
|
|
8706
|
+
}).finally(() => rmSync8(pcmPath, { force: true }));
|
|
8707
|
+
if (!peak.success) throw encoderFailureError("[plan] audio true-peak check failed", peak);
|
|
8708
|
+
}
|
|
8709
|
+
async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, options = {}) {
|
|
8710
|
+
const planDir = executionPlanDir;
|
|
8711
|
+
const c = await compilePlanComposition(projectDir, config, planDir, options);
|
|
8712
|
+
const {
|
|
8713
|
+
log,
|
|
8714
|
+
sizeLimitBytes,
|
|
8715
|
+
cfg,
|
|
8716
|
+
job,
|
|
8717
|
+
workDir,
|
|
8718
|
+
compiledDir,
|
|
8719
|
+
finalCompiledDir,
|
|
8720
|
+
remoteFiles,
|
|
8721
|
+
urlByAbsentLocalPath,
|
|
8722
|
+
localPathByUrl,
|
|
8723
|
+
compileResult,
|
|
8724
|
+
compiled,
|
|
8725
|
+
composition,
|
|
8726
|
+
deviceScaleFactor,
|
|
8727
|
+
forceScreenshot,
|
|
8728
|
+
width,
|
|
8729
|
+
height,
|
|
8730
|
+
probeResult,
|
|
8731
|
+
totalFrames
|
|
8732
|
+
} = c;
|
|
8580
8733
|
const megapixels = compileResult.outputWidth * compileResult.outputHeight / 1e6;
|
|
8581
8734
|
let maxParallel = resolveMaxParallelChunks(config, totalFrames, megapixels, log);
|
|
8582
8735
|
const measureFrameCost = chunkSizingUsesFrameCost(config) && planNeedsFrameCostMeasurement(compiled.html);
|
|
@@ -8587,24 +8740,9 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
8587
8740
|
observedAt: "pre-extract",
|
|
8588
8741
|
log
|
|
8589
8742
|
});
|
|
8590
|
-
stageStarted = Date.now();
|
|
8591
|
-
const extractResult = await
|
|
8592
|
-
projectDir,
|
|
8593
|
-
compiledDir,
|
|
8594
|
-
job,
|
|
8595
|
-
cfg,
|
|
8596
|
-
composition,
|
|
8597
|
-
abortSignal,
|
|
8598
|
-
assertNotAborted,
|
|
8599
|
-
materializeSymlinks: true,
|
|
8600
|
-
deferRangeExtraction: options.deferVideoExtraction,
|
|
8601
|
-
urlByAbsentLocalPath
|
|
8602
|
-
});
|
|
8743
|
+
let stageStarted = Date.now();
|
|
8744
|
+
const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
|
|
8603
8745
|
logPlanStage(log, "extract", stageStarted);
|
|
8604
|
-
if (extractResult.failureToEnforce) throw extractResult.failureToEnforce;
|
|
8605
|
-
if (extractResult.extractionResult) {
|
|
8606
|
-
assertVideoExtractionSucceeded(extractResult.extractionResult);
|
|
8607
|
-
}
|
|
8608
8746
|
const stagedVideoFrames = join22(compiledDir, "__hyperframes_video_frames");
|
|
8609
8747
|
const videoFramesDst = join22(planDir, "video-frames");
|
|
8610
8748
|
if (existsSync15(videoFramesDst)) rmSync8(videoFramesDst, { recursive: true, force: true });
|
|
@@ -8660,23 +8798,18 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
8660
8798
|
"utf-8"
|
|
8661
8799
|
);
|
|
8662
8800
|
const digests = options.digests ?? /* @__PURE__ */ new Map();
|
|
8663
|
-
const
|
|
8664
|
-
const
|
|
8801
|
+
const separateAudio = plansSeparateAudio(config, options);
|
|
8802
|
+
const chunksCarryAudio = !separateAudio && carriesChunkAudio(config, options);
|
|
8803
|
+
const audioSeconds = chunksCarryAudio ? totalFrames / config.fps : probeResult.duration;
|
|
8804
|
+
const planMixPcmPath = join22(workDir, "plan-mix.wav");
|
|
8665
8805
|
stageStarted = Date.now();
|
|
8666
8806
|
const [audioOutcome, overlapOutcome, frameCostOutcome] = await Promise.allSettled([
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
|
|
8671
|
-
|
|
8672
|
-
|
|
8673
|
-
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout,
|
|
8674
|
-
audioGain: cfg.audioGain,
|
|
8675
|
-
audios: composition.audios,
|
|
8676
|
-
masterFxChain: composition.masterFxChain,
|
|
8677
|
-
abortSignal,
|
|
8678
|
-
assertNotAborted
|
|
8679
|
-
}).finally(() => logPlanStage(log, "audio", stageStarted)),
|
|
8807
|
+
separateAudio ? null : mixPlanAudio(
|
|
8808
|
+
c,
|
|
8809
|
+
finalCompiledDir,
|
|
8810
|
+
audioSeconds,
|
|
8811
|
+
chunksCarryAudio ? planMixPcmPath : void 0
|
|
8812
|
+
).finally(() => logPlanStage(log, "audio", stageStarted)),
|
|
8680
8813
|
(async () => {
|
|
8681
8814
|
for (const dir of [finalCompiledDir, videoFramesDst, videoSourcesDir]) {
|
|
8682
8815
|
await prehashPlanFiles(dir, digests);
|
|
@@ -8726,22 +8859,16 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
8726
8859
|
);
|
|
8727
8860
|
const chunks = buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize);
|
|
8728
8861
|
const audioResult = audioOutcome.value;
|
|
8729
|
-
if (audioResult
|
|
8862
|
+
if (audioResult?.audioError) {
|
|
8730
8863
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
8731
8864
|
}
|
|
8732
8865
|
const planAudioPath = join22(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
8733
|
-
if (audioResult
|
|
8866
|
+
if (audioResult?.hasAudio && existsSync15(audioResult.audioOutputPath)) {
|
|
8734
8867
|
renameSync4(audioResult.audioOutputPath, planAudioPath);
|
|
8735
8868
|
}
|
|
8736
|
-
if (
|
|
8869
|
+
if (chunksCarryAudio && existsSync15(planAudioPath)) {
|
|
8737
8870
|
stageStarted = Date.now();
|
|
8738
|
-
|
|
8739
|
-
audioPath: planAudioPath,
|
|
8740
|
-
targetDurationSeconds: audioSeconds,
|
|
8741
|
-
signal: abortSignal,
|
|
8742
|
-
timeoutMs: cfg.ffmpegProcessTimeout
|
|
8743
|
-
});
|
|
8744
|
-
if (!peak.success) throw encoderFailureError("[plan] audio true-peak check failed", peak);
|
|
8871
|
+
await holdPlanAudioPeak(c, planAudioPath, planMixPcmPath, audioSeconds);
|
|
8745
8872
|
logPlanStage(log, "audio-peak", stageStarted);
|
|
8746
8873
|
if (options.sliceChunkAudio) {
|
|
8747
8874
|
stageStarted = Date.now();
|
|
@@ -8767,7 +8894,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
8767
8894
|
effectiveChunkSize,
|
|
8768
8895
|
chunkCount,
|
|
8769
8896
|
runtimeEnv,
|
|
8770
|
-
chunksCarryAudio
|
|
8897
|
+
chunksCarryAudio,
|
|
8898
|
+
separateAudio
|
|
8771
8899
|
});
|
|
8772
8900
|
const compositionJson = {
|
|
8773
8901
|
durationSeconds: job.duration ?? 0,
|
|
@@ -8812,7 +8940,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
8812
8940
|
fontSnapshotSha,
|
|
8813
8941
|
durationSeconds: job.duration ?? 0,
|
|
8814
8942
|
totalFrames,
|
|
8815
|
-
hasAudio: audioResult
|
|
8943
|
+
hasAudio: audioResult?.hasAudio ?? false,
|
|
8816
8944
|
digests
|
|
8817
8945
|
});
|
|
8818
8946
|
logPlanStage(log, "freeze", stageStarted);
|
|
@@ -8837,6 +8965,33 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
8837
8965
|
producerVersion
|
|
8838
8966
|
};
|
|
8839
8967
|
}
|
|
8968
|
+
async function buildPlanAudio(projectDir, config, workRoot, outputPath, options = {}) {
|
|
8969
|
+
const c = await compilePlanComposition(projectDir, config, workRoot, {
|
|
8970
|
+
...options,
|
|
8971
|
+
executionPlanSizeLimitBytes: Number.MAX_SAFE_INTEGER
|
|
8972
|
+
});
|
|
8973
|
+
try {
|
|
8974
|
+
const seconds = c.totalFrames / config.fps;
|
|
8975
|
+
const pcmPath = join22(c.workDir, "plan-mix.wav");
|
|
8976
|
+
let stageStarted = Date.now();
|
|
8977
|
+
await extractPlanVideos(c, true);
|
|
8978
|
+
logPlanStage(c.log, "extract", stageStarted);
|
|
8979
|
+
stageStarted = Date.now();
|
|
8980
|
+
const mixed = await mixPlanAudio(c, c.compiledDir, seconds, pcmPath);
|
|
8981
|
+
logPlanStage(c.log, "audio", stageStarted);
|
|
8982
|
+
if (mixed.audioError) {
|
|
8983
|
+
applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
|
|
8984
|
+
}
|
|
8985
|
+
if (!mixed.hasAudio || !existsSync15(mixed.audioOutputPath)) return { hasAudio: false };
|
|
8986
|
+
renameSync4(mixed.audioOutputPath, outputPath);
|
|
8987
|
+
stageStarted = Date.now();
|
|
8988
|
+
await holdPlanAudioPeak(c, outputPath, pcmPath, seconds);
|
|
8989
|
+
logPlanStage(c.log, "audio-peak", stageStarted);
|
|
8990
|
+
return { hasAudio: true };
|
|
8991
|
+
} finally {
|
|
8992
|
+
rmSync8(c.workDir, { recursive: true, force: true });
|
|
8993
|
+
}
|
|
8994
|
+
}
|
|
8840
8995
|
async function plan(projectDir, config, planDir, options = {}) {
|
|
8841
8996
|
const executionPlan = await buildLocalExecutionPlan(projectDir, config, planDir, options);
|
|
8842
8997
|
return {
|
|
@@ -9445,7 +9600,8 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
9445
9600
|
)
|
|
9446
9601
|
),
|
|
9447
9602
|
chunksCarryAudio: options.chunksCarryAudio,
|
|
9448
|
-
sliceChunkAudio: true
|
|
9603
|
+
sliceChunkAudio: true,
|
|
9604
|
+
deferAudio: options.deferAudio
|
|
9449
9605
|
});
|
|
9450
9606
|
return await publishExecutionPlan(stagingRoot, publisher, { digests, log: config.logger });
|
|
9451
9607
|
} catch (error) {
|
|
@@ -9781,7 +9937,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
9781
9937
|
return mergePngFrameDirs(chunkPaths, outputPath, plan2.totalFrames, audioPath, start);
|
|
9782
9938
|
}
|
|
9783
9939
|
if (chunksAreStitchedMp4(planDir, cfr)) {
|
|
9784
|
-
|
|
9940
|
+
const separateAudio = stitchesSeparateAudio(planDir) && audioPath !== null;
|
|
9941
|
+
await writeStitchedMp4(chunkPaths, outputPath, separateAudio ? audioPath : void 0);
|
|
9785
9942
|
return {
|
|
9786
9943
|
outputPath,
|
|
9787
9944
|
durationMs: Date.now() - start,
|
|
@@ -9979,16 +10136,20 @@ async function runAssembleFfmpeg(args, step, signal, timeout) {
|
|
|
9979
10136
|
}
|
|
9980
10137
|
}
|
|
9981
10138
|
function chunksAreStitchedMp4(planDir, cfr) {
|
|
9982
|
-
|
|
10139
|
+
const { chunksCarryAudio, separateAudio } = lockedAudio(planDir);
|
|
10140
|
+
if (!chunksCarryAudio && !separateAudio) return false;
|
|
9983
10141
|
if (cfr) {
|
|
9984
|
-
throw new Error("[assemble] cfr=true cannot re-encode a plan whose mp4 chunks
|
|
10142
|
+
throw new Error("[assemble] cfr=true cannot re-encode a plan whose mp4 chunks are stitched");
|
|
9985
10143
|
}
|
|
9986
10144
|
return true;
|
|
9987
10145
|
}
|
|
9988
|
-
function
|
|
10146
|
+
function stitchesSeparateAudio(planDir) {
|
|
10147
|
+
return lockedAudio(planDir).separateAudio === true;
|
|
10148
|
+
}
|
|
10149
|
+
function lockedAudio(planDir) {
|
|
9989
10150
|
const encoderJsonPath = join26(planDir, "meta", "encoder.json");
|
|
9990
|
-
if (!existsSync18(encoderJsonPath)) return
|
|
9991
|
-
return JSON.parse(readFileSync11(encoderJsonPath, "utf-8"))
|
|
10151
|
+
if (!existsSync18(encoderJsonPath)) return {};
|
|
10152
|
+
return JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
|
|
9992
10153
|
}
|
|
9993
10154
|
function readCfrEncoder(planDir, format) {
|
|
9994
10155
|
if (format !== "mp4") {
|
|
@@ -10062,11 +10223,12 @@ import {
|
|
|
10062
10223
|
calculateOptimalWorkers as calculateOptimalWorkers2,
|
|
10063
10224
|
classifyCaptureFailure as classifyCaptureFailure4,
|
|
10064
10225
|
closeCaptureSession as closeCaptureSession10,
|
|
10065
|
-
createCaptureSession as
|
|
10226
|
+
createCaptureSession as createCaptureSession10,
|
|
10066
10227
|
createFrameLookupTable as createFrameLookupTable5,
|
|
10067
10228
|
deriveBeginFrameProbeTimeTicks as deriveBeginFrameProbeTimeTicks2,
|
|
10068
10229
|
getEncoderPreset as getEncoderPreset6,
|
|
10069
|
-
initializeSession as
|
|
10230
|
+
initializeSession as initializeSession10,
|
|
10231
|
+
measureVideoShownBoxes,
|
|
10070
10232
|
probeBeginFrameLiveness as probeBeginFrameLiveness2,
|
|
10071
10233
|
readWebGlVendorInfoFromCanvas,
|
|
10072
10234
|
resolveConfig as resolveConfig5
|
|
@@ -10095,9 +10257,9 @@ function resolveChunkFrameRange(slice, chunkIndex, frameRange) {
|
|
|
10095
10257
|
return { startFrame, endFrame };
|
|
10096
10258
|
}
|
|
10097
10259
|
var distributedCaptureSessionDependencies = {
|
|
10098
|
-
createCaptureSession:
|
|
10260
|
+
createCaptureSession: createCaptureSession10,
|
|
10099
10261
|
assertSwiftShader,
|
|
10100
|
-
initializeSession:
|
|
10262
|
+
initializeSession: initializeSession10,
|
|
10101
10263
|
closeCaptureSession: closeCaptureSession10,
|
|
10102
10264
|
readWebGlVendorInfo: readWebGlVendorInfoFromCanvas
|
|
10103
10265
|
};
|
|
@@ -10288,34 +10450,15 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10288
10450
|
const framesDir = join27(workDir, "captured-frames");
|
|
10289
10451
|
mkdirSync14(framesDir, { recursive: true });
|
|
10290
10452
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
10291
|
-
const
|
|
10292
|
-
|
|
10293
|
-
|
|
10294
|
-
|
|
10295
|
-
fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
|
|
10296
|
-
outputRoot: videoFramesDir,
|
|
10297
|
-
cfg,
|
|
10298
|
-
remoteSources
|
|
10299
|
-
}) : [];
|
|
10453
|
+
const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
|
|
10454
|
+
const chunkFps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
|
|
10455
|
+
const deferredVideos = planVideos?.extracted.filter((video) => video.deferredRange) ?? [];
|
|
10456
|
+
const shownFrames = planVideos ? shownDeferredFrameIndexes(planVideos, chunkFrameNumbers, chunkFps) : /* @__PURE__ */ new Map();
|
|
10300
10457
|
const remoteVideoIds = new Set(
|
|
10301
10458
|
planVideos?.extracted.filter(
|
|
10302
10459
|
(video) => video.deferredRange && Object.hasOwn(remoteSources, video.deferredRange.sourcePath)
|
|
10303
10460
|
).map((video) => video.videoId)
|
|
10304
10461
|
);
|
|
10305
|
-
const remoteVideos = deferredVideoFrames.filter((video) => remoteVideoIds.has(video.videoId));
|
|
10306
|
-
const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? createFrameLookupTable5(
|
|
10307
|
-
planVideos.videos,
|
|
10308
|
-
[
|
|
10309
|
-
...rebuildExtractedFramesFromPlanDir(
|
|
10310
|
-
planDir,
|
|
10311
|
-
planVideos.extracted.filter((video) => !video.deferredRange),
|
|
10312
|
-
v2Manifest === null ? "dense-v1" : "sparse-v2"
|
|
10313
|
-
),
|
|
10314
|
-
...deferredVideoFrames
|
|
10315
|
-
],
|
|
10316
|
-
resolveRenderFpsConfig(job.config.fps).value
|
|
10317
|
-
) : null;
|
|
10318
|
-
const createChunkVideoFrameInjector = createChunkVideoFrameInjectorFactory(videoFrameLookup);
|
|
10319
10462
|
const fileServer = await createFileServer({
|
|
10320
10463
|
projectDir: compiledDir,
|
|
10321
10464
|
compiledDir,
|
|
@@ -10331,8 +10474,12 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10331
10474
|
videoCount: planVideos?.videos.length ?? 0,
|
|
10332
10475
|
html: readFileSync12(join27(compiledDir, "index.html"), "utf-8"),
|
|
10333
10476
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
10334
|
-
skipReadinessVideoIds:
|
|
10335
|
-
|
|
10477
|
+
skipReadinessVideoIds: deferredVideos.filter(
|
|
10478
|
+
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
10479
|
+
).map((video) => video.videoId),
|
|
10480
|
+
videoMetadataHints: collectVideoMetadataHints(
|
|
10481
|
+
deferredVideos.filter((video) => remoteVideoIds.has(video.videoId))
|
|
10482
|
+
)
|
|
10336
10483
|
});
|
|
10337
10484
|
const chunkWorkerCount = calculateOptimalWorkers2(framesInChunk, void 0, cfg);
|
|
10338
10485
|
let session = null;
|
|
@@ -10345,6 +10492,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10345
10492
|
const capturePerfs = [];
|
|
10346
10493
|
const captureAttempts = [];
|
|
10347
10494
|
let forceScreenshotForChunk = encoder.forceScreenshot;
|
|
10495
|
+
let shownBoxes;
|
|
10348
10496
|
try {
|
|
10349
10497
|
if (chunkWorkerCount === 1 || !forceScreenshotForChunk) {
|
|
10350
10498
|
const bootStarted = Date.now();
|
|
@@ -10355,6 +10503,11 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10355
10503
|
cfg
|
|
10356
10504
|
);
|
|
10357
10505
|
sessionBootMs = Date.now() - bootStarted;
|
|
10506
|
+
shownBoxes = await measureVideoShownBoxes(
|
|
10507
|
+
session.page,
|
|
10508
|
+
[...shownFrames.keys()],
|
|
10509
|
+
chunkFrameNumbers.map((frame) => frame * chunkFps.den / chunkFps.num)
|
|
10510
|
+
);
|
|
10358
10511
|
const browserSelectedScreenshot = session.launchCaptureMode === "screenshot";
|
|
10359
10512
|
const beginFrameStalled = !browserSelectedScreenshot && await beginFrameSessionNeedsScreenshotFallback(session);
|
|
10360
10513
|
if (browserSelectedScreenshot) {
|
|
@@ -10396,6 +10549,29 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10396
10549
|
session = null;
|
|
10397
10550
|
}
|
|
10398
10551
|
}
|
|
10552
|
+
const deferredVideoFrames = planVideos ? await extractDeferredVideoFramesForChunk({
|
|
10553
|
+
planDir,
|
|
10554
|
+
planVideos,
|
|
10555
|
+
frames: chunkFrameNumbers,
|
|
10556
|
+
fps: chunkFps,
|
|
10557
|
+
outputRoot: videoFramesDir,
|
|
10558
|
+
cfg,
|
|
10559
|
+
remoteSources,
|
|
10560
|
+
shownBoxes
|
|
10561
|
+
}) : [];
|
|
10562
|
+
const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? createFrameLookupTable5(
|
|
10563
|
+
planVideos.videos,
|
|
10564
|
+
[
|
|
10565
|
+
...rebuildExtractedFramesFromPlanDir(
|
|
10566
|
+
planDir,
|
|
10567
|
+
planVideos.extracted.filter((video) => !video.deferredRange),
|
|
10568
|
+
v2Manifest === null ? "dense-v1" : "sparse-v2"
|
|
10569
|
+
),
|
|
10570
|
+
...deferredVideoFrames
|
|
10571
|
+
],
|
|
10572
|
+
resolveRenderFpsConfig(job.config.fps).value
|
|
10573
|
+
) : null;
|
|
10574
|
+
const createChunkVideoFrameInjector = createChunkVideoFrameInjectorFactory(videoFrameLookup);
|
|
10399
10575
|
const captureStarted = Date.now();
|
|
10400
10576
|
captureMode = await runCaptureWithScreenshotFallback({
|
|
10401
10577
|
forceScreenshot: forceScreenshotForChunk,
|
|
@@ -10586,6 +10762,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
10586
10762
|
encodeStageMs,
|
|
10587
10763
|
workers: chunkWorkerCount,
|
|
10588
10764
|
captureMode,
|
|
10765
|
+
// Device-pixel box each video's frames were extracted to cover; a video not listed kept full size.
|
|
10766
|
+
videoShownBoxes: Object.fromEntries(shownBoxes ?? []),
|
|
10589
10767
|
sha256,
|
|
10590
10768
|
outputKind,
|
|
10591
10769
|
producerVersion: plan2.producerVersion,
|
|
@@ -10897,10 +11075,11 @@ function validateIntDimension(field, value) {
|
|
|
10897
11075
|
}
|
|
10898
11076
|
|
|
10899
11077
|
// src/services/distributed/projectHash.ts
|
|
10900
|
-
import { readdirSync as readdirSync10,
|
|
11078
|
+
import { closeSync as closeSync5, openSync as openSync5, readdirSync as readdirSync10, readSync as readSync3, realpathSync as realpathSync2, statSync as statSync8 } from "node:fs";
|
|
10901
11079
|
import { createHash as createHash6 } from "node:crypto";
|
|
10902
11080
|
import { join as join29 } from "node:path";
|
|
10903
11081
|
var SITE_ID_SCHEME = "site-v2\0";
|
|
11082
|
+
var READ_CHUNK_BYTES = 256 * 1024;
|
|
10904
11083
|
function listSiteFiles(projectDir) {
|
|
10905
11084
|
const files = [];
|
|
10906
11085
|
const ancestors = /* @__PURE__ */ new Set([realpathSync2(projectDir)]);
|
|
@@ -10933,10 +11112,19 @@ function listSiteFiles(projectDir) {
|
|
|
10933
11112
|
}
|
|
10934
11113
|
function hashProjectDir(projectDir, files = listSiteFiles(projectDir)) {
|
|
10935
11114
|
const hash = createHash6("sha256").update(SITE_ID_SCHEME);
|
|
11115
|
+
const chunk = Buffer.allocUnsafe(READ_CHUNK_BYTES);
|
|
10936
11116
|
for (const rel of files) {
|
|
10937
11117
|
hash.update(rel);
|
|
10938
11118
|
hash.update("\0");
|
|
10939
|
-
|
|
11119
|
+
const fd = openSync5(join29(projectDir, rel), "r");
|
|
11120
|
+
try {
|
|
11121
|
+
let read;
|
|
11122
|
+
while ((read = readSync3(fd, chunk, 0, chunk.length, null)) > 0) {
|
|
11123
|
+
hash.update(chunk.subarray(0, read));
|
|
11124
|
+
}
|
|
11125
|
+
} finally {
|
|
11126
|
+
closeSync5(fd);
|
|
11127
|
+
}
|
|
10940
11128
|
}
|
|
10941
11129
|
return hash.digest("hex").slice(0, 16);
|
|
10942
11130
|
}
|
|
@@ -10984,6 +11172,7 @@ export {
|
|
|
10984
11172
|
assemble,
|
|
10985
11173
|
assembleV2,
|
|
10986
11174
|
buildChunkSlices,
|
|
11175
|
+
buildPlanAudio,
|
|
10987
11176
|
buildStitchedHeader,
|
|
10988
11177
|
chunksAreStitchedMp4,
|
|
10989
11178
|
createPlanV2FromExecutionPlan,
|
|
@@ -11001,6 +11190,7 @@ export {
|
|
|
11001
11190
|
plan,
|
|
11002
11191
|
planV2,
|
|
11003
11192
|
planV2WithPublisher,
|
|
11193
|
+
plansSeparateAudio,
|
|
11004
11194
|
publishPlanV2FromExecutionPlan,
|
|
11005
11195
|
publishPlanV2FromV1,
|
|
11006
11196
|
readMp4Layout,
|
|
@@ -11013,6 +11203,7 @@ export {
|
|
|
11013
11203
|
renderChunkV2,
|
|
11014
11204
|
resolveChunkPlan,
|
|
11015
11205
|
resolvePlanAudioPath,
|
|
11206
|
+
stitchesSeparateAudio,
|
|
11016
11207
|
validateDistributedRenderConfig,
|
|
11017
11208
|
validatePlanV2MaterializedTarget,
|
|
11018
11209
|
validateVariablesPayload
|