@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/public-server.js
CHANGED
|
@@ -48,6 +48,8 @@ import {
|
|
|
48
48
|
extractMediaMetadata as extractMediaMetadata4,
|
|
49
49
|
resolveConfig as resolveConfig2,
|
|
50
50
|
closeCaptureSession as closeCaptureSession8,
|
|
51
|
+
createCaptureSession as createCaptureSession8,
|
|
52
|
+
initializeSession as initializeSession8,
|
|
51
53
|
createVideoFrameInjector,
|
|
52
54
|
getEncoderPreset as getEncoderPreset3,
|
|
53
55
|
distributeFrames as distributeFrames2,
|
|
@@ -1421,7 +1423,7 @@ function pidScope() {
|
|
|
1421
1423
|
return {
|
|
1422
1424
|
host: hostname(),
|
|
1423
1425
|
// Without /proc (macOS, Windows) the boot time, to the minute, stands in for the boot id.
|
|
1424
|
-
boot: attempt(() => readFileSync2("/proc/sys/kernel/random/boot_id", "utf-8").trim()) ?? String(Math.round(Date.now() / 6e4 - uptime() / 60)),
|
|
1426
|
+
boot: attempt(() => readFileSync2("/proc/sys/kernel/random/boot_id", "utf-8").trim()) ?? attempt(() => String(Math.round(Date.now() / 6e4 - uptime() / 60))),
|
|
1425
1427
|
pidns: attempt(() => readlinkSync("/proc/self/ns/pid"))
|
|
1426
1428
|
};
|
|
1427
1429
|
}
|
|
@@ -1439,9 +1441,14 @@ function alive(pid) {
|
|
|
1439
1441
|
return error.code === "EPERM";
|
|
1440
1442
|
}
|
|
1441
1443
|
}
|
|
1442
|
-
function
|
|
1444
|
+
function knownPidScope() {
|
|
1443
1445
|
const scope = pidScope();
|
|
1444
|
-
|
|
1446
|
+
const unknown = scope.boot === void 0 || process.platform === "linux" && scope.pidns === void 0;
|
|
1447
|
+
return unknown ? void 0 : scope;
|
|
1448
|
+
}
|
|
1449
|
+
function inThisPidScope(owner) {
|
|
1450
|
+
const scope = knownPidScope();
|
|
1451
|
+
return scope !== void 0 && owner.host === scope.host && owner.boot === scope.boot && owner.pidns === scope.pidns;
|
|
1445
1452
|
}
|
|
1446
1453
|
function ownerState(dir) {
|
|
1447
1454
|
let owner;
|
|
@@ -1506,7 +1513,9 @@ async function defaultArtifactDurationProbe(path) {
|
|
|
1506
1513
|
const meta = await extractMediaMetadata(path);
|
|
1507
1514
|
return {
|
|
1508
1515
|
durationSeconds: meta.durationSeconds,
|
|
1509
|
-
frames: meta.frames
|
|
1516
|
+
frames: meta.frames,
|
|
1517
|
+
width: meta.width,
|
|
1518
|
+
height: meta.height
|
|
1510
1519
|
};
|
|
1511
1520
|
}
|
|
1512
1521
|
function buildArtifactExpectation(input) {
|
|
@@ -1515,7 +1524,9 @@ function buildArtifactExpectation(input) {
|
|
|
1515
1524
|
const expectation = {
|
|
1516
1525
|
expectedDurationSeconds: input.durationSeconds,
|
|
1517
1526
|
fps: input.fps,
|
|
1518
|
-
expectedFrames: input.expectedFrames
|
|
1527
|
+
expectedFrames: input.expectedFrames,
|
|
1528
|
+
expectedWidth: input.width,
|
|
1529
|
+
expectedHeight: input.height
|
|
1519
1530
|
};
|
|
1520
1531
|
if (input.outputFormat !== "hls") return expectation;
|
|
1521
1532
|
return {
|
|
@@ -1583,6 +1594,16 @@ function assertFrameCountWithinTolerance(expectedFrames, probedFrames, stagingPa
|
|
|
1583
1594
|
`Render artifact is truncated: expected ${expectedFrames} frames, probed ${probedFrames} frames (shortfall ${shortfall} frames). Artifact: ${stagingPath}`
|
|
1584
1595
|
);
|
|
1585
1596
|
}
|
|
1597
|
+
function assertSizeMatches(expected, probed, stagingPath) {
|
|
1598
|
+
const { expectedWidth: width, expectedHeight: height } = expected;
|
|
1599
|
+
if (width === void 0 || height === void 0) return;
|
|
1600
|
+
if (probed.width === void 0 || probed.height === void 0) return;
|
|
1601
|
+
const fits = (want, got) => got === want || got === want + want % 2;
|
|
1602
|
+
if (fits(width, probed.width) && fits(height, probed.height)) return;
|
|
1603
|
+
throw new Error(
|
|
1604
|
+
`Render artifact has the wrong size: expected ${width}x${height}, probed ${probed.width}x${probed.height}. Artifact: ${stagingPath}`
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1586
1607
|
var ArtifactTransaction = class {
|
|
1587
1608
|
constructor(destinationPath, kind, fileSystem = defaultFileSystem, durationProbe = defaultArtifactDurationProbe) {
|
|
1588
1609
|
this.kind = kind;
|
|
@@ -1653,6 +1674,7 @@ var ArtifactTransaction = class {
|
|
|
1653
1674
|
if (expected.expectedFrames !== void 0) {
|
|
1654
1675
|
assertFrameCountWithinTolerance(expected.expectedFrames, probed.frames, probePath);
|
|
1655
1676
|
}
|
|
1677
|
+
assertSizeMatches(expected, probed, probePath);
|
|
1656
1678
|
}
|
|
1657
1679
|
async runDurationProbe(probePath) {
|
|
1658
1680
|
try {
|
|
@@ -3201,7 +3223,9 @@ function aggregateDrawElement(perfs, de) {
|
|
|
3201
3223
|
if (perfs.length === 0) return void 0;
|
|
3202
3224
|
const modes = [...new Set(perfs.map((p) => p.captureMode).filter(Boolean))].sort();
|
|
3203
3225
|
const gateReasons = [
|
|
3204
|
-
...new Set(
|
|
3226
|
+
...new Set(
|
|
3227
|
+
[de.probeGateReason, ...perfs.map((p) => p.deGateReason)].filter((r) => !!r)
|
|
3228
|
+
)
|
|
3205
3229
|
].sort();
|
|
3206
3230
|
const gpuRenderers = [
|
|
3207
3231
|
...new Set(perfs.map((p) => p.gpuRenderer).filter((r) => !!r))
|
|
@@ -8665,6 +8689,7 @@ async function runAudioStage(input) {
|
|
|
8665
8689
|
audios,
|
|
8666
8690
|
masterFxChain,
|
|
8667
8691
|
urlByAbsentLocalPath,
|
|
8692
|
+
keepEncoderInputAt,
|
|
8668
8693
|
abortSignal,
|
|
8669
8694
|
assertNotAborted,
|
|
8670
8695
|
log
|
|
@@ -8684,7 +8709,13 @@ async function runAudioStage(input) {
|
|
|
8684
8709
|
audioOutputPath,
|
|
8685
8710
|
duration,
|
|
8686
8711
|
abortSignal,
|
|
8687
|
-
{
|
|
8712
|
+
{
|
|
8713
|
+
ffmpegProcessTimeout,
|
|
8714
|
+
audioGain,
|
|
8715
|
+
masterFxChain,
|
|
8716
|
+
urlByAbsentLocalPath,
|
|
8717
|
+
keepEncoderInputAt
|
|
8718
|
+
},
|
|
8688
8719
|
compiledDir
|
|
8689
8720
|
);
|
|
8690
8721
|
} catch (err) {
|
|
@@ -8798,13 +8829,29 @@ function inspectDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, free
|
|
|
8798
8829
|
}
|
|
8799
8830
|
return { available: false, estimatedBytes, freeBytes };
|
|
8800
8831
|
}
|
|
8801
|
-
function
|
|
8832
|
+
function diskCaptureRemedy(cfg, outputFormat, durationSeconds, workerCount) {
|
|
8833
|
+
const oneWorker = explainStreamingEncodeGate(cfg, outputFormat, 1, durationSeconds);
|
|
8834
|
+
if (workerCount > 1 && oneWorker.enabled) {
|
|
8835
|
+
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.`;
|
|
8836
|
+
}
|
|
8837
|
+
switch (oneWorker.reason) {
|
|
8838
|
+
case "format_excluded":
|
|
8839
|
+
return `${outputFormat} output stores every frame at any worker count. Free up disk space or render to mp4.`;
|
|
8840
|
+
case "disabled_by_config":
|
|
8841
|
+
return "PRODUCER_ENABLE_STREAMING_ENCODE=false sends every render to disk. Free up disk space or unset it.";
|
|
8842
|
+
case "duration_cap":
|
|
8843
|
+
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.";
|
|
8844
|
+
default:
|
|
8845
|
+
return "Free up disk space.";
|
|
8846
|
+
}
|
|
8847
|
+
}
|
|
8848
|
+
function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remedy) {
|
|
8802
8849
|
const basisNote = basis === "measured" ? ` (measured from the first ${DISK_PROJECTION_SAMPLE_FRAMES} frames)` : "";
|
|
8803
8850
|
return new Error(
|
|
8804
|
-
`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).
|
|
8851
|
+
`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}`
|
|
8805
8852
|
);
|
|
8806
8853
|
}
|
|
8807
|
-
function assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, freeDiskBytes) {
|
|
8854
|
+
function assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, remedy, freeDiskBytes) {
|
|
8808
8855
|
const headroom = inspectDiskCaptureHeadroom(
|
|
8809
8856
|
framesDir,
|
|
8810
8857
|
totalFrames,
|
|
@@ -8816,7 +8863,8 @@ function assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, freeD
|
|
|
8816
8863
|
headroom.estimatedBytes,
|
|
8817
8864
|
headroom.freeBytes,
|
|
8818
8865
|
framesDir,
|
|
8819
|
-
"estimate"
|
|
8866
|
+
"estimate",
|
|
8867
|
+
remedy
|
|
8820
8868
|
);
|
|
8821
8869
|
}
|
|
8822
8870
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
@@ -8836,7 +8884,7 @@ function measureCaptureFrameBytes(workDir, framesDir) {
|
|
|
8836
8884
|
return total;
|
|
8837
8885
|
}
|
|
8838
8886
|
function createDiskCaptureProjection(input) {
|
|
8839
|
-
const { framesDir, totalFrames, measureBytes, freeDiskBytes = freeDiskBytesAt } = input;
|
|
8887
|
+
const { framesDir, totalFrames, measureBytes, remedy, freeDiskBytes = freeDiskBytesAt } = input;
|
|
8840
8888
|
let checked = false;
|
|
8841
8889
|
return (capturedFrames) => {
|
|
8842
8890
|
if (checked || capturedFrames < DISK_PROJECTION_SAMPLE_FRAMES) return;
|
|
@@ -8848,7 +8896,7 @@ function createDiskCaptureProjection(input) {
|
|
|
8848
8896
|
if (remaining <= 0 || freeBytes === null) return;
|
|
8849
8897
|
const needBytes = Math.ceil(measured / capturedFrames * remaining);
|
|
8850
8898
|
if (needBytes > freeBytes * 0.9) {
|
|
8851
|
-
throw diskCaptureShortfallError(needBytes, freeBytes, framesDir, "measured");
|
|
8899
|
+
throw diskCaptureShortfallError(needBytes, freeBytes, framesDir, "measured", remedy);
|
|
8852
8900
|
}
|
|
8853
8901
|
};
|
|
8854
8902
|
}
|
|
@@ -8890,11 +8938,13 @@ async function runCaptureStage(input) {
|
|
|
8890
8938
|
}
|
|
8891
8939
|
}
|
|
8892
8940
|
const captureOptions = buildCaptureOptions();
|
|
8893
|
-
|
|
8941
|
+
const remedy = diskCaptureRemedy(cfg, job.config.format ?? "mp4", job.duration ?? 0, workerCount);
|
|
8942
|
+
assertDiskCaptureHeadroom(framesDir, totalFrames, captureOptions, remedy);
|
|
8894
8943
|
const checkDiskProjection = createDiskCaptureProjection({
|
|
8895
8944
|
framesDir,
|
|
8896
8945
|
totalFrames,
|
|
8897
|
-
measureBytes: () => measureCaptureFrameBytes(workDir, framesDir)
|
|
8946
|
+
measureBytes: () => measureCaptureFrameBytes(workDir, framesDir),
|
|
8947
|
+
remedy
|
|
8898
8948
|
});
|
|
8899
8949
|
if (workerCount > 1) {
|
|
8900
8950
|
const attempts = await executeDiskCaptureWithAdaptiveRetry({
|
|
@@ -8915,6 +8965,7 @@ async function runCaptureStage(input) {
|
|
|
8915
8965
|
if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
|
|
8916
8966
|
return;
|
|
8917
8967
|
}
|
|
8968
|
+
if (progress.landedFrame) input.onFrameLanded?.(progress.landedFrame);
|
|
8918
8969
|
job.framesRendered = progress.capturedFrames;
|
|
8919
8970
|
checkDiskProjection(progress.capturedFrames);
|
|
8920
8971
|
const frameProgress = progress.capturedFrames / progress.totalFrames;
|
|
@@ -10084,6 +10135,7 @@ import {
|
|
|
10084
10135
|
statSync as statSync5,
|
|
10085
10136
|
writeFileSync as writeFileSync4
|
|
10086
10137
|
} from "node:fs";
|
|
10138
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
10087
10139
|
import { dirname as dirname8, join as join24 } from "node:path";
|
|
10088
10140
|
import {
|
|
10089
10141
|
encodeFramesChunkedConcat,
|
|
@@ -10091,7 +10143,8 @@ import {
|
|
|
10091
10143
|
formatFfmpegError,
|
|
10092
10144
|
MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME2,
|
|
10093
10145
|
resolveConfig,
|
|
10094
|
-
runFfmpeg as runFfmpeg3
|
|
10146
|
+
runFfmpeg as runFfmpeg3,
|
|
10147
|
+
spawnStreamingEncoder as spawnStreamingEncoder4
|
|
10095
10148
|
} from "@hyperframes/engine";
|
|
10096
10149
|
import {
|
|
10097
10150
|
clearGifFramesBeforeNext,
|
|
@@ -10105,8 +10158,10 @@ function fpsToFfmpegArg2(fps) {
|
|
|
10105
10158
|
}
|
|
10106
10159
|
var KEEP_FILTER_GRAPH_WHEN_FRAMES_DROP_ALPHA = ["-reinit_filter", "0"];
|
|
10107
10160
|
var WRITE_EVERY_FRAME_WHOLE = ["-gifflags", "0"];
|
|
10161
|
+
var FAIL_ON_UNREADABLE_FRAME_INSTEAD_OF_ENDING_THERE = ["-xerror"];
|
|
10108
10162
|
function framesInput(input, fpsArg) {
|
|
10109
10163
|
return [
|
|
10164
|
+
...FAIL_ON_UNREADABLE_FRAME_INSTEAD_OF_ENDING_THERE,
|
|
10110
10165
|
"-framerate",
|
|
10111
10166
|
fpsArg,
|
|
10112
10167
|
...KEEP_FILTER_GRAPH_WHEN_FRAMES_DROP_ALPHA,
|
|
@@ -10226,6 +10281,124 @@ function startEncodeProgress(job, stage, onProgress) {
|
|
|
10226
10281
|
});
|
|
10227
10282
|
return (frames = total) => reportEncodeProgress(job, frames, total, onProgress, 75);
|
|
10228
10283
|
}
|
|
10284
|
+
function resolveEngineConfig(input) {
|
|
10285
|
+
return input.engineConfig ?? input.job.config.producerConfig ?? resolveConfig();
|
|
10286
|
+
}
|
|
10287
|
+
function videoEncoderOptions(input, engineCfg) {
|
|
10288
|
+
const { job, preset } = input;
|
|
10289
|
+
return {
|
|
10290
|
+
fps: job.config.fps,
|
|
10291
|
+
width: input.width,
|
|
10292
|
+
height: input.height,
|
|
10293
|
+
codec: preset.codec,
|
|
10294
|
+
preset: preset.preset,
|
|
10295
|
+
quality: input.effectiveQuality,
|
|
10296
|
+
bitrate: input.effectiveBitrate,
|
|
10297
|
+
pixelFormat: preset.pixelFormat,
|
|
10298
|
+
vp9CpuUsed: engineCfg.vp9CpuUsed,
|
|
10299
|
+
useGpu: job.config.useGpu,
|
|
10300
|
+
hdr: preset.hdr,
|
|
10301
|
+
// Distributed chunk renders pass these so the encoder writes closed-GOP
|
|
10302
|
+
// keyframes that survive `-f concat -c copy` at assemble time. In-process
|
|
10303
|
+
// renders leave both undefined → preserves the existing open-GOP output.
|
|
10304
|
+
lockGopForChunkConcat: input.lockGopForChunkConcat === true,
|
|
10305
|
+
gopSize: input.gopSize
|
|
10306
|
+
};
|
|
10307
|
+
}
|
|
10308
|
+
function canEncodeWhileCapturing(input, workerCount, cpuCount) {
|
|
10309
|
+
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;
|
|
10310
|
+
}
|
|
10311
|
+
async function readLandedFrame(workerPath, mergedPath) {
|
|
10312
|
+
if (workerPath !== void 0) {
|
|
10313
|
+
try {
|
|
10314
|
+
return await readFile2(workerPath);
|
|
10315
|
+
} catch (error) {
|
|
10316
|
+
if (error.code !== "ENOENT") throw error;
|
|
10317
|
+
}
|
|
10318
|
+
}
|
|
10319
|
+
return readFile2(mergedPath);
|
|
10320
|
+
}
|
|
10321
|
+
function encodeWhileCapturing(input, totalFrames) {
|
|
10322
|
+
const engineCfg = resolveEngineConfig(input);
|
|
10323
|
+
const stop = new AbortController();
|
|
10324
|
+
const signal = input.abortSignal ? AbortSignal.any([input.abortSignal, stop.signal]) : stop.signal;
|
|
10325
|
+
const landed = /* @__PURE__ */ new Map();
|
|
10326
|
+
let captureDone = false;
|
|
10327
|
+
let framesFed = 0;
|
|
10328
|
+
let wake = null;
|
|
10329
|
+
const nudge = () => {
|
|
10330
|
+
wake?.();
|
|
10331
|
+
wake = null;
|
|
10332
|
+
};
|
|
10333
|
+
signal.addEventListener("abort", nudge, { once: true });
|
|
10334
|
+
let reportFed = null;
|
|
10335
|
+
const encoder = spawnStreamingEncoder4(
|
|
10336
|
+
input.videoOnlyPath,
|
|
10337
|
+
{ ...videoEncoderOptions(input, engineCfg), imageFormat: "jpeg" },
|
|
10338
|
+
signal,
|
|
10339
|
+
engineCfg
|
|
10340
|
+
);
|
|
10341
|
+
const fed = (async () => {
|
|
10342
|
+
const stream = await encoder;
|
|
10343
|
+
for (let next = 0; next < totalFrames; next++) {
|
|
10344
|
+
while (!landed.has(next) && !captureDone && !signal.aborted) {
|
|
10345
|
+
await new Promise((resolve10) => wake = resolve10);
|
|
10346
|
+
}
|
|
10347
|
+
if (signal.aborted) return;
|
|
10348
|
+
const mergedPath = join24(input.framesDir, formatCaptureFrameName(next, "jpg"));
|
|
10349
|
+
const frame = await readLandedFrame(landed.get(next), mergedPath);
|
|
10350
|
+
landed.delete(next);
|
|
10351
|
+
if (!await stream.writeFrame(frame)) {
|
|
10352
|
+
throw new Error(stream.getExitError() ?? "encoder exited before the last frame");
|
|
10353
|
+
}
|
|
10354
|
+
framesFed++;
|
|
10355
|
+
reportFed?.(framesFed);
|
|
10356
|
+
}
|
|
10357
|
+
})().then(
|
|
10358
|
+
() => void 0,
|
|
10359
|
+
(error) => error ?? new Error("encode failed")
|
|
10360
|
+
);
|
|
10361
|
+
const closeEncoder = async () => (await encoder.catch(() => null))?.close();
|
|
10362
|
+
return {
|
|
10363
|
+
onFrameLanded: (frame) => {
|
|
10364
|
+
landed.set(frame.index, frame.path);
|
|
10365
|
+
nudge();
|
|
10366
|
+
},
|
|
10367
|
+
finish: async () => {
|
|
10368
|
+
const start = Date.now();
|
|
10369
|
+
captureDone = true;
|
|
10370
|
+
input.log.info("[Render] Encoded frames while capturing", {
|
|
10371
|
+
framesFed,
|
|
10372
|
+
totalFrames
|
|
10373
|
+
});
|
|
10374
|
+
const encoded = startEncodeProgress(input.job, "Encoding video", input.onProgress);
|
|
10375
|
+
reportFed = encoded;
|
|
10376
|
+
nudge();
|
|
10377
|
+
const failure = await fed;
|
|
10378
|
+
const result = await closeEncoder();
|
|
10379
|
+
input.assertNotAborted();
|
|
10380
|
+
if (failure === void 0 && result?.success) {
|
|
10381
|
+
encoded();
|
|
10382
|
+
return { encodeMs: Date.now() - start };
|
|
10383
|
+
}
|
|
10384
|
+
input.log.warn(
|
|
10385
|
+
"[Render] Encoding while capturing failed; encoding the frames after capture",
|
|
10386
|
+
{
|
|
10387
|
+
error: String(failure ?? result?.error)
|
|
10388
|
+
}
|
|
10389
|
+
);
|
|
10390
|
+
return runEncodeStage(input);
|
|
10391
|
+
},
|
|
10392
|
+
abort: async () => {
|
|
10393
|
+
stop.abort();
|
|
10394
|
+
await fed;
|
|
10395
|
+
await closeEncoder();
|
|
10396
|
+
},
|
|
10397
|
+
get framesFed() {
|
|
10398
|
+
return framesFed;
|
|
10399
|
+
}
|
|
10400
|
+
};
|
|
10401
|
+
}
|
|
10229
10402
|
async function runEncodeStage(input) {
|
|
10230
10403
|
const {
|
|
10231
10404
|
job,
|
|
@@ -10233,16 +10406,11 @@ async function runEncodeStage(input) {
|
|
|
10233
10406
|
outputPath,
|
|
10234
10407
|
framesDir,
|
|
10235
10408
|
videoOnlyPath,
|
|
10236
|
-
width,
|
|
10237
|
-
height,
|
|
10238
10409
|
needsAlpha,
|
|
10239
10410
|
hasAudio,
|
|
10240
10411
|
audioOutputPath,
|
|
10241
10412
|
isPngSequence,
|
|
10242
10413
|
isGif,
|
|
10243
|
-
preset,
|
|
10244
|
-
effectiveQuality,
|
|
10245
|
-
effectiveBitrate,
|
|
10246
10414
|
enableChunkedEncode,
|
|
10247
10415
|
chunkedEncodeSize,
|
|
10248
10416
|
abortSignal,
|
|
@@ -10272,7 +10440,7 @@ async function runEncodeStage(input) {
|
|
|
10272
10440
|
encoded();
|
|
10273
10441
|
return { encodeMs: Date.now() - stage5Start };
|
|
10274
10442
|
}
|
|
10275
|
-
const engineCfg = input
|
|
10443
|
+
const engineCfg = resolveEngineConfig(input);
|
|
10276
10444
|
if (isGif) {
|
|
10277
10445
|
const encoded = startEncodeProgress(job, "Encoding GIF", onProgress);
|
|
10278
10446
|
if (hasAudio) {
|
|
@@ -10302,24 +10470,7 @@ async function runEncodeStage(input) {
|
|
|
10302
10470
|
const videoEngineCfg = scaledEncodeTimeout > engineCfg.ffmpegEncodeTimeout ? { ...engineCfg, ffmpegEncodeTimeout: scaledEncodeTimeout } : engineCfg;
|
|
10303
10471
|
const frameExt = frameFileExtension2(input.captureImageFormat);
|
|
10304
10472
|
const framePattern = `frame_%06d.${frameExt}`;
|
|
10305
|
-
const encoderOpts =
|
|
10306
|
-
fps: job.config.fps,
|
|
10307
|
-
width,
|
|
10308
|
-
height,
|
|
10309
|
-
codec: preset.codec,
|
|
10310
|
-
preset: preset.preset,
|
|
10311
|
-
quality: effectiveQuality,
|
|
10312
|
-
bitrate: effectiveBitrate,
|
|
10313
|
-
pixelFormat: preset.pixelFormat,
|
|
10314
|
-
vp9CpuUsed: engineCfg.vp9CpuUsed,
|
|
10315
|
-
useGpu: job.config.useGpu,
|
|
10316
|
-
hdr: preset.hdr,
|
|
10317
|
-
// Distributed chunk renders pass these so the encoder writes closed-GOP
|
|
10318
|
-
// keyframes that survive `-f concat -c copy` at assemble time. In-process
|
|
10319
|
-
// renders leave both undefined → preserves the existing open-GOP output.
|
|
10320
|
-
lockGopForChunkConcat: input.lockGopForChunkConcat === true,
|
|
10321
|
-
gopSize: input.gopSize
|
|
10322
|
-
};
|
|
10473
|
+
const encoderOpts = videoEncoderOptions(input, engineCfg);
|
|
10323
10474
|
const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
|
|
10324
10475
|
framesDir,
|
|
10325
10476
|
framePattern,
|
|
@@ -10357,7 +10508,8 @@ import { dirname as dirname9, join as join25 } from "node:path";
|
|
|
10357
10508
|
import {
|
|
10358
10509
|
DEFAULT_CONFIG,
|
|
10359
10510
|
describeFfmpegFailure as describeFfmpegFailure2,
|
|
10360
|
-
|
|
10511
|
+
encodeAac,
|
|
10512
|
+
extractAudioMetadata as extractAudioMetadata3,
|
|
10361
10513
|
getFfprobeBinary,
|
|
10362
10514
|
ManagedChildProcess,
|
|
10363
10515
|
runFfmpeg as runFfmpeg5,
|
|
@@ -10366,7 +10518,12 @@ import {
|
|
|
10366
10518
|
import { redactKnownPaths, redactTelemetryString as redactTelemetryString3 } from "@hyperframes/core";
|
|
10367
10519
|
|
|
10368
10520
|
// src/services/render/audioTruePeak.ts
|
|
10369
|
-
import {
|
|
10521
|
+
import {
|
|
10522
|
+
aacSegmentCount,
|
|
10523
|
+
describeFfmpegFailure,
|
|
10524
|
+
extractAudioMetadata as extractAudioMetadata2,
|
|
10525
|
+
runFfmpeg as runFfmpeg4
|
|
10526
|
+
} from "@hyperframes/engine";
|
|
10370
10527
|
var toDbfs = (token) => token.toLowerCase() === "-inf" ? Number.NEGATIVE_INFINITY : Number(token);
|
|
10371
10528
|
function checkedDbfs(value) {
|
|
10372
10529
|
if (Number.isNaN(value) || value === Number.POSITIVE_INFINITY) {
|
|
@@ -10404,12 +10561,13 @@ async function scanTruePeak(audioPath, trim, timeoutMs, signal) {
|
|
|
10404
10561
|
};
|
|
10405
10562
|
}
|
|
10406
10563
|
async function measureTruePeakDbfs(audioPath, options) {
|
|
10407
|
-
const { timeoutMs, signal
|
|
10564
|
+
const { timeoutMs, signal } = options;
|
|
10408
10565
|
const wholeFile = async () => (await scanTruePeak(audioPath, "", timeoutMs, signal)).peak;
|
|
10566
|
+
const { durationSeconds, sampleRate } = await extractAudioMetadata2(audioPath, { signal });
|
|
10409
10567
|
const block = 4800;
|
|
10410
|
-
const count =
|
|
10411
|
-
if (
|
|
10412
|
-
const cut = (k) => k * Math.floor(
|
|
10568
|
+
const count = sampleRate === 48e3 && Number.isFinite(durationSeconds) ? aacSegmentCount(durationSeconds) : 1;
|
|
10569
|
+
if (count === 1) return wholeFile();
|
|
10570
|
+
const cut = (k) => k * Math.floor(durationSeconds * 10 / count) * block;
|
|
10413
10571
|
const peers = new AbortController();
|
|
10414
10572
|
const segmentSignal = signal ? AbortSignal.any([signal, peers.signal]) : peers.signal;
|
|
10415
10573
|
const scans = await Promise.all(
|
|
@@ -10438,24 +10596,8 @@ var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
|
|
|
10438
10596
|
var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
|
|
10439
10597
|
var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
|
|
10440
10598
|
var MAX_TRUE_PEAK_CORRECTION_PASSES = 3;
|
|
10441
|
-
function
|
|
10442
|
-
return
|
|
10443
|
-
"-i",
|
|
10444
|
-
inputPath,
|
|
10445
|
-
"-map",
|
|
10446
|
-
"0:a:0",
|
|
10447
|
-
"-vn",
|
|
10448
|
-
"-af",
|
|
10449
|
-
`volume=${attenuationDb.toFixed(3)}dB`,
|
|
10450
|
-
"-t",
|
|
10451
|
-
formatSeconds(targetDurationSeconds),
|
|
10452
|
-
"-c:a",
|
|
10453
|
-
"aac",
|
|
10454
|
-
"-b:a",
|
|
10455
|
-
"192k",
|
|
10456
|
-
"-y",
|
|
10457
|
-
outputPath
|
|
10458
|
-
];
|
|
10599
|
+
function aacTruePeakCorrectionFilter(targetDurationSeconds, attenuationDb) {
|
|
10600
|
+
return `volume=${attenuationDb.toFixed(3)}dB,apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
|
|
10459
10601
|
}
|
|
10460
10602
|
function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
|
|
10461
10603
|
const delta = targetDurationSeconds - sourceDurationSeconds;
|
|
@@ -10470,53 +10612,13 @@ function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, tar
|
|
|
10470
10612
|
if (delta > 0) {
|
|
10471
10613
|
return {
|
|
10472
10614
|
operation: "pad",
|
|
10473
|
-
steps: [
|
|
10474
|
-
{
|
|
10475
|
-
kind: "normalize",
|
|
10476
|
-
args: [
|
|
10477
|
-
"-i",
|
|
10478
|
-
audioPath,
|
|
10479
|
-
"-af",
|
|
10480
|
-
`apad,atrim=0:${targetSec}`,
|
|
10481
|
-
"-t",
|
|
10482
|
-
targetSec,
|
|
10483
|
-
"-c:a",
|
|
10484
|
-
"aac",
|
|
10485
|
-
"-b:a",
|
|
10486
|
-
"192k",
|
|
10487
|
-
"-y",
|
|
10488
|
-
outputPath
|
|
10489
|
-
]
|
|
10490
|
-
}
|
|
10491
|
-
],
|
|
10615
|
+
steps: [{ kind: "normalize", filter: `apad,atrim=0:${targetSec}` }],
|
|
10492
10616
|
cleanupPaths: []
|
|
10493
10617
|
};
|
|
10494
10618
|
}
|
|
10495
10619
|
return {
|
|
10496
10620
|
operation: "trim",
|
|
10497
|
-
steps: [
|
|
10498
|
-
{
|
|
10499
|
-
kind: "trim",
|
|
10500
|
-
args: [
|
|
10501
|
-
"-i",
|
|
10502
|
-
audioPath,
|
|
10503
|
-
"-af",
|
|
10504
|
-
`atrim=duration=${targetSec},asetpts=PTS-STARTPTS`,
|
|
10505
|
-
// `atrim` limits decoded samples but does not cap muxer timestamps
|
|
10506
|
-
// introduced by encoder delay/flush. The output duration contract
|
|
10507
|
-
// is enforced at the container boundary as well; without `-t`, a
|
|
10508
|
-
// long primed source can retain its original tail after re-encode.
|
|
10509
|
-
"-t",
|
|
10510
|
-
targetSec,
|
|
10511
|
-
"-c:a",
|
|
10512
|
-
"aac",
|
|
10513
|
-
"-b:a",
|
|
10514
|
-
"192k",
|
|
10515
|
-
"-y",
|
|
10516
|
-
outputPath
|
|
10517
|
-
]
|
|
10518
|
-
}
|
|
10519
|
-
],
|
|
10621
|
+
steps: [{ kind: "trim", filter: `atrim=duration=${targetSec},asetpts=PTS-STARTPTS` }],
|
|
10520
10622
|
cleanupPaths: []
|
|
10521
10623
|
};
|
|
10522
10624
|
}
|
|
@@ -10533,7 +10635,8 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10533
10635
|
const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
|
|
10534
10636
|
const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
|
|
10535
10637
|
const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
|
|
10536
|
-
const
|
|
10638
|
+
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal);
|
|
10639
|
+
const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg || input.encodeAac ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
|
|
10537
10640
|
const [videoResult, audioResult] = await Promise.allSettled([
|
|
10538
10641
|
probeVideo(input.videoPath),
|
|
10539
10642
|
probeAudio(input.audioPath, input.signal)
|
|
@@ -10580,7 +10683,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10580
10683
|
);
|
|
10581
10684
|
try {
|
|
10582
10685
|
for (const step of plan.steps) {
|
|
10583
|
-
const ffmpegResult = await runner(step.args);
|
|
10686
|
+
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(input.audioPath, input.outputPath, targetDurationSeconds, step.filter);
|
|
10584
10687
|
if (!ffmpegResult.success) {
|
|
10585
10688
|
return {
|
|
10586
10689
|
success: false,
|
|
@@ -10612,7 +10715,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10612
10715
|
targetDurationSeconds,
|
|
10613
10716
|
signal: input.signal,
|
|
10614
10717
|
probeTruePeak,
|
|
10615
|
-
|
|
10718
|
+
encode
|
|
10616
10719
|
});
|
|
10617
10720
|
if (!correction.success) {
|
|
10618
10721
|
return {
|
|
@@ -10660,13 +10763,11 @@ async function enforceAacTruePeak(input) {
|
|
|
10660
10763
|
}
|
|
10661
10764
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
10662
10765
|
attenuationDb += correctionTargetDbfs - truePeakDbfs;
|
|
10663
|
-
const result = await input.
|
|
10664
|
-
|
|
10665
|
-
|
|
10666
|
-
|
|
10667
|
-
|
|
10668
|
-
attenuationDb
|
|
10669
|
-
)
|
|
10766
|
+
const result = await input.encode(
|
|
10767
|
+
input.audioPath,
|
|
10768
|
+
correctedPath,
|
|
10769
|
+
input.targetDurationSeconds,
|
|
10770
|
+
aacTruePeakCorrectionFilter(input.targetDurationSeconds, attenuationDb)
|
|
10670
10771
|
);
|
|
10671
10772
|
if (!result.success) {
|
|
10672
10773
|
return {
|
|
@@ -10761,7 +10862,7 @@ function parseFrameRate(rate) {
|
|
|
10761
10862
|
return { fpsNum, fpsDen };
|
|
10762
10863
|
}
|
|
10763
10864
|
async function defaultProbeAudioInfo(audioPath, signal) {
|
|
10764
|
-
const metadata = await
|
|
10865
|
+
const metadata = await extractAudioMetadata3(audioPath, { signal });
|
|
10765
10866
|
return {
|
|
10766
10867
|
durationSeconds: metadata.durationSeconds,
|
|
10767
10868
|
sampleRate: metadata.sampleRate,
|
|
@@ -10770,7 +10871,18 @@ async function defaultProbeAudioInfo(audioPath, signal) {
|
|
|
10770
10871
|
};
|
|
10771
10872
|
}
|
|
10772
10873
|
async function defaultRunFfmpeg(args, timeoutMs, signal) {
|
|
10773
|
-
|
|
10874
|
+
return toStepResult(await runFfmpeg5(args, { signal, timeout: timeoutMs }), timeoutMs);
|
|
10875
|
+
}
|
|
10876
|
+
function defaultAacEncoder(timeoutMs, signal, pcmSourcePath) {
|
|
10877
|
+
return async (inputPath, outputPath, durationSeconds, filter) => {
|
|
10878
|
+
const options = pcmSourcePath ? { signal, timeout: timeoutMs, filter } : { signal, timeout: timeoutMs, filter, segments: 1 };
|
|
10879
|
+
return toStepResult(
|
|
10880
|
+
await encodeAac(pcmSourcePath ?? inputPath, outputPath, durationSeconds, options),
|
|
10881
|
+
timeoutMs
|
|
10882
|
+
);
|
|
10883
|
+
};
|
|
10884
|
+
}
|
|
10885
|
+
function toStepResult(result, timeoutMs) {
|
|
10774
10886
|
if (result.success) return { success: true };
|
|
10775
10887
|
return {
|
|
10776
10888
|
success: false,
|
|
@@ -10958,7 +11070,7 @@ function assertOutputScaleSupported(deviceScaleFactor, route) {
|
|
|
10958
11070
|
const reason = UNSUPPORTED_REASON2[route];
|
|
10959
11071
|
if (reason === null) return;
|
|
10960
11072
|
throw new Error(
|
|
10961
|
-
`[Render] --resolution other than the composition's size is not supported on the "${route}" capture route: ${reason}. Render at the composition's size, or
|
|
11073
|
+
`[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.`
|
|
10962
11074
|
);
|
|
10963
11075
|
}
|
|
10964
11076
|
|
|
@@ -11175,7 +11287,7 @@ function getNextRetryWorkerCount(currentWorkers) {
|
|
|
11175
11287
|
return Math.max(1, Math.floor(currentWorkers / 2));
|
|
11176
11288
|
}
|
|
11177
11289
|
function resolveRenderWorkDirPrefix(outputPath, jobId, platform2 = process.platform, systemTempDir = tmpdir2()) {
|
|
11178
|
-
if (platform2 === "win32") return join26(systemTempDir, "hf-render-");
|
|
11290
|
+
if (platform2 === "win32" || platform2 === "darwin") return join26(systemTempDir, "hf-render-");
|
|
11179
11291
|
return join26(dirname10(outputPath), `work-${jobId}-`);
|
|
11180
11292
|
}
|
|
11181
11293
|
var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
|
|
@@ -11669,6 +11781,9 @@ function resolveDeShortBand(args) {
|
|
|
11669
11781
|
function shouldPreferSingleWorkerDrawElement(args) {
|
|
11670
11782
|
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;
|
|
11671
11783
|
}
|
|
11784
|
+
function isProbeDeGated(session) {
|
|
11785
|
+
return session !== null && session.captureMode !== "drawelement" && !session.deInitDeferred;
|
|
11786
|
+
}
|
|
11672
11787
|
function resolveInversionRetryPlan(args) {
|
|
11673
11788
|
if (args.deWorkerInversion !== "inverted") return null;
|
|
11674
11789
|
const workerCount = args.isMemoryExhaustion ? 1 : args.preInversionWorkerCount;
|
|
@@ -11750,7 +11865,8 @@ async function closeOrphanedProbeForRetry(probe, closer, log, retryContext) {
|
|
|
11750
11865
|
}
|
|
11751
11866
|
function resolveParallelCaptureMode(args) {
|
|
11752
11867
|
const supersampling = (args.deviceScaleFactor ?? 1) !== 1;
|
|
11753
|
-
|
|
11868
|
+
const headlessShell = args.headlessShellPath !== void 0;
|
|
11869
|
+
return headlessShell && args.platform === "linux" && !args.forceScreenshot && !supersampling ? "beginframe" : "screenshot";
|
|
11754
11870
|
}
|
|
11755
11871
|
function isCaptureParallelStreamRouterEnabled(env, resolvedMode) {
|
|
11756
11872
|
const raw = env.HF_CAPTURE_PARALLEL_STREAM?.trim().toLowerCase();
|
|
@@ -12358,6 +12474,7 @@ async function executeRenderPipeline(input) {
|
|
|
12358
12474
|
);
|
|
12359
12475
|
const nativeHdrIds = /* @__PURE__ */ new Set([...nativeHdrVideoIds, ...nativeHdrImageIds]);
|
|
12360
12476
|
const hasHdrContent = Boolean(effectiveHdr && nativeHdrIds.size > 0);
|
|
12477
|
+
if (hasHdrContent) assertOutputScaleSupported(deviceScaleFactor, "hdr_layered");
|
|
12361
12478
|
const deSingleMinFramesRaw = process.env.HF_DE_SINGLE_MIN_FRAMES;
|
|
12362
12479
|
const deSingleMinFramesNum = deSingleMinFramesRaw === void 0 || deSingleMinFramesRaw.trim() === "" ? 900 : Number(deSingleMinFramesRaw);
|
|
12363
12480
|
const deSingleMinFrames = Number.isFinite(deSingleMinFramesNum) ? deSingleMinFramesNum : 900;
|
|
@@ -12398,7 +12515,7 @@ async function executeRenderPipeline(input) {
|
|
|
12398
12515
|
singleWorkerStreamingOk: shouldUseStreamingEncode(cfg, outputFormat, 1, job.duration),
|
|
12399
12516
|
layeredOrEffectRoute: hasHdrContent || compiled.hasShaderTransitions,
|
|
12400
12517
|
supersampling: deviceScaleFactor !== 1,
|
|
12401
|
-
probeDeGated: probeSession
|
|
12518
|
+
probeDeGated: isProbeDeGated(probeSession),
|
|
12402
12519
|
experimentalParallelDeOptIn: process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true" || // Verified parallel DE streaming (opt-in) wants its parallelism kept.
|
|
12403
12520
|
process.env.HF_DE_PARALLEL_STREAM === "true"
|
|
12404
12521
|
};
|
|
@@ -12407,14 +12524,14 @@ async function executeRenderPipeline(input) {
|
|
|
12407
12524
|
...deInversionArgs,
|
|
12408
12525
|
minFrames: Math.min(deSingleMinFrames, deShortBandMinFrames)
|
|
12409
12526
|
});
|
|
12410
|
-
|
|
12527
|
+
let deShortBand = resolveDeShortBand({
|
|
12411
12528
|
invertAtBaseFloor,
|
|
12412
12529
|
invertAtBandFloor,
|
|
12413
12530
|
bandEnabled: deShortBandEnabled,
|
|
12414
12531
|
bandOpen: deShortBandOpen,
|
|
12415
12532
|
elementCountSource: compositionElementCountSource
|
|
12416
12533
|
});
|
|
12417
|
-
|
|
12534
|
+
let deInversionEligible = deShortBandRoute && deShortBand === "applied" ? invertAtBandFloor : invertAtBaseFloor;
|
|
12418
12535
|
const deInversionEffectiveMinFrames = deShortBandRoute && deShortBand === "applied" ? Math.min(deSingleMinFrames, deShortBandMinFrames) : deSingleMinFrames;
|
|
12419
12536
|
const deParallelRouterEnabled = isDeParallelRouterEnabled(process.env);
|
|
12420
12537
|
const deParallelMinFramesRaw = process.env.HF_DE_PARALLEL_MIN_FRAMES;
|
|
@@ -12423,7 +12540,7 @@ async function executeRenderPipeline(input) {
|
|
|
12423
12540
|
const deParallelMinMemRaw = process.env.HF_DE_PARALLEL_MIN_MEM_MB;
|
|
12424
12541
|
const deParallelMinMemNum = deParallelMinMemRaw === void 0 || deParallelMinMemRaw.trim() === "" ? 24576 : Number(deParallelMinMemRaw);
|
|
12425
12542
|
const deParallelMinMemoryMb = Number.isFinite(deParallelMinMemNum) ? deParallelMinMemNum : 24576;
|
|
12426
|
-
|
|
12543
|
+
let deParallelRouterEligible = shouldPreferParallelDrawElement({
|
|
12427
12544
|
workerCount: WOULD_RESOLVE_MULTI_WORKER,
|
|
12428
12545
|
requestedWorkers: job.config.workers,
|
|
12429
12546
|
useDrawElement: cfg.useDrawElement,
|
|
@@ -12434,7 +12551,7 @@ async function executeRenderPipeline(input) {
|
|
|
12434
12551
|
minFrames: deParallelMinFrames,
|
|
12435
12552
|
layeredOrEffectRoute: hasHdrContent || compiled.hasShaderTransitions,
|
|
12436
12553
|
supersampling: deviceScaleFactor !== 1,
|
|
12437
|
-
probeDeGated: probeSession
|
|
12554
|
+
probeDeGated: isProbeDeGated(probeSession),
|
|
12438
12555
|
experimentalParallelDeOptIn: process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true" || process.env.HF_DE_PARALLEL_STREAM === "true",
|
|
12439
12556
|
routerEnabled: deParallelRouterEnabled,
|
|
12440
12557
|
// Router pins 3 workers for the streaming path; don't pin when a
|
|
@@ -12450,6 +12567,35 @@ async function executeRenderPipeline(input) {
|
|
|
12450
12567
|
totalMemoryMb: Math.round(totalmem2() / (1024 * 1024)),
|
|
12451
12568
|
minMemoryMb: deParallelMinMemoryMb
|
|
12452
12569
|
});
|
|
12570
|
+
if (probeSession === null && (deInversionEligible || deParallelRouterEligible)) {
|
|
12571
|
+
try {
|
|
12572
|
+
probeSession = await createCaptureSession8(
|
|
12573
|
+
activeFileServer.url,
|
|
12574
|
+
framesDir,
|
|
12575
|
+
buildCaptureOptions(),
|
|
12576
|
+
null,
|
|
12577
|
+
{ ...cfg, forceScreenshot: captureForceScreenshot }
|
|
12578
|
+
);
|
|
12579
|
+
await initializeSession8(probeSession);
|
|
12580
|
+
} catch (error) {
|
|
12581
|
+
assertNotAborted();
|
|
12582
|
+
if (probeSession) {
|
|
12583
|
+
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
12584
|
+
await closeCaptureSession8(probeSession).catch(() => {
|
|
12585
|
+
});
|
|
12586
|
+
probeSession = null;
|
|
12587
|
+
}
|
|
12588
|
+
log.warn("[Render] Fast capture gate check failed; routing without it.", {
|
|
12589
|
+
error: error instanceof Error ? error.message : String(error)
|
|
12590
|
+
});
|
|
12591
|
+
}
|
|
12592
|
+
if (isProbeDeGated(probeSession)) {
|
|
12593
|
+
deInversionEligible = false;
|
|
12594
|
+
deParallelRouterEligible = false;
|
|
12595
|
+
deShortBand = void 0;
|
|
12596
|
+
}
|
|
12597
|
+
}
|
|
12598
|
+
const deProbeGateReason = isProbeDeGated(probeSession) ? probeSession.deGateReason : void 0;
|
|
12453
12599
|
let workerCount;
|
|
12454
12600
|
const captureStageObservationData = (extra = {}) => ({
|
|
12455
12601
|
stagePhase: "capturing",
|
|
@@ -12637,7 +12783,7 @@ async function executeRenderPipeline(input) {
|
|
|
12637
12783
|
}
|
|
12638
12784
|
const parallelCaptureMode = resolveParallelCaptureMode({
|
|
12639
12785
|
platform: process.platform,
|
|
12640
|
-
|
|
12786
|
+
headlessShellPath: resolveHeadlessShellPath(cfg),
|
|
12641
12787
|
forceScreenshot: captureForceScreenshot,
|
|
12642
12788
|
deviceScaleFactor: captureOptions.deviceScaleFactor
|
|
12643
12789
|
});
|
|
@@ -13239,6 +13385,34 @@ async function executeRenderPipeline(input) {
|
|
|
13239
13385
|
throw new Error(`Disk capture requires sdr_disk plan; got ${capturePlan.kind}`);
|
|
13240
13386
|
}
|
|
13241
13387
|
resetCaptureAttemptProgress(job);
|
|
13388
|
+
const encodeInput = {
|
|
13389
|
+
job,
|
|
13390
|
+
log,
|
|
13391
|
+
outputPath: stagedOutputPath,
|
|
13392
|
+
framesDir,
|
|
13393
|
+
videoOnlyPath,
|
|
13394
|
+
width,
|
|
13395
|
+
height,
|
|
13396
|
+
needsAlpha,
|
|
13397
|
+
captureImageFormat: captureOptions.format ?? "jpeg",
|
|
13398
|
+
hasAudio,
|
|
13399
|
+
audioOutputPath,
|
|
13400
|
+
isPngSequence,
|
|
13401
|
+
isGif,
|
|
13402
|
+
preset,
|
|
13403
|
+
effectiveQuality,
|
|
13404
|
+
effectiveBitrate,
|
|
13405
|
+
enableChunkedEncode,
|
|
13406
|
+
chunkedEncodeSize,
|
|
13407
|
+
engineConfig: cfg,
|
|
13408
|
+
// Same value the streaming encoder above spreads — the disk and
|
|
13409
|
+
// chunked-concat encoders need an identical GOP lock for HLS.
|
|
13410
|
+
...hlsEncoderGopLock,
|
|
13411
|
+
abortSignal: executionSignal,
|
|
13412
|
+
assertNotAborted,
|
|
13413
|
+
onProgress
|
|
13414
|
+
};
|
|
13415
|
+
const overlap = !cfg.useDrawElement && workerSizing !== void 0 && canEncodeWhileCapturing(encodeInput, capturePlan.workerCount, workerSizing.cpuCount) ? encodeWhileCapturing(encodeInput, totalFrames) : null;
|
|
13242
13416
|
const captureFrameStart = Date.now();
|
|
13243
13417
|
const invokeDiskCapture = (diskPlan) => observeRenderStage(
|
|
13244
13418
|
observability,
|
|
@@ -13260,13 +13434,15 @@ async function executeRenderPipeline(input) {
|
|
|
13260
13434
|
createRenderVideoFrameInjector,
|
|
13261
13435
|
abortSignal: executionSignal,
|
|
13262
13436
|
assertNotAborted,
|
|
13263
|
-
onProgress
|
|
13437
|
+
onProgress,
|
|
13438
|
+
onFrameLanded: (frame) => overlap?.onFrameLanded(frame)
|
|
13264
13439
|
})
|
|
13265
13440
|
);
|
|
13266
13441
|
let captureRes;
|
|
13267
13442
|
try {
|
|
13268
13443
|
captureRes = await invokeDiskCapture(capturePlan);
|
|
13269
13444
|
} catch (err) {
|
|
13445
|
+
await overlap?.abort();
|
|
13270
13446
|
if (!isDrawElementVerificationError(err) && !isDrawElementCaptureError(err) || err instanceof RenderCancelledError || executionSignal?.aborted === true) {
|
|
13271
13447
|
throw err;
|
|
13272
13448
|
}
|
|
@@ -13335,33 +13511,7 @@ async function executeRenderPipeline(input) {
|
|
|
13335
13511
|
isGif,
|
|
13336
13512
|
chunkedEncode: enableChunkedEncode
|
|
13337
13513
|
}),
|
|
13338
|
-
() => runEncodeStage(
|
|
13339
|
-
job,
|
|
13340
|
-
log,
|
|
13341
|
-
outputPath: stagedOutputPath,
|
|
13342
|
-
framesDir,
|
|
13343
|
-
videoOnlyPath,
|
|
13344
|
-
width,
|
|
13345
|
-
height,
|
|
13346
|
-
needsAlpha,
|
|
13347
|
-
captureImageFormat: captureOptions.format ?? "jpeg",
|
|
13348
|
-
hasAudio,
|
|
13349
|
-
audioOutputPath,
|
|
13350
|
-
isPngSequence,
|
|
13351
|
-
isGif,
|
|
13352
|
-
preset,
|
|
13353
|
-
effectiveQuality,
|
|
13354
|
-
effectiveBitrate,
|
|
13355
|
-
enableChunkedEncode,
|
|
13356
|
-
chunkedEncodeSize,
|
|
13357
|
-
engineConfig: cfg,
|
|
13358
|
-
// Same value the streaming encoder above spreads — the disk and
|
|
13359
|
-
// chunked-concat encoders need an identical GOP lock for HLS.
|
|
13360
|
-
...hlsEncoderGopLock,
|
|
13361
|
-
abortSignal: executionSignal,
|
|
13362
|
-
assertNotAborted,
|
|
13363
|
-
onProgress
|
|
13364
|
-
})
|
|
13514
|
+
() => overlap ? overlap.finish() : runEncodeStage(encodeInput)
|
|
13365
13515
|
);
|
|
13366
13516
|
perfStages.encodeMs = encodeRes.encodeMs;
|
|
13367
13517
|
}
|
|
@@ -13409,7 +13559,9 @@ async function executeRenderPipeline(input) {
|
|
|
13409
13559
|
outputFormat,
|
|
13410
13560
|
durationSeconds: job.duration,
|
|
13411
13561
|
fps: fpsToNumber9(job.config.fps),
|
|
13412
|
-
expectedFrames: captureTotalFrames
|
|
13562
|
+
expectedFrames: captureTotalFrames,
|
|
13563
|
+
width: outputWidth,
|
|
13564
|
+
height: outputHeight
|
|
13413
13565
|
})
|
|
13414
13566
|
);
|
|
13415
13567
|
const totalElapsed = Date.now() - pipelineStart;
|
|
@@ -13445,6 +13597,7 @@ async function executeRenderPipeline(input) {
|
|
|
13445
13597
|
drawElement: {
|
|
13446
13598
|
compileGate: deCompileGate,
|
|
13447
13599
|
clampReason: deClampReason,
|
|
13600
|
+
probeGateReason: deProbeGateReason,
|
|
13448
13601
|
workerInversion: deWorkerInversion,
|
|
13449
13602
|
preInversionWorkers: deWorkerInversion ? preRoutingWorkerCount : void 0,
|
|
13450
13603
|
compositionElementCount,
|
|
@@ -13846,9 +13999,9 @@ import {
|
|
|
13846
13999
|
// src/services/distributed/planFrameCost.ts
|
|
13847
14000
|
import {
|
|
13848
14001
|
closeCaptureSession as closeCaptureSession9,
|
|
13849
|
-
createCaptureSession as
|
|
14002
|
+
createCaptureSession as createCaptureSession9,
|
|
13850
14003
|
createFrameLookupTable as createFrameLookupTable3,
|
|
13851
|
-
initializeSession as
|
|
14004
|
+
initializeSession as initializeSession9
|
|
13852
14005
|
} from "@hyperframes/engine";
|
|
13853
14006
|
|
|
13854
14007
|
// src/services/distributed/chunkFrames.ts
|