@hyperframes/producer 0.8.206 → 0.8.207
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/distributed.js +185 -96
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +74 -69
- package/dist/index.js +513 -309
- package/dist/public-server.js +450 -278
- package/dist/renderRequest.d.ts +3 -0
- package/dist/server.d.ts +3 -0
- package/dist/services/distributed/assemble.d.ts +1 -1
- package/dist/services/distributed/plan.d.ts +6 -0
- package/dist/services/render/deliveryEncoder.d.ts +22 -0
- package/dist/services/render/hlsConfig.d.ts +1 -1
- package/dist/services/render/segmentManifest.d.ts +3 -0
- package/dist/services/render/stages/captureHdrStage.d.ts +2 -0
- package/dist/services/render/stages/encodeStage.d.ts +2 -0
- package/dist/services/render/stages/freezePlan.d.ts +3 -0
- package/dist/services/render/stages/probeStage.d.ts +2 -3
- package/dist/services/renderOrchestrator.d.ts +6 -0
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
mkdirSync as mkdirSync10,
|
|
6
6
|
readFileSync as readFileSync9,
|
|
7
7
|
readdirSync as readdirSync5,
|
|
8
|
-
rmSync as
|
|
8
|
+
rmSync as rmSync10,
|
|
9
9
|
statSync as statSync8,
|
|
10
10
|
writeFileSync as writeFileSync6,
|
|
11
11
|
copyFileSync as copyFileSync5,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
15
15
|
import {
|
|
16
16
|
chainCapture,
|
|
17
|
-
fpsToNumber as
|
|
17
|
+
fpsToNumber as fpsToNumber8,
|
|
18
18
|
HF_COLOR_GRADING_ATTR,
|
|
19
19
|
HF_VFX_ATTR,
|
|
20
20
|
parseVfxChain,
|
|
@@ -810,7 +810,7 @@ function outputUsesH264Pipeline(format) {
|
|
|
810
810
|
}
|
|
811
811
|
|
|
812
812
|
// src/services/render/hlsConfig.ts
|
|
813
|
-
import {
|
|
813
|
+
import { keyframeIntervalToGopSize } from "@hyperframes/engine";
|
|
814
814
|
var DEFAULT_HLS_SEGMENT_SECONDS = 4;
|
|
815
815
|
function validateHlsRenderConfig(config) {
|
|
816
816
|
if (config.format !== "hls") return;
|
|
@@ -835,7 +835,7 @@ function resolveHlsSegmentSeconds(config) {
|
|
|
835
835
|
return config.hlsSegmentSeconds ?? DEFAULT_HLS_SEGMENT_SECONDS;
|
|
836
836
|
}
|
|
837
837
|
function resolveHlsGopSize(fps, segmentSeconds) {
|
|
838
|
-
return
|
|
838
|
+
return keyframeIntervalToGopSize(fps, segmentSeconds);
|
|
839
839
|
}
|
|
840
840
|
function resolveHlsEncoderGopLock(outputFormat, fps, segmentSeconds) {
|
|
841
841
|
if (outputFormat !== "hls") return { lockGopForChunkConcat: false, gopSize: void 0 };
|
|
@@ -845,6 +845,41 @@ function resolveHlsEncoderGopLock(outputFormat, fps, segmentSeconds) {
|
|
|
845
845
|
};
|
|
846
846
|
}
|
|
847
847
|
|
|
848
|
+
// src/services/render/deliveryEncoder.ts
|
|
849
|
+
import { appendRateCapArgs, keyframeIntervalToGopSize as keyframeIntervalToGopSize2 } from "@hyperframes/engine";
|
|
850
|
+
function validateDeliveryEncoderConfig(config) {
|
|
851
|
+
const { keyframeIntervalSeconds, maxBitrate, bufferSize, format } = config;
|
|
852
|
+
if (keyframeIntervalSeconds === void 0 && maxBitrate === void 0 && bufferSize === void 0)
|
|
853
|
+
return;
|
|
854
|
+
if (keyframeIntervalSeconds !== void 0) {
|
|
855
|
+
if (!Number.isFinite(keyframeIntervalSeconds) || keyframeIntervalSeconds <= 0) {
|
|
856
|
+
throw new Error(
|
|
857
|
+
`keyframeIntervalSeconds must be a positive number of seconds; got ${keyframeIntervalSeconds}`
|
|
858
|
+
);
|
|
859
|
+
}
|
|
860
|
+
if (format === "hls") {
|
|
861
|
+
throw new Error(
|
|
862
|
+
"keyframeIntervalSeconds does not apply to hls; hlsSegmentSeconds sets its keyframes"
|
|
863
|
+
);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
if (format !== void 0 && format !== "mp4") {
|
|
867
|
+
throw new Error(`keyframeIntervalSeconds and maxBitrate apply to mp4 renders, not ${format}`);
|
|
868
|
+
}
|
|
869
|
+
appendRateCapArgs([], { maxBitrate, bufferSize });
|
|
870
|
+
}
|
|
871
|
+
function chunkGopFrames(framesInChunk, intervalFrames) {
|
|
872
|
+
return Math.min(framesInChunk, intervalFrames ?? framesInChunk);
|
|
873
|
+
}
|
|
874
|
+
function resolveDeliveryEncoderOptions(config) {
|
|
875
|
+
const { maxBitrate, bufferSize, keyframeIntervalSeconds } = config;
|
|
876
|
+
const gop = keyframeIntervalSeconds === void 0 ? resolveHlsEncoderGopLock(config.format ?? "mp4", config.fps, config.hlsSegmentSeconds) : {
|
|
877
|
+
lockGopForChunkConcat: true,
|
|
878
|
+
gopSize: keyframeIntervalToGopSize2(config.fps, keyframeIntervalSeconds)
|
|
879
|
+
};
|
|
880
|
+
return { maxBitrate, bufferSize, ...gop };
|
|
881
|
+
}
|
|
882
|
+
|
|
848
883
|
// src/services/render/shared.ts
|
|
849
884
|
import {
|
|
850
885
|
copyFileSync,
|
|
@@ -1864,7 +1899,7 @@ function replanSegmentedAfterFailure(plan2, failure) {
|
|
|
1864
1899
|
const keepSegmenting = failure.kind === "draw_element_verification" || failure.kind === "draw_element_capture";
|
|
1865
1900
|
return createCapturePlan({
|
|
1866
1901
|
...plan2,
|
|
1867
|
-
forceScreenshot: keepSegmenting || failure.kind === "capture_failure",
|
|
1902
|
+
forceScreenshot: plan2.forceScreenshot || keepSegmenting || failure.kind === "capture_failure",
|
|
1868
1903
|
useStreamingEncode: true,
|
|
1869
1904
|
useLayeredComposite: false,
|
|
1870
1905
|
useSegmentedCapture: keepSegmenting,
|
|
@@ -1934,7 +1969,7 @@ import { CaptureFailure, classifyCaptureFailure } from "@hyperframes/engine";
|
|
|
1934
1969
|
|
|
1935
1970
|
// src/services/render/perfSummary.ts
|
|
1936
1971
|
import { arch, cpus, platform, totalmem } from "node:os";
|
|
1937
|
-
import { fpsToNumber as
|
|
1972
|
+
import { fpsToNumber as fpsToNumber2 } from "@hyperframes/core";
|
|
1938
1973
|
|
|
1939
1974
|
// src/services/render/hdrPerf.ts
|
|
1940
1975
|
function createHdrPerfCollector() {
|
|
@@ -2205,7 +2240,7 @@ function buildRenderPerfSummary(input) {
|
|
|
2205
2240
|
// payloads (CLI telemetry, regression-harness reports) where a single
|
|
2206
2241
|
// number is friendlier than `{num,den}`. Callers needing the rational
|
|
2207
2242
|
// back can read `job.config.fps`.
|
|
2208
|
-
fps:
|
|
2243
|
+
fps: fpsToNumber2(input.job.config.fps),
|
|
2209
2244
|
quality: input.job.config.quality,
|
|
2210
2245
|
workers: input.workerCount,
|
|
2211
2246
|
workerSizing: input.workerSizing,
|
|
@@ -2790,7 +2825,7 @@ import {
|
|
|
2790
2825
|
resolveVideoExtractionWindow,
|
|
2791
2826
|
runFfmpeg
|
|
2792
2827
|
} from "@hyperframes/engine";
|
|
2793
|
-
import { fpsToFfmpegArg, fpsToNumber as
|
|
2828
|
+
import { fpsToFfmpegArg, fpsToNumber as fpsToNumber3 } from "@hyperframes/core";
|
|
2794
2829
|
|
|
2795
2830
|
// src/services/hdrFrameWindows.ts
|
|
2796
2831
|
import { closeSync as closeSync3, constants as constants2, openSync as openSync3, rmSync as rmSync4 } from "node:fs";
|
|
@@ -3093,7 +3128,7 @@ async function extractHdrVideoFrames(args) {
|
|
|
3093
3128
|
const runFfmpegImpl = args.runFfmpegImpl ?? runFfmpeg;
|
|
3094
3129
|
const extractMediaMetadataImpl = args.extractMediaMetadataImpl ?? extractMediaMetadata2;
|
|
3095
3130
|
const resolveFinalFrameExtractionWindowImpl = args.resolveFinalFrameExtractionWindowImpl ?? resolveFinalFrameExtractionWindow;
|
|
3096
|
-
const fps =
|
|
3131
|
+
const fps = fpsToNumber3(job.config.fps);
|
|
3097
3132
|
const out = /* @__PURE__ */ new Map();
|
|
3098
3133
|
mkdirSync2(framesDir, { recursive: true });
|
|
3099
3134
|
let scratchBytes = 0;
|
|
@@ -3931,7 +3966,6 @@ async function runCaptureStreamingStage(input) {
|
|
|
3931
3966
|
const reorderBuffer = createFrameReorderBuffer(0, totalFrames);
|
|
3932
3967
|
if (workerCount > 1) {
|
|
3933
3968
|
const deParallelStream = forceParallelStream === true || process.env.HF_DE_PARALLEL_STREAM === "true";
|
|
3934
|
-
const tasks = deParallelStream ? distributeFramesInterleaved(totalFrames, workerCount, workDir) : distributeFrames(totalFrames, workerCount, workDir);
|
|
3935
3969
|
const parallelStats = {
|
|
3936
3970
|
verifyChecked: 0,
|
|
3937
3971
|
blankSuspects: 0,
|
|
@@ -3944,143 +3978,158 @@ async function runCaptureStreamingStage(input) {
|
|
|
3944
3978
|
frameTime: (i) => i * job.config.fps.den / job.config.fps.num
|
|
3945
3979
|
});
|
|
3946
3980
|
let parallelGuardRan = false;
|
|
3947
|
-
let
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3981
|
+
let writtenFrames = 0;
|
|
3982
|
+
let resumeFrom = 0;
|
|
3983
|
+
let resumedAfterStall = false;
|
|
3984
|
+
let workerResults;
|
|
3985
|
+
for (; ; ) {
|
|
3986
|
+
const parallelReorder = createFrameReorderBuffer(resumeFrom, totalFrames);
|
|
3987
|
+
const tasks = deParallelStream ? distributeFramesInterleaved(totalFrames - resumeFrom, workerCount, workDir, resumeFrom) : distributeFrames(totalFrames - resumeFrom, workerCount, workDir, resumeFrom);
|
|
3988
|
+
let parallelDrainError = null;
|
|
3989
|
+
const onFrameBuffer = async (frameIndex, buffer, workerSession) => {
|
|
3990
|
+
try {
|
|
3991
|
+
if (deParallelStream && workerSession.captureMode === "drawelement") {
|
|
3992
|
+
parallelGuardRan = true;
|
|
3993
|
+
buffer = await parallelGuard(workerSession, frameIndex, buffer);
|
|
3994
|
+
}
|
|
3995
|
+
await parallelReorder.waitForFrame(frameIndex);
|
|
3996
|
+
ensureFrameWritten(await currentEncoder.writeFrame(buffer), frameIndex, currentEncoder);
|
|
3997
|
+
parallelReorder.advanceTo(frameIndex + 1);
|
|
3998
|
+
writtenFrames = frameIndex + 1;
|
|
3999
|
+
} catch (err) {
|
|
4000
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
4001
|
+
if (!parallelDrainError) {
|
|
4002
|
+
parallelDrainError = e;
|
|
4003
|
+
parallelReorder.abort(e);
|
|
4004
|
+
}
|
|
4005
|
+
throw e;
|
|
3962
4006
|
}
|
|
3963
|
-
|
|
4007
|
+
};
|
|
4008
|
+
const captureLabel = input.parallelCaptureLabel ?? "worker";
|
|
4009
|
+
let watchdogArmed = false;
|
|
4010
|
+
const stallController = new AbortController();
|
|
4011
|
+
const forwardParentAbort = () => stallController.abort();
|
|
4012
|
+
if (abortSignal) {
|
|
4013
|
+
if (abortSignal.aborted) stallController.abort();
|
|
4014
|
+
else abortSignal.addEventListener("abort", forwardParentAbort, { once: true });
|
|
3964
4015
|
}
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4016
|
+
const stallTimeoutMs = resolveCaptureStallTimeoutMs();
|
|
4017
|
+
let lastCapturedFrames = 0;
|
|
4018
|
+
let lastProgressAt = Date.now();
|
|
4019
|
+
const workerPhases = /* @__PURE__ */ new Map();
|
|
4020
|
+
const phaseDetail = (phase) => `phase=${phase.phase} frame=${phase.frameIndex ?? "n/a"} step=${phase.readStep?.() ?? "n/a"} browser=${phase.browserExecutable} version=${phase.browserVersion} CanvasDrawElement=${phase.canvasDrawElement} gpu=${phase.gpuBackend}`;
|
|
4021
|
+
const phaseSummary = () => [...workerPhases.entries()].sort(([left], [right]) => left - right).map(([workerId, detail]) => `worker=${workerId} ${detail}`).join("; ");
|
|
4022
|
+
let stalled = false;
|
|
4023
|
+
const stallTimer = setInterval(
|
|
4024
|
+
() => {
|
|
4025
|
+
if (!watchdogArmed || Date.now() - lastProgressAt <= stallTimeoutMs) return;
|
|
4026
|
+
stalled = true;
|
|
4027
|
+
const stallErr = new ParallelCaptureStallError({
|
|
4028
|
+
variant: "drain",
|
|
4029
|
+
captureLabel,
|
|
4030
|
+
stallTimeoutMs,
|
|
4031
|
+
lastCapturedFrames: resumeFrom + lastCapturedFrames,
|
|
4032
|
+
totalFrames,
|
|
4033
|
+
phaseSummary: phaseSummary()
|
|
4034
|
+
});
|
|
4035
|
+
parallelReorder.abort(stallErr);
|
|
4036
|
+
stallController.abort();
|
|
4037
|
+
},
|
|
4038
|
+
Math.min(stallTimeoutMs, DE_STALL_POLL_MS)
|
|
4039
|
+
);
|
|
4040
|
+
try {
|
|
4041
|
+
workerResults = await executeParallelCapture(
|
|
4042
|
+
fileServer.url,
|
|
4043
|
+
workDir,
|
|
4044
|
+
tasks,
|
|
4045
|
+
buildCaptureOptions(),
|
|
4046
|
+
createRenderVideoFrameInjector,
|
|
4047
|
+
stallController.signal,
|
|
4048
|
+
(progress) => {
|
|
4049
|
+
if (progress.latestWorkerPhase) {
|
|
4050
|
+
const phase = progress.latestWorkerPhase;
|
|
4051
|
+
workerPhases.set(phase.workerId, phaseDetail(phase));
|
|
4052
|
+
if (phase.phase === "frame_capture" && !watchdogArmed) {
|
|
4053
|
+
watchdogArmed = true;
|
|
4054
|
+
lastProgressAt = Date.now();
|
|
4055
|
+
}
|
|
4056
|
+
log.info("[Render] Parallel capture worker phase", {
|
|
4057
|
+
workerId: phase.workerId,
|
|
4058
|
+
phase: phase.phase,
|
|
4059
|
+
frameIndex: phase.frameIndex,
|
|
4060
|
+
browserExecutable: phase.browserExecutable,
|
|
4061
|
+
browserVersion: phase.browserVersion,
|
|
4062
|
+
canvasDrawElement: phase.canvasDrawElement,
|
|
4063
|
+
gpuBackend: phase.gpuBackend
|
|
4064
|
+
});
|
|
4065
|
+
if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
|
|
4066
|
+
return;
|
|
4067
|
+
}
|
|
4068
|
+
if (progress.capturedFrames > lastCapturedFrames) {
|
|
4069
|
+
lastCapturedFrames = progress.capturedFrames;
|
|
4013
4070
|
lastProgressAt = Date.now();
|
|
4071
|
+
watchdogArmed = true;
|
|
4072
|
+
}
|
|
4073
|
+
const doneFrames = resumeFrom + progress.capturedFrames;
|
|
4074
|
+
job.framesRendered = doneFrames;
|
|
4075
|
+
const frameProgress = doneFrames / totalFrames;
|
|
4076
|
+
reportFrameProgress(
|
|
4077
|
+
job,
|
|
4078
|
+
`Streaming frame ${doneFrames}/${totalFrames} (${workerCount} workers)`,
|
|
4079
|
+
Math.round(25 + frameProgress * 55),
|
|
4080
|
+
onProgress,
|
|
4081
|
+
doneFrames,
|
|
4082
|
+
totalFrames
|
|
4083
|
+
);
|
|
4084
|
+
},
|
|
4085
|
+
onFrameBuffer,
|
|
4086
|
+
// Interleaved DE workers each get their own browser process: co-tenant pages starve
|
|
4087
|
+
// each other's paint events.
|
|
4088
|
+
deParallelStream ? { ...captureCfg, enableBrowserPool: false } : captureCfg,
|
|
4089
|
+
{
|
|
4090
|
+
// A worker died: release parked peers with its original failure, not the stall the
|
|
4091
|
+
// watchdog raises later.
|
|
4092
|
+
onWorkerOperation: (phase) => workerPhases.set(phase.workerId, phaseDetail(phase)),
|
|
4093
|
+
onWorkerFailure: (failure) => {
|
|
4094
|
+
if (parallelDrainError) return;
|
|
4095
|
+
parallelDrainError = failure;
|
|
4096
|
+
parallelReorder.abort(failure);
|
|
4014
4097
|
}
|
|
4015
|
-
log.info("[Render] Parallel capture worker phase", {
|
|
4016
|
-
workerId: phase.workerId,
|
|
4017
|
-
phase: phase.phase,
|
|
4018
|
-
frameIndex: phase.frameIndex,
|
|
4019
|
-
browserExecutable: phase.browserExecutable,
|
|
4020
|
-
browserVersion: phase.browserVersion,
|
|
4021
|
-
canvasDrawElement: phase.canvasDrawElement,
|
|
4022
|
-
gpuBackend: phase.gpuBackend
|
|
4023
|
-
});
|
|
4024
|
-
if (progress.capturedFrames === 0) reportWorkerStartup(job, progress, onProgress);
|
|
4025
|
-
return;
|
|
4026
|
-
}
|
|
4027
|
-
if (progress.capturedFrames > lastCapturedFrames) {
|
|
4028
|
-
lastCapturedFrames = progress.capturedFrames;
|
|
4029
|
-
lastProgressAt = Date.now();
|
|
4030
|
-
watchdogArmed = true;
|
|
4031
4098
|
}
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4099
|
+
);
|
|
4100
|
+
} catch (err) {
|
|
4101
|
+
if (stalled && abortSignal?.aborted !== true && captureLabel === "BeginFrame" && !resumedAfterStall && writtenFrames > resumeFrom) {
|
|
4102
|
+
resumedAfterStall = true;
|
|
4103
|
+
log.warn(
|
|
4104
|
+
"[Render] Parallel BeginFrame capture stalled; resuming from the first unwritten frame",
|
|
4105
|
+
{
|
|
4106
|
+
resumeFrom: writtenFrames,
|
|
4107
|
+
totalFrames,
|
|
4108
|
+
workerPhases: phaseSummary()
|
|
4109
|
+
}
|
|
4041
4110
|
);
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
// Interleaved DE workers each need their own browser PROCESS:
|
|
4045
|
-
// pages co-tenant in one browser share one compositor, whose
|
|
4046
|
-
// internal frame scheduling deprioritizes non-active pages — their
|
|
4047
|
-
// canvas `paint` events starve and the drawElement paint-wait slows
|
|
4048
|
-
// ~3x (measured: 86s vs 30s on a 3,245-frame rAF comp). This is
|
|
4049
|
-
// Chromium's internal frame-production scheduling on ALL platforms,
|
|
4050
|
-
// NOT the Linux-only HeadlessExperimental.beginFrame capture mode.
|
|
4051
|
-
deParallelStream ? { ...captureCfg, enableBrowserPool: false } : captureCfg,
|
|
4052
|
-
{
|
|
4053
|
-
// A worker died (Target closed, Page crashed, launch failure).
|
|
4054
|
-
// Its frames are gone and peers are parked in waitForFrame on the
|
|
4055
|
-
// first of them; release them with the ORIGINAL failure so the
|
|
4056
|
-
// orchestrator sees a transient browser error it knows how to
|
|
4057
|
-
// retry, not the stall the watchdog would have raised a minute
|
|
4058
|
-
// later. First failure wins, same as the drain guard above.
|
|
4059
|
-
onWorkerFailure: (failure) => {
|
|
4060
|
-
if (parallelDrainError) return;
|
|
4061
|
-
parallelDrainError = failure;
|
|
4062
|
-
reorderBuffer.abort(failure);
|
|
4063
|
-
}
|
|
4111
|
+
resumeFrom = writtenFrames;
|
|
4112
|
+
continue;
|
|
4064
4113
|
}
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4114
|
+
if (parallelDrainError) throw poolFailure(parallelDrainError, probeSession);
|
|
4115
|
+
if (stalled && abortSignal?.aborted !== true) {
|
|
4116
|
+
throw new ParallelCaptureStallError({
|
|
4117
|
+
variant: "pool",
|
|
4118
|
+
captureLabel,
|
|
4119
|
+
stallTimeoutMs,
|
|
4120
|
+
lastCapturedFrames: resumeFrom + lastCapturedFrames,
|
|
4121
|
+
totalFrames,
|
|
4122
|
+
phaseSummary: phaseSummary()
|
|
4123
|
+
});
|
|
4124
|
+
}
|
|
4125
|
+
throw poolFailure(err, probeSession);
|
|
4126
|
+
} finally {
|
|
4127
|
+
clearInterval(stallTimer);
|
|
4128
|
+
abortSignal?.removeEventListener("abort", forwardParentAbort);
|
|
4077
4129
|
}
|
|
4078
|
-
throw poolFailure(
|
|
4079
|
-
|
|
4080
|
-
clearInterval(stallTimer);
|
|
4081
|
-
abortSignal?.removeEventListener("abort", forwardParentAbort);
|
|
4130
|
+
if (parallelDrainError) throw poolFailure(parallelDrainError, probeSession);
|
|
4131
|
+
break;
|
|
4082
4132
|
}
|
|
4083
|
-
if (parallelDrainError) throw poolFailure(parallelDrainError, probeSession);
|
|
4084
4133
|
pushWorkerDedupPerfs(workerResults, dedupPerfs);
|
|
4085
4134
|
const workerPageErrors = [];
|
|
4086
4135
|
pushWorkerPageErrors(workerResults, workerPageErrors);
|
|
@@ -4241,7 +4290,7 @@ function segmentEncoderOptions(base, segment) {
|
|
|
4241
4290
|
return {
|
|
4242
4291
|
...base,
|
|
4243
4292
|
lockGopForChunkConcat: true,
|
|
4244
|
-
gopSize: segment.endFrame - segment.startFrame
|
|
4293
|
+
gopSize: Math.min(base.gopSize ?? Infinity, segment.endFrame - segment.startFrame)
|
|
4245
4294
|
};
|
|
4246
4295
|
}
|
|
4247
4296
|
var SegmentEncoderSpawnError = class extends Error {
|
|
@@ -4335,6 +4384,12 @@ function defaultSessionFactory(input) {
|
|
|
4335
4384
|
let probeSession = input.probeSession;
|
|
4336
4385
|
return {
|
|
4337
4386
|
create: async () => {
|
|
4387
|
+
const probeCannotCaptureEveryFrame = Boolean(probeSession?.deRiskFrames?.size);
|
|
4388
|
+
if (probeSession && probeCannotCaptureEveryFrame) {
|
|
4389
|
+
await closeCaptureSession2(probeSession).catch(() => {
|
|
4390
|
+
});
|
|
4391
|
+
probeSession = null;
|
|
4392
|
+
}
|
|
4338
4393
|
const session = probeSession ?? await openSegmentedSession(input);
|
|
4339
4394
|
if (probeSession) {
|
|
4340
4395
|
openSegmentedSessionReuse(input, probeSession);
|
|
@@ -4665,8 +4720,10 @@ function computeSegmentPlanHash(input) {
|
|
|
4665
4720
|
input.motionBlur,
|
|
4666
4721
|
input.pageMotionBlur ?? "",
|
|
4667
4722
|
input.captureQuality === void 0 ? "" : String(input.captureQuality)
|
|
4668
|
-
]
|
|
4669
|
-
|
|
4723
|
+
];
|
|
4724
|
+
const delivery = [input.maxBitrate, input.bufferSize, input.gopSize].map((v) => v ?? "");
|
|
4725
|
+
if (delivery.some((v) => v !== "")) ordered.push(...delivery.map(String));
|
|
4726
|
+
return createHash2("sha256").update(ordered.join(" ")).digest("hex").slice(0, 16);
|
|
4670
4727
|
}
|
|
4671
4728
|
function segmentDirFor(projectRendersDir, planHash) {
|
|
4672
4729
|
return join11(projectRendersDir, ".hf-segments", planHash);
|
|
@@ -4813,7 +4870,7 @@ function resolveVideoCaptureBeyondViewport(videoCount) {
|
|
|
4813
4870
|
|
|
4814
4871
|
// src/services/render/captureCost.ts
|
|
4815
4872
|
import { join as join12 } from "node:path";
|
|
4816
|
-
import { fpsToNumber as
|
|
4873
|
+
import { fpsToNumber as fpsToNumber4 } from "@hyperframes/core";
|
|
4817
4874
|
import {
|
|
4818
4875
|
calculateOptimalWorkers,
|
|
4819
4876
|
captureFrameToBuffer as captureFrameToBuffer3,
|
|
@@ -5017,7 +5074,7 @@ async function runCaptureCalibration(input) {
|
|
|
5017
5074
|
let probeSession = input.probeSession;
|
|
5018
5075
|
let forceScreenshot = input.forceScreenshot;
|
|
5019
5076
|
let lastBrowserConsole = [];
|
|
5020
|
-
const fps =
|
|
5077
|
+
const fps = fpsToNumber4(job.config.fps);
|
|
5021
5078
|
const sessionRef = { current: null };
|
|
5022
5079
|
const runOneCalibration = async (sessionDir, sessionCfg) => {
|
|
5023
5080
|
log.info("Launching browser for capture calibration...");
|
|
@@ -5477,7 +5534,7 @@ function emitFallbackCaptureProfile(recorder, perfSummaries, env = process.env)
|
|
|
5477
5534
|
// src/services/render/videoFrameCoverage.ts
|
|
5478
5535
|
import { parseHTML } from "linkedom";
|
|
5479
5536
|
import {
|
|
5480
|
-
fpsToNumber as
|
|
5537
|
+
fpsToNumber as fpsToNumber5,
|
|
5481
5538
|
normalizeRateSpec,
|
|
5482
5539
|
sourceTimeAt,
|
|
5483
5540
|
toFps
|
|
@@ -5512,7 +5569,7 @@ function resolveVideoCoverageThreshold(envValue = process.env.HF_VIDEO_COVERAGE_
|
|
|
5512
5569
|
return parsed;
|
|
5513
5570
|
}
|
|
5514
5571
|
function expectedFramesForClip(start, end, fps, rounding = "ceil") {
|
|
5515
|
-
const fpsValue =
|
|
5572
|
+
const fpsValue = fpsToNumber5(toFps(fps));
|
|
5516
5573
|
if (!Number.isFinite(start) || !Number.isFinite(end) || !Number.isFinite(fpsValue)) return 0;
|
|
5517
5574
|
if (fpsValue <= 0) return 0;
|
|
5518
5575
|
const duration = Math.max(0, end - start);
|
|
@@ -6774,7 +6831,7 @@ function detectShaderTransitionUsage(html) {
|
|
|
6774
6831
|
}
|
|
6775
6832
|
return false;
|
|
6776
6833
|
}
|
|
6777
|
-
async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath) {
|
|
6834
|
+
async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, downloadDir, tagName, elementIdentity, log, urlByAbsentLocalPath, probes) {
|
|
6778
6835
|
let filePath = src;
|
|
6779
6836
|
if (isHttpUrl(src)) {
|
|
6780
6837
|
if (!existsSync10(downloadDir)) mkdirSync7(downloadDir, { recursive: true });
|
|
@@ -6801,45 +6858,52 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
6801
6858
|
if (error instanceof Error && error.stack) wrapped.stack = error.stack;
|
|
6802
6859
|
return wrapped;
|
|
6803
6860
|
};
|
|
6804
|
-
|
|
6805
|
-
let profile;
|
|
6861
|
+
const probe = () => withMediaProbeSlot(async () => {
|
|
6806
6862
|
try {
|
|
6807
6863
|
await assertMediaPayload(filePath, elementIdentity);
|
|
6808
|
-
profile = await probeMediaProfile2(filePath);
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
if (error instanceof NotMediaPayloadError2) {
|
|
6812
|
-
log?.warn(
|
|
6813
|
-
`[compile] Audio "${elementIdentity}" (${src}) is a text document, not a media file, so the element is dropped from the render. Point it at a rendered media file.`
|
|
6814
|
-
);
|
|
6815
|
-
}
|
|
6816
|
-
return { duration: null, resolvedPath: filePath };
|
|
6817
|
-
}
|
|
6818
|
-
throw withSrcContext(error);
|
|
6819
|
-
}
|
|
6820
|
-
assertAssetMediaTypeProfile(tagName === "video" ? "video" : "audio", profile, elementIdentity);
|
|
6821
|
-
let metadata;
|
|
6822
|
-
if (tagName === "video") {
|
|
6823
|
-
try {
|
|
6824
|
-
metadata = await extractMediaMetadata(filePath);
|
|
6825
|
-
} catch (error) {
|
|
6826
|
-
throw withSrcContext(error);
|
|
6864
|
+
const profile = await probeMediaProfile2(filePath);
|
|
6865
|
+
if (tagName === "video") {
|
|
6866
|
+
return { profile, fileDuration: (await extractMediaMetadata(filePath)).durationSeconds };
|
|
6827
6867
|
}
|
|
6828
|
-
} else {
|
|
6829
6868
|
try {
|
|
6830
|
-
|
|
6869
|
+
return { profile, fileDuration: (await extractAudioMetadata(filePath)).durationSeconds };
|
|
6831
6870
|
} catch {
|
|
6832
|
-
return {
|
|
6871
|
+
return { profile, fileDuration: null };
|
|
6833
6872
|
}
|
|
6873
|
+
} catch (error) {
|
|
6874
|
+
return { error };
|
|
6834
6875
|
}
|
|
6835
|
-
const fileDuration = metadata.durationSeconds;
|
|
6836
|
-
const duration = resolveNaturalMediaTimelineDurationFromValues(
|
|
6837
|
-
fileDuration,
|
|
6838
|
-
mediaStart,
|
|
6839
|
-
playbackRate
|
|
6840
|
-
);
|
|
6841
|
-
return { duration, resolvedPath: filePath };
|
|
6842
6876
|
});
|
|
6877
|
+
const key = `${tagName}\0${filePath}`;
|
|
6878
|
+
let probed = probes?.get(key);
|
|
6879
|
+
if (!probed) {
|
|
6880
|
+
probed = probe();
|
|
6881
|
+
probes?.set(key, probed);
|
|
6882
|
+
}
|
|
6883
|
+
const outcome = await probed;
|
|
6884
|
+
if ("error" in outcome) {
|
|
6885
|
+
if (tagName !== "video") {
|
|
6886
|
+
if (outcome.error instanceof NotMediaPayloadError2) {
|
|
6887
|
+
log?.warn(
|
|
6888
|
+
`[compile] Audio "${elementIdentity}" (${src}) is a text document, not a media file, so the element is dropped from the render. Point it at a rendered media file.`
|
|
6889
|
+
);
|
|
6890
|
+
}
|
|
6891
|
+
return { duration: null, resolvedPath: filePath };
|
|
6892
|
+
}
|
|
6893
|
+
throw withSrcContext(outcome.error);
|
|
6894
|
+
}
|
|
6895
|
+
assertAssetMediaTypeProfile(
|
|
6896
|
+
tagName === "video" ? "video" : "audio",
|
|
6897
|
+
outcome.profile,
|
|
6898
|
+
elementIdentity
|
|
6899
|
+
);
|
|
6900
|
+
if (outcome.fileDuration === null) return { duration: null, resolvedPath: filePath };
|
|
6901
|
+
const duration = resolveNaturalMediaTimelineDurationFromValues(
|
|
6902
|
+
outcome.fileDuration,
|
|
6903
|
+
mediaStart,
|
|
6904
|
+
playbackRate
|
|
6905
|
+
);
|
|
6906
|
+
return { duration, resolvedPath: filePath };
|
|
6843
6907
|
}
|
|
6844
6908
|
function markInferredVariableMediaDurations(html, unresolvedMedia) {
|
|
6845
6909
|
const unresolvedIds = new Set(unresolvedMedia.map((element) => element.id));
|
|
@@ -6853,7 +6917,7 @@ function markInferredVariableMediaDurations(html, unresolvedMedia) {
|
|
|
6853
6917
|
}
|
|
6854
6918
|
return changed ? document2.toString() : html;
|
|
6855
6919
|
}
|
|
6856
|
-
async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocalPath) {
|
|
6920
|
+
async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocalPath, probes) {
|
|
6857
6921
|
const { html: staticCompiled, unresolved } = compileTimingAttrs(html);
|
|
6858
6922
|
const mediaUnresolved = unresolved.filter(
|
|
6859
6923
|
(el) => (el.tagName === "video" || el.tagName === "audio") && el.src
|
|
@@ -6870,7 +6934,8 @@ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocal
|
|
|
6870
6934
|
el.tagName,
|
|
6871
6935
|
el.id,
|
|
6872
6936
|
log,
|
|
6873
|
-
urlByAbsentLocalPath
|
|
6937
|
+
urlByAbsentLocalPath,
|
|
6938
|
+
probes
|
|
6874
6939
|
).then(({ duration }) => ({ id: el.id, duration }))
|
|
6875
6940
|
)
|
|
6876
6941
|
);
|
|
@@ -6891,7 +6956,9 @@ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocal
|
|
|
6891
6956
|
downloadDir,
|
|
6892
6957
|
el.tagName,
|
|
6893
6958
|
el.id,
|
|
6894
|
-
log
|
|
6959
|
+
log,
|
|
6960
|
+
void 0,
|
|
6961
|
+
probes
|
|
6895
6962
|
);
|
|
6896
6963
|
return { id: el.id, tagName: el.tagName, duration: el.duration, maxDuration, src: el.src };
|
|
6897
6964
|
})
|
|
@@ -6913,7 +6980,7 @@ async function compileHtmlFile(html, baseDir, downloadDir, log, urlByAbsentLocal
|
|
|
6913
6980
|
compiledHtml = compiledHtml.replace(/(<audio\b[^>]*)\s+crossorigin(?:=["'][^"']*["'])?/gi, "$1");
|
|
6914
6981
|
return { html: compiledHtml, unresolvedCompositions };
|
|
6915
6982
|
}
|
|
6916
|
-
async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath) {
|
|
6983
|
+
async function parseSubCompositions(html, projectDir, downloadDir, visited = /* @__PURE__ */ new Set(), urlByAbsentLocalPath, probes) {
|
|
6917
6984
|
const subCompositions = /* @__PURE__ */ new Map();
|
|
6918
6985
|
const { document: document2 } = parseHTML5(html);
|
|
6919
6986
|
const compEls = document2.querySelectorAll("[data-composition-src]");
|
|
@@ -6941,14 +7008,16 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
6941
7008
|
dirname7(item.filePath),
|
|
6942
7009
|
downloadDir,
|
|
6943
7010
|
void 0,
|
|
6944
|
-
urlByAbsentLocalPath
|
|
7011
|
+
urlByAbsentLocalPath,
|
|
7012
|
+
probes
|
|
6945
7013
|
);
|
|
6946
7014
|
const nested = await parseSubCompositions(
|
|
6947
7015
|
compiledSub,
|
|
6948
7016
|
projectDir,
|
|
6949
7017
|
downloadDir,
|
|
6950
7018
|
item.nestedVisited,
|
|
6951
|
-
urlByAbsentLocalPath
|
|
7019
|
+
urlByAbsentLocalPath,
|
|
7020
|
+
probes
|
|
6952
7021
|
);
|
|
6953
7022
|
return {
|
|
6954
7023
|
srcPath: item.srcPath,
|
|
@@ -7718,19 +7787,22 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7718
7787
|
);
|
|
7719
7788
|
const rawHtml = rewriteUnresolvableGsapToCdn(entryHtml, projectDir);
|
|
7720
7789
|
assertSubCompositionsUsable(rawHtml, projectDir);
|
|
7790
|
+
const probes = /* @__PURE__ */ new Map();
|
|
7721
7791
|
const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
|
|
7722
7792
|
rawHtml,
|
|
7723
7793
|
projectDir,
|
|
7724
7794
|
downloadDir,
|
|
7725
7795
|
options.log,
|
|
7726
|
-
options.urlByAbsentLocalPath
|
|
7796
|
+
options.urlByAbsentLocalPath,
|
|
7797
|
+
probes
|
|
7727
7798
|
);
|
|
7728
7799
|
const { subCompositions } = await parseSubCompositions(
|
|
7729
7800
|
compiledHtml,
|
|
7730
7801
|
projectDir,
|
|
7731
7802
|
downloadDir,
|
|
7732
7803
|
void 0,
|
|
7733
|
-
options.urlByAbsentLocalPath
|
|
7804
|
+
options.urlByAbsentLocalPath,
|
|
7805
|
+
probes
|
|
7734
7806
|
);
|
|
7735
7807
|
const fullHtml = ensureFullDocument(compiledHtml);
|
|
7736
7808
|
const inlinedHtml = inlineSubCompositions(
|
|
@@ -7833,9 +7905,12 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
7833
7905
|
}
|
|
7834
7906
|
const { videos, audios, images, droppedMediaIds } = collectRenderMedia(html);
|
|
7835
7907
|
const masterFxChain = parseMasterFxChain(html);
|
|
7908
|
+
const advised = /* @__PURE__ */ new Set();
|
|
7836
7909
|
for (const video of videos) {
|
|
7837
7910
|
if (isHttpUrl(video.src)) continue;
|
|
7838
7911
|
const videoPath = resolve7(projectDir, video.src);
|
|
7912
|
+
if (advised.has(videoPath)) continue;
|
|
7913
|
+
advised.add(videoPath);
|
|
7839
7914
|
const reencode = `ffmpeg -i "${video.src}" -c:v libx264 -r 30 -g 30 -keyint_min 30 -movflags +faststart -c:a copy output.mp4`;
|
|
7840
7915
|
(async () => {
|
|
7841
7916
|
const metadata = await withMediaProbeSlot(() => extractMediaMetadata(videoPath));
|
|
@@ -8386,13 +8461,14 @@ import {
|
|
|
8386
8461
|
} from "@hyperframes/engine";
|
|
8387
8462
|
import {
|
|
8388
8463
|
MEDIA_RENDER_ID_ATTR as MEDIA_RENDER_ID_ATTR2,
|
|
8389
|
-
fpsToNumber as
|
|
8464
|
+
fpsToNumber as fpsToNumber6,
|
|
8390
8465
|
normalizeRateSpec as normalizeRateSpec3,
|
|
8391
8466
|
readMediaStart as readMediaStart2,
|
|
8392
8467
|
shiftRateLane as shiftRateLane2,
|
|
8393
8468
|
sourceTimeAt as sourceTimeAt3,
|
|
8394
8469
|
timeAtSourceTime
|
|
8395
8470
|
} from "@hyperframes/core";
|
|
8471
|
+
import { parseHostVariableValues as parseHostVariableValues2, readDeclaredDefaults as readDeclaredDefaults2 } from "@hyperframes/core/compiler";
|
|
8396
8472
|
import { extractMediaSrcMutations } from "@hyperframes/parsers";
|
|
8397
8473
|
|
|
8398
8474
|
// src/services/render/stages/probeFailures.ts
|
|
@@ -8430,8 +8506,15 @@ function hasAutoStartVideos(html) {
|
|
|
8430
8506
|
const { document: document2 } = parseHTML6(html);
|
|
8431
8507
|
return document2.querySelector("video[data-hf-auto-start]") !== null;
|
|
8432
8508
|
}
|
|
8509
|
+
function variableHasValue(element, variableId, variables) {
|
|
8510
|
+
if (variables && Object.hasOwn(variables, variableId)) return true;
|
|
8511
|
+
for (let scope = element.parentElement; scope; scope = scope.parentElement) {
|
|
8512
|
+
if (Object.hasOwn(parseHostVariableValues2(scope), variableId)) return true;
|
|
8513
|
+
if (Object.hasOwn(readDeclaredDefaults2(scope), variableId)) return true;
|
|
8514
|
+
}
|
|
8515
|
+
return Object.hasOwn(readDeclaredDefaults2(element.ownerDocument.documentElement), variableId);
|
|
8516
|
+
}
|
|
8433
8517
|
function hasVariableBoundMedia(html, variables) {
|
|
8434
|
-
if (!variables || Object.keys(variables).length === 0) return false;
|
|
8435
8518
|
const { document: document2 } = parseHTML6(html);
|
|
8436
8519
|
return Array.from(
|
|
8437
8520
|
document2.querySelectorAll(
|
|
@@ -8439,7 +8522,7 @@ function hasVariableBoundMedia(html, variables) {
|
|
|
8439
8522
|
)
|
|
8440
8523
|
).some((element) => {
|
|
8441
8524
|
const variableId = element.getAttribute("data-var-src")?.trim();
|
|
8442
|
-
return Boolean(variableId &&
|
|
8525
|
+
return Boolean(variableId && variableHasValue(element, variableId, variables));
|
|
8443
8526
|
});
|
|
8444
8527
|
}
|
|
8445
8528
|
function authoredMediaStarts(html) {
|
|
@@ -8865,7 +8948,7 @@ async function runProbeStage(input) {
|
|
|
8865
8948
|
session.page,
|
|
8866
8949
|
composition.audios.map((audio) => audio.id),
|
|
8867
8950
|
composition.duration,
|
|
8868
|
-
|
|
8951
|
+
fpsToNumber6(job.config.fps)
|
|
8869
8952
|
);
|
|
8870
8953
|
assertNotAborted();
|
|
8871
8954
|
if (automation.length > 0) {
|
|
@@ -8928,7 +9011,7 @@ async function runProbeStage(input) {
|
|
|
8928
9011
|
}
|
|
8929
9012
|
const browserProbeMs = Date.now() - probeStart;
|
|
8930
9013
|
const duration = composition.duration;
|
|
8931
|
-
const totalFrames = durationToFrameCount(duration,
|
|
9014
|
+
const totalFrames = durationToFrameCount(duration, fpsToNumber6(job.config.fps));
|
|
8932
9015
|
if (duration <= 0) {
|
|
8933
9016
|
const diagnostics = [];
|
|
8934
9017
|
try {
|
|
@@ -9464,16 +9547,17 @@ function appendAutoDetectedVideoAudio(composition, extracted) {
|
|
|
9464
9547
|
|
|
9465
9548
|
// src/services/render/stages/audioStage.ts
|
|
9466
9549
|
import { join as join20 } from "node:path";
|
|
9467
|
-
import { rmSync as rmSync10 } from "node:fs";
|
|
9468
9550
|
import {
|
|
9469
9551
|
MIXED_AUDIO_FILENAME,
|
|
9470
9552
|
audioFailureDiagnostics,
|
|
9471
|
-
processCompositionAudio
|
|
9553
|
+
processCompositionAudio,
|
|
9554
|
+
removeScratch as removeScratch2,
|
|
9555
|
+
collectScratchLeftovers
|
|
9472
9556
|
} from "@hyperframes/engine";
|
|
9473
9557
|
|
|
9474
9558
|
// src/services/render/audioPadTrim.ts
|
|
9475
9559
|
import { spawn } from "node:child_process";
|
|
9476
|
-
import { mkdtempSync as mkdtempSync3
|
|
9560
|
+
import { mkdtempSync as mkdtempSync3 } from "node:fs";
|
|
9477
9561
|
import { dirname as dirname8, join as join19 } from "node:path";
|
|
9478
9562
|
import {
|
|
9479
9563
|
DEFAULT_CONFIG,
|
|
@@ -9482,6 +9566,7 @@ import {
|
|
|
9482
9566
|
extractAudioMetadata as extractAudioMetadata3,
|
|
9483
9567
|
getFfprobeBinary,
|
|
9484
9568
|
ManagedChildProcess,
|
|
9569
|
+
removeScratch,
|
|
9485
9570
|
renameWithRetry as renameWithRetry5,
|
|
9486
9571
|
runFfmpeg as runFfmpeg5,
|
|
9487
9572
|
trackChildProcess
|
|
@@ -9677,7 +9762,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
9677
9762
|
error: `audioPadTrim: failed to materialize ${plan2.operation}: ${err instanceof Error ? err.message : String(err)}`
|
|
9678
9763
|
};
|
|
9679
9764
|
} finally {
|
|
9680
|
-
for (const path of plan2.cleanupPaths)
|
|
9765
|
+
for (const path of plan2.cleanupPaths) removeScratch(path);
|
|
9681
9766
|
}
|
|
9682
9767
|
let loweredDb = 0;
|
|
9683
9768
|
if (probeTruePeak) {
|
|
@@ -9740,7 +9825,7 @@ async function enforceAacTruePeak(input) {
|
|
|
9740
9825
|
}
|
|
9741
9826
|
if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
|
|
9742
9827
|
if (measuredPath !== correctedPath) return { success: true };
|
|
9743
|
-
renameWithRetry5(correctedPath, input.audioPath);
|
|
9828
|
+
renameWithRetry5(correctedPath, input.audioPath, { copyFallback: true });
|
|
9744
9829
|
return { success: true, loweredDb: Number((-attenuationDb).toFixed(2)) };
|
|
9745
9830
|
}
|
|
9746
9831
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
@@ -9781,7 +9866,7 @@ async function enforceAacTruePeak(input) {
|
|
|
9781
9866
|
)}`
|
|
9782
9867
|
};
|
|
9783
9868
|
} finally {
|
|
9784
|
-
if (scratchDir)
|
|
9869
|
+
if (scratchDir) removeScratch(scratchDir);
|
|
9785
9870
|
}
|
|
9786
9871
|
}
|
|
9787
9872
|
function failResult(outputPath, target, source, error) {
|
|
@@ -9926,7 +10011,15 @@ function assertAudioNotInterrupted(failures) {
|
|
|
9926
10011
|
])
|
|
9927
10012
|
});
|
|
9928
10013
|
}
|
|
9929
|
-
|
|
10014
|
+
function runAudioStage(input) {
|
|
10015
|
+
return collectScratchLeftovers(
|
|
10016
|
+
() => runAudioStageScoped(input),
|
|
10017
|
+
(left) => {
|
|
10018
|
+
if (left.length > 0) input.log?.warn("[audio] scratch left behind", { count: left.length });
|
|
10019
|
+
}
|
|
10020
|
+
);
|
|
10021
|
+
}
|
|
10022
|
+
async function runAudioStageScoped(input) {
|
|
9930
10023
|
const stageStart = Date.now();
|
|
9931
10024
|
const pcmSourcePath = join20(input.workDir, "audio-source.wav");
|
|
9932
10025
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
@@ -9948,7 +10041,7 @@ async function runAudioStage(input) {
|
|
|
9948
10041
|
}
|
|
9949
10042
|
return { ...result, audioLoweredDb, audioProcessMs: Date.now() - stageStart };
|
|
9950
10043
|
} finally {
|
|
9951
|
-
|
|
10044
|
+
removeScratch2(pcmSourcePath);
|
|
9952
10045
|
}
|
|
9953
10046
|
}
|
|
9954
10047
|
async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
@@ -10428,7 +10521,7 @@ import {
|
|
|
10428
10521
|
initializeSession as initializeSession7,
|
|
10429
10522
|
spawnStreamingEncoder as spawnStreamingEncoder3
|
|
10430
10523
|
} from "@hyperframes/engine";
|
|
10431
|
-
import { fpsToNumber as
|
|
10524
|
+
import { fpsToNumber as fpsToNumber7 } from "@hyperframes/core";
|
|
10432
10525
|
|
|
10433
10526
|
// src/services/hdrImageTransferCache.ts
|
|
10434
10527
|
import { convertTransfer as convertTransfer2 } from "@hyperframes/engine";
|
|
@@ -11102,6 +11195,7 @@ async function runCaptureHdrStage(input) {
|
|
|
11102
11195
|
preset,
|
|
11103
11196
|
effectiveQuality,
|
|
11104
11197
|
effectiveBitrate,
|
|
11198
|
+
deliveryEncoder,
|
|
11105
11199
|
fileServer,
|
|
11106
11200
|
buildCaptureOptions,
|
|
11107
11201
|
createRenderVideoFrameInjector,
|
|
@@ -11163,7 +11257,7 @@ async function runCaptureHdrStage(input) {
|
|
|
11163
11257
|
}
|
|
11164
11258
|
return map;
|
|
11165
11259
|
});
|
|
11166
|
-
const fpsDecimal =
|
|
11260
|
+
const fpsDecimal = fpsToNumber7(job.config.fps);
|
|
11167
11261
|
const transitionRanges = transitionMeta.map((t) => ({
|
|
11168
11262
|
...t,
|
|
11169
11263
|
startFrame: Math.floor(t.time * fpsDecimal),
|
|
@@ -11191,6 +11285,7 @@ async function runCaptureHdrStage(input) {
|
|
|
11191
11285
|
preset: preset.preset,
|
|
11192
11286
|
quality: effectiveQuality,
|
|
11193
11287
|
bitrate: effectiveBitrate,
|
|
11288
|
+
...deliveryEncoder,
|
|
11194
11289
|
pixelFormat: preset.pixelFormat,
|
|
11195
11290
|
hdr: preset.hdr,
|
|
11196
11291
|
rawInputFormat: "rgb48le"
|
|
@@ -11250,7 +11345,7 @@ async function runCaptureHdrStage(input) {
|
|
|
11250
11345
|
beforeCaptureHook: domSession.onBeforeCapture,
|
|
11251
11346
|
width,
|
|
11252
11347
|
height,
|
|
11253
|
-
fps:
|
|
11348
|
+
fps: fpsToNumber7(job.config.fps),
|
|
11254
11349
|
compositeTransfer,
|
|
11255
11350
|
nativeHdrImageIds,
|
|
11256
11351
|
hdrImageBuffers,
|
|
@@ -11394,7 +11489,7 @@ import {
|
|
|
11394
11489
|
mkdirSync as mkdirSync9,
|
|
11395
11490
|
readdirSync as readdirSync4,
|
|
11396
11491
|
readFileSync as readFileSync8,
|
|
11397
|
-
rmSync as
|
|
11492
|
+
rmSync as rmSync9,
|
|
11398
11493
|
statSync as statSync7,
|
|
11399
11494
|
writeFileSync as writeFileSync5
|
|
11400
11495
|
} from "node:fs";
|
|
@@ -11533,7 +11628,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
11533
11628
|
fileSize
|
|
11534
11629
|
};
|
|
11535
11630
|
} finally {
|
|
11536
|
-
|
|
11631
|
+
rmSync9(input.palettePath, { force: true });
|
|
11537
11632
|
}
|
|
11538
11633
|
}
|
|
11539
11634
|
function startEncodeProgress(job, stage, onProgress) {
|
|
@@ -11557,6 +11652,8 @@ function videoEncoderOptions(input, engineCfg) {
|
|
|
11557
11652
|
preset: preset.preset,
|
|
11558
11653
|
quality: input.effectiveQuality,
|
|
11559
11654
|
bitrate: input.effectiveBitrate,
|
|
11655
|
+
maxBitrate: input.maxBitrate,
|
|
11656
|
+
bufferSize: input.bufferSize,
|
|
11560
11657
|
pixelFormat: preset.pixelFormat,
|
|
11561
11658
|
vp9CpuUsed: engineCfg.vp9CpuUsed,
|
|
11562
11659
|
useGpu: job.config.useGpu,
|
|
@@ -12831,6 +12928,7 @@ function resolveRenderDebugDir() {
|
|
|
12831
12928
|
}
|
|
12832
12929
|
async function executeRenderJob(job, projectDir, outputPath, progressSink, abortSignal, assertRenderActive) {
|
|
12833
12930
|
validateHlsRenderConfig(job.config);
|
|
12931
|
+
validateDeliveryEncoderConfig(job.config);
|
|
12834
12932
|
const debugDir = resolveRenderDebugDir();
|
|
12835
12933
|
const outputDir = dirname11(outputPath);
|
|
12836
12934
|
if (!existsSync16(outputDir)) mkdirSync10(outputDir, { recursive: true });
|
|
@@ -12851,7 +12949,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
12851
12949
|
log.info("KEEP_TEMP=1, leaving workDir on disk for inspection", { workDir });
|
|
12852
12950
|
return;
|
|
12853
12951
|
}
|
|
12854
|
-
|
|
12952
|
+
rmSync10(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
12855
12953
|
});
|
|
12856
12954
|
try {
|
|
12857
12955
|
await executeRenderPipeline({
|
|
@@ -12909,11 +13007,14 @@ async function executeRenderPipeline(input) {
|
|
|
12909
13007
|
const isGif = outputFormat === "gif";
|
|
12910
13008
|
const isHls = outputFormat === "hls";
|
|
12911
13009
|
const hlsSegmentSeconds = isHls ? resolveHlsSegmentSeconds(job.config) : void 0;
|
|
12912
|
-
const
|
|
12913
|
-
outputFormat,
|
|
12914
|
-
job.config.fps,
|
|
12915
|
-
job.config.hlsSegmentSeconds
|
|
12916
|
-
|
|
13010
|
+
const deliveryEncoder = resolveDeliveryEncoderOptions({
|
|
13011
|
+
format: outputFormat,
|
|
13012
|
+
fps: job.config.fps,
|
|
13013
|
+
hlsSegmentSeconds: job.config.hlsSegmentSeconds,
|
|
13014
|
+
keyframeIntervalSeconds: job.config.keyframeIntervalSeconds,
|
|
13015
|
+
maxBitrate: job.config.maxBitrate,
|
|
13016
|
+
bufferSize: job.config.bufferSize
|
|
13017
|
+
});
|
|
12917
13018
|
const artifactTransaction = new ArtifactTransaction(
|
|
12918
13019
|
outputPath,
|
|
12919
13020
|
isPngSequence || isHls ? "directory" : "file",
|
|
@@ -13153,7 +13254,7 @@ async function executeRenderPipeline(input) {
|
|
|
13153
13254
|
validateRenderDuration({
|
|
13154
13255
|
duration: probeResult.duration,
|
|
13155
13256
|
totalFrames,
|
|
13156
|
-
fps:
|
|
13257
|
+
fps: fpsToNumber8(job.config.fps)
|
|
13157
13258
|
});
|
|
13158
13259
|
perfStages.browserProbeMs = probeResult.browserProbeMs;
|
|
13159
13260
|
perfStages.compileMs = Date.now() - stage1Start;
|
|
@@ -13996,6 +14097,7 @@ async function executeRenderPipeline(input) {
|
|
|
13996
14097
|
preset,
|
|
13997
14098
|
effectiveQuality,
|
|
13998
14099
|
effectiveBitrate,
|
|
14100
|
+
deliveryEncoder,
|
|
13999
14101
|
fileServer: activeFileServer,
|
|
14000
14102
|
buildCaptureOptions,
|
|
14001
14103
|
createRenderVideoFrameInjector,
|
|
@@ -14020,9 +14122,7 @@ async function executeRenderPipeline(input) {
|
|
|
14020
14122
|
} else {
|
|
14021
14123
|
let streamingHandled = false;
|
|
14022
14124
|
if (capturePlan.kind === "sdr_segmented") {
|
|
14023
|
-
const segmentedPlan = capturePlan;
|
|
14024
14125
|
const captureFrameStart = Date.now();
|
|
14025
|
-
resetCaptureAttemptProgress(job);
|
|
14026
14126
|
const segmentFrames = resolveSegmentFrames(process.env);
|
|
14027
14127
|
const segmentImageFormat = captureOptions.format || "jpeg";
|
|
14028
14128
|
const segmentPlanHash = computeSegmentPlanHash({
|
|
@@ -14037,6 +14137,9 @@ async function executeRenderPipeline(input) {
|
|
|
14037
14137
|
preset: preset.preset,
|
|
14038
14138
|
quality: effectiveQuality,
|
|
14039
14139
|
bitrate: effectiveBitrate,
|
|
14140
|
+
maxBitrate: deliveryEncoder.maxBitrate,
|
|
14141
|
+
bufferSize: deliveryEncoder.bufferSize,
|
|
14142
|
+
gopSize: deliveryEncoder.gopSize,
|
|
14040
14143
|
pixelFormat: preset.pixelFormat,
|
|
14041
14144
|
imageFormat: segmentImageFormat,
|
|
14042
14145
|
useGpu: job.config.useGpu === true,
|
|
@@ -14062,71 +14165,129 @@ async function executeRenderPipeline(input) {
|
|
|
14062
14165
|
});
|
|
14063
14166
|
}
|
|
14064
14167
|
if (completedSegments.size === 0) {
|
|
14065
|
-
|
|
14168
|
+
rmSync10(segmentDir, { recursive: true, force: true });
|
|
14066
14169
|
}
|
|
14067
14170
|
} else {
|
|
14068
|
-
|
|
14171
|
+
rmSync10(segmentDir, { recursive: true, force: true });
|
|
14069
14172
|
}
|
|
14070
|
-
const
|
|
14173
|
+
const emptySegmentManifest = () => ({
|
|
14071
14174
|
version: 1,
|
|
14072
14175
|
planHash: segmentPlanHash,
|
|
14073
14176
|
totalFrames,
|
|
14074
14177
|
segmentFrames,
|
|
14075
14178
|
completed: []
|
|
14179
|
+
});
|
|
14180
|
+
let segmentManifest = readSegmentManifest(segmentDir) ?? emptySegmentManifest();
|
|
14181
|
+
const invokeSegmented = () => {
|
|
14182
|
+
if (capturePlan.kind !== "sdr_segmented") {
|
|
14183
|
+
throw new Error(`Cannot invoke segmented stage with ${capturePlan.kind} plan`);
|
|
14184
|
+
}
|
|
14185
|
+
const segmentedPlan = capturePlan;
|
|
14186
|
+
resetCaptureAttemptProgress(job);
|
|
14187
|
+
return observeRenderStage(
|
|
14188
|
+
observability,
|
|
14189
|
+
"capture_segmented",
|
|
14190
|
+
captureStageObservationData(),
|
|
14191
|
+
() => runCaptureSegmentedStage({
|
|
14192
|
+
fileServer: activeFileServer,
|
|
14193
|
+
workDir,
|
|
14194
|
+
framesDir,
|
|
14195
|
+
videoOnlyPath,
|
|
14196
|
+
job,
|
|
14197
|
+
totalFrames,
|
|
14198
|
+
cfg,
|
|
14199
|
+
plan: segmentedPlan,
|
|
14200
|
+
log,
|
|
14201
|
+
probeSession,
|
|
14202
|
+
outputFormat,
|
|
14203
|
+
streamingEncoderOptions: {
|
|
14204
|
+
fps: job.config.fps,
|
|
14205
|
+
width: captureCompositionWidth ?? width,
|
|
14206
|
+
height: captureCompositionHeight ?? height,
|
|
14207
|
+
codec: preset.codec,
|
|
14208
|
+
preset: preset.preset,
|
|
14209
|
+
quality: effectiveQuality,
|
|
14210
|
+
bitrate: effectiveBitrate,
|
|
14211
|
+
pixelFormat: preset.pixelFormat,
|
|
14212
|
+
vp9CpuUsed: cfg.vp9CpuUsed,
|
|
14213
|
+
useGpu: job.config.useGpu,
|
|
14214
|
+
imageFormat: segmentImageFormat,
|
|
14215
|
+
hdr: preset.hdr,
|
|
14216
|
+
// Each segment sets its own GOP (at most the interval); hls never reaches this route.
|
|
14217
|
+
...deliveryEncoder
|
|
14218
|
+
},
|
|
14219
|
+
buildCaptureOptions,
|
|
14220
|
+
createRenderVideoFrameInjector,
|
|
14221
|
+
abortSignal: executionSignal,
|
|
14222
|
+
assertNotAborted,
|
|
14223
|
+
onProgress,
|
|
14224
|
+
dedupPerfs,
|
|
14225
|
+
segmentFrames,
|
|
14226
|
+
segmentDir,
|
|
14227
|
+
workerCount: segmentedPlan.workerCount,
|
|
14228
|
+
browserRecycleEverySegments: resolveSegmentBrowserRecycle(process.env),
|
|
14229
|
+
completedSegments,
|
|
14230
|
+
onSegmentComplete: (entry) => {
|
|
14231
|
+
segmentManifest.completed = [
|
|
14232
|
+
...segmentManifest.completed.filter((e) => e.index !== entry.index),
|
|
14233
|
+
{ ...entry, completedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
14234
|
+
];
|
|
14235
|
+
writeSegmentManifest(segmentDir, segmentManifest);
|
|
14236
|
+
},
|
|
14237
|
+
updateCaptureObservability
|
|
14238
|
+
})
|
|
14239
|
+
);
|
|
14076
14240
|
};
|
|
14077
|
-
|
|
14078
|
-
|
|
14079
|
-
|
|
14080
|
-
|
|
14081
|
-
|
|
14082
|
-
|
|
14083
|
-
|
|
14084
|
-
|
|
14085
|
-
|
|
14086
|
-
|
|
14087
|
-
|
|
14088
|
-
|
|
14089
|
-
|
|
14090
|
-
|
|
14091
|
-
|
|
14092
|
-
|
|
14093
|
-
|
|
14094
|
-
|
|
14095
|
-
|
|
14096
|
-
|
|
14097
|
-
|
|
14098
|
-
|
|
14099
|
-
|
|
14100
|
-
|
|
14101
|
-
|
|
14102
|
-
|
|
14103
|
-
|
|
14104
|
-
|
|
14105
|
-
|
|
14106
|
-
|
|
14107
|
-
|
|
14108
|
-
|
|
14109
|
-
|
|
14110
|
-
|
|
14111
|
-
|
|
14112
|
-
|
|
14113
|
-
|
|
14114
|
-
|
|
14115
|
-
|
|
14116
|
-
|
|
14117
|
-
|
|
14118
|
-
|
|
14119
|
-
|
|
14120
|
-
|
|
14121
|
-
|
|
14122
|
-
|
|
14123
|
-
|
|
14124
|
-
|
|
14125
|
-
|
|
14126
|
-
|
|
14127
|
-
updateCaptureObservability
|
|
14128
|
-
})
|
|
14129
|
-
);
|
|
14241
|
+
let segmentedRes;
|
|
14242
|
+
try {
|
|
14243
|
+
segmentedRes = await invokeSegmented();
|
|
14244
|
+
} catch (err) {
|
|
14245
|
+
const isVerifyError = isDrawElementVerificationError(err);
|
|
14246
|
+
const isDeStall = isDeRendererStallError(err);
|
|
14247
|
+
if (!isVerifyError && !isDrawElementCaptureError(err) && !isDeStall || err instanceof RenderCancelledError || executionSignal?.aborted === true) {
|
|
14248
|
+
throw err;
|
|
14249
|
+
}
|
|
14250
|
+
deSelfVerifyFallback = isVerifyError;
|
|
14251
|
+
const t = deVerifyFallbackTelemetry(err);
|
|
14252
|
+
deFallbackReason = isVerifyError ? t.reason : isDeStall ? "de_renderer_stall" : "capture_error";
|
|
14253
|
+
deFallbackFailedDb = t.failedDb;
|
|
14254
|
+
deFallbackFrameIndex = t.frameIndex;
|
|
14255
|
+
deFallbackThresholdDb = t.thresholdDb;
|
|
14256
|
+
log.warn(
|
|
14257
|
+
"[Render] drawElement capture failed on the segmented path; re-rendering via screenshot",
|
|
14258
|
+
{
|
|
14259
|
+
error: err instanceof Error ? err.message : String(err)
|
|
14260
|
+
}
|
|
14261
|
+
);
|
|
14262
|
+
observability.checkpoint(
|
|
14263
|
+
"capture_segmented",
|
|
14264
|
+
"drawElement capture failed; retrying with a fresh screenshot session"
|
|
14265
|
+
);
|
|
14266
|
+
if (probeSession) {
|
|
14267
|
+
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
14268
|
+
const orphaned = probeSession;
|
|
14269
|
+
probeSession = null;
|
|
14270
|
+
await closeOrphanedProbeForRetry(orphaned, closeCaptureSession8, log, "segmented");
|
|
14271
|
+
}
|
|
14272
|
+
rmSync10(segmentDir, { recursive: true, force: true });
|
|
14273
|
+
completedSegments = /* @__PURE__ */ new Set();
|
|
14274
|
+
segmentManifest = emptySegmentManifest();
|
|
14275
|
+
dedupPerfs.length = 0;
|
|
14276
|
+
capturePlan = replanAfterFailure(capturePlan, {
|
|
14277
|
+
kind: isVerifyError ? "draw_element_verification" : "draw_element_capture"
|
|
14278
|
+
});
|
|
14279
|
+
syncCapturePlan();
|
|
14280
|
+
updateCaptureObservability({
|
|
14281
|
+
forceScreenshot: capturePlan.forceScreenshot,
|
|
14282
|
+
deSelfVerifyFallback,
|
|
14283
|
+
deFallbackReason,
|
|
14284
|
+
deFallbackFailedDb,
|
|
14285
|
+
deFallbackFrameIndex,
|
|
14286
|
+
deFallbackThresholdDb
|
|
14287
|
+
});
|
|
14288
|
+
segmentedRes = await invokeSegmented();
|
|
14289
|
+
observability.clearFailure("capture_segmented");
|
|
14290
|
+
}
|
|
14130
14291
|
if (segmentedRes.success) {
|
|
14131
14292
|
streamingHandled = true;
|
|
14132
14293
|
workerCount = segmentedRes.workerCount;
|
|
@@ -14146,7 +14307,7 @@ async function executeRenderPipeline(input) {
|
|
|
14146
14307
|
);
|
|
14147
14308
|
updateCaptureObservability({ segmentRetries: segmentedRes.segmentRetries });
|
|
14148
14309
|
if (job.config.keepSegments !== true) {
|
|
14149
|
-
|
|
14310
|
+
rmSync10(segmentDir, { recursive: true, force: true });
|
|
14150
14311
|
}
|
|
14151
14312
|
} else {
|
|
14152
14313
|
capturePlan = replanAfterFailure(capturePlan, { kind: "streaming_unavailable" });
|
|
@@ -14199,7 +14360,7 @@ async function executeRenderPipeline(input) {
|
|
|
14199
14360
|
// assemble stage's `-c copy` segmentation cuts exactly on
|
|
14200
14361
|
// the segment boundary. Every other format leaves the
|
|
14201
14362
|
// encoder's open-GOP output untouched.
|
|
14202
|
-
...
|
|
14363
|
+
...deliveryEncoder
|
|
14203
14364
|
},
|
|
14204
14365
|
buildCaptureOptions,
|
|
14205
14366
|
createRenderVideoFrameInjector,
|
|
@@ -14362,7 +14523,7 @@ async function executeRenderPipeline(input) {
|
|
|
14362
14523
|
engineConfig: cfg,
|
|
14363
14524
|
// Same value the streaming encoder above spreads — the disk and
|
|
14364
14525
|
// chunked-concat encoders need an identical GOP lock for HLS.
|
|
14365
|
-
...
|
|
14526
|
+
...deliveryEncoder,
|
|
14366
14527
|
abortSignal: executionSignal,
|
|
14367
14528
|
assertNotAborted,
|
|
14368
14529
|
onProgress
|
|
@@ -14418,7 +14579,7 @@ async function executeRenderPipeline(input) {
|
|
|
14418
14579
|
"capture_disk",
|
|
14419
14580
|
"drawElement capture failed; retrying with a fresh screenshot session"
|
|
14420
14581
|
);
|
|
14421
|
-
|
|
14582
|
+
rmSync10(framesDir, { recursive: true, force: true });
|
|
14422
14583
|
mkdirSync10(framesDir, { recursive: true });
|
|
14423
14584
|
resetCaptureAttemptProgress(job);
|
|
14424
14585
|
dedupPerfs.length = 0;
|
|
@@ -14516,7 +14677,7 @@ async function executeRenderPipeline(input) {
|
|
|
14516
14677
|
buildArtifactExpectation({
|
|
14517
14678
|
outputFormat,
|
|
14518
14679
|
durationSeconds: job.duration,
|
|
14519
|
-
fps:
|
|
14680
|
+
fps: fpsToNumber8(job.config.fps),
|
|
14520
14681
|
expectedFrames: captureTotalFrames,
|
|
14521
14682
|
width: outputWidth,
|
|
14522
14683
|
height: outputHeight
|
|
@@ -14735,16 +14896,17 @@ import {
|
|
|
14735
14896
|
existsSync as existsSync21,
|
|
14736
14897
|
linkSync,
|
|
14737
14898
|
mkdirSync as mkdirSync14,
|
|
14738
|
-
rmSync as
|
|
14899
|
+
rmSync as rmSync12,
|
|
14739
14900
|
writeFileSync as writeFileSync9
|
|
14740
14901
|
} from "node:fs";
|
|
14741
14902
|
import { extname as extname6, isAbsolute as isAbsolute3, join as join35, relative as relative5, sep as sep2 } from "node:path";
|
|
14742
14903
|
import {
|
|
14743
|
-
fpsToNumber as
|
|
14904
|
+
fpsToNumber as fpsToNumber9,
|
|
14744
14905
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
14745
14906
|
} from "@hyperframes/core";
|
|
14746
14907
|
import {
|
|
14747
14908
|
getEncoderPreset as getEncoderPreset4,
|
|
14909
|
+
keyframeIntervalToGopSize as keyframeIntervalToGopSize3,
|
|
14748
14910
|
normalizeVp9CpuUsed,
|
|
14749
14911
|
resolveConfig as resolveConfig3,
|
|
14750
14912
|
declareContentAddressedUrl,
|
|
@@ -15770,7 +15932,7 @@ import { join as join33 } from "node:path";
|
|
|
15770
15932
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure3, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
15771
15933
|
|
|
15772
15934
|
// src/services/distributed/mp4Stitch.ts
|
|
15773
|
-
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync4, rmSync as
|
|
15935
|
+
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync12, mkdtempSync as mkdtempSync4, rmSync as rmSync11 } from "node:fs";
|
|
15774
15936
|
import { open } from "node:fs/promises";
|
|
15775
15937
|
import { renameWithRetry as renameWithRetry6 } from "@hyperframes/engine";
|
|
15776
15938
|
import { dirname as dirname13, join as join32 } from "node:path";
|
|
@@ -16148,7 +16310,7 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
|
16148
16310
|
renameWithRetry6(join32(workDir, "out.mp4"), outputPath);
|
|
16149
16311
|
} finally {
|
|
16150
16312
|
out.destroy();
|
|
16151
|
-
|
|
16313
|
+
rmSync11(workDir, { recursive: true, force: true });
|
|
16152
16314
|
}
|
|
16153
16315
|
}
|
|
16154
16316
|
|
|
@@ -16656,7 +16818,7 @@ var FREEZE_OWNED_PLAN_FILES = [
|
|
|
16656
16818
|
];
|
|
16657
16819
|
function removeFreezeOwnedPlanFiles(planDir) {
|
|
16658
16820
|
for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
|
|
16659
|
-
|
|
16821
|
+
rmSync12(join35(planDir, relativePath), { force: true });
|
|
16660
16822
|
}
|
|
16661
16823
|
}
|
|
16662
16824
|
function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
@@ -16716,6 +16878,9 @@ function buildLockedRenderConfig(input) {
|
|
|
16716
16878
|
preset,
|
|
16717
16879
|
crf: config.crf,
|
|
16718
16880
|
bitrate: config.bitrate,
|
|
16881
|
+
maxBitrate: config.maxBitrate,
|
|
16882
|
+
bufferSize: config.bufferSize,
|
|
16883
|
+
keyframeIntervalFrames: config.keyframeIntervalSeconds === void 0 ? void 0 : keyframeIntervalToGopSize3({ num: config.fps, den: 1 }, config.keyframeIntervalSeconds),
|
|
16719
16884
|
...locksVp9CpuUsed,
|
|
16720
16885
|
// GOP === chunkSize so every chunk's first frame is an IDR keyframe and
|
|
16721
16886
|
// ffmpeg concat-copy round-trips losslessly.
|
|
@@ -16867,7 +17032,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
16867
17032
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
16868
17033
|
}
|
|
16869
17034
|
const workDir = join35(planDir, ".plan-work");
|
|
16870
|
-
|
|
17035
|
+
rmSync12(workDir, { recursive: true, force: true });
|
|
16871
17036
|
mkdirSync14(workDir, { recursive: true });
|
|
16872
17037
|
const compiledDir = join35(workDir, "compiled");
|
|
16873
17038
|
mkdirSync14(compiledDir, { recursive: true });
|
|
@@ -16960,7 +17125,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
16960
17125
|
validateDistributedDuration({
|
|
16961
17126
|
duration: probeResult.duration,
|
|
16962
17127
|
totalFrames,
|
|
16963
|
-
fps:
|
|
17128
|
+
fps: fpsToNumber9(job.config.fps)
|
|
16964
17129
|
});
|
|
16965
17130
|
if (probeResult.fileServer) closeFileServerSafely(probeResult.fileServer, "plan", log);
|
|
16966
17131
|
if (probeResult.probeSession) {
|
|
@@ -17074,15 +17239,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
17074
17239
|
logPlanStage(log, "extract", stageStarted);
|
|
17075
17240
|
const stagedVideoFrames = join35(compiledDir, "__hyperframes_video_frames");
|
|
17076
17241
|
const videoFramesDst = join35(planDir, "video-frames");
|
|
17077
|
-
if (existsSync21(videoFramesDst))
|
|
17242
|
+
if (existsSync21(videoFramesDst)) rmSync12(videoFramesDst, { recursive: true, force: true });
|
|
17078
17243
|
const videoSourcesDir = join35(planDir, "video-sources");
|
|
17079
|
-
|
|
17244
|
+
rmSync12(videoSourcesDir, { recursive: true, force: true });
|
|
17080
17245
|
if (existsSync21(stagedVideoFrames)) {
|
|
17081
17246
|
renameWithRetry7(stagedVideoFrames, videoFramesDst);
|
|
17082
17247
|
} else {
|
|
17083
17248
|
mkdirSync14(videoFramesDst, { recursive: true });
|
|
17084
17249
|
}
|
|
17085
|
-
if (existsSync21(finalCompiledDir))
|
|
17250
|
+
if (existsSync21(finalCompiledDir)) rmSync12(finalCompiledDir, { recursive: true, force: true });
|
|
17086
17251
|
renameWithRetry7(compiledDir, finalCompiledDir);
|
|
17087
17252
|
const shipVideoSource = createVideoSourceShipper({
|
|
17088
17253
|
planDir,
|
|
@@ -17250,7 +17415,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
17250
17415
|
format: config.format
|
|
17251
17416
|
};
|
|
17252
17417
|
try {
|
|
17253
|
-
|
|
17418
|
+
rmSync12(workDir, { recursive: true, force: true });
|
|
17254
17419
|
} catch (err) {
|
|
17255
17420
|
log.warn("[plan] failed to remove temp work dir", {
|
|
17256
17421
|
workDir,
|
|
@@ -17401,6 +17566,20 @@ function validateDistributedRenderConfig(config) {
|
|
|
17401
17566
|
`must look like "10M" or "5000k"; got ${JSON.stringify(config.bitrate)}`
|
|
17402
17567
|
);
|
|
17403
17568
|
}
|
|
17569
|
+
try {
|
|
17570
|
+
validateDeliveryEncoderConfig({
|
|
17571
|
+
format: config.format,
|
|
17572
|
+
fps: { num: config.fps, den: 1 },
|
|
17573
|
+
keyframeIntervalSeconds: config.keyframeIntervalSeconds,
|
|
17574
|
+
maxBitrate: config.maxBitrate,
|
|
17575
|
+
bufferSize: config.bufferSize
|
|
17576
|
+
});
|
|
17577
|
+
} catch (error) {
|
|
17578
|
+
throw new InvalidConfigError(
|
|
17579
|
+
"config.maxBitrate",
|
|
17580
|
+
error instanceof Error ? error.message : String(error)
|
|
17581
|
+
);
|
|
17582
|
+
}
|
|
17404
17583
|
if (config.chunkSize !== void 0) {
|
|
17405
17584
|
if (!Number.isInteger(config.chunkSize) || config.chunkSize < 1) {
|
|
17406
17585
|
throw new InvalidConfigError(
|
|
@@ -17580,6 +17759,12 @@ function assertOptionalInteger(options, field, min = 0) {
|
|
|
17580
17759
|
throw new Error(`Render request ${field} must be an integer >= ${min}`);
|
|
17581
17760
|
}
|
|
17582
17761
|
}
|
|
17762
|
+
function assertOptionalPositiveNumber(options, field) {
|
|
17763
|
+
const value = options[field];
|
|
17764
|
+
if (value !== void 0 && !(typeof value === "number" && value > 0)) {
|
|
17765
|
+
throw new Error(`Render request ${field} must be a positive number`);
|
|
17766
|
+
}
|
|
17767
|
+
}
|
|
17583
17768
|
function assertOptionalString(options, field) {
|
|
17584
17769
|
if (options[field] !== void 0 && typeof options[field] !== "string") {
|
|
17585
17770
|
throw new Error(`Render request ${field} must be a string`);
|
|
@@ -17627,10 +17812,11 @@ function assertRequestOptionScalars(options) {
|
|
|
17627
17812
|
assertOptionalInteger(options, "workers", 1);
|
|
17628
17813
|
assertOptionalInteger(options, "maxRenderMs");
|
|
17629
17814
|
assertOptionalInteger(options, "crf");
|
|
17815
|
+
assertOptionalPositiveNumber(options, "keyframeIntervalSeconds");
|
|
17630
17816
|
for (const field of ["useGpu", "debug", "outputResolutionAspectAgnostic"]) {
|
|
17631
17817
|
assertOptionalBoolean(options, field);
|
|
17632
17818
|
}
|
|
17633
|
-
for (const field of ["entryFile", "videoBitrate"]) {
|
|
17819
|
+
for (const field of ["entryFile", "videoBitrate", "maxBitrate", "bufferSize"]) {
|
|
17634
17820
|
assertOptionalString(options, field);
|
|
17635
17821
|
}
|
|
17636
17822
|
assertOptionalEnum(options, "strictness", ["strict", "best-effort"]);
|
|
@@ -17733,6 +17919,9 @@ function distributedConfigFromRequest(request, runtime = {}) {
|
|
|
17733
17919
|
crf: options.crf,
|
|
17734
17920
|
pageMotionBlur: options.pageMotionBlur,
|
|
17735
17921
|
bitrate: options.videoBitrate,
|
|
17922
|
+
keyframeIntervalSeconds: options.keyframeIntervalSeconds,
|
|
17923
|
+
maxBitrate: options.maxBitrate,
|
|
17924
|
+
bufferSize: options.bufferSize,
|
|
17736
17925
|
videoFrameFormat: options.videoFrameFormat,
|
|
17737
17926
|
outputResolution: options.outputResolution,
|
|
17738
17927
|
outputResolutionAspectAgnostic: options.outputResolutionAspectAgnostic,
|
|
@@ -17780,6 +17969,9 @@ function renderRequestFromDistributedConfig(input) {
|
|
|
17780
17969
|
...optionalProperty("crf", config.crf),
|
|
17781
17970
|
...optionalProperty("pageMotionBlur", config.pageMotionBlur),
|
|
17782
17971
|
...optionalProperty("videoBitrate", config.bitrate),
|
|
17972
|
+
...optionalProperty("keyframeIntervalSeconds", config.keyframeIntervalSeconds),
|
|
17973
|
+
...optionalProperty("maxBitrate", config.maxBitrate),
|
|
17974
|
+
...optionalProperty("bufferSize", config.bufferSize),
|
|
17783
17975
|
...optionalProperty("videoFrameFormat", config.videoFrameFormat),
|
|
17784
17976
|
...optionalProperty("outputResolution", config.outputResolution),
|
|
17785
17977
|
...optionalProperty("outputResolutionAspectAgnostic", config.outputResolutionAspectAgnostic),
|
|
@@ -17825,7 +18017,7 @@ import {
|
|
|
17825
18017
|
statSync as statSync10,
|
|
17826
18018
|
mkdtempSync as mkdtempSync5,
|
|
17827
18019
|
writeFileSync as writeFileSync10,
|
|
17828
|
-
rmSync as
|
|
18020
|
+
rmSync as rmSync13,
|
|
17829
18021
|
createReadStream as createReadStream5
|
|
17830
18022
|
} from "node:fs";
|
|
17831
18023
|
import { resolve as resolve11, dirname as dirname15, join as join38 } from "node:path";
|
|
@@ -18131,7 +18323,7 @@ function parseRenderOptions(body) {
|
|
|
18131
18323
|
const format = parseServerFormat(body.format);
|
|
18132
18324
|
const outputDynamicRange = parseServerOutputDynamicRange(body.outputDynamicRange) ?? fromRenderHdrMode(parseLegacyServerHdrMode(body.hdrMode));
|
|
18133
18325
|
const encoder = parseEncoderOptions(body, format ?? "mp4");
|
|
18134
|
-
const { crf, videoBitrate, videoFrameFormat } = encoder.ok ? encoder.options : {};
|
|
18326
|
+
const { crf, videoBitrate, videoFrameFormat, keyframeIntervalSeconds, maxBitrate, bufferSize } = encoder.ok ? encoder.options : {};
|
|
18135
18327
|
const { variables, outputResolution, outputResolutionAspectAgnostic } = parseRenderOverrides(body);
|
|
18136
18328
|
return {
|
|
18137
18329
|
outputPath,
|
|
@@ -18141,6 +18333,9 @@ function parseRenderOptions(body) {
|
|
|
18141
18333
|
maxRenderMs,
|
|
18142
18334
|
crf,
|
|
18143
18335
|
videoBitrate,
|
|
18336
|
+
keyframeIntervalSeconds,
|
|
18337
|
+
maxBitrate,
|
|
18338
|
+
bufferSize,
|
|
18144
18339
|
useGpu,
|
|
18145
18340
|
debug,
|
|
18146
18341
|
strictness,
|
|
@@ -18175,6 +18370,9 @@ function buildRenderJobConfig(input, outputPath, log) {
|
|
|
18175
18370
|
maxRenderMs: input.maxRenderMs,
|
|
18176
18371
|
crf: input.crf,
|
|
18177
18372
|
videoBitrate: input.videoBitrate,
|
|
18373
|
+
keyframeIntervalSeconds: input.keyframeIntervalSeconds,
|
|
18374
|
+
maxBitrate: input.maxBitrate,
|
|
18375
|
+
bufferSize: input.bufferSize,
|
|
18178
18376
|
useGpu: input.useGpu,
|
|
18179
18377
|
debug: input.debug,
|
|
18180
18378
|
strictness: input.strictness,
|
|
@@ -18321,7 +18519,7 @@ function createArtifactStore(ttlMs) {
|
|
|
18321
18519
|
function cleanupTempDir(dir, log) {
|
|
18322
18520
|
if (!dir) return;
|
|
18323
18521
|
try {
|
|
18324
|
-
|
|
18522
|
+
rmSync13(dir, { recursive: true, force: true });
|
|
18325
18523
|
} catch (error) {
|
|
18326
18524
|
log.warn("Failed to cleanup temp project dir", {
|
|
18327
18525
|
cleanupProjectDir: dir,
|
|
@@ -18735,7 +18933,7 @@ import {
|
|
|
18735
18933
|
mkdtempSync as mkdtempSync7,
|
|
18736
18934
|
readFileSync as readFileSync14,
|
|
18737
18935
|
readdirSync as readdirSync9,
|
|
18738
|
-
rmSync as
|
|
18936
|
+
rmSync as rmSync15,
|
|
18739
18937
|
statSync as statSync12,
|
|
18740
18938
|
writeFileSync as writeFileSync12
|
|
18741
18939
|
} from "node:fs";
|
|
@@ -18751,7 +18949,7 @@ import {
|
|
|
18751
18949
|
linkSync as linkSync2,
|
|
18752
18950
|
mkdirSync as mkdirSync16,
|
|
18753
18951
|
mkdtempSync as mkdtempSync6,
|
|
18754
|
-
rmSync as
|
|
18952
|
+
rmSync as rmSync14,
|
|
18755
18953
|
statSync as statSync11,
|
|
18756
18954
|
writeFileSync as writeFileSync11
|
|
18757
18955
|
} from "node:fs";
|
|
@@ -18846,7 +19044,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18846
19044
|
}
|
|
18847
19045
|
renameWithRetry8(temporaryPath, destinationPath);
|
|
18848
19046
|
} finally {
|
|
18849
|
-
|
|
19047
|
+
rmSync14(stagingDir, { recursive: true, force: true });
|
|
18850
19048
|
}
|
|
18851
19049
|
}
|
|
18852
19050
|
async commitManifest(manifestBytes) {
|
|
@@ -18863,7 +19061,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18863
19061
|
}
|
|
18864
19062
|
async abort() {
|
|
18865
19063
|
if (!this.#committed) {
|
|
18866
|
-
|
|
19064
|
+
rmSync14(this.temporaryDir, { recursive: true, force: true });
|
|
18867
19065
|
}
|
|
18868
19066
|
}
|
|
18869
19067
|
};
|
|
@@ -19132,7 +19330,7 @@ function writeBlob(sourcePath, destinationPath) {
|
|
|
19132
19330
|
copyFileSync8(sourcePath, temporaryPath);
|
|
19133
19331
|
renameWithRetry9(temporaryPath, destinationPath);
|
|
19134
19332
|
} finally {
|
|
19135
|
-
|
|
19333
|
+
rmSync15(temporaryDir, { recursive: true, force: true });
|
|
19136
19334
|
}
|
|
19137
19335
|
}
|
|
19138
19336
|
function publishBlobFor(absolutePath, digests) {
|
|
@@ -19265,7 +19463,7 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
19265
19463
|
renameWithRetry9(tempDir, planV2Dir);
|
|
19266
19464
|
return resultFromManifest(planV2Dir, publication.manifest);
|
|
19267
19465
|
} catch (error) {
|
|
19268
|
-
|
|
19466
|
+
rmSync15(tempDir, { recursive: true, force: true });
|
|
19269
19467
|
throw error;
|
|
19270
19468
|
}
|
|
19271
19469
|
}
|
|
@@ -19332,7 +19530,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
19332
19530
|
}
|
|
19333
19531
|
throw error;
|
|
19334
19532
|
} finally {
|
|
19335
|
-
|
|
19533
|
+
rmSync15(stagingRoot, { recursive: true, force: true });
|
|
19336
19534
|
}
|
|
19337
19535
|
}
|
|
19338
19536
|
async function planV2(projectDir, config, planV2Dir) {
|
|
@@ -19549,7 +19747,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
19549
19747
|
);
|
|
19550
19748
|
renameWithRetry9(tempDir, destinationDir);
|
|
19551
19749
|
} catch (error) {
|
|
19552
|
-
|
|
19750
|
+
rmSync15(tempDir, { recursive: true, force: true });
|
|
19553
19751
|
throw error;
|
|
19554
19752
|
}
|
|
19555
19753
|
return {
|
|
@@ -19613,7 +19811,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
19613
19811
|
}
|
|
19614
19812
|
|
|
19615
19813
|
// src/services/distributed/planV2Execution.ts
|
|
19616
|
-
import { mkdtempSync as mkdtempSync9, rmSync as
|
|
19814
|
+
import { mkdtempSync as mkdtempSync9, rmSync as rmSync18 } from "node:fs";
|
|
19617
19815
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
19618
19816
|
import { join as join44 } from "node:path";
|
|
19619
19817
|
|
|
@@ -19625,12 +19823,13 @@ import {
|
|
|
19625
19823
|
mkdtempSync as mkdtempSync8,
|
|
19626
19824
|
readFileSync as readFileSync15,
|
|
19627
19825
|
readdirSync as readdirSync10,
|
|
19628
|
-
rmSync as
|
|
19826
|
+
rmSync as rmSync16,
|
|
19629
19827
|
statSync as statSync13,
|
|
19630
19828
|
writeFileSync as writeFileSync13
|
|
19631
19829
|
} from "node:fs";
|
|
19632
19830
|
import { dirname as dirname18, join as join42 } from "node:path";
|
|
19633
19831
|
import {
|
|
19832
|
+
appendRateCapArgs as appendRateCapArgs2,
|
|
19634
19833
|
appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
|
|
19635
19834
|
describeFfmpegFailure as describeFfmpegFailure4,
|
|
19636
19835
|
getEncoderPreset as getEncoderPreset5,
|
|
@@ -19704,7 +19903,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
19704
19903
|
});
|
|
19705
19904
|
} finally {
|
|
19706
19905
|
try {
|
|
19707
|
-
|
|
19906
|
+
rmSync16(workDir, { recursive: true, force: true });
|
|
19708
19907
|
} catch (err) {
|
|
19709
19908
|
log.warn("[assemble] failed to remove work dir", {
|
|
19710
19909
|
workDir,
|
|
@@ -19766,6 +19965,9 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
|
|
|
19766
19965
|
"-r",
|
|
19767
19966
|
fpsArg
|
|
19768
19967
|
];
|
|
19968
|
+
if (encoder.keyframeIntervalFrames !== void 0)
|
|
19969
|
+
args.push("-g", String(encoder.keyframeIntervalFrames));
|
|
19970
|
+
appendRateCapArgs2(args, encoder);
|
|
19769
19971
|
appendSoftwareEncoderThreadsArg(args, "libx264");
|
|
19770
19972
|
return args;
|
|
19771
19973
|
}
|
|
@@ -19902,7 +20104,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
19902
20104
|
return encoderJson;
|
|
19903
20105
|
}
|
|
19904
20106
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
19905
|
-
if (existsSync27(outputPath))
|
|
20107
|
+
if (existsSync27(outputPath)) rmSync16(outputPath, { recursive: true, force: true });
|
|
19906
20108
|
mkdirSync18(outputPath, { recursive: true });
|
|
19907
20109
|
let globalIdx = 0;
|
|
19908
20110
|
for (const chunkDir of chunkPaths) {
|
|
@@ -19947,7 +20149,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
19947
20149
|
|
|
19948
20150
|
// src/services/distributed/renderChunk.ts
|
|
19949
20151
|
import { randomBytes } from "node:crypto";
|
|
19950
|
-
import { existsSync as existsSync28, mkdirSync as mkdirSync19, readFileSync as readFileSync16, readdirSync as readdirSync11, rmSync as
|
|
20152
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync19, readFileSync as readFileSync16, readdirSync as readdirSync11, rmSync as rmSync17, writeFileSync as writeFileSync14 } from "node:fs";
|
|
19951
20153
|
import { cpus as cpus3 } from "node:os";
|
|
19952
20154
|
import { join as join43 } from "node:path";
|
|
19953
20155
|
import {
|
|
@@ -20402,7 +20604,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20402
20604
|
// first frame is an IDR keyframe and concat-copy at assemble time
|
|
20403
20605
|
// round-trips losslessly.
|
|
20404
20606
|
lockGopForChunkConcat: !isPngSequence,
|
|
20405
|
-
gopSize: framesInChunk
|
|
20607
|
+
gopSize: chunkGopFrames(framesInChunk, encoder.keyframeIntervalFrames),
|
|
20608
|
+
maxBitrate: encoder.maxBitrate,
|
|
20609
|
+
bufferSize: encoder.bufferSize
|
|
20406
20610
|
};
|
|
20407
20611
|
const captureStarted = Date.now();
|
|
20408
20612
|
captureMode = await runCaptureWithScreenshotFallback({
|
|
@@ -20474,7 +20678,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20474
20678
|
return observedChunkCaptureMode(capturePerfs);
|
|
20475
20679
|
},
|
|
20476
20680
|
resetForScreenshotRetry: () => {
|
|
20477
|
-
|
|
20681
|
+
rmSync17(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
20478
20682
|
mkdirSync19(framesDir, { recursive: true });
|
|
20479
20683
|
capturePerfs.length = 0;
|
|
20480
20684
|
captureAttempts.length = 0;
|
|
@@ -20517,8 +20721,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20517
20721
|
fps: { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen },
|
|
20518
20722
|
timeoutMs: cfg.ffmpegProcessTimeout
|
|
20519
20723
|
});
|
|
20520
|
-
|
|
20521
|
-
|
|
20724
|
+
rmSync17(videoOnlyPath, { force: true });
|
|
20725
|
+
rmSync17(audioScratchPath, { force: true });
|
|
20522
20726
|
}
|
|
20523
20727
|
encodeStageMs = Date.now() - encodeStarted;
|
|
20524
20728
|
} finally {
|
|
@@ -20568,8 +20772,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
20568
20772
|
writeFileSync14(perfPath, `${JSON.stringify(perfPayload, null, 2)}
|
|
20569
20773
|
`, "utf-8");
|
|
20570
20774
|
try {
|
|
20571
|
-
|
|
20572
|
-
|
|
20775
|
+
rmSync17(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
20776
|
+
rmSync17(videoFramesDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
20573
20777
|
} catch (err) {
|
|
20574
20778
|
log.warn("[renderChunk] failed to remove work dir", {
|
|
20575
20779
|
workDir,
|
|
@@ -20604,7 +20808,7 @@ async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
|
20604
20808
|
materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
|
|
20605
20809
|
return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
|
|
20606
20810
|
} finally {
|
|
20607
|
-
|
|
20811
|
+
rmSync18(workRoot, { recursive: true, force: true });
|
|
20608
20812
|
}
|
|
20609
20813
|
}
|
|
20610
20814
|
async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
@@ -20624,7 +20828,7 @@ async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
|
20624
20828
|
options
|
|
20625
20829
|
);
|
|
20626
20830
|
} finally {
|
|
20627
|
-
|
|
20831
|
+
rmSync18(workRoot, { recursive: true, force: true });
|
|
20628
20832
|
}
|
|
20629
20833
|
}
|
|
20630
20834
|
|