@hyperframes/producer 0.8.227 → 0.8.228
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 +303 -242
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +61 -61
- package/dist/index.js +362 -301
- package/dist/public-server.js +155 -94
- package/dist/services/render/audioDeliveryLimit.d.ts +26 -0
- package/package.json +7 -7
package/dist/distributed.js
CHANGED
|
@@ -3,13 +3,13 @@ import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframe
|
|
|
3
3
|
import {
|
|
4
4
|
copyFileSync as copyFileSync6,
|
|
5
5
|
cpSync as cpSync2,
|
|
6
|
-
existsSync as
|
|
6
|
+
existsSync as existsSync16,
|
|
7
7
|
linkSync,
|
|
8
8
|
mkdirSync as mkdirSync10,
|
|
9
9
|
rmSync as rmSync7,
|
|
10
10
|
writeFileSync as writeFileSync7
|
|
11
11
|
} from "node:fs";
|
|
12
|
-
import { extname as extname4, isAbsolute as isAbsolute3, join as
|
|
12
|
+
import { extname as extname4, isAbsolute as isAbsolute3, join as join23, relative as relative5, sep as sep2 } from "node:path";
|
|
13
13
|
import {
|
|
14
14
|
fpsToNumber as fpsToNumber9,
|
|
15
15
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
normalizeVp9CpuUsed,
|
|
21
21
|
resolveConfig as resolveConfig3,
|
|
22
22
|
declareContentAddressedUrl,
|
|
23
|
-
renameWithRetry as
|
|
23
|
+
renameWithRetry as renameWithRetry8,
|
|
24
24
|
declareProbeFactsUrl
|
|
25
25
|
} from "@hyperframes/engine";
|
|
26
26
|
|
|
@@ -66,7 +66,7 @@ var defaultLogger = createConsoleLogger("info");
|
|
|
66
66
|
// src/services/renderOrchestrator.ts
|
|
67
67
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
68
68
|
import {
|
|
69
|
-
existsSync as
|
|
69
|
+
existsSync as existsSync11,
|
|
70
70
|
mkdirSync as mkdirSync6,
|
|
71
71
|
readdirSync as readdirSync3,
|
|
72
72
|
rmSync as rmSync5,
|
|
@@ -120,7 +120,7 @@ import {
|
|
|
120
120
|
augmentProtocolTimeoutError,
|
|
121
121
|
augmentPageNavigationTimeoutError
|
|
122
122
|
} from "@hyperframes/engine";
|
|
123
|
-
import { join as
|
|
123
|
+
import { join as join15, dirname as dirname9, resolve as resolve7 } from "path";
|
|
124
124
|
import { randomUUID } from "crypto";
|
|
125
125
|
|
|
126
126
|
// src/services/fileServer.ts
|
|
@@ -5873,7 +5873,7 @@ import {
|
|
|
5873
5873
|
} from "@hyperframes/engine";
|
|
5874
5874
|
|
|
5875
5875
|
// src/services/render/stages/audioStage.ts
|
|
5876
|
-
import { join as
|
|
5876
|
+
import { join as join12 } from "node:path";
|
|
5877
5877
|
import {
|
|
5878
5878
|
MIXED_AUDIO_FILENAME,
|
|
5879
5879
|
audioFailureDiagnostics,
|
|
@@ -5882,6 +5882,16 @@ import {
|
|
|
5882
5882
|
collectScratchLeftovers
|
|
5883
5883
|
} from "@hyperframes/engine";
|
|
5884
5884
|
|
|
5885
|
+
// src/services/render/audioDeliveryLimit.ts
|
|
5886
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
5887
|
+
import { dirname as dirname7, join as join11 } from "node:path";
|
|
5888
|
+
import {
|
|
5889
|
+
applyAudioFxChain,
|
|
5890
|
+
describeFfmpegFailure as describeFfmpegFailure3,
|
|
5891
|
+
encodeAac as encodeAac2,
|
|
5892
|
+
renameWithRetry as renameWithRetry6
|
|
5893
|
+
} from "@hyperframes/engine";
|
|
5894
|
+
|
|
5885
5895
|
// src/services/render/audioPadTrim.ts
|
|
5886
5896
|
import { spawn } from "node:child_process";
|
|
5887
5897
|
import { mkdtempSync } from "node:fs";
|
|
@@ -6342,6 +6352,48 @@ async function runFfprobeJson(args, signal) {
|
|
|
6342
6352
|
}
|
|
6343
6353
|
}
|
|
6344
6354
|
|
|
6355
|
+
// src/services/render/audioDeliveryLimit.ts
|
|
6356
|
+
var LIMIT_CEILING_DBTP = AAC_DELIVERY_TRUE_PEAK_DBFS - 2;
|
|
6357
|
+
var LIMIT_CHAIN = {
|
|
6358
|
+
version: 1,
|
|
6359
|
+
nodes: [{ type: "truepeak", id: "delivery", params: { ceiling: LIMIT_CEILING_DBTP } }]
|
|
6360
|
+
};
|
|
6361
|
+
async function limitDeliveryPeaks(input, deps = {
|
|
6362
|
+
measure: measureTruePeakDbfs,
|
|
6363
|
+
applyChain: applyAudioFxChain,
|
|
6364
|
+
encode: encodeAac2,
|
|
6365
|
+
rename: renameWithRetry6,
|
|
6366
|
+
sourceExists: existsSync8
|
|
6367
|
+
}) {
|
|
6368
|
+
const { audioPath, sourcePath, signal, timeoutMs } = input;
|
|
6369
|
+
if (!deps.sourceExists(sourcePath)) return { success: true };
|
|
6370
|
+
const peak = await deps.measure(audioPath, { timeoutMs, signal });
|
|
6371
|
+
if (peak <= AAC_DELIVERY_TRUE_PEAK_DBFS) return { success: true };
|
|
6372
|
+
const limitedPath = join11(dirname7(sourcePath), "audio-source-limited.wav");
|
|
6373
|
+
try {
|
|
6374
|
+
await deps.applyChain(sourcePath, LIMIT_CHAIN, limitedPath, { trackId: "delivery", signal });
|
|
6375
|
+
} catch (error) {
|
|
6376
|
+
if (signal?.aborted) throw error;
|
|
6377
|
+
input.warn?.("[audio] true-peak limit failed, the whole-mix cut applies", {
|
|
6378
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6379
|
+
});
|
|
6380
|
+
return { success: true };
|
|
6381
|
+
}
|
|
6382
|
+
const encoded = await deps.encode(limitedPath, audioPath, input.targetDurationSeconds, {
|
|
6383
|
+
signal,
|
|
6384
|
+
timeout: timeoutMs
|
|
6385
|
+
});
|
|
6386
|
+
if (!encoded.success) {
|
|
6387
|
+
return {
|
|
6388
|
+
success: false,
|
|
6389
|
+
error: `[audioDeliveryLimit] ${describeFfmpegFailure3(encoded, timeoutMs)}`,
|
|
6390
|
+
failureReason: encoded.failureReason
|
|
6391
|
+
};
|
|
6392
|
+
}
|
|
6393
|
+
deps.rename(limitedPath, sourcePath, { copyFallback: true });
|
|
6394
|
+
return { success: true };
|
|
6395
|
+
}
|
|
6396
|
+
|
|
6345
6397
|
// src/services/render/stages/audioStage.ts
|
|
6346
6398
|
function assertAudioNotInterrupted(failures) {
|
|
6347
6399
|
const interrupted = failures?.find((failure) => failure.reason === "external_interruption");
|
|
@@ -6365,13 +6417,22 @@ function runAudioStage(input) {
|
|
|
6365
6417
|
}
|
|
6366
6418
|
async function runAudioStageScoped(input) {
|
|
6367
6419
|
const stageStart = Date.now();
|
|
6368
|
-
const pcmSourcePath =
|
|
6420
|
+
const pcmSourcePath = join12(input.workDir, "audio-source.wav");
|
|
6369
6421
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
6370
6422
|
try {
|
|
6371
6423
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
6372
6424
|
let audioLoweredDb;
|
|
6373
6425
|
if (result.hasAudio) {
|
|
6374
6426
|
const guardStart = Date.now();
|
|
6427
|
+
const limited = await limitDeliveryPeaks({
|
|
6428
|
+
audioPath: result.audioOutputPath,
|
|
6429
|
+
sourcePath: pcmSourcePath,
|
|
6430
|
+
targetDurationSeconds: duration,
|
|
6431
|
+
signal: input.abortSignal,
|
|
6432
|
+
timeoutMs: input.ffmpegProcessTimeout,
|
|
6433
|
+
warn: (message, details) => input.log?.warn?.(message, details)
|
|
6434
|
+
});
|
|
6435
|
+
if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
|
|
6375
6436
|
const peak = await constrainAacTruePeak({
|
|
6376
6437
|
audioPath: result.audioOutputPath,
|
|
6377
6438
|
sourcePath: pcmSourcePath,
|
|
@@ -6403,7 +6464,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
6403
6464
|
assertNotAborted,
|
|
6404
6465
|
log
|
|
6405
6466
|
} = input;
|
|
6406
|
-
const audioOutputPath =
|
|
6467
|
+
const audioOutputPath = join12(workDir, MIXED_AUDIO_FILENAME);
|
|
6407
6468
|
let hasAudio = false;
|
|
6408
6469
|
let audioError;
|
|
6409
6470
|
let audioFailures;
|
|
@@ -6414,7 +6475,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
6414
6475
|
audioResult = await processCompositionAudio(
|
|
6415
6476
|
audios,
|
|
6416
6477
|
projectDir,
|
|
6417
|
-
|
|
6478
|
+
join12(workDir, "audio-work"),
|
|
6418
6479
|
audioOutputPath,
|
|
6419
6480
|
duration,
|
|
6420
6481
|
abortSignal,
|
|
@@ -6482,8 +6543,8 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
6482
6543
|
}
|
|
6483
6544
|
|
|
6484
6545
|
// src/services/render/stages/captureStage.ts
|
|
6485
|
-
import { existsSync as
|
|
6486
|
-
import { join as
|
|
6546
|
+
import { existsSync as existsSync9, readdirSync, statfsSync } from "node:fs";
|
|
6547
|
+
import { join as join13 } from "node:path";
|
|
6487
6548
|
import {
|
|
6488
6549
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
6489
6550
|
frameFileExtension,
|
|
@@ -6534,16 +6595,16 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
6534
6595
|
}
|
|
6535
6596
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
6536
6597
|
let total = sampleDirectoryBytes(framesDir);
|
|
6537
|
-
if (!
|
|
6598
|
+
if (!existsSync9(workDir)) return total;
|
|
6538
6599
|
for (const attempt of readdirSync(workDir)) {
|
|
6539
6600
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
6540
|
-
total += sampleDirectoryBytes(
|
|
6601
|
+
total += sampleDirectoryBytes(join13(workDir, attempt));
|
|
6541
6602
|
continue;
|
|
6542
6603
|
}
|
|
6543
6604
|
if (!/^capture-attempt-\d+$/.test(attempt)) continue;
|
|
6544
|
-
const attemptDir =
|
|
6605
|
+
const attemptDir = join13(workDir, attempt);
|
|
6545
6606
|
for (const name of readdirSync(attemptDir)) {
|
|
6546
|
-
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(
|
|
6607
|
+
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join13(attemptDir, name));
|
|
6547
6608
|
}
|
|
6548
6609
|
}
|
|
6549
6610
|
return total;
|
|
@@ -6871,7 +6932,7 @@ import {
|
|
|
6871
6932
|
// src/services/render/stages/encodeStage.ts
|
|
6872
6933
|
import {
|
|
6873
6934
|
copyFileSync as copyFileSync4,
|
|
6874
|
-
existsSync as
|
|
6935
|
+
existsSync as existsSync10,
|
|
6875
6936
|
mkdirSync as mkdirSync5,
|
|
6876
6937
|
readdirSync as readdirSync2,
|
|
6877
6938
|
readFileSync as readFileSync4,
|
|
@@ -6880,7 +6941,7 @@ import {
|
|
|
6880
6941
|
writeFileSync as writeFileSync3
|
|
6881
6942
|
} from "node:fs";
|
|
6882
6943
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
6883
|
-
import { dirname as
|
|
6944
|
+
import { dirname as dirname8, join as join14 } from "node:path";
|
|
6884
6945
|
import {
|
|
6885
6946
|
encodeFramesChunkedConcat,
|
|
6886
6947
|
encodeFramesFromDir,
|
|
@@ -7005,7 +7066,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
7005
7066
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
7006
7067
|
if (failure) return failure;
|
|
7007
7068
|
}
|
|
7008
|
-
const fileSize =
|
|
7069
|
+
const fileSize = existsSync10(outputPath) ? statSync4(outputPath).size : 0;
|
|
7009
7070
|
return {
|
|
7010
7071
|
success: true,
|
|
7011
7072
|
outputPath,
|
|
@@ -7091,7 +7152,7 @@ function encodeWhileCapturing(input, totalFrames) {
|
|
|
7091
7152
|
await new Promise((resolve10) => wake = resolve10);
|
|
7092
7153
|
}
|
|
7093
7154
|
if (signal.aborted) return;
|
|
7094
|
-
const mergedPath =
|
|
7155
|
+
const mergedPath = join14(input.framesDir, formatCaptureFrameName(next, "jpg"));
|
|
7095
7156
|
const frame = await readLandedFrame(landed.get(next), mergedPath);
|
|
7096
7157
|
landed.delete(next);
|
|
7097
7158
|
if (!await stream.writeFrame(frame)) {
|
|
@@ -7166,7 +7227,7 @@ async function runEncodeStage(input) {
|
|
|
7166
7227
|
const stage5Start = Date.now();
|
|
7167
7228
|
if (isPngSequence) {
|
|
7168
7229
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
7169
|
-
if (!
|
|
7230
|
+
if (!existsSync10(outputPath)) mkdirSync5(outputPath, { recursive: true });
|
|
7170
7231
|
const captured = readdirSync2(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
7171
7232
|
if (captured.length === 0) {
|
|
7172
7233
|
throw new Error(
|
|
@@ -7174,11 +7235,11 @@ async function runEncodeStage(input) {
|
|
|
7174
7235
|
);
|
|
7175
7236
|
}
|
|
7176
7237
|
captured.forEach((name, i) => {
|
|
7177
|
-
const dst =
|
|
7178
|
-
copyFileSync4(
|
|
7238
|
+
const dst = join14(outputPath, formatExportFrameName(i, "png"));
|
|
7239
|
+
copyFileSync4(join14(framesDir, name), dst);
|
|
7179
7240
|
});
|
|
7180
|
-
if (hasAudio && audioOutputPath &&
|
|
7181
|
-
copyFileSync4(audioOutputPath,
|
|
7241
|
+
if (hasAudio && audioOutputPath && existsSync10(audioOutputPath)) {
|
|
7242
|
+
copyFileSync4(audioOutputPath, join14(outputPath, MIXED_AUDIO_FILENAME2));
|
|
7182
7243
|
log.info(
|
|
7183
7244
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
7184
7245
|
);
|
|
@@ -7198,7 +7259,7 @@ async function runEncodeStage(input) {
|
|
|
7198
7259
|
const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
|
|
7199
7260
|
fps: job.config.fps,
|
|
7200
7261
|
loop,
|
|
7201
|
-
palettePath:
|
|
7262
|
+
palettePath: join14(dirname8(videoOnlyPath), "gif-palette.png"),
|
|
7202
7263
|
preserveAlpha: needsAlpha,
|
|
7203
7264
|
signal: abortSignal,
|
|
7204
7265
|
timeout: engineCfg.ffmpegEncodeTimeout
|
|
@@ -7257,7 +7318,7 @@ function sampleDirectoryBytes(dir) {
|
|
|
7257
7318
|
continue;
|
|
7258
7319
|
}
|
|
7259
7320
|
for (const name of entries) {
|
|
7260
|
-
const full =
|
|
7321
|
+
const full = join15(current, name);
|
|
7261
7322
|
try {
|
|
7262
7323
|
const st = statSync5(full);
|
|
7263
7324
|
if (st.isDirectory()) {
|
|
@@ -7343,8 +7404,8 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
7343
7404
|
const ranges = [];
|
|
7344
7405
|
let rangeStart = null;
|
|
7345
7406
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7346
|
-
const framePath =
|
|
7347
|
-
const missing = !
|
|
7407
|
+
const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7408
|
+
const missing = !existsSync11(framePath) || statSync5(framePath).size <= 8;
|
|
7348
7409
|
if (missing && rangeStart === null) {
|
|
7349
7410
|
rangeStart = frameIndex;
|
|
7350
7411
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -7360,7 +7421,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
7360
7421
|
function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
|
|
7361
7422
|
const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
|
|
7362
7423
|
const batches = [];
|
|
7363
|
-
const outputDirFor = (batchIndex, workerId) =>
|
|
7424
|
+
const outputDirFor = (batchIndex, workerId) => join15(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
|
|
7364
7425
|
if (stride > 1) {
|
|
7365
7426
|
const spans = /* @__PURE__ */ new Map();
|
|
7366
7427
|
for (const range of ranges) {
|
|
@@ -7433,8 +7494,8 @@ function isRecoverableParallelCaptureError(error) {
|
|
|
7433
7494
|
function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
7434
7495
|
let captured = 0;
|
|
7435
7496
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7436
|
-
const framePath =
|
|
7437
|
-
if (
|
|
7497
|
+
const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7498
|
+
if (existsSync11(framePath)) captured++;
|
|
7438
7499
|
}
|
|
7439
7500
|
return captured;
|
|
7440
7501
|
}
|
|
@@ -7459,7 +7520,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
7459
7520
|
reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
|
|
7460
7521
|
});
|
|
7461
7522
|
pendingTransientRetry = false;
|
|
7462
|
-
const attemptWorkDir =
|
|
7523
|
+
const attemptWorkDir = join15(options.workDir, `capture-attempt-${attempt}`);
|
|
7463
7524
|
const batches = missingRanges ? buildMissingFrameRetryBatches(
|
|
7464
7525
|
missingRanges,
|
|
7465
7526
|
currentWorkers,
|
|
@@ -7651,7 +7712,7 @@ function explainStreamingEncodeGate(cfg, outputFormat, workerCount, durationSeco
|
|
|
7651
7712
|
var DEFAULT_SEGMENTED_MIN_SECONDS = Number.POSITIVE_INFINITY;
|
|
7652
7713
|
|
|
7653
7714
|
// src/services/distributed/planFrameCost.ts
|
|
7654
|
-
import { join as
|
|
7715
|
+
import { join as join18 } from "node:path";
|
|
7655
7716
|
import {
|
|
7656
7717
|
closeCaptureSession as closeCaptureSession9,
|
|
7657
7718
|
createCaptureSession as createCaptureSession9,
|
|
@@ -7660,8 +7721,8 @@ import {
|
|
|
7660
7721
|
} from "@hyperframes/engine";
|
|
7661
7722
|
|
|
7662
7723
|
// src/services/distributed/chunkFrames.ts
|
|
7663
|
-
import { existsSync as
|
|
7664
|
-
import { extname as extname2, join as
|
|
7724
|
+
import { existsSync as existsSync13, readdirSync as readdirSync4 } from "node:fs";
|
|
7725
|
+
import { extname as extname2, join as join17 } from "node:path";
|
|
7665
7726
|
import {
|
|
7666
7727
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
7667
7728
|
createFrameLookupTable as createFrameLookupTable2,
|
|
@@ -7671,8 +7732,8 @@ import {
|
|
|
7671
7732
|
|
|
7672
7733
|
// src/services/distributed/shared.ts
|
|
7673
7734
|
import { execFile as execFileCallback } from "node:child_process";
|
|
7674
|
-
import { dirname as
|
|
7675
|
-
import { existsSync as
|
|
7735
|
+
import { dirname as dirname10, join as join16 } from "node:path";
|
|
7736
|
+
import { existsSync as existsSync12, readFileSync as readFileSync5 } from "node:fs";
|
|
7676
7737
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7677
7738
|
import { promisify as promisify2 } from "node:util";
|
|
7678
7739
|
import {
|
|
@@ -7687,8 +7748,8 @@ function isPlanAudioArtifactPath(path) {
|
|
|
7687
7748
|
}
|
|
7688
7749
|
function resolvePlanAudioPath(planDir) {
|
|
7689
7750
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
7690
|
-
const candidate =
|
|
7691
|
-
if (
|
|
7751
|
+
const candidate = join16(planDir, name);
|
|
7752
|
+
if (existsSync12(candidate)) return candidate;
|
|
7692
7753
|
}
|
|
7693
7754
|
return null;
|
|
7694
7755
|
}
|
|
@@ -7945,11 +8006,11 @@ function buildSyntheticRenderJob(input) {
|
|
|
7945
8006
|
var cachedProducerVersion = null;
|
|
7946
8007
|
function readProducerVersion() {
|
|
7947
8008
|
if (cachedProducerVersion !== null) return cachedProducerVersion;
|
|
7948
|
-
const startDir =
|
|
8009
|
+
const startDir = dirname10(fileURLToPath3(import.meta.url));
|
|
7949
8010
|
let current = startDir;
|
|
7950
8011
|
for (let i = 0; i < 10; i++) {
|
|
7951
|
-
const candidate =
|
|
7952
|
-
if (
|
|
8012
|
+
const candidate = join16(current, "package.json");
|
|
8013
|
+
if (existsSync12(candidate)) {
|
|
7953
8014
|
try {
|
|
7954
8015
|
const pkg = JSON.parse(readFileSync5(candidate, "utf-8"));
|
|
7955
8016
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
@@ -7959,7 +8020,7 @@ function readProducerVersion() {
|
|
|
7959
8020
|
} catch {
|
|
7960
8021
|
}
|
|
7961
8022
|
}
|
|
7962
|
-
const parent =
|
|
8023
|
+
const parent = dirname10(current);
|
|
7963
8024
|
if (parent === current) break;
|
|
7964
8025
|
current = parent;
|
|
7965
8026
|
}
|
|
@@ -8002,8 +8063,8 @@ function frameNumberFromFileName(name) {
|
|
|
8002
8063
|
function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
|
|
8003
8064
|
const result = [];
|
|
8004
8065
|
for (const v of videos) {
|
|
8005
|
-
const outputDir =
|
|
8006
|
-
if (!
|
|
8066
|
+
const outputDir = join17(planDir, "video-frames", v.videoId);
|
|
8067
|
+
if (!existsSync13(outputDir)) {
|
|
8007
8068
|
throw new Error(
|
|
8008
8069
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
8009
8070
|
);
|
|
@@ -8021,7 +8082,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
8021
8082
|
if (!frameName) continue;
|
|
8022
8083
|
const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
|
|
8023
8084
|
const frameIndex = frameNumber === null ? i : frameNumber - 1;
|
|
8024
|
-
framePaths.set(frameIndex,
|
|
8085
|
+
framePaths.set(frameIndex, join17(outputDir, frameName));
|
|
8025
8086
|
}
|
|
8026
8087
|
result.push({
|
|
8027
8088
|
videoId: v.videoId,
|
|
@@ -8111,15 +8172,15 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
8111
8172
|
const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
|
|
8112
8173
|
return Promise.all(
|
|
8113
8174
|
deferred.map(async (video) => {
|
|
8114
|
-
const outputDir =
|
|
8175
|
+
const outputDir = join17(input.outputRoot, video.videoId);
|
|
8115
8176
|
const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
|
|
8116
8177
|
if (indexes.length === 0) {
|
|
8117
8178
|
return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
|
|
8118
8179
|
}
|
|
8119
8180
|
const range = video.deferredRange;
|
|
8120
8181
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
8121
|
-
const sourcePath = remoteUrl ??
|
|
8122
|
-
if (remoteUrl === void 0 && !
|
|
8182
|
+
const sourcePath = remoteUrl ?? join17(input.planDir, ...range.sourcePath.split("/"));
|
|
8183
|
+
if (remoteUrl === void 0 && !existsSync13(sourcePath)) {
|
|
8123
8184
|
throw new RenderChunkValidationError(
|
|
8124
8185
|
MISSING_PLAN_ARTIFACT,
|
|
8125
8186
|
`[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
|
|
@@ -8167,12 +8228,12 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
8167
8228
|
planVideos,
|
|
8168
8229
|
frames: sampled,
|
|
8169
8230
|
fps: fpsRatio,
|
|
8170
|
-
outputRoot:
|
|
8231
|
+
outputRoot: join18(workDir, "frame-cost-video-frames"),
|
|
8171
8232
|
cfg: input.cfg
|
|
8172
8233
|
})
|
|
8173
8234
|
];
|
|
8174
8235
|
const lookup = extracted.length > 0 ? createFrameLookupTable3(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
|
|
8175
|
-
const compiledDir =
|
|
8236
|
+
const compiledDir = join18(planDir, "compiled");
|
|
8176
8237
|
const fileServer = await createFileServer({
|
|
8177
8238
|
projectDir: compiledDir,
|
|
8178
8239
|
compiledDir,
|
|
@@ -8184,7 +8245,7 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
8184
8245
|
try {
|
|
8185
8246
|
session = await createCaptureSession9(
|
|
8186
8247
|
fileServer.url,
|
|
8187
|
-
|
|
8248
|
+
join18(workDir, "frame-cost"),
|
|
8188
8249
|
buildChunkCaptureOptions({
|
|
8189
8250
|
dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
|
|
8190
8251
|
encoder: input,
|
|
@@ -8281,8 +8342,8 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
|
|
|
8281
8342
|
}
|
|
8282
8343
|
|
|
8283
8344
|
// src/services/render/stages/freezePlan.ts
|
|
8284
|
-
import { existsSync as
|
|
8285
|
-
import { join as
|
|
8345
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync6, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8346
|
+
import { join as join19, relative as relative3, resolve as resolve8 } from "node:path";
|
|
8286
8347
|
|
|
8287
8348
|
// src/services/distributed/planProtocol.ts
|
|
8288
8349
|
var PLAN_SCHEMA_VERSION = 1;
|
|
@@ -8475,7 +8536,7 @@ function planFileSha256(absolutePath, digests) {
|
|
|
8475
8536
|
return sha256;
|
|
8476
8537
|
}
|
|
8477
8538
|
async function prehashPlanFiles(dir, digests) {
|
|
8478
|
-
if (!
|
|
8539
|
+
if (!existsSync14(dir)) return;
|
|
8479
8540
|
for (const { absolutePath } of listPlanFiles(dir)) {
|
|
8480
8541
|
if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
|
|
8481
8542
|
}
|
|
@@ -8500,7 +8561,7 @@ function listPlanFiles(planDir) {
|
|
|
8500
8561
|
function walk(dir) {
|
|
8501
8562
|
const entries = readdirSync5(dir, { withFileTypes: true });
|
|
8502
8563
|
for (const entry of entries) {
|
|
8503
|
-
const full =
|
|
8564
|
+
const full = join19(dir, entry.name);
|
|
8504
8565
|
if (entry.isDirectory()) {
|
|
8505
8566
|
walk(full);
|
|
8506
8567
|
} else if (entry.isFile()) {
|
|
@@ -8541,19 +8602,19 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
|
|
|
8541
8602
|
function writePlanRemoteFiles(planDir, files) {
|
|
8542
8603
|
if (files.length === 0) return;
|
|
8543
8604
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
8544
|
-
writeFileSync5(
|
|
8605
|
+
writeFileSync5(join19(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
|
|
8545
8606
|
}
|
|
8546
8607
|
function readPlanRemoteFiles(planDir) {
|
|
8547
|
-
const path =
|
|
8548
|
-
return
|
|
8608
|
+
const path = join19(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
8609
|
+
return existsSync14(path) ? JSON.parse(readFileSync6(path, "utf-8")) : [];
|
|
8549
8610
|
}
|
|
8550
8611
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
8551
|
-
const planJsonPath =
|
|
8552
|
-
const encoderJsonPath =
|
|
8553
|
-
if (!
|
|
8612
|
+
const planJsonPath = join19(planDir, "plan.json");
|
|
8613
|
+
const encoderJsonPath = join19(planDir, "meta", "encoder.json");
|
|
8614
|
+
if (!existsSync14(planJsonPath)) {
|
|
8554
8615
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
8555
8616
|
}
|
|
8556
|
-
if (!
|
|
8617
|
+
if (!existsSync14(encoderJsonPath)) {
|
|
8557
8618
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
8558
8619
|
}
|
|
8559
8620
|
const planJson = JSON.parse(readFileSync6(planJsonPath, "utf-8"));
|
|
@@ -8583,21 +8644,21 @@ async function freezePlan(input) {
|
|
|
8583
8644
|
hasAudio,
|
|
8584
8645
|
digests
|
|
8585
8646
|
} = input;
|
|
8586
|
-
if (!
|
|
8647
|
+
if (!existsSync14(planDir)) {
|
|
8587
8648
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
8588
8649
|
}
|
|
8589
|
-
const metaDir =
|
|
8590
|
-
if (!
|
|
8650
|
+
const metaDir = join19(planDir, "meta");
|
|
8651
|
+
if (!existsSync14(metaDir)) mkdirSync7(metaDir, { recursive: true });
|
|
8591
8652
|
writeFileSync5(
|
|
8592
|
-
|
|
8653
|
+
join19(metaDir, "composition.json"),
|
|
8593
8654
|
`${JSON.stringify(composition, null, 2)}
|
|
8594
8655
|
`,
|
|
8595
8656
|
"utf-8"
|
|
8596
8657
|
);
|
|
8597
8658
|
const encoderForCanonical = stripUndefined(encoder);
|
|
8598
8659
|
const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
|
|
8599
|
-
writeFileSync5(
|
|
8600
|
-
writeFileSync5(
|
|
8660
|
+
writeFileSync5(join19(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
|
|
8661
|
+
writeFileSync5(join19(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
|
|
8601
8662
|
`, "utf-8");
|
|
8602
8663
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
8603
8664
|
const planHash = computePlanHash({
|
|
@@ -8621,7 +8682,7 @@ async function freezePlan(input) {
|
|
|
8621
8682
|
duration: durationSeconds,
|
|
8622
8683
|
hasAudio
|
|
8623
8684
|
};
|
|
8624
|
-
const planJsonPath =
|
|
8685
|
+
const planJsonPath = join19(planDir, "plan.json");
|
|
8625
8686
|
writeFileSync5(planJsonPath, `${JSON.stringify(planJson, null, 2)}
|
|
8626
8687
|
`, "utf-8");
|
|
8627
8688
|
return { planJsonPath, planHash };
|
|
@@ -8663,16 +8724,16 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
8663
8724
|
}
|
|
8664
8725
|
|
|
8665
8726
|
// src/services/distributed/chunkAudio.ts
|
|
8666
|
-
import { closeSync as closeSync4, existsSync as
|
|
8727
|
+
import { closeSync as closeSync4, existsSync as existsSync15, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync7 } from "node:fs";
|
|
8667
8728
|
import { writeFileSync as writeFileSync6, writeSync } from "node:fs";
|
|
8668
|
-
import { join as
|
|
8669
|
-
import { appendRenderProvenanceArgs, describeFfmpegFailure as
|
|
8729
|
+
import { join as join21 } from "node:path";
|
|
8730
|
+
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
8670
8731
|
|
|
8671
8732
|
// src/services/distributed/mp4Stitch.ts
|
|
8672
8733
|
import { createReadStream as createReadStream4, createWriteStream, mkdirSync as mkdirSync8, mkdtempSync as mkdtempSync2, rmSync as rmSync6 } from "node:fs";
|
|
8673
8734
|
import { open } from "node:fs/promises";
|
|
8674
|
-
import { renameWithRetry as
|
|
8675
|
-
import { dirname as
|
|
8735
|
+
import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
|
|
8736
|
+
import { dirname as dirname11, join as join20 } from "node:path";
|
|
8676
8737
|
import { finished, pipeline } from "node:stream/promises";
|
|
8677
8738
|
function boxAt(src, off, at, limit) {
|
|
8678
8739
|
let size = src.readUInt32BE(off);
|
|
@@ -9036,15 +9097,15 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
|
9036
9097
|
const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
|
|
9037
9098
|
const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
|
|
9038
9099
|
const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
|
|
9039
|
-
mkdirSync8(
|
|
9100
|
+
mkdirSync8(dirname11(outputPath), { recursive: true });
|
|
9040
9101
|
const workDir = mkdtempSync2(`${outputPath}.stitch-`);
|
|
9041
|
-
const out = createWriteStream(
|
|
9102
|
+
const out = createWriteStream(join20(workDir, "out.mp4"));
|
|
9042
9103
|
try {
|
|
9043
9104
|
out.write(header);
|
|
9044
9105
|
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
9045
9106
|
out.end();
|
|
9046
9107
|
await finished(out);
|
|
9047
|
-
|
|
9108
|
+
renameWithRetry7(join20(workDir, "out.mp4"), outputPath);
|
|
9048
9109
|
} finally {
|
|
9049
9110
|
out.destroy();
|
|
9050
9111
|
rmSync6(workDir, { recursive: true, force: true });
|
|
@@ -9138,7 +9199,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
9138
9199
|
});
|
|
9139
9200
|
const bytes = readFileSync7(input.audioPath);
|
|
9140
9201
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
9141
|
-
mkdirSync9(
|
|
9202
|
+
mkdirSync9(join21(input.planDir, SLICE_DIR), { recursive: true });
|
|
9142
9203
|
input.chunks.forEach((_, index) => {
|
|
9143
9204
|
const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
|
|
9144
9205
|
if (!cut) return;
|
|
@@ -9146,17 +9207,17 @@ async function writeChunkAudioSlices(input) {
|
|
|
9146
9207
|
const start = offsets[cut.firstPacket];
|
|
9147
9208
|
layout.partStarts[index] = start;
|
|
9148
9209
|
const end = offsets[last] + track.sizes[last];
|
|
9149
|
-
writeFileSync6(
|
|
9210
|
+
writeFileSync6(join21(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
|
|
9150
9211
|
});
|
|
9151
9212
|
const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
|
|
9152
|
-
writeFileSync6(
|
|
9153
|
-
writeFileSync6(
|
|
9213
|
+
writeFileSync6(join21(input.planDir, SLICE_FRAME), Buffer.concat(frame));
|
|
9214
|
+
writeFileSync6(join21(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
|
|
9154
9215
|
}
|
|
9155
9216
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
9156
|
-
const layoutPath =
|
|
9157
|
-
if (!
|
|
9217
|
+
const layoutPath = join21(planDir, SLICE_LAYOUT);
|
|
9218
|
+
if (!existsSync15(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
9158
9219
|
const layout = JSON.parse(readFileSync7(layoutPath, "utf-8"));
|
|
9159
|
-
const frame = readFileSync7(
|
|
9220
|
+
const frame = readFileSync7(join21(planDir, SLICE_FRAME));
|
|
9160
9221
|
const fd = openSync4(scratchPath, "w");
|
|
9161
9222
|
try {
|
|
9162
9223
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
@@ -9169,7 +9230,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
9169
9230
|
);
|
|
9170
9231
|
const start = layout.partStarts[chunkIndex];
|
|
9171
9232
|
if (start !== void 0) {
|
|
9172
|
-
const part = readFileSync7(
|
|
9233
|
+
const part = readFileSync7(join21(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
9173
9234
|
writeSync(fd, part, 0, part.length, start);
|
|
9174
9235
|
}
|
|
9175
9236
|
ftruncateSync(fd, layout.sizeBytes);
|
|
@@ -9197,7 +9258,7 @@ async function muxChunkWithAudio(input) {
|
|
|
9197
9258
|
const result = await runFfmpeg7(args, { signal: input.signal, timeout: input.timeoutMs });
|
|
9198
9259
|
if (!result.success) {
|
|
9199
9260
|
throw new Error(
|
|
9200
|
-
`[chunkAudio] chunk mux failed: ${
|
|
9261
|
+
`[chunkAudio] chunk mux failed: ${describeFfmpegFailure4(result, input.timeoutMs)}`
|
|
9201
9262
|
);
|
|
9202
9263
|
}
|
|
9203
9264
|
const slice = input.chunks[input.chunkIndex];
|
|
@@ -9225,7 +9286,7 @@ async function muxChunkWithAudio(input) {
|
|
|
9225
9286
|
// src/services/distributed/planSize.ts
|
|
9226
9287
|
import { createHash as createHash7 } from "node:crypto";
|
|
9227
9288
|
import { lstatSync, readdirSync as readdirSync6 } from "node:fs";
|
|
9228
|
-
import { extname as extname3, join as
|
|
9289
|
+
import { extname as extname3, join as join22, relative as relative4 } from "node:path";
|
|
9229
9290
|
var MAX_TOP_COMPONENTS = 10;
|
|
9230
9291
|
var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
9231
9292
|
".aac",
|
|
@@ -9337,7 +9398,7 @@ function walkRegularFiles(dir, visit) {
|
|
|
9337
9398
|
return;
|
|
9338
9399
|
}
|
|
9339
9400
|
for (const entry of entries) {
|
|
9340
|
-
const full =
|
|
9401
|
+
const full = join22(dir, entry.name);
|
|
9341
9402
|
if (entry.isDirectory()) {
|
|
9342
9403
|
walkRegularFiles(full, visit);
|
|
9343
9404
|
} else if (entry.isFile()) {
|
|
@@ -9553,13 +9614,13 @@ function assertPositiveInteger(name, value) {
|
|
|
9553
9614
|
}
|
|
9554
9615
|
var FREEZE_OWNED_PLAN_FILES = [
|
|
9555
9616
|
"plan.json",
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9617
|
+
join23("meta", "composition.json"),
|
|
9618
|
+
join23("meta", "encoder.json"),
|
|
9619
|
+
join23("meta", "chunks.json")
|
|
9559
9620
|
];
|
|
9560
9621
|
function removeFreezeOwnedPlanFiles(planDir) {
|
|
9561
9622
|
for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
|
|
9562
|
-
rmSync7(
|
|
9623
|
+
rmSync7(join23(planDir, relativePath), { force: true });
|
|
9563
9624
|
}
|
|
9564
9625
|
}
|
|
9565
9626
|
function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
@@ -9714,19 +9775,19 @@ function createVideoSourceShipper(input) {
|
|
|
9714
9775
|
const known = shipped.get(srcPath);
|
|
9715
9776
|
if (known) return known;
|
|
9716
9777
|
const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
|
|
9717
|
-
const current = stagedRel === null ? srcPath :
|
|
9778
|
+
const current = stagedRel === null ? srcPath : join23(input.planDir, "video-frames", stagedRel);
|
|
9718
9779
|
let result = null;
|
|
9719
9780
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
9720
9781
|
const rel = pathInside(root, srcPath);
|
|
9721
|
-
if (rel !== null && (
|
|
9782
|
+
if (rel !== null && (existsSync16(join23(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
9722
9783
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
9723
9784
|
break;
|
|
9724
9785
|
}
|
|
9725
9786
|
}
|
|
9726
9787
|
if (result === null) {
|
|
9727
9788
|
result = `video-sources/${shipped.size}${extname4(current)}`;
|
|
9728
|
-
const destination =
|
|
9729
|
-
mkdirSync10(
|
|
9789
|
+
const destination = join23(input.planDir, ...result.split("/"));
|
|
9790
|
+
mkdirSync10(join23(input.planDir, "video-sources"), { recursive: true });
|
|
9730
9791
|
try {
|
|
9731
9792
|
linkSync(current, destination);
|
|
9732
9793
|
} catch {
|
|
@@ -9746,7 +9807,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9746
9807
|
useGpu: false,
|
|
9747
9808
|
browserGpuMode: "software"
|
|
9748
9809
|
});
|
|
9749
|
-
if (!
|
|
9810
|
+
if (!existsSync16(planDir)) mkdirSync10(planDir, { recursive: true });
|
|
9750
9811
|
const log = config.logger ?? defaultLogger;
|
|
9751
9812
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
9752
9813
|
const abortSignal = config.abortSignal;
|
|
@@ -9775,14 +9836,14 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9775
9836
|
variables: config.variables
|
|
9776
9837
|
});
|
|
9777
9838
|
const entryFile = config.entryFile ?? "index.html";
|
|
9778
|
-
const htmlPath =
|
|
9779
|
-
if (!
|
|
9839
|
+
const htmlPath = join23(projectDir, entryFile);
|
|
9840
|
+
if (!existsSync16(htmlPath)) {
|
|
9780
9841
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
9781
9842
|
}
|
|
9782
|
-
const workDir =
|
|
9843
|
+
const workDir = join23(planDir, ".plan-work");
|
|
9783
9844
|
rmSync7(workDir, { recursive: true, force: true });
|
|
9784
9845
|
mkdirSync10(workDir, { recursive: true });
|
|
9785
|
-
const compiledDir =
|
|
9846
|
+
const compiledDir = join23(workDir, "compiled");
|
|
9786
9847
|
mkdirSync10(compiledDir, { recursive: true });
|
|
9787
9848
|
let stageStarted = Date.now();
|
|
9788
9849
|
cpSync2(projectDir, compiledDir, {
|
|
@@ -9796,10 +9857,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9796
9857
|
}
|
|
9797
9858
|
});
|
|
9798
9859
|
logPlanStage(log, "copy", stageStarted);
|
|
9799
|
-
const finalCompiledDir =
|
|
9860
|
+
const finalCompiledDir = join23(planDir, "compiled");
|
|
9800
9861
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
9801
9862
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
9802
|
-
const remoteLocalPath = (rel) =>
|
|
9863
|
+
const remoteLocalPath = (rel) => join23(projectDir, ...rel.split("/"));
|
|
9803
9864
|
for (const [, file] of remoteFiles) {
|
|
9804
9865
|
declareContentAddressedUrl(file.url, file.sha256);
|
|
9805
9866
|
if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
|
|
@@ -9985,18 +10046,18 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
9985
10046
|
let stageStarted = Date.now();
|
|
9986
10047
|
const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
|
|
9987
10048
|
logPlanStage(log, "extract", stageStarted);
|
|
9988
|
-
const stagedVideoFrames =
|
|
9989
|
-
const videoFramesDst =
|
|
9990
|
-
if (
|
|
9991
|
-
const videoSourcesDir =
|
|
10049
|
+
const stagedVideoFrames = join23(compiledDir, "__hyperframes_video_frames");
|
|
10050
|
+
const videoFramesDst = join23(planDir, "video-frames");
|
|
10051
|
+
if (existsSync16(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
|
|
10052
|
+
const videoSourcesDir = join23(planDir, "video-sources");
|
|
9992
10053
|
rmSync7(videoSourcesDir, { recursive: true, force: true });
|
|
9993
|
-
if (
|
|
9994
|
-
|
|
10054
|
+
if (existsSync16(stagedVideoFrames)) {
|
|
10055
|
+
renameWithRetry8(stagedVideoFrames, videoFramesDst);
|
|
9995
10056
|
} else {
|
|
9996
10057
|
mkdirSync10(videoFramesDst, { recursive: true });
|
|
9997
10058
|
}
|
|
9998
|
-
if (
|
|
9999
|
-
|
|
10059
|
+
if (existsSync16(finalCompiledDir)) rmSync7(finalCompiledDir, { recursive: true, force: true });
|
|
10060
|
+
renameWithRetry8(compiledDir, finalCompiledDir);
|
|
10000
10061
|
const shipVideoSource = createVideoSourceShipper({
|
|
10001
10062
|
planDir,
|
|
10002
10063
|
projectDir,
|
|
@@ -10025,7 +10086,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10025
10086
|
};
|
|
10026
10087
|
})
|
|
10027
10088
|
});
|
|
10028
|
-
mkdirSync10(
|
|
10089
|
+
mkdirSync10(join23(planDir, "meta"), { recursive: true });
|
|
10029
10090
|
writePlanRemoteFiles(
|
|
10030
10091
|
planDir,
|
|
10031
10092
|
remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
|
|
@@ -10035,7 +10096,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10035
10096
|
}))
|
|
10036
10097
|
);
|
|
10037
10098
|
writeFileSync7(
|
|
10038
|
-
|
|
10099
|
+
join23(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
10039
10100
|
JSON.stringify(planVideosJson, null, 2),
|
|
10040
10101
|
"utf-8"
|
|
10041
10102
|
);
|
|
@@ -10114,11 +10175,11 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10114
10175
|
if (audioResult?.audioError) {
|
|
10115
10176
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
10116
10177
|
}
|
|
10117
|
-
const planAudioPath =
|
|
10118
|
-
if (audioResult?.hasAudio &&
|
|
10119
|
-
|
|
10178
|
+
const planAudioPath = join23(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
10179
|
+
if (audioResult?.hasAudio && existsSync16(audioResult.audioOutputPath)) {
|
|
10180
|
+
renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
|
|
10120
10181
|
}
|
|
10121
|
-
if (chunksCarryAudio &&
|
|
10182
|
+
if (chunksCarryAudio && existsSync16(planAudioPath)) {
|
|
10122
10183
|
if (options.sliceChunkAudio) {
|
|
10123
10184
|
stageStarted = Date.now();
|
|
10124
10185
|
await writeChunkAudioSlices({
|
|
@@ -10229,8 +10290,8 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
|
|
|
10229
10290
|
if (mixed.audioError) {
|
|
10230
10291
|
applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
|
|
10231
10292
|
}
|
|
10232
|
-
if (!mixed.hasAudio || !
|
|
10233
|
-
|
|
10293
|
+
if (!mixed.hasAudio || !existsSync16(mixed.audioOutputPath)) return { hasAudio: false };
|
|
10294
|
+
renameWithRetry8(mixed.audioOutputPath, outputPath);
|
|
10234
10295
|
return { hasAudio: true };
|
|
10235
10296
|
} finally {
|
|
10236
10297
|
rmSync7(c.workDir, { recursive: true, force: true });
|
|
@@ -10256,7 +10317,7 @@ async function plan(projectDir, config, planDir, options = {}) {
|
|
|
10256
10317
|
// src/services/distributed/planV2.ts
|
|
10257
10318
|
import {
|
|
10258
10319
|
copyFileSync as copyFileSync8,
|
|
10259
|
-
existsSync as
|
|
10320
|
+
existsSync as existsSync18,
|
|
10260
10321
|
lstatSync as lstatSync2,
|
|
10261
10322
|
mkdirSync as mkdirSync12,
|
|
10262
10323
|
mkdtempSync as mkdtempSync4,
|
|
@@ -10267,14 +10328,14 @@ import {
|
|
|
10267
10328
|
writeFileSync as writeFileSync9
|
|
10268
10329
|
} from "node:fs";
|
|
10269
10330
|
import { tmpdir } from "node:os";
|
|
10270
|
-
import { dirname as
|
|
10331
|
+
import { dirname as dirname13, isAbsolute as isAbsolute4, join as join26, relative as relative6, resolve as resolve9, sep as sep3 } from "node:path";
|
|
10271
10332
|
import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
|
|
10272
|
-
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as
|
|
10333
|
+
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
|
|
10273
10334
|
|
|
10274
10335
|
// src/services/distributed/planV2Publisher.ts
|
|
10275
10336
|
import {
|
|
10276
10337
|
copyFileSync as copyFileSync7,
|
|
10277
|
-
existsSync as
|
|
10338
|
+
existsSync as existsSync17,
|
|
10278
10339
|
linkSync as linkSync2,
|
|
10279
10340
|
mkdirSync as mkdirSync11,
|
|
10280
10341
|
mkdtempSync as mkdtempSync3,
|
|
@@ -10282,8 +10343,8 @@ import {
|
|
|
10282
10343
|
statSync as statSync6,
|
|
10283
10344
|
writeFileSync as writeFileSync8
|
|
10284
10345
|
} from "node:fs";
|
|
10285
|
-
import { renameWithRetry as
|
|
10286
|
-
import { dirname as
|
|
10346
|
+
import { renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
|
|
10347
|
+
import { dirname as dirname12, join as join25 } from "node:path";
|
|
10287
10348
|
|
|
10288
10349
|
// src/services/distributed/planV2Errors.ts
|
|
10289
10350
|
var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
|
|
@@ -10298,7 +10359,7 @@ var PlanV2IntegrityError = class extends Error {
|
|
|
10298
10359
|
};
|
|
10299
10360
|
|
|
10300
10361
|
// src/services/distributed/planV2Layout.ts
|
|
10301
|
-
import { join as
|
|
10362
|
+
import { join as join24 } from "node:path";
|
|
10302
10363
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
10303
10364
|
function assertPlanV2Sha256(value, field) {
|
|
10304
10365
|
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
|
|
@@ -10308,7 +10369,7 @@ function assertPlanV2Sha256(value, field) {
|
|
|
10308
10369
|
}
|
|
10309
10370
|
function planV2BlobPath(root, sha256) {
|
|
10310
10371
|
const digest = assertPlanV2Sha256(sha256, "artifact sha256");
|
|
10311
|
-
return
|
|
10372
|
+
return join24(root, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
10312
10373
|
}
|
|
10313
10374
|
|
|
10314
10375
|
// src/services/distributed/planV2Publisher.ts
|
|
@@ -10343,13 +10404,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10343
10404
|
#linkFile;
|
|
10344
10405
|
#committed = false;
|
|
10345
10406
|
constructor(destinationDir, options = {}) {
|
|
10346
|
-
if (
|
|
10407
|
+
if (existsSync17(destinationDir)) {
|
|
10347
10408
|
throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
|
|
10348
10409
|
}
|
|
10349
10410
|
this.destinationDir = destinationDir;
|
|
10350
10411
|
this.#linkFile = options.linkFile ?? linkSync2;
|
|
10351
|
-
mkdirSync11(
|
|
10352
|
-
this.temporaryDir = mkdtempSync3(
|
|
10412
|
+
mkdirSync11(dirname12(destinationDir), { recursive: true });
|
|
10413
|
+
this.temporaryDir = mkdtempSync3(join25(dirname12(destinationDir), ".plan-v2-publish-"));
|
|
10353
10414
|
}
|
|
10354
10415
|
async putBlob(blob) {
|
|
10355
10416
|
const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
|
|
@@ -10360,10 +10421,10 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10360
10421
|
);
|
|
10361
10422
|
}
|
|
10362
10423
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
10363
|
-
if (
|
|
10364
|
-
mkdirSync11(
|
|
10365
|
-
const stagingDir = mkdtempSync3(
|
|
10366
|
-
const temporaryPath =
|
|
10424
|
+
if (existsSync17(destinationPath)) return;
|
|
10425
|
+
mkdirSync11(dirname12(destinationPath), { recursive: true });
|
|
10426
|
+
const stagingDir = mkdtempSync3(join25(dirname12(destinationPath), ".plan-v2-blob-"));
|
|
10427
|
+
const temporaryPath = join25(stagingDir, "blob");
|
|
10367
10428
|
try {
|
|
10368
10429
|
try {
|
|
10369
10430
|
this.#linkFile(blob.sourcePath, temporaryPath);
|
|
@@ -10371,21 +10432,21 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10371
10432
|
if (!canFallbackToCopy(error)) throw error;
|
|
10372
10433
|
copyFileSync7(blob.sourcePath, temporaryPath);
|
|
10373
10434
|
}
|
|
10374
|
-
|
|
10435
|
+
renameWithRetry9(temporaryPath, destinationPath);
|
|
10375
10436
|
} finally {
|
|
10376
10437
|
rmSync8(stagingDir, { recursive: true, force: true });
|
|
10377
10438
|
}
|
|
10378
10439
|
}
|
|
10379
10440
|
async commitManifest(manifestBytes) {
|
|
10380
10441
|
for (const digest of new Set(manifestDigests(manifestBytes))) {
|
|
10381
|
-
if (!
|
|
10442
|
+
if (!existsSync17(planV2BlobPath(this.temporaryDir, digest))) {
|
|
10382
10443
|
throw new PlanV2IntegrityError(
|
|
10383
10444
|
`cannot commit manifest before referenced blob is durable: ${digest}`
|
|
10384
10445
|
);
|
|
10385
10446
|
}
|
|
10386
10447
|
}
|
|
10387
|
-
writeFileSync8(
|
|
10388
|
-
|
|
10448
|
+
writeFileSync8(join25(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
10449
|
+
renameWithRetry9(this.temporaryDir, this.destinationDir);
|
|
10389
10450
|
this.#committed = true;
|
|
10390
10451
|
}
|
|
10391
10452
|
async abort() {
|
|
@@ -10429,7 +10490,7 @@ function listFiles(root) {
|
|
|
10429
10490
|
const rootResolved = resolve9(root);
|
|
10430
10491
|
function walk(dir) {
|
|
10431
10492
|
for (const entry of readdirSync7(dir, { withFileTypes: true })) {
|
|
10432
|
-
const absolutePath =
|
|
10493
|
+
const absolutePath = join26(dir, entry.name);
|
|
10433
10494
|
if (entry.isDirectory()) {
|
|
10434
10495
|
walk(absolutePath);
|
|
10435
10496
|
} else if (entry.isFile()) {
|
|
@@ -10456,13 +10517,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
10456
10517
|
}
|
|
10457
10518
|
}
|
|
10458
10519
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
10459
|
-
const videoRoot =
|
|
10460
|
-
if (!
|
|
10520
|
+
const videoRoot = join26(executionPlanDir, "video-frames");
|
|
10521
|
+
if (!existsSync18(videoRoot)) return;
|
|
10461
10522
|
for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
|
|
10462
10523
|
if (!videoEntry.isDirectory()) continue;
|
|
10463
|
-
const videoDir =
|
|
10524
|
+
const videoDir = join26(videoRoot, videoEntry.name);
|
|
10464
10525
|
if (readdirSync7(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
|
|
10465
|
-
const sentinelPath =
|
|
10526
|
+
const sentinelPath = join26(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
|
|
10466
10527
|
assertValidExtractionCacheCompleteSentinel(sentinelPath);
|
|
10467
10528
|
}
|
|
10468
10529
|
}
|
|
@@ -10509,7 +10570,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
10509
10570
|
if (video.deferredRange) {
|
|
10510
10571
|
const framePaths2 = /* @__PURE__ */ new Map();
|
|
10511
10572
|
for (let index = 0; index < video.totalFrames; index++) {
|
|
10512
|
-
framePaths2.set(index,
|
|
10573
|
+
framePaths2.set(index, join26(outputDir, String(index)));
|
|
10513
10574
|
}
|
|
10514
10575
|
return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
|
|
10515
10576
|
}
|
|
@@ -10517,7 +10578,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
10517
10578
|
const framePaths = /* @__PURE__ */ new Map();
|
|
10518
10579
|
for (const frameName of frameNames) {
|
|
10519
10580
|
if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
|
|
10520
|
-
assertValidExtractionCacheCompleteSentinel(
|
|
10581
|
+
assertValidExtractionCacheCompleteSentinel(join26(outputDir, frameName));
|
|
10521
10582
|
continue;
|
|
10522
10583
|
}
|
|
10523
10584
|
const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
|
|
@@ -10536,7 +10597,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
10536
10597
|
`duplicate extracted frame index ${frameIndex} in ${outputDir}`
|
|
10537
10598
|
);
|
|
10538
10599
|
}
|
|
10539
|
-
framePaths.set(frameIndex,
|
|
10600
|
+
framePaths.set(frameIndex, join26(outputDir, frameName));
|
|
10540
10601
|
}
|
|
10541
10602
|
return {
|
|
10542
10603
|
...video,
|
|
@@ -10557,8 +10618,8 @@ function parsePlanVideosJson2(value) {
|
|
|
10557
10618
|
}
|
|
10558
10619
|
}
|
|
10559
10620
|
function materializeExtractedVideoDirectories(planDir) {
|
|
10560
|
-
const videosPath =
|
|
10561
|
-
if (!
|
|
10621
|
+
const videosPath = join26(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10622
|
+
if (!existsSync18(videosPath)) return;
|
|
10562
10623
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
10563
10624
|
for (const video of videos.extracted) {
|
|
10564
10625
|
mkdirSync12(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
@@ -10591,13 +10652,13 @@ function parseChunkSlices(value) {
|
|
|
10591
10652
|
});
|
|
10592
10653
|
}
|
|
10593
10654
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
10594
|
-
const videoRoot =
|
|
10595
|
-
const hasExtractedFrames =
|
|
10596
|
-
const videosPath =
|
|
10597
|
-
if (!
|
|
10655
|
+
const videoRoot = join26(executionPlanDir, "video-frames");
|
|
10656
|
+
const hasExtractedFrames = existsSync18(videoRoot) && listFiles(videoRoot).length > 0;
|
|
10657
|
+
const videosPath = join26(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10658
|
+
if (!existsSync18(videosPath)) {
|
|
10598
10659
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
10599
10660
|
}
|
|
10600
|
-
const chunksPath =
|
|
10661
|
+
const chunksPath = join26(executionPlanDir, "meta", "chunks.json");
|
|
10601
10662
|
const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10602
10663
|
const chunks = readJsonFile(chunksPath, "meta/chunks.json");
|
|
10603
10664
|
const parsedVideos = parsePlanVideosJson2(videos);
|
|
@@ -10651,13 +10712,13 @@ function computeManifestHash(manifest) {
|
|
|
10651
10712
|
return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
|
|
10652
10713
|
}
|
|
10653
10714
|
function writeBlob(sourcePath, destinationPath) {
|
|
10654
|
-
if (
|
|
10655
|
-
mkdirSync12(
|
|
10656
|
-
const temporaryDir = mkdtempSync4(
|
|
10657
|
-
const temporaryPath =
|
|
10715
|
+
if (existsSync18(destinationPath)) return;
|
|
10716
|
+
mkdirSync12(dirname13(destinationPath), { recursive: true });
|
|
10717
|
+
const temporaryDir = mkdtempSync4(join26(dirname13(destinationPath), ".plan-v2-blob-"));
|
|
10718
|
+
const temporaryPath = join26(temporaryDir, "blob");
|
|
10658
10719
|
try {
|
|
10659
10720
|
copyFileSync8(sourcePath, temporaryPath);
|
|
10660
|
-
|
|
10721
|
+
renameWithRetry10(temporaryPath, destinationPath);
|
|
10661
10722
|
} finally {
|
|
10662
10723
|
rmSync9(temporaryDir, { recursive: true, force: true });
|
|
10663
10724
|
}
|
|
@@ -10690,8 +10751,8 @@ async function putTasks(tasks) {
|
|
|
10690
10751
|
if (failure) throw failure.reason;
|
|
10691
10752
|
}
|
|
10692
10753
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
10693
|
-
const executionPlanPath =
|
|
10694
|
-
if (!
|
|
10754
|
+
const executionPlanPath = join26(executionPlanDir, "plan.json");
|
|
10755
|
+
if (!existsSync18(executionPlanPath)) {
|
|
10695
10756
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
10696
10757
|
}
|
|
10697
10758
|
const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
|
|
@@ -10718,7 +10779,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
10718
10779
|
}
|
|
10719
10780
|
validateExtractionCacheCompleteSentinels(executionPlanDir);
|
|
10720
10781
|
const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
|
|
10721
|
-
const encoder = readJsonFile(
|
|
10782
|
+
const encoder = readJsonFile(join26(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
|
|
10722
10783
|
const files = listFiles(executionPlanDir);
|
|
10723
10784
|
const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
|
|
10724
10785
|
for (const file of files) {
|
|
@@ -10771,25 +10832,25 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
10771
10832
|
};
|
|
10772
10833
|
}
|
|
10773
10834
|
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
10774
|
-
if (
|
|
10835
|
+
if (existsSync18(planV2Dir)) {
|
|
10775
10836
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
10776
10837
|
}
|
|
10777
10838
|
const publication = buildPlanV2Publication(executionPlanDir);
|
|
10778
10839
|
if (publication.storedBlobs.length > 0) {
|
|
10779
10840
|
throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
|
|
10780
10841
|
}
|
|
10781
|
-
mkdirSync12(
|
|
10782
|
-
const tempDir = mkdtempSync4(
|
|
10842
|
+
mkdirSync12(dirname13(planV2Dir), { recursive: true });
|
|
10843
|
+
const tempDir = mkdtempSync4(join26(dirname13(planV2Dir), ".plan-v2-build-"));
|
|
10783
10844
|
try {
|
|
10784
10845
|
for (const blob of publication.blobs) {
|
|
10785
10846
|
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
10786
10847
|
}
|
|
10787
10848
|
writeFileSync9(
|
|
10788
|
-
|
|
10849
|
+
join26(tempDir, "plan.json"),
|
|
10789
10850
|
canonicalJsonStringify(publication.manifest),
|
|
10790
10851
|
"utf-8"
|
|
10791
10852
|
);
|
|
10792
|
-
|
|
10853
|
+
renameWithRetry10(tempDir, planV2Dir);
|
|
10793
10854
|
return resultFromManifest(planV2Dir, publication.manifest);
|
|
10794
10855
|
} catch (error) {
|
|
10795
10856
|
rmSync9(tempDir, { recursive: true, force: true });
|
|
@@ -10832,7 +10893,7 @@ async function publishPlanV2FromV1(executionPlanDir, publisher) {
|
|
|
10832
10893
|
return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
|
|
10833
10894
|
}
|
|
10834
10895
|
async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
|
|
10835
|
-
const stagingRoot = mkdtempSync4(
|
|
10896
|
+
const stagingRoot = mkdtempSync4(join26(options.stagingParentDir ?? tmpdir(), ".execution-plan-"));
|
|
10836
10897
|
const digests = /* @__PURE__ */ new Map();
|
|
10837
10898
|
try {
|
|
10838
10899
|
await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
|
|
@@ -10843,7 +10904,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
10843
10904
|
// artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
|
|
10844
10905
|
whileAudioMixes: () => putBlobs(
|
|
10845
10906
|
publisher,
|
|
10846
|
-
listFiles(
|
|
10907
|
+
listFiles(join26(stagingRoot, "compiled")).map(
|
|
10847
10908
|
(file) => publishBlobFor(file.absolutePath, digests)
|
|
10848
10909
|
)
|
|
10849
10910
|
),
|
|
@@ -10863,19 +10924,19 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
10863
10924
|
}
|
|
10864
10925
|
}
|
|
10865
10926
|
async function planV2(projectDir, config, planV2Dir) {
|
|
10866
|
-
if (
|
|
10927
|
+
if (existsSync18(planV2Dir)) {
|
|
10867
10928
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
10868
10929
|
}
|
|
10869
10930
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
10870
10931
|
const manifest = await planV2WithPublisher(projectDir, config, publisher, {
|
|
10871
|
-
stagingParentDir:
|
|
10932
|
+
stagingParentDir: dirname13(planV2Dir)
|
|
10872
10933
|
});
|
|
10873
10934
|
return resultFromManifest(planV2Dir, manifest);
|
|
10874
10935
|
}
|
|
10875
10936
|
function resultFromManifest(planV2Dir, manifest) {
|
|
10876
10937
|
return {
|
|
10877
10938
|
planDir: planV2Dir,
|
|
10878
|
-
manifestPath:
|
|
10939
|
+
manifestPath: join26(planV2Dir, "plan.json"),
|
|
10879
10940
|
planProtocol: PLAN_PROTOCOL_V2,
|
|
10880
10941
|
planHash: manifest.planHash,
|
|
10881
10942
|
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
@@ -11011,8 +11072,8 @@ function parsePlanV2Manifest(value) {
|
|
|
11011
11072
|
return parsed;
|
|
11012
11073
|
}
|
|
11013
11074
|
function readPlanV2Manifest(planV2Dir) {
|
|
11014
|
-
const manifestPath =
|
|
11015
|
-
if (!
|
|
11075
|
+
const manifestPath = join26(planV2Dir, "plan.json");
|
|
11076
|
+
if (!existsSync18(manifestPath)) {
|
|
11016
11077
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
11017
11078
|
}
|
|
11018
11079
|
const value = readJsonFile(manifestPath, "v2 manifest");
|
|
@@ -11031,7 +11092,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
|
|
|
11031
11092
|
}
|
|
11032
11093
|
function verifyBlob(planV2Dir, artifact) {
|
|
11033
11094
|
const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
|
|
11034
|
-
if (!
|
|
11095
|
+
if (!existsSync18(sourcePath)) {
|
|
11035
11096
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
11036
11097
|
}
|
|
11037
11098
|
const stats = statSync7(sourcePath);
|
|
@@ -11044,7 +11105,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
11044
11105
|
return sourcePath;
|
|
11045
11106
|
}
|
|
11046
11107
|
function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
|
|
11047
|
-
if (
|
|
11108
|
+
if (existsSync18(destinationDir)) {
|
|
11048
11109
|
throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
|
|
11049
11110
|
}
|
|
11050
11111
|
const manifest = readPlanV2Manifest(planV2Dir);
|
|
@@ -11056,17 +11117,17 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
11056
11117
|
artifact,
|
|
11057
11118
|
sourcePath: verifyBlob(planV2Dir, artifact)
|
|
11058
11119
|
}));
|
|
11059
|
-
mkdirSync12(
|
|
11060
|
-
const tempDir = mkdtempSync4(
|
|
11120
|
+
mkdirSync12(dirname13(destinationDir), { recursive: true });
|
|
11121
|
+
const tempDir = mkdtempSync4(join26(dirname13(destinationDir), ".plan-v2-materialize-"));
|
|
11061
11122
|
try {
|
|
11062
11123
|
for (const { artifact, sourcePath } of verified) {
|
|
11063
|
-
const destinationPath =
|
|
11064
|
-
mkdirSync12(
|
|
11124
|
+
const destinationPath = join26(tempDir, ...artifact.path.split("/"));
|
|
11125
|
+
mkdirSync12(dirname13(destinationPath), { recursive: true });
|
|
11065
11126
|
copyFileSync8(sourcePath, destinationPath);
|
|
11066
11127
|
}
|
|
11067
11128
|
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
11068
11129
|
writeFileSync9(
|
|
11069
|
-
|
|
11130
|
+
join26(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
11070
11131
|
canonicalJsonStringify({
|
|
11071
11132
|
manifest,
|
|
11072
11133
|
target,
|
|
@@ -11074,7 +11135,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
11074
11135
|
}),
|
|
11075
11136
|
"utf-8"
|
|
11076
11137
|
);
|
|
11077
|
-
|
|
11138
|
+
renameWithRetry10(tempDir, destinationDir);
|
|
11078
11139
|
} catch (error) {
|
|
11079
11140
|
rmSync9(tempDir, { recursive: true, force: true });
|
|
11080
11141
|
throw error;
|
|
@@ -11089,7 +11150,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
11089
11150
|
// Join the artifact's OWN name, not the current constant: a plan written
|
|
11090
11151
|
// before the container change carries the legacy name, and materializing it
|
|
11091
11152
|
// under the new one would point at a file that was never written.
|
|
11092
|
-
audioPath: target.role === "assembler" ? audioArtifact ?
|
|
11153
|
+
audioPath: target.role === "assembler" ? audioArtifact ? join26(destinationDir, audioArtifact.path) : null : null
|
|
11093
11154
|
};
|
|
11094
11155
|
}
|
|
11095
11156
|
function assertRemoteSources(remoteSources, artifacts) {
|
|
@@ -11108,8 +11169,8 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
11108
11169
|
return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
|
|
11109
11170
|
}
|
|
11110
11171
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
11111
|
-
const markerPath =
|
|
11112
|
-
if (!
|
|
11172
|
+
const markerPath = join26(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
11173
|
+
if (!existsSync18(markerPath)) return null;
|
|
11113
11174
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
11114
11175
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
11115
11176
|
throw new PlanV2IntegrityError("malformed materialization marker");
|
|
@@ -11126,8 +11187,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
11126
11187
|
assertRemoteSources(remoteSources, required);
|
|
11127
11188
|
for (const artifact of required) {
|
|
11128
11189
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
11129
|
-
const path =
|
|
11130
|
-
if (!
|
|
11190
|
+
const path = join26(planDir, ...artifact.path.split("/"));
|
|
11191
|
+
if (!existsSync18(path) || statSync7(path).size !== artifact.sizeBytes) {
|
|
11131
11192
|
throw new PlanV2IntegrityError(
|
|
11132
11193
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
11133
11194
|
);
|
|
@@ -11142,12 +11203,12 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
11142
11203
|
// src/services/distributed/planV2Execution.ts
|
|
11143
11204
|
import { mkdtempSync as mkdtempSync6, rmSync as rmSync12 } from "node:fs";
|
|
11144
11205
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
11145
|
-
import { join as
|
|
11206
|
+
import { join as join29 } from "node:path";
|
|
11146
11207
|
|
|
11147
11208
|
// src/services/distributed/assemble.ts
|
|
11148
11209
|
import {
|
|
11149
11210
|
cpSync as cpSync3,
|
|
11150
|
-
existsSync as
|
|
11211
|
+
existsSync as existsSync19,
|
|
11151
11212
|
mkdirSync as mkdirSync13,
|
|
11152
11213
|
mkdtempSync as mkdtempSync5,
|
|
11153
11214
|
readFileSync as readFileSync9,
|
|
@@ -11156,18 +11217,18 @@ import {
|
|
|
11156
11217
|
statSync as statSync8,
|
|
11157
11218
|
writeFileSync as writeFileSync10
|
|
11158
11219
|
} from "node:fs";
|
|
11159
|
-
import { dirname as
|
|
11220
|
+
import { dirname as dirname14, join as join27 } from "node:path";
|
|
11160
11221
|
import {
|
|
11161
11222
|
appendRateCapArgs as appendRateCapArgs2,
|
|
11162
11223
|
appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
|
|
11163
|
-
describeFfmpegFailure as
|
|
11224
|
+
describeFfmpegFailure as describeFfmpegFailure5,
|
|
11164
11225
|
getEncoderPreset as getEncoderPreset5,
|
|
11165
11226
|
MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME4,
|
|
11166
11227
|
muxVideoWithAudio as muxVideoWithAudio2,
|
|
11167
11228
|
resolveConfig as resolveConfig4,
|
|
11168
11229
|
runFfmpeg as runFfmpeg8,
|
|
11169
11230
|
appendSoftwareEncoderThreadsArg,
|
|
11170
|
-
renameWithRetry as
|
|
11231
|
+
renameWithRetry as renameWithRetry11
|
|
11171
11232
|
} from "@hyperframes/engine";
|
|
11172
11233
|
import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
|
|
11173
11234
|
function cfrRateControlArgs(locked) {
|
|
@@ -11198,8 +11259,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11198
11259
|
}
|
|
11199
11260
|
const format = plan2.dimensions.format;
|
|
11200
11261
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
11201
|
-
if (!
|
|
11202
|
-
mkdirSync13(
|
|
11262
|
+
if (!existsSync19(dirname14(outputPath))) {
|
|
11263
|
+
mkdirSync13(dirname14(outputPath), { recursive: true });
|
|
11203
11264
|
}
|
|
11204
11265
|
const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
|
|
11205
11266
|
const fps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
|
|
@@ -11221,10 +11282,10 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11221
11282
|
};
|
|
11222
11283
|
try {
|
|
11223
11284
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
11224
|
-
const finalPath =
|
|
11225
|
-
const audio = audioPath !== null &&
|
|
11285
|
+
const finalPath = join27(workDir, `output.${format}`);
|
|
11286
|
+
const audio = audioPath !== null && existsSync19(audioPath) ? audioPath : null;
|
|
11226
11287
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
11227
|
-
|
|
11288
|
+
renameWithRetry11(finalPath, outputPath);
|
|
11228
11289
|
log.info("[assemble] stage timings", {
|
|
11229
11290
|
...stageMs,
|
|
11230
11291
|
chunks: chunkPaths.length,
|
|
@@ -11240,7 +11301,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11240
11301
|
});
|
|
11241
11302
|
}
|
|
11242
11303
|
}
|
|
11243
|
-
const fileSize =
|
|
11304
|
+
const fileSize = existsSync19(outputPath) ? statSync8(outputPath).size : 0;
|
|
11244
11305
|
return {
|
|
11245
11306
|
outputPath,
|
|
11246
11307
|
durationMs: Date.now() - start,
|
|
@@ -11249,15 +11310,15 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11249
11310
|
};
|
|
11250
11311
|
}
|
|
11251
11312
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
11252
|
-
const planJsonPath =
|
|
11253
|
-
const chunksJsonPath =
|
|
11254
|
-
if (!
|
|
11313
|
+
const planJsonPath = join27(planDir, "plan.json");
|
|
11314
|
+
const chunksJsonPath = join27(planDir, "meta", "chunks.json");
|
|
11315
|
+
if (!existsSync19(planJsonPath)) {
|
|
11255
11316
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
11256
11317
|
}
|
|
11257
11318
|
const plan2 = JSON.parse(readFileSync9(planJsonPath, "utf-8"));
|
|
11258
11319
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
11259
11320
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
11260
|
-
if (!
|
|
11321
|
+
if (!existsSync19(chunksJsonPath)) {
|
|
11261
11322
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
11262
11323
|
}
|
|
11263
11324
|
const chunks = JSON.parse(readFileSync9(chunksJsonPath, "utf-8"));
|
|
@@ -11272,7 +11333,7 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
11272
11333
|
}
|
|
11273
11334
|
}
|
|
11274
11335
|
for (const path of chunkPaths.flat()) {
|
|
11275
|
-
if (!
|
|
11336
|
+
if (!existsSync19(path)) {
|
|
11276
11337
|
throw new Error(`[assemble] chunk path does not exist: ${path}`);
|
|
11277
11338
|
}
|
|
11278
11339
|
}
|
|
@@ -11301,7 +11362,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
|
|
|
11301
11362
|
return args;
|
|
11302
11363
|
}
|
|
11303
11364
|
async function reencodeCfr(ctx, chunkPaths, encoder) {
|
|
11304
|
-
const cfrOutputPath =
|
|
11365
|
+
const cfrOutputPath = join27(ctx.workDir, `cfr.${ctx.format}`);
|
|
11305
11366
|
const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
|
|
11306
11367
|
appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
|
|
11307
11368
|
cfrArgs.push("-y", cfrOutputPath);
|
|
@@ -11328,7 +11389,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
11328
11389
|
);
|
|
11329
11390
|
return;
|
|
11330
11391
|
}
|
|
11331
|
-
const paddedAudioPath =
|
|
11392
|
+
const paddedAudioPath = join27(ctx.workDir, "audio-padded.m4a");
|
|
11332
11393
|
const padTrimResult = await ctx.timed(
|
|
11333
11394
|
"audioMs",
|
|
11334
11395
|
() => padOrTrimAudioToVideoFrameCount({
|
|
@@ -11368,7 +11429,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
11368
11429
|
}
|
|
11369
11430
|
}
|
|
11370
11431
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
11371
|
-
const listPath =
|
|
11432
|
+
const listPath = join27(workDir, "concat-list.txt");
|
|
11372
11433
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
11373
11434
|
writeFileSync10(listPath, `${body}
|
|
11374
11435
|
`, "utf-8");
|
|
@@ -11388,7 +11449,7 @@ async function runAssembleFfmpeg(args, step, signal, timeout) {
|
|
|
11388
11449
|
const result = await runFfmpeg8(args, { signal, timeout });
|
|
11389
11450
|
if (!result.success) {
|
|
11390
11451
|
throw encoderFailureError(`[assemble] ffmpeg ${step} failed`, {
|
|
11391
|
-
error:
|
|
11452
|
+
error: describeFfmpegFailure5(result, timeout),
|
|
11392
11453
|
failureReason: result.failureReason
|
|
11393
11454
|
});
|
|
11394
11455
|
}
|
|
@@ -11406,12 +11467,12 @@ function stitchesSeparateAudio(planDir) {
|
|
|
11406
11467
|
return lockedAudio(planDir).separateAudio === true;
|
|
11407
11468
|
}
|
|
11408
11469
|
function planFormat(planDir) {
|
|
11409
|
-
const plan2 = JSON.parse(readFileSync9(
|
|
11470
|
+
const plan2 = JSON.parse(readFileSync9(join27(planDir, "plan.json"), "utf-8"));
|
|
11410
11471
|
return plan2.dimensions.format;
|
|
11411
11472
|
}
|
|
11412
11473
|
function lockedAudio(planDir) {
|
|
11413
|
-
const encoderJsonPath =
|
|
11414
|
-
if (!
|
|
11474
|
+
const encoderJsonPath = join27(planDir, "meta", "encoder.json");
|
|
11475
|
+
if (!existsSync19(encoderJsonPath)) return {};
|
|
11415
11476
|
return JSON.parse(readFileSync9(encoderJsonPath, "utf-8"));
|
|
11416
11477
|
}
|
|
11417
11478
|
function readCfrEncoder(planDir, format) {
|
|
@@ -11420,8 +11481,8 @@ function readCfrEncoder(planDir, format) {
|
|
|
11420
11481
|
`[assemble] cfr=true is only supported for format="mp4" (got "${format}"). Stream-copy paths for webm and mov already produce exact avg_frame_rate; cfr re-encode is not needed.`
|
|
11421
11482
|
);
|
|
11422
11483
|
}
|
|
11423
|
-
const encoderJsonPath =
|
|
11424
|
-
if (!
|
|
11484
|
+
const encoderJsonPath = join27(planDir, "meta", "encoder.json");
|
|
11485
|
+
if (!existsSync19(encoderJsonPath)) {
|
|
11425
11486
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
11426
11487
|
}
|
|
11427
11488
|
const encoderJson = JSON.parse(readFileSync9(encoderJsonPath, "utf-8"));
|
|
@@ -11433,7 +11494,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
11433
11494
|
return encoderJson;
|
|
11434
11495
|
}
|
|
11435
11496
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
11436
|
-
if (
|
|
11497
|
+
if (existsSync19(outputPath)) rmSync10(outputPath, { recursive: true, force: true });
|
|
11437
11498
|
mkdirSync13(outputPath, { recursive: true });
|
|
11438
11499
|
let globalIdx = 0;
|
|
11439
11500
|
for (const chunkDir of chunkPaths) {
|
|
@@ -11447,8 +11508,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11447
11508
|
throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
|
|
11448
11509
|
}
|
|
11449
11510
|
for (const frame of frames) {
|
|
11450
|
-
const dst =
|
|
11451
|
-
cpSync3(
|
|
11511
|
+
const dst = join27(outputPath, formatExportFrameName(globalIdx, "png"));
|
|
11512
|
+
cpSync3(join27(chunkDir, frame), dst);
|
|
11452
11513
|
globalIdx += 1;
|
|
11453
11514
|
}
|
|
11454
11515
|
}
|
|
@@ -11457,14 +11518,14 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11457
11518
|
`[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
|
|
11458
11519
|
);
|
|
11459
11520
|
}
|
|
11460
|
-
if (audioPath !== null &&
|
|
11461
|
-
const sidecar =
|
|
11521
|
+
if (audioPath !== null && existsSync19(audioPath)) {
|
|
11522
|
+
const sidecar = join27(outputPath, MIXED_AUDIO_FILENAME4);
|
|
11462
11523
|
cpSync3(audioPath, sidecar);
|
|
11463
11524
|
}
|
|
11464
11525
|
let fileSize = 0;
|
|
11465
11526
|
for (const name of readdirSync8(outputPath)) {
|
|
11466
11527
|
try {
|
|
11467
|
-
fileSize += statSync8(
|
|
11528
|
+
fileSize += statSync8(join27(outputPath, name)).size;
|
|
11468
11529
|
} catch {
|
|
11469
11530
|
}
|
|
11470
11531
|
}
|
|
@@ -11478,9 +11539,9 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11478
11539
|
|
|
11479
11540
|
// src/services/distributed/renderChunk.ts
|
|
11480
11541
|
import { randomBytes } from "node:crypto";
|
|
11481
|
-
import { existsSync as
|
|
11542
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync10, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
|
|
11482
11543
|
import { cpus } from "node:os";
|
|
11483
|
-
import { join as
|
|
11544
|
+
import { join as join28 } from "node:path";
|
|
11484
11545
|
import {
|
|
11485
11546
|
assertSwiftShader,
|
|
11486
11547
|
BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
|
|
@@ -11604,7 +11665,7 @@ function hashChunkOutput(outputPath, kind) {
|
|
|
11604
11665
|
if (kind === "file") return sha256Hex(readFileSync10(outputPath));
|
|
11605
11666
|
const entries = readdirSync9(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
11606
11667
|
const lines = entries.map(
|
|
11607
|
-
(name) => `${name}\0${sha256Hex(readFileSync10(
|
|
11668
|
+
(name) => `${name}\0${sha256Hex(readFileSync10(join28(outputPath, name)))}`
|
|
11608
11669
|
);
|
|
11609
11670
|
return sha256Hex(lines.join("\0"));
|
|
11610
11671
|
}
|
|
@@ -11621,10 +11682,10 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
|
|
|
11621
11682
|
async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
11622
11683
|
const start = Date.now();
|
|
11623
11684
|
const log = defaultLogger;
|
|
11624
|
-
const planJsonPath =
|
|
11625
|
-
const encoderJsonPath =
|
|
11626
|
-
const chunksJsonPath =
|
|
11627
|
-
if (!
|
|
11685
|
+
const planJsonPath = join28(planDir, "plan.json");
|
|
11686
|
+
const encoderJsonPath = join28(planDir, "meta", "encoder.json");
|
|
11687
|
+
const chunksJsonPath = join28(planDir, "meta", "chunks.json");
|
|
11688
|
+
if (!existsSync20(planJsonPath)) {
|
|
11628
11689
|
throw new RenderChunkValidationError(
|
|
11629
11690
|
MISSING_PLAN_ARTIFACT,
|
|
11630
11691
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
@@ -11640,7 +11701,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11640
11701
|
const v2Manifest = v2Materialization?.manifest ?? null;
|
|
11641
11702
|
const remoteSources = v2Materialization?.remoteSources ?? {};
|
|
11642
11703
|
for (const required of [encoderJsonPath, chunksJsonPath]) {
|
|
11643
|
-
if (!
|
|
11704
|
+
if (!existsSync20(required)) {
|
|
11644
11705
|
throw new RenderChunkValidationError(
|
|
11645
11706
|
MISSING_PLAN_ARTIFACT,
|
|
11646
11707
|
`[renderChunk] planDir is missing required artifact: ${required}`
|
|
@@ -11649,9 +11710,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11649
11710
|
}
|
|
11650
11711
|
const encoder = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
11651
11712
|
const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
|
|
11652
|
-
const videosJsonPath =
|
|
11713
|
+
const videosJsonPath = join28(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
11653
11714
|
let planVideos = null;
|
|
11654
|
-
if (
|
|
11715
|
+
if (existsSync20(videosJsonPath)) {
|
|
11655
11716
|
try {
|
|
11656
11717
|
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync10(videosJsonPath, "utf-8")));
|
|
11657
11718
|
} catch (err) {
|
|
@@ -11681,8 +11742,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11681
11742
|
}
|
|
11682
11743
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
11683
11744
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
11684
|
-
const compiledDir =
|
|
11685
|
-
if (!
|
|
11745
|
+
const compiledDir = join28(planDir, "compiled");
|
|
11746
|
+
if (!existsSync20(compiledDir)) {
|
|
11686
11747
|
throw new RenderChunkValidationError(
|
|
11687
11748
|
MISSING_PLAN_ARTIFACT,
|
|
11688
11749
|
`[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
|
|
@@ -11739,7 +11800,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11739
11800
|
};
|
|
11740
11801
|
const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
11741
11802
|
mkdirSync14(workDir, { recursive: true });
|
|
11742
|
-
const framesDir =
|
|
11803
|
+
const framesDir = join28(workDir, "captured-frames");
|
|
11743
11804
|
mkdirSync14(framesDir, { recursive: true });
|
|
11744
11805
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
11745
11806
|
const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
|
|
@@ -11764,7 +11825,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11764
11825
|
dimensions: plan2.dimensions,
|
|
11765
11826
|
encoder,
|
|
11766
11827
|
videoCount: planVideos?.videos.length ?? 0,
|
|
11767
|
-
html: readFileSync10(
|
|
11828
|
+
html: readFileSync10(join28(compiledDir, "index.html"), "utf-8"),
|
|
11768
11829
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
11769
11830
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
11770
11831
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -11932,10 +11993,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11932
11993
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
11933
11994
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
11934
11995
|
if (isPngSequence) {
|
|
11935
|
-
if (!
|
|
11996
|
+
if (!existsSync20(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
|
|
11936
11997
|
} else {
|
|
11937
|
-
const outDir =
|
|
11938
|
-
if (!
|
|
11998
|
+
const outDir = join28(outputChunkPath, "..");
|
|
11999
|
+
if (!existsSync20(outDir)) mkdirSync14(outDir, { recursive: true });
|
|
11939
12000
|
}
|
|
11940
12001
|
const encodeInput = {
|
|
11941
12002
|
job,
|
|
@@ -12178,8 +12239,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12178
12239
|
|
|
12179
12240
|
// src/services/distributed/planV2Execution.ts
|
|
12180
12241
|
async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
12181
|
-
const workRoot = mkdtempSync6(
|
|
12182
|
-
const materializedPlanDir =
|
|
12242
|
+
const workRoot = mkdtempSync6(join29(tmpdir2(), "hf-plan-v2-chunk-"));
|
|
12243
|
+
const materializedPlanDir = join29(workRoot, "plan");
|
|
12183
12244
|
try {
|
|
12184
12245
|
materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
|
|
12185
12246
|
return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
|
|
@@ -12188,8 +12249,8 @@ async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
|
12188
12249
|
}
|
|
12189
12250
|
}
|
|
12190
12251
|
async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
12191
|
-
const workRoot = mkdtempSync6(
|
|
12192
|
-
const materializedPlanDir =
|
|
12252
|
+
const workRoot = mkdtempSync6(join29(tmpdir2(), "hf-plan-v2-assembler-"));
|
|
12253
|
+
const materializedPlanDir = join29(workRoot, "plan");
|
|
12193
12254
|
try {
|
|
12194
12255
|
const materialized = materializePlanV2Target(
|
|
12195
12256
|
planV2Dir,
|
|
@@ -12481,7 +12542,7 @@ function validateIntDimension(field, value) {
|
|
|
12481
12542
|
// src/services/distributed/projectHash.ts
|
|
12482
12543
|
import { closeSync as closeSync5, openSync as openSync5, readdirSync as readdirSync10, readSync as readSync4, realpathSync as realpathSync2, statSync as statSync9 } from "node:fs";
|
|
12483
12544
|
import { createHash as createHash8 } from "node:crypto";
|
|
12484
|
-
import { join as
|
|
12545
|
+
import { join as join30 } from "node:path";
|
|
12485
12546
|
var SITE_ID_SCHEME = "site-v2\0";
|
|
12486
12547
|
var READ_CHUNK_BYTES = 256 * 1024;
|
|
12487
12548
|
function listSiteFiles(projectDir) {
|
|
@@ -12490,7 +12551,7 @@ function listSiteFiles(projectDir) {
|
|
|
12490
12551
|
function walk(dir, rel) {
|
|
12491
12552
|
for (const name of readdirSync10(dir).sort()) {
|
|
12492
12553
|
if (rel === "" && PLAN_PROJECT_DIR_SKIP_SEGMENTS.has(name)) continue;
|
|
12493
|
-
const full =
|
|
12554
|
+
const full = join30(dir, name);
|
|
12494
12555
|
const relPath = rel === "" ? name : `${rel}/${name}`;
|
|
12495
12556
|
let stat;
|
|
12496
12557
|
try {
|
|
@@ -12520,7 +12581,7 @@ function hashProjectDir(projectDir, files = listSiteFiles(projectDir)) {
|
|
|
12520
12581
|
for (const rel of files) {
|
|
12521
12582
|
hash.update(rel);
|
|
12522
12583
|
hash.update("\0");
|
|
12523
|
-
const fd = openSync5(
|
|
12584
|
+
const fd = openSync5(join30(projectDir, rel), "r");
|
|
12524
12585
|
try {
|
|
12525
12586
|
let read;
|
|
12526
12587
|
while ((read = readSync4(fd, chunk, 0, chunk.length, null)) > 0) {
|