@hyperframes/producer 0.8.181 → 0.8.184
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 +451 -246
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +59 -59
- package/dist/index.d.ts +1 -0
- package/dist/index.js +601 -292
- package/dist/public-server.js +311 -158
- 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/distributed/renderChunk.d.ts +9 -2
- 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 +11 -4
- package/dist/services/render/stages/encodeStage.d.ts +26 -2
- 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/index.js
CHANGED
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
extractMediaMetadata as extractMediaMetadata4,
|
|
29
29
|
resolveConfig as resolveConfig2,
|
|
30
30
|
closeCaptureSession as closeCaptureSession8,
|
|
31
|
+
createCaptureSession as createCaptureSession8,
|
|
32
|
+
initializeSession as initializeSession8,
|
|
31
33
|
createVideoFrameInjector,
|
|
32
34
|
getEncoderPreset as getEncoderPreset3,
|
|
33
35
|
distributeFrames as distributeFrames2,
|
|
@@ -1402,7 +1404,7 @@ function pidScope() {
|
|
|
1402
1404
|
return {
|
|
1403
1405
|
host: hostname(),
|
|
1404
1406
|
// Without /proc (macOS, Windows) the boot time, to the minute, stands in for the boot id.
|
|
1405
|
-
boot: attempt(() => readFileSync2("/proc/sys/kernel/random/boot_id", "utf-8").trim()) ?? String(Math.round(Date.now() / 6e4 - uptime() / 60)),
|
|
1407
|
+
boot: attempt(() => readFileSync2("/proc/sys/kernel/random/boot_id", "utf-8").trim()) ?? attempt(() => String(Math.round(Date.now() / 6e4 - uptime() / 60))),
|
|
1406
1408
|
pidns: attempt(() => readlinkSync("/proc/self/ns/pid"))
|
|
1407
1409
|
};
|
|
1408
1410
|
}
|
|
@@ -1420,9 +1422,14 @@ function alive(pid) {
|
|
|
1420
1422
|
return error.code === "EPERM";
|
|
1421
1423
|
}
|
|
1422
1424
|
}
|
|
1423
|
-
function
|
|
1425
|
+
function knownPidScope() {
|
|
1424
1426
|
const scope = pidScope();
|
|
1425
|
-
|
|
1427
|
+
const unknown = scope.boot === void 0 || process.platform === "linux" && scope.pidns === void 0;
|
|
1428
|
+
return unknown ? void 0 : scope;
|
|
1429
|
+
}
|
|
1430
|
+
function inThisPidScope(owner) {
|
|
1431
|
+
const scope = knownPidScope();
|
|
1432
|
+
return scope !== void 0 && owner.host === scope.host && owner.boot === scope.boot && owner.pidns === scope.pidns;
|
|
1426
1433
|
}
|
|
1427
1434
|
function ownerState(dir) {
|
|
1428
1435
|
let owner;
|
|
@@ -1487,7 +1494,9 @@ async function defaultArtifactDurationProbe(path) {
|
|
|
1487
1494
|
const meta = await extractMediaMetadata(path);
|
|
1488
1495
|
return {
|
|
1489
1496
|
durationSeconds: meta.durationSeconds,
|
|
1490
|
-
frames: meta.frames
|
|
1497
|
+
frames: meta.frames,
|
|
1498
|
+
width: meta.width,
|
|
1499
|
+
height: meta.height
|
|
1491
1500
|
};
|
|
1492
1501
|
}
|
|
1493
1502
|
function buildArtifactExpectation(input) {
|
|
@@ -1496,7 +1505,9 @@ function buildArtifactExpectation(input) {
|
|
|
1496
1505
|
const expectation = {
|
|
1497
1506
|
expectedDurationSeconds: input.durationSeconds,
|
|
1498
1507
|
fps: input.fps,
|
|
1499
|
-
expectedFrames: input.expectedFrames
|
|
1508
|
+
expectedFrames: input.expectedFrames,
|
|
1509
|
+
expectedWidth: input.width,
|
|
1510
|
+
expectedHeight: input.height
|
|
1500
1511
|
};
|
|
1501
1512
|
if (input.outputFormat !== "hls") return expectation;
|
|
1502
1513
|
return {
|
|
@@ -1564,6 +1575,16 @@ function assertFrameCountWithinTolerance(expectedFrames, probedFrames, stagingPa
|
|
|
1564
1575
|
`Render artifact is truncated: expected ${expectedFrames} frames, probed ${probedFrames} frames (shortfall ${shortfall} frames). Artifact: ${stagingPath}`
|
|
1565
1576
|
);
|
|
1566
1577
|
}
|
|
1578
|
+
function assertSizeMatches(expected, probed, stagingPath) {
|
|
1579
|
+
const { expectedWidth: width, expectedHeight: height } = expected;
|
|
1580
|
+
if (width === void 0 || height === void 0) return;
|
|
1581
|
+
if (probed.width === void 0 || probed.height === void 0) return;
|
|
1582
|
+
const fits = (want, got) => got === want || got === want + want % 2;
|
|
1583
|
+
if (fits(width, probed.width) && fits(height, probed.height)) return;
|
|
1584
|
+
throw new Error(
|
|
1585
|
+
`Render artifact has the wrong size: expected ${width}x${height}, probed ${probed.width}x${probed.height}. Artifact: ${stagingPath}`
|
|
1586
|
+
);
|
|
1587
|
+
}
|
|
1567
1588
|
var ArtifactTransaction = class {
|
|
1568
1589
|
constructor(destinationPath, kind, fileSystem = defaultFileSystem, durationProbe = defaultArtifactDurationProbe) {
|
|
1569
1590
|
this.kind = kind;
|
|
@@ -1634,6 +1655,7 @@ var ArtifactTransaction = class {
|
|
|
1634
1655
|
if (expected.expectedFrames !== void 0) {
|
|
1635
1656
|
assertFrameCountWithinTolerance(expected.expectedFrames, probed.frames, probePath);
|
|
1636
1657
|
}
|
|
1658
|
+
assertSizeMatches(expected, probed, probePath);
|
|
1637
1659
|
}
|
|
1638
1660
|
async runDurationProbe(probePath) {
|
|
1639
1661
|
try {
|
|
@@ -3182,7 +3204,9 @@ function aggregateDrawElement(perfs, de) {
|
|
|
3182
3204
|
if (perfs.length === 0) return void 0;
|
|
3183
3205
|
const modes = [...new Set(perfs.map((p) => p.captureMode).filter(Boolean))].sort();
|
|
3184
3206
|
const gateReasons = [
|
|
3185
|
-
...new Set(
|
|
3207
|
+
...new Set(
|
|
3208
|
+
[de.probeGateReason, ...perfs.map((p) => p.deGateReason)].filter((r) => !!r)
|
|
3209
|
+
)
|
|
3186
3210
|
].sort();
|
|
3187
3211
|
const gpuRenderers = [
|
|
3188
3212
|
...new Set(perfs.map((p) => p.gpuRenderer).filter((r) => !!r))
|
|
@@ -8682,6 +8706,7 @@ async function runAudioStage(input) {
|
|
|
8682
8706
|
audios,
|
|
8683
8707
|
masterFxChain,
|
|
8684
8708
|
urlByAbsentLocalPath,
|
|
8709
|
+
keepEncoderInputAt,
|
|
8685
8710
|
abortSignal,
|
|
8686
8711
|
assertNotAborted,
|
|
8687
8712
|
log
|
|
@@ -8701,7 +8726,13 @@ async function runAudioStage(input) {
|
|
|
8701
8726
|
audioOutputPath,
|
|
8702
8727
|
duration,
|
|
8703
8728
|
abortSignal,
|
|
8704
|
-
{
|
|
8729
|
+
{
|
|
8730
|
+
ffmpegProcessTimeout,
|
|
8731
|
+
audioGain,
|
|
8732
|
+
masterFxChain,
|
|
8733
|
+
urlByAbsentLocalPath,
|
|
8734
|
+
keepEncoderInputAt
|
|
8735
|
+
},
|
|
8705
8736
|
compiledDir
|
|
8706
8737
|
);
|
|
8707
8738
|
} catch (err) {
|
|
@@ -8815,13 +8846,29 @@ function inspectDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, free
|
|
|
8815
8846
|
}
|
|
8816
8847
|
return { available: false, estimatedBytes, freeBytes };
|
|
8817
8848
|
}
|
|
8818
|
-
function
|
|
8849
|
+
function diskCaptureRemedy(cfg, outputFormat, durationSeconds, workerCount) {
|
|
8850
|
+
const oneWorker = explainStreamingEncodeGate(cfg, outputFormat, 1, durationSeconds);
|
|
8851
|
+
if (workerCount > 1 && oneWorker.enabled) {
|
|
8852
|
+
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.`;
|
|
8853
|
+
}
|
|
8854
|
+
switch (oneWorker.reason) {
|
|
8855
|
+
case "format_excluded":
|
|
8856
|
+
return `${outputFormat} output stores every frame at any worker count. Free up disk space or render to mp4.`;
|
|
8857
|
+
case "disabled_by_config":
|
|
8858
|
+
return "PRODUCER_ENABLE_STREAMING_ENCODE=false sends every render to disk. Free up disk space or unset it.";
|
|
8859
|
+
case "duration_cap":
|
|
8860
|
+
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.";
|
|
8861
|
+
default:
|
|
8862
|
+
return "Free up disk space.";
|
|
8863
|
+
}
|
|
8864
|
+
}
|
|
8865
|
+
function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remedy) {
|
|
8819
8866
|
const basisNote = basis === "measured" ? ` (measured from the first ${DISK_PROJECTION_SAMPLE_FRAMES} frames)` : "";
|
|
8820
8867
|
return new Error(
|
|
8821
|
-
`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).
|
|
8868
|
+
`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}`
|
|
8822
8869
|
);
|
|
8823
8870
|
}
|
|
8824
|
-
function assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, freeDiskBytes) {
|
|
8871
|
+
function assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, remedy, freeDiskBytes) {
|
|
8825
8872
|
const headroom = inspectDiskCaptureHeadroom(
|
|
8826
8873
|
framesDir,
|
|
8827
8874
|
totalFrames,
|
|
@@ -8833,7 +8880,8 @@ function assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, freeD
|
|
|
8833
8880
|
headroom.estimatedBytes,
|
|
8834
8881
|
headroom.freeBytes,
|
|
8835
8882
|
framesDir,
|
|
8836
|
-
"estimate"
|
|
8883
|
+
"estimate",
|
|
8884
|
+
remedy
|
|
8837
8885
|
);
|
|
8838
8886
|
}
|
|
8839
8887
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
@@ -8853,7 +8901,7 @@ function measureCaptureFrameBytes(workDir, framesDir) {
|
|
|
8853
8901
|
return total;
|
|
8854
8902
|
}
|
|
8855
8903
|
function createDiskCaptureProjection(input) {
|
|
8856
|
-
const { framesDir, totalFrames, measureBytes, freeDiskBytes = freeDiskBytesAt } = input;
|
|
8904
|
+
const { framesDir, totalFrames, measureBytes, remedy, freeDiskBytes = freeDiskBytesAt } = input;
|
|
8857
8905
|
let checked = false;
|
|
8858
8906
|
return (capturedFrames) => {
|
|
8859
8907
|
if (checked || capturedFrames < DISK_PROJECTION_SAMPLE_FRAMES) return;
|
|
@@ -8865,7 +8913,7 @@ function createDiskCaptureProjection(input) {
|
|
|
8865
8913
|
if (remaining <= 0 || freeBytes === null) return;
|
|
8866
8914
|
const needBytes = Math.ceil(measured / capturedFrames * remaining);
|
|
8867
8915
|
if (needBytes > freeBytes * 0.9) {
|
|
8868
|
-
throw diskCaptureShortfallError(needBytes, freeBytes, framesDir, "measured");
|
|
8916
|
+
throw diskCaptureShortfallError(needBytes, freeBytes, framesDir, "measured", remedy);
|
|
8869
8917
|
}
|
|
8870
8918
|
};
|
|
8871
8919
|
}
|
|
@@ -8907,11 +8955,13 @@ async function runCaptureStage(input) {
|
|
|
8907
8955
|
}
|
|
8908
8956
|
}
|
|
8909
8957
|
const captureOptions = buildCaptureOptions();
|
|
8910
|
-
|
|
8958
|
+
const remedy = diskCaptureRemedy(cfg, job.config.format ?? "mp4", job.duration ?? 0, workerCount);
|
|
8959
|
+
assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, remedy);
|
|
8911
8960
|
const checkDiskProjection = createDiskCaptureProjection({
|
|
8912
8961
|
framesDir,
|
|
8913
8962
|
totalFrames,
|
|
8914
|
-
measureBytes: () => measureCaptureFrameBytes(workDir, framesDir)
|
|
8963
|
+
measureBytes: () => measureCaptureFrameBytes(workDir, framesDir),
|
|
8964
|
+
remedy
|
|
8915
8965
|
});
|
|
8916
8966
|
if (workerCount > 1) {
|
|
8917
8967
|
const attempts = await executeDiskCaptureWithAdaptiveRetry({
|
|
@@ -8932,6 +8982,7 @@ async function runCaptureStage(input) {
|
|
|
8932
8982
|
if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
|
|
8933
8983
|
return;
|
|
8934
8984
|
}
|
|
8985
|
+
if (progress.landedFrame) input.onFrameLanded?.(progress.landedFrame);
|
|
8935
8986
|
job.framesRendered = progress.capturedFrames;
|
|
8936
8987
|
checkDiskProjection(progress.capturedFrames);
|
|
8937
8988
|
const frameProgress = progress.capturedFrames / progress.totalFrames;
|
|
@@ -10101,6 +10152,7 @@ import {
|
|
|
10101
10152
|
statSync as statSync5,
|
|
10102
10153
|
writeFileSync as writeFileSync4
|
|
10103
10154
|
} from "node:fs";
|
|
10155
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
10104
10156
|
import { dirname as dirname8, join as join24 } from "node:path";
|
|
10105
10157
|
import {
|
|
10106
10158
|
encodeFramesChunkedConcat,
|
|
@@ -10108,7 +10160,8 @@ import {
|
|
|
10108
10160
|
formatFfmpegError,
|
|
10109
10161
|
MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME2,
|
|
10110
10162
|
resolveConfig,
|
|
10111
|
-
runFfmpeg as runFfmpeg3
|
|
10163
|
+
runFfmpeg as runFfmpeg3,
|
|
10164
|
+
spawnStreamingEncoder as spawnStreamingEncoder4
|
|
10112
10165
|
} from "@hyperframes/engine";
|
|
10113
10166
|
import {
|
|
10114
10167
|
clearGifFramesBeforeNext,
|
|
@@ -10122,8 +10175,10 @@ function fpsToFfmpegArg2(fps) {
|
|
|
10122
10175
|
}
|
|
10123
10176
|
var KEEP_FILTER_GRAPH_WHEN_FRAMES_DROP_ALPHA = ["-reinit_filter", "0"];
|
|
10124
10177
|
var WRITE_EVERY_FRAME_WHOLE = ["-gifflags", "0"];
|
|
10178
|
+
var FAIL_ON_UNREADABLE_FRAME_INSTEAD_OF_ENDING_THERE = ["-xerror"];
|
|
10125
10179
|
function framesInput(input, fpsArg) {
|
|
10126
10180
|
return [
|
|
10181
|
+
...FAIL_ON_UNREADABLE_FRAME_INSTEAD_OF_ENDING_THERE,
|
|
10127
10182
|
"-framerate",
|
|
10128
10183
|
fpsArg,
|
|
10129
10184
|
...KEEP_FILTER_GRAPH_WHEN_FRAMES_DROP_ALPHA,
|
|
@@ -10243,6 +10298,124 @@ function startEncodeProgress(job, stage, onProgress) {
|
|
|
10243
10298
|
});
|
|
10244
10299
|
return (frames = total) => reportEncodeProgress(job, frames, total, onProgress, 75);
|
|
10245
10300
|
}
|
|
10301
|
+
function resolveEngineConfig(input) {
|
|
10302
|
+
return input.engineConfig ?? input.job.config.producerConfig ?? resolveConfig();
|
|
10303
|
+
}
|
|
10304
|
+
function videoEncoderOptions(input, engineCfg) {
|
|
10305
|
+
const { job, preset } = input;
|
|
10306
|
+
return {
|
|
10307
|
+
fps: job.config.fps,
|
|
10308
|
+
width: input.width,
|
|
10309
|
+
height: input.height,
|
|
10310
|
+
codec: preset.codec,
|
|
10311
|
+
preset: preset.preset,
|
|
10312
|
+
quality: input.effectiveQuality,
|
|
10313
|
+
bitrate: input.effectiveBitrate,
|
|
10314
|
+
pixelFormat: preset.pixelFormat,
|
|
10315
|
+
vp9CpuUsed: engineCfg.vp9CpuUsed,
|
|
10316
|
+
useGpu: job.config.useGpu,
|
|
10317
|
+
hdr: preset.hdr,
|
|
10318
|
+
// Distributed chunk renders pass these so the encoder writes closed-GOP
|
|
10319
|
+
// keyframes that survive `-f concat -c copy` at assemble time. In-process
|
|
10320
|
+
// renders leave both undefined → preserves the existing open-GOP output.
|
|
10321
|
+
lockGopForChunkConcat: input.lockGopForChunkConcat === true,
|
|
10322
|
+
gopSize: input.gopSize
|
|
10323
|
+
};
|
|
10324
|
+
}
|
|
10325
|
+
function canEncodeWhileCapturing(input, workerCount, cpuCount) {
|
|
10326
|
+
return workerCount > 1 && workerCount < cpuCount && !input.isPngSequence && !input.isGif && !input.enableChunkedEncode && input.preset.codec === "h264" && !input.preset.hdr && input.job.config.useGpu !== true && input.captureImageFormat === "jpeg" && input.lockGopForChunkConcat !== true;
|
|
10327
|
+
}
|
|
10328
|
+
async function readLandedFrame(workerPath, mergedPath) {
|
|
10329
|
+
if (workerPath !== void 0) {
|
|
10330
|
+
try {
|
|
10331
|
+
return await readFile2(workerPath);
|
|
10332
|
+
} catch (error) {
|
|
10333
|
+
if (error.code !== "ENOENT") throw error;
|
|
10334
|
+
}
|
|
10335
|
+
}
|
|
10336
|
+
return readFile2(mergedPath);
|
|
10337
|
+
}
|
|
10338
|
+
function encodeWhileCapturing(input, totalFrames) {
|
|
10339
|
+
const engineCfg = resolveEngineConfig(input);
|
|
10340
|
+
const stop = new AbortController();
|
|
10341
|
+
const signal = input.abortSignal ? AbortSignal.any([input.abortSignal, stop.signal]) : stop.signal;
|
|
10342
|
+
const landed = /* @__PURE__ */ new Map();
|
|
10343
|
+
let captureDone = false;
|
|
10344
|
+
let framesFed = 0;
|
|
10345
|
+
let wake = null;
|
|
10346
|
+
const nudge = () => {
|
|
10347
|
+
wake?.();
|
|
10348
|
+
wake = null;
|
|
10349
|
+
};
|
|
10350
|
+
signal.addEventListener("abort", nudge, { once: true });
|
|
10351
|
+
let reportFed = null;
|
|
10352
|
+
const encoder = spawnStreamingEncoder4(
|
|
10353
|
+
input.videoOnlyPath,
|
|
10354
|
+
{ ...videoEncoderOptions(input, engineCfg), imageFormat: "jpeg" },
|
|
10355
|
+
signal,
|
|
10356
|
+
engineCfg
|
|
10357
|
+
);
|
|
10358
|
+
const fed = (async () => {
|
|
10359
|
+
const stream = await encoder;
|
|
10360
|
+
for (let next = 0; next < totalFrames; next++) {
|
|
10361
|
+
while (!landed.has(next) && !captureDone && !signal.aborted) {
|
|
10362
|
+
await new Promise((resolve12) => wake = resolve12);
|
|
10363
|
+
}
|
|
10364
|
+
if (signal.aborted) return;
|
|
10365
|
+
const mergedPath = join24(input.framesDir, formatCaptureFrameName(next, "jpg"));
|
|
10366
|
+
const frame = await readLandedFrame(landed.get(next), mergedPath);
|
|
10367
|
+
landed.delete(next);
|
|
10368
|
+
if (!await stream.writeFrame(frame)) {
|
|
10369
|
+
throw new Error(stream.getExitError() ?? "encoder exited before the last frame");
|
|
10370
|
+
}
|
|
10371
|
+
framesFed++;
|
|
10372
|
+
reportFed?.(framesFed);
|
|
10373
|
+
}
|
|
10374
|
+
})().then(
|
|
10375
|
+
() => void 0,
|
|
10376
|
+
(error) => error ?? new Error("encode failed")
|
|
10377
|
+
);
|
|
10378
|
+
const closeEncoder = async () => (await encoder.catch(() => null))?.close();
|
|
10379
|
+
return {
|
|
10380
|
+
onFrameLanded: (frame) => {
|
|
10381
|
+
landed.set(frame.index, frame.path);
|
|
10382
|
+
nudge();
|
|
10383
|
+
},
|
|
10384
|
+
finish: async () => {
|
|
10385
|
+
const start = Date.now();
|
|
10386
|
+
captureDone = true;
|
|
10387
|
+
input.log.info("[Render] Encoded frames while capturing", {
|
|
10388
|
+
framesFed,
|
|
10389
|
+
totalFrames
|
|
10390
|
+
});
|
|
10391
|
+
const encoded = startEncodeProgress(input.job, "Encoding video", input.onProgress);
|
|
10392
|
+
reportFed = encoded;
|
|
10393
|
+
nudge();
|
|
10394
|
+
const failure = await fed;
|
|
10395
|
+
const result = await closeEncoder();
|
|
10396
|
+
input.assertNotAborted();
|
|
10397
|
+
if (failure === void 0 && result?.success) {
|
|
10398
|
+
encoded();
|
|
10399
|
+
return { encodeMs: Date.now() - start };
|
|
10400
|
+
}
|
|
10401
|
+
input.log.warn(
|
|
10402
|
+
"[Render] Encoding while capturing failed; encoding the frames after capture",
|
|
10403
|
+
{
|
|
10404
|
+
error: String(failure ?? result?.error)
|
|
10405
|
+
}
|
|
10406
|
+
);
|
|
10407
|
+
return runEncodeStage(input);
|
|
10408
|
+
},
|
|
10409
|
+
abort: async () => {
|
|
10410
|
+
stop.abort();
|
|
10411
|
+
await fed;
|
|
10412
|
+
await closeEncoder();
|
|
10413
|
+
},
|
|
10414
|
+
get framesFed() {
|
|
10415
|
+
return framesFed;
|
|
10416
|
+
}
|
|
10417
|
+
};
|
|
10418
|
+
}
|
|
10246
10419
|
async function runEncodeStage(input) {
|
|
10247
10420
|
const {
|
|
10248
10421
|
job,
|
|
@@ -10250,16 +10423,11 @@ async function runEncodeStage(input) {
|
|
|
10250
10423
|
outputPath,
|
|
10251
10424
|
framesDir,
|
|
10252
10425
|
videoOnlyPath,
|
|
10253
|
-
width,
|
|
10254
|
-
height,
|
|
10255
10426
|
needsAlpha,
|
|
10256
10427
|
hasAudio,
|
|
10257
10428
|
audioOutputPath,
|
|
10258
10429
|
isPngSequence,
|
|
10259
10430
|
isGif,
|
|
10260
|
-
preset,
|
|
10261
|
-
effectiveQuality,
|
|
10262
|
-
effectiveBitrate,
|
|
10263
10431
|
enableChunkedEncode,
|
|
10264
10432
|
chunkedEncodeSize,
|
|
10265
10433
|
abortSignal,
|
|
@@ -10289,7 +10457,7 @@ async function runEncodeStage(input) {
|
|
|
10289
10457
|
encoded();
|
|
10290
10458
|
return { encodeMs: Date.now() - stage5Start };
|
|
10291
10459
|
}
|
|
10292
|
-
const engineCfg = input
|
|
10460
|
+
const engineCfg = resolveEngineConfig(input);
|
|
10293
10461
|
if (isGif) {
|
|
10294
10462
|
const encoded = startEncodeProgress(job, "Encoding GIF", onProgress);
|
|
10295
10463
|
if (hasAudio) {
|
|
@@ -10319,24 +10487,7 @@ async function runEncodeStage(input) {
|
|
|
10319
10487
|
const videoEngineCfg = scaledEncodeTimeout > engineCfg.ffmpegEncodeTimeout ? { ...engineCfg, ffmpegEncodeTimeout: scaledEncodeTimeout } : engineCfg;
|
|
10320
10488
|
const frameExt = frameFileExtension2(input.captureImageFormat);
|
|
10321
10489
|
const framePattern = `frame_%06d.${frameExt}`;
|
|
10322
|
-
const encoderOpts =
|
|
10323
|
-
fps: job.config.fps,
|
|
10324
|
-
width,
|
|
10325
|
-
height,
|
|
10326
|
-
codec: preset.codec,
|
|
10327
|
-
preset: preset.preset,
|
|
10328
|
-
quality: effectiveQuality,
|
|
10329
|
-
bitrate: effectiveBitrate,
|
|
10330
|
-
pixelFormat: preset.pixelFormat,
|
|
10331
|
-
vp9CpuUsed: engineCfg.vp9CpuUsed,
|
|
10332
|
-
useGpu: job.config.useGpu,
|
|
10333
|
-
hdr: preset.hdr,
|
|
10334
|
-
// Distributed chunk renders pass these so the encoder writes closed-GOP
|
|
10335
|
-
// keyframes that survive `-f concat -c copy` at assemble time. In-process
|
|
10336
|
-
// renders leave both undefined → preserves the existing open-GOP output.
|
|
10337
|
-
lockGopForChunkConcat: input.lockGopForChunkConcat === true,
|
|
10338
|
-
gopSize: input.gopSize
|
|
10339
|
-
};
|
|
10490
|
+
const encoderOpts = videoEncoderOptions(input, engineCfg);
|
|
10340
10491
|
const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
|
|
10341
10492
|
framesDir,
|
|
10342
10493
|
framePattern,
|
|
@@ -10374,7 +10525,8 @@ import { dirname as dirname9, join as join25 } from "node:path";
|
|
|
10374
10525
|
import {
|
|
10375
10526
|
DEFAULT_CONFIG,
|
|
10376
10527
|
describeFfmpegFailure as describeFfmpegFailure2,
|
|
10377
|
-
|
|
10528
|
+
encodeAac,
|
|
10529
|
+
extractAudioMetadata as extractAudioMetadata3,
|
|
10378
10530
|
getFfprobeBinary,
|
|
10379
10531
|
ManagedChildProcess,
|
|
10380
10532
|
runFfmpeg as runFfmpeg5,
|
|
@@ -10383,7 +10535,12 @@ import {
|
|
|
10383
10535
|
import { redactKnownPaths, redactTelemetryString as redactTelemetryString3 } from "@hyperframes/core";
|
|
10384
10536
|
|
|
10385
10537
|
// src/services/render/audioTruePeak.ts
|
|
10386
|
-
import {
|
|
10538
|
+
import {
|
|
10539
|
+
aacSegmentCount,
|
|
10540
|
+
describeFfmpegFailure,
|
|
10541
|
+
extractAudioMetadata as extractAudioMetadata2,
|
|
10542
|
+
runFfmpeg as runFfmpeg4
|
|
10543
|
+
} from "@hyperframes/engine";
|
|
10387
10544
|
var toDbfs = (token) => token.toLowerCase() === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
|
|
10388
10545
|
function checkedDbfs(value) {
|
|
10389
10546
|
if (Number.isNaN(value) || value === Number.POSITIVE_INFINITY) {
|
|
@@ -10421,12 +10578,13 @@ async function scanTruePeak(audioPath, trim, timeoutMs, signal) {
|
|
|
10421
10578
|
};
|
|
10422
10579
|
}
|
|
10423
10580
|
async function measureTruePeakDbfs(audioPath, options) {
|
|
10424
|
-
const { timeoutMs, signal
|
|
10581
|
+
const { timeoutMs, signal } = options;
|
|
10425
10582
|
const wholeFile = async () => (await scanTruePeak(audioPath, "", timeoutMs, signal)).peak;
|
|
10583
|
+
const { durationSeconds, sampleRate } = await extractAudioMetadata2(audioPath, { signal });
|
|
10426
10584
|
const block = 4800;
|
|
10427
|
-
const count =
|
|
10428
|
-
if (
|
|
10429
|
-
const cut = (k) => k * Math.floor(
|
|
10585
|
+
const count = sampleRate === 48e3 && Number.isFinite(durationSeconds) ? aacSegmentCount(durationSeconds) : 1;
|
|
10586
|
+
if (count === 1) return wholeFile();
|
|
10587
|
+
const cut = (k) => k * Math.floor(durationSeconds * 10 / count) * block;
|
|
10430
10588
|
const peers = new AbortController();
|
|
10431
10589
|
const segmentSignal = signal ? AbortSignal.any([signal, peers.signal]) : peers.signal;
|
|
10432
10590
|
const scans = await Promise.all(
|
|
@@ -10455,24 +10613,8 @@ var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
|
|
|
10455
10613
|
var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
|
|
10456
10614
|
var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
|
|
10457
10615
|
var MAX_TRUE_PEAK_CORRECTION_PASSES = 3;
|
|
10458
|
-
function
|
|
10459
|
-
return
|
|
10460
|
-
"-i",
|
|
10461
|
-
inputPath,
|
|
10462
|
-
"-map",
|
|
10463
|
-
"0:a:0",
|
|
10464
|
-
"-vn",
|
|
10465
|
-
"-af",
|
|
10466
|
-
`volume=${attenuationDb.toFixed(3)}dB`,
|
|
10467
|
-
"-t",
|
|
10468
|
-
formatSeconds(targetDurationSeconds),
|
|
10469
|
-
"-c:a",
|
|
10470
|
-
"aac",
|
|
10471
|
-
"-b:a",
|
|
10472
|
-
"192k",
|
|
10473
|
-
"-y",
|
|
10474
|
-
outputPath
|
|
10475
|
-
];
|
|
10616
|
+
function aacTruePeakCorrectionFilter(targetDurationSeconds, attenuationDb) {
|
|
10617
|
+
return `volume=${attenuationDb.toFixed(3)}dB,apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
|
|
10476
10618
|
}
|
|
10477
10619
|
function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
|
|
10478
10620
|
const delta = targetDurationSeconds - sourceDurationSeconds;
|
|
@@ -10487,53 +10629,13 @@ function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, tar
|
|
|
10487
10629
|
if (delta > 0) {
|
|
10488
10630
|
return {
|
|
10489
10631
|
operation: "pad",
|
|
10490
|
-
steps: [
|
|
10491
|
-
{
|
|
10492
|
-
kind: "normalize",
|
|
10493
|
-
args: [
|
|
10494
|
-
"-i",
|
|
10495
|
-
audioPath,
|
|
10496
|
-
"-af",
|
|
10497
|
-
`apad,atrim=0:${targetSec}`,
|
|
10498
|
-
"-t",
|
|
10499
|
-
targetSec,
|
|
10500
|
-
"-c:a",
|
|
10501
|
-
"aac",
|
|
10502
|
-
"-b:a",
|
|
10503
|
-
"192k",
|
|
10504
|
-
"-y",
|
|
10505
|
-
outputPath
|
|
10506
|
-
]
|
|
10507
|
-
}
|
|
10508
|
-
],
|
|
10632
|
+
steps: [{ kind: "normalize", filter: `apad,atrim=0:${targetSec}` }],
|
|
10509
10633
|
cleanupPaths: []
|
|
10510
10634
|
};
|
|
10511
10635
|
}
|
|
10512
10636
|
return {
|
|
10513
10637
|
operation: "trim",
|
|
10514
|
-
steps: [
|
|
10515
|
-
{
|
|
10516
|
-
kind: "trim",
|
|
10517
|
-
args: [
|
|
10518
|
-
"-i",
|
|
10519
|
-
audioPath,
|
|
10520
|
-
"-af",
|
|
10521
|
-
`atrim=duration=${targetSec},asetpts=PTS-STARTPTS`,
|
|
10522
|
-
// `atrim` limits decoded samples but does not cap muxer timestamps
|
|
10523
|
-
// introduced by encoder delay/flush. The output duration contract
|
|
10524
|
-
// is enforced at the container boundary as well; without `-t`, a
|
|
10525
|
-
// long primed source can retain its original tail after re-encode.
|
|
10526
|
-
"-t",
|
|
10527
|
-
targetSec,
|
|
10528
|
-
"-c:a",
|
|
10529
|
-
"aac",
|
|
10530
|
-
"-b:a",
|
|
10531
|
-
"192k",
|
|
10532
|
-
"-y",
|
|
10533
|
-
outputPath
|
|
10534
|
-
]
|
|
10535
|
-
}
|
|
10536
|
-
],
|
|
10638
|
+
steps: [{ kind: "trim", filter: `atrim=duration=${targetSec},asetpts=PTS-STARTPTS` }],
|
|
10537
10639
|
cleanupPaths: []
|
|
10538
10640
|
};
|
|
10539
10641
|
}
|
|
@@ -10550,7 +10652,8 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10550
10652
|
const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
|
|
10551
10653
|
const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
|
|
10552
10654
|
const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
|
|
10553
|
-
const
|
|
10655
|
+
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal);
|
|
10656
|
+
const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg || input.encodeAac ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
|
|
10554
10657
|
const [videoResult, audioResult] = await Promise.allSettled([
|
|
10555
10658
|
probeVideo(input.videoPath),
|
|
10556
10659
|
probeAudio(input.audioPath, input.signal)
|
|
@@ -10597,7 +10700,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10597
10700
|
);
|
|
10598
10701
|
try {
|
|
10599
10702
|
for (const step of plan2.steps) {
|
|
10600
|
-
const ffmpegResult = await runner(step.args);
|
|
10703
|
+
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(input.audioPath, input.outputPath, targetDurationSeconds, step.filter);
|
|
10601
10704
|
if (!ffmpegResult.success) {
|
|
10602
10705
|
return {
|
|
10603
10706
|
success: false,
|
|
@@ -10629,7 +10732,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10629
10732
|
targetDurationSeconds,
|
|
10630
10733
|
signal: input.signal,
|
|
10631
10734
|
probeTruePeak,
|
|
10632
|
-
|
|
10735
|
+
encode
|
|
10633
10736
|
});
|
|
10634
10737
|
if (!correction.success) {
|
|
10635
10738
|
return {
|
|
@@ -10659,12 +10762,8 @@ async function constrainAacTruePeak(input) {
|
|
|
10659
10762
|
audioPath: input.audioPath,
|
|
10660
10763
|
targetDurationSeconds: input.targetDurationSeconds,
|
|
10661
10764
|
signal: input.signal,
|
|
10662
|
-
probeTruePeak:
|
|
10663
|
-
|
|
10664
|
-
const segments = { durationSeconds: input.targetDurationSeconds, sampleRate };
|
|
10665
|
-
return measureTruePeakDbfs(audioPath, { timeoutMs, signal, segments });
|
|
10666
|
-
},
|
|
10667
|
-
runner: (args) => defaultRunFfmpeg(args, timeoutMs, input.signal)
|
|
10765
|
+
probeTruePeak: (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }),
|
|
10766
|
+
encode: defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
|
|
10668
10767
|
});
|
|
10669
10768
|
}
|
|
10670
10769
|
async function enforceAacTruePeak(input) {
|
|
@@ -10691,13 +10790,11 @@ async function enforceAacTruePeak(input) {
|
|
|
10691
10790
|
}
|
|
10692
10791
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
10693
10792
|
attenuationDb += correctionTargetDbfs - truePeakDbfs;
|
|
10694
|
-
const result = await input.
|
|
10695
|
-
|
|
10696
|
-
|
|
10697
|
-
|
|
10698
|
-
|
|
10699
|
-
attenuationDb
|
|
10700
|
-
)
|
|
10793
|
+
const result = await input.encode(
|
|
10794
|
+
input.audioPath,
|
|
10795
|
+
correctedPath,
|
|
10796
|
+
input.targetDurationSeconds,
|
|
10797
|
+
aacTruePeakCorrectionFilter(input.targetDurationSeconds, attenuationDb)
|
|
10701
10798
|
);
|
|
10702
10799
|
if (!result.success) {
|
|
10703
10800
|
return {
|
|
@@ -10792,7 +10889,7 @@ function parseFrameRate(rate) {
|
|
|
10792
10889
|
return { fpsNum, fpsDen };
|
|
10793
10890
|
}
|
|
10794
10891
|
async function defaultProbeAudioInfo(audioPath, signal) {
|
|
10795
|
-
const metadata = await
|
|
10892
|
+
const metadata = await extractAudioMetadata3(audioPath, { signal });
|
|
10796
10893
|
return {
|
|
10797
10894
|
durationSeconds: metadata.durationSeconds,
|
|
10798
10895
|
sampleRate: metadata.sampleRate,
|
|
@@ -10801,7 +10898,18 @@ async function defaultProbeAudioInfo(audioPath, signal) {
|
|
|
10801
10898
|
};
|
|
10802
10899
|
}
|
|
10803
10900
|
async function defaultRunFfmpeg(args, timeoutMs, signal) {
|
|
10804
|
-
|
|
10901
|
+
return toStepResult(await runFfmpeg5(args, { signal, timeout: timeoutMs }), timeoutMs);
|
|
10902
|
+
}
|
|
10903
|
+
function defaultAacEncoder(timeoutMs, signal, pcmSourcePath) {
|
|
10904
|
+
return async (inputPath, outputPath, durationSeconds, filter) => {
|
|
10905
|
+
const options = pcmSourcePath ? { signal, timeout: timeoutMs, filter } : { signal, timeout: timeoutMs, filter, segments: 1 };
|
|
10906
|
+
return toStepResult(
|
|
10907
|
+
await encodeAac(pcmSourcePath ?? inputPath, outputPath, durationSeconds, options),
|
|
10908
|
+
timeoutMs
|
|
10909
|
+
);
|
|
10910
|
+
};
|
|
10911
|
+
}
|
|
10912
|
+
function toStepResult(result, timeoutMs) {
|
|
10805
10913
|
if (result.success) return { success: true };
|
|
10806
10914
|
return {
|
|
10807
10915
|
success: false,
|
|
@@ -10989,7 +11097,7 @@ function assertOutputScaleSupported(deviceScaleFactor, route) {
|
|
|
10989
11097
|
const reason = UNSUPPORTED_REASON2[route];
|
|
10990
11098
|
if (reason === null) return;
|
|
10991
11099
|
throw new Error(
|
|
10992
|
-
`[Render] --resolution other than the composition's size is not supported on the "${route}" capture route: ${reason}. Render at the composition's size, or
|
|
11100
|
+
`[Render] --resolution other than the composition's size is not supported on the "${route}" capture route: ${reason}. Render at the composition's size; or, for HDR footage, add --sdr to render this size in standard colour; or, for shader transitions, render as mp4.`
|
|
10993
11101
|
);
|
|
10994
11102
|
}
|
|
10995
11103
|
|
|
@@ -11206,7 +11314,7 @@ function getNextRetryWorkerCount(currentWorkers) {
|
|
|
11206
11314
|
return Math.max(1, Math.floor(currentWorkers / 2));
|
|
11207
11315
|
}
|
|
11208
11316
|
function resolveRenderWorkDirPrefix(outputPath, jobId, platform2 = process.platform, systemTempDir = tmpdir2()) {
|
|
11209
|
-
if (platform2 === "win32") return join26(systemTempDir, "hf-render-");
|
|
11317
|
+
if (platform2 === "win32" || platform2 === "darwin") return join26(systemTempDir, "hf-render-");
|
|
11210
11318
|
return join26(dirname10(outputPath), `work-${jobId}-`);
|
|
11211
11319
|
}
|
|
11212
11320
|
var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
|
|
@@ -11700,6 +11808,9 @@ function resolveDeShortBand(args) {
|
|
|
11700
11808
|
function shouldPreferSingleWorkerDrawElement(args) {
|
|
11701
11809
|
return args.workerCount > 1 && typeof args.requestedWorkers !== "number" && args.useDrawElement && !args.deCompileGate && !args.forceScreenshot && outputUsesH264Pipeline(args.outputFormat) && args.minFrames > 0 && args.totalFrames >= args.minFrames && args.singleWorkerStreamingOk && !args.layeredOrEffectRoute && !args.supersampling && !args.probeDeGated && !args.experimentalParallelDeOptIn;
|
|
11702
11810
|
}
|
|
11811
|
+
function isProbeDeGated(session) {
|
|
11812
|
+
return session !== null && session.captureMode !== "drawelement" && !session.deInitDeferred;
|
|
11813
|
+
}
|
|
11703
11814
|
function resolveInversionRetryPlan(args) {
|
|
11704
11815
|
if (args.deWorkerInversion !== "inverted") return null;
|
|
11705
11816
|
const workerCount = args.isMemoryExhaustion ? 1 : args.preInversionWorkerCount;
|
|
@@ -11781,7 +11892,8 @@ async function closeOrphanedProbeForRetry(probe, closer, log, retryContext) {
|
|
|
11781
11892
|
}
|
|
11782
11893
|
function resolveParallelCaptureMode(args) {
|
|
11783
11894
|
const supersampling = (args.deviceScaleFactor ?? 1) !== 1;
|
|
11784
|
-
|
|
11895
|
+
const headlessShell = args.headlessShellPath !== void 0;
|
|
11896
|
+
return headlessShell && args.platform === "linux" && !args.forceScreenshot && !supersampling ? "beginframe" : "screenshot";
|
|
11785
11897
|
}
|
|
11786
11898
|
function isCaptureParallelStreamRouterEnabled(env, resolvedMode) {
|
|
11787
11899
|
const raw = env.HF_CAPTURE_PARALLEL_STREAM?.trim().toLowerCase();
|
|
@@ -12389,6 +12501,7 @@ async function executeRenderPipeline(input) {
|
|
|
12389
12501
|
);
|
|
12390
12502
|
const nativeHdrIds = /* @__PURE__ */ new Set([...nativeHdrVideoIds, ...nativeHdrImageIds]);
|
|
12391
12503
|
const hasHdrContent = Boolean(effectiveHdr && nativeHdrIds.size > 0);
|
|
12504
|
+
if (hasHdrContent) assertOutputScaleSupported(deviceScaleFactor, "hdr_layered");
|
|
12392
12505
|
const deSingleMinFramesRaw = process.env.HF_DE_SINGLE_MIN_FRAMES;
|
|
12393
12506
|
const deSingleMinFramesNum = deSingleMinFramesRaw === void 0 || deSingleMinFramesRaw.trim() === "" ? 900 : Number(deSingleMinFramesRaw);
|
|
12394
12507
|
const deSingleMinFrames = Number.isFinite(deSingleMinFramesNum) ? deSingleMinFramesNum : 900;
|
|
@@ -12429,7 +12542,7 @@ async function executeRenderPipeline(input) {
|
|
|
12429
12542
|
singleWorkerStreamingOk: shouldUseStreamingEncode(cfg, outputFormat, 1, job.duration),
|
|
12430
12543
|
layeredOrEffectRoute: hasHdrContent || compiled.hasShaderTransitions,
|
|
12431
12544
|
supersampling: deviceScaleFactor !== 1,
|
|
12432
|
-
probeDeGated: probeSession
|
|
12545
|
+
probeDeGated: isProbeDeGated(probeSession),
|
|
12433
12546
|
experimentalParallelDeOptIn: process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true" || // Verified parallel DE streaming (opt-in) wants its parallelism kept.
|
|
12434
12547
|
process.env.HF_DE_PARALLEL_STREAM === "true"
|
|
12435
12548
|
};
|
|
@@ -12438,14 +12551,14 @@ async function executeRenderPipeline(input) {
|
|
|
12438
12551
|
...deInversionArgs,
|
|
12439
12552
|
minFrames: Math.min(deSingleMinFrames, deShortBandMinFrames)
|
|
12440
12553
|
});
|
|
12441
|
-
|
|
12554
|
+
let deShortBand = resolveDeShortBand({
|
|
12442
12555
|
invertAtBaseFloor,
|
|
12443
12556
|
invertAtBandFloor,
|
|
12444
12557
|
bandEnabled: deShortBandEnabled,
|
|
12445
12558
|
bandOpen: deShortBandOpen,
|
|
12446
12559
|
elementCountSource: compositionElementCountSource
|
|
12447
12560
|
});
|
|
12448
|
-
|
|
12561
|
+
let deInversionEligible = deShortBandRoute && deShortBand === "applied" ? invertAtBandFloor : invertAtBaseFloor;
|
|
12449
12562
|
const deInversionEffectiveMinFrames = deShortBandRoute && deShortBand === "applied" ? Math.min(deSingleMinFrames, deShortBandMinFrames) : deSingleMinFrames;
|
|
12450
12563
|
const deParallelRouterEnabled = isDeParallelRouterEnabled(process.env);
|
|
12451
12564
|
const deParallelMinFramesRaw = process.env.HF_DE_PARALLEL_MIN_FRAMES;
|
|
@@ -12454,7 +12567,7 @@ async function executeRenderPipeline(input) {
|
|
|
12454
12567
|
const deParallelMinMemRaw = process.env.HF_DE_PARALLEL_MIN_MEM_MB;
|
|
12455
12568
|
const deParallelMinMemNum = deParallelMinMemRaw === void 0 || deParallelMinMemRaw.trim() === "" ? 24576 : Number(deParallelMinMemRaw);
|
|
12456
12569
|
const deParallelMinMemoryMb = Number.isFinite(deParallelMinMemNum) ? deParallelMinMemNum : 24576;
|
|
12457
|
-
|
|
12570
|
+
let deParallelRouterEligible = shouldPreferParallelDrawElement({
|
|
12458
12571
|
workerCount: WOULD_RESOLVE_MULTI_WORKER,
|
|
12459
12572
|
requestedWorkers: job.config.workers,
|
|
12460
12573
|
useDrawElement: cfg.useDrawElement,
|
|
@@ -12465,7 +12578,7 @@ async function executeRenderPipeline(input) {
|
|
|
12465
12578
|
minFrames: deParallelMinFrames,
|
|
12466
12579
|
layeredOrEffectRoute: hasHdrContent || compiled.hasShaderTransitions,
|
|
12467
12580
|
supersampling: deviceScaleFactor !== 1,
|
|
12468
|
-
probeDeGated: probeSession
|
|
12581
|
+
probeDeGated: isProbeDeGated(probeSession),
|
|
12469
12582
|
experimentalParallelDeOptIn: process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true" || process.env.HF_DE_PARALLEL_STREAM === "true",
|
|
12470
12583
|
routerEnabled: deParallelRouterEnabled,
|
|
12471
12584
|
// Router pins 3 workers for the streaming path; don't pin when a
|
|
@@ -12481,6 +12594,35 @@ async function executeRenderPipeline(input) {
|
|
|
12481
12594
|
totalMemoryMb: Math.round(totalmem2() / (1024 * 1024)),
|
|
12482
12595
|
minMemoryMb: deParallelMinMemoryMb
|
|
12483
12596
|
});
|
|
12597
|
+
if (probeSession === null && (deInversionEligible || deParallelRouterEligible)) {
|
|
12598
|
+
try {
|
|
12599
|
+
probeSession = await createCaptureSession8(
|
|
12600
|
+
activeFileServer.url,
|
|
12601
|
+
framesDir,
|
|
12602
|
+
buildCaptureOptions(),
|
|
12603
|
+
null,
|
|
12604
|
+
{ ...cfg, forceScreenshot: captureForceScreenshot }
|
|
12605
|
+
);
|
|
12606
|
+
await initializeSession8(probeSession);
|
|
12607
|
+
} catch (error) {
|
|
12608
|
+
assertNotAborted();
|
|
12609
|
+
if (probeSession) {
|
|
12610
|
+
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
12611
|
+
await closeCaptureSession8(probeSession).catch(() => {
|
|
12612
|
+
});
|
|
12613
|
+
probeSession = null;
|
|
12614
|
+
}
|
|
12615
|
+
log.warn("[Render] Fast capture gate check failed; routing without it.", {
|
|
12616
|
+
error: error instanceof Error ? error.message : String(error)
|
|
12617
|
+
});
|
|
12618
|
+
}
|
|
12619
|
+
if (isProbeDeGated(probeSession)) {
|
|
12620
|
+
deInversionEligible = false;
|
|
12621
|
+
deParallelRouterEligible = false;
|
|
12622
|
+
deShortBand = void 0;
|
|
12623
|
+
}
|
|
12624
|
+
}
|
|
12625
|
+
const deProbeGateReason = isProbeDeGated(probeSession) ? probeSession.deGateReason : void 0;
|
|
12484
12626
|
let workerCount;
|
|
12485
12627
|
const captureStageObservationData = (extra = {}) => ({
|
|
12486
12628
|
stagePhase: "capturing",
|
|
@@ -12668,7 +12810,7 @@ async function executeRenderPipeline(input) {
|
|
|
12668
12810
|
}
|
|
12669
12811
|
const parallelCaptureMode = resolveParallelCaptureMode({
|
|
12670
12812
|
platform: process.platform,
|
|
12671
|
-
|
|
12813
|
+
headlessShellPath: resolveHeadlessShellPath(cfg),
|
|
12672
12814
|
forceScreenshot: captureForceScreenshot,
|
|
12673
12815
|
deviceScaleFactor: captureOptions.deviceScaleFactor
|
|
12674
12816
|
});
|
|
@@ -13270,6 +13412,34 @@ async function executeRenderPipeline(input) {
|
|
|
13270
13412
|
throw new Error(`Disk capture requires sdr_disk plan; got ${capturePlan.kind}`);
|
|
13271
13413
|
}
|
|
13272
13414
|
resetCaptureAttemptProgress(job);
|
|
13415
|
+
const encodeInput = {
|
|
13416
|
+
job,
|
|
13417
|
+
log,
|
|
13418
|
+
outputPath: stagedOutputPath,
|
|
13419
|
+
framesDir,
|
|
13420
|
+
videoOnlyPath,
|
|
13421
|
+
width,
|
|
13422
|
+
height,
|
|
13423
|
+
needsAlpha,
|
|
13424
|
+
captureImageFormat: captureOptions.format ?? "jpeg",
|
|
13425
|
+
hasAudio,
|
|
13426
|
+
audioOutputPath,
|
|
13427
|
+
isPngSequence,
|
|
13428
|
+
isGif,
|
|
13429
|
+
preset,
|
|
13430
|
+
effectiveQuality,
|
|
13431
|
+
effectiveBitrate,
|
|
13432
|
+
enableChunkedEncode,
|
|
13433
|
+
chunkedEncodeSize,
|
|
13434
|
+
engineConfig: cfg,
|
|
13435
|
+
// Same value the streaming encoder above spreads — the disk and
|
|
13436
|
+
// chunked-concat encoders need an identical GOP lock for HLS.
|
|
13437
|
+
...hlsEncoderGopLock,
|
|
13438
|
+
abortSignal: executionSignal,
|
|
13439
|
+
assertNotAborted,
|
|
13440
|
+
onProgress
|
|
13441
|
+
};
|
|
13442
|
+
const overlap = !cfg.useDrawElement && workerSizing !== void 0 && canEncodeWhileCapturing(encodeInput, capturePlan.workerCount, workerSizing.cpuCount) ? encodeWhileCapturing(encodeInput, totalFrames) : null;
|
|
13273
13443
|
const captureFrameStart = Date.now();
|
|
13274
13444
|
const invokeDiskCapture = (diskPlan) => observeRenderStage(
|
|
13275
13445
|
observability,
|
|
@@ -13291,13 +13461,15 @@ async function executeRenderPipeline(input) {
|
|
|
13291
13461
|
createRenderVideoFrameInjector,
|
|
13292
13462
|
abortSignal: executionSignal,
|
|
13293
13463
|
assertNotAborted,
|
|
13294
|
-
onProgress
|
|
13464
|
+
onProgress,
|
|
13465
|
+
onFrameLanded: (frame) => overlap?.onFrameLanded(frame)
|
|
13295
13466
|
})
|
|
13296
13467
|
);
|
|
13297
13468
|
let captureRes;
|
|
13298
13469
|
try {
|
|
13299
13470
|
captureRes = await invokeDiskCapture(capturePlan);
|
|
13300
13471
|
} catch (err) {
|
|
13472
|
+
await overlap?.abort();
|
|
13301
13473
|
if (!isDrawElementVerificationError(err) && !isDrawElementCaptureError(err) || err instanceof RenderCancelledError || executionSignal?.aborted === true) {
|
|
13302
13474
|
throw err;
|
|
13303
13475
|
}
|
|
@@ -13366,33 +13538,7 @@ async function executeRenderPipeline(input) {
|
|
|
13366
13538
|
isGif,
|
|
13367
13539
|
chunkedEncode: enableChunkedEncode
|
|
13368
13540
|
}),
|
|
13369
|
-
() => runEncodeStage(
|
|
13370
|
-
job,
|
|
13371
|
-
log,
|
|
13372
|
-
outputPath: stagedOutputPath,
|
|
13373
|
-
framesDir,
|
|
13374
|
-
videoOnlyPath,
|
|
13375
|
-
width,
|
|
13376
|
-
height,
|
|
13377
|
-
needsAlpha,
|
|
13378
|
-
captureImageFormat: captureOptions.format ?? "jpeg",
|
|
13379
|
-
hasAudio,
|
|
13380
|
-
audioOutputPath,
|
|
13381
|
-
isPngSequence,
|
|
13382
|
-
isGif,
|
|
13383
|
-
preset,
|
|
13384
|
-
effectiveQuality,
|
|
13385
|
-
effectiveBitrate,
|
|
13386
|
-
enableChunkedEncode,
|
|
13387
|
-
chunkedEncodeSize,
|
|
13388
|
-
engineConfig: cfg,
|
|
13389
|
-
// Same value the streaming encoder above spreads — the disk and
|
|
13390
|
-
// chunked-concat encoders need an identical GOP lock for HLS.
|
|
13391
|
-
...hlsEncoderGopLock,
|
|
13392
|
-
abortSignal: executionSignal,
|
|
13393
|
-
assertNotAborted,
|
|
13394
|
-
onProgress
|
|
13395
|
-
})
|
|
13541
|
+
() => overlap ? overlap.finish() : runEncodeStage(encodeInput)
|
|
13396
13542
|
);
|
|
13397
13543
|
perfStages.encodeMs = encodeRes.encodeMs;
|
|
13398
13544
|
}
|
|
@@ -13440,7 +13586,9 @@ async function executeRenderPipeline(input) {
|
|
|
13440
13586
|
outputFormat,
|
|
13441
13587
|
durationSeconds: job.duration,
|
|
13442
13588
|
fps: fpsToNumber9(job.config.fps),
|
|
13443
|
-
expectedFrames: captureTotalFrames
|
|
13589
|
+
expectedFrames: captureTotalFrames,
|
|
13590
|
+
width: outputWidth,
|
|
13591
|
+
height: outputHeight
|
|
13444
13592
|
})
|
|
13445
13593
|
);
|
|
13446
13594
|
const totalElapsed = Date.now() - pipelineStart;
|
|
@@ -13476,6 +13624,7 @@ async function executeRenderPipeline(input) {
|
|
|
13476
13624
|
drawElement: {
|
|
13477
13625
|
compileGate: deCompileGate,
|
|
13478
13626
|
clampReason: deClampReason,
|
|
13627
|
+
probeGateReason: deProbeGateReason,
|
|
13479
13628
|
workerInversion: deWorkerInversion,
|
|
13480
13629
|
preInversionWorkers: deWorkerInversion ? preRoutingWorkerCount : void 0,
|
|
13481
13630
|
compositionElementCount,
|
|
@@ -13673,9 +13822,9 @@ import {
|
|
|
13673
13822
|
import { join as join29 } from "node:path";
|
|
13674
13823
|
import {
|
|
13675
13824
|
closeCaptureSession as closeCaptureSession9,
|
|
13676
|
-
createCaptureSession as
|
|
13825
|
+
createCaptureSession as createCaptureSession9,
|
|
13677
13826
|
createFrameLookupTable as createFrameLookupTable3,
|
|
13678
|
-
initializeSession as
|
|
13827
|
+
initializeSession as initializeSession9
|
|
13679
13828
|
} from "@hyperframes/engine";
|
|
13680
13829
|
|
|
13681
13830
|
// src/services/distributed/chunkFrames.ts
|
|
@@ -14073,6 +14222,29 @@ function buildChunkCaptureOptions(input) {
|
|
|
14073
14222
|
requiresWebGpu: compositionRequiresWebGpu3(input.html)
|
|
14074
14223
|
};
|
|
14075
14224
|
}
|
|
14225
|
+
function shownDeferredFrameIndexes(planVideos, frames, fps) {
|
|
14226
|
+
const shown = /* @__PURE__ */ new Map();
|
|
14227
|
+
const deferred = planVideos.extracted.filter((video) => video.deferredRange);
|
|
14228
|
+
if (deferred.length === 0) return shown;
|
|
14229
|
+
const table = createFrameLookupTable2(
|
|
14230
|
+
planVideos.videos,
|
|
14231
|
+
deferred.map((video) => ({
|
|
14232
|
+
...video,
|
|
14233
|
+
outputDir: "",
|
|
14234
|
+
framePaths: new Map(Array.from({ length: video.totalFrames }, (_, i) => [i, String(i)]))
|
|
14235
|
+
})),
|
|
14236
|
+
resolveRenderFpsConfig(fps).value
|
|
14237
|
+
);
|
|
14238
|
+
for (const frame of frames) {
|
|
14239
|
+
const globalTime = frame * fps.den / fps.num;
|
|
14240
|
+
for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
|
|
14241
|
+
const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
|
|
14242
|
+
indexes.add(payload.frameIndex);
|
|
14243
|
+
shown.set(videoId, indexes);
|
|
14244
|
+
}
|
|
14245
|
+
}
|
|
14246
|
+
return shown;
|
|
14247
|
+
}
|
|
14076
14248
|
async function extractDeferredVideoFramesForChunk(input) {
|
|
14077
14249
|
const deferred = input.planVideos.extracted.filter((video) => video.deferredRange);
|
|
14078
14250
|
const remoteSources = input.remoteSources ?? {};
|
|
@@ -14089,24 +14261,7 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
14089
14261
|
});
|
|
14090
14262
|
}
|
|
14091
14263
|
if (deferred.length === 0) return [];
|
|
14092
|
-
const
|
|
14093
|
-
input.planVideos.videos,
|
|
14094
|
-
deferred.map((video) => ({
|
|
14095
|
-
...video,
|
|
14096
|
-
outputDir: input.outputRoot,
|
|
14097
|
-
framePaths: new Map(Array.from({ length: video.totalFrames }, (_, i) => [i, String(i)]))
|
|
14098
|
-
})),
|
|
14099
|
-
resolveRenderFpsConfig(input.fps).value
|
|
14100
|
-
);
|
|
14101
|
-
const shown = /* @__PURE__ */ new Map();
|
|
14102
|
-
for (const frame of input.frames) {
|
|
14103
|
-
const globalTime = frame * input.fps.den / input.fps.num;
|
|
14104
|
-
for (const [videoId, payload] of table.getActiveFramePayloads(globalTime)) {
|
|
14105
|
-
const indexes = shown.get(videoId) ?? /* @__PURE__ */ new Set();
|
|
14106
|
-
indexes.add(payload.frameIndex);
|
|
14107
|
-
shown.set(videoId, indexes);
|
|
14108
|
-
}
|
|
14109
|
-
}
|
|
14264
|
+
const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
|
|
14110
14265
|
return Promise.all(
|
|
14111
14266
|
deferred.map(async (video) => {
|
|
14112
14267
|
const outputDir = join28(input.outputRoot, video.videoId);
|
|
@@ -14132,7 +14287,8 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
14132
14287
|
fps: input.fps,
|
|
14133
14288
|
outputDir: input.outputRoot,
|
|
14134
14289
|
format: range.format,
|
|
14135
|
-
frameRanges: contiguousRuns(indexes)
|
|
14290
|
+
frameRanges: contiguousRuns(indexes),
|
|
14291
|
+
shownBox: input.shownBoxes?.get(video.videoId)
|
|
14136
14292
|
},
|
|
14137
14293
|
void 0,
|
|
14138
14294
|
input.cfg
|
|
@@ -14179,7 +14335,7 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
14179
14335
|
});
|
|
14180
14336
|
let session = null;
|
|
14181
14337
|
try {
|
|
14182
|
-
session = await
|
|
14338
|
+
session = await createCaptureSession9(
|
|
14183
14339
|
fileServer.url,
|
|
14184
14340
|
join29(workDir, "frame-cost"),
|
|
14185
14341
|
buildChunkCaptureOptions({
|
|
@@ -14191,7 +14347,7 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
14191
14347
|
createChunkVideoFrameInjectorFactory(lookup)(),
|
|
14192
14348
|
createCaptureCalibrationConfig(input.cfg)
|
|
14193
14349
|
);
|
|
14194
|
-
await
|
|
14350
|
+
await initializeSession9(session);
|
|
14195
14351
|
const { estimate } = await measureCaptureCostFromSession(session, input.totalFrames, fps, log);
|
|
14196
14352
|
return (estimate.p95Ms ?? 0) / 1e3;
|
|
14197
14353
|
} finally {
|
|
@@ -14785,6 +14941,20 @@ function withDuration(buf, target, duration) {
|
|
|
14785
14941
|
function timescaleOf(buf, header) {
|
|
14786
14942
|
return buf.readUInt32BE(header.bodyStart + (buf[header.bodyStart] === 1 ? 20 : 12));
|
|
14787
14943
|
}
|
|
14944
|
+
var TRACK_ID_AT = { tkhd: [12, 20], mvhd: [96, 108] };
|
|
14945
|
+
function idOf(buf, target) {
|
|
14946
|
+
return buf.readUInt32BE(
|
|
14947
|
+
target.bodyStart + TRACK_ID_AT[target.type][buf[target.bodyStart] === 1 ? 1 : 0]
|
|
14948
|
+
);
|
|
14949
|
+
}
|
|
14950
|
+
function withId(boxBytes, type, id) {
|
|
14951
|
+
const header = boxAt(boxBytes, 0, 0, boxBytes.length);
|
|
14952
|
+
boxBytes.writeUInt32BE(
|
|
14953
|
+
id,
|
|
14954
|
+
header.bodyStart + TRACK_ID_AT[type][boxBytes[header.bodyStart] === 1 ? 1 : 0]
|
|
14955
|
+
);
|
|
14956
|
+
return boxBytes;
|
|
14957
|
+
}
|
|
14788
14958
|
function buildStbl(stsd, track) {
|
|
14789
14959
|
const stts = runs(track.durations);
|
|
14790
14960
|
const stsc = [];
|
|
@@ -14826,24 +14996,48 @@ function planTracksOf(tracks, plan2, index) {
|
|
|
14826
14996
|
}
|
|
14827
14997
|
return matched;
|
|
14828
14998
|
}
|
|
14829
|
-
function
|
|
14830
|
-
const
|
|
14831
|
-
const
|
|
14999
|
+
function sourceTracks(layouts, audio) {
|
|
15000
|
+
const chunkPlan = readTracks(layouts[0].moov);
|
|
15001
|
+
const audioTracks = audio ? readTracks(audio.moov) : [];
|
|
15002
|
+
if (audio && (audioTracks.length !== 1 || audioTracks[0].handler !== "soun")) {
|
|
15003
|
+
throw new Error("[mp4Stitch] the separate audio file must hold one audio track");
|
|
15004
|
+
}
|
|
15005
|
+
const plan2 = [...chunkPlan, ...audioTracks];
|
|
15006
|
+
const chunks = layouts.map((layout, i) => {
|
|
15007
|
+
const tracks = readTracks(layout.moov);
|
|
15008
|
+
if (audio && tracks.some((track) => track.handler === "soun")) {
|
|
15009
|
+
throw new Error(`[mp4Stitch] chunk ${i} carries its own audio`);
|
|
15010
|
+
}
|
|
15011
|
+
return planTracksOf(tracks, plan2, i);
|
|
15012
|
+
});
|
|
15013
|
+
return {
|
|
15014
|
+
plan: plan2,
|
|
15015
|
+
chunkTrackCount: chunkPlan.length,
|
|
15016
|
+
sources: audio ? [[...chunkPlan.map(() => null), audioTracks[0]], ...chunks] : chunks
|
|
15017
|
+
};
|
|
15018
|
+
}
|
|
15019
|
+
function buildStitchedHeader(layouts, audio) {
|
|
15020
|
+
const { plan: plan2, chunkTrackCount, sources } = sourceTracks(layouts, audio);
|
|
15021
|
+
const sourceLayouts = audio ? [audio, ...layouts] : layouts;
|
|
14832
15022
|
const payloadBases = [];
|
|
14833
15023
|
let payloadBytes = 0;
|
|
14834
|
-
for (const layout of
|
|
15024
|
+
for (const layout of sourceLayouts) {
|
|
14835
15025
|
payloadBases.push(payloadBytes);
|
|
14836
15026
|
payloadBytes += layout.payloadEnd - layout.payloadStart;
|
|
14837
15027
|
}
|
|
14838
15028
|
const template = layouts[0].moov;
|
|
14839
15029
|
const root = boxAt(template, 0, 0, template.length);
|
|
14840
|
-
const
|
|
15030
|
+
const mvhd = child(template, root, "mvhd");
|
|
15031
|
+
const movieTimescale = timescaleOf(template, mvhd);
|
|
15032
|
+
const chunkTrackIds = plan2.slice(0, chunkTrackCount).map((track) => idOf(template, child(template, track.trak, "tkhd")));
|
|
15033
|
+
const audioTrackId = Math.max(...chunkTrackIds) + 1;
|
|
14841
15034
|
const buildMoov = (dataStart2) => {
|
|
14842
15035
|
const traks = plan2.map((track, t) => {
|
|
14843
|
-
const parts =
|
|
15036
|
+
const parts = sources.flatMap((tracks, chunk) => {
|
|
14844
15037
|
const part = tracks[t];
|
|
14845
15038
|
return part ? [{ ...part, chunk }] : [];
|
|
14846
15039
|
});
|
|
15040
|
+
const moov = t < chunkTrackCount ? template : audio.moov;
|
|
14847
15041
|
const merged = {
|
|
14848
15042
|
durations: parts.flatMap((part) => part.durations),
|
|
14849
15043
|
sizes: parts.flatMap((part) => part.sizes),
|
|
@@ -14852,9 +15046,9 @@ function buildStitchedHeader(layouts) {
|
|
|
14852
15046
|
chunkOffsets: parts.flatMap(
|
|
14853
15047
|
(part) => part.chunkOffsets.map((offset) => {
|
|
14854
15048
|
const i = part.chunk;
|
|
14855
|
-
const { payloadStart, payloadEnd } =
|
|
15049
|
+
const { payloadStart, payloadEnd } = sourceLayouts[i];
|
|
14856
15050
|
if (offset < payloadStart || offset >= payloadEnd) {
|
|
14857
|
-
throw new Error(`[mp4Stitch]
|
|
15051
|
+
throw new Error(`[mp4Stitch] source ${i} has samples outside its mdat`);
|
|
14858
15052
|
}
|
|
14859
15053
|
return offset - payloadStart + payloadBases[i] + dataStart2;
|
|
14860
15054
|
})
|
|
@@ -14870,20 +15064,25 @@ function buildStitchedHeader(layouts) {
|
|
|
14870
15064
|
elst.writeBigInt64BE(BigInt(track.mediaTime), 12);
|
|
14871
15065
|
elst.writeUInt32BE(65536, 20);
|
|
14872
15066
|
const edts = track.mediaTime ? box("edts", fullBox("elst", 1, elst)) : Buffer.alloc(0);
|
|
14873
|
-
const
|
|
14874
|
-
|
|
15067
|
+
const tkhdOf = (tkhd) => {
|
|
15068
|
+
const timed = withDuration(moov, tkhd, shown);
|
|
15069
|
+
return t < chunkTrackCount ? timed : withId(timed, "tkhd", audioTrackId);
|
|
15070
|
+
};
|
|
15071
|
+
const trak = rebuild(moov, track.trak, {
|
|
15072
|
+
tkhd: (tkhd) => Buffer.concat([tkhdOf(tkhd), edts]),
|
|
14875
15073
|
edts: () => Buffer.alloc(0),
|
|
14876
|
-
mdia: (mdia) => rebuild(
|
|
14877
|
-
mdhd: (mdhd) => withDuration(
|
|
14878
|
-
minf: (minf) => rebuild(
|
|
15074
|
+
mdia: (mdia) => rebuild(moov, mdia, {
|
|
15075
|
+
mdhd: (mdhd) => withDuration(moov, mdhd, mediaDuration),
|
|
15076
|
+
minf: (minf) => rebuild(moov, minf, { stbl: () => buildStbl(track.stsd, merged) })
|
|
14879
15077
|
})
|
|
14880
15078
|
});
|
|
14881
15079
|
return { trak, shown };
|
|
14882
15080
|
});
|
|
14883
15081
|
let next = 0;
|
|
15082
|
+
const movieHeader = withDuration(template, mvhd, Math.max(...traks.map((t) => t.shown)));
|
|
14884
15083
|
return rebuild(template, root, {
|
|
14885
|
-
mvhd: (
|
|
14886
|
-
trak: () => traks[next
|
|
15084
|
+
mvhd: () => audio ? withId(movieHeader, "mvhd", audioTrackId + 1) : movieHeader,
|
|
15085
|
+
trak: () => ++next < chunkTrackCount ? traks[next - 1].trak : Buffer.concat(traks.slice(next - 1).map((t) => t.trak))
|
|
14887
15086
|
});
|
|
14888
15087
|
};
|
|
14889
15088
|
const ftyp = layouts[0].ftyp;
|
|
@@ -14916,15 +15115,16 @@ async function copyPayload(path, layout, index, out) {
|
|
|
14916
15115
|
);
|
|
14917
15116
|
}
|
|
14918
15117
|
}
|
|
14919
|
-
async function writeStitchedMp4(chunkPaths, outputPath) {
|
|
14920
|
-
const
|
|
14921
|
-
const
|
|
15118
|
+
async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
15119
|
+
const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
|
|
15120
|
+
const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
|
|
15121
|
+
const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
|
|
14922
15122
|
mkdirSync11(dirname12(outputPath), { recursive: true });
|
|
14923
15123
|
const workDir = mkdtempSync4(`${outputPath}.stitch-`);
|
|
14924
15124
|
const out = createWriteStream(join31(workDir, "out.mp4"));
|
|
14925
15125
|
try {
|
|
14926
15126
|
out.write(header);
|
|
14927
|
-
for (const [i, path] of
|
|
15127
|
+
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
14928
15128
|
out.end();
|
|
14929
15129
|
await finished(out);
|
|
14930
15130
|
renameSync5(join31(workDir, "out.mp4"), outputPath);
|
|
@@ -15495,7 +15695,8 @@ function buildLockedRenderConfig(input) {
|
|
|
15495
15695
|
chunkCount: input.chunkCount,
|
|
15496
15696
|
runtimeEnv: input.runtimeEnv,
|
|
15497
15697
|
variables: config.variables,
|
|
15498
|
-
...input.chunksCarryAudio ? { chunksCarryAudio: true } : {}
|
|
15698
|
+
...input.chunksCarryAudio ? { chunksCarryAudio: true } : {},
|
|
15699
|
+
...input.separateAudio ? { separateAudio: true } : {}
|
|
15499
15700
|
};
|
|
15500
15701
|
}
|
|
15501
15702
|
function carriesChunkAudio(config, options) {
|
|
@@ -15590,8 +15791,10 @@ function createVideoSourceShipper(input) {
|
|
|
15590
15791
|
return result;
|
|
15591
15792
|
};
|
|
15592
15793
|
}
|
|
15593
|
-
|
|
15594
|
-
|
|
15794
|
+
function plansSeparateAudio(config, options) {
|
|
15795
|
+
return options.deferAudio === true && config.format === "mp4" && config.cfr !== true;
|
|
15796
|
+
}
|
|
15797
|
+
async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
15595
15798
|
rejectUnsupportedDistributedFormat(config);
|
|
15596
15799
|
validateNoGpuEncode({
|
|
15597
15800
|
useGpu: false,
|
|
@@ -15727,6 +15930,100 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
15727
15930
|
});
|
|
15728
15931
|
}
|
|
15729
15932
|
}
|
|
15933
|
+
return {
|
|
15934
|
+
projectDir,
|
|
15935
|
+
log,
|
|
15936
|
+
sizeLimitBytes,
|
|
15937
|
+
abortSignal,
|
|
15938
|
+
assertNotAborted,
|
|
15939
|
+
cfg,
|
|
15940
|
+
job,
|
|
15941
|
+
workDir,
|
|
15942
|
+
compiledDir,
|
|
15943
|
+
finalCompiledDir,
|
|
15944
|
+
remoteFiles,
|
|
15945
|
+
urlByAbsentLocalPath,
|
|
15946
|
+
localPathByUrl,
|
|
15947
|
+
compileResult,
|
|
15948
|
+
compiled,
|
|
15949
|
+
composition,
|
|
15950
|
+
deviceScaleFactor,
|
|
15951
|
+
forceScreenshot,
|
|
15952
|
+
width,
|
|
15953
|
+
height,
|
|
15954
|
+
probeResult,
|
|
15955
|
+
totalFrames
|
|
15956
|
+
};
|
|
15957
|
+
}
|
|
15958
|
+
function mixPlanAudio(c, compiledDir, seconds, pcmPath) {
|
|
15959
|
+
return runAudioStage({
|
|
15960
|
+
projectDir: c.projectDir,
|
|
15961
|
+
workDir: c.workDir,
|
|
15962
|
+
compiledDir,
|
|
15963
|
+
urlByAbsentLocalPath: c.urlByAbsentLocalPath,
|
|
15964
|
+
duration: seconds,
|
|
15965
|
+
ffmpegProcessTimeout: c.cfg.ffmpegProcessTimeout,
|
|
15966
|
+
audioGain: c.cfg.audioGain,
|
|
15967
|
+
audios: c.composition.audios,
|
|
15968
|
+
masterFxChain: c.composition.masterFxChain,
|
|
15969
|
+
...pcmPath ? { keepEncoderInputAt: pcmPath } : {},
|
|
15970
|
+
abortSignal: c.abortSignal,
|
|
15971
|
+
assertNotAborted: c.assertNotAborted
|
|
15972
|
+
});
|
|
15973
|
+
}
|
|
15974
|
+
async function extractPlanVideos(c, deferRangeExtraction) {
|
|
15975
|
+
const extractResult = await runExtractVideosStage({
|
|
15976
|
+
projectDir: c.projectDir,
|
|
15977
|
+
compiledDir: c.compiledDir,
|
|
15978
|
+
job: c.job,
|
|
15979
|
+
cfg: c.cfg,
|
|
15980
|
+
composition: c.composition,
|
|
15981
|
+
abortSignal: c.abortSignal,
|
|
15982
|
+
assertNotAborted: c.assertNotAborted,
|
|
15983
|
+
materializeSymlinks: true,
|
|
15984
|
+
deferRangeExtraction,
|
|
15985
|
+
urlByAbsentLocalPath: c.urlByAbsentLocalPath
|
|
15986
|
+
});
|
|
15987
|
+
if (extractResult.failureToEnforce) throw extractResult.failureToEnforce;
|
|
15988
|
+
if (extractResult.extractionResult) {
|
|
15989
|
+
assertVideoExtractionSucceeded(extractResult.extractionResult);
|
|
15990
|
+
}
|
|
15991
|
+
return extractResult;
|
|
15992
|
+
}
|
|
15993
|
+
async function holdPlanAudioPeak(c, audioPath, pcmPath, seconds) {
|
|
15994
|
+
const peak = await constrainAacTruePeak({
|
|
15995
|
+
audioPath,
|
|
15996
|
+
...existsSync21(pcmPath) ? { sourcePath: pcmPath } : {},
|
|
15997
|
+
targetDurationSeconds: seconds,
|
|
15998
|
+
signal: c.abortSignal,
|
|
15999
|
+
timeoutMs: c.cfg.ffmpegProcessTimeout
|
|
16000
|
+
}).finally(() => rmSync11(pcmPath, { force: true }));
|
|
16001
|
+
if (!peak.success) throw encoderFailureError("[plan] audio true-peak check failed", peak);
|
|
16002
|
+
}
|
|
16003
|
+
async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, options = {}) {
|
|
16004
|
+
const planDir = executionPlanDir;
|
|
16005
|
+
const c = await compilePlanComposition(projectDir, config, planDir, options);
|
|
16006
|
+
const {
|
|
16007
|
+
log,
|
|
16008
|
+
sizeLimitBytes,
|
|
16009
|
+
cfg,
|
|
16010
|
+
job,
|
|
16011
|
+
workDir,
|
|
16012
|
+
compiledDir,
|
|
16013
|
+
finalCompiledDir,
|
|
16014
|
+
remoteFiles,
|
|
16015
|
+
urlByAbsentLocalPath,
|
|
16016
|
+
localPathByUrl,
|
|
16017
|
+
compileResult,
|
|
16018
|
+
compiled,
|
|
16019
|
+
composition,
|
|
16020
|
+
deviceScaleFactor,
|
|
16021
|
+
forceScreenshot,
|
|
16022
|
+
width,
|
|
16023
|
+
height,
|
|
16024
|
+
probeResult,
|
|
16025
|
+
totalFrames
|
|
16026
|
+
} = c;
|
|
15730
16027
|
const megapixels = compileResult.outputWidth * compileResult.outputHeight / 1e6;
|
|
15731
16028
|
let maxParallel = resolveMaxParallelChunks(config, totalFrames, megapixels, log);
|
|
15732
16029
|
const measureFrameCost = chunkSizingUsesFrameCost(config) && planNeedsFrameCostMeasurement(compiled.html);
|
|
@@ -15737,24 +16034,9 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
15737
16034
|
observedAt: "pre-extract",
|
|
15738
16035
|
log
|
|
15739
16036
|
});
|
|
15740
|
-
stageStarted = Date.now();
|
|
15741
|
-
const extractResult = await
|
|
15742
|
-
projectDir,
|
|
15743
|
-
compiledDir,
|
|
15744
|
-
job,
|
|
15745
|
-
cfg,
|
|
15746
|
-
composition,
|
|
15747
|
-
abortSignal,
|
|
15748
|
-
assertNotAborted,
|
|
15749
|
-
materializeSymlinks: true,
|
|
15750
|
-
deferRangeExtraction: options.deferVideoExtraction,
|
|
15751
|
-
urlByAbsentLocalPath
|
|
15752
|
-
});
|
|
16037
|
+
let stageStarted = Date.now();
|
|
16038
|
+
const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
|
|
15753
16039
|
logPlanStage(log, "extract", stageStarted);
|
|
15754
|
-
if (extractResult.failureToEnforce) throw extractResult.failureToEnforce;
|
|
15755
|
-
if (extractResult.extractionResult) {
|
|
15756
|
-
assertVideoExtractionSucceeded(extractResult.extractionResult);
|
|
15757
|
-
}
|
|
15758
16040
|
const stagedVideoFrames = join34(compiledDir, "__hyperframes_video_frames");
|
|
15759
16041
|
const videoFramesDst = join34(planDir, "video-frames");
|
|
15760
16042
|
if (existsSync21(videoFramesDst)) rmSync11(videoFramesDst, { recursive: true, force: true });
|
|
@@ -15810,23 +16092,18 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
15810
16092
|
"utf-8"
|
|
15811
16093
|
);
|
|
15812
16094
|
const digests = options.digests ?? /* @__PURE__ */ new Map();
|
|
15813
|
-
const
|
|
15814
|
-
const
|
|
16095
|
+
const separateAudio = plansSeparateAudio(config, options);
|
|
16096
|
+
const chunksCarryAudio = !separateAudio && carriesChunkAudio(config, options);
|
|
16097
|
+
const audioSeconds = chunksCarryAudio ? totalFrames / config.fps : probeResult.duration;
|
|
16098
|
+
const planMixPcmPath = join34(workDir, "plan-mix.wav");
|
|
15815
16099
|
stageStarted = Date.now();
|
|
15816
16100
|
const [audioOutcome, overlapOutcome, frameCostOutcome] = await Promise.allSettled([
|
|
15817
|
-
|
|
15818
|
-
|
|
15819
|
-
|
|
15820
|
-
|
|
15821
|
-
|
|
15822
|
-
|
|
15823
|
-
ffmpegProcessTimeout: cfg.ffmpegProcessTimeout,
|
|
15824
|
-
audioGain: cfg.audioGain,
|
|
15825
|
-
audios: composition.audios,
|
|
15826
|
-
masterFxChain: composition.masterFxChain,
|
|
15827
|
-
abortSignal,
|
|
15828
|
-
assertNotAborted
|
|
15829
|
-
}).finally(() => logPlanStage(log, "audio", stageStarted)),
|
|
16101
|
+
separateAudio ? null : mixPlanAudio(
|
|
16102
|
+
c,
|
|
16103
|
+
finalCompiledDir,
|
|
16104
|
+
audioSeconds,
|
|
16105
|
+
chunksCarryAudio ? planMixPcmPath : void 0
|
|
16106
|
+
).finally(() => logPlanStage(log, "audio", stageStarted)),
|
|
15830
16107
|
(async () => {
|
|
15831
16108
|
for (const dir of [finalCompiledDir, videoFramesDst, videoSourcesDir]) {
|
|
15832
16109
|
await prehashPlanFiles(dir, digests);
|
|
@@ -15876,22 +16153,16 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
15876
16153
|
);
|
|
15877
16154
|
const chunks = buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize);
|
|
15878
16155
|
const audioResult = audioOutcome.value;
|
|
15879
|
-
if (audioResult
|
|
16156
|
+
if (audioResult?.audioError) {
|
|
15880
16157
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
15881
16158
|
}
|
|
15882
16159
|
const planAudioPath = join34(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
15883
|
-
if (audioResult
|
|
16160
|
+
if (audioResult?.hasAudio && existsSync21(audioResult.audioOutputPath)) {
|
|
15884
16161
|
renameSync6(audioResult.audioOutputPath, planAudioPath);
|
|
15885
16162
|
}
|
|
15886
|
-
if (
|
|
16163
|
+
if (chunksCarryAudio && existsSync21(planAudioPath)) {
|
|
15887
16164
|
stageStarted = Date.now();
|
|
15888
|
-
|
|
15889
|
-
audioPath: planAudioPath,
|
|
15890
|
-
targetDurationSeconds: audioSeconds,
|
|
15891
|
-
signal: abortSignal,
|
|
15892
|
-
timeoutMs: cfg.ffmpegProcessTimeout
|
|
15893
|
-
});
|
|
15894
|
-
if (!peak.success) throw encoderFailureError("[plan] audio true-peak check failed", peak);
|
|
16165
|
+
await holdPlanAudioPeak(c, planAudioPath, planMixPcmPath, audioSeconds);
|
|
15895
16166
|
logPlanStage(log, "audio-peak", stageStarted);
|
|
15896
16167
|
if (options.sliceChunkAudio) {
|
|
15897
16168
|
stageStarted = Date.now();
|
|
@@ -15917,7 +16188,8 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
15917
16188
|
effectiveChunkSize,
|
|
15918
16189
|
chunkCount,
|
|
15919
16190
|
runtimeEnv,
|
|
15920
|
-
chunksCarryAudio
|
|
16191
|
+
chunksCarryAudio,
|
|
16192
|
+
separateAudio
|
|
15921
16193
|
});
|
|
15922
16194
|
const compositionJson = {
|
|
15923
16195
|
durationSeconds: job.duration ?? 0,
|
|
@@ -15962,7 +16234,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
15962
16234
|
fontSnapshotSha,
|
|
15963
16235
|
durationSeconds: job.duration ?? 0,
|
|
15964
16236
|
totalFrames,
|
|
15965
|
-
hasAudio: audioResult
|
|
16237
|
+
hasAudio: audioResult?.hasAudio ?? false,
|
|
15966
16238
|
digests
|
|
15967
16239
|
});
|
|
15968
16240
|
logPlanStage(log, "freeze", stageStarted);
|
|
@@ -16478,8 +16750,8 @@ function renderRequestFromDistributedConfig(input) {
|
|
|
16478
16750
|
|
|
16479
16751
|
// src/services/frameCapture.ts
|
|
16480
16752
|
import {
|
|
16481
|
-
createCaptureSession as
|
|
16482
|
-
initializeSession as
|
|
16753
|
+
createCaptureSession as createCaptureSession10,
|
|
16754
|
+
initializeSession as initializeSession10,
|
|
16483
16755
|
closeCaptureSession as closeCaptureSession10,
|
|
16484
16756
|
captureFrame as captureFrame2,
|
|
16485
16757
|
captureFrameToBuffer as captureFrameToBuffer4,
|
|
@@ -17980,7 +18252,8 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
17980
18252
|
)
|
|
17981
18253
|
),
|
|
17982
18254
|
chunksCarryAudio: options.chunksCarryAudio,
|
|
17983
|
-
sliceChunkAudio: true
|
|
18255
|
+
sliceChunkAudio: true,
|
|
18256
|
+
deferAudio: options.deferAudio
|
|
17984
18257
|
});
|
|
17985
18258
|
return await publishExecutionPlan(stagingRoot, publisher, { digests, log: config.logger });
|
|
17986
18259
|
} catch (error) {
|
|
@@ -18316,7 +18589,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
18316
18589
|
return mergePngFrameDirs(chunkPaths, outputPath, plan2.totalFrames, audioPath, start);
|
|
18317
18590
|
}
|
|
18318
18591
|
if (chunksAreStitchedMp4(planDir, cfr)) {
|
|
18319
|
-
|
|
18592
|
+
const separateAudio = stitchesSeparateAudio(planDir) && audioPath !== null;
|
|
18593
|
+
await writeStitchedMp4(chunkPaths, outputPath, separateAudio ? audioPath : void 0);
|
|
18320
18594
|
return {
|
|
18321
18595
|
outputPath,
|
|
18322
18596
|
durationMs: Date.now() - start,
|
|
@@ -18514,16 +18788,20 @@ async function runAssembleFfmpeg(args, step, signal, timeout) {
|
|
|
18514
18788
|
}
|
|
18515
18789
|
}
|
|
18516
18790
|
function chunksAreStitchedMp4(planDir, cfr) {
|
|
18517
|
-
|
|
18791
|
+
const { chunksCarryAudio, separateAudio } = lockedAudio(planDir);
|
|
18792
|
+
if (!chunksCarryAudio && !separateAudio) return false;
|
|
18518
18793
|
if (cfr) {
|
|
18519
|
-
throw new Error("[assemble] cfr=true cannot re-encode a plan whose mp4 chunks
|
|
18794
|
+
throw new Error("[assemble] cfr=true cannot re-encode a plan whose mp4 chunks are stitched");
|
|
18520
18795
|
}
|
|
18521
18796
|
return true;
|
|
18522
18797
|
}
|
|
18523
|
-
function
|
|
18798
|
+
function stitchesSeparateAudio(planDir) {
|
|
18799
|
+
return lockedAudio(planDir).separateAudio === true;
|
|
18800
|
+
}
|
|
18801
|
+
function lockedAudio(planDir) {
|
|
18524
18802
|
const encoderJsonPath = join41(planDir, "meta", "encoder.json");
|
|
18525
|
-
if (!existsSync27(encoderJsonPath)) return
|
|
18526
|
-
return JSON.parse(readFileSync14(encoderJsonPath, "utf-8"))
|
|
18803
|
+
if (!existsSync27(encoderJsonPath)) return {};
|
|
18804
|
+
return JSON.parse(readFileSync14(encoderJsonPath, "utf-8"));
|
|
18527
18805
|
}
|
|
18528
18806
|
function readCfrEncoder(planDir, format) {
|
|
18529
18807
|
if (format !== "mp4") {
|
|
@@ -18597,11 +18875,12 @@ import {
|
|
|
18597
18875
|
calculateOptimalWorkers as calculateOptimalWorkers2,
|
|
18598
18876
|
classifyCaptureFailure as classifyCaptureFailure4,
|
|
18599
18877
|
closeCaptureSession as closeCaptureSession11,
|
|
18600
|
-
createCaptureSession as
|
|
18878
|
+
createCaptureSession as createCaptureSession11,
|
|
18601
18879
|
createFrameLookupTable as createFrameLookupTable5,
|
|
18602
18880
|
deriveBeginFrameProbeTimeTicks as deriveBeginFrameProbeTimeTicks2,
|
|
18603
18881
|
getEncoderPreset as getEncoderPreset6,
|
|
18604
|
-
initializeSession as
|
|
18882
|
+
initializeSession as initializeSession11,
|
|
18883
|
+
measureVideoShownBoxes,
|
|
18605
18884
|
probeBeginFrameLiveness as probeBeginFrameLiveness2,
|
|
18606
18885
|
readWebGlVendorInfoFromCanvas,
|
|
18607
18886
|
resolveConfig as resolveConfig7
|
|
@@ -18618,6 +18897,12 @@ function validatePlanVideosForChunk(value) {
|
|
|
18618
18897
|
);
|
|
18619
18898
|
}
|
|
18620
18899
|
}
|
|
18900
|
+
var MIN_FRAMES_PER_CHUNK_BROWSER = 20;
|
|
18901
|
+
function resolveChunkCaptureWorkers(framesInChunk, machineSized, maxCaptureWorkers) {
|
|
18902
|
+
if (maxCaptureWorkers === void 0) return machineSized;
|
|
18903
|
+
const byFrames = Math.floor(framesInChunk / MIN_FRAMES_PER_CHUNK_BROWSER);
|
|
18904
|
+
return Math.max(machineSized, Math.floor(Math.min(maxCaptureWorkers, byFrames)));
|
|
18905
|
+
}
|
|
18621
18906
|
function resolveChunkFrameRange(slice, chunkIndex, frameRange) {
|
|
18622
18907
|
if (frameRange === void 0) return { startFrame: slice.startFrame, endFrame: slice.endFrame };
|
|
18623
18908
|
const { startFrame, endFrame } = frameRange;
|
|
@@ -18630,9 +18915,9 @@ function resolveChunkFrameRange(slice, chunkIndex, frameRange) {
|
|
|
18630
18915
|
return { startFrame, endFrame };
|
|
18631
18916
|
}
|
|
18632
18917
|
var distributedCaptureSessionDependencies = {
|
|
18633
|
-
createCaptureSession:
|
|
18918
|
+
createCaptureSession: createCaptureSession11,
|
|
18634
18919
|
assertSwiftShader,
|
|
18635
|
-
initializeSession:
|
|
18920
|
+
initializeSession: initializeSession11,
|
|
18636
18921
|
closeCaptureSession: closeCaptureSession11,
|
|
18637
18922
|
readWebGlVendorInfo: readWebGlVendorInfoFromCanvas
|
|
18638
18923
|
};
|
|
@@ -18823,34 +19108,15 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
18823
19108
|
const framesDir = join42(workDir, "captured-frames");
|
|
18824
19109
|
mkdirSync18(framesDir, { recursive: true });
|
|
18825
19110
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
18826
|
-
const
|
|
18827
|
-
|
|
18828
|
-
|
|
18829
|
-
|
|
18830
|
-
fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
|
|
18831
|
-
outputRoot: videoFramesDir,
|
|
18832
|
-
cfg,
|
|
18833
|
-
remoteSources
|
|
18834
|
-
}) : [];
|
|
19111
|
+
const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
|
|
19112
|
+
const chunkFps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
|
|
19113
|
+
const deferredVideos = planVideos?.extracted.filter((video) => video.deferredRange) ?? [];
|
|
19114
|
+
const shownFrames = planVideos ? shownDeferredFrameIndexes(planVideos, chunkFrameNumbers, chunkFps) : /* @__PURE__ */ new Map();
|
|
18835
19115
|
const remoteVideoIds = new Set(
|
|
18836
19116
|
planVideos?.extracted.filter(
|
|
18837
19117
|
(video) => video.deferredRange && Object.hasOwn(remoteSources, video.deferredRange.sourcePath)
|
|
18838
19118
|
).map((video) => video.videoId)
|
|
18839
19119
|
);
|
|
18840
|
-
const remoteVideos = deferredVideoFrames.filter((video) => remoteVideoIds.has(video.videoId));
|
|
18841
|
-
const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? createFrameLookupTable5(
|
|
18842
|
-
planVideos.videos,
|
|
18843
|
-
[
|
|
18844
|
-
...rebuildExtractedFramesFromPlanDir(
|
|
18845
|
-
planDir,
|
|
18846
|
-
planVideos.extracted.filter((video) => !video.deferredRange),
|
|
18847
|
-
v2Manifest === null ? "dense-v1" : "sparse-v2"
|
|
18848
|
-
),
|
|
18849
|
-
...deferredVideoFrames
|
|
18850
|
-
],
|
|
18851
|
-
resolveRenderFpsConfig(job.config.fps).value
|
|
18852
|
-
) : null;
|
|
18853
|
-
const createChunkVideoFrameInjector = createChunkVideoFrameInjectorFactory(videoFrameLookup);
|
|
18854
19120
|
const fileServer = await createFileServer({
|
|
18855
19121
|
projectDir: compiledDir,
|
|
18856
19122
|
compiledDir,
|
|
@@ -18866,10 +19132,18 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
18866
19132
|
videoCount: planVideos?.videos.length ?? 0,
|
|
18867
19133
|
html: readFileSync15(join42(compiledDir, "index.html"), "utf-8"),
|
|
18868
19134
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
18869
|
-
skipReadinessVideoIds:
|
|
18870
|
-
|
|
19135
|
+
skipReadinessVideoIds: deferredVideos.filter(
|
|
19136
|
+
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
19137
|
+
).map((video) => video.videoId),
|
|
19138
|
+
videoMetadataHints: collectVideoMetadataHints(
|
|
19139
|
+
deferredVideos.filter((video) => remoteVideoIds.has(video.videoId))
|
|
19140
|
+
)
|
|
18871
19141
|
});
|
|
18872
|
-
const chunkWorkerCount =
|
|
19142
|
+
const chunkWorkerCount = resolveChunkCaptureWorkers(
|
|
19143
|
+
framesInChunk,
|
|
19144
|
+
calculateOptimalWorkers2(framesInChunk, void 0, cfg),
|
|
19145
|
+
options?.maxCaptureWorkers
|
|
19146
|
+
);
|
|
18873
19147
|
let session = null;
|
|
18874
19148
|
let outputKind;
|
|
18875
19149
|
let framesEncoded = 0;
|
|
@@ -18880,6 +19154,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
18880
19154
|
const capturePerfs = [];
|
|
18881
19155
|
const captureAttempts = [];
|
|
18882
19156
|
let forceScreenshotForChunk = encoder.forceScreenshot;
|
|
19157
|
+
let shownBoxes;
|
|
18883
19158
|
try {
|
|
18884
19159
|
if (chunkWorkerCount === 1 || !forceScreenshotForChunk) {
|
|
18885
19160
|
const bootStarted = Date.now();
|
|
@@ -18890,6 +19165,11 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
18890
19165
|
cfg
|
|
18891
19166
|
);
|
|
18892
19167
|
sessionBootMs = Date.now() - bootStarted;
|
|
19168
|
+
shownBoxes = await measureVideoShownBoxes(
|
|
19169
|
+
session.page,
|
|
19170
|
+
[...shownFrames.keys()],
|
|
19171
|
+
chunkFrameNumbers.map((frame) => frame * chunkFps.den / chunkFps.num)
|
|
19172
|
+
);
|
|
18893
19173
|
const browserSelectedScreenshot = session.launchCaptureMode === "screenshot";
|
|
18894
19174
|
const beginFrameStalled = !browserSelectedScreenshot && await beginFrameSessionNeedsScreenshotFallback(session);
|
|
18895
19175
|
if (browserSelectedScreenshot) {
|
|
@@ -18931,6 +19211,29 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
18931
19211
|
session = null;
|
|
18932
19212
|
}
|
|
18933
19213
|
}
|
|
19214
|
+
const deferredVideoFrames = planVideos ? await extractDeferredVideoFramesForChunk({
|
|
19215
|
+
planDir,
|
|
19216
|
+
planVideos,
|
|
19217
|
+
frames: chunkFrameNumbers,
|
|
19218
|
+
fps: chunkFps,
|
|
19219
|
+
outputRoot: videoFramesDir,
|
|
19220
|
+
cfg,
|
|
19221
|
+
remoteSources,
|
|
19222
|
+
shownBoxes
|
|
19223
|
+
}) : [];
|
|
19224
|
+
const videoFrameLookup = planVideos && planVideos.extracted.length > 0 ? createFrameLookupTable5(
|
|
19225
|
+
planVideos.videos,
|
|
19226
|
+
[
|
|
19227
|
+
...rebuildExtractedFramesFromPlanDir(
|
|
19228
|
+
planDir,
|
|
19229
|
+
planVideos.extracted.filter((video) => !video.deferredRange),
|
|
19230
|
+
v2Manifest === null ? "dense-v1" : "sparse-v2"
|
|
19231
|
+
),
|
|
19232
|
+
...deferredVideoFrames
|
|
19233
|
+
],
|
|
19234
|
+
resolveRenderFpsConfig(job.config.fps).value
|
|
19235
|
+
) : null;
|
|
19236
|
+
const createChunkVideoFrameInjector = createChunkVideoFrameInjectorFactory(videoFrameLookup);
|
|
18934
19237
|
const captureStarted = Date.now();
|
|
18935
19238
|
captureMode = await runCaptureWithScreenshotFallback({
|
|
18936
19239
|
forceScreenshot: forceScreenshotForChunk,
|
|
@@ -19121,6 +19424,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19121
19424
|
encodeStageMs,
|
|
19122
19425
|
workers: chunkWorkerCount,
|
|
19123
19426
|
captureMode,
|
|
19427
|
+
// Device-pixel box each video's frames were extracted to cover; a video not listed kept full size.
|
|
19428
|
+
videoShownBoxes: Object.fromEntries(shownBoxes ?? []),
|
|
19124
19429
|
sha256,
|
|
19125
19430
|
outputKind,
|
|
19126
19431
|
producerVersion: plan2.producerVersion,
|
|
@@ -19188,6 +19493,9 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
|
19188
19493
|
rmSync17(workRoot, { recursive: true, force: true });
|
|
19189
19494
|
}
|
|
19190
19495
|
}
|
|
19496
|
+
|
|
19497
|
+
// src/services/distributed/projectHash.ts
|
|
19498
|
+
var READ_CHUNK_BYTES = 256 * 1024;
|
|
19191
19499
|
export {
|
|
19192
19500
|
CURRENT_PLAN_PROTOCOL,
|
|
19193
19501
|
DEFAULT_CONFIG2 as DEFAULT_CONFIG,
|
|
@@ -19220,7 +19528,7 @@ export {
|
|
|
19220
19528
|
captureFrameToBuffer4 as captureFrameToBuffer,
|
|
19221
19529
|
closeCaptureSession10 as closeCaptureSession,
|
|
19222
19530
|
collectRenderMedia,
|
|
19223
|
-
|
|
19531
|
+
createCaptureSession10 as createCaptureSession,
|
|
19224
19532
|
createConsoleLogger,
|
|
19225
19533
|
createFileServer,
|
|
19226
19534
|
createOwnedRenderDir,
|
|
@@ -19238,7 +19546,7 @@ export {
|
|
|
19238
19546
|
getCompositionDuration2 as getCompositionDuration,
|
|
19239
19547
|
getDistributedRenderCapabilities,
|
|
19240
19548
|
getPlanV2ExecutionPlanHash,
|
|
19241
|
-
|
|
19549
|
+
initializeSession10 as initializeSession,
|
|
19242
19550
|
injectDeterministicFontFaces2 as injectDeterministicFontFaces,
|
|
19243
19551
|
isTransientBrowserError3 as isTransientBrowserError,
|
|
19244
19552
|
listAbandonedRenderDirs,
|
|
@@ -19252,6 +19560,7 @@ export {
|
|
|
19252
19560
|
plan,
|
|
19253
19561
|
planV2,
|
|
19254
19562
|
planV2WithPublisher,
|
|
19563
|
+
prepareAnimatedGifInputs,
|
|
19255
19564
|
prepareCaptureSessionForReuse4 as prepareCaptureSessionForReuse,
|
|
19256
19565
|
prepareHyperframeLintBody,
|
|
19257
19566
|
publishPlanV2FromExecutionPlan,
|