@hyperframes/producer 0.9.14 → 0.9.16
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 +205 -136
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +68 -68
- package/dist/index.js +233 -187
- package/dist/public-server.js +154 -107
- package/dist/services/render/audioPadTrim.d.ts +6 -0
- package/dist/services/render/stages/assembleStage.d.ts +1 -0
- package/dist/services/render/stages/audioStage.d.ts +4 -0
- package/dist/services/render/stages/compileStage.d.ts +12 -3
- package/dist/services/renderOrchestrator.d.ts +2 -5
- package/package.json +7 -7
package/dist/distributed.js
CHANGED
|
@@ -8,11 +8,11 @@ import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframe
|
|
|
8
8
|
import {
|
|
9
9
|
copyFileSync as copyFileSync6,
|
|
10
10
|
cpSync as cpSync2,
|
|
11
|
-
existsSync as
|
|
11
|
+
existsSync as existsSync18,
|
|
12
12
|
linkSync,
|
|
13
13
|
mkdirSync as mkdirSync10,
|
|
14
14
|
rmSync as rmSync7,
|
|
15
|
-
writeFileSync as
|
|
15
|
+
writeFileSync as writeFileSync8
|
|
16
16
|
} from "node:fs";
|
|
17
17
|
import { extname as extname4, isAbsolute as isAbsolute3, join as join24, relative as relative5, sep as sep2 } from "node:path";
|
|
18
18
|
import {
|
|
@@ -71,12 +71,12 @@ var defaultLogger = createConsoleLogger("info");
|
|
|
71
71
|
// src/services/renderOrchestrator.ts
|
|
72
72
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
73
73
|
import {
|
|
74
|
-
existsSync as
|
|
74
|
+
existsSync as existsSync13,
|
|
75
75
|
mkdirSync as mkdirSync6,
|
|
76
76
|
readdirSync as readdirSync3,
|
|
77
77
|
rmSync as rmSync5,
|
|
78
78
|
statSync as statSync5,
|
|
79
|
-
writeFileSync as
|
|
79
|
+
writeFileSync as writeFileSync5,
|
|
80
80
|
copyFileSync as copyFileSync5,
|
|
81
81
|
appendFileSync
|
|
82
82
|
} from "fs";
|
|
@@ -90,9 +90,7 @@ import {
|
|
|
90
90
|
toFps as toFps2
|
|
91
91
|
} from "@hyperframes/core";
|
|
92
92
|
import { HF_AUDIO_GROUP_TAG } from "@hyperframes/core/audio-groups";
|
|
93
|
-
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
94
93
|
import { HTML_BODY_CSS_HEIGHT_FIRST_RE, HTML_BODY_CSS_WIDTH_FIRST_RE } from "@hyperframes/parsers";
|
|
95
|
-
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
96
94
|
import {
|
|
97
95
|
extractMediaMetadata as extractMediaMetadata4,
|
|
98
96
|
resolveConfig as resolveConfig2,
|
|
@@ -123,7 +121,8 @@ import {
|
|
|
123
121
|
isDrawElementCaptureError,
|
|
124
122
|
getDrawElementVerificationDetails,
|
|
125
123
|
augmentProtocolTimeoutError,
|
|
126
|
-
augmentPageNavigationTimeoutError
|
|
124
|
+
augmentPageNavigationTimeoutError,
|
|
125
|
+
displaySize
|
|
127
126
|
} from "@hyperframes/engine";
|
|
128
127
|
import {
|
|
129
128
|
referenceBackgroundResult,
|
|
@@ -1914,8 +1913,11 @@ import {
|
|
|
1914
1913
|
} from "@hyperframes/engine";
|
|
1915
1914
|
|
|
1916
1915
|
// src/services/render/stages/compileStage.ts
|
|
1916
|
+
import { existsSync as existsSync7, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1917
1917
|
import { join as join7 } from "node:path";
|
|
1918
1918
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
1919
|
+
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
1920
|
+
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
1919
1921
|
|
|
1920
1922
|
// src/services/htmlCompiler.ts
|
|
1921
1923
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
@@ -4620,11 +4622,36 @@ function adaptAspectAgnosticResolution(requested, aspectAgnostic, width, height,
|
|
|
4620
4622
|
});
|
|
4621
4623
|
return flipped;
|
|
4622
4624
|
}
|
|
4625
|
+
function resolveEntryHtmlPath(input) {
|
|
4626
|
+
const { projectDir, workDir, entryFile, log } = input;
|
|
4627
|
+
const htmlPath = join7(projectDir, entryFile);
|
|
4628
|
+
const rawEntry = readTextFile2(htmlPath);
|
|
4629
|
+
if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
|
|
4630
|
+
const projectIndexPath = join7(projectDir, "index.html");
|
|
4631
|
+
if (!existsSync7(projectIndexPath)) {
|
|
4632
|
+
throw new Error(
|
|
4633
|
+
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
4634
|
+
);
|
|
4635
|
+
}
|
|
4636
|
+
const standaloneHtml = extractStandaloneEntryFromIndex(
|
|
4637
|
+
readTextFile2(projectIndexPath),
|
|
4638
|
+
entryFile,
|
|
4639
|
+
rawEntry
|
|
4640
|
+
);
|
|
4641
|
+
if (!standaloneHtml) {
|
|
4642
|
+
throw new Error(
|
|
4643
|
+
`Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
|
|
4644
|
+
);
|
|
4645
|
+
}
|
|
4646
|
+
const wrapperPath = join7(workDir, "standalone-entry.html");
|
|
4647
|
+
writeFileSync3(wrapperPath, standaloneHtml, "utf-8");
|
|
4648
|
+
log.info("Extracted standalone entry from index.html host context", { entryFile });
|
|
4649
|
+
return wrapperPath;
|
|
4650
|
+
}
|
|
4623
4651
|
async function runCompileStage(input) {
|
|
4624
4652
|
const {
|
|
4625
4653
|
projectDir,
|
|
4626
4654
|
workDir,
|
|
4627
|
-
htmlPath,
|
|
4628
4655
|
entryFile,
|
|
4629
4656
|
job,
|
|
4630
4657
|
cfg,
|
|
@@ -4635,6 +4662,7 @@ async function runCompileStage(input) {
|
|
|
4635
4662
|
allowSystemFontCapture,
|
|
4636
4663
|
abortSignal
|
|
4637
4664
|
} = input;
|
|
4665
|
+
const htmlPath = resolveEntryHtmlPath({ projectDir, workDir, entryFile, log });
|
|
4638
4666
|
const compileStart = Date.now();
|
|
4639
4667
|
const compiled = await compileForRender(projectDir, htmlPath, join7(workDir, "downloads"), {
|
|
4640
4668
|
log,
|
|
@@ -5466,7 +5494,7 @@ function validateDistributedDuration(input) {
|
|
|
5466
5494
|
}
|
|
5467
5495
|
|
|
5468
5496
|
// src/services/render/stages/extractVideosStage.ts
|
|
5469
|
-
import { existsSync as
|
|
5497
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
5470
5498
|
import { join as join9 } from "node:path";
|
|
5471
5499
|
import {
|
|
5472
5500
|
classifyVideoExtractionError,
|
|
@@ -5741,7 +5769,7 @@ async function runExtractVideosStage(input) {
|
|
|
5741
5769
|
const probeFailures = await Promise.all(
|
|
5742
5770
|
composition.videos.map(async (v) => {
|
|
5743
5771
|
const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
|
|
5744
|
-
if (!
|
|
5772
|
+
if (!existsSync8(videoPath)) return null;
|
|
5745
5773
|
try {
|
|
5746
5774
|
const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry(() => extractMediaMetadata3(videoPath), {
|
|
5747
5775
|
signal: abortSignal,
|
|
@@ -5779,7 +5807,7 @@ async function runExtractVideosStage(input) {
|
|
|
5779
5807
|
const probed = await Promise.all(
|
|
5780
5808
|
composition.images.map(async (img) => {
|
|
5781
5809
|
const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
|
|
5782
|
-
if (!
|
|
5810
|
+
if (!existsSync8(imgPath)) return null;
|
|
5783
5811
|
const colorSpace = await probeColorSpaceSafely(imgPath, log);
|
|
5784
5812
|
if (isHdrColorSpace2(colorSpace)) {
|
|
5785
5813
|
nativeHdrImageIds.add(img.id);
|
|
@@ -5899,7 +5927,7 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
5899
5927
|
videos.map(async (video) => {
|
|
5900
5928
|
const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
|
|
5901
5929
|
const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
|
|
5902
|
-
if (!isHttpUrl2(readFrom) && !
|
|
5930
|
+
if (!isHttpUrl2(readFrom) && !existsSync8(readFrom)) return null;
|
|
5903
5931
|
const metadata = await extractMediaMetadata3(readFrom);
|
|
5904
5932
|
const window2 = await resolveFinalFrameExtractionWindow2(
|
|
5905
5933
|
readFrom,
|
|
@@ -5927,6 +5955,7 @@ import {
|
|
|
5927
5955
|
} from "@hyperframes/engine";
|
|
5928
5956
|
|
|
5929
5957
|
// src/services/render/stages/audioStage.ts
|
|
5958
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
5930
5959
|
import { join as join12 } from "node:path";
|
|
5931
5960
|
import {
|
|
5932
5961
|
MIXED_AUDIO_FILENAME,
|
|
@@ -5937,7 +5966,7 @@ import {
|
|
|
5937
5966
|
} from "@hyperframes/engine";
|
|
5938
5967
|
|
|
5939
5968
|
// src/services/render/audioDeliveryLimit.ts
|
|
5940
|
-
import { existsSync as
|
|
5969
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
5941
5970
|
import { dirname as dirname7, join as join11 } from "node:path";
|
|
5942
5971
|
import {
|
|
5943
5972
|
applyAudioFxChain,
|
|
@@ -6060,8 +6089,9 @@ var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
|
|
|
6060
6089
|
var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
|
|
6061
6090
|
var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
|
|
6062
6091
|
var MAX_TRUE_PEAK_CORRECTION_PASSES = 3;
|
|
6092
|
+
var gainFilter = (gainDb) => `volume=${gainDb.toFixed(3)}dB`;
|
|
6063
6093
|
function aacTruePeakCorrectionFilter(targetDurationSeconds, attenuationDb) {
|
|
6064
|
-
return
|
|
6094
|
+
return `${gainFilter(attenuationDb)},apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
|
|
6065
6095
|
}
|
|
6066
6096
|
function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
|
|
6067
6097
|
const delta = targetDurationSeconds - sourceDurationSeconds;
|
|
@@ -6099,7 +6129,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
6099
6129
|
const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
|
|
6100
6130
|
const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
|
|
6101
6131
|
const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
|
|
6102
|
-
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal);
|
|
6132
|
+
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath);
|
|
6103
6133
|
const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg || input.encodeAac ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
|
|
6104
6134
|
const [videoResult, audioResult] = await Promise.allSettled([
|
|
6105
6135
|
probeVideo(input.videoPath),
|
|
@@ -6147,7 +6177,12 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
6147
6177
|
);
|
|
6148
6178
|
try {
|
|
6149
6179
|
for (const step of plan.steps) {
|
|
6150
|
-
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
|
|
6180
|
+
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
|
|
6181
|
+
input.sourcePath ?? input.audioPath,
|
|
6182
|
+
input.outputPath,
|
|
6183
|
+
targetDurationSeconds,
|
|
6184
|
+
step.filter
|
|
6185
|
+
);
|
|
6151
6186
|
if (!ffmpegResult.success) {
|
|
6152
6187
|
return {
|
|
6153
6188
|
success: false,
|
|
@@ -6176,6 +6211,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
6176
6211
|
if (probeTruePeak) {
|
|
6177
6212
|
const correction = await enforceAacTruePeak({
|
|
6178
6213
|
audioPath: input.outputPath,
|
|
6214
|
+
sourcePath: input.sourcePath,
|
|
6179
6215
|
targetDurationSeconds,
|
|
6180
6216
|
signal: input.signal,
|
|
6181
6217
|
probeTruePeak,
|
|
@@ -6211,9 +6247,27 @@ async function constrainAacTruePeak(input) {
|
|
|
6211
6247
|
targetDurationSeconds: input.targetDurationSeconds,
|
|
6212
6248
|
signal: input.signal,
|
|
6213
6249
|
probeTruePeak: input.probeTruePeakDbfs ?? ((audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal })),
|
|
6214
|
-
encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
|
|
6250
|
+
encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath),
|
|
6251
|
+
rewriteSource: input.rewriteSource ?? ((sourcePath, outputPath, filter) => defaultRunFfmpeg(
|
|
6252
|
+
["-i", sourcePath, "-af", filter, "-c:a", "pcm_f32le", "-y", outputPath],
|
|
6253
|
+
timeoutMs,
|
|
6254
|
+
input.signal
|
|
6255
|
+
))
|
|
6215
6256
|
});
|
|
6216
6257
|
}
|
|
6258
|
+
async function cutSource(input, scratchDir, attenuationDb) {
|
|
6259
|
+
if (!input.sourcePath || !input.rewriteSource) return true;
|
|
6260
|
+
const cutPath = join10(scratchDir, "source.wav");
|
|
6261
|
+
try {
|
|
6262
|
+
const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
|
|
6263
|
+
if (!result.success) throw new Error(result.error);
|
|
6264
|
+
renameWithRetry5(cutPath, input.sourcePath, { copyFallback: true });
|
|
6265
|
+
return true;
|
|
6266
|
+
} catch {
|
|
6267
|
+
removeScratch(input.sourcePath);
|
|
6268
|
+
return false;
|
|
6269
|
+
}
|
|
6270
|
+
}
|
|
6217
6271
|
async function enforceAacTruePeak(input) {
|
|
6218
6272
|
let scratchDir;
|
|
6219
6273
|
try {
|
|
@@ -6233,8 +6287,10 @@ async function enforceAacTruePeak(input) {
|
|
|
6233
6287
|
}
|
|
6234
6288
|
if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
|
|
6235
6289
|
if (measuredPath !== correctedPath) return { success: true };
|
|
6290
|
+
const sourceCut = await cutSource(input, scratchDir, attenuationDb);
|
|
6236
6291
|
renameWithRetry5(correctedPath, input.audioPath, { copyFallback: true });
|
|
6237
|
-
|
|
6292
|
+
const loweredDb = Number((-attenuationDb).toFixed(2));
|
|
6293
|
+
return sourceCut ? { success: true, loweredDb } : { success: true, loweredDb, sourceStale: true };
|
|
6238
6294
|
}
|
|
6239
6295
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
6240
6296
|
const headroomDb = AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB * 2 ** pass;
|
|
@@ -6244,7 +6300,7 @@ async function enforceAacTruePeak(input) {
|
|
|
6244
6300
|
const referenceDbfs = Math.max(truePeakDbfs, (sourcePeakDbfs ?? -Infinity) + attenuationDb);
|
|
6245
6301
|
attenuationDb += AAC_DELIVERY_TRUE_PEAK_DBFS - headroomDb - referenceDbfs;
|
|
6246
6302
|
const result = await input.encode(
|
|
6247
|
-
input.audioPath,
|
|
6303
|
+
input.sourcePath ?? input.audioPath,
|
|
6248
6304
|
correctedPath,
|
|
6249
6305
|
input.targetDurationSeconds,
|
|
6250
6306
|
aacTruePeakCorrectionFilter(input.targetDurationSeconds, attenuationDb)
|
|
@@ -6417,7 +6473,7 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
6417
6473
|
applyChain: applyAudioFxChain,
|
|
6418
6474
|
encode: encodeAac2,
|
|
6419
6475
|
rename: renameWithRetry6,
|
|
6420
|
-
sourceExists:
|
|
6476
|
+
sourceExists: existsSync9
|
|
6421
6477
|
}) {
|
|
6422
6478
|
const { audioPath, sourcePath, signal, timeoutMs } = input;
|
|
6423
6479
|
if (!deps.sourceExists(sourcePath)) return { success: true };
|
|
@@ -6473,36 +6529,54 @@ async function runAudioStageScoped(input) {
|
|
|
6473
6529
|
const stageStart = Date.now();
|
|
6474
6530
|
const pcmSourcePath = join12(input.workDir, "audio-source.wav");
|
|
6475
6531
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
6532
|
+
let deliverySourcePath;
|
|
6476
6533
|
try {
|
|
6477
6534
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
6478
6535
|
let audioLoweredDb;
|
|
6479
6536
|
if (result.hasAudio) {
|
|
6480
6537
|
const guardStart = Date.now();
|
|
6481
|
-
const
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
});
|
|
6489
|
-
if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
|
|
6490
|
-
const peak = await constrainAacTruePeak({
|
|
6491
|
-
audioPath: result.audioOutputPath,
|
|
6492
|
-
sourcePath: pcmSourcePath,
|
|
6493
|
-
targetDurationSeconds: duration,
|
|
6494
|
-
signal: input.abortSignal,
|
|
6495
|
-
timeoutMs: input.ffmpegProcessTimeout
|
|
6496
|
-
});
|
|
6497
|
-
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
6498
|
-
audioLoweredDb = peak.loweredDb;
|
|
6538
|
+
const guard = await guardDeliveryPeaks(
|
|
6539
|
+
input,
|
|
6540
|
+
result.audioOutputPath,
|
|
6541
|
+
pcmSourcePath,
|
|
6542
|
+
duration
|
|
6543
|
+
);
|
|
6544
|
+
audioLoweredDb = guard.loweredDb;
|
|
6499
6545
|
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
6546
|
+
if (input.keepDeliverySource && !guard.sourceStale && existsSync10(pcmSourcePath)) {
|
|
6547
|
+
deliverySourcePath = pcmSourcePath;
|
|
6548
|
+
}
|
|
6500
6549
|
}
|
|
6501
|
-
return {
|
|
6550
|
+
return {
|
|
6551
|
+
...result,
|
|
6552
|
+
audioLoweredDb,
|
|
6553
|
+
deliverySourcePath,
|
|
6554
|
+
audioProcessMs: Date.now() - stageStart
|
|
6555
|
+
};
|
|
6502
6556
|
} finally {
|
|
6503
|
-
removeScratch2(pcmSourcePath);
|
|
6557
|
+
if (!deliverySourcePath) removeScratch2(pcmSourcePath);
|
|
6504
6558
|
}
|
|
6505
6559
|
}
|
|
6560
|
+
async function guardDeliveryPeaks(input, audioPath, sourcePath, duration) {
|
|
6561
|
+
const limited = await limitDeliveryPeaks({
|
|
6562
|
+
audioPath,
|
|
6563
|
+
sourcePath,
|
|
6564
|
+
targetDurationSeconds: duration,
|
|
6565
|
+
signal: input.abortSignal,
|
|
6566
|
+
timeoutMs: input.ffmpegProcessTimeout,
|
|
6567
|
+
warn: (message, details) => input.log?.warn?.(message, details)
|
|
6568
|
+
});
|
|
6569
|
+
if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
|
|
6570
|
+
const peak = await constrainAacTruePeak({
|
|
6571
|
+
audioPath,
|
|
6572
|
+
sourcePath,
|
|
6573
|
+
targetDurationSeconds: duration,
|
|
6574
|
+
signal: input.abortSignal,
|
|
6575
|
+
timeoutMs: input.ffmpegProcessTimeout
|
|
6576
|
+
});
|
|
6577
|
+
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
6578
|
+
return { loweredDb: peak.loweredDb, sourceStale: peak.sourceStale };
|
|
6579
|
+
}
|
|
6506
6580
|
async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
6507
6581
|
const {
|
|
6508
6582
|
projectDir,
|
|
@@ -6597,7 +6671,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
6597
6671
|
}
|
|
6598
6672
|
|
|
6599
6673
|
// src/services/render/stages/captureStage.ts
|
|
6600
|
-
import { existsSync as
|
|
6674
|
+
import { existsSync as existsSync11, readdirSync, statfsSync } from "node:fs";
|
|
6601
6675
|
import { join as join13 } from "node:path";
|
|
6602
6676
|
import {
|
|
6603
6677
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
@@ -6649,7 +6723,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
6649
6723
|
}
|
|
6650
6724
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
6651
6725
|
let total = sampleDirectoryBytes(framesDir);
|
|
6652
|
-
if (!
|
|
6726
|
+
if (!existsSync11(workDir)) return total;
|
|
6653
6727
|
for (const attempt of readdirSync(workDir)) {
|
|
6654
6728
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
6655
6729
|
total += sampleDirectoryBytes(join13(workDir, attempt));
|
|
@@ -6986,13 +7060,13 @@ import {
|
|
|
6986
7060
|
// src/services/render/stages/encodeStage.ts
|
|
6987
7061
|
import {
|
|
6988
7062
|
copyFileSync as copyFileSync4,
|
|
6989
|
-
existsSync as
|
|
7063
|
+
existsSync as existsSync12,
|
|
6990
7064
|
mkdirSync as mkdirSync5,
|
|
6991
7065
|
readdirSync as readdirSync2,
|
|
6992
7066
|
readFileSync as readFileSync5,
|
|
6993
7067
|
rmSync as rmSync4,
|
|
6994
7068
|
statSync as statSync4,
|
|
6995
|
-
writeFileSync as
|
|
7069
|
+
writeFileSync as writeFileSync4
|
|
6996
7070
|
} from "node:fs";
|
|
6997
7071
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
6998
7072
|
import { dirname as dirname8, join as join14 } from "node:path";
|
|
@@ -7088,7 +7162,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
|
7088
7162
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
7089
7163
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
7090
7164
|
}
|
|
7091
|
-
|
|
7165
|
+
writeFileSync4(outputPath, gif);
|
|
7092
7166
|
return null;
|
|
7093
7167
|
}
|
|
7094
7168
|
async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
@@ -7120,7 +7194,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
7120
7194
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
7121
7195
|
if (failure) return failure;
|
|
7122
7196
|
}
|
|
7123
|
-
const fileSize =
|
|
7197
|
+
const fileSize = existsSync12(outputPath) ? statSync4(outputPath).size : 0;
|
|
7124
7198
|
return {
|
|
7125
7199
|
success: true,
|
|
7126
7200
|
outputPath,
|
|
@@ -7281,7 +7355,7 @@ async function runEncodeStage(input) {
|
|
|
7281
7355
|
const stage5Start = Date.now();
|
|
7282
7356
|
if (isPngSequence) {
|
|
7283
7357
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
7284
|
-
if (!
|
|
7358
|
+
if (!existsSync12(outputPath)) mkdirSync5(outputPath, { recursive: true });
|
|
7285
7359
|
const captured = readdirSync2(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
7286
7360
|
if (captured.length === 0) {
|
|
7287
7361
|
throw new Error(
|
|
@@ -7292,7 +7366,7 @@ async function runEncodeStage(input) {
|
|
|
7292
7366
|
const dst = join14(outputPath, formatExportFrameName(i, "png"));
|
|
7293
7367
|
copyFileSync4(join14(framesDir, name), dst);
|
|
7294
7368
|
});
|
|
7295
|
-
if (hasAudio && audioOutputPath &&
|
|
7369
|
+
if (hasAudio && audioOutputPath && existsSync12(audioOutputPath)) {
|
|
7296
7370
|
copyFileSync4(audioOutputPath, join14(outputPath, MIXED_AUDIO_FILENAME2));
|
|
7297
7371
|
log.info(
|
|
7298
7372
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
@@ -7448,18 +7522,14 @@ function hasUsableVideoDimensions(metadata) {
|
|
|
7448
7522
|
return Number.isFinite(metadata.width) && Number.isFinite(metadata.height) && metadata.width > 0 && metadata.height > 0;
|
|
7449
7523
|
}
|
|
7450
7524
|
function collectVideoMetadataHints(extractedVideos) {
|
|
7451
|
-
return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({
|
|
7452
|
-
id: video.videoId,
|
|
7453
|
-
width: video.metadata.width,
|
|
7454
|
-
height: video.metadata.height
|
|
7455
|
-
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
7525
|
+
return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({ id: video.videoId, ...displaySize(video.metadata) })).sort((a, b) => a.id.localeCompare(b.id));
|
|
7456
7526
|
}
|
|
7457
7527
|
function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
7458
7528
|
const ranges = [];
|
|
7459
7529
|
let rangeStart = null;
|
|
7460
7530
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7461
7531
|
const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7462
|
-
const missing = !
|
|
7532
|
+
const missing = !existsSync13(framePath) || statSync5(framePath).size <= 8;
|
|
7463
7533
|
if (missing && rangeStart === null) {
|
|
7464
7534
|
rangeStart = frameIndex;
|
|
7465
7535
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -7549,7 +7619,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
|
7549
7619
|
let captured = 0;
|
|
7550
7620
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7551
7621
|
const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7552
|
-
if (
|
|
7622
|
+
if (existsSync13(framePath)) captured++;
|
|
7553
7623
|
}
|
|
7554
7624
|
return captured;
|
|
7555
7625
|
}
|
|
@@ -7776,7 +7846,7 @@ import {
|
|
|
7776
7846
|
} from "@hyperframes/engine";
|
|
7777
7847
|
|
|
7778
7848
|
// src/services/distributed/chunkFrames.ts
|
|
7779
|
-
import { existsSync as
|
|
7849
|
+
import { existsSync as existsSync15, readdirSync as readdirSync4 } from "node:fs";
|
|
7780
7850
|
import { extname as extname2, join as join17 } from "node:path";
|
|
7781
7851
|
import {
|
|
7782
7852
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
@@ -7788,7 +7858,7 @@ import {
|
|
|
7788
7858
|
// src/services/distributed/shared.ts
|
|
7789
7859
|
import { execFile as execFileCallback } from "node:child_process";
|
|
7790
7860
|
import { dirname as dirname10, join as join16 } from "node:path";
|
|
7791
|
-
import { existsSync as
|
|
7861
|
+
import { existsSync as existsSync14, readFileSync as readFileSync6 } from "node:fs";
|
|
7792
7862
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7793
7863
|
import { promisify as promisify2 } from "node:util";
|
|
7794
7864
|
import {
|
|
@@ -7804,7 +7874,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
7804
7874
|
function resolvePlanAudioPath(planDir) {
|
|
7805
7875
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
7806
7876
|
const candidate = join16(planDir, name);
|
|
7807
|
-
if (
|
|
7877
|
+
if (existsSync14(candidate)) return candidate;
|
|
7808
7878
|
}
|
|
7809
7879
|
return null;
|
|
7810
7880
|
}
|
|
@@ -8065,7 +8135,7 @@ function readProducerVersion() {
|
|
|
8065
8135
|
let current = startDir;
|
|
8066
8136
|
for (let i = 0; i < 10; i++) {
|
|
8067
8137
|
const candidate = join16(current, "package.json");
|
|
8068
|
-
if (
|
|
8138
|
+
if (existsSync14(candidate)) {
|
|
8069
8139
|
try {
|
|
8070
8140
|
const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
|
|
8071
8141
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
@@ -8119,7 +8189,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
8119
8189
|
const result = [];
|
|
8120
8190
|
for (const v of videos) {
|
|
8121
8191
|
const outputDir = join17(planDir, "video-frames", v.videoId);
|
|
8122
|
-
if (!
|
|
8192
|
+
if (!existsSync15(outputDir)) {
|
|
8123
8193
|
throw new Error(
|
|
8124
8194
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
8125
8195
|
);
|
|
@@ -8235,7 +8305,7 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
8235
8305
|
const range = video.deferredRange;
|
|
8236
8306
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
8237
8307
|
const sourcePath = remoteUrl ?? join17(input.planDir, ...range.sourcePath.split("/"));
|
|
8238
|
-
if (remoteUrl === void 0 && !
|
|
8308
|
+
if (remoteUrl === void 0 && !existsSync15(sourcePath)) {
|
|
8239
8309
|
throw new RenderChunkValidationError(
|
|
8240
8310
|
MISSING_PLAN_ARTIFACT,
|
|
8241
8311
|
`[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
|
|
@@ -8453,7 +8523,7 @@ async function readPlanReferenceBackground(input) {
|
|
|
8453
8523
|
}
|
|
8454
8524
|
|
|
8455
8525
|
// src/services/render/stages/freezePlan.ts
|
|
8456
|
-
import { existsSync as
|
|
8526
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
8457
8527
|
import { join as join20, relative as relative3, resolve as resolve8 } from "node:path";
|
|
8458
8528
|
import {
|
|
8459
8529
|
REFERENCE_BACKGROUND_FIELD,
|
|
@@ -8647,7 +8717,7 @@ function planFileSha256(absolutePath, digests) {
|
|
|
8647
8717
|
return sha256;
|
|
8648
8718
|
}
|
|
8649
8719
|
async function prehashPlanFiles(dir, digests) {
|
|
8650
|
-
if (!
|
|
8720
|
+
if (!existsSync16(dir)) return;
|
|
8651
8721
|
for (const { absolutePath } of listPlanFiles(dir)) {
|
|
8652
8722
|
if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
|
|
8653
8723
|
}
|
|
@@ -8713,19 +8783,19 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
|
|
|
8713
8783
|
function writePlanRemoteFiles(planDir, files) {
|
|
8714
8784
|
if (files.length === 0) return;
|
|
8715
8785
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
8716
|
-
|
|
8786
|
+
writeFileSync6(join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
|
|
8717
8787
|
}
|
|
8718
8788
|
function readPlanRemoteFiles(planDir) {
|
|
8719
8789
|
const path = join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
8720
|
-
return
|
|
8790
|
+
return existsSync16(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
|
|
8721
8791
|
}
|
|
8722
8792
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
8723
8793
|
const planJsonPath = join20(planDir, "plan.json");
|
|
8724
8794
|
const encoderJsonPath = join20(planDir, "meta", "encoder.json");
|
|
8725
|
-
if (!
|
|
8795
|
+
if (!existsSync16(planJsonPath)) {
|
|
8726
8796
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
8727
8797
|
}
|
|
8728
|
-
if (!
|
|
8798
|
+
if (!existsSync16(encoderJsonPath)) {
|
|
8729
8799
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
8730
8800
|
}
|
|
8731
8801
|
const planJson = JSON.parse(readFileSync7(planJsonPath, "utf-8"));
|
|
@@ -8755,12 +8825,12 @@ async function freezePlan(input) {
|
|
|
8755
8825
|
hasAudio,
|
|
8756
8826
|
digests
|
|
8757
8827
|
} = input;
|
|
8758
|
-
if (!
|
|
8828
|
+
if (!existsSync16(planDir)) {
|
|
8759
8829
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
8760
8830
|
}
|
|
8761
8831
|
const metaDir = join20(planDir, "meta");
|
|
8762
|
-
if (!
|
|
8763
|
-
|
|
8832
|
+
if (!existsSync16(metaDir)) mkdirSync7(metaDir, { recursive: true });
|
|
8833
|
+
writeFileSync6(
|
|
8764
8834
|
join20(metaDir, "composition.json"),
|
|
8765
8835
|
`${JSON.stringify(composition, null, 2)}
|
|
8766
8836
|
`,
|
|
@@ -8768,8 +8838,8 @@ async function freezePlan(input) {
|
|
|
8768
8838
|
);
|
|
8769
8839
|
const encoderForCanonical = stripUndefined(encoder);
|
|
8770
8840
|
const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
|
|
8771
|
-
|
|
8772
|
-
|
|
8841
|
+
writeFileSync6(join20(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
|
|
8842
|
+
writeFileSync6(join20(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
|
|
8773
8843
|
`, "utf-8");
|
|
8774
8844
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
8775
8845
|
const planHash = computePlanHash({
|
|
@@ -8795,7 +8865,7 @@ async function freezePlan(input) {
|
|
|
8795
8865
|
hasAudio
|
|
8796
8866
|
};
|
|
8797
8867
|
const planJsonPath = join20(planDir, "plan.json");
|
|
8798
|
-
|
|
8868
|
+
writeFileSync6(planJsonPath, `${JSON.stringify(planJson, null, 2)}
|
|
8799
8869
|
`, "utf-8");
|
|
8800
8870
|
return { planJsonPath, planHash };
|
|
8801
8871
|
}
|
|
@@ -8836,8 +8906,8 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
8836
8906
|
}
|
|
8837
8907
|
|
|
8838
8908
|
// src/services/distributed/chunkAudio.ts
|
|
8839
|
-
import { closeSync as closeSync4, existsSync as
|
|
8840
|
-
import { writeFileSync as
|
|
8909
|
+
import { closeSync as closeSync4, existsSync as existsSync17, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
|
|
8910
|
+
import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
|
|
8841
8911
|
import { join as join22 } from "node:path";
|
|
8842
8912
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
8843
8913
|
|
|
@@ -9319,15 +9389,15 @@ async function writeChunkAudioSlices(input) {
|
|
|
9319
9389
|
const start = offsets[cut.firstPacket];
|
|
9320
9390
|
layout.partStarts[index] = start;
|
|
9321
9391
|
const end = offsets[last] + track.sizes[last];
|
|
9322
|
-
|
|
9392
|
+
writeFileSync7(join22(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
|
|
9323
9393
|
});
|
|
9324
9394
|
const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
|
|
9325
|
-
|
|
9326
|
-
|
|
9395
|
+
writeFileSync7(join22(input.planDir, SLICE_FRAME), Buffer.concat(frame));
|
|
9396
|
+
writeFileSync7(join22(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
|
|
9327
9397
|
}
|
|
9328
9398
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
9329
9399
|
const layoutPath = join22(planDir, SLICE_LAYOUT);
|
|
9330
|
-
if (!
|
|
9400
|
+
if (!existsSync17(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
9331
9401
|
const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
|
|
9332
9402
|
const frame = readFileSync8(join22(planDir, SLICE_FRAME));
|
|
9333
9403
|
const fd = openSync4(scratchPath, "w");
|
|
@@ -9891,7 +9961,7 @@ function createVideoSourceShipper(input) {
|
|
|
9891
9961
|
let result = null;
|
|
9892
9962
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
9893
9963
|
const rel = pathInside(root, srcPath);
|
|
9894
|
-
if (rel !== null && (
|
|
9964
|
+
if (rel !== null && (existsSync18(join24(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
9895
9965
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
9896
9966
|
break;
|
|
9897
9967
|
}
|
|
@@ -9919,7 +9989,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9919
9989
|
useGpu: false,
|
|
9920
9990
|
browserGpuMode: "software"
|
|
9921
9991
|
});
|
|
9922
|
-
if (!
|
|
9992
|
+
if (!existsSync18(planDir)) mkdirSync10(planDir, { recursive: true });
|
|
9923
9993
|
const log = config.logger ?? defaultLogger;
|
|
9924
9994
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
9925
9995
|
const abortSignal = config.abortSignal;
|
|
@@ -9949,7 +10019,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9949
10019
|
});
|
|
9950
10020
|
const entryFile = config.entryFile ?? "index.html";
|
|
9951
10021
|
const htmlPath = join24(projectDir, entryFile);
|
|
9952
|
-
if (!
|
|
10022
|
+
if (!existsSync18(htmlPath)) {
|
|
9953
10023
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
9954
10024
|
}
|
|
9955
10025
|
const workDir = join24(planDir, ".plan-work");
|
|
@@ -9993,7 +10063,6 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9993
10063
|
urlByAbsentLocalPath,
|
|
9994
10064
|
projectDir,
|
|
9995
10065
|
workDir,
|
|
9996
|
-
htmlPath,
|
|
9997
10066
|
entryFile,
|
|
9998
10067
|
job,
|
|
9999
10068
|
cfg,
|
|
@@ -10178,15 +10247,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10178
10247
|
logPlanStage(log, "extract", stageStarted);
|
|
10179
10248
|
const stagedVideoFrames = join24(compiledDir, "__hyperframes_video_frames");
|
|
10180
10249
|
const videoFramesDst = join24(planDir, "video-frames");
|
|
10181
|
-
if (
|
|
10250
|
+
if (existsSync18(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
|
|
10182
10251
|
const videoSourcesDir = join24(planDir, "video-sources");
|
|
10183
10252
|
rmSync7(videoSourcesDir, { recursive: true, force: true });
|
|
10184
|
-
if (
|
|
10253
|
+
if (existsSync18(stagedVideoFrames)) {
|
|
10185
10254
|
renameWithRetry8(stagedVideoFrames, videoFramesDst);
|
|
10186
10255
|
} else {
|
|
10187
10256
|
mkdirSync10(videoFramesDst, { recursive: true });
|
|
10188
10257
|
}
|
|
10189
|
-
if (
|
|
10258
|
+
if (existsSync18(finalCompiledDir)) rmSync7(finalCompiledDir, { recursive: true, force: true });
|
|
10190
10259
|
renameWithRetry8(compiledDir, finalCompiledDir);
|
|
10191
10260
|
const shipVideoSource = createVideoSourceShipper({
|
|
10192
10261
|
planDir,
|
|
@@ -10225,7 +10294,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10225
10294
|
sizeBytes
|
|
10226
10295
|
}))
|
|
10227
10296
|
);
|
|
10228
|
-
|
|
10297
|
+
writeFileSync8(
|
|
10229
10298
|
join24(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
10230
10299
|
JSON.stringify(planVideosJson, null, 2),
|
|
10231
10300
|
"utf-8"
|
|
@@ -10306,10 +10375,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10306
10375
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
10307
10376
|
}
|
|
10308
10377
|
const planAudioPath = join24(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
10309
|
-
if (audioResult?.hasAudio &&
|
|
10378
|
+
if (audioResult?.hasAudio && existsSync18(audioResult.audioOutputPath)) {
|
|
10310
10379
|
renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
|
|
10311
10380
|
}
|
|
10312
|
-
if (chunksCarryAudio &&
|
|
10381
|
+
if (chunksCarryAudio && existsSync18(planAudioPath)) {
|
|
10313
10382
|
if (options.sliceChunkAudio) {
|
|
10314
10383
|
stageStarted = Date.now();
|
|
10315
10384
|
await writeChunkAudioSlices({
|
|
@@ -10422,7 +10491,7 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
|
|
|
10422
10491
|
if (mixed.audioError) {
|
|
10423
10492
|
applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
|
|
10424
10493
|
}
|
|
10425
|
-
if (!mixed.hasAudio || !
|
|
10494
|
+
if (!mixed.hasAudio || !existsSync18(mixed.audioOutputPath)) return { hasAudio: false };
|
|
10426
10495
|
renameWithRetry8(mixed.audioOutputPath, outputPath);
|
|
10427
10496
|
return { hasAudio: true };
|
|
10428
10497
|
} finally {
|
|
@@ -10437,7 +10506,7 @@ import {
|
|
|
10437
10506
|
} from "@hyperframes/engine";
|
|
10438
10507
|
import {
|
|
10439
10508
|
copyFileSync as copyFileSync8,
|
|
10440
|
-
existsSync as
|
|
10509
|
+
existsSync as existsSync20,
|
|
10441
10510
|
lstatSync as lstatSync2,
|
|
10442
10511
|
mkdirSync as mkdirSync12,
|
|
10443
10512
|
mkdtempSync as mkdtempSync4,
|
|
@@ -10445,7 +10514,7 @@ import {
|
|
|
10445
10514
|
readdirSync as readdirSync7,
|
|
10446
10515
|
rmSync as rmSync9,
|
|
10447
10516
|
statSync as statSync7,
|
|
10448
|
-
writeFileSync as
|
|
10517
|
+
writeFileSync as writeFileSync10
|
|
10449
10518
|
} from "node:fs";
|
|
10450
10519
|
import { tmpdir } from "node:os";
|
|
10451
10520
|
import { dirname as dirname13, isAbsolute as isAbsolute4, join as join27, relative as relative6, resolve as resolve9, sep as sep3 } from "node:path";
|
|
@@ -10455,13 +10524,13 @@ import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as r
|
|
|
10455
10524
|
// src/services/distributed/planV2Publisher.ts
|
|
10456
10525
|
import {
|
|
10457
10526
|
copyFileSync as copyFileSync7,
|
|
10458
|
-
existsSync as
|
|
10527
|
+
existsSync as existsSync19,
|
|
10459
10528
|
linkSync as linkSync2,
|
|
10460
10529
|
mkdirSync as mkdirSync11,
|
|
10461
10530
|
mkdtempSync as mkdtempSync3,
|
|
10462
10531
|
rmSync as rmSync8,
|
|
10463
10532
|
statSync as statSync6,
|
|
10464
|
-
writeFileSync as
|
|
10533
|
+
writeFileSync as writeFileSync9
|
|
10465
10534
|
} from "node:fs";
|
|
10466
10535
|
import { renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
|
|
10467
10536
|
import { dirname as dirname12, join as join26 } from "node:path";
|
|
@@ -10524,7 +10593,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10524
10593
|
#linkFile;
|
|
10525
10594
|
#committed = false;
|
|
10526
10595
|
constructor(destinationDir, options = {}) {
|
|
10527
|
-
if (
|
|
10596
|
+
if (existsSync19(destinationDir)) {
|
|
10528
10597
|
throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
|
|
10529
10598
|
}
|
|
10530
10599
|
this.destinationDir = destinationDir;
|
|
@@ -10541,7 +10610,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10541
10610
|
);
|
|
10542
10611
|
}
|
|
10543
10612
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
10544
|
-
if (
|
|
10613
|
+
if (existsSync19(destinationPath)) return;
|
|
10545
10614
|
mkdirSync11(dirname12(destinationPath), { recursive: true });
|
|
10546
10615
|
const stagingDir = mkdtempSync3(join26(dirname12(destinationPath), ".plan-v2-blob-"));
|
|
10547
10616
|
const temporaryPath = join26(stagingDir, "blob");
|
|
@@ -10559,13 +10628,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10559
10628
|
}
|
|
10560
10629
|
async commitManifest(manifestBytes) {
|
|
10561
10630
|
for (const digest of new Set(manifestDigests(manifestBytes))) {
|
|
10562
|
-
if (!
|
|
10631
|
+
if (!existsSync19(planV2BlobPath(this.temporaryDir, digest))) {
|
|
10563
10632
|
throw new PlanV2IntegrityError(
|
|
10564
10633
|
`cannot commit manifest before referenced blob is durable: ${digest}`
|
|
10565
10634
|
);
|
|
10566
10635
|
}
|
|
10567
10636
|
}
|
|
10568
|
-
|
|
10637
|
+
writeFileSync9(join26(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
10569
10638
|
renameWithRetry9(this.temporaryDir, this.destinationDir);
|
|
10570
10639
|
this.#committed = true;
|
|
10571
10640
|
}
|
|
@@ -10638,7 +10707,7 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
10638
10707
|
}
|
|
10639
10708
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
10640
10709
|
const videoRoot = join27(executionPlanDir, "video-frames");
|
|
10641
|
-
if (!
|
|
10710
|
+
if (!existsSync20(videoRoot)) return;
|
|
10642
10711
|
for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
|
|
10643
10712
|
if (!videoEntry.isDirectory()) continue;
|
|
10644
10713
|
const videoDir = join27(videoRoot, videoEntry.name);
|
|
@@ -10739,7 +10808,7 @@ function parsePlanVideosJson2(value) {
|
|
|
10739
10808
|
}
|
|
10740
10809
|
function materializeExtractedVideoDirectories(planDir) {
|
|
10741
10810
|
const videosPath = join27(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10742
|
-
if (!
|
|
10811
|
+
if (!existsSync20(videosPath)) return;
|
|
10743
10812
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
10744
10813
|
for (const video of videos.extracted) {
|
|
10745
10814
|
mkdirSync12(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
@@ -10773,9 +10842,9 @@ function parseChunkSlices(value) {
|
|
|
10773
10842
|
}
|
|
10774
10843
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
10775
10844
|
const videoRoot = join27(executionPlanDir, "video-frames");
|
|
10776
|
-
const hasExtractedFrames =
|
|
10845
|
+
const hasExtractedFrames = existsSync20(videoRoot) && listFiles(videoRoot).length > 0;
|
|
10777
10846
|
const videosPath = join27(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10778
|
-
if (!
|
|
10847
|
+
if (!existsSync20(videosPath)) {
|
|
10779
10848
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
10780
10849
|
}
|
|
10781
10850
|
const chunksPath = join27(executionPlanDir, "meta", "chunks.json");
|
|
@@ -10832,7 +10901,7 @@ function computeManifestHash(manifest) {
|
|
|
10832
10901
|
return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
|
|
10833
10902
|
}
|
|
10834
10903
|
function writeBlob(sourcePath, destinationPath) {
|
|
10835
|
-
if (
|
|
10904
|
+
if (existsSync20(destinationPath)) return;
|
|
10836
10905
|
mkdirSync12(dirname13(destinationPath), { recursive: true });
|
|
10837
10906
|
const temporaryDir = mkdtempSync4(join27(dirname13(destinationPath), ".plan-v2-blob-"));
|
|
10838
10907
|
const temporaryPath = join27(temporaryDir, "blob");
|
|
@@ -10872,7 +10941,7 @@ async function putTasks(tasks) {
|
|
|
10872
10941
|
}
|
|
10873
10942
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
10874
10943
|
const executionPlanPath = join27(executionPlanDir, "plan.json");
|
|
10875
|
-
if (!
|
|
10944
|
+
if (!existsSync20(executionPlanPath)) {
|
|
10876
10945
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
10877
10946
|
}
|
|
10878
10947
|
const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
|
|
@@ -10952,7 +11021,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
10952
11021
|
};
|
|
10953
11022
|
}
|
|
10954
11023
|
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
10955
|
-
if (
|
|
11024
|
+
if (existsSync20(planV2Dir)) {
|
|
10956
11025
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
10957
11026
|
}
|
|
10958
11027
|
const publication = buildPlanV2Publication(executionPlanDir);
|
|
@@ -10965,7 +11034,7 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
10965
11034
|
for (const blob of publication.blobs) {
|
|
10966
11035
|
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
10967
11036
|
}
|
|
10968
|
-
|
|
11037
|
+
writeFileSync10(
|
|
10969
11038
|
join27(tempDir, "plan.json"),
|
|
10970
11039
|
canonicalJsonStringify(publication.manifest),
|
|
10971
11040
|
"utf-8"
|
|
@@ -11038,7 +11107,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
11038
11107
|
}
|
|
11039
11108
|
}
|
|
11040
11109
|
async function planV2(projectDir, config, planV2Dir) {
|
|
11041
|
-
if (
|
|
11110
|
+
if (existsSync20(planV2Dir)) {
|
|
11042
11111
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
11043
11112
|
}
|
|
11044
11113
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
@@ -11192,7 +11261,7 @@ function parsePlanV2Manifest(value) {
|
|
|
11192
11261
|
}
|
|
11193
11262
|
function readPlanV2Manifest(planV2Dir) {
|
|
11194
11263
|
const manifestPath = join27(planV2Dir, "plan.json");
|
|
11195
|
-
if (!
|
|
11264
|
+
if (!existsSync20(manifestPath)) {
|
|
11196
11265
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
11197
11266
|
}
|
|
11198
11267
|
const value = readJsonFile(manifestPath, "v2 manifest");
|
|
@@ -11211,7 +11280,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
|
|
|
11211
11280
|
}
|
|
11212
11281
|
function verifyBlob(planV2Dir, artifact) {
|
|
11213
11282
|
const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
|
|
11214
|
-
if (!
|
|
11283
|
+
if (!existsSync20(sourcePath)) {
|
|
11215
11284
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
11216
11285
|
}
|
|
11217
11286
|
const stats = statSync7(sourcePath);
|
|
@@ -11224,7 +11293,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
11224
11293
|
return sourcePath;
|
|
11225
11294
|
}
|
|
11226
11295
|
function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
|
|
11227
|
-
if (
|
|
11296
|
+
if (existsSync20(destinationDir)) {
|
|
11228
11297
|
throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
|
|
11229
11298
|
}
|
|
11230
11299
|
const manifest = readPlanV2Manifest(planV2Dir);
|
|
@@ -11245,7 +11314,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
11245
11314
|
copyFileSync8(sourcePath, destinationPath);
|
|
11246
11315
|
}
|
|
11247
11316
|
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
11248
|
-
|
|
11317
|
+
writeFileSync10(
|
|
11249
11318
|
join27(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
11250
11319
|
canonicalJsonStringify({
|
|
11251
11320
|
manifest,
|
|
@@ -11289,7 +11358,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
11289
11358
|
}
|
|
11290
11359
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
11291
11360
|
const markerPath = join27(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
11292
|
-
if (!
|
|
11361
|
+
if (!existsSync20(markerPath)) return null;
|
|
11293
11362
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
11294
11363
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
11295
11364
|
throw new PlanV2IntegrityError("malformed materialization marker");
|
|
@@ -11307,7 +11376,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
11307
11376
|
for (const artifact of required) {
|
|
11308
11377
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
11309
11378
|
const path = join27(planDir, ...artifact.path.split("/"));
|
|
11310
|
-
if (!
|
|
11379
|
+
if (!existsSync20(path) || statSync7(path).size !== artifact.sizeBytes) {
|
|
11311
11380
|
throw new PlanV2IntegrityError(
|
|
11312
11381
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
11313
11382
|
);
|
|
@@ -11331,14 +11400,14 @@ import {
|
|
|
11331
11400
|
} from "@hyperframes/engine";
|
|
11332
11401
|
import {
|
|
11333
11402
|
cpSync as cpSync3,
|
|
11334
|
-
existsSync as
|
|
11403
|
+
existsSync as existsSync21,
|
|
11335
11404
|
mkdirSync as mkdirSync13,
|
|
11336
11405
|
mkdtempSync as mkdtempSync5,
|
|
11337
11406
|
readFileSync as readFileSync10,
|
|
11338
11407
|
readdirSync as readdirSync8,
|
|
11339
11408
|
rmSync as rmSync10,
|
|
11340
11409
|
statSync as statSync8,
|
|
11341
|
-
writeFileSync as
|
|
11410
|
+
writeFileSync as writeFileSync11
|
|
11342
11411
|
} from "node:fs";
|
|
11343
11412
|
import { dirname as dirname14, join as join28 } from "node:path";
|
|
11344
11413
|
import {
|
|
@@ -11386,7 +11455,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11386
11455
|
}
|
|
11387
11456
|
const format = plan.dimensions.format;
|
|
11388
11457
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
11389
|
-
if (!
|
|
11458
|
+
if (!existsSync21(dirname14(outputPath))) {
|
|
11390
11459
|
mkdirSync13(dirname14(outputPath), { recursive: true });
|
|
11391
11460
|
}
|
|
11392
11461
|
const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
|
|
@@ -11410,7 +11479,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11410
11479
|
try {
|
|
11411
11480
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
11412
11481
|
const finalPath = join28(workDir, `output.${format}`);
|
|
11413
|
-
const audio = audioPath !== null &&
|
|
11482
|
+
const audio = audioPath !== null && existsSync21(audioPath) ? audioPath : null;
|
|
11414
11483
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
11415
11484
|
renameWithRetry11(finalPath, outputPath);
|
|
11416
11485
|
log.info("[assemble] stage timings", {
|
|
@@ -11428,7 +11497,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11428
11497
|
});
|
|
11429
11498
|
}
|
|
11430
11499
|
}
|
|
11431
|
-
const fileSize =
|
|
11500
|
+
const fileSize = existsSync21(outputPath) ? statSync8(outputPath).size : 0;
|
|
11432
11501
|
return {
|
|
11433
11502
|
...referenceBackgroundResult6(plan[REFERENCE_BACKGROUND_FIELD4]),
|
|
11434
11503
|
outputPath,
|
|
@@ -11440,13 +11509,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11440
11509
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
11441
11510
|
const planJsonPath = join28(planDir, "plan.json");
|
|
11442
11511
|
const chunksJsonPath = join28(planDir, "meta", "chunks.json");
|
|
11443
|
-
if (!
|
|
11512
|
+
if (!existsSync21(planJsonPath)) {
|
|
11444
11513
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
11445
11514
|
}
|
|
11446
11515
|
const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
|
|
11447
11516
|
readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
11448
11517
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
11449
|
-
if (!
|
|
11518
|
+
if (!existsSync21(chunksJsonPath)) {
|
|
11450
11519
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
11451
11520
|
}
|
|
11452
11521
|
const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
|
|
@@ -11461,7 +11530,7 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
11461
11530
|
}
|
|
11462
11531
|
}
|
|
11463
11532
|
for (const path of chunkPaths.flat()) {
|
|
11464
|
-
if (!
|
|
11533
|
+
if (!existsSync21(path)) {
|
|
11465
11534
|
throw new Error(`[assemble] chunk path does not exist: ${path}`);
|
|
11466
11535
|
}
|
|
11467
11536
|
}
|
|
@@ -11559,7 +11628,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
11559
11628
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
11560
11629
|
const listPath = join28(workDir, "concat-list.txt");
|
|
11561
11630
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
11562
|
-
|
|
11631
|
+
writeFileSync11(listPath, `${body}
|
|
11563
11632
|
`, "utf-8");
|
|
11564
11633
|
return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
|
|
11565
11634
|
}
|
|
@@ -11600,7 +11669,7 @@ function planFormat(planDir) {
|
|
|
11600
11669
|
}
|
|
11601
11670
|
function lockedAudio(planDir) {
|
|
11602
11671
|
const encoderJsonPath = join28(planDir, "meta", "encoder.json");
|
|
11603
|
-
if (!
|
|
11672
|
+
if (!existsSync21(encoderJsonPath)) return {};
|
|
11604
11673
|
return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
11605
11674
|
}
|
|
11606
11675
|
function readCfrEncoder(planDir, format) {
|
|
@@ -11610,7 +11679,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
11610
11679
|
);
|
|
11611
11680
|
}
|
|
11612
11681
|
const encoderJsonPath = join28(planDir, "meta", "encoder.json");
|
|
11613
|
-
if (!
|
|
11682
|
+
if (!existsSync21(encoderJsonPath)) {
|
|
11614
11683
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
11615
11684
|
}
|
|
11616
11685
|
const encoderJson = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
@@ -11622,7 +11691,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
11622
11691
|
return encoderJson;
|
|
11623
11692
|
}
|
|
11624
11693
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
11625
|
-
if (
|
|
11694
|
+
if (existsSync21(outputPath)) rmSync10(outputPath, { recursive: true, force: true });
|
|
11626
11695
|
mkdirSync13(outputPath, { recursive: true });
|
|
11627
11696
|
let globalIdx = 0;
|
|
11628
11697
|
for (const chunkDir of chunkPaths) {
|
|
@@ -11646,7 +11715,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11646
11715
|
`[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
|
|
11647
11716
|
);
|
|
11648
11717
|
}
|
|
11649
|
-
if (audioPath !== null &&
|
|
11718
|
+
if (audioPath !== null && existsSync21(audioPath)) {
|
|
11650
11719
|
const sidecar = join28(outputPath, MIXED_AUDIO_FILENAME4);
|
|
11651
11720
|
cpSync3(audioPath, sidecar);
|
|
11652
11721
|
}
|
|
@@ -11667,7 +11736,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11667
11736
|
|
|
11668
11737
|
// src/services/distributed/renderChunk.ts
|
|
11669
11738
|
import { randomBytes } from "node:crypto";
|
|
11670
|
-
import { existsSync as
|
|
11739
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
|
|
11671
11740
|
import { cpus } from "node:os";
|
|
11672
11741
|
import { join as join29 } from "node:path";
|
|
11673
11742
|
import {
|
|
@@ -11813,7 +11882,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11813
11882
|
const planJsonPath = join29(planDir, "plan.json");
|
|
11814
11883
|
const encoderJsonPath = join29(planDir, "meta", "encoder.json");
|
|
11815
11884
|
const chunksJsonPath = join29(planDir, "meta", "chunks.json");
|
|
11816
|
-
if (!
|
|
11885
|
+
if (!existsSync22(planJsonPath)) {
|
|
11817
11886
|
throw new RenderChunkValidationError(
|
|
11818
11887
|
MISSING_PLAN_ARTIFACT,
|
|
11819
11888
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
@@ -11829,7 +11898,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11829
11898
|
const v2Manifest = v2Materialization?.manifest ?? null;
|
|
11830
11899
|
const remoteSources = v2Materialization?.remoteSources ?? {};
|
|
11831
11900
|
for (const required of [encoderJsonPath, chunksJsonPath]) {
|
|
11832
|
-
if (!
|
|
11901
|
+
if (!existsSync22(required)) {
|
|
11833
11902
|
throw new RenderChunkValidationError(
|
|
11834
11903
|
MISSING_PLAN_ARTIFACT,
|
|
11835
11904
|
`[renderChunk] planDir is missing required artifact: ${required}`
|
|
@@ -11840,7 +11909,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11840
11909
|
const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
|
|
11841
11910
|
const videosJsonPath = join29(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
11842
11911
|
let planVideos = null;
|
|
11843
|
-
if (
|
|
11912
|
+
if (existsSync22(videosJsonPath)) {
|
|
11844
11913
|
try {
|
|
11845
11914
|
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync11(videosJsonPath, "utf-8")));
|
|
11846
11915
|
} catch (err) {
|
|
@@ -11871,7 +11940,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11871
11940
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
11872
11941
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
11873
11942
|
const compiledDir = join29(planDir, "compiled");
|
|
11874
|
-
if (!
|
|
11943
|
+
if (!existsSync22(compiledDir)) {
|
|
11875
11944
|
throw new RenderChunkValidationError(
|
|
11876
11945
|
MISSING_PLAN_ARTIFACT,
|
|
11877
11946
|
`[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
|
|
@@ -12121,10 +12190,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12121
12190
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
12122
12191
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
12123
12192
|
if (isPngSequence) {
|
|
12124
|
-
if (!
|
|
12193
|
+
if (!existsSync22(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
|
|
12125
12194
|
} else {
|
|
12126
12195
|
const outDir = join29(outputChunkPath, "..");
|
|
12127
|
-
if (!
|
|
12196
|
+
if (!existsSync22(outDir)) mkdirSync14(outDir, { recursive: true });
|
|
12128
12197
|
}
|
|
12129
12198
|
const encodeInput = {
|
|
12130
12199
|
job,
|
|
@@ -12334,7 +12403,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12334
12403
|
producerVersion: plan.producerVersion,
|
|
12335
12404
|
ffmpegVersion
|
|
12336
12405
|
};
|
|
12337
|
-
|
|
12406
|
+
writeFileSync12(perfPath, `${JSON.stringify(perfPayload, null, 2)}
|
|
12338
12407
|
`, "utf-8");
|
|
12339
12408
|
try {
|
|
12340
12409
|
rmSync11(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
@@ -12751,7 +12820,7 @@ var isMissing = (result) => result.error?.code === "ENOENT";
|
|
|
12751
12820
|
var ffmpegIsMissing = async (signal) => isMissing(await runFfmpeg9(["-version"], { signal }));
|
|
12752
12821
|
|
|
12753
12822
|
// src/services/distributed/probeFacts.ts
|
|
12754
|
-
import { writeFileSync as
|
|
12823
|
+
import { writeFileSync as writeFileSync13 } from "node:fs";
|
|
12755
12824
|
import { extname as extname6 } from "node:path";
|
|
12756
12825
|
import { buildProbeFactsText } from "@hyperframes/engine";
|
|
12757
12826
|
var PROBE_FACTS_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v", ".webm", ".mkv"]);
|
|
@@ -12760,7 +12829,7 @@ function wantsProbeFacts(path) {
|
|
|
12760
12829
|
}
|
|
12761
12830
|
async function buildProbeFacts(inputPath, outputPath, signal) {
|
|
12762
12831
|
try {
|
|
12763
|
-
|
|
12832
|
+
writeFileSync13(outputPath, await buildProbeFactsText(inputPath, signal));
|
|
12764
12833
|
return "built";
|
|
12765
12834
|
} catch (error) {
|
|
12766
12835
|
if (signal?.aborted) throw error;
|