@hyperframes/producer 0.9.14 → 0.9.15
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 +142 -96
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +62 -62
- package/dist/index.js +167 -118
- package/dist/public-server.js +107 -58
- package/dist/services/render/audioPadTrim.d.ts +6 -0
- package/dist/services/render/stages/assembleStage.d.ts +1 -0
- package/dist/services/render/stages/audioStage.d.ts +4 -0
- package/package.json +7 -7
package/dist/distributed.js
CHANGED
|
@@ -8,7 +8,7 @@ import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframe
|
|
|
8
8
|
import {
|
|
9
9
|
copyFileSync as copyFileSync6,
|
|
10
10
|
cpSync as cpSync2,
|
|
11
|
-
existsSync as
|
|
11
|
+
existsSync as existsSync17,
|
|
12
12
|
linkSync,
|
|
13
13
|
mkdirSync as mkdirSync10,
|
|
14
14
|
rmSync as rmSync7,
|
|
@@ -71,7 +71,7 @@ var defaultLogger = createConsoleLogger("info");
|
|
|
71
71
|
// src/services/renderOrchestrator.ts
|
|
72
72
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
73
73
|
import {
|
|
74
|
-
existsSync as
|
|
74
|
+
existsSync as existsSync12,
|
|
75
75
|
mkdirSync as mkdirSync6,
|
|
76
76
|
readdirSync as readdirSync3,
|
|
77
77
|
rmSync as rmSync5,
|
|
@@ -5927,6 +5927,7 @@ import {
|
|
|
5927
5927
|
} from "@hyperframes/engine";
|
|
5928
5928
|
|
|
5929
5929
|
// src/services/render/stages/audioStage.ts
|
|
5930
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
5930
5931
|
import { join as join12 } from "node:path";
|
|
5931
5932
|
import {
|
|
5932
5933
|
MIXED_AUDIO_FILENAME,
|
|
@@ -6060,8 +6061,9 @@ var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
|
|
|
6060
6061
|
var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
|
|
6061
6062
|
var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
|
|
6062
6063
|
var MAX_TRUE_PEAK_CORRECTION_PASSES = 3;
|
|
6064
|
+
var gainFilter = (gainDb) => `volume=${gainDb.toFixed(3)}dB`;
|
|
6063
6065
|
function aacTruePeakCorrectionFilter(targetDurationSeconds, attenuationDb) {
|
|
6064
|
-
return
|
|
6066
|
+
return `${gainFilter(attenuationDb)},apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
|
|
6065
6067
|
}
|
|
6066
6068
|
function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
|
|
6067
6069
|
const delta = targetDurationSeconds - sourceDurationSeconds;
|
|
@@ -6099,7 +6101,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
6099
6101
|
const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
|
|
6100
6102
|
const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
|
|
6101
6103
|
const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
|
|
6102
|
-
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal);
|
|
6104
|
+
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath);
|
|
6103
6105
|
const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg || input.encodeAac ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
|
|
6104
6106
|
const [videoResult, audioResult] = await Promise.allSettled([
|
|
6105
6107
|
probeVideo(input.videoPath),
|
|
@@ -6147,7 +6149,12 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
6147
6149
|
);
|
|
6148
6150
|
try {
|
|
6149
6151
|
for (const step of plan.steps) {
|
|
6150
|
-
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
|
|
6152
|
+
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
|
|
6153
|
+
input.sourcePath ?? input.audioPath,
|
|
6154
|
+
input.outputPath,
|
|
6155
|
+
targetDurationSeconds,
|
|
6156
|
+
step.filter
|
|
6157
|
+
);
|
|
6151
6158
|
if (!ffmpegResult.success) {
|
|
6152
6159
|
return {
|
|
6153
6160
|
success: false,
|
|
@@ -6176,6 +6183,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
6176
6183
|
if (probeTruePeak) {
|
|
6177
6184
|
const correction = await enforceAacTruePeak({
|
|
6178
6185
|
audioPath: input.outputPath,
|
|
6186
|
+
sourcePath: input.sourcePath,
|
|
6179
6187
|
targetDurationSeconds,
|
|
6180
6188
|
signal: input.signal,
|
|
6181
6189
|
probeTruePeak,
|
|
@@ -6211,9 +6219,27 @@ async function constrainAacTruePeak(input) {
|
|
|
6211
6219
|
targetDurationSeconds: input.targetDurationSeconds,
|
|
6212
6220
|
signal: input.signal,
|
|
6213
6221
|
probeTruePeak: input.probeTruePeakDbfs ?? ((audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal })),
|
|
6214
|
-
encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
|
|
6222
|
+
encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath),
|
|
6223
|
+
rewriteSource: input.rewriteSource ?? ((sourcePath, outputPath, filter) => defaultRunFfmpeg(
|
|
6224
|
+
["-i", sourcePath, "-af", filter, "-c:a", "pcm_f32le", "-y", outputPath],
|
|
6225
|
+
timeoutMs,
|
|
6226
|
+
input.signal
|
|
6227
|
+
))
|
|
6215
6228
|
});
|
|
6216
6229
|
}
|
|
6230
|
+
async function cutSource(input, scratchDir, attenuationDb) {
|
|
6231
|
+
if (!input.sourcePath || !input.rewriteSource) return true;
|
|
6232
|
+
const cutPath = join10(scratchDir, "source.wav");
|
|
6233
|
+
try {
|
|
6234
|
+
const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
|
|
6235
|
+
if (!result.success) throw new Error(result.error);
|
|
6236
|
+
renameWithRetry5(cutPath, input.sourcePath, { copyFallback: true });
|
|
6237
|
+
return true;
|
|
6238
|
+
} catch {
|
|
6239
|
+
removeScratch(input.sourcePath);
|
|
6240
|
+
return false;
|
|
6241
|
+
}
|
|
6242
|
+
}
|
|
6217
6243
|
async function enforceAacTruePeak(input) {
|
|
6218
6244
|
let scratchDir;
|
|
6219
6245
|
try {
|
|
@@ -6233,8 +6259,10 @@ async function enforceAacTruePeak(input) {
|
|
|
6233
6259
|
}
|
|
6234
6260
|
if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
|
|
6235
6261
|
if (measuredPath !== correctedPath) return { success: true };
|
|
6262
|
+
const sourceCut = await cutSource(input, scratchDir, attenuationDb);
|
|
6236
6263
|
renameWithRetry5(correctedPath, input.audioPath, { copyFallback: true });
|
|
6237
|
-
|
|
6264
|
+
const loweredDb = Number((-attenuationDb).toFixed(2));
|
|
6265
|
+
return sourceCut ? { success: true, loweredDb } : { success: true, loweredDb, sourceStale: true };
|
|
6238
6266
|
}
|
|
6239
6267
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
6240
6268
|
const headroomDb = AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB * 2 ** pass;
|
|
@@ -6244,7 +6272,7 @@ async function enforceAacTruePeak(input) {
|
|
|
6244
6272
|
const referenceDbfs = Math.max(truePeakDbfs, (sourcePeakDbfs ?? -Infinity) + attenuationDb);
|
|
6245
6273
|
attenuationDb += AAC_DELIVERY_TRUE_PEAK_DBFS - headroomDb - referenceDbfs;
|
|
6246
6274
|
const result = await input.encode(
|
|
6247
|
-
input.audioPath,
|
|
6275
|
+
input.sourcePath ?? input.audioPath,
|
|
6248
6276
|
correctedPath,
|
|
6249
6277
|
input.targetDurationSeconds,
|
|
6250
6278
|
aacTruePeakCorrectionFilter(input.targetDurationSeconds, attenuationDb)
|
|
@@ -6473,36 +6501,54 @@ async function runAudioStageScoped(input) {
|
|
|
6473
6501
|
const stageStart = Date.now();
|
|
6474
6502
|
const pcmSourcePath = join12(input.workDir, "audio-source.wav");
|
|
6475
6503
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
6504
|
+
let deliverySourcePath;
|
|
6476
6505
|
try {
|
|
6477
6506
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
6478
6507
|
let audioLoweredDb;
|
|
6479
6508
|
if (result.hasAudio) {
|
|
6480
6509
|
const guardStart = Date.now();
|
|
6481
|
-
const
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
});
|
|
6489
|
-
if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
|
|
6490
|
-
const peak = await constrainAacTruePeak({
|
|
6491
|
-
audioPath: result.audioOutputPath,
|
|
6492
|
-
sourcePath: pcmSourcePath,
|
|
6493
|
-
targetDurationSeconds: duration,
|
|
6494
|
-
signal: input.abortSignal,
|
|
6495
|
-
timeoutMs: input.ffmpegProcessTimeout
|
|
6496
|
-
});
|
|
6497
|
-
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
6498
|
-
audioLoweredDb = peak.loweredDb;
|
|
6510
|
+
const guard = await guardDeliveryPeaks(
|
|
6511
|
+
input,
|
|
6512
|
+
result.audioOutputPath,
|
|
6513
|
+
pcmSourcePath,
|
|
6514
|
+
duration
|
|
6515
|
+
);
|
|
6516
|
+
audioLoweredDb = guard.loweredDb;
|
|
6499
6517
|
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
6518
|
+
if (input.keepDeliverySource && !guard.sourceStale && existsSync9(pcmSourcePath)) {
|
|
6519
|
+
deliverySourcePath = pcmSourcePath;
|
|
6520
|
+
}
|
|
6500
6521
|
}
|
|
6501
|
-
return {
|
|
6522
|
+
return {
|
|
6523
|
+
...result,
|
|
6524
|
+
audioLoweredDb,
|
|
6525
|
+
deliverySourcePath,
|
|
6526
|
+
audioProcessMs: Date.now() - stageStart
|
|
6527
|
+
};
|
|
6502
6528
|
} finally {
|
|
6503
|
-
removeScratch2(pcmSourcePath);
|
|
6529
|
+
if (!deliverySourcePath) removeScratch2(pcmSourcePath);
|
|
6504
6530
|
}
|
|
6505
6531
|
}
|
|
6532
|
+
async function guardDeliveryPeaks(input, audioPath, sourcePath, duration) {
|
|
6533
|
+
const limited = await limitDeliveryPeaks({
|
|
6534
|
+
audioPath,
|
|
6535
|
+
sourcePath,
|
|
6536
|
+
targetDurationSeconds: duration,
|
|
6537
|
+
signal: input.abortSignal,
|
|
6538
|
+
timeoutMs: input.ffmpegProcessTimeout,
|
|
6539
|
+
warn: (message, details) => input.log?.warn?.(message, details)
|
|
6540
|
+
});
|
|
6541
|
+
if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
|
|
6542
|
+
const peak = await constrainAacTruePeak({
|
|
6543
|
+
audioPath,
|
|
6544
|
+
sourcePath,
|
|
6545
|
+
targetDurationSeconds: duration,
|
|
6546
|
+
signal: input.abortSignal,
|
|
6547
|
+
timeoutMs: input.ffmpegProcessTimeout
|
|
6548
|
+
});
|
|
6549
|
+
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
6550
|
+
return { loweredDb: peak.loweredDb, sourceStale: peak.sourceStale };
|
|
6551
|
+
}
|
|
6506
6552
|
async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
6507
6553
|
const {
|
|
6508
6554
|
projectDir,
|
|
@@ -6597,7 +6643,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
6597
6643
|
}
|
|
6598
6644
|
|
|
6599
6645
|
// src/services/render/stages/captureStage.ts
|
|
6600
|
-
import { existsSync as
|
|
6646
|
+
import { existsSync as existsSync10, readdirSync, statfsSync } from "node:fs";
|
|
6601
6647
|
import { join as join13 } from "node:path";
|
|
6602
6648
|
import {
|
|
6603
6649
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
@@ -6649,7 +6695,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
6649
6695
|
}
|
|
6650
6696
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
6651
6697
|
let total = sampleDirectoryBytes(framesDir);
|
|
6652
|
-
if (!
|
|
6698
|
+
if (!existsSync10(workDir)) return total;
|
|
6653
6699
|
for (const attempt of readdirSync(workDir)) {
|
|
6654
6700
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
6655
6701
|
total += sampleDirectoryBytes(join13(workDir, attempt));
|
|
@@ -6986,7 +7032,7 @@ import {
|
|
|
6986
7032
|
// src/services/render/stages/encodeStage.ts
|
|
6987
7033
|
import {
|
|
6988
7034
|
copyFileSync as copyFileSync4,
|
|
6989
|
-
existsSync as
|
|
7035
|
+
existsSync as existsSync11,
|
|
6990
7036
|
mkdirSync as mkdirSync5,
|
|
6991
7037
|
readdirSync as readdirSync2,
|
|
6992
7038
|
readFileSync as readFileSync5,
|
|
@@ -7120,7 +7166,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
7120
7166
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
7121
7167
|
if (failure) return failure;
|
|
7122
7168
|
}
|
|
7123
|
-
const fileSize =
|
|
7169
|
+
const fileSize = existsSync11(outputPath) ? statSync4(outputPath).size : 0;
|
|
7124
7170
|
return {
|
|
7125
7171
|
success: true,
|
|
7126
7172
|
outputPath,
|
|
@@ -7281,7 +7327,7 @@ async function runEncodeStage(input) {
|
|
|
7281
7327
|
const stage5Start = Date.now();
|
|
7282
7328
|
if (isPngSequence) {
|
|
7283
7329
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
7284
|
-
if (!
|
|
7330
|
+
if (!existsSync11(outputPath)) mkdirSync5(outputPath, { recursive: true });
|
|
7285
7331
|
const captured = readdirSync2(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
7286
7332
|
if (captured.length === 0) {
|
|
7287
7333
|
throw new Error(
|
|
@@ -7292,7 +7338,7 @@ async function runEncodeStage(input) {
|
|
|
7292
7338
|
const dst = join14(outputPath, formatExportFrameName(i, "png"));
|
|
7293
7339
|
copyFileSync4(join14(framesDir, name), dst);
|
|
7294
7340
|
});
|
|
7295
|
-
if (hasAudio && audioOutputPath &&
|
|
7341
|
+
if (hasAudio && audioOutputPath && existsSync11(audioOutputPath)) {
|
|
7296
7342
|
copyFileSync4(audioOutputPath, join14(outputPath, MIXED_AUDIO_FILENAME2));
|
|
7297
7343
|
log.info(
|
|
7298
7344
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
@@ -7459,7 +7505,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
7459
7505
|
let rangeStart = null;
|
|
7460
7506
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7461
7507
|
const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7462
|
-
const missing = !
|
|
7508
|
+
const missing = !existsSync12(framePath) || statSync5(framePath).size <= 8;
|
|
7463
7509
|
if (missing && rangeStart === null) {
|
|
7464
7510
|
rangeStart = frameIndex;
|
|
7465
7511
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -7549,7 +7595,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
|
7549
7595
|
let captured = 0;
|
|
7550
7596
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7551
7597
|
const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7552
|
-
if (
|
|
7598
|
+
if (existsSync12(framePath)) captured++;
|
|
7553
7599
|
}
|
|
7554
7600
|
return captured;
|
|
7555
7601
|
}
|
|
@@ -7776,7 +7822,7 @@ import {
|
|
|
7776
7822
|
} from "@hyperframes/engine";
|
|
7777
7823
|
|
|
7778
7824
|
// src/services/distributed/chunkFrames.ts
|
|
7779
|
-
import { existsSync as
|
|
7825
|
+
import { existsSync as existsSync14, readdirSync as readdirSync4 } from "node:fs";
|
|
7780
7826
|
import { extname as extname2, join as join17 } from "node:path";
|
|
7781
7827
|
import {
|
|
7782
7828
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
@@ -7788,7 +7834,7 @@ import {
|
|
|
7788
7834
|
// src/services/distributed/shared.ts
|
|
7789
7835
|
import { execFile as execFileCallback } from "node:child_process";
|
|
7790
7836
|
import { dirname as dirname10, join as join16 } from "node:path";
|
|
7791
|
-
import { existsSync as
|
|
7837
|
+
import { existsSync as existsSync13, readFileSync as readFileSync6 } from "node:fs";
|
|
7792
7838
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7793
7839
|
import { promisify as promisify2 } from "node:util";
|
|
7794
7840
|
import {
|
|
@@ -7804,7 +7850,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
7804
7850
|
function resolvePlanAudioPath(planDir) {
|
|
7805
7851
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
7806
7852
|
const candidate = join16(planDir, name);
|
|
7807
|
-
if (
|
|
7853
|
+
if (existsSync13(candidate)) return candidate;
|
|
7808
7854
|
}
|
|
7809
7855
|
return null;
|
|
7810
7856
|
}
|
|
@@ -8065,7 +8111,7 @@ function readProducerVersion() {
|
|
|
8065
8111
|
let current = startDir;
|
|
8066
8112
|
for (let i = 0; i < 10; i++) {
|
|
8067
8113
|
const candidate = join16(current, "package.json");
|
|
8068
|
-
if (
|
|
8114
|
+
if (existsSync13(candidate)) {
|
|
8069
8115
|
try {
|
|
8070
8116
|
const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
|
|
8071
8117
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
@@ -8119,7 +8165,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
8119
8165
|
const result = [];
|
|
8120
8166
|
for (const v of videos) {
|
|
8121
8167
|
const outputDir = join17(planDir, "video-frames", v.videoId);
|
|
8122
|
-
if (!
|
|
8168
|
+
if (!existsSync14(outputDir)) {
|
|
8123
8169
|
throw new Error(
|
|
8124
8170
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
8125
8171
|
);
|
|
@@ -8235,7 +8281,7 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
8235
8281
|
const range = video.deferredRange;
|
|
8236
8282
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
8237
8283
|
const sourcePath = remoteUrl ?? join17(input.planDir, ...range.sourcePath.split("/"));
|
|
8238
|
-
if (remoteUrl === void 0 && !
|
|
8284
|
+
if (remoteUrl === void 0 && !existsSync14(sourcePath)) {
|
|
8239
8285
|
throw new RenderChunkValidationError(
|
|
8240
8286
|
MISSING_PLAN_ARTIFACT,
|
|
8241
8287
|
`[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
|
|
@@ -8453,7 +8499,7 @@ async function readPlanReferenceBackground(input) {
|
|
|
8453
8499
|
}
|
|
8454
8500
|
|
|
8455
8501
|
// src/services/render/stages/freezePlan.ts
|
|
8456
|
-
import { existsSync as
|
|
8502
|
+
import { existsSync as existsSync15, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8457
8503
|
import { join as join20, relative as relative3, resolve as resolve8 } from "node:path";
|
|
8458
8504
|
import {
|
|
8459
8505
|
REFERENCE_BACKGROUND_FIELD,
|
|
@@ -8647,7 +8693,7 @@ function planFileSha256(absolutePath, digests) {
|
|
|
8647
8693
|
return sha256;
|
|
8648
8694
|
}
|
|
8649
8695
|
async function prehashPlanFiles(dir, digests) {
|
|
8650
|
-
if (!
|
|
8696
|
+
if (!existsSync15(dir)) return;
|
|
8651
8697
|
for (const { absolutePath } of listPlanFiles(dir)) {
|
|
8652
8698
|
if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
|
|
8653
8699
|
}
|
|
@@ -8717,15 +8763,15 @@ function writePlanRemoteFiles(planDir, files) {
|
|
|
8717
8763
|
}
|
|
8718
8764
|
function readPlanRemoteFiles(planDir) {
|
|
8719
8765
|
const path = join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
8720
|
-
return
|
|
8766
|
+
return existsSync15(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
|
|
8721
8767
|
}
|
|
8722
8768
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
8723
8769
|
const planJsonPath = join20(planDir, "plan.json");
|
|
8724
8770
|
const encoderJsonPath = join20(planDir, "meta", "encoder.json");
|
|
8725
|
-
if (!
|
|
8771
|
+
if (!existsSync15(planJsonPath)) {
|
|
8726
8772
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
8727
8773
|
}
|
|
8728
|
-
if (!
|
|
8774
|
+
if (!existsSync15(encoderJsonPath)) {
|
|
8729
8775
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
8730
8776
|
}
|
|
8731
8777
|
const planJson = JSON.parse(readFileSync7(planJsonPath, "utf-8"));
|
|
@@ -8755,11 +8801,11 @@ async function freezePlan(input) {
|
|
|
8755
8801
|
hasAudio,
|
|
8756
8802
|
digests
|
|
8757
8803
|
} = input;
|
|
8758
|
-
if (!
|
|
8804
|
+
if (!existsSync15(planDir)) {
|
|
8759
8805
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
8760
8806
|
}
|
|
8761
8807
|
const metaDir = join20(planDir, "meta");
|
|
8762
|
-
if (!
|
|
8808
|
+
if (!existsSync15(metaDir)) mkdirSync7(metaDir, { recursive: true });
|
|
8763
8809
|
writeFileSync5(
|
|
8764
8810
|
join20(metaDir, "composition.json"),
|
|
8765
8811
|
`${JSON.stringify(composition, null, 2)}
|
|
@@ -8836,7 +8882,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
8836
8882
|
}
|
|
8837
8883
|
|
|
8838
8884
|
// src/services/distributed/chunkAudio.ts
|
|
8839
|
-
import { closeSync as closeSync4, existsSync as
|
|
8885
|
+
import { closeSync as closeSync4, existsSync as existsSync16, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
|
|
8840
8886
|
import { writeFileSync as writeFileSync6, writeSync } from "node:fs";
|
|
8841
8887
|
import { join as join22 } from "node:path";
|
|
8842
8888
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
@@ -9327,7 +9373,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
9327
9373
|
}
|
|
9328
9374
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
9329
9375
|
const layoutPath = join22(planDir, SLICE_LAYOUT);
|
|
9330
|
-
if (!
|
|
9376
|
+
if (!existsSync16(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
9331
9377
|
const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
|
|
9332
9378
|
const frame = readFileSync8(join22(planDir, SLICE_FRAME));
|
|
9333
9379
|
const fd = openSync4(scratchPath, "w");
|
|
@@ -9891,7 +9937,7 @@ function createVideoSourceShipper(input) {
|
|
|
9891
9937
|
let result = null;
|
|
9892
9938
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
9893
9939
|
const rel = pathInside(root, srcPath);
|
|
9894
|
-
if (rel !== null && (
|
|
9940
|
+
if (rel !== null && (existsSync17(join24(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
9895
9941
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
9896
9942
|
break;
|
|
9897
9943
|
}
|
|
@@ -9919,7 +9965,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9919
9965
|
useGpu: false,
|
|
9920
9966
|
browserGpuMode: "software"
|
|
9921
9967
|
});
|
|
9922
|
-
if (!
|
|
9968
|
+
if (!existsSync17(planDir)) mkdirSync10(planDir, { recursive: true });
|
|
9923
9969
|
const log = config.logger ?? defaultLogger;
|
|
9924
9970
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
9925
9971
|
const abortSignal = config.abortSignal;
|
|
@@ -9949,7 +9995,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9949
9995
|
});
|
|
9950
9996
|
const entryFile = config.entryFile ?? "index.html";
|
|
9951
9997
|
const htmlPath = join24(projectDir, entryFile);
|
|
9952
|
-
if (!
|
|
9998
|
+
if (!existsSync17(htmlPath)) {
|
|
9953
9999
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
9954
10000
|
}
|
|
9955
10001
|
const workDir = join24(planDir, ".plan-work");
|
|
@@ -10178,15 +10224,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10178
10224
|
logPlanStage(log, "extract", stageStarted);
|
|
10179
10225
|
const stagedVideoFrames = join24(compiledDir, "__hyperframes_video_frames");
|
|
10180
10226
|
const videoFramesDst = join24(planDir, "video-frames");
|
|
10181
|
-
if (
|
|
10227
|
+
if (existsSync17(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
|
|
10182
10228
|
const videoSourcesDir = join24(planDir, "video-sources");
|
|
10183
10229
|
rmSync7(videoSourcesDir, { recursive: true, force: true });
|
|
10184
|
-
if (
|
|
10230
|
+
if (existsSync17(stagedVideoFrames)) {
|
|
10185
10231
|
renameWithRetry8(stagedVideoFrames, videoFramesDst);
|
|
10186
10232
|
} else {
|
|
10187
10233
|
mkdirSync10(videoFramesDst, { recursive: true });
|
|
10188
10234
|
}
|
|
10189
|
-
if (
|
|
10235
|
+
if (existsSync17(finalCompiledDir)) rmSync7(finalCompiledDir, { recursive: true, force: true });
|
|
10190
10236
|
renameWithRetry8(compiledDir, finalCompiledDir);
|
|
10191
10237
|
const shipVideoSource = createVideoSourceShipper({
|
|
10192
10238
|
planDir,
|
|
@@ -10306,10 +10352,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10306
10352
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
10307
10353
|
}
|
|
10308
10354
|
const planAudioPath = join24(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
10309
|
-
if (audioResult?.hasAudio &&
|
|
10355
|
+
if (audioResult?.hasAudio && existsSync17(audioResult.audioOutputPath)) {
|
|
10310
10356
|
renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
|
|
10311
10357
|
}
|
|
10312
|
-
if (chunksCarryAudio &&
|
|
10358
|
+
if (chunksCarryAudio && existsSync17(planAudioPath)) {
|
|
10313
10359
|
if (options.sliceChunkAudio) {
|
|
10314
10360
|
stageStarted = Date.now();
|
|
10315
10361
|
await writeChunkAudioSlices({
|
|
@@ -10422,7 +10468,7 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
|
|
|
10422
10468
|
if (mixed.audioError) {
|
|
10423
10469
|
applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
|
|
10424
10470
|
}
|
|
10425
|
-
if (!mixed.hasAudio || !
|
|
10471
|
+
if (!mixed.hasAudio || !existsSync17(mixed.audioOutputPath)) return { hasAudio: false };
|
|
10426
10472
|
renameWithRetry8(mixed.audioOutputPath, outputPath);
|
|
10427
10473
|
return { hasAudio: true };
|
|
10428
10474
|
} finally {
|
|
@@ -10437,7 +10483,7 @@ import {
|
|
|
10437
10483
|
} from "@hyperframes/engine";
|
|
10438
10484
|
import {
|
|
10439
10485
|
copyFileSync as copyFileSync8,
|
|
10440
|
-
existsSync as
|
|
10486
|
+
existsSync as existsSync19,
|
|
10441
10487
|
lstatSync as lstatSync2,
|
|
10442
10488
|
mkdirSync as mkdirSync12,
|
|
10443
10489
|
mkdtempSync as mkdtempSync4,
|
|
@@ -10455,7 +10501,7 @@ import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as r
|
|
|
10455
10501
|
// src/services/distributed/planV2Publisher.ts
|
|
10456
10502
|
import {
|
|
10457
10503
|
copyFileSync as copyFileSync7,
|
|
10458
|
-
existsSync as
|
|
10504
|
+
existsSync as existsSync18,
|
|
10459
10505
|
linkSync as linkSync2,
|
|
10460
10506
|
mkdirSync as mkdirSync11,
|
|
10461
10507
|
mkdtempSync as mkdtempSync3,
|
|
@@ -10524,7 +10570,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10524
10570
|
#linkFile;
|
|
10525
10571
|
#committed = false;
|
|
10526
10572
|
constructor(destinationDir, options = {}) {
|
|
10527
|
-
if (
|
|
10573
|
+
if (existsSync18(destinationDir)) {
|
|
10528
10574
|
throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
|
|
10529
10575
|
}
|
|
10530
10576
|
this.destinationDir = destinationDir;
|
|
@@ -10541,7 +10587,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10541
10587
|
);
|
|
10542
10588
|
}
|
|
10543
10589
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
10544
|
-
if (
|
|
10590
|
+
if (existsSync18(destinationPath)) return;
|
|
10545
10591
|
mkdirSync11(dirname12(destinationPath), { recursive: true });
|
|
10546
10592
|
const stagingDir = mkdtempSync3(join26(dirname12(destinationPath), ".plan-v2-blob-"));
|
|
10547
10593
|
const temporaryPath = join26(stagingDir, "blob");
|
|
@@ -10559,7 +10605,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10559
10605
|
}
|
|
10560
10606
|
async commitManifest(manifestBytes) {
|
|
10561
10607
|
for (const digest of new Set(manifestDigests(manifestBytes))) {
|
|
10562
|
-
if (!
|
|
10608
|
+
if (!existsSync18(planV2BlobPath(this.temporaryDir, digest))) {
|
|
10563
10609
|
throw new PlanV2IntegrityError(
|
|
10564
10610
|
`cannot commit manifest before referenced blob is durable: ${digest}`
|
|
10565
10611
|
);
|
|
@@ -10638,7 +10684,7 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
10638
10684
|
}
|
|
10639
10685
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
10640
10686
|
const videoRoot = join27(executionPlanDir, "video-frames");
|
|
10641
|
-
if (!
|
|
10687
|
+
if (!existsSync19(videoRoot)) return;
|
|
10642
10688
|
for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
|
|
10643
10689
|
if (!videoEntry.isDirectory()) continue;
|
|
10644
10690
|
const videoDir = join27(videoRoot, videoEntry.name);
|
|
@@ -10739,7 +10785,7 @@ function parsePlanVideosJson2(value) {
|
|
|
10739
10785
|
}
|
|
10740
10786
|
function materializeExtractedVideoDirectories(planDir) {
|
|
10741
10787
|
const videosPath = join27(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10742
|
-
if (!
|
|
10788
|
+
if (!existsSync19(videosPath)) return;
|
|
10743
10789
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
10744
10790
|
for (const video of videos.extracted) {
|
|
10745
10791
|
mkdirSync12(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
@@ -10773,9 +10819,9 @@ function parseChunkSlices(value) {
|
|
|
10773
10819
|
}
|
|
10774
10820
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
10775
10821
|
const videoRoot = join27(executionPlanDir, "video-frames");
|
|
10776
|
-
const hasExtractedFrames =
|
|
10822
|
+
const hasExtractedFrames = existsSync19(videoRoot) && listFiles(videoRoot).length > 0;
|
|
10777
10823
|
const videosPath = join27(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10778
|
-
if (!
|
|
10824
|
+
if (!existsSync19(videosPath)) {
|
|
10779
10825
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
10780
10826
|
}
|
|
10781
10827
|
const chunksPath = join27(executionPlanDir, "meta", "chunks.json");
|
|
@@ -10832,7 +10878,7 @@ function computeManifestHash(manifest) {
|
|
|
10832
10878
|
return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
|
|
10833
10879
|
}
|
|
10834
10880
|
function writeBlob(sourcePath, destinationPath) {
|
|
10835
|
-
if (
|
|
10881
|
+
if (existsSync19(destinationPath)) return;
|
|
10836
10882
|
mkdirSync12(dirname13(destinationPath), { recursive: true });
|
|
10837
10883
|
const temporaryDir = mkdtempSync4(join27(dirname13(destinationPath), ".plan-v2-blob-"));
|
|
10838
10884
|
const temporaryPath = join27(temporaryDir, "blob");
|
|
@@ -10872,7 +10918,7 @@ async function putTasks(tasks) {
|
|
|
10872
10918
|
}
|
|
10873
10919
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
10874
10920
|
const executionPlanPath = join27(executionPlanDir, "plan.json");
|
|
10875
|
-
if (!
|
|
10921
|
+
if (!existsSync19(executionPlanPath)) {
|
|
10876
10922
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
10877
10923
|
}
|
|
10878
10924
|
const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
|
|
@@ -10952,7 +10998,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
10952
10998
|
};
|
|
10953
10999
|
}
|
|
10954
11000
|
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
10955
|
-
if (
|
|
11001
|
+
if (existsSync19(planV2Dir)) {
|
|
10956
11002
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
10957
11003
|
}
|
|
10958
11004
|
const publication = buildPlanV2Publication(executionPlanDir);
|
|
@@ -11038,7 +11084,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
11038
11084
|
}
|
|
11039
11085
|
}
|
|
11040
11086
|
async function planV2(projectDir, config, planV2Dir) {
|
|
11041
|
-
if (
|
|
11087
|
+
if (existsSync19(planV2Dir)) {
|
|
11042
11088
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
11043
11089
|
}
|
|
11044
11090
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
@@ -11192,7 +11238,7 @@ function parsePlanV2Manifest(value) {
|
|
|
11192
11238
|
}
|
|
11193
11239
|
function readPlanV2Manifest(planV2Dir) {
|
|
11194
11240
|
const manifestPath = join27(planV2Dir, "plan.json");
|
|
11195
|
-
if (!
|
|
11241
|
+
if (!existsSync19(manifestPath)) {
|
|
11196
11242
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
11197
11243
|
}
|
|
11198
11244
|
const value = readJsonFile(manifestPath, "v2 manifest");
|
|
@@ -11211,7 +11257,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
|
|
|
11211
11257
|
}
|
|
11212
11258
|
function verifyBlob(planV2Dir, artifact) {
|
|
11213
11259
|
const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
|
|
11214
|
-
if (!
|
|
11260
|
+
if (!existsSync19(sourcePath)) {
|
|
11215
11261
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
11216
11262
|
}
|
|
11217
11263
|
const stats = statSync7(sourcePath);
|
|
@@ -11224,7 +11270,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
11224
11270
|
return sourcePath;
|
|
11225
11271
|
}
|
|
11226
11272
|
function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
|
|
11227
|
-
if (
|
|
11273
|
+
if (existsSync19(destinationDir)) {
|
|
11228
11274
|
throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
|
|
11229
11275
|
}
|
|
11230
11276
|
const manifest = readPlanV2Manifest(planV2Dir);
|
|
@@ -11289,7 +11335,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
11289
11335
|
}
|
|
11290
11336
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
11291
11337
|
const markerPath = join27(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
11292
|
-
if (!
|
|
11338
|
+
if (!existsSync19(markerPath)) return null;
|
|
11293
11339
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
11294
11340
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
11295
11341
|
throw new PlanV2IntegrityError("malformed materialization marker");
|
|
@@ -11307,7 +11353,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
11307
11353
|
for (const artifact of required) {
|
|
11308
11354
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
11309
11355
|
const path = join27(planDir, ...artifact.path.split("/"));
|
|
11310
|
-
if (!
|
|
11356
|
+
if (!existsSync19(path) || statSync7(path).size !== artifact.sizeBytes) {
|
|
11311
11357
|
throw new PlanV2IntegrityError(
|
|
11312
11358
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
11313
11359
|
);
|
|
@@ -11331,7 +11377,7 @@ import {
|
|
|
11331
11377
|
} from "@hyperframes/engine";
|
|
11332
11378
|
import {
|
|
11333
11379
|
cpSync as cpSync3,
|
|
11334
|
-
existsSync as
|
|
11380
|
+
existsSync as existsSync20,
|
|
11335
11381
|
mkdirSync as mkdirSync13,
|
|
11336
11382
|
mkdtempSync as mkdtempSync5,
|
|
11337
11383
|
readFileSync as readFileSync10,
|
|
@@ -11386,7 +11432,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11386
11432
|
}
|
|
11387
11433
|
const format = plan.dimensions.format;
|
|
11388
11434
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
11389
|
-
if (!
|
|
11435
|
+
if (!existsSync20(dirname14(outputPath))) {
|
|
11390
11436
|
mkdirSync13(dirname14(outputPath), { recursive: true });
|
|
11391
11437
|
}
|
|
11392
11438
|
const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
|
|
@@ -11410,7 +11456,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11410
11456
|
try {
|
|
11411
11457
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
11412
11458
|
const finalPath = join28(workDir, `output.${format}`);
|
|
11413
|
-
const audio = audioPath !== null &&
|
|
11459
|
+
const audio = audioPath !== null && existsSync20(audioPath) ? audioPath : null;
|
|
11414
11460
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
11415
11461
|
renameWithRetry11(finalPath, outputPath);
|
|
11416
11462
|
log.info("[assemble] stage timings", {
|
|
@@ -11428,7 +11474,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11428
11474
|
});
|
|
11429
11475
|
}
|
|
11430
11476
|
}
|
|
11431
|
-
const fileSize =
|
|
11477
|
+
const fileSize = existsSync20(outputPath) ? statSync8(outputPath).size : 0;
|
|
11432
11478
|
return {
|
|
11433
11479
|
...referenceBackgroundResult6(plan[REFERENCE_BACKGROUND_FIELD4]),
|
|
11434
11480
|
outputPath,
|
|
@@ -11440,13 +11486,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11440
11486
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
11441
11487
|
const planJsonPath = join28(planDir, "plan.json");
|
|
11442
11488
|
const chunksJsonPath = join28(planDir, "meta", "chunks.json");
|
|
11443
|
-
if (!
|
|
11489
|
+
if (!existsSync20(planJsonPath)) {
|
|
11444
11490
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
11445
11491
|
}
|
|
11446
11492
|
const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
|
|
11447
11493
|
readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
11448
11494
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
11449
|
-
if (!
|
|
11495
|
+
if (!existsSync20(chunksJsonPath)) {
|
|
11450
11496
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
11451
11497
|
}
|
|
11452
11498
|
const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
|
|
@@ -11461,7 +11507,7 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
11461
11507
|
}
|
|
11462
11508
|
}
|
|
11463
11509
|
for (const path of chunkPaths.flat()) {
|
|
11464
|
-
if (!
|
|
11510
|
+
if (!existsSync20(path)) {
|
|
11465
11511
|
throw new Error(`[assemble] chunk path does not exist: ${path}`);
|
|
11466
11512
|
}
|
|
11467
11513
|
}
|
|
@@ -11600,7 +11646,7 @@ function planFormat(planDir) {
|
|
|
11600
11646
|
}
|
|
11601
11647
|
function lockedAudio(planDir) {
|
|
11602
11648
|
const encoderJsonPath = join28(planDir, "meta", "encoder.json");
|
|
11603
|
-
if (!
|
|
11649
|
+
if (!existsSync20(encoderJsonPath)) return {};
|
|
11604
11650
|
return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
11605
11651
|
}
|
|
11606
11652
|
function readCfrEncoder(planDir, format) {
|
|
@@ -11610,7 +11656,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
11610
11656
|
);
|
|
11611
11657
|
}
|
|
11612
11658
|
const encoderJsonPath = join28(planDir, "meta", "encoder.json");
|
|
11613
|
-
if (!
|
|
11659
|
+
if (!existsSync20(encoderJsonPath)) {
|
|
11614
11660
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
11615
11661
|
}
|
|
11616
11662
|
const encoderJson = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
@@ -11622,7 +11668,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
11622
11668
|
return encoderJson;
|
|
11623
11669
|
}
|
|
11624
11670
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
11625
|
-
if (
|
|
11671
|
+
if (existsSync20(outputPath)) rmSync10(outputPath, { recursive: true, force: true });
|
|
11626
11672
|
mkdirSync13(outputPath, { recursive: true });
|
|
11627
11673
|
let globalIdx = 0;
|
|
11628
11674
|
for (const chunkDir of chunkPaths) {
|
|
@@ -11646,7 +11692,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11646
11692
|
`[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
|
|
11647
11693
|
);
|
|
11648
11694
|
}
|
|
11649
|
-
if (audioPath !== null &&
|
|
11695
|
+
if (audioPath !== null && existsSync20(audioPath)) {
|
|
11650
11696
|
const sidecar = join28(outputPath, MIXED_AUDIO_FILENAME4);
|
|
11651
11697
|
cpSync3(audioPath, sidecar);
|
|
11652
11698
|
}
|
|
@@ -11667,7 +11713,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11667
11713
|
|
|
11668
11714
|
// src/services/distributed/renderChunk.ts
|
|
11669
11715
|
import { randomBytes } from "node:crypto";
|
|
11670
|
-
import { existsSync as
|
|
11716
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
|
|
11671
11717
|
import { cpus } from "node:os";
|
|
11672
11718
|
import { join as join29 } from "node:path";
|
|
11673
11719
|
import {
|
|
@@ -11813,7 +11859,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11813
11859
|
const planJsonPath = join29(planDir, "plan.json");
|
|
11814
11860
|
const encoderJsonPath = join29(planDir, "meta", "encoder.json");
|
|
11815
11861
|
const chunksJsonPath = join29(planDir, "meta", "chunks.json");
|
|
11816
|
-
if (!
|
|
11862
|
+
if (!existsSync21(planJsonPath)) {
|
|
11817
11863
|
throw new RenderChunkValidationError(
|
|
11818
11864
|
MISSING_PLAN_ARTIFACT,
|
|
11819
11865
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
@@ -11829,7 +11875,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11829
11875
|
const v2Manifest = v2Materialization?.manifest ?? null;
|
|
11830
11876
|
const remoteSources = v2Materialization?.remoteSources ?? {};
|
|
11831
11877
|
for (const required of [encoderJsonPath, chunksJsonPath]) {
|
|
11832
|
-
if (!
|
|
11878
|
+
if (!existsSync21(required)) {
|
|
11833
11879
|
throw new RenderChunkValidationError(
|
|
11834
11880
|
MISSING_PLAN_ARTIFACT,
|
|
11835
11881
|
`[renderChunk] planDir is missing required artifact: ${required}`
|
|
@@ -11840,7 +11886,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11840
11886
|
const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
|
|
11841
11887
|
const videosJsonPath = join29(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
11842
11888
|
let planVideos = null;
|
|
11843
|
-
if (
|
|
11889
|
+
if (existsSync21(videosJsonPath)) {
|
|
11844
11890
|
try {
|
|
11845
11891
|
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync11(videosJsonPath, "utf-8")));
|
|
11846
11892
|
} catch (err) {
|
|
@@ -11871,7 +11917,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11871
11917
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
11872
11918
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
11873
11919
|
const compiledDir = join29(planDir, "compiled");
|
|
11874
|
-
if (!
|
|
11920
|
+
if (!existsSync21(compiledDir)) {
|
|
11875
11921
|
throw new RenderChunkValidationError(
|
|
11876
11922
|
MISSING_PLAN_ARTIFACT,
|
|
11877
11923
|
`[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
|
|
@@ -12121,10 +12167,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12121
12167
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
12122
12168
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
12123
12169
|
if (isPngSequence) {
|
|
12124
|
-
if (!
|
|
12170
|
+
if (!existsSync21(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
|
|
12125
12171
|
} else {
|
|
12126
12172
|
const outDir = join29(outputChunkPath, "..");
|
|
12127
|
-
if (!
|
|
12173
|
+
if (!existsSync21(outDir)) mkdirSync14(outDir, { recursive: true });
|
|
12128
12174
|
}
|
|
12129
12175
|
const encodeInput = {
|
|
12130
12176
|
job,
|