@hyperframes/producer 0.8.227 → 0.8.229
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/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/services/renderOrchestrator.ts
|
|
2
2
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
3
3
|
import {
|
|
4
|
-
existsSync as
|
|
4
|
+
existsSync as existsSync18,
|
|
5
5
|
mkdirSync as mkdirSync10,
|
|
6
6
|
readdirSync as readdirSync5,
|
|
7
7
|
rmSync as rmSync10,
|
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
augmentProtocolTimeoutError,
|
|
57
57
|
augmentPageNavigationTimeoutError
|
|
58
58
|
} from "@hyperframes/engine";
|
|
59
|
-
import { join as
|
|
59
|
+
import { join as join28, dirname as dirname12, resolve as resolve8 } from "path";
|
|
60
60
|
import { totalmem as totalmem2 } from "node:os";
|
|
61
61
|
import { randomUUID } from "crypto";
|
|
62
62
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
@@ -9829,7 +9829,7 @@ async function planVideoLayerPassthrough(input) {
|
|
|
9829
9829
|
}
|
|
9830
9830
|
|
|
9831
9831
|
// src/services/render/stages/audioStage.ts
|
|
9832
|
-
import { join as
|
|
9832
|
+
import { join as join21 } from "node:path";
|
|
9833
9833
|
import {
|
|
9834
9834
|
MIXED_AUDIO_FILENAME,
|
|
9835
9835
|
audioFailureDiagnostics,
|
|
@@ -9838,6 +9838,16 @@ import {
|
|
|
9838
9838
|
collectScratchLeftovers
|
|
9839
9839
|
} from "@hyperframes/engine";
|
|
9840
9840
|
|
|
9841
|
+
// src/services/render/audioDeliveryLimit.ts
|
|
9842
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
9843
|
+
import { dirname as dirname9, join as join20 } from "node:path";
|
|
9844
|
+
import {
|
|
9845
|
+
applyAudioFxChain,
|
|
9846
|
+
describeFfmpegFailure as describeFfmpegFailure3,
|
|
9847
|
+
encodeAac as encodeAac2,
|
|
9848
|
+
renameWithRetry as renameWithRetry6
|
|
9849
|
+
} from "@hyperframes/engine";
|
|
9850
|
+
|
|
9841
9851
|
// src/services/render/audioPadTrim.ts
|
|
9842
9852
|
import { spawn } from "node:child_process";
|
|
9843
9853
|
import { mkdtempSync as mkdtempSync3 } from "node:fs";
|
|
@@ -10298,6 +10308,48 @@ async function runFfprobeJson(args, signal) {
|
|
|
10298
10308
|
}
|
|
10299
10309
|
}
|
|
10300
10310
|
|
|
10311
|
+
// src/services/render/audioDeliveryLimit.ts
|
|
10312
|
+
var LIMIT_CEILING_DBTP = AAC_DELIVERY_TRUE_PEAK_DBFS - 2;
|
|
10313
|
+
var LIMIT_CHAIN = {
|
|
10314
|
+
version: 1,
|
|
10315
|
+
nodes: [{ type: "truepeak", id: "delivery", params: { ceiling: LIMIT_CEILING_DBTP } }]
|
|
10316
|
+
};
|
|
10317
|
+
async function limitDeliveryPeaks(input, deps = {
|
|
10318
|
+
measure: measureTruePeakDbfs,
|
|
10319
|
+
applyChain: applyAudioFxChain,
|
|
10320
|
+
encode: encodeAac2,
|
|
10321
|
+
rename: renameWithRetry6,
|
|
10322
|
+
sourceExists: existsSync13
|
|
10323
|
+
}) {
|
|
10324
|
+
const { audioPath, sourcePath, signal, timeoutMs } = input;
|
|
10325
|
+
if (!deps.sourceExists(sourcePath)) return { success: true };
|
|
10326
|
+
const peak = await deps.measure(audioPath, { timeoutMs, signal });
|
|
10327
|
+
if (peak <= AAC_DELIVERY_TRUE_PEAK_DBFS) return { success: true };
|
|
10328
|
+
const limitedPath = join20(dirname9(sourcePath), "audio-source-limited.wav");
|
|
10329
|
+
try {
|
|
10330
|
+
await deps.applyChain(sourcePath, LIMIT_CHAIN, limitedPath, { trackId: "delivery", signal });
|
|
10331
|
+
} catch (error) {
|
|
10332
|
+
if (signal?.aborted) throw error;
|
|
10333
|
+
input.warn?.("[audio] true-peak limit failed, the whole-mix cut applies", {
|
|
10334
|
+
error: error instanceof Error ? error.message : String(error)
|
|
10335
|
+
});
|
|
10336
|
+
return { success: true };
|
|
10337
|
+
}
|
|
10338
|
+
const encoded = await deps.encode(limitedPath, audioPath, input.targetDurationSeconds, {
|
|
10339
|
+
signal,
|
|
10340
|
+
timeout: timeoutMs
|
|
10341
|
+
});
|
|
10342
|
+
if (!encoded.success) {
|
|
10343
|
+
return {
|
|
10344
|
+
success: false,
|
|
10345
|
+
error: `[audioDeliveryLimit] ${describeFfmpegFailure3(encoded, timeoutMs)}`,
|
|
10346
|
+
failureReason: encoded.failureReason
|
|
10347
|
+
};
|
|
10348
|
+
}
|
|
10349
|
+
deps.rename(limitedPath, sourcePath, { copyFallback: true });
|
|
10350
|
+
return { success: true };
|
|
10351
|
+
}
|
|
10352
|
+
|
|
10301
10353
|
// src/services/render/stages/audioStage.ts
|
|
10302
10354
|
function assertAudioNotInterrupted(failures) {
|
|
10303
10355
|
const interrupted = failures?.find((failure) => failure.reason === "external_interruption");
|
|
@@ -10321,13 +10373,22 @@ function runAudioStage(input) {
|
|
|
10321
10373
|
}
|
|
10322
10374
|
async function runAudioStageScoped(input) {
|
|
10323
10375
|
const stageStart = Date.now();
|
|
10324
|
-
const pcmSourcePath =
|
|
10376
|
+
const pcmSourcePath = join21(input.workDir, "audio-source.wav");
|
|
10325
10377
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
10326
10378
|
try {
|
|
10327
10379
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
10328
10380
|
let audioLoweredDb;
|
|
10329
10381
|
if (result.hasAudio) {
|
|
10330
10382
|
const guardStart = Date.now();
|
|
10383
|
+
const limited = await limitDeliveryPeaks({
|
|
10384
|
+
audioPath: result.audioOutputPath,
|
|
10385
|
+
sourcePath: pcmSourcePath,
|
|
10386
|
+
targetDurationSeconds: duration,
|
|
10387
|
+
signal: input.abortSignal,
|
|
10388
|
+
timeoutMs: input.ffmpegProcessTimeout,
|
|
10389
|
+
warn: (message, details) => input.log?.warn?.(message, details)
|
|
10390
|
+
});
|
|
10391
|
+
if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
|
|
10331
10392
|
const peak = await constrainAacTruePeak({
|
|
10332
10393
|
audioPath: result.audioOutputPath,
|
|
10333
10394
|
sourcePath: pcmSourcePath,
|
|
@@ -10359,7 +10420,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
10359
10420
|
assertNotAborted,
|
|
10360
10421
|
log
|
|
10361
10422
|
} = input;
|
|
10362
|
-
const audioOutputPath =
|
|
10423
|
+
const audioOutputPath = join21(workDir, MIXED_AUDIO_FILENAME);
|
|
10363
10424
|
let hasAudio = false;
|
|
10364
10425
|
let audioError;
|
|
10365
10426
|
let audioFailures;
|
|
@@ -10370,7 +10431,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
10370
10431
|
audioResult = await processCompositionAudio(
|
|
10371
10432
|
audios,
|
|
10372
10433
|
projectDir,
|
|
10373
|
-
|
|
10434
|
+
join21(workDir, "audio-work"),
|
|
10374
10435
|
audioOutputPath,
|
|
10375
10436
|
duration,
|
|
10376
10437
|
abortSignal,
|
|
@@ -10438,8 +10499,8 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
10438
10499
|
}
|
|
10439
10500
|
|
|
10440
10501
|
// src/services/render/stages/captureStage.ts
|
|
10441
|
-
import { existsSync as
|
|
10442
|
-
import { join as
|
|
10502
|
+
import { existsSync as existsSync14, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
|
|
10503
|
+
import { join as join22 } from "node:path";
|
|
10443
10504
|
import {
|
|
10444
10505
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
10445
10506
|
frameFileExtension,
|
|
@@ -10509,16 +10570,16 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
10509
10570
|
}
|
|
10510
10571
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
10511
10572
|
let total = sampleDirectoryBytes(framesDir);
|
|
10512
|
-
if (!
|
|
10573
|
+
if (!existsSync14(workDir)) return total;
|
|
10513
10574
|
for (const attempt of readdirSync3(workDir)) {
|
|
10514
10575
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
10515
|
-
total += sampleDirectoryBytes(
|
|
10576
|
+
total += sampleDirectoryBytes(join22(workDir, attempt));
|
|
10516
10577
|
continue;
|
|
10517
10578
|
}
|
|
10518
10579
|
if (!/^capture-attempt-\d+$/.test(attempt)) continue;
|
|
10519
|
-
const attemptDir =
|
|
10580
|
+
const attemptDir = join22(workDir, attempt);
|
|
10520
10581
|
for (const name of readdirSync3(attemptDir)) {
|
|
10521
|
-
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(
|
|
10582
|
+
if (/^worker-\d+$/.test(name)) total += sampleDirectoryBytes(join22(attemptDir, name));
|
|
10522
10583
|
}
|
|
10523
10584
|
}
|
|
10524
10585
|
return total;
|
|
@@ -10812,8 +10873,8 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
|
|
|
10812
10873
|
}
|
|
10813
10874
|
|
|
10814
10875
|
// src/services/render/stages/captureHdrStage.ts
|
|
10815
|
-
import { existsSync as
|
|
10816
|
-
import { join as
|
|
10876
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync8 } from "node:fs";
|
|
10877
|
+
import { join as join26 } from "node:path";
|
|
10817
10878
|
import {
|
|
10818
10879
|
closeCaptureSession as closeCaptureSession7,
|
|
10819
10880
|
cloneCaptureWarnings as cloneCaptureWarnings2,
|
|
@@ -10885,7 +10946,7 @@ function createHdrImageTransferCache(options = {}) {
|
|
|
10885
10946
|
}
|
|
10886
10947
|
|
|
10887
10948
|
// src/services/render/stages/captureHdrSequentialLoop.ts
|
|
10888
|
-
import { join as
|
|
10949
|
+
import { join as join23 } from "node:path";
|
|
10889
10950
|
import {
|
|
10890
10951
|
TRANSITIONS,
|
|
10891
10952
|
crossfade
|
|
@@ -11010,7 +11071,7 @@ async function runSequentialLayeredFrameLoop(input) {
|
|
|
11010
11071
|
);
|
|
11011
11072
|
if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
|
|
11012
11073
|
writeFileExclusiveSync(
|
|
11013
|
-
|
|
11074
|
+
join23(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
|
|
11014
11075
|
normalCanvas
|
|
11015
11076
|
);
|
|
11016
11077
|
}
|
|
@@ -11042,7 +11103,7 @@ async function runSequentialLayeredFrameLoop(input) {
|
|
|
11042
11103
|
}
|
|
11043
11104
|
|
|
11044
11105
|
// src/services/render/stages/captureHdrHybridLoop.ts
|
|
11045
|
-
import { join as
|
|
11106
|
+
import { join as join25 } from "node:path";
|
|
11046
11107
|
import {
|
|
11047
11108
|
TRANSITIONS as TRANSITIONS2,
|
|
11048
11109
|
closeCaptureSession as closeCaptureSession6,
|
|
@@ -11056,9 +11117,9 @@ import {
|
|
|
11056
11117
|
// src/services/shaderTransitionWorkerPool.ts
|
|
11057
11118
|
import { Worker } from "node:worker_threads";
|
|
11058
11119
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
11059
|
-
import { dirname as
|
|
11120
|
+
import { dirname as dirname10, join as join24 } from "node:path";
|
|
11060
11121
|
import { createRequire } from "node:module";
|
|
11061
|
-
import { existsSync as
|
|
11122
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
11062
11123
|
import { cpus as cpus2 } from "node:os";
|
|
11063
11124
|
function resolveWorkerEntry(explicit) {
|
|
11064
11125
|
if (explicit && explicit.length > 0) {
|
|
@@ -11069,10 +11130,10 @@ function resolveWorkerEntry(explicit) {
|
|
|
11069
11130
|
const isTs = override.endsWith(".ts");
|
|
11070
11131
|
return { path: override, isTs };
|
|
11071
11132
|
}
|
|
11072
|
-
const moduleDir =
|
|
11073
|
-
const jsPath =
|
|
11074
|
-
if (
|
|
11075
|
-
const tsPath =
|
|
11133
|
+
const moduleDir = dirname10(fileURLToPath3(import.meta.url));
|
|
11134
|
+
const jsPath = join24(moduleDir, "shaderTransitionWorker.js");
|
|
11135
|
+
if (existsSync15(jsPath)) return { path: jsPath, isTs: false };
|
|
11136
|
+
const tsPath = join24(moduleDir, "shaderTransitionWorker.ts");
|
|
11076
11137
|
return { path: tsPath, isTs: true };
|
|
11077
11138
|
}
|
|
11078
11139
|
function buildExecArgv(entryIsTs) {
|
|
@@ -11440,7 +11501,7 @@ async function runHybridLayeredFrameLoop(input) {
|
|
|
11440
11501
|
);
|
|
11441
11502
|
if (debugDumpEnabled && debugDumpDir && i % 30 === 0) {
|
|
11442
11503
|
writeFileExclusiveSync(
|
|
11443
|
-
|
|
11504
|
+
join25(debugDumpDir, `frame_${String(i).padStart(4, "0")}_final_rgb48le.bin`),
|
|
11444
11505
|
canvas
|
|
11445
11506
|
);
|
|
11446
11507
|
}
|
|
@@ -11631,8 +11692,8 @@ async function runCaptureHdrStage(input) {
|
|
|
11631
11692
|
if (hdrVideoFrameSources.has(v.id)) hdrVideoEndTimes.set(v.id, v.end);
|
|
11632
11693
|
}
|
|
11633
11694
|
const debugDumpEnabled = process.env.KEEP_TEMP === "1";
|
|
11634
|
-
const debugDumpDir = debugDumpEnabled ?
|
|
11635
|
-
if (debugDumpDir && !
|
|
11695
|
+
const debugDumpDir = debugDumpEnabled ? join26(framesDir, "debug-composite") : null;
|
|
11696
|
+
if (debugDumpDir && !existsSync16(debugDumpDir)) {
|
|
11636
11697
|
mkdirSync8(debugDumpDir, { recursive: true });
|
|
11637
11698
|
}
|
|
11638
11699
|
const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
|
|
@@ -11789,7 +11850,7 @@ async function runCaptureHdrStage(input) {
|
|
|
11789
11850
|
// src/services/render/stages/encodeStage.ts
|
|
11790
11851
|
import {
|
|
11791
11852
|
copyFileSync as copyFileSync4,
|
|
11792
|
-
existsSync as
|
|
11853
|
+
existsSync as existsSync17,
|
|
11793
11854
|
mkdirSync as mkdirSync9,
|
|
11794
11855
|
readdirSync as readdirSync4,
|
|
11795
11856
|
readFileSync as readFileSync7,
|
|
@@ -11798,7 +11859,7 @@ import {
|
|
|
11798
11859
|
writeFileSync as writeFileSync5
|
|
11799
11860
|
} from "node:fs";
|
|
11800
11861
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
11801
|
-
import { dirname as
|
|
11862
|
+
import { dirname as dirname11, join as join27 } from "node:path";
|
|
11802
11863
|
import {
|
|
11803
11864
|
encodeFramesChunkedConcat,
|
|
11804
11865
|
encodeFramesFromDir,
|
|
@@ -11923,7 +11984,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
11923
11984
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
11924
11985
|
if (failure) return failure;
|
|
11925
11986
|
}
|
|
11926
|
-
const fileSize =
|
|
11987
|
+
const fileSize = existsSync17(outputPath) ? statSync7(outputPath).size : 0;
|
|
11927
11988
|
return {
|
|
11928
11989
|
success: true,
|
|
11929
11990
|
outputPath,
|
|
@@ -12009,7 +12070,7 @@ function encodeWhileCapturing(input, totalFrames) {
|
|
|
12009
12070
|
await new Promise((resolve13) => wake = resolve13);
|
|
12010
12071
|
}
|
|
12011
12072
|
if (signal.aborted) return;
|
|
12012
|
-
const mergedPath =
|
|
12073
|
+
const mergedPath = join27(input.framesDir, formatCaptureFrameName(next, "jpg"));
|
|
12013
12074
|
const frame = await readLandedFrame(landed.get(next), mergedPath);
|
|
12014
12075
|
landed.delete(next);
|
|
12015
12076
|
if (!await stream.writeFrame(frame)) {
|
|
@@ -12084,7 +12145,7 @@ async function runEncodeStage(input) {
|
|
|
12084
12145
|
const stage5Start = Date.now();
|
|
12085
12146
|
if (isPngSequence) {
|
|
12086
12147
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
12087
|
-
if (!
|
|
12148
|
+
if (!existsSync17(outputPath)) mkdirSync9(outputPath, { recursive: true });
|
|
12088
12149
|
const captured = readdirSync4(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
12089
12150
|
if (captured.length === 0) {
|
|
12090
12151
|
throw new Error(
|
|
@@ -12092,11 +12153,11 @@ async function runEncodeStage(input) {
|
|
|
12092
12153
|
);
|
|
12093
12154
|
}
|
|
12094
12155
|
captured.forEach((name, i) => {
|
|
12095
|
-
const dst =
|
|
12096
|
-
copyFileSync4(
|
|
12156
|
+
const dst = join27(outputPath, formatExportFrameName(i, "png"));
|
|
12157
|
+
copyFileSync4(join27(framesDir, name), dst);
|
|
12097
12158
|
});
|
|
12098
|
-
if (hasAudio && audioOutputPath &&
|
|
12099
|
-
copyFileSync4(audioOutputPath,
|
|
12159
|
+
if (hasAudio && audioOutputPath && existsSync17(audioOutputPath)) {
|
|
12160
|
+
copyFileSync4(audioOutputPath, join27(outputPath, MIXED_AUDIO_FILENAME2));
|
|
12100
12161
|
log.info(
|
|
12101
12162
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
12102
12163
|
);
|
|
@@ -12116,7 +12177,7 @@ async function runEncodeStage(input) {
|
|
|
12116
12177
|
const encodeResult2 = await encodeGifFromDir(framesDir, framePattern2, outputPath, {
|
|
12117
12178
|
fps: job.config.fps,
|
|
12118
12179
|
loop,
|
|
12119
|
-
palettePath:
|
|
12180
|
+
palettePath: join27(dirname11(videoOnlyPath), "gif-palette.png"),
|
|
12120
12181
|
preserveAlpha: needsAlpha,
|
|
12121
12182
|
signal: abortSignal,
|
|
12122
12183
|
timeout: engineCfg.ffmpegEncodeTimeout
|
|
@@ -12326,7 +12387,7 @@ function sampleDirectoryBytes(dir) {
|
|
|
12326
12387
|
continue;
|
|
12327
12388
|
}
|
|
12328
12389
|
for (const name of entries) {
|
|
12329
|
-
const full =
|
|
12390
|
+
const full = join28(current, name);
|
|
12330
12391
|
try {
|
|
12331
12392
|
const st = statSync8(full);
|
|
12332
12393
|
if (st.isDirectory()) {
|
|
@@ -12475,8 +12536,8 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
12475
12536
|
const ranges = [];
|
|
12476
12537
|
let rangeStart = null;
|
|
12477
12538
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12478
|
-
const framePath =
|
|
12479
|
-
const missing = !
|
|
12539
|
+
const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12540
|
+
const missing = !existsSync18(framePath) || statSync8(framePath).size <= 8;
|
|
12480
12541
|
if (missing && rangeStart === null) {
|
|
12481
12542
|
rangeStart = frameIndex;
|
|
12482
12543
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -12492,7 +12553,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
12492
12553
|
function buildMissingFrameRetryBatches(ranges, maxWorkers, workDir, attempt, rangeStart = 0, stride = 1) {
|
|
12493
12554
|
const workersPerBatch = Math.max(1, Math.floor(maxWorkers));
|
|
12494
12555
|
const batches = [];
|
|
12495
|
-
const outputDirFor = (batchIndex, workerId) =>
|
|
12556
|
+
const outputDirFor = (batchIndex, workerId) => join28(workDir, `retry-${attempt}-batch-${batchIndex}-worker-${workerId}`);
|
|
12496
12557
|
if (stride > 1) {
|
|
12497
12558
|
const spans = /* @__PURE__ */ new Map();
|
|
12498
12559
|
for (const range of ranges) {
|
|
@@ -12552,8 +12613,8 @@ function getNextRetryWorkerCount(currentWorkers) {
|
|
|
12552
12613
|
return Math.max(1, Math.floor(currentWorkers / 2));
|
|
12553
12614
|
}
|
|
12554
12615
|
function resolveRenderWorkDirPrefix(outputPath, jobId, platform2 = process.platform, systemTempDir = tmpdir2()) {
|
|
12555
|
-
if (platform2 === "win32" || platform2 === "darwin") return
|
|
12556
|
-
return
|
|
12616
|
+
if (platform2 === "win32" || platform2 === "darwin") return join28(systemTempDir, "hf-render-");
|
|
12617
|
+
return join28(dirname12(outputPath), `work-${jobId}-`);
|
|
12557
12618
|
}
|
|
12558
12619
|
var MAX_TRANSIENT_CAPTURE_RETRIES = 1;
|
|
12559
12620
|
function isTransientCaptureRetryEligible(failureKind, missing, retriesUsed) {
|
|
@@ -12600,8 +12661,8 @@ The composition is too large for the available memory. To reduce memory pressure
|
|
|
12600
12661
|
function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
12601
12662
|
let captured = 0;
|
|
12602
12663
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12603
|
-
const framePath =
|
|
12604
|
-
if (
|
|
12664
|
+
const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12665
|
+
if (existsSync18(framePath)) captured++;
|
|
12605
12666
|
}
|
|
12606
12667
|
return captured;
|
|
12607
12668
|
}
|
|
@@ -12626,7 +12687,7 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
12626
12687
|
reason: attempt === 0 ? "initial" : pendingTransientRetry ? "transient-retry" : "retry"
|
|
12627
12688
|
});
|
|
12628
12689
|
pendingTransientRetry = false;
|
|
12629
|
-
const attemptWorkDir =
|
|
12690
|
+
const attemptWorkDir = join28(options.workDir, `capture-attempt-${attempt}`);
|
|
12630
12691
|
const batches = missingRanges ? buildMissingFrameRetryBatches(
|
|
12631
12692
|
missingRanges,
|
|
12632
12693
|
currentWorkers,
|
|
@@ -13256,19 +13317,19 @@ function deVerifyFallbackTelemetry(err) {
|
|
|
13256
13317
|
};
|
|
13257
13318
|
}
|
|
13258
13319
|
function resolveRenderDebugDir() {
|
|
13259
|
-
const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve8(process.env.PRODUCER_RENDERS_DIR, "..") : resolve8(
|
|
13260
|
-
return
|
|
13320
|
+
const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve8(process.env.PRODUCER_RENDERS_DIR, "..") : resolve8(dirname12(fileURLToPath4(import.meta.url)), "../..");
|
|
13321
|
+
return join28(producerRoot, ".debug");
|
|
13261
13322
|
}
|
|
13262
13323
|
async function executeRenderJob(job, projectDir, outputPath, progressSink, abortSignal, assertRenderActive) {
|
|
13263
13324
|
validateHlsRenderConfig(job.config);
|
|
13264
13325
|
validateDeliveryEncoderConfig(job.config);
|
|
13265
13326
|
const debugDir = resolveRenderDebugDir();
|
|
13266
|
-
const outputDir =
|
|
13267
|
-
if (!
|
|
13268
|
-
const workDir = job.config.debug ?
|
|
13327
|
+
const outputDir = dirname12(outputPath);
|
|
13328
|
+
if (!existsSync18(outputDir)) mkdirSync10(outputDir, { recursive: true });
|
|
13329
|
+
const workDir = job.config.debug ? join28(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
|
|
13269
13330
|
const pipelineStart = Date.now();
|
|
13270
13331
|
const baseLog = job.config.logger ?? defaultLogger;
|
|
13271
|
-
const logPath = job.config.debug ?
|
|
13332
|
+
const logPath = job.config.debug ? join28(workDir, "render.log") : null;
|
|
13272
13333
|
const execution = new RenderExecutionContext({
|
|
13273
13334
|
request: { renderJobId: job.id, projectDir, outputPath },
|
|
13274
13335
|
logger: logPath ? createRenderFileLogger(logPath, baseLog) : baseLog,
|
|
@@ -13329,7 +13390,7 @@ async function executeRenderPipeline(input) {
|
|
|
13329
13390
|
imageDecodeFailures: 0
|
|
13330
13391
|
};
|
|
13331
13392
|
let hdrPerf;
|
|
13332
|
-
const perfOutputPath =
|
|
13393
|
+
const perfOutputPath = join28(workDir, "perf-summary.json");
|
|
13333
13394
|
const cfg = { ...job.config.producerConfig ?? resolveConfig2() };
|
|
13334
13395
|
const observability = new RenderObservabilityRecorder({
|
|
13335
13396
|
pipelineStartMs: pipelineStart,
|
|
@@ -13405,7 +13466,7 @@ async function executeRenderPipeline(input) {
|
|
|
13405
13466
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
13406
13467
|
assertNotAborted();
|
|
13407
13468
|
assertConfiguredFfmpegBinariesExist();
|
|
13408
|
-
if (!
|
|
13469
|
+
if (!existsSync18(workDir)) mkdirSync10(workDir, { recursive: true });
|
|
13409
13470
|
if (job.config.debug) {
|
|
13410
13471
|
log.info("[Render] Debug artifacts enabled", { workDir, logPath });
|
|
13411
13472
|
}
|
|
@@ -13434,16 +13495,16 @@ async function executeRenderPipeline(input) {
|
|
|
13434
13495
|
requestedWorkers: job.config.workers ?? "auto"
|
|
13435
13496
|
});
|
|
13436
13497
|
const entryFile = job.config.entryFile || "index.html";
|
|
13437
|
-
let htmlPath =
|
|
13438
|
-
if (!
|
|
13498
|
+
let htmlPath = join28(projectDir, entryFile);
|
|
13499
|
+
if (!existsSync18(htmlPath)) {
|
|
13439
13500
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
13440
13501
|
}
|
|
13441
13502
|
assertNotAborted();
|
|
13442
13503
|
const rawEntry = readTextFile2(htmlPath);
|
|
13443
13504
|
if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
|
|
13444
|
-
const wrapperPath =
|
|
13445
|
-
const projectIndexPath =
|
|
13446
|
-
if (!
|
|
13505
|
+
const wrapperPath = join28(workDir, "standalone-entry.html");
|
|
13506
|
+
const projectIndexPath = join28(projectDir, "index.html");
|
|
13507
|
+
if (!existsSync18(projectIndexPath)) {
|
|
13447
13508
|
throw new Error(
|
|
13448
13509
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
13449
13510
|
);
|
|
@@ -13633,7 +13694,7 @@ async function executeRenderPipeline(input) {
|
|
|
13633
13694
|
canvas: { width, height },
|
|
13634
13695
|
page: probeSession.page,
|
|
13635
13696
|
projectDir,
|
|
13636
|
-
compiledDir:
|
|
13697
|
+
compiledDir: join28(workDir, "compiled")
|
|
13637
13698
|
});
|
|
13638
13699
|
if (videoLayerPlan.videoId === void 0) {
|
|
13639
13700
|
log.info(`[Render] Video layer passthrough off: ${videoLayerPlan.reason}`);
|
|
@@ -13651,7 +13712,7 @@ async function executeRenderPipeline(input) {
|
|
|
13651
13712
|
updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress, {
|
|
13652
13713
|
code: "extract_video"
|
|
13653
13714
|
});
|
|
13654
|
-
const compiledDir =
|
|
13715
|
+
const compiledDir = join28(workDir, "compiled");
|
|
13655
13716
|
const extractResult = await observeRenderStage(
|
|
13656
13717
|
observability,
|
|
13657
13718
|
"video_extract",
|
|
@@ -13763,7 +13824,7 @@ async function executeRenderPipeline(input) {
|
|
|
13763
13824
|
try {
|
|
13764
13825
|
fileServer = await createFileServer({
|
|
13765
13826
|
projectDir,
|
|
13766
|
-
compiledDir:
|
|
13827
|
+
compiledDir: join28(workDir, "compiled"),
|
|
13767
13828
|
port: 0,
|
|
13768
13829
|
preHeadScripts: [VIRTUAL_TIME_SHIM],
|
|
13769
13830
|
fps: job.config.fps
|
|
@@ -13781,8 +13842,8 @@ async function executeRenderPipeline(input) {
|
|
|
13781
13842
|
if (!activeFileServer) {
|
|
13782
13843
|
throw new Error("File server failed to initialize before frame capture");
|
|
13783
13844
|
}
|
|
13784
|
-
const framesDir =
|
|
13785
|
-
if (!
|
|
13845
|
+
const framesDir = join28(workDir, "captured-frames");
|
|
13846
|
+
if (!existsSync18(framesDir)) mkdirSync10(framesDir, { recursive: true });
|
|
13786
13847
|
updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
|
|
13787
13848
|
code: "start_browsers"
|
|
13788
13849
|
});
|
|
@@ -14279,7 +14340,7 @@ async function executeRenderPipeline(input) {
|
|
|
14279
14340
|
hls: ".mp4"
|
|
14280
14341
|
};
|
|
14281
14342
|
const videoExt = FORMAT_EXT[outputFormat] ?? ".mp4";
|
|
14282
|
-
const videoOnlyPath =
|
|
14343
|
+
const videoOnlyPath = join28(workDir, `video-only${videoExt}`);
|
|
14283
14344
|
const usePageSideCompositingForTransitions = (cfg.enablePageSideCompositing || isGif) && compiled.hasShaderTransitions && !hasHdrContent && outputSupportsPageSideShaderCompositing(outputFormat);
|
|
14284
14345
|
if (usePageSideCompositingForTransitions) {
|
|
14285
14346
|
activeFileServer.addPreHeadScript(HF_PAGE_SIDE_COMPOSITING_STUB);
|
|
@@ -14567,7 +14628,7 @@ async function executeRenderPipeline(input) {
|
|
|
14567
14628
|
pageMotionBlur: job.config.pageMotionBlur,
|
|
14568
14629
|
captureQuality: captureOptions.quality
|
|
14569
14630
|
});
|
|
14570
|
-
const segmentDir = segmentDirFor(
|
|
14631
|
+
const segmentDir = segmentDirFor(join28(projectDir, "renders"), segmentPlanHash);
|
|
14571
14632
|
let completedSegments = /* @__PURE__ */ new Set();
|
|
14572
14633
|
if (job.config.resumeSegments === true) {
|
|
14573
14634
|
const existing = readSegmentManifest(segmentDir);
|
|
@@ -15104,7 +15165,7 @@ async function executeRenderPipeline(input) {
|
|
|
15104
15165
|
})
|
|
15105
15166
|
);
|
|
15106
15167
|
const totalElapsed = Date.now() - pipelineStart;
|
|
15107
|
-
const tmpPeakBytes =
|
|
15168
|
+
const tmpPeakBytes = existsSync18(workDir) ? sampleDirectoryBytes(workDir) : 0;
|
|
15108
15169
|
recordTransientRetryObservability();
|
|
15109
15170
|
observability.checkpoint("pipeline", "artifact validated", { totalElapsedMs: totalElapsed });
|
|
15110
15171
|
const observabilitySummary = observability.summary({
|
|
@@ -15185,8 +15246,8 @@ async function executeRenderPipeline(input) {
|
|
|
15185
15246
|
}
|
|
15186
15247
|
}
|
|
15187
15248
|
if (job.config.debug) {
|
|
15188
|
-
if (!isPngSequence && !isHls &&
|
|
15189
|
-
const debugOutput =
|
|
15249
|
+
if (!isPngSequence && !isHls && existsSync18(stagedOutputPath)) {
|
|
15250
|
+
const debugOutput = join28(workDir, `output${videoExt}`);
|
|
15190
15251
|
copyFileSync5(stagedOutputPath, debugOutput);
|
|
15191
15252
|
}
|
|
15192
15253
|
}
|
|
@@ -15292,14 +15353,14 @@ async function executeRenderPipeline(input) {
|
|
|
15292
15353
|
|
|
15293
15354
|
// src/services/distributed/renderSpan.ts
|
|
15294
15355
|
import { mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync7, readFileSync as readFileSync13, rmSync as rmSync16 } from "node:fs";
|
|
15295
|
-
import { join as
|
|
15356
|
+
import { join as join41 } from "node:path";
|
|
15296
15357
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
15297
15358
|
|
|
15298
15359
|
// src/services/distributed/mp4Stitch.ts
|
|
15299
15360
|
import { createReadStream as createReadStream3, createWriteStream, mkdirSync as mkdirSync11, mkdtempSync as mkdtempSync4, rmSync as rmSync11 } from "node:fs";
|
|
15300
15361
|
import { open } from "node:fs/promises";
|
|
15301
|
-
import { renameWithRetry as
|
|
15302
|
-
import { dirname as
|
|
15362
|
+
import { renameWithRetry as renameWithRetry7 } from "@hyperframes/engine";
|
|
15363
|
+
import { dirname as dirname13, join as join29 } from "node:path";
|
|
15303
15364
|
import { finished, pipeline } from "node:stream/promises";
|
|
15304
15365
|
function boxAt(src, off, at, limit) {
|
|
15305
15366
|
let size = src.readUInt32BE(off);
|
|
@@ -15663,15 +15724,15 @@ async function writeStitchedMp4(chunkPaths, outputPath, audioPath) {
|
|
|
15663
15724
|
const sourcePaths = audioPath ? [audioPath, ...chunkPaths] : chunkPaths;
|
|
15664
15725
|
const layouts = await Promise.all(sourcePaths.map((path) => readMp4Layout(fileRange(path))));
|
|
15665
15726
|
const header = audioPath ? buildStitchedHeader(layouts.slice(1), layouts[0]) : buildStitchedHeader(layouts);
|
|
15666
|
-
mkdirSync11(
|
|
15727
|
+
mkdirSync11(dirname13(outputPath), { recursive: true });
|
|
15667
15728
|
const workDir = mkdtempSync4(`${outputPath}.stitch-`);
|
|
15668
|
-
const out = createWriteStream(
|
|
15729
|
+
const out = createWriteStream(join29(workDir, "out.mp4"));
|
|
15669
15730
|
try {
|
|
15670
15731
|
out.write(header);
|
|
15671
15732
|
for (const [i, path] of sourcePaths.entries()) await copyPayload(path, layouts[i], i, out);
|
|
15672
15733
|
out.end();
|
|
15673
15734
|
await finished(out);
|
|
15674
|
-
|
|
15735
|
+
renameWithRetry7(join29(workDir, "out.mp4"), outputPath);
|
|
15675
15736
|
} finally {
|
|
15676
15737
|
out.destroy();
|
|
15677
15738
|
rmSync11(workDir, { recursive: true, force: true });
|
|
@@ -15683,13 +15744,13 @@ import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframe
|
|
|
15683
15744
|
import {
|
|
15684
15745
|
copyFileSync as copyFileSync6,
|
|
15685
15746
|
cpSync as cpSync2,
|
|
15686
|
-
existsSync as
|
|
15747
|
+
existsSync as existsSync23,
|
|
15687
15748
|
linkSync,
|
|
15688
15749
|
mkdirSync as mkdirSync14,
|
|
15689
15750
|
rmSync as rmSync12,
|
|
15690
15751
|
writeFileSync as writeFileSync9
|
|
15691
15752
|
} from "node:fs";
|
|
15692
|
-
import { extname as extname6, isAbsolute as isAbsolute3, join as
|
|
15753
|
+
import { extname as extname6, isAbsolute as isAbsolute3, join as join36, relative as relative5, sep as sep2 } from "node:path";
|
|
15693
15754
|
import {
|
|
15694
15755
|
fpsToNumber as fpsToNumber9,
|
|
15695
15756
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
@@ -15700,12 +15761,12 @@ import {
|
|
|
15700
15761
|
normalizeVp9CpuUsed,
|
|
15701
15762
|
resolveConfig as resolveConfig3,
|
|
15702
15763
|
declareContentAddressedUrl,
|
|
15703
|
-
renameWithRetry as
|
|
15764
|
+
renameWithRetry as renameWithRetry8,
|
|
15704
15765
|
declareProbeFactsUrl
|
|
15705
15766
|
} from "@hyperframes/engine";
|
|
15706
15767
|
|
|
15707
15768
|
// src/services/distributed/planFrameCost.ts
|
|
15708
|
-
import { join as
|
|
15769
|
+
import { join as join32 } from "node:path";
|
|
15709
15770
|
import {
|
|
15710
15771
|
closeCaptureSession as closeCaptureSession9,
|
|
15711
15772
|
createCaptureSession as createCaptureSession9,
|
|
@@ -15714,8 +15775,8 @@ import {
|
|
|
15714
15775
|
} from "@hyperframes/engine";
|
|
15715
15776
|
|
|
15716
15777
|
// src/services/distributed/chunkFrames.ts
|
|
15717
|
-
import { existsSync as
|
|
15718
|
-
import { extname as extname4, join as
|
|
15778
|
+
import { existsSync as existsSync20, readdirSync as readdirSync6 } from "node:fs";
|
|
15779
|
+
import { extname as extname4, join as join31 } from "node:path";
|
|
15719
15780
|
import {
|
|
15720
15781
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
15721
15782
|
createFrameLookupTable as createFrameLookupTable2,
|
|
@@ -15725,8 +15786,8 @@ import {
|
|
|
15725
15786
|
|
|
15726
15787
|
// src/services/distributed/shared.ts
|
|
15727
15788
|
import { execFile as execFileCallback } from "node:child_process";
|
|
15728
|
-
import { dirname as
|
|
15729
|
-
import { existsSync as
|
|
15789
|
+
import { dirname as dirname14, join as join30 } from "node:path";
|
|
15790
|
+
import { existsSync as existsSync19, readFileSync as readFileSync8 } from "node:fs";
|
|
15730
15791
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
15731
15792
|
import { promisify as promisify2 } from "node:util";
|
|
15732
15793
|
import {
|
|
@@ -15741,8 +15802,8 @@ function isPlanAudioArtifactPath(path) {
|
|
|
15741
15802
|
}
|
|
15742
15803
|
function resolvePlanAudioPath(planDir) {
|
|
15743
15804
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
15744
|
-
const candidate =
|
|
15745
|
-
if (
|
|
15805
|
+
const candidate = join30(planDir, name);
|
|
15806
|
+
if (existsSync19(candidate)) return candidate;
|
|
15746
15807
|
}
|
|
15747
15808
|
return null;
|
|
15748
15809
|
}
|
|
@@ -15999,11 +16060,11 @@ function buildSyntheticRenderJob(input) {
|
|
|
15999
16060
|
var cachedProducerVersion = null;
|
|
16000
16061
|
function readProducerVersion() {
|
|
16001
16062
|
if (cachedProducerVersion !== null) return cachedProducerVersion;
|
|
16002
|
-
const startDir =
|
|
16063
|
+
const startDir = dirname14(fileURLToPath5(import.meta.url));
|
|
16003
16064
|
let current = startDir;
|
|
16004
16065
|
for (let i = 0; i < 10; i++) {
|
|
16005
|
-
const candidate =
|
|
16006
|
-
if (
|
|
16066
|
+
const candidate = join30(current, "package.json");
|
|
16067
|
+
if (existsSync19(candidate)) {
|
|
16007
16068
|
try {
|
|
16008
16069
|
const pkg = JSON.parse(readFileSync8(candidate, "utf-8"));
|
|
16009
16070
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
@@ -16013,7 +16074,7 @@ function readProducerVersion() {
|
|
|
16013
16074
|
} catch {
|
|
16014
16075
|
}
|
|
16015
16076
|
}
|
|
16016
|
-
const parent =
|
|
16077
|
+
const parent = dirname14(current);
|
|
16017
16078
|
if (parent === current) break;
|
|
16018
16079
|
current = parent;
|
|
16019
16080
|
}
|
|
@@ -16056,8 +16117,8 @@ function frameNumberFromFileName(name) {
|
|
|
16056
16117
|
function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v1") {
|
|
16057
16118
|
const result = [];
|
|
16058
16119
|
for (const v of videos) {
|
|
16059
|
-
const outputDir =
|
|
16060
|
-
if (!
|
|
16120
|
+
const outputDir = join31(planDir, "video-frames", v.videoId);
|
|
16121
|
+
if (!existsSync20(outputDir)) {
|
|
16061
16122
|
throw new Error(
|
|
16062
16123
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
16063
16124
|
);
|
|
@@ -16075,7 +16136,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
16075
16136
|
if (!frameName) continue;
|
|
16076
16137
|
const frameNumber = indexMode === "sparse-v2" ? frameNumberFromFileName(frameName) : null;
|
|
16077
16138
|
const frameIndex = frameNumber === null ? i : frameNumber - 1;
|
|
16078
|
-
framePaths.set(frameIndex,
|
|
16139
|
+
framePaths.set(frameIndex, join31(outputDir, frameName));
|
|
16079
16140
|
}
|
|
16080
16141
|
result.push({
|
|
16081
16142
|
videoId: v.videoId,
|
|
@@ -16165,15 +16226,15 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
16165
16226
|
const shown = shownDeferredFrameIndexes(input.planVideos, input.frames, input.fps);
|
|
16166
16227
|
return Promise.all(
|
|
16167
16228
|
deferred.map(async (video) => {
|
|
16168
|
-
const outputDir =
|
|
16229
|
+
const outputDir = join31(input.outputRoot, video.videoId);
|
|
16169
16230
|
const indexes = [...shown.get(video.videoId) ?? []].sort((a, b) => a - b);
|
|
16170
16231
|
if (indexes.length === 0) {
|
|
16171
16232
|
return { ...video, outputDir, framePaths: /* @__PURE__ */ new Map(), ownedByLookup: false };
|
|
16172
16233
|
}
|
|
16173
16234
|
const range = video.deferredRange;
|
|
16174
16235
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
16175
|
-
const sourcePath = remoteUrl ??
|
|
16176
|
-
if (remoteUrl === void 0 && !
|
|
16236
|
+
const sourcePath = remoteUrl ?? join31(input.planDir, ...range.sourcePath.split("/"));
|
|
16237
|
+
if (remoteUrl === void 0 && !existsSync20(sourcePath)) {
|
|
16177
16238
|
throw new RenderChunkValidationError(
|
|
16178
16239
|
MISSING_PLAN_ARTIFACT,
|
|
16179
16240
|
`[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
|
|
@@ -16221,12 +16282,12 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
16221
16282
|
planVideos,
|
|
16222
16283
|
frames: sampled,
|
|
16223
16284
|
fps: fpsRatio,
|
|
16224
|
-
outputRoot:
|
|
16285
|
+
outputRoot: join32(workDir, "frame-cost-video-frames"),
|
|
16225
16286
|
cfg: input.cfg
|
|
16226
16287
|
})
|
|
16227
16288
|
];
|
|
16228
16289
|
const lookup = extracted.length > 0 ? createFrameLookupTable3(planVideos.videos, extracted, resolveRenderFpsConfig(fpsRatio).value) : null;
|
|
16229
|
-
const compiledDir =
|
|
16290
|
+
const compiledDir = join32(planDir, "compiled");
|
|
16230
16291
|
const fileServer = await createFileServer({
|
|
16231
16292
|
projectDir: compiledDir,
|
|
16232
16293
|
compiledDir,
|
|
@@ -16238,7 +16299,7 @@ async function measurePlanSecondsPerFrame(input) {
|
|
|
16238
16299
|
try {
|
|
16239
16300
|
session = await createCaptureSession9(
|
|
16240
16301
|
fileServer.url,
|
|
16241
|
-
|
|
16302
|
+
join32(workDir, "frame-cost"),
|
|
16242
16303
|
buildChunkCaptureOptions({
|
|
16243
16304
|
dimensions: { ...input, fpsNum: fps, fpsDen: 1 },
|
|
16244
16305
|
encoder: input,
|
|
@@ -16335,8 +16396,8 @@ function buildWeightedChunkSlices(weights, chunkCount, minFrames, maxFrames) {
|
|
|
16335
16396
|
}
|
|
16336
16397
|
|
|
16337
16398
|
// src/services/render/stages/freezePlan.ts
|
|
16338
|
-
import { existsSync as
|
|
16339
|
-
import { join as
|
|
16399
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync9, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
16400
|
+
import { join as join33, relative as relative3, resolve as resolve9 } from "node:path";
|
|
16340
16401
|
|
|
16341
16402
|
// src/services/distributed/planProtocol.ts
|
|
16342
16403
|
var PLAN_SCHEMA_VERSION = 1;
|
|
@@ -16529,7 +16590,7 @@ function planFileSha256(absolutePath, digests) {
|
|
|
16529
16590
|
return sha256;
|
|
16530
16591
|
}
|
|
16531
16592
|
async function prehashPlanFiles(dir, digests) {
|
|
16532
|
-
if (!
|
|
16593
|
+
if (!existsSync21(dir)) return;
|
|
16533
16594
|
for (const { absolutePath } of listPlanFiles(dir)) {
|
|
16534
16595
|
if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
|
|
16535
16596
|
}
|
|
@@ -16554,7 +16615,7 @@ function listPlanFiles(planDir) {
|
|
|
16554
16615
|
function walk(dir) {
|
|
16555
16616
|
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
16556
16617
|
for (const entry of entries) {
|
|
16557
|
-
const full =
|
|
16618
|
+
const full = join33(dir, entry.name);
|
|
16558
16619
|
if (entry.isDirectory()) {
|
|
16559
16620
|
walk(full);
|
|
16560
16621
|
} else if (entry.isFile()) {
|
|
@@ -16595,19 +16656,19 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
|
|
|
16595
16656
|
function writePlanRemoteFiles(planDir, files) {
|
|
16596
16657
|
if (files.length === 0) return;
|
|
16597
16658
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
16598
|
-
writeFileSync7(
|
|
16659
|
+
writeFileSync7(join33(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
|
|
16599
16660
|
}
|
|
16600
16661
|
function readPlanRemoteFiles(planDir) {
|
|
16601
|
-
const path =
|
|
16602
|
-
return
|
|
16662
|
+
const path = join33(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
16663
|
+
return existsSync21(path) ? JSON.parse(readFileSync9(path, "utf-8")) : [];
|
|
16603
16664
|
}
|
|
16604
16665
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
16605
|
-
const planJsonPath =
|
|
16606
|
-
const encoderJsonPath =
|
|
16607
|
-
if (!
|
|
16666
|
+
const planJsonPath = join33(planDir, "plan.json");
|
|
16667
|
+
const encoderJsonPath = join33(planDir, "meta", "encoder.json");
|
|
16668
|
+
if (!existsSync21(planJsonPath)) {
|
|
16608
16669
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
16609
16670
|
}
|
|
16610
|
-
if (!
|
|
16671
|
+
if (!existsSync21(encoderJsonPath)) {
|
|
16611
16672
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
16612
16673
|
}
|
|
16613
16674
|
const planJson = JSON.parse(readFileSync9(planJsonPath, "utf-8"));
|
|
@@ -16637,21 +16698,21 @@ async function freezePlan(input) {
|
|
|
16637
16698
|
hasAudio,
|
|
16638
16699
|
digests
|
|
16639
16700
|
} = input;
|
|
16640
|
-
if (!
|
|
16701
|
+
if (!existsSync21(planDir)) {
|
|
16641
16702
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
16642
16703
|
}
|
|
16643
|
-
const metaDir =
|
|
16644
|
-
if (!
|
|
16704
|
+
const metaDir = join33(planDir, "meta");
|
|
16705
|
+
if (!existsSync21(metaDir)) mkdirSync12(metaDir, { recursive: true });
|
|
16645
16706
|
writeFileSync7(
|
|
16646
|
-
|
|
16707
|
+
join33(metaDir, "composition.json"),
|
|
16647
16708
|
`${JSON.stringify(composition, null, 2)}
|
|
16648
16709
|
`,
|
|
16649
16710
|
"utf-8"
|
|
16650
16711
|
);
|
|
16651
16712
|
const encoderForCanonical = stripUndefined(encoder);
|
|
16652
16713
|
const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
|
|
16653
|
-
writeFileSync7(
|
|
16654
|
-
writeFileSync7(
|
|
16714
|
+
writeFileSync7(join33(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
|
|
16715
|
+
writeFileSync7(join33(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
|
|
16655
16716
|
`, "utf-8");
|
|
16656
16717
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
16657
16718
|
const planHash = computePlanHash({
|
|
@@ -16675,7 +16736,7 @@ async function freezePlan(input) {
|
|
|
16675
16736
|
duration: durationSeconds,
|
|
16676
16737
|
hasAudio
|
|
16677
16738
|
};
|
|
16678
|
-
const planJsonPath =
|
|
16739
|
+
const planJsonPath = join33(planDir, "plan.json");
|
|
16679
16740
|
writeFileSync7(planJsonPath, `${JSON.stringify(planJson, null, 2)}
|
|
16680
16741
|
`, "utf-8");
|
|
16681
16742
|
return { planJsonPath, planHash };
|
|
@@ -16717,10 +16778,10 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
16717
16778
|
}
|
|
16718
16779
|
|
|
16719
16780
|
// src/services/distributed/chunkAudio.ts
|
|
16720
|
-
import { closeSync as closeSync6, existsSync as
|
|
16781
|
+
import { closeSync as closeSync6, existsSync as existsSync22, ftruncateSync, mkdirSync as mkdirSync13, openSync as openSync6, readFileSync as readFileSync10 } from "node:fs";
|
|
16721
16782
|
import { writeFileSync as writeFileSync8, writeSync } from "node:fs";
|
|
16722
|
-
import { join as
|
|
16723
|
-
import { appendRenderProvenanceArgs, describeFfmpegFailure as
|
|
16783
|
+
import { join as join34 } from "node:path";
|
|
16784
|
+
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
16724
16785
|
function audioCutForChunk(grid, chunks, index, fps) {
|
|
16725
16786
|
const firstPacketAt = (frame) => Math.min(
|
|
16726
16787
|
grid.packetCount,
|
|
@@ -16807,7 +16868,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
16807
16868
|
});
|
|
16808
16869
|
const bytes = readFileSync10(input.audioPath);
|
|
16809
16870
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
16810
|
-
mkdirSync13(
|
|
16871
|
+
mkdirSync13(join34(input.planDir, SLICE_DIR), { recursive: true });
|
|
16811
16872
|
input.chunks.forEach((_, index) => {
|
|
16812
16873
|
const cut = audioCutForChunk(grid, input.chunks, index, input.fps);
|
|
16813
16874
|
if (!cut) return;
|
|
@@ -16815,17 +16876,17 @@ async function writeChunkAudioSlices(input) {
|
|
|
16815
16876
|
const start = offsets[cut.firstPacket];
|
|
16816
16877
|
layout.partStarts[index] = start;
|
|
16817
16878
|
const end = offsets[last] + track.sizes[last];
|
|
16818
|
-
writeFileSync8(
|
|
16879
|
+
writeFileSync8(join34(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
|
|
16819
16880
|
});
|
|
16820
16881
|
const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
|
|
16821
|
-
writeFileSync8(
|
|
16822
|
-
writeFileSync8(
|
|
16882
|
+
writeFileSync8(join34(input.planDir, SLICE_FRAME), Buffer.concat(frame));
|
|
16883
|
+
writeFileSync8(join34(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
|
|
16823
16884
|
}
|
|
16824
16885
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
16825
|
-
const layoutPath =
|
|
16826
|
-
if (!
|
|
16886
|
+
const layoutPath = join34(planDir, SLICE_LAYOUT);
|
|
16887
|
+
if (!existsSync22(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
16827
16888
|
const layout = JSON.parse(readFileSync10(layoutPath, "utf-8"));
|
|
16828
|
-
const frame = readFileSync10(
|
|
16889
|
+
const frame = readFileSync10(join34(planDir, SLICE_FRAME));
|
|
16829
16890
|
const fd = openSync6(scratchPath, "w");
|
|
16830
16891
|
try {
|
|
16831
16892
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
@@ -16838,7 +16899,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
16838
16899
|
);
|
|
16839
16900
|
const start = layout.partStarts[chunkIndex];
|
|
16840
16901
|
if (start !== void 0) {
|
|
16841
|
-
const part = readFileSync10(
|
|
16902
|
+
const part = readFileSync10(join34(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
16842
16903
|
writeSync(fd, part, 0, part.length, start);
|
|
16843
16904
|
}
|
|
16844
16905
|
ftruncateSync(fd, layout.sizeBytes);
|
|
@@ -16866,7 +16927,7 @@ async function muxChunkWithAudio(input) {
|
|
|
16866
16927
|
const result = await runFfmpeg7(args, { signal: input.signal, timeout: input.timeoutMs });
|
|
16867
16928
|
if (!result.success) {
|
|
16868
16929
|
throw new Error(
|
|
16869
|
-
`[chunkAudio] chunk mux failed: ${
|
|
16930
|
+
`[chunkAudio] chunk mux failed: ${describeFfmpegFailure4(result, input.timeoutMs)}`
|
|
16870
16931
|
);
|
|
16871
16932
|
}
|
|
16872
16933
|
const slice = input.chunks[input.chunkIndex];
|
|
@@ -16894,7 +16955,7 @@ async function muxChunkWithAudio(input) {
|
|
|
16894
16955
|
// src/services/distributed/planSize.ts
|
|
16895
16956
|
import { createHash as createHash9 } from "node:crypto";
|
|
16896
16957
|
import { lstatSync as lstatSync2, readdirSync as readdirSync8 } from "node:fs";
|
|
16897
|
-
import { extname as extname5, join as
|
|
16958
|
+
import { extname as extname5, join as join35, relative as relative4 } from "node:path";
|
|
16898
16959
|
var MAX_TOP_COMPONENTS = 10;
|
|
16899
16960
|
var SOURCE_MEDIA_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
16900
16961
|
".aac",
|
|
@@ -17006,7 +17067,7 @@ function walkRegularFiles(dir, visit) {
|
|
|
17006
17067
|
return;
|
|
17007
17068
|
}
|
|
17008
17069
|
for (const entry of entries) {
|
|
17009
|
-
const full =
|
|
17070
|
+
const full = join35(dir, entry.name);
|
|
17010
17071
|
if (entry.isDirectory()) {
|
|
17011
17072
|
walkRegularFiles(full, visit);
|
|
17012
17073
|
} else if (entry.isFile()) {
|
|
@@ -17218,13 +17279,13 @@ function assertPositiveInteger2(name, value) {
|
|
|
17218
17279
|
}
|
|
17219
17280
|
var FREEZE_OWNED_PLAN_FILES = [
|
|
17220
17281
|
"plan.json",
|
|
17221
|
-
|
|
17222
|
-
|
|
17223
|
-
|
|
17282
|
+
join36("meta", "composition.json"),
|
|
17283
|
+
join36("meta", "encoder.json"),
|
|
17284
|
+
join36("meta", "chunks.json")
|
|
17224
17285
|
];
|
|
17225
17286
|
function removeFreezeOwnedPlanFiles(planDir) {
|
|
17226
17287
|
for (const relativePath of FREEZE_OWNED_PLAN_FILES) {
|
|
17227
|
-
rmSync12(
|
|
17288
|
+
rmSync12(join36(planDir, relativePath), { force: true });
|
|
17228
17289
|
}
|
|
17229
17290
|
}
|
|
17230
17291
|
function buildChunkSlices(totalFrames, chunkCount, effectiveChunkSize) {
|
|
@@ -17379,19 +17440,19 @@ function createVideoSourceShipper(input) {
|
|
|
17379
17440
|
const known = shipped.get(srcPath);
|
|
17380
17441
|
if (known) return known;
|
|
17381
17442
|
const stagedRel = pathInside(input.workVideoFramesDir, srcPath);
|
|
17382
|
-
const current = stagedRel === null ? srcPath :
|
|
17443
|
+
const current = stagedRel === null ? srcPath : join36(input.planDir, "video-frames", stagedRel);
|
|
17383
17444
|
let result = null;
|
|
17384
17445
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
17385
17446
|
const rel = pathInside(root, srcPath);
|
|
17386
|
-
if (rel !== null && (
|
|
17447
|
+
if (rel !== null && (existsSync23(join36(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
17387
17448
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
17388
17449
|
break;
|
|
17389
17450
|
}
|
|
17390
17451
|
}
|
|
17391
17452
|
if (result === null) {
|
|
17392
17453
|
result = `video-sources/${shipped.size}${extname6(current)}`;
|
|
17393
|
-
const destination =
|
|
17394
|
-
mkdirSync14(
|
|
17454
|
+
const destination = join36(input.planDir, ...result.split("/"));
|
|
17455
|
+
mkdirSync14(join36(input.planDir, "video-sources"), { recursive: true });
|
|
17395
17456
|
try {
|
|
17396
17457
|
linkSync(current, destination);
|
|
17397
17458
|
} catch {
|
|
@@ -17411,7 +17472,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
17411
17472
|
useGpu: false,
|
|
17412
17473
|
browserGpuMode: "software"
|
|
17413
17474
|
});
|
|
17414
|
-
if (!
|
|
17475
|
+
if (!existsSync23(planDir)) mkdirSync14(planDir, { recursive: true });
|
|
17415
17476
|
const log = config.logger ?? defaultLogger;
|
|
17416
17477
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
17417
17478
|
const abortSignal = config.abortSignal;
|
|
@@ -17440,14 +17501,14 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
17440
17501
|
variables: config.variables
|
|
17441
17502
|
});
|
|
17442
17503
|
const entryFile = config.entryFile ?? "index.html";
|
|
17443
|
-
const htmlPath =
|
|
17444
|
-
if (!
|
|
17504
|
+
const htmlPath = join36(projectDir, entryFile);
|
|
17505
|
+
if (!existsSync23(htmlPath)) {
|
|
17445
17506
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
17446
17507
|
}
|
|
17447
|
-
const workDir =
|
|
17508
|
+
const workDir = join36(planDir, ".plan-work");
|
|
17448
17509
|
rmSync12(workDir, { recursive: true, force: true });
|
|
17449
17510
|
mkdirSync14(workDir, { recursive: true });
|
|
17450
|
-
const compiledDir =
|
|
17511
|
+
const compiledDir = join36(workDir, "compiled");
|
|
17451
17512
|
mkdirSync14(compiledDir, { recursive: true });
|
|
17452
17513
|
let stageStarted = Date.now();
|
|
17453
17514
|
cpSync2(projectDir, compiledDir, {
|
|
@@ -17461,10 +17522,10 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
17461
17522
|
}
|
|
17462
17523
|
});
|
|
17463
17524
|
logPlanStage(log, "copy", stageStarted);
|
|
17464
|
-
const finalCompiledDir =
|
|
17525
|
+
const finalCompiledDir = join36(planDir, "compiled");
|
|
17465
17526
|
const needsAlpha = outputNeedsAlpha(config.format);
|
|
17466
17527
|
const remoteFiles = Object.entries(options.remoteFiles ?? {});
|
|
17467
|
-
const remoteLocalPath = (rel) =>
|
|
17528
|
+
const remoteLocalPath = (rel) => join36(projectDir, ...rel.split("/"));
|
|
17468
17529
|
for (const [, file] of remoteFiles) {
|
|
17469
17530
|
declareContentAddressedUrl(file.url, file.sha256);
|
|
17470
17531
|
if (file.probeFactsUrl) declareProbeFactsUrl(file.url, file.probeFactsUrl);
|
|
@@ -17650,18 +17711,18 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
17650
17711
|
let stageStarted = Date.now();
|
|
17651
17712
|
const extractResult = await extractPlanVideos(c, options.deferVideoExtraction);
|
|
17652
17713
|
logPlanStage(log, "extract", stageStarted);
|
|
17653
|
-
const stagedVideoFrames =
|
|
17654
|
-
const videoFramesDst =
|
|
17655
|
-
if (
|
|
17656
|
-
const videoSourcesDir =
|
|
17714
|
+
const stagedVideoFrames = join36(compiledDir, "__hyperframes_video_frames");
|
|
17715
|
+
const videoFramesDst = join36(planDir, "video-frames");
|
|
17716
|
+
if (existsSync23(videoFramesDst)) rmSync12(videoFramesDst, { recursive: true, force: true });
|
|
17717
|
+
const videoSourcesDir = join36(planDir, "video-sources");
|
|
17657
17718
|
rmSync12(videoSourcesDir, { recursive: true, force: true });
|
|
17658
|
-
if (
|
|
17659
|
-
|
|
17719
|
+
if (existsSync23(stagedVideoFrames)) {
|
|
17720
|
+
renameWithRetry8(stagedVideoFrames, videoFramesDst);
|
|
17660
17721
|
} else {
|
|
17661
17722
|
mkdirSync14(videoFramesDst, { recursive: true });
|
|
17662
17723
|
}
|
|
17663
|
-
if (
|
|
17664
|
-
|
|
17724
|
+
if (existsSync23(finalCompiledDir)) rmSync12(finalCompiledDir, { recursive: true, force: true });
|
|
17725
|
+
renameWithRetry8(compiledDir, finalCompiledDir);
|
|
17665
17726
|
const shipVideoSource = createVideoSourceShipper({
|
|
17666
17727
|
planDir,
|
|
17667
17728
|
projectDir,
|
|
@@ -17690,7 +17751,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
17690
17751
|
};
|
|
17691
17752
|
})
|
|
17692
17753
|
});
|
|
17693
|
-
mkdirSync14(
|
|
17754
|
+
mkdirSync14(join36(planDir, "meta"), { recursive: true });
|
|
17694
17755
|
writePlanRemoteFiles(
|
|
17695
17756
|
planDir,
|
|
17696
17757
|
remoteFiles.map(([rel, { sha256, sizeBytes }]) => ({
|
|
@@ -17700,7 +17761,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
17700
17761
|
}))
|
|
17701
17762
|
);
|
|
17702
17763
|
writeFileSync9(
|
|
17703
|
-
|
|
17764
|
+
join36(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
17704
17765
|
JSON.stringify(planVideosJson, null, 2),
|
|
17705
17766
|
"utf-8"
|
|
17706
17767
|
);
|
|
@@ -17779,11 +17840,11 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
17779
17840
|
if (audioResult?.audioError) {
|
|
17780
17841
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
17781
17842
|
}
|
|
17782
|
-
const planAudioPath =
|
|
17783
|
-
if (audioResult?.hasAudio &&
|
|
17784
|
-
|
|
17843
|
+
const planAudioPath = join36(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
17844
|
+
if (audioResult?.hasAudio && existsSync23(audioResult.audioOutputPath)) {
|
|
17845
|
+
renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
|
|
17785
17846
|
}
|
|
17786
|
-
if (chunksCarryAudio &&
|
|
17847
|
+
if (chunksCarryAudio && existsSync23(planAudioPath)) {
|
|
17787
17848
|
if (options.sliceChunkAudio) {
|
|
17788
17849
|
stageStarted = Date.now();
|
|
17789
17850
|
await writeChunkAudioSlices({
|
|
@@ -17898,9 +17959,9 @@ async function plan(projectDir, config, planDir, options = {}) {
|
|
|
17898
17959
|
|
|
17899
17960
|
// src/services/distributed/renderChunk.ts
|
|
17900
17961
|
import { randomBytes } from "node:crypto";
|
|
17901
|
-
import { existsSync as
|
|
17962
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync12, readdirSync as readdirSync10, rmSync as rmSync15, writeFileSync as writeFileSync12 } from "node:fs";
|
|
17902
17963
|
import { cpus as cpus3 } from "node:os";
|
|
17903
|
-
import { join as
|
|
17964
|
+
import { join as join40 } from "node:path";
|
|
17904
17965
|
import {
|
|
17905
17966
|
assertSwiftShader,
|
|
17906
17967
|
BROWSER_GPU_NOT_SOFTWARE as BROWSER_GPU_NOT_SOFTWARE4,
|
|
@@ -17921,7 +17982,7 @@ import {
|
|
|
17921
17982
|
// src/services/distributed/planV2.ts
|
|
17922
17983
|
import {
|
|
17923
17984
|
copyFileSync as copyFileSync8,
|
|
17924
|
-
existsSync as
|
|
17985
|
+
existsSync as existsSync25,
|
|
17925
17986
|
lstatSync as lstatSync3,
|
|
17926
17987
|
mkdirSync as mkdirSync16,
|
|
17927
17988
|
mkdtempSync as mkdtempSync6,
|
|
@@ -17932,14 +17993,14 @@ import {
|
|
|
17932
17993
|
writeFileSync as writeFileSync11
|
|
17933
17994
|
} from "node:fs";
|
|
17934
17995
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
17935
|
-
import { dirname as
|
|
17996
|
+
import { dirname as dirname16, isAbsolute as isAbsolute4, join as join39, relative as relative6, resolve as resolve10, sep as sep3 } from "node:path";
|
|
17936
17997
|
import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
|
|
17937
|
-
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as
|
|
17998
|
+
import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
|
|
17938
17999
|
|
|
17939
18000
|
// src/services/distributed/planV2Publisher.ts
|
|
17940
18001
|
import {
|
|
17941
18002
|
copyFileSync as copyFileSync7,
|
|
17942
|
-
existsSync as
|
|
18003
|
+
existsSync as existsSync24,
|
|
17943
18004
|
linkSync as linkSync2,
|
|
17944
18005
|
mkdirSync as mkdirSync15,
|
|
17945
18006
|
mkdtempSync as mkdtempSync5,
|
|
@@ -17947,8 +18008,8 @@ import {
|
|
|
17947
18008
|
statSync as statSync9,
|
|
17948
18009
|
writeFileSync as writeFileSync10
|
|
17949
18010
|
} from "node:fs";
|
|
17950
|
-
import { renameWithRetry as
|
|
17951
|
-
import { dirname as
|
|
18011
|
+
import { renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
|
|
18012
|
+
import { dirname as dirname15, join as join38 } from "node:path";
|
|
17952
18013
|
|
|
17953
18014
|
// src/services/distributed/planV2Errors.ts
|
|
17954
18015
|
var PLAN_V2_INTEGRITY_UNRECOVERABLE = "PLAN_V2_INTEGRITY_UNRECOVERABLE";
|
|
@@ -17963,7 +18024,7 @@ var PlanV2IntegrityError = class extends Error {
|
|
|
17963
18024
|
};
|
|
17964
18025
|
|
|
17965
18026
|
// src/services/distributed/planV2Layout.ts
|
|
17966
|
-
import { join as
|
|
18027
|
+
import { join as join37 } from "node:path";
|
|
17967
18028
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
17968
18029
|
function assertPlanV2Sha256(value, field) {
|
|
17969
18030
|
if (typeof value !== "string" || !SHA256_PATTERN.test(value)) {
|
|
@@ -17973,7 +18034,7 @@ function assertPlanV2Sha256(value, field) {
|
|
|
17973
18034
|
}
|
|
17974
18035
|
function planV2BlobPath(root, sha256) {
|
|
17975
18036
|
const digest = assertPlanV2Sha256(sha256, "artifact sha256");
|
|
17976
|
-
return
|
|
18037
|
+
return join37(root, "artifacts", "sha256", digest.slice(0, 2), digest);
|
|
17977
18038
|
}
|
|
17978
18039
|
|
|
17979
18040
|
// src/services/distributed/planV2Publisher.ts
|
|
@@ -18008,13 +18069,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18008
18069
|
#linkFile;
|
|
18009
18070
|
#committed = false;
|
|
18010
18071
|
constructor(destinationDir, options = {}) {
|
|
18011
|
-
if (
|
|
18072
|
+
if (existsSync24(destinationDir)) {
|
|
18012
18073
|
throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
|
|
18013
18074
|
}
|
|
18014
18075
|
this.destinationDir = destinationDir;
|
|
18015
18076
|
this.#linkFile = options.linkFile ?? linkSync2;
|
|
18016
|
-
mkdirSync15(
|
|
18017
|
-
this.temporaryDir = mkdtempSync5(
|
|
18077
|
+
mkdirSync15(dirname15(destinationDir), { recursive: true });
|
|
18078
|
+
this.temporaryDir = mkdtempSync5(join38(dirname15(destinationDir), ".plan-v2-publish-"));
|
|
18018
18079
|
}
|
|
18019
18080
|
async putBlob(blob) {
|
|
18020
18081
|
const digest = assertPlanV2Sha256(blob.sha256, "published blob sha256");
|
|
@@ -18025,10 +18086,10 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18025
18086
|
);
|
|
18026
18087
|
}
|
|
18027
18088
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
18028
|
-
if (
|
|
18029
|
-
mkdirSync15(
|
|
18030
|
-
const stagingDir = mkdtempSync5(
|
|
18031
|
-
const temporaryPath =
|
|
18089
|
+
if (existsSync24(destinationPath)) return;
|
|
18090
|
+
mkdirSync15(dirname15(destinationPath), { recursive: true });
|
|
18091
|
+
const stagingDir = mkdtempSync5(join38(dirname15(destinationPath), ".plan-v2-blob-"));
|
|
18092
|
+
const temporaryPath = join38(stagingDir, "blob");
|
|
18032
18093
|
try {
|
|
18033
18094
|
try {
|
|
18034
18095
|
this.#linkFile(blob.sourcePath, temporaryPath);
|
|
@@ -18036,21 +18097,21 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18036
18097
|
if (!canFallbackToCopy(error)) throw error;
|
|
18037
18098
|
copyFileSync7(blob.sourcePath, temporaryPath);
|
|
18038
18099
|
}
|
|
18039
|
-
|
|
18100
|
+
renameWithRetry9(temporaryPath, destinationPath);
|
|
18040
18101
|
} finally {
|
|
18041
18102
|
rmSync13(stagingDir, { recursive: true, force: true });
|
|
18042
18103
|
}
|
|
18043
18104
|
}
|
|
18044
18105
|
async commitManifest(manifestBytes) {
|
|
18045
18106
|
for (const digest of new Set(manifestDigests(manifestBytes))) {
|
|
18046
|
-
if (!
|
|
18107
|
+
if (!existsSync24(planV2BlobPath(this.temporaryDir, digest))) {
|
|
18047
18108
|
throw new PlanV2IntegrityError(
|
|
18048
18109
|
`cannot commit manifest before referenced blob is durable: ${digest}`
|
|
18049
18110
|
);
|
|
18050
18111
|
}
|
|
18051
18112
|
}
|
|
18052
|
-
writeFileSync10(
|
|
18053
|
-
|
|
18113
|
+
writeFileSync10(join38(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
18114
|
+
renameWithRetry9(this.temporaryDir, this.destinationDir);
|
|
18054
18115
|
this.#committed = true;
|
|
18055
18116
|
}
|
|
18056
18117
|
async abort() {
|
|
@@ -18094,7 +18155,7 @@ function listFiles(root) {
|
|
|
18094
18155
|
const rootResolved = resolve10(root);
|
|
18095
18156
|
function walk(dir) {
|
|
18096
18157
|
for (const entry of readdirSync9(dir, { withFileTypes: true })) {
|
|
18097
|
-
const absolutePath =
|
|
18158
|
+
const absolutePath = join39(dir, entry.name);
|
|
18098
18159
|
if (entry.isDirectory()) {
|
|
18099
18160
|
walk(absolutePath);
|
|
18100
18161
|
} else if (entry.isFile()) {
|
|
@@ -18121,13 +18182,13 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
18121
18182
|
}
|
|
18122
18183
|
}
|
|
18123
18184
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
18124
|
-
const videoRoot =
|
|
18125
|
-
if (!
|
|
18185
|
+
const videoRoot = join39(executionPlanDir, "video-frames");
|
|
18186
|
+
if (!existsSync25(videoRoot)) return;
|
|
18126
18187
|
for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
|
|
18127
18188
|
if (!videoEntry.isDirectory()) continue;
|
|
18128
|
-
const videoDir =
|
|
18189
|
+
const videoDir = join39(videoRoot, videoEntry.name);
|
|
18129
18190
|
if (readdirSync9(videoDir).includes(EXTRACTION_CACHE_COMPLETE_SENTINEL)) {
|
|
18130
|
-
const sentinelPath =
|
|
18191
|
+
const sentinelPath = join39(videoDir, EXTRACTION_CACHE_COMPLETE_SENTINEL);
|
|
18131
18192
|
assertValidExtractionCacheCompleteSentinel(sentinelPath);
|
|
18132
18193
|
}
|
|
18133
18194
|
}
|
|
@@ -18174,7 +18235,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18174
18235
|
if (video.deferredRange) {
|
|
18175
18236
|
const framePaths2 = /* @__PURE__ */ new Map();
|
|
18176
18237
|
for (let index = 0; index < video.totalFrames; index++) {
|
|
18177
|
-
framePaths2.set(index,
|
|
18238
|
+
framePaths2.set(index, join39(outputDir, String(index)));
|
|
18178
18239
|
}
|
|
18179
18240
|
return { ...video, outputDir, framePaths: framePaths2, ownedByLookup: false };
|
|
18180
18241
|
}
|
|
@@ -18182,7 +18243,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18182
18243
|
const framePaths = /* @__PURE__ */ new Map();
|
|
18183
18244
|
for (const frameName of frameNames) {
|
|
18184
18245
|
if (frameName === EXTRACTION_CACHE_COMPLETE_SENTINEL) {
|
|
18185
|
-
assertValidExtractionCacheCompleteSentinel(
|
|
18246
|
+
assertValidExtractionCacheCompleteSentinel(join39(outputDir, frameName));
|
|
18186
18247
|
continue;
|
|
18187
18248
|
}
|
|
18188
18249
|
const match = /(\d+)(?=\.[^.]+$)/.exec(frameName);
|
|
@@ -18201,7 +18262,7 @@ function listVideoFramePaths(executionPlanDir, videos) {
|
|
|
18201
18262
|
`duplicate extracted frame index ${frameIndex} in ${outputDir}`
|
|
18202
18263
|
);
|
|
18203
18264
|
}
|
|
18204
|
-
framePaths.set(frameIndex,
|
|
18265
|
+
framePaths.set(frameIndex, join39(outputDir, frameName));
|
|
18205
18266
|
}
|
|
18206
18267
|
return {
|
|
18207
18268
|
...video,
|
|
@@ -18222,8 +18283,8 @@ function parsePlanVideosJson2(value) {
|
|
|
18222
18283
|
}
|
|
18223
18284
|
}
|
|
18224
18285
|
function materializeExtractedVideoDirectories(planDir) {
|
|
18225
|
-
const videosPath =
|
|
18226
|
-
if (!
|
|
18286
|
+
const videosPath = join39(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18287
|
+
if (!existsSync25(videosPath)) return;
|
|
18227
18288
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
18228
18289
|
for (const video of videos.extracted) {
|
|
18229
18290
|
mkdirSync16(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
@@ -18256,13 +18317,13 @@ function parseChunkSlices(value) {
|
|
|
18256
18317
|
});
|
|
18257
18318
|
}
|
|
18258
18319
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
18259
|
-
const videoRoot =
|
|
18260
|
-
const hasExtractedFrames =
|
|
18261
|
-
const videosPath =
|
|
18262
|
-
if (!
|
|
18320
|
+
const videoRoot = join39(executionPlanDir, "video-frames");
|
|
18321
|
+
const hasExtractedFrames = existsSync25(videoRoot) && listFiles(videoRoot).length > 0;
|
|
18322
|
+
const videosPath = join39(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18323
|
+
if (!existsSync25(videosPath)) {
|
|
18263
18324
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
18264
18325
|
}
|
|
18265
|
-
const chunksPath =
|
|
18326
|
+
const chunksPath = join39(executionPlanDir, "meta", "chunks.json");
|
|
18266
18327
|
const videos = readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18267
18328
|
const chunks = readJsonFile(chunksPath, "meta/chunks.json");
|
|
18268
18329
|
const parsedVideos = parsePlanVideosJson2(videos);
|
|
@@ -18316,13 +18377,13 @@ function computeManifestHash(manifest) {
|
|
|
18316
18377
|
return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
|
|
18317
18378
|
}
|
|
18318
18379
|
function writeBlob(sourcePath, destinationPath) {
|
|
18319
|
-
if (
|
|
18320
|
-
mkdirSync16(
|
|
18321
|
-
const temporaryDir = mkdtempSync6(
|
|
18322
|
-
const temporaryPath =
|
|
18380
|
+
if (existsSync25(destinationPath)) return;
|
|
18381
|
+
mkdirSync16(dirname16(destinationPath), { recursive: true });
|
|
18382
|
+
const temporaryDir = mkdtempSync6(join39(dirname16(destinationPath), ".plan-v2-blob-"));
|
|
18383
|
+
const temporaryPath = join39(temporaryDir, "blob");
|
|
18323
18384
|
try {
|
|
18324
18385
|
copyFileSync8(sourcePath, temporaryPath);
|
|
18325
|
-
|
|
18386
|
+
renameWithRetry10(temporaryPath, destinationPath);
|
|
18326
18387
|
} finally {
|
|
18327
18388
|
rmSync14(temporaryDir, { recursive: true, force: true });
|
|
18328
18389
|
}
|
|
@@ -18355,8 +18416,8 @@ async function putTasks(tasks) {
|
|
|
18355
18416
|
if (failure) throw failure.reason;
|
|
18356
18417
|
}
|
|
18357
18418
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
18358
|
-
const executionPlanPath =
|
|
18359
|
-
if (!
|
|
18419
|
+
const executionPlanPath = join39(executionPlanDir, "plan.json");
|
|
18420
|
+
if (!existsSync25(executionPlanPath)) {
|
|
18360
18421
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
18361
18422
|
}
|
|
18362
18423
|
const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
|
|
@@ -18383,7 +18444,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
18383
18444
|
}
|
|
18384
18445
|
validateExtractionCacheCompleteSentinels(executionPlanDir);
|
|
18385
18446
|
const videoDependencyPlan = buildVideoChunkDependencies(executionPlanDir, dimensions);
|
|
18386
|
-
const encoder = readJsonFile(
|
|
18447
|
+
const encoder = readJsonFile(join39(executionPlanDir, "meta", "encoder.json"), "meta/encoder.json");
|
|
18387
18448
|
const files = listFiles(executionPlanDir);
|
|
18388
18449
|
const chunkAudio = !isRecord3(encoder) || encoder.chunksCarryAudio !== true ? "none" : files.some((file) => chunkAudioSliceTarget(file.path) !== null) ? "sliced" : "whole";
|
|
18389
18450
|
for (const file of files) {
|
|
@@ -18436,25 +18497,25 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
18436
18497
|
};
|
|
18437
18498
|
}
|
|
18438
18499
|
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
18439
|
-
if (
|
|
18500
|
+
if (existsSync25(planV2Dir)) {
|
|
18440
18501
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
18441
18502
|
}
|
|
18442
18503
|
const publication = buildPlanV2Publication(executionPlanDir);
|
|
18443
18504
|
if (publication.storedBlobs.length > 0) {
|
|
18444
18505
|
throw new PlanV2IntegrityError("a local plan v2 directory cannot reference remote files");
|
|
18445
18506
|
}
|
|
18446
|
-
mkdirSync16(
|
|
18447
|
-
const tempDir = mkdtempSync6(
|
|
18507
|
+
mkdirSync16(dirname16(planV2Dir), { recursive: true });
|
|
18508
|
+
const tempDir = mkdtempSync6(join39(dirname16(planV2Dir), ".plan-v2-build-"));
|
|
18448
18509
|
try {
|
|
18449
18510
|
for (const blob of publication.blobs) {
|
|
18450
18511
|
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
18451
18512
|
}
|
|
18452
18513
|
writeFileSync11(
|
|
18453
|
-
|
|
18514
|
+
join39(tempDir, "plan.json"),
|
|
18454
18515
|
canonicalJsonStringify(publication.manifest),
|
|
18455
18516
|
"utf-8"
|
|
18456
18517
|
);
|
|
18457
|
-
|
|
18518
|
+
renameWithRetry10(tempDir, planV2Dir);
|
|
18458
18519
|
return resultFromManifest(planV2Dir, publication.manifest);
|
|
18459
18520
|
} catch (error) {
|
|
18460
18521
|
rmSync14(tempDir, { recursive: true, force: true });
|
|
@@ -18497,7 +18558,7 @@ async function publishPlanV2FromV1(executionPlanDir, publisher) {
|
|
|
18497
18558
|
return publishPlanV2FromExecutionPlan(executionPlanDir, publisher);
|
|
18498
18559
|
}
|
|
18499
18560
|
async function planV2WithPublisher(projectDir, config, publisher, options = {}) {
|
|
18500
|
-
const stagingRoot = mkdtempSync6(
|
|
18561
|
+
const stagingRoot = mkdtempSync6(join39(options.stagingParentDir ?? tmpdir3(), ".execution-plan-"));
|
|
18501
18562
|
const digests = /* @__PURE__ */ new Map();
|
|
18502
18563
|
try {
|
|
18503
18564
|
await buildLocalExecutionPlan(projectDir, config, stagingRoot, {
|
|
@@ -18508,7 +18569,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
18508
18569
|
// artifactTargets ships every compiled/ file to all chunks, so each is in the manifest.
|
|
18509
18570
|
whileAudioMixes: () => putBlobs(
|
|
18510
18571
|
publisher,
|
|
18511
|
-
listFiles(
|
|
18572
|
+
listFiles(join39(stagingRoot, "compiled")).map(
|
|
18512
18573
|
(file) => publishBlobFor(file.absolutePath, digests)
|
|
18513
18574
|
)
|
|
18514
18575
|
),
|
|
@@ -18528,19 +18589,19 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
18528
18589
|
}
|
|
18529
18590
|
}
|
|
18530
18591
|
async function planV2(projectDir, config, planV2Dir) {
|
|
18531
|
-
if (
|
|
18592
|
+
if (existsSync25(planV2Dir)) {
|
|
18532
18593
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
18533
18594
|
}
|
|
18534
18595
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
18535
18596
|
const manifest = await planV2WithPublisher(projectDir, config, publisher, {
|
|
18536
|
-
stagingParentDir:
|
|
18597
|
+
stagingParentDir: dirname16(planV2Dir)
|
|
18537
18598
|
});
|
|
18538
18599
|
return resultFromManifest(planV2Dir, manifest);
|
|
18539
18600
|
}
|
|
18540
18601
|
function resultFromManifest(planV2Dir, manifest) {
|
|
18541
18602
|
return {
|
|
18542
18603
|
planDir: planV2Dir,
|
|
18543
|
-
manifestPath:
|
|
18604
|
+
manifestPath: join39(planV2Dir, "plan.json"),
|
|
18544
18605
|
planProtocol: PLAN_PROTOCOL_V2,
|
|
18545
18606
|
planHash: manifest.planHash,
|
|
18546
18607
|
sourcePlanV1Hash: getPlanV2ExecutionPlanHash(manifest),
|
|
@@ -18676,8 +18737,8 @@ function parsePlanV2Manifest(value) {
|
|
|
18676
18737
|
return parsed;
|
|
18677
18738
|
}
|
|
18678
18739
|
function readPlanV2Manifest(planV2Dir) {
|
|
18679
|
-
const manifestPath =
|
|
18680
|
-
if (!
|
|
18740
|
+
const manifestPath = join39(planV2Dir, "plan.json");
|
|
18741
|
+
if (!existsSync25(manifestPath)) {
|
|
18681
18742
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
18682
18743
|
}
|
|
18683
18744
|
const value = readJsonFile(manifestPath, "v2 manifest");
|
|
@@ -18696,7 +18757,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
|
|
|
18696
18757
|
}
|
|
18697
18758
|
function verifyBlob(planV2Dir, artifact) {
|
|
18698
18759
|
const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
|
|
18699
|
-
if (!
|
|
18760
|
+
if (!existsSync25(sourcePath)) {
|
|
18700
18761
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
18701
18762
|
}
|
|
18702
18763
|
const stats = statSync10(sourcePath);
|
|
@@ -18709,7 +18770,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
18709
18770
|
return sourcePath;
|
|
18710
18771
|
}
|
|
18711
18772
|
function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
|
|
18712
|
-
if (
|
|
18773
|
+
if (existsSync25(destinationDir)) {
|
|
18713
18774
|
throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
|
|
18714
18775
|
}
|
|
18715
18776
|
const manifest = readPlanV2Manifest(planV2Dir);
|
|
@@ -18721,17 +18782,17 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
18721
18782
|
artifact,
|
|
18722
18783
|
sourcePath: verifyBlob(planV2Dir, artifact)
|
|
18723
18784
|
}));
|
|
18724
|
-
mkdirSync16(
|
|
18725
|
-
const tempDir = mkdtempSync6(
|
|
18785
|
+
mkdirSync16(dirname16(destinationDir), { recursive: true });
|
|
18786
|
+
const tempDir = mkdtempSync6(join39(dirname16(destinationDir), ".plan-v2-materialize-"));
|
|
18726
18787
|
try {
|
|
18727
18788
|
for (const { artifact, sourcePath } of verified) {
|
|
18728
|
-
const destinationPath =
|
|
18729
|
-
mkdirSync16(
|
|
18789
|
+
const destinationPath = join39(tempDir, ...artifact.path.split("/"));
|
|
18790
|
+
mkdirSync16(dirname16(destinationPath), { recursive: true });
|
|
18730
18791
|
copyFileSync8(sourcePath, destinationPath);
|
|
18731
18792
|
}
|
|
18732
18793
|
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
18733
18794
|
writeFileSync11(
|
|
18734
|
-
|
|
18795
|
+
join39(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
18735
18796
|
canonicalJsonStringify({
|
|
18736
18797
|
manifest,
|
|
18737
18798
|
target,
|
|
@@ -18739,7 +18800,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
18739
18800
|
}),
|
|
18740
18801
|
"utf-8"
|
|
18741
18802
|
);
|
|
18742
|
-
|
|
18803
|
+
renameWithRetry10(tempDir, destinationDir);
|
|
18743
18804
|
} catch (error) {
|
|
18744
18805
|
rmSync14(tempDir, { recursive: true, force: true });
|
|
18745
18806
|
throw error;
|
|
@@ -18754,7 +18815,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
18754
18815
|
// Join the artifact's OWN name, not the current constant: a plan written
|
|
18755
18816
|
// before the container change carries the legacy name, and materializing it
|
|
18756
18817
|
// under the new one would point at a file that was never written.
|
|
18757
|
-
audioPath: target.role === "assembler" ? audioArtifact ?
|
|
18818
|
+
audioPath: target.role === "assembler" ? audioArtifact ? join39(destinationDir, audioArtifact.path) : null : null
|
|
18758
18819
|
};
|
|
18759
18820
|
}
|
|
18760
18821
|
function assertRemoteSources(remoteSources, artifacts) {
|
|
@@ -18773,8 +18834,8 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
18773
18834
|
return readValidatedPlanV2Materialization(planDir, expectedTarget)?.manifest ?? null;
|
|
18774
18835
|
}
|
|
18775
18836
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
18776
|
-
const markerPath =
|
|
18777
|
-
if (!
|
|
18837
|
+
const markerPath = join39(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
18838
|
+
if (!existsSync25(markerPath)) return null;
|
|
18778
18839
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
18779
18840
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
18780
18841
|
throw new PlanV2IntegrityError("malformed materialization marker");
|
|
@@ -18791,8 +18852,8 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
18791
18852
|
assertRemoteSources(remoteSources, required);
|
|
18792
18853
|
for (const artifact of required) {
|
|
18793
18854
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
18794
|
-
const path =
|
|
18795
|
-
if (!
|
|
18855
|
+
const path = join39(planDir, ...artifact.path.split("/"));
|
|
18856
|
+
if (!existsSync25(path) || statSync10(path).size !== artifact.sizeBytes) {
|
|
18796
18857
|
throw new PlanV2IntegrityError(
|
|
18797
18858
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
18798
18859
|
);
|
|
@@ -18912,7 +18973,7 @@ function hashChunkOutput(outputPath, kind) {
|
|
|
18912
18973
|
if (kind === "file") return sha256Hex(readFileSync12(outputPath));
|
|
18913
18974
|
const entries = readdirSync10(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
18914
18975
|
const lines = entries.map(
|
|
18915
|
-
(name) => `${name}\0${sha256Hex(readFileSync12(
|
|
18976
|
+
(name) => `${name}\0${sha256Hex(readFileSync12(join40(outputPath, name)))}`
|
|
18916
18977
|
);
|
|
18917
18978
|
return sha256Hex(lines.join("\0"));
|
|
18918
18979
|
}
|
|
@@ -18929,10 +18990,10 @@ function resolveLockedVp9CpuUsed(lockedEncoder) {
|
|
|
18929
18990
|
async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
18930
18991
|
const start = Date.now();
|
|
18931
18992
|
const log = defaultLogger;
|
|
18932
|
-
const planJsonPath =
|
|
18933
|
-
const encoderJsonPath =
|
|
18934
|
-
const chunksJsonPath =
|
|
18935
|
-
if (!
|
|
18993
|
+
const planJsonPath = join40(planDir, "plan.json");
|
|
18994
|
+
const encoderJsonPath = join40(planDir, "meta", "encoder.json");
|
|
18995
|
+
const chunksJsonPath = join40(planDir, "meta", "chunks.json");
|
|
18996
|
+
if (!existsSync26(planJsonPath)) {
|
|
18936
18997
|
throw new RenderChunkValidationError(
|
|
18937
18998
|
MISSING_PLAN_ARTIFACT,
|
|
18938
18999
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
@@ -18948,7 +19009,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
18948
19009
|
const v2Manifest = v2Materialization?.manifest ?? null;
|
|
18949
19010
|
const remoteSources = v2Materialization?.remoteSources ?? {};
|
|
18950
19011
|
for (const required of [encoderJsonPath, chunksJsonPath]) {
|
|
18951
|
-
if (!
|
|
19012
|
+
if (!existsSync26(required)) {
|
|
18952
19013
|
throw new RenderChunkValidationError(
|
|
18953
19014
|
MISSING_PLAN_ARTIFACT,
|
|
18954
19015
|
`[renderChunk] planDir is missing required artifact: ${required}`
|
|
@@ -18957,9 +19018,9 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
18957
19018
|
}
|
|
18958
19019
|
const encoder = JSON.parse(readFileSync12(encoderJsonPath, "utf-8"));
|
|
18959
19020
|
const chunks = JSON.parse(readFileSync12(chunksJsonPath, "utf-8"));
|
|
18960
|
-
const videosJsonPath =
|
|
19021
|
+
const videosJsonPath = join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18961
19022
|
let planVideos = null;
|
|
18962
|
-
if (
|
|
19023
|
+
if (existsSync26(videosJsonPath)) {
|
|
18963
19024
|
try {
|
|
18964
19025
|
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync12(videosJsonPath, "utf-8")));
|
|
18965
19026
|
} catch (err) {
|
|
@@ -18989,8 +19050,8 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
18989
19050
|
}
|
|
18990
19051
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
18991
19052
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
18992
|
-
const compiledDir =
|
|
18993
|
-
if (!
|
|
19053
|
+
const compiledDir = join40(planDir, "compiled");
|
|
19054
|
+
if (!existsSync26(compiledDir)) {
|
|
18994
19055
|
throw new RenderChunkValidationError(
|
|
18995
19056
|
MISSING_PLAN_ARTIFACT,
|
|
18996
19057
|
`[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
|
|
@@ -19047,7 +19108,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19047
19108
|
};
|
|
19048
19109
|
const workDir = `${outputChunkPath}.work.${process.pid}.${randomBytes(4).toString("hex")}`;
|
|
19049
19110
|
mkdirSync17(workDir, { recursive: true });
|
|
19050
|
-
const framesDir =
|
|
19111
|
+
const framesDir = join40(workDir, "captured-frames");
|
|
19051
19112
|
mkdirSync17(framesDir, { recursive: true });
|
|
19052
19113
|
const videoFramesDir = `${workDir}.video-frames`;
|
|
19053
19114
|
const chunkFrameNumbers = Array.from({ length: framesInChunk }, (_, i) => range.startFrame + i);
|
|
@@ -19072,7 +19133,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19072
19133
|
dimensions: plan2.dimensions,
|
|
19073
19134
|
encoder,
|
|
19074
19135
|
videoCount: planVideos?.videos.length ?? 0,
|
|
19075
|
-
html: readFileSync12(
|
|
19136
|
+
html: readFileSync12(join40(compiledDir, "index.html"), "utf-8"),
|
|
19076
19137
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
19077
19138
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
19078
19139
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -19240,10 +19301,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19240
19301
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
19241
19302
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
19242
19303
|
if (isPngSequence) {
|
|
19243
|
-
if (!
|
|
19304
|
+
if (!existsSync26(outputChunkPath)) mkdirSync17(outputChunkPath, { recursive: true });
|
|
19244
19305
|
} else {
|
|
19245
|
-
const outDir =
|
|
19246
|
-
if (!
|
|
19306
|
+
const outDir = join40(outputChunkPath, "..");
|
|
19307
|
+
if (!existsSync26(outDir)) mkdirSync17(outDir, { recursive: true });
|
|
19247
19308
|
}
|
|
19248
19309
|
const encodeInput = {
|
|
19249
19310
|
job,
|
|
@@ -19516,10 +19577,10 @@ async function renderSpan(projectDir, config, span, outputPath) {
|
|
|
19516
19577
|
if (config.cfr === true) {
|
|
19517
19578
|
throw new RenderSpanUnsupportedError("--start/--end cannot be combined with a cfr re-encode.");
|
|
19518
19579
|
}
|
|
19519
|
-
const workDir = mkdtempSync7(
|
|
19580
|
+
const workDir = mkdtempSync7(join41(tmpdir4(), "hf-render-span-"));
|
|
19520
19581
|
try {
|
|
19521
|
-
const planDir =
|
|
19522
|
-
const partsDir =
|
|
19582
|
+
const planDir = join41(workDir, "plan");
|
|
19583
|
+
const partsDir = join41(workDir, "parts");
|
|
19523
19584
|
mkdirSync18(partsDir, { recursive: true });
|
|
19524
19585
|
const planned = await plan(
|
|
19525
19586
|
projectDir,
|
|
@@ -19534,7 +19595,7 @@ async function renderSpan(projectDir, config, span, outputPath) {
|
|
|
19534
19595
|
planned.totalFrames
|
|
19535
19596
|
);
|
|
19536
19597
|
const slices = JSON.parse(
|
|
19537
|
-
readFileSync13(
|
|
19598
|
+
readFileSync13(join41(planDir, "meta", "chunks.json"), "utf-8")
|
|
19538
19599
|
);
|
|
19539
19600
|
const parts = [];
|
|
19540
19601
|
for (const slice of slices) {
|
|
@@ -19544,7 +19605,7 @@ async function renderSpan(projectDir, config, span, outputPath) {
|
|
|
19544
19605
|
};
|
|
19545
19606
|
if (range.endFrame <= range.startFrame) continue;
|
|
19546
19607
|
throwIfAborted(config.abortSignal);
|
|
19547
|
-
const part =
|
|
19608
|
+
const part = join41(partsDir, `part-${String(parts.length).padStart(4, "0")}.mp4`);
|
|
19548
19609
|
await renderChunk(planDir, slice.index, part, { frameRange: range });
|
|
19549
19610
|
parts.push(part);
|
|
19550
19611
|
}
|
|
@@ -20137,7 +20198,7 @@ import {
|
|
|
20137
20198
|
|
|
20138
20199
|
// src/server.ts
|
|
20139
20200
|
import {
|
|
20140
|
-
existsSync as
|
|
20201
|
+
existsSync as existsSync29,
|
|
20141
20202
|
mkdirSync as mkdirSync19,
|
|
20142
20203
|
statSync as statSync12,
|
|
20143
20204
|
mkdtempSync as mkdtempSync8,
|
|
@@ -20145,7 +20206,7 @@ import {
|
|
|
20145
20206
|
rmSync as rmSync17,
|
|
20146
20207
|
createReadStream as createReadStream5
|
|
20147
20208
|
} from "node:fs";
|
|
20148
|
-
import { resolve as resolve12, dirname as
|
|
20209
|
+
import { resolve as resolve12, dirname as dirname18, join as join44 } from "node:path";
|
|
20149
20210
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
20150
20211
|
import { parseArgs } from "node:util";
|
|
20151
20212
|
import crypto from "node:crypto";
|
|
@@ -20156,7 +20217,7 @@ import { fetchPublicHttpsText as fetchPublicHttpsText2 } from "@hyperframes/engi
|
|
|
20156
20217
|
|
|
20157
20218
|
// src/services/hyperframeLint.ts
|
|
20158
20219
|
import {
|
|
20159
|
-
existsSync as
|
|
20220
|
+
existsSync as existsSync27,
|
|
20160
20221
|
readFileSync as readFileSync14,
|
|
20161
20222
|
statSync as statSync11,
|
|
20162
20223
|
openSync as openSync7,
|
|
@@ -20164,7 +20225,7 @@ import {
|
|
|
20164
20225
|
closeSync as closeSync7,
|
|
20165
20226
|
constants as constants3
|
|
20166
20227
|
} from "node:fs";
|
|
20167
|
-
import { resolve as resolve11, join as
|
|
20228
|
+
import { resolve as resolve11, join as join42, relative as relative7, isAbsolute as isAbsolute5, sep as sep4 } from "node:path";
|
|
20168
20229
|
import { lintHyperframeHtml } from "@hyperframes/lint";
|
|
20169
20230
|
function isStringRecord(value) {
|
|
20170
20231
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -20210,7 +20271,7 @@ function readEntryHtml(filePath) {
|
|
|
20210
20271
|
}
|
|
20211
20272
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
20212
20273
|
const absProjectDir = resolve11(projectDir);
|
|
20213
|
-
if (!
|
|
20274
|
+
if (!existsSync27(absProjectDir) || !statSync11(absProjectDir).isDirectory()) {
|
|
20214
20275
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
20215
20276
|
}
|
|
20216
20277
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -20232,7 +20293,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
20232
20293
|
}
|
|
20233
20294
|
}
|
|
20234
20295
|
return {
|
|
20235
|
-
error: `No HTML entry file found in project directory: ${
|
|
20296
|
+
error: `No HTML entry file found in project directory: ${join42(absProjectDir, preferredEntryFile || "index.html")}`
|
|
20236
20297
|
};
|
|
20237
20298
|
}
|
|
20238
20299
|
function prepareHyperframeLintBody(body) {
|
|
@@ -20278,8 +20339,8 @@ async function runHyperframeLint(prepared) {
|
|
|
20278
20339
|
// src/services/healthWorker.ts
|
|
20279
20340
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
20280
20341
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
20281
|
-
import { dirname as
|
|
20282
|
-
import { existsSync as
|
|
20342
|
+
import { dirname as dirname17, join as join43 } from "node:path";
|
|
20343
|
+
import { existsSync as existsSync28 } from "node:fs";
|
|
20283
20344
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
20284
20345
|
async function startHealthWorker(options = {}) {
|
|
20285
20346
|
const log = options.logger ?? defaultLogger2();
|
|
@@ -20353,10 +20414,10 @@ function defaultLogger2() {
|
|
|
20353
20414
|
};
|
|
20354
20415
|
}
|
|
20355
20416
|
function resolveWorkerEntry2() {
|
|
20356
|
-
const here =
|
|
20357
|
-
const candidates = [
|
|
20417
|
+
const here = dirname17(fileURLToPath6(import.meta.url));
|
|
20418
|
+
const candidates = [join43(here, "healthWorkerThread.js"), join43(here, "healthWorkerThread.ts")];
|
|
20358
20419
|
for (const candidate of candidates) {
|
|
20359
|
-
if (
|
|
20420
|
+
if (existsSync28(candidate)) return candidate;
|
|
20360
20421
|
}
|
|
20361
20422
|
return void 0;
|
|
20362
20423
|
}
|
|
@@ -20498,8 +20559,8 @@ function buildRenderJobConfig(input, outputPath, log) {
|
|
|
20498
20559
|
function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
20499
20560
|
const { input, cleanupProjectDir } = prepared;
|
|
20500
20561
|
const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
|
|
20501
|
-
const outputDir =
|
|
20502
|
-
if (!
|
|
20562
|
+
const outputDir = dirname18(absoluteOutputPath);
|
|
20563
|
+
if (!existsSync29(outputDir)) mkdirSync19(outputDir, { recursive: true });
|
|
20503
20564
|
return { input, cleanupProjectDir, absoluteOutputPath };
|
|
20504
20565
|
}
|
|
20505
20566
|
function validateRenderOverrides(body) {
|
|
@@ -20540,11 +20601,11 @@ function prepareProjectDirectory(projectDir, options) {
|
|
|
20540
20601
|
const candidate = nonEmptyString(projectDir);
|
|
20541
20602
|
if (!candidate) return null;
|
|
20542
20603
|
const absProjectDir = resolve12(candidate);
|
|
20543
|
-
if (!
|
|
20604
|
+
if (!existsSync29(absProjectDir) || !statSync12(absProjectDir).isDirectory()) {
|
|
20544
20605
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
20545
20606
|
}
|
|
20546
20607
|
const entry = options.entryFile || "index.html";
|
|
20547
|
-
if (!
|
|
20608
|
+
if (!existsSync29(resolve12(absProjectDir, entry))) {
|
|
20548
20609
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
20549
20610
|
}
|
|
20550
20611
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -20565,8 +20626,8 @@ async function resolveInlineRenderHtml(body) {
|
|
|
20565
20626
|
}
|
|
20566
20627
|
function materializeInlineProject(html, options) {
|
|
20567
20628
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir5();
|
|
20568
|
-
const tempProjectDir = mkdtempSync8(
|
|
20569
|
-
writeFileSync13(
|
|
20629
|
+
const tempProjectDir = mkdtempSync8(join44(tempRoot, "producer-project-"));
|
|
20630
|
+
writeFileSync13(join44(tempProjectDir, "index.html"), html, "utf-8");
|
|
20570
20631
|
return {
|
|
20571
20632
|
prepared: {
|
|
20572
20633
|
input: { projectDir: tempProjectDir, ...options },
|
|
@@ -20636,7 +20697,7 @@ function cleanupTempDir(dir, log) {
|
|
|
20636
20697
|
}
|
|
20637
20698
|
}
|
|
20638
20699
|
function outputFileSize(path) {
|
|
20639
|
-
return
|
|
20700
|
+
return existsSync29(path) ? statSync12(path).size : 0;
|
|
20640
20701
|
}
|
|
20641
20702
|
function createBlockingProgressReporter(log, requestId) {
|
|
20642
20703
|
let lastLoggedPct = -10;
|
|
@@ -20938,7 +20999,7 @@ function createRenderHandlers(options = {}) {
|
|
|
20938
20999
|
if (!artifact) {
|
|
20939
21000
|
return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
|
|
20940
21001
|
}
|
|
20941
|
-
if (!
|
|
21002
|
+
if (!existsSync29(artifact.path)) {
|
|
20942
21003
|
store.delete(token);
|
|
20943
21004
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
20944
21005
|
}
|
|
@@ -21035,12 +21096,12 @@ import { quantizeTimeToFrame, MEDIA_VISUAL_STYLE_PROPERTIES } from "@hyperframes
|
|
|
21035
21096
|
// src/services/distributed/planV2Execution.ts
|
|
21036
21097
|
import { mkdtempSync as mkdtempSync10, rmSync as rmSync19 } from "node:fs";
|
|
21037
21098
|
import { tmpdir as tmpdir6 } from "node:os";
|
|
21038
|
-
import { join as
|
|
21099
|
+
import { join as join46 } from "node:path";
|
|
21039
21100
|
|
|
21040
21101
|
// src/services/distributed/assemble.ts
|
|
21041
21102
|
import {
|
|
21042
21103
|
cpSync as cpSync3,
|
|
21043
|
-
existsSync as
|
|
21104
|
+
existsSync as existsSync30,
|
|
21044
21105
|
mkdirSync as mkdirSync20,
|
|
21045
21106
|
mkdtempSync as mkdtempSync9,
|
|
21046
21107
|
readFileSync as readFileSync15,
|
|
@@ -21049,18 +21110,18 @@ import {
|
|
|
21049
21110
|
statSync as statSync13,
|
|
21050
21111
|
writeFileSync as writeFileSync14
|
|
21051
21112
|
} from "node:fs";
|
|
21052
|
-
import { dirname as
|
|
21113
|
+
import { dirname as dirname19, join as join45 } from "node:path";
|
|
21053
21114
|
import {
|
|
21054
21115
|
appendRateCapArgs as appendRateCapArgs2,
|
|
21055
21116
|
appendRenderProvenanceArgs as appendRenderProvenanceArgs2,
|
|
21056
|
-
describeFfmpegFailure as
|
|
21117
|
+
describeFfmpegFailure as describeFfmpegFailure5,
|
|
21057
21118
|
getEncoderPreset as getEncoderPreset6,
|
|
21058
21119
|
MIXED_AUDIO_FILENAME as MIXED_AUDIO_FILENAME4,
|
|
21059
21120
|
muxVideoWithAudio as muxVideoWithAudio2,
|
|
21060
21121
|
resolveConfig as resolveConfig7,
|
|
21061
21122
|
runFfmpeg as runFfmpeg8,
|
|
21062
21123
|
appendSoftwareEncoderThreadsArg,
|
|
21063
|
-
renameWithRetry as
|
|
21124
|
+
renameWithRetry as renameWithRetry11
|
|
21064
21125
|
} from "@hyperframes/engine";
|
|
21065
21126
|
import { fpsToFfmpegArg as fpsToFfmpegArg3 } from "@hyperframes/core";
|
|
21066
21127
|
function cfrRateControlArgs(locked) {
|
|
@@ -21091,8 +21152,8 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21091
21152
|
}
|
|
21092
21153
|
const format = plan2.dimensions.format;
|
|
21093
21154
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
21094
|
-
if (!
|
|
21095
|
-
mkdirSync20(
|
|
21155
|
+
if (!existsSync30(dirname19(outputPath))) {
|
|
21156
|
+
mkdirSync20(dirname19(outputPath), { recursive: true });
|
|
21096
21157
|
}
|
|
21097
21158
|
const workDir = mkdtempSync9(`${outputPath}.assemble-work-`);
|
|
21098
21159
|
const fps = { num: plan2.dimensions.fpsNum, den: plan2.dimensions.fpsDen };
|
|
@@ -21114,10 +21175,10 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21114
21175
|
};
|
|
21115
21176
|
try {
|
|
21116
21177
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
21117
|
-
const finalPath =
|
|
21118
|
-
const audio = audioPath !== null &&
|
|
21178
|
+
const finalPath = join45(workDir, `output.${format}`);
|
|
21179
|
+
const audio = audioPath !== null && existsSync30(audioPath) ? audioPath : null;
|
|
21119
21180
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
21120
|
-
|
|
21181
|
+
renameWithRetry11(finalPath, outputPath);
|
|
21121
21182
|
log.info("[assemble] stage timings", {
|
|
21122
21183
|
...stageMs,
|
|
21123
21184
|
chunks: chunkPaths.length,
|
|
@@ -21133,7 +21194,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21133
21194
|
});
|
|
21134
21195
|
}
|
|
21135
21196
|
}
|
|
21136
|
-
const fileSize =
|
|
21197
|
+
const fileSize = existsSync30(outputPath) ? statSync13(outputPath).size : 0;
|
|
21137
21198
|
return {
|
|
21138
21199
|
outputPath,
|
|
21139
21200
|
durationMs: Date.now() - start,
|
|
@@ -21142,15 +21203,15 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21142
21203
|
};
|
|
21143
21204
|
}
|
|
21144
21205
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
21145
|
-
const planJsonPath =
|
|
21146
|
-
const chunksJsonPath =
|
|
21147
|
-
if (!
|
|
21206
|
+
const planJsonPath = join45(planDir, "plan.json");
|
|
21207
|
+
const chunksJsonPath = join45(planDir, "meta", "chunks.json");
|
|
21208
|
+
if (!existsSync30(planJsonPath)) {
|
|
21148
21209
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
21149
21210
|
}
|
|
21150
21211
|
const plan2 = JSON.parse(readFileSync15(planJsonPath, "utf-8"));
|
|
21151
21212
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
21152
21213
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
21153
|
-
if (!
|
|
21214
|
+
if (!existsSync30(chunksJsonPath)) {
|
|
21154
21215
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
21155
21216
|
}
|
|
21156
21217
|
const chunks = JSON.parse(readFileSync15(chunksJsonPath, "utf-8"));
|
|
@@ -21165,7 +21226,7 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
21165
21226
|
}
|
|
21166
21227
|
}
|
|
21167
21228
|
for (const path of chunkPaths.flat()) {
|
|
21168
|
-
if (!
|
|
21229
|
+
if (!existsSync30(path)) {
|
|
21169
21230
|
throw new Error(`[assemble] chunk path does not exist: ${path}`);
|
|
21170
21231
|
}
|
|
21171
21232
|
}
|
|
@@ -21194,7 +21255,7 @@ function cfrEncodeArgs(inputArgs, encoder, fpsArg) {
|
|
|
21194
21255
|
return args;
|
|
21195
21256
|
}
|
|
21196
21257
|
async function reencodeCfr(ctx, chunkPaths, encoder) {
|
|
21197
|
-
const cfrOutputPath =
|
|
21258
|
+
const cfrOutputPath = join45(ctx.workDir, `cfr.${ctx.format}`);
|
|
21198
21259
|
const cfrArgs = cfrEncodeArgs(videoInputArgs(ctx, chunkPaths), encoder, ctx.fpsArg);
|
|
21199
21260
|
appendRenderProvenanceArgs2(cfrArgs, cfrOutputPath);
|
|
21200
21261
|
cfrArgs.push("-y", cfrOutputPath);
|
|
@@ -21221,7 +21282,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
21221
21282
|
);
|
|
21222
21283
|
return;
|
|
21223
21284
|
}
|
|
21224
|
-
const paddedAudioPath =
|
|
21285
|
+
const paddedAudioPath = join45(ctx.workDir, "audio-padded.m4a");
|
|
21225
21286
|
const padTrimResult = await ctx.timed(
|
|
21226
21287
|
"audioMs",
|
|
21227
21288
|
() => padOrTrimAudioToVideoFrameCount({
|
|
@@ -21261,7 +21322,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
21261
21322
|
}
|
|
21262
21323
|
}
|
|
21263
21324
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
21264
|
-
const listPath =
|
|
21325
|
+
const listPath = join45(workDir, "concat-list.txt");
|
|
21265
21326
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
21266
21327
|
writeFileSync14(listPath, `${body}
|
|
21267
21328
|
`, "utf-8");
|
|
@@ -21281,7 +21342,7 @@ async function runAssembleFfmpeg(args, step, signal, timeout) {
|
|
|
21281
21342
|
const result = await runFfmpeg8(args, { signal, timeout });
|
|
21282
21343
|
if (!result.success) {
|
|
21283
21344
|
throw encoderFailureError(`[assemble] ffmpeg ${step} failed`, {
|
|
21284
|
-
error:
|
|
21345
|
+
error: describeFfmpegFailure5(result, timeout),
|
|
21285
21346
|
failureReason: result.failureReason
|
|
21286
21347
|
});
|
|
21287
21348
|
}
|
|
@@ -21299,12 +21360,12 @@ function stitchesSeparateAudio(planDir) {
|
|
|
21299
21360
|
return lockedAudio(planDir).separateAudio === true;
|
|
21300
21361
|
}
|
|
21301
21362
|
function planFormat(planDir) {
|
|
21302
|
-
const plan2 = JSON.parse(readFileSync15(
|
|
21363
|
+
const plan2 = JSON.parse(readFileSync15(join45(planDir, "plan.json"), "utf-8"));
|
|
21303
21364
|
return plan2.dimensions.format;
|
|
21304
21365
|
}
|
|
21305
21366
|
function lockedAudio(planDir) {
|
|
21306
|
-
const encoderJsonPath =
|
|
21307
|
-
if (!
|
|
21367
|
+
const encoderJsonPath = join45(planDir, "meta", "encoder.json");
|
|
21368
|
+
if (!existsSync30(encoderJsonPath)) return {};
|
|
21308
21369
|
return JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
|
|
21309
21370
|
}
|
|
21310
21371
|
function readCfrEncoder(planDir, format) {
|
|
@@ -21313,8 +21374,8 @@ function readCfrEncoder(planDir, format) {
|
|
|
21313
21374
|
`[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.`
|
|
21314
21375
|
);
|
|
21315
21376
|
}
|
|
21316
|
-
const encoderJsonPath =
|
|
21317
|
-
if (!
|
|
21377
|
+
const encoderJsonPath = join45(planDir, "meta", "encoder.json");
|
|
21378
|
+
if (!existsSync30(encoderJsonPath)) {
|
|
21318
21379
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
21319
21380
|
}
|
|
21320
21381
|
const encoderJson = JSON.parse(readFileSync15(encoderJsonPath, "utf-8"));
|
|
@@ -21326,7 +21387,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
21326
21387
|
return encoderJson;
|
|
21327
21388
|
}
|
|
21328
21389
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
21329
|
-
if (
|
|
21390
|
+
if (existsSync30(outputPath)) rmSync18(outputPath, { recursive: true, force: true });
|
|
21330
21391
|
mkdirSync20(outputPath, { recursive: true });
|
|
21331
21392
|
let globalIdx = 0;
|
|
21332
21393
|
for (const chunkDir of chunkPaths) {
|
|
@@ -21340,8 +21401,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
21340
21401
|
throw new Error(`[assemble] png-sequence chunk has no frames: ${chunkDir}`);
|
|
21341
21402
|
}
|
|
21342
21403
|
for (const frame of frames) {
|
|
21343
|
-
const dst =
|
|
21344
|
-
cpSync3(
|
|
21404
|
+
const dst = join45(outputPath, formatExportFrameName(globalIdx, "png"));
|
|
21405
|
+
cpSync3(join45(chunkDir, frame), dst);
|
|
21345
21406
|
globalIdx += 1;
|
|
21346
21407
|
}
|
|
21347
21408
|
}
|
|
@@ -21350,14 +21411,14 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
21350
21411
|
`[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
|
|
21351
21412
|
);
|
|
21352
21413
|
}
|
|
21353
|
-
if (audioPath !== null &&
|
|
21354
|
-
const sidecar =
|
|
21414
|
+
if (audioPath !== null && existsSync30(audioPath)) {
|
|
21415
|
+
const sidecar = join45(outputPath, MIXED_AUDIO_FILENAME4);
|
|
21355
21416
|
cpSync3(audioPath, sidecar);
|
|
21356
21417
|
}
|
|
21357
21418
|
let fileSize = 0;
|
|
21358
21419
|
for (const name of readdirSync11(outputPath)) {
|
|
21359
21420
|
try {
|
|
21360
|
-
fileSize += statSync13(
|
|
21421
|
+
fileSize += statSync13(join45(outputPath, name)).size;
|
|
21361
21422
|
} catch {
|
|
21362
21423
|
}
|
|
21363
21424
|
}
|
|
@@ -21371,8 +21432,8 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
21371
21432
|
|
|
21372
21433
|
// src/services/distributed/planV2Execution.ts
|
|
21373
21434
|
async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
21374
|
-
const workRoot = mkdtempSync10(
|
|
21375
|
-
const materializedPlanDir =
|
|
21435
|
+
const workRoot = mkdtempSync10(join46(tmpdir6(), "hf-plan-v2-chunk-"));
|
|
21436
|
+
const materializedPlanDir = join46(workRoot, "plan");
|
|
21376
21437
|
try {
|
|
21377
21438
|
materializePlanV2Target(planV2Dir, { role: "chunk", chunkIndex }, materializedPlanDir);
|
|
21378
21439
|
return await renderChunk(materializedPlanDir, chunkIndex, outputChunkPath, options);
|
|
@@ -21381,8 +21442,8 @@ async function renderChunkV2(planV2Dir, chunkIndex, outputChunkPath, options) {
|
|
|
21381
21442
|
}
|
|
21382
21443
|
}
|
|
21383
21444
|
async function assembleV2(planV2Dir, chunkPaths, outputPath, options) {
|
|
21384
|
-
const workRoot = mkdtempSync10(
|
|
21385
|
-
const materializedPlanDir =
|
|
21445
|
+
const workRoot = mkdtempSync10(join46(tmpdir6(), "hf-plan-v2-assembler-"));
|
|
21446
|
+
const materializedPlanDir = join46(workRoot, "plan");
|
|
21386
21447
|
try {
|
|
21387
21448
|
const materialized = materializePlanV2Target(
|
|
21388
21449
|
planV2Dir,
|