@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/index.js
CHANGED
|
@@ -9,12 +9,12 @@ import {
|
|
|
9
9
|
// src/services/renderOrchestrator.ts
|
|
10
10
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
11
11
|
import {
|
|
12
|
-
existsSync as
|
|
12
|
+
existsSync as existsSync20,
|
|
13
13
|
mkdirSync as mkdirSync10,
|
|
14
14
|
readdirSync as readdirSync5,
|
|
15
15
|
rmSync as rmSync10,
|
|
16
16
|
statSync as statSync8,
|
|
17
|
-
writeFileSync as
|
|
17
|
+
writeFileSync as writeFileSync7,
|
|
18
18
|
copyFileSync as copyFileSync5,
|
|
19
19
|
appendFileSync
|
|
20
20
|
} from "fs";
|
|
@@ -29,9 +29,7 @@ import {
|
|
|
29
29
|
toFps as toFps2
|
|
30
30
|
} from "@hyperframes/core";
|
|
31
31
|
import { HF_AUDIO_GROUP_TAG } from "@hyperframes/core/audio-groups";
|
|
32
|
-
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
33
32
|
import { HTML_BODY_CSS_HEIGHT_FIRST_RE, HTML_BODY_CSS_WIDTH_FIRST_RE } from "@hyperframes/parsers";
|
|
34
|
-
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
35
33
|
import {
|
|
36
34
|
extractMediaMetadata as extractMediaMetadata4,
|
|
37
35
|
resolveConfig as resolveConfig2,
|
|
@@ -62,7 +60,8 @@ import {
|
|
|
62
60
|
isDrawElementCaptureError,
|
|
63
61
|
getDrawElementVerificationDetails,
|
|
64
62
|
augmentProtocolTimeoutError,
|
|
65
|
-
augmentPageNavigationTimeoutError
|
|
63
|
+
augmentPageNavigationTimeoutError,
|
|
64
|
+
displaySize
|
|
66
65
|
} from "@hyperframes/engine";
|
|
67
66
|
import {
|
|
68
67
|
referenceBackgroundResult,
|
|
@@ -5895,8 +5894,11 @@ function countAuthoredTimedClips(html) {
|
|
|
5895
5894
|
}
|
|
5896
5895
|
|
|
5897
5896
|
// src/services/render/stages/compileStage.ts
|
|
5897
|
+
import { existsSync as existsSync11, writeFileSync as writeFileSync5 } from "node:fs";
|
|
5898
5898
|
import { join as join16 } from "node:path";
|
|
5899
5899
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
5900
|
+
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
5901
|
+
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
5900
5902
|
|
|
5901
5903
|
// src/services/htmlCompiler.ts
|
|
5902
5904
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
@@ -8604,11 +8606,36 @@ function adaptAspectAgnosticResolution(requested, aspectAgnostic, width, height,
|
|
|
8604
8606
|
});
|
|
8605
8607
|
return flipped;
|
|
8606
8608
|
}
|
|
8609
|
+
function resolveEntryHtmlPath(input) {
|
|
8610
|
+
const { projectDir, workDir, entryFile, log } = input;
|
|
8611
|
+
const htmlPath = join16(projectDir, entryFile);
|
|
8612
|
+
const rawEntry = readTextFile2(htmlPath);
|
|
8613
|
+
if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
|
|
8614
|
+
const projectIndexPath = join16(projectDir, "index.html");
|
|
8615
|
+
if (!existsSync11(projectIndexPath)) {
|
|
8616
|
+
throw new Error(
|
|
8617
|
+
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
8618
|
+
);
|
|
8619
|
+
}
|
|
8620
|
+
const standaloneHtml = extractStandaloneEntryFromIndex(
|
|
8621
|
+
readTextFile2(projectIndexPath),
|
|
8622
|
+
entryFile,
|
|
8623
|
+
rawEntry
|
|
8624
|
+
);
|
|
8625
|
+
if (!standaloneHtml) {
|
|
8626
|
+
throw new Error(
|
|
8627
|
+
`Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
|
|
8628
|
+
);
|
|
8629
|
+
}
|
|
8630
|
+
const wrapperPath = join16(workDir, "standalone-entry.html");
|
|
8631
|
+
writeFileSync5(wrapperPath, standaloneHtml, "utf-8");
|
|
8632
|
+
log.info("Extracted standalone entry from index.html host context", { entryFile });
|
|
8633
|
+
return wrapperPath;
|
|
8634
|
+
}
|
|
8607
8635
|
async function runCompileStage(input) {
|
|
8608
8636
|
const {
|
|
8609
8637
|
projectDir,
|
|
8610
8638
|
workDir,
|
|
8611
|
-
htmlPath,
|
|
8612
8639
|
entryFile,
|
|
8613
8640
|
job,
|
|
8614
8641
|
cfg,
|
|
@@ -8619,6 +8646,7 @@ async function runCompileStage(input) {
|
|
|
8619
8646
|
allowSystemFontCapture,
|
|
8620
8647
|
abortSignal
|
|
8621
8648
|
} = input;
|
|
8649
|
+
const htmlPath = resolveEntryHtmlPath({ projectDir, workDir, entryFile, log });
|
|
8622
8650
|
const compileStart = Date.now();
|
|
8623
8651
|
const compiled = await compileForRender(projectDir, htmlPath, join16(workDir, "downloads"), {
|
|
8624
8652
|
log,
|
|
@@ -9450,7 +9478,7 @@ function validateDistributedDuration(input) {
|
|
|
9450
9478
|
}
|
|
9451
9479
|
|
|
9452
9480
|
// src/services/render/stages/extractVideosStage.ts
|
|
9453
|
-
import { existsSync as
|
|
9481
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
9454
9482
|
import { join as join18 } from "node:path";
|
|
9455
9483
|
import {
|
|
9456
9484
|
classifyVideoExtractionError,
|
|
@@ -9725,7 +9753,7 @@ async function runExtractVideosStage(input) {
|
|
|
9725
9753
|
const probeFailures = await Promise.all(
|
|
9726
9754
|
composition.videos.map(async (v) => {
|
|
9727
9755
|
const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
|
|
9728
|
-
if (!
|
|
9756
|
+
if (!existsSync12(videoPath)) return null;
|
|
9729
9757
|
try {
|
|
9730
9758
|
const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry(() => extractMediaMetadata3(videoPath), {
|
|
9731
9759
|
signal: abortSignal,
|
|
@@ -9763,7 +9791,7 @@ async function runExtractVideosStage(input) {
|
|
|
9763
9791
|
const probed = await Promise.all(
|
|
9764
9792
|
composition.images.map(async (img) => {
|
|
9765
9793
|
const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
|
|
9766
|
-
if (!
|
|
9794
|
+
if (!existsSync12(imgPath)) return null;
|
|
9767
9795
|
const colorSpace = await probeColorSpaceSafely(imgPath, log);
|
|
9768
9796
|
if (isHdrColorSpace2(colorSpace)) {
|
|
9769
9797
|
nativeHdrImageIds.add(img.id);
|
|
@@ -9883,7 +9911,7 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
9883
9911
|
videos.map(async (video) => {
|
|
9884
9912
|
const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
|
|
9885
9913
|
const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
|
|
9886
|
-
if (!isHttpUrl2(readFrom) && !
|
|
9914
|
+
if (!isHttpUrl2(readFrom) && !existsSync12(readFrom)) return null;
|
|
9887
9915
|
const metadata = await extractMediaMetadata3(readFrom);
|
|
9888
9916
|
const window2 = await resolveFinalFrameExtractionWindow2(
|
|
9889
9917
|
readFrom,
|
|
@@ -9903,7 +9931,7 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
9903
9931
|
}
|
|
9904
9932
|
|
|
9905
9933
|
// src/services/render/videoLayerPassthroughPlan.ts
|
|
9906
|
-
import { existsSync as
|
|
9934
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
9907
9935
|
import {
|
|
9908
9936
|
buildVideoLayerInput,
|
|
9909
9937
|
isHttpUrl as isHttpUrl3,
|
|
@@ -9971,7 +9999,7 @@ async function planVideoLayerPassthrough(input) {
|
|
|
9971
9999
|
const { video } = q;
|
|
9972
10000
|
if (isHttpUrl3(video.src)) return { reason: "remote_source" };
|
|
9973
10001
|
const path = resolveProjectRelativeSrc4(video.src, input.projectDir, input.compiledDir);
|
|
9974
|
-
if (!
|
|
10002
|
+
if (!existsSync13(path)) return { reason: "source_missing" };
|
|
9975
10003
|
const layout = await probeVideoLayout(input.page, video.id, input.canvas, input.duration);
|
|
9976
10004
|
if ("reason" in layout) return layout;
|
|
9977
10005
|
const layer = await buildVideoLayerInput(
|
|
@@ -9986,6 +10014,7 @@ async function planVideoLayerPassthrough(input) {
|
|
|
9986
10014
|
}
|
|
9987
10015
|
|
|
9988
10016
|
// src/services/render/stages/audioStage.ts
|
|
10017
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
9989
10018
|
import { join as join21 } from "node:path";
|
|
9990
10019
|
import {
|
|
9991
10020
|
MIXED_AUDIO_FILENAME,
|
|
@@ -9996,7 +10025,7 @@ import {
|
|
|
9996
10025
|
} from "@hyperframes/engine";
|
|
9997
10026
|
|
|
9998
10027
|
// src/services/render/audioDeliveryLimit.ts
|
|
9999
|
-
import { existsSync as
|
|
10028
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
10000
10029
|
import { dirname as dirname9, join as join20 } from "node:path";
|
|
10001
10030
|
import {
|
|
10002
10031
|
applyAudioFxChain,
|
|
@@ -10119,8 +10148,9 @@ var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
|
|
|
10119
10148
|
var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
|
|
10120
10149
|
var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
|
|
10121
10150
|
var MAX_TRUE_PEAK_CORRECTION_PASSES = 3;
|
|
10151
|
+
var gainFilter = (gainDb) => `volume=${gainDb.toFixed(3)}dB`;
|
|
10122
10152
|
function aacTruePeakCorrectionFilter(targetDurationSeconds, attenuationDb) {
|
|
10123
|
-
return
|
|
10153
|
+
return `${gainFilter(attenuationDb)},apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
|
|
10124
10154
|
}
|
|
10125
10155
|
function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
|
|
10126
10156
|
const delta = targetDurationSeconds - sourceDurationSeconds;
|
|
@@ -10158,7 +10188,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10158
10188
|
const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
|
|
10159
10189
|
const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
|
|
10160
10190
|
const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
|
|
10161
|
-
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal);
|
|
10191
|
+
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath);
|
|
10162
10192
|
const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg || input.encodeAac ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
|
|
10163
10193
|
const [videoResult, audioResult] = await Promise.allSettled([
|
|
10164
10194
|
probeVideo(input.videoPath),
|
|
@@ -10206,7 +10236,12 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10206
10236
|
);
|
|
10207
10237
|
try {
|
|
10208
10238
|
for (const step of plan2.steps) {
|
|
10209
|
-
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
|
|
10239
|
+
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
|
|
10240
|
+
input.sourcePath ?? input.audioPath,
|
|
10241
|
+
input.outputPath,
|
|
10242
|
+
targetDurationSeconds,
|
|
10243
|
+
step.filter
|
|
10244
|
+
);
|
|
10210
10245
|
if (!ffmpegResult.success) {
|
|
10211
10246
|
return {
|
|
10212
10247
|
success: false,
|
|
@@ -10235,6 +10270,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10235
10270
|
if (probeTruePeak) {
|
|
10236
10271
|
const correction = await enforceAacTruePeak({
|
|
10237
10272
|
audioPath: input.outputPath,
|
|
10273
|
+
sourcePath: input.sourcePath,
|
|
10238
10274
|
targetDurationSeconds,
|
|
10239
10275
|
signal: input.signal,
|
|
10240
10276
|
probeTruePeak,
|
|
@@ -10270,9 +10306,27 @@ async function constrainAacTruePeak(input) {
|
|
|
10270
10306
|
targetDurationSeconds: input.targetDurationSeconds,
|
|
10271
10307
|
signal: input.signal,
|
|
10272
10308
|
probeTruePeak: input.probeTruePeakDbfs ?? ((audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal })),
|
|
10273
|
-
encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
|
|
10309
|
+
encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath),
|
|
10310
|
+
rewriteSource: input.rewriteSource ?? ((sourcePath, outputPath, filter) => defaultRunFfmpeg(
|
|
10311
|
+
["-i", sourcePath, "-af", filter, "-c:a", "pcm_f32le", "-y", outputPath],
|
|
10312
|
+
timeoutMs,
|
|
10313
|
+
input.signal
|
|
10314
|
+
))
|
|
10274
10315
|
});
|
|
10275
10316
|
}
|
|
10317
|
+
async function cutSource(input, scratchDir, attenuationDb) {
|
|
10318
|
+
if (!input.sourcePath || !input.rewriteSource) return true;
|
|
10319
|
+
const cutPath = join19(scratchDir, "source.wav");
|
|
10320
|
+
try {
|
|
10321
|
+
const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
|
|
10322
|
+
if (!result.success) throw new Error(result.error);
|
|
10323
|
+
renameWithRetry5(cutPath, input.sourcePath, { copyFallback: true });
|
|
10324
|
+
return true;
|
|
10325
|
+
} catch {
|
|
10326
|
+
removeScratch(input.sourcePath);
|
|
10327
|
+
return false;
|
|
10328
|
+
}
|
|
10329
|
+
}
|
|
10276
10330
|
async function enforceAacTruePeak(input) {
|
|
10277
10331
|
let scratchDir;
|
|
10278
10332
|
try {
|
|
@@ -10292,8 +10346,10 @@ async function enforceAacTruePeak(input) {
|
|
|
10292
10346
|
}
|
|
10293
10347
|
if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
|
|
10294
10348
|
if (measuredPath !== correctedPath) return { success: true };
|
|
10349
|
+
const sourceCut = await cutSource(input, scratchDir, attenuationDb);
|
|
10295
10350
|
renameWithRetry5(correctedPath, input.audioPath, { copyFallback: true });
|
|
10296
|
-
|
|
10351
|
+
const loweredDb = Number((-attenuationDb).toFixed(2));
|
|
10352
|
+
return sourceCut ? { success: true, loweredDb } : { success: true, loweredDb, sourceStale: true };
|
|
10297
10353
|
}
|
|
10298
10354
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
10299
10355
|
const headroomDb = AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB * 2 ** pass;
|
|
@@ -10303,7 +10359,7 @@ async function enforceAacTruePeak(input) {
|
|
|
10303
10359
|
const referenceDbfs = Math.max(truePeakDbfs, (sourcePeakDbfs ?? -Infinity) + attenuationDb);
|
|
10304
10360
|
attenuationDb += AAC_DELIVERY_TRUE_PEAK_DBFS - headroomDb - referenceDbfs;
|
|
10305
10361
|
const result = await input.encode(
|
|
10306
|
-
input.audioPath,
|
|
10362
|
+
input.sourcePath ?? input.audioPath,
|
|
10307
10363
|
correctedPath,
|
|
10308
10364
|
input.targetDurationSeconds,
|
|
10309
10365
|
aacTruePeakCorrectionFilter(input.targetDurationSeconds, attenuationDb)
|
|
@@ -10476,7 +10532,7 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
10476
10532
|
applyChain: applyAudioFxChain,
|
|
10477
10533
|
encode: encodeAac2,
|
|
10478
10534
|
rename: renameWithRetry6,
|
|
10479
|
-
sourceExists:
|
|
10535
|
+
sourceExists: existsSync14
|
|
10480
10536
|
}) {
|
|
10481
10537
|
const { audioPath, sourcePath, signal, timeoutMs } = input;
|
|
10482
10538
|
if (!deps.sourceExists(sourcePath)) return { success: true };
|
|
@@ -10532,36 +10588,54 @@ async function runAudioStageScoped(input) {
|
|
|
10532
10588
|
const stageStart = Date.now();
|
|
10533
10589
|
const pcmSourcePath = join21(input.workDir, "audio-source.wav");
|
|
10534
10590
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
10591
|
+
let deliverySourcePath;
|
|
10535
10592
|
try {
|
|
10536
10593
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
10537
10594
|
let audioLoweredDb;
|
|
10538
10595
|
if (result.hasAudio) {
|
|
10539
10596
|
const guardStart = Date.now();
|
|
10540
|
-
const
|
|
10541
|
-
|
|
10542
|
-
|
|
10543
|
-
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
});
|
|
10548
|
-
if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
|
|
10549
|
-
const peak = await constrainAacTruePeak({
|
|
10550
|
-
audioPath: result.audioOutputPath,
|
|
10551
|
-
sourcePath: pcmSourcePath,
|
|
10552
|
-
targetDurationSeconds: duration,
|
|
10553
|
-
signal: input.abortSignal,
|
|
10554
|
-
timeoutMs: input.ffmpegProcessTimeout
|
|
10555
|
-
});
|
|
10556
|
-
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
10557
|
-
audioLoweredDb = peak.loweredDb;
|
|
10597
|
+
const guard = await guardDeliveryPeaks(
|
|
10598
|
+
input,
|
|
10599
|
+
result.audioOutputPath,
|
|
10600
|
+
pcmSourcePath,
|
|
10601
|
+
duration
|
|
10602
|
+
);
|
|
10603
|
+
audioLoweredDb = guard.loweredDb;
|
|
10558
10604
|
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
10605
|
+
if (input.keepDeliverySource && !guard.sourceStale && existsSync15(pcmSourcePath)) {
|
|
10606
|
+
deliverySourcePath = pcmSourcePath;
|
|
10607
|
+
}
|
|
10559
10608
|
}
|
|
10560
|
-
return {
|
|
10609
|
+
return {
|
|
10610
|
+
...result,
|
|
10611
|
+
audioLoweredDb,
|
|
10612
|
+
deliverySourcePath,
|
|
10613
|
+
audioProcessMs: Date.now() - stageStart
|
|
10614
|
+
};
|
|
10561
10615
|
} finally {
|
|
10562
|
-
removeScratch2(pcmSourcePath);
|
|
10616
|
+
if (!deliverySourcePath) removeScratch2(pcmSourcePath);
|
|
10563
10617
|
}
|
|
10564
10618
|
}
|
|
10619
|
+
async function guardDeliveryPeaks(input, audioPath, sourcePath, duration) {
|
|
10620
|
+
const limited = await limitDeliveryPeaks({
|
|
10621
|
+
audioPath,
|
|
10622
|
+
sourcePath,
|
|
10623
|
+
targetDurationSeconds: duration,
|
|
10624
|
+
signal: input.abortSignal,
|
|
10625
|
+
timeoutMs: input.ffmpegProcessTimeout,
|
|
10626
|
+
warn: (message, details) => input.log?.warn?.(message, details)
|
|
10627
|
+
});
|
|
10628
|
+
if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
|
|
10629
|
+
const peak = await constrainAacTruePeak({
|
|
10630
|
+
audioPath,
|
|
10631
|
+
sourcePath,
|
|
10632
|
+
targetDurationSeconds: duration,
|
|
10633
|
+
signal: input.abortSignal,
|
|
10634
|
+
timeoutMs: input.ffmpegProcessTimeout
|
|
10635
|
+
});
|
|
10636
|
+
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
10637
|
+
return { loweredDb: peak.loweredDb, sourceStale: peak.sourceStale };
|
|
10638
|
+
}
|
|
10565
10639
|
async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
10566
10640
|
const {
|
|
10567
10641
|
projectDir,
|
|
@@ -10656,7 +10730,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
10656
10730
|
}
|
|
10657
10731
|
|
|
10658
10732
|
// src/services/render/stages/captureStage.ts
|
|
10659
|
-
import { existsSync as
|
|
10733
|
+
import { existsSync as existsSync16, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
|
|
10660
10734
|
import { join as join22 } from "node:path";
|
|
10661
10735
|
import {
|
|
10662
10736
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
@@ -10727,7 +10801,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
10727
10801
|
}
|
|
10728
10802
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
10729
10803
|
let total = sampleDirectoryBytes(framesDir);
|
|
10730
|
-
if (!
|
|
10804
|
+
if (!existsSync16(workDir)) return total;
|
|
10731
10805
|
for (const attempt of readdirSync3(workDir)) {
|
|
10732
10806
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
10733
10807
|
total += sampleDirectoryBytes(join22(workDir, attempt));
|
|
@@ -11030,7 +11104,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
|
|
|
11030
11104
|
}
|
|
11031
11105
|
|
|
11032
11106
|
// src/services/render/stages/captureHdrStage.ts
|
|
11033
|
-
import { existsSync as
|
|
11107
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync8 } from "node:fs";
|
|
11034
11108
|
import { join as join26 } from "node:path";
|
|
11035
11109
|
import {
|
|
11036
11110
|
closeCaptureSession as closeCaptureSession7,
|
|
@@ -11276,7 +11350,7 @@ import { Worker } from "node:worker_threads";
|
|
|
11276
11350
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
11277
11351
|
import { dirname as dirname10, join as join24 } from "node:path";
|
|
11278
11352
|
import { createRequire } from "node:module";
|
|
11279
|
-
import { existsSync as
|
|
11353
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
11280
11354
|
import { cpus as cpus2 } from "node:os";
|
|
11281
11355
|
function resolveWorkerEntry(explicit) {
|
|
11282
11356
|
if (explicit && explicit.length > 0) {
|
|
@@ -11289,7 +11363,7 @@ function resolveWorkerEntry(explicit) {
|
|
|
11289
11363
|
}
|
|
11290
11364
|
const moduleDir = dirname10(fileURLToPath3(import.meta.url));
|
|
11291
11365
|
const jsPath = join24(moduleDir, "shaderTransitionWorker.js");
|
|
11292
|
-
if (
|
|
11366
|
+
if (existsSync17(jsPath)) return { path: jsPath, isTs: false };
|
|
11293
11367
|
const tsPath = join24(moduleDir, "shaderTransitionWorker.ts");
|
|
11294
11368
|
return { path: tsPath, isTs: true };
|
|
11295
11369
|
}
|
|
@@ -11850,7 +11924,7 @@ async function runCaptureHdrStage(input) {
|
|
|
11850
11924
|
}
|
|
11851
11925
|
const debugDumpEnabled = process.env.KEEP_TEMP === "1";
|
|
11852
11926
|
const debugDumpDir = debugDumpEnabled ? join26(framesDir, "debug-composite") : null;
|
|
11853
|
-
if (debugDumpDir && !
|
|
11927
|
+
if (debugDumpDir && !existsSync18(debugDumpDir)) {
|
|
11854
11928
|
mkdirSync8(debugDumpDir, { recursive: true });
|
|
11855
11929
|
}
|
|
11856
11930
|
const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
|
|
@@ -12007,13 +12081,13 @@ async function runCaptureHdrStage(input) {
|
|
|
12007
12081
|
// src/services/render/stages/encodeStage.ts
|
|
12008
12082
|
import {
|
|
12009
12083
|
copyFileSync as copyFileSync4,
|
|
12010
|
-
existsSync as
|
|
12084
|
+
existsSync as existsSync19,
|
|
12011
12085
|
mkdirSync as mkdirSync9,
|
|
12012
12086
|
readdirSync as readdirSync4,
|
|
12013
12087
|
readFileSync as readFileSync8,
|
|
12014
12088
|
rmSync as rmSync9,
|
|
12015
12089
|
statSync as statSync7,
|
|
12016
|
-
writeFileSync as
|
|
12090
|
+
writeFileSync as writeFileSync6
|
|
12017
12091
|
} from "node:fs";
|
|
12018
12092
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
12019
12093
|
import { dirname as dirname11, join as join27 } from "node:path";
|
|
@@ -12109,7 +12183,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
|
12109
12183
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
12110
12184
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
12111
12185
|
}
|
|
12112
|
-
|
|
12186
|
+
writeFileSync6(outputPath, gif);
|
|
12113
12187
|
return null;
|
|
12114
12188
|
}
|
|
12115
12189
|
async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
@@ -12141,7 +12215,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
12141
12215
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
12142
12216
|
if (failure) return failure;
|
|
12143
12217
|
}
|
|
12144
|
-
const fileSize =
|
|
12218
|
+
const fileSize = existsSync19(outputPath) ? statSync7(outputPath).size : 0;
|
|
12145
12219
|
return {
|
|
12146
12220
|
success: true,
|
|
12147
12221
|
outputPath,
|
|
@@ -12302,7 +12376,7 @@ async function runEncodeStage(input) {
|
|
|
12302
12376
|
const stage5Start = Date.now();
|
|
12303
12377
|
if (isPngSequence) {
|
|
12304
12378
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
12305
|
-
if (!
|
|
12379
|
+
if (!existsSync19(outputPath)) mkdirSync9(outputPath, { recursive: true });
|
|
12306
12380
|
const captured = readdirSync4(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
12307
12381
|
if (captured.length === 0) {
|
|
12308
12382
|
throw new Error(
|
|
@@ -12313,7 +12387,7 @@ async function runEncodeStage(input) {
|
|
|
12313
12387
|
const dst = join27(outputPath, formatExportFrameName(i, "png"));
|
|
12314
12388
|
copyFileSync4(join27(framesDir, name), dst);
|
|
12315
12389
|
});
|
|
12316
|
-
if (hasAudio && audioOutputPath &&
|
|
12390
|
+
if (hasAudio && audioOutputPath && existsSync19(audioOutputPath)) {
|
|
12317
12391
|
copyFileSync4(audioOutputPath, join27(outputPath, MIXED_AUDIO_FILENAME2));
|
|
12318
12392
|
log.info(
|
|
12319
12393
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
@@ -12424,6 +12498,7 @@ async function assembleOutput(input, onSecondsWritten) {
|
|
|
12424
12498
|
const normalizeResult = await padOrTrimAudioToVideoFrameCount({
|
|
12425
12499
|
videoPath: videoOnlyPath,
|
|
12426
12500
|
audioPath: audioOutputPath,
|
|
12501
|
+
sourcePath: input.audioPcmPath,
|
|
12427
12502
|
outputPath: normalizedAudioPath,
|
|
12428
12503
|
signal: abortSignal,
|
|
12429
12504
|
timeoutMs: ffmpegProcessTimeout
|
|
@@ -12694,18 +12769,14 @@ function hasUsableVideoDimensions(metadata) {
|
|
|
12694
12769
|
return Number.isFinite(metadata.width) && Number.isFinite(metadata.height) && metadata.width > 0 && metadata.height > 0;
|
|
12695
12770
|
}
|
|
12696
12771
|
function collectVideoMetadataHints(extractedVideos) {
|
|
12697
|
-
return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({
|
|
12698
|
-
id: video.videoId,
|
|
12699
|
-
width: video.metadata.width,
|
|
12700
|
-
height: video.metadata.height
|
|
12701
|
-
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
12772
|
+
return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({ id: video.videoId, ...displaySize(video.metadata) })).sort((a, b) => a.id.localeCompare(b.id));
|
|
12702
12773
|
}
|
|
12703
12774
|
function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
12704
12775
|
const ranges = [];
|
|
12705
12776
|
let rangeStart = null;
|
|
12706
12777
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12707
12778
|
const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12708
|
-
const missing = !
|
|
12779
|
+
const missing = !existsSync20(framePath) || statSync8(framePath).size <= 8;
|
|
12709
12780
|
if (missing && rangeStart === null) {
|
|
12710
12781
|
rangeStart = frameIndex;
|
|
12711
12782
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -12830,7 +12901,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
|
12830
12901
|
let captured = 0;
|
|
12831
12902
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12832
12903
|
const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12833
|
-
if (
|
|
12904
|
+
if (existsSync20(framePath)) captured++;
|
|
12834
12905
|
}
|
|
12835
12906
|
return captured;
|
|
12836
12907
|
}
|
|
@@ -13495,7 +13566,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
13495
13566
|
validateDeliveryEncoderConfig(job.config);
|
|
13496
13567
|
const debugDir = resolveRenderDebugDir();
|
|
13497
13568
|
const outputDir = dirname12(outputPath);
|
|
13498
|
-
if (!
|
|
13569
|
+
if (!existsSync20(outputDir)) mkdirSync10(outputDir, { recursive: true });
|
|
13499
13570
|
const workDir = job.config.debug ? join28(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
|
|
13500
13571
|
const pipelineStart = Date.now();
|
|
13501
13572
|
const baseLog = job.config.logger ?? defaultLogger;
|
|
@@ -13637,7 +13708,7 @@ async function executeRenderPipeline(input) {
|
|
|
13637
13708
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
13638
13709
|
assertNotAborted();
|
|
13639
13710
|
assertConfiguredFfmpegBinariesExist();
|
|
13640
|
-
if (!
|
|
13711
|
+
if (!existsSync20(workDir)) mkdirSync10(workDir, { recursive: true });
|
|
13641
13712
|
if (job.config.debug) {
|
|
13642
13713
|
log.info("[Render] Debug artifacts enabled", { workDir, logPath });
|
|
13643
13714
|
}
|
|
@@ -13666,36 +13737,11 @@ async function executeRenderPipeline(input) {
|
|
|
13666
13737
|
requestedWorkers: job.config.workers ?? "auto"
|
|
13667
13738
|
});
|
|
13668
13739
|
const entryFile = job.config.entryFile || "index.html";
|
|
13669
|
-
|
|
13670
|
-
if (!
|
|
13740
|
+
const htmlPath = join28(projectDir, entryFile);
|
|
13741
|
+
if (!existsSync20(htmlPath)) {
|
|
13671
13742
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
13672
13743
|
}
|
|
13673
13744
|
assertNotAborted();
|
|
13674
|
-
const rawEntry = readTextFile2(htmlPath);
|
|
13675
|
-
if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
|
|
13676
|
-
const wrapperPath = join28(workDir, "standalone-entry.html");
|
|
13677
|
-
const projectIndexPath = join28(projectDir, "index.html");
|
|
13678
|
-
if (!existsSync18(projectIndexPath)) {
|
|
13679
|
-
throw new Error(
|
|
13680
|
-
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
13681
|
-
);
|
|
13682
|
-
}
|
|
13683
|
-
const standaloneHtml = extractStandaloneEntryFromIndex(
|
|
13684
|
-
readTextFile2(projectIndexPath),
|
|
13685
|
-
entryFile,
|
|
13686
|
-
rawEntry
|
|
13687
|
-
);
|
|
13688
|
-
if (!standaloneHtml) {
|
|
13689
|
-
throw new Error(
|
|
13690
|
-
`Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
|
|
13691
|
-
);
|
|
13692
|
-
}
|
|
13693
|
-
writeFileSync6(wrapperPath, standaloneHtml, "utf-8");
|
|
13694
|
-
htmlPath = wrapperPath;
|
|
13695
|
-
log.info("Extracted standalone entry from index.html host context", {
|
|
13696
|
-
entryFile
|
|
13697
|
-
});
|
|
13698
|
-
}
|
|
13699
13745
|
const stage1Start = Date.now();
|
|
13700
13746
|
updateJobStatus(job, "preprocessing", "Compiling composition", 5, onProgress, {
|
|
13701
13747
|
code: "compile"
|
|
@@ -13707,7 +13753,6 @@ async function executeRenderPipeline(input) {
|
|
|
13707
13753
|
() => runCompileStage({
|
|
13708
13754
|
projectDir,
|
|
13709
13755
|
workDir,
|
|
13710
|
-
htmlPath,
|
|
13711
13756
|
entryFile,
|
|
13712
13757
|
job,
|
|
13713
13758
|
cfg,
|
|
@@ -13986,7 +14031,8 @@ async function executeRenderPipeline(input) {
|
|
|
13986
14031
|
masterFxChain: composition.masterFxChain,
|
|
13987
14032
|
abortSignal: executionSignal,
|
|
13988
14033
|
assertNotAborted,
|
|
13989
|
-
log
|
|
14034
|
+
log,
|
|
14035
|
+
keepDeliverySource: true
|
|
13990
14036
|
})
|
|
13991
14037
|
);
|
|
13992
14038
|
pendingAudio = audioStage.catch(() => void 0);
|
|
@@ -14018,7 +14064,7 @@ async function executeRenderPipeline(input) {
|
|
|
14018
14064
|
throw new Error("File server failed to initialize before frame capture");
|
|
14019
14065
|
}
|
|
14020
14066
|
const framesDir = join28(workDir, "captured-frames");
|
|
14021
|
-
if (!
|
|
14067
|
+
if (!existsSync20(framesDir)) mkdirSync10(framesDir, { recursive: true });
|
|
14022
14068
|
updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
|
|
14023
14069
|
code: "start_browsers"
|
|
14024
14070
|
});
|
|
@@ -14694,7 +14740,7 @@ async function executeRenderPipeline(input) {
|
|
|
14694
14740
|
const effectiveQuality = job.config.crf ?? preset.quality;
|
|
14695
14741
|
const effectiveBitrate = job.config.crf != null ? void 0 : job.config.videoBitrate;
|
|
14696
14742
|
const audioResult = await audioStage;
|
|
14697
|
-
const { audioOutputPath, hasAudio } = audioResult;
|
|
14743
|
+
const { audioOutputPath, deliverySourcePath, hasAudio } = audioResult;
|
|
14698
14744
|
perfStages.audioProcessMs = audioResult.audioProcessMs;
|
|
14699
14745
|
recordLimiterAttenuation(job, audioResult.audioLoweredDb);
|
|
14700
14746
|
if (audioResult.audioError) {
|
|
@@ -15320,6 +15366,7 @@ async function executeRenderPipeline(input) {
|
|
|
15320
15366
|
job,
|
|
15321
15367
|
videoOnlyPath,
|
|
15322
15368
|
audioOutputPath,
|
|
15369
|
+
audioPcmPath: deliverySourcePath,
|
|
15323
15370
|
outputPath: stagedOutputPath,
|
|
15324
15371
|
hasAudio,
|
|
15325
15372
|
format: outputFormat,
|
|
@@ -15345,7 +15392,7 @@ async function executeRenderPipeline(input) {
|
|
|
15345
15392
|
})
|
|
15346
15393
|
);
|
|
15347
15394
|
const totalElapsed = Date.now() - pipelineStart;
|
|
15348
|
-
const tmpPeakBytes =
|
|
15395
|
+
const tmpPeakBytes = existsSync20(workDir) ? sampleDirectoryBytes(workDir) : 0;
|
|
15349
15396
|
recordTransientRetryObservability();
|
|
15350
15397
|
observability.checkpoint("pipeline", "artifact validated", { totalElapsedMs: totalElapsed });
|
|
15351
15398
|
const observabilitySummary = observability.summary({
|
|
@@ -15417,7 +15464,7 @@ async function executeRenderPipeline(input) {
|
|
|
15417
15464
|
job.perfSummary = perfSummary;
|
|
15418
15465
|
if (job.config.debug) {
|
|
15419
15466
|
try {
|
|
15420
|
-
|
|
15467
|
+
writeFileSync7(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
|
|
15421
15468
|
} catch (err) {
|
|
15422
15469
|
log.debug("Failed to write perf summary", {
|
|
15423
15470
|
perfOutputPath,
|
|
@@ -15426,7 +15473,7 @@ async function executeRenderPipeline(input) {
|
|
|
15426
15473
|
}
|
|
15427
15474
|
}
|
|
15428
15475
|
if (job.config.debug) {
|
|
15429
|
-
if (!isPngSequence && !isHls &&
|
|
15476
|
+
if (!isPngSequence && !isHls && existsSync20(stagedOutputPath)) {
|
|
15430
15477
|
const debugOutput = join28(workDir, `output${videoExt}`);
|
|
15431
15478
|
copyFileSync5(stagedOutputPath, debugOutput);
|
|
15432
15479
|
}
|
|
@@ -15938,11 +15985,11 @@ import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframe
|
|
|
15938
15985
|
import {
|
|
15939
15986
|
copyFileSync as copyFileSync6,
|
|
15940
15987
|
cpSync as cpSync2,
|
|
15941
|
-
existsSync as
|
|
15988
|
+
existsSync as existsSync25,
|
|
15942
15989
|
linkSync,
|
|
15943
15990
|
mkdirSync as mkdirSync14,
|
|
15944
15991
|
rmSync as rmSync12,
|
|
15945
|
-
writeFileSync as
|
|
15992
|
+
writeFileSync as writeFileSync10
|
|
15946
15993
|
} from "node:fs";
|
|
15947
15994
|
import { extname as extname6, isAbsolute as isAbsolute3, join as join37, relative as relative5, sep as sep2 } from "node:path";
|
|
15948
15995
|
import {
|
|
@@ -15969,7 +16016,7 @@ import {
|
|
|
15969
16016
|
} from "@hyperframes/engine";
|
|
15970
16017
|
|
|
15971
16018
|
// src/services/distributed/chunkFrames.ts
|
|
15972
|
-
import { existsSync as
|
|
16019
|
+
import { existsSync as existsSync22, readdirSync as readdirSync6 } from "node:fs";
|
|
15973
16020
|
import { extname as extname4, join as join31 } from "node:path";
|
|
15974
16021
|
import {
|
|
15975
16022
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
@@ -15981,7 +16028,7 @@ import {
|
|
|
15981
16028
|
// src/services/distributed/shared.ts
|
|
15982
16029
|
import { execFile as execFileCallback } from "node:child_process";
|
|
15983
16030
|
import { dirname as dirname14, join as join30 } from "node:path";
|
|
15984
|
-
import { existsSync as
|
|
16031
|
+
import { existsSync as existsSync21, readFileSync as readFileSync9 } from "node:fs";
|
|
15985
16032
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
15986
16033
|
import { promisify as promisify2 } from "node:util";
|
|
15987
16034
|
import {
|
|
@@ -15997,7 +16044,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
15997
16044
|
function resolvePlanAudioPath(planDir) {
|
|
15998
16045
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
15999
16046
|
const candidate = join30(planDir, name);
|
|
16000
|
-
if (
|
|
16047
|
+
if (existsSync21(candidate)) return candidate;
|
|
16001
16048
|
}
|
|
16002
16049
|
return null;
|
|
16003
16050
|
}
|
|
@@ -16258,7 +16305,7 @@ function readProducerVersion() {
|
|
|
16258
16305
|
let current = startDir;
|
|
16259
16306
|
for (let i = 0; i < 10; i++) {
|
|
16260
16307
|
const candidate = join30(current, "package.json");
|
|
16261
|
-
if (
|
|
16308
|
+
if (existsSync21(candidate)) {
|
|
16262
16309
|
try {
|
|
16263
16310
|
const pkg = JSON.parse(readFileSync9(candidate, "utf-8"));
|
|
16264
16311
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
@@ -16312,7 +16359,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
16312
16359
|
const result = [];
|
|
16313
16360
|
for (const v of videos) {
|
|
16314
16361
|
const outputDir = join31(planDir, "video-frames", v.videoId);
|
|
16315
|
-
if (!
|
|
16362
|
+
if (!existsSync22(outputDir)) {
|
|
16316
16363
|
throw new Error(
|
|
16317
16364
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
16318
16365
|
);
|
|
@@ -16428,7 +16475,7 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
16428
16475
|
const range = video.deferredRange;
|
|
16429
16476
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
16430
16477
|
const sourcePath = remoteUrl ?? join31(input.planDir, ...range.sourcePath.split("/"));
|
|
16431
|
-
if (remoteUrl === void 0 && !
|
|
16478
|
+
if (remoteUrl === void 0 && !existsSync22(sourcePath)) {
|
|
16432
16479
|
throw new RenderChunkValidationError(
|
|
16433
16480
|
MISSING_PLAN_ARTIFACT,
|
|
16434
16481
|
`[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
|
|
@@ -16646,7 +16693,7 @@ async function readPlanReferenceBackground(input) {
|
|
|
16646
16693
|
}
|
|
16647
16694
|
|
|
16648
16695
|
// src/services/render/stages/freezePlan.ts
|
|
16649
|
-
import { existsSync as
|
|
16696
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync12, readFileSync as readFileSync10, readdirSync as readdirSync7, writeFileSync as writeFileSync8 } from "node:fs";
|
|
16650
16697
|
import { join as join34, relative as relative3, resolve as resolve9 } from "node:path";
|
|
16651
16698
|
import {
|
|
16652
16699
|
REFERENCE_BACKGROUND_FIELD,
|
|
@@ -16840,7 +16887,7 @@ function planFileSha256(absolutePath, digests) {
|
|
|
16840
16887
|
return sha256;
|
|
16841
16888
|
}
|
|
16842
16889
|
async function prehashPlanFiles(dir, digests) {
|
|
16843
|
-
if (!
|
|
16890
|
+
if (!existsSync23(dir)) return;
|
|
16844
16891
|
for (const { absolutePath } of listPlanFiles(dir)) {
|
|
16845
16892
|
if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
|
|
16846
16893
|
}
|
|
@@ -16906,19 +16953,19 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
|
|
|
16906
16953
|
function writePlanRemoteFiles(planDir, files) {
|
|
16907
16954
|
if (files.length === 0) return;
|
|
16908
16955
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
16909
|
-
|
|
16956
|
+
writeFileSync8(join34(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
|
|
16910
16957
|
}
|
|
16911
16958
|
function readPlanRemoteFiles(planDir) {
|
|
16912
16959
|
const path = join34(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
16913
|
-
return
|
|
16960
|
+
return existsSync23(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
|
|
16914
16961
|
}
|
|
16915
16962
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
16916
16963
|
const planJsonPath = join34(planDir, "plan.json");
|
|
16917
16964
|
const encoderJsonPath = join34(planDir, "meta", "encoder.json");
|
|
16918
|
-
if (!
|
|
16965
|
+
if (!existsSync23(planJsonPath)) {
|
|
16919
16966
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
16920
16967
|
}
|
|
16921
|
-
if (!
|
|
16968
|
+
if (!existsSync23(encoderJsonPath)) {
|
|
16922
16969
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
16923
16970
|
}
|
|
16924
16971
|
const planJson = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
|
|
@@ -16948,12 +16995,12 @@ async function freezePlan(input) {
|
|
|
16948
16995
|
hasAudio,
|
|
16949
16996
|
digests
|
|
16950
16997
|
} = input;
|
|
16951
|
-
if (!
|
|
16998
|
+
if (!existsSync23(planDir)) {
|
|
16952
16999
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
16953
17000
|
}
|
|
16954
17001
|
const metaDir = join34(planDir, "meta");
|
|
16955
|
-
if (!
|
|
16956
|
-
|
|
17002
|
+
if (!existsSync23(metaDir)) mkdirSync12(metaDir, { recursive: true });
|
|
17003
|
+
writeFileSync8(
|
|
16957
17004
|
join34(metaDir, "composition.json"),
|
|
16958
17005
|
`${JSON.stringify(composition, null, 2)}
|
|
16959
17006
|
`,
|
|
@@ -16961,8 +17008,8 @@ async function freezePlan(input) {
|
|
|
16961
17008
|
);
|
|
16962
17009
|
const encoderForCanonical = stripUndefined(encoder);
|
|
16963
17010
|
const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
|
|
16964
|
-
|
|
16965
|
-
|
|
17011
|
+
writeFileSync8(join34(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
|
|
17012
|
+
writeFileSync8(join34(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
|
|
16966
17013
|
`, "utf-8");
|
|
16967
17014
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
16968
17015
|
const planHash = computePlanHash({
|
|
@@ -16988,7 +17035,7 @@ async function freezePlan(input) {
|
|
|
16988
17035
|
hasAudio
|
|
16989
17036
|
};
|
|
16990
17037
|
const planJsonPath = join34(planDir, "plan.json");
|
|
16991
|
-
|
|
17038
|
+
writeFileSync8(planJsonPath, `${JSON.stringify(planJson, null, 2)}
|
|
16992
17039
|
`, "utf-8");
|
|
16993
17040
|
return { planJsonPath, planHash };
|
|
16994
17041
|
}
|
|
@@ -17029,8 +17076,8 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
17029
17076
|
}
|
|
17030
17077
|
|
|
17031
17078
|
// src/services/distributed/chunkAudio.ts
|
|
17032
|
-
import { closeSync as closeSync6, existsSync as
|
|
17033
|
-
import { writeFileSync as
|
|
17079
|
+
import { closeSync as closeSync6, existsSync as existsSync24, ftruncateSync, mkdirSync as mkdirSync13, openSync as openSync6, readFileSync as readFileSync11 } from "node:fs";
|
|
17080
|
+
import { writeFileSync as writeFileSync9, writeSync } from "node:fs";
|
|
17034
17081
|
import { join as join35 } from "node:path";
|
|
17035
17082
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
17036
17083
|
function audioCutForChunk(grid, chunks, index, fps) {
|
|
@@ -17127,15 +17174,15 @@ async function writeChunkAudioSlices(input) {
|
|
|
17127
17174
|
const start = offsets[cut.firstPacket];
|
|
17128
17175
|
layout.partStarts[index] = start;
|
|
17129
17176
|
const end = offsets[last] + track.sizes[last];
|
|
17130
|
-
|
|
17177
|
+
writeFileSync9(join35(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
|
|
17131
17178
|
});
|
|
17132
17179
|
const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
|
|
17133
|
-
|
|
17134
|
-
|
|
17180
|
+
writeFileSync9(join35(input.planDir, SLICE_FRAME), Buffer.concat(frame));
|
|
17181
|
+
writeFileSync9(join35(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
|
|
17135
17182
|
}
|
|
17136
17183
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
17137
17184
|
const layoutPath = join35(planDir, SLICE_LAYOUT);
|
|
17138
|
-
if (!
|
|
17185
|
+
if (!existsSync24(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
17139
17186
|
const layout = JSON.parse(readFileSync11(layoutPath, "utf-8"));
|
|
17140
17187
|
const frame = readFileSync11(join35(planDir, SLICE_FRAME));
|
|
17141
17188
|
const fd = openSync6(scratchPath, "w");
|
|
@@ -17695,7 +17742,7 @@ function createVideoSourceShipper(input) {
|
|
|
17695
17742
|
let result = null;
|
|
17696
17743
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
17697
17744
|
const rel = pathInside(root, srcPath);
|
|
17698
|
-
if (rel !== null && (
|
|
17745
|
+
if (rel !== null && (existsSync25(join37(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
17699
17746
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
17700
17747
|
break;
|
|
17701
17748
|
}
|
|
@@ -17723,7 +17770,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
17723
17770
|
useGpu: false,
|
|
17724
17771
|
browserGpuMode: "software"
|
|
17725
17772
|
});
|
|
17726
|
-
if (!
|
|
17773
|
+
if (!existsSync25(planDir)) mkdirSync14(planDir, { recursive: true });
|
|
17727
17774
|
const log = config.logger ?? defaultLogger;
|
|
17728
17775
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
17729
17776
|
const abortSignal = config.abortSignal;
|
|
@@ -17753,7 +17800,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
17753
17800
|
});
|
|
17754
17801
|
const entryFile = config.entryFile ?? "index.html";
|
|
17755
17802
|
const htmlPath = join37(projectDir, entryFile);
|
|
17756
|
-
if (!
|
|
17803
|
+
if (!existsSync25(htmlPath)) {
|
|
17757
17804
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
17758
17805
|
}
|
|
17759
17806
|
const workDir = join37(planDir, ".plan-work");
|
|
@@ -17797,7 +17844,6 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
17797
17844
|
urlByAbsentLocalPath,
|
|
17798
17845
|
projectDir,
|
|
17799
17846
|
workDir,
|
|
17800
|
-
htmlPath,
|
|
17801
17847
|
entryFile,
|
|
17802
17848
|
job,
|
|
17803
17849
|
cfg,
|
|
@@ -17982,15 +18028,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
17982
18028
|
logPlanStage(log, "extract", stageStarted);
|
|
17983
18029
|
const stagedVideoFrames = join37(compiledDir, "__hyperframes_video_frames");
|
|
17984
18030
|
const videoFramesDst = join37(planDir, "video-frames");
|
|
17985
|
-
if (
|
|
18031
|
+
if (existsSync25(videoFramesDst)) rmSync12(videoFramesDst, { recursive: true, force: true });
|
|
17986
18032
|
const videoSourcesDir = join37(planDir, "video-sources");
|
|
17987
18033
|
rmSync12(videoSourcesDir, { recursive: true, force: true });
|
|
17988
|
-
if (
|
|
18034
|
+
if (existsSync25(stagedVideoFrames)) {
|
|
17989
18035
|
renameWithRetry8(stagedVideoFrames, videoFramesDst);
|
|
17990
18036
|
} else {
|
|
17991
18037
|
mkdirSync14(videoFramesDst, { recursive: true });
|
|
17992
18038
|
}
|
|
17993
|
-
if (
|
|
18039
|
+
if (existsSync25(finalCompiledDir)) rmSync12(finalCompiledDir, { recursive: true, force: true });
|
|
17994
18040
|
renameWithRetry8(compiledDir, finalCompiledDir);
|
|
17995
18041
|
const shipVideoSource = createVideoSourceShipper({
|
|
17996
18042
|
planDir,
|
|
@@ -18029,7 +18075,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
18029
18075
|
sizeBytes
|
|
18030
18076
|
}))
|
|
18031
18077
|
);
|
|
18032
|
-
|
|
18078
|
+
writeFileSync10(
|
|
18033
18079
|
join37(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
18034
18080
|
JSON.stringify(planVideosJson, null, 2),
|
|
18035
18081
|
"utf-8"
|
|
@@ -18110,10 +18156,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
18110
18156
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
18111
18157
|
}
|
|
18112
18158
|
const planAudioPath = join37(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
18113
|
-
if (audioResult?.hasAudio &&
|
|
18159
|
+
if (audioResult?.hasAudio && existsSync25(audioResult.audioOutputPath)) {
|
|
18114
18160
|
renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
|
|
18115
18161
|
}
|
|
18116
|
-
if (chunksCarryAudio &&
|
|
18162
|
+
if (chunksCarryAudio && existsSync25(planAudioPath)) {
|
|
18117
18163
|
if (options.sliceChunkAudio) {
|
|
18118
18164
|
stageStarted = Date.now();
|
|
18119
18165
|
await writeChunkAudioSlices({
|
|
@@ -18231,7 +18277,7 @@ async function plan(projectDir, config, planDir, options = {}) {
|
|
|
18231
18277
|
|
|
18232
18278
|
// src/services/distributed/renderChunk.ts
|
|
18233
18279
|
import { randomBytes } from "node:crypto";
|
|
18234
|
-
import { existsSync as
|
|
18280
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync17, readFileSync as readFileSync13, readdirSync as readdirSync10, rmSync as rmSync15, writeFileSync as writeFileSync13 } from "node:fs";
|
|
18235
18281
|
import { cpus as cpus3 } from "node:os";
|
|
18236
18282
|
import { join as join41 } from "node:path";
|
|
18237
18283
|
import {
|
|
@@ -18258,7 +18304,7 @@ import {
|
|
|
18258
18304
|
} from "@hyperframes/engine";
|
|
18259
18305
|
import {
|
|
18260
18306
|
copyFileSync as copyFileSync8,
|
|
18261
|
-
existsSync as
|
|
18307
|
+
existsSync as existsSync27,
|
|
18262
18308
|
lstatSync as lstatSync3,
|
|
18263
18309
|
mkdirSync as mkdirSync16,
|
|
18264
18310
|
mkdtempSync as mkdtempSync6,
|
|
@@ -18266,7 +18312,7 @@ import {
|
|
|
18266
18312
|
readdirSync as readdirSync9,
|
|
18267
18313
|
rmSync as rmSync14,
|
|
18268
18314
|
statSync as statSync10,
|
|
18269
|
-
writeFileSync as
|
|
18315
|
+
writeFileSync as writeFileSync12
|
|
18270
18316
|
} from "node:fs";
|
|
18271
18317
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
18272
18318
|
import { dirname as dirname16, isAbsolute as isAbsolute4, join as join40, relative as relative6, resolve as resolve10, sep as sep3 } from "node:path";
|
|
@@ -18276,13 +18322,13 @@ import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as r
|
|
|
18276
18322
|
// src/services/distributed/planV2Publisher.ts
|
|
18277
18323
|
import {
|
|
18278
18324
|
copyFileSync as copyFileSync7,
|
|
18279
|
-
existsSync as
|
|
18325
|
+
existsSync as existsSync26,
|
|
18280
18326
|
linkSync as linkSync2,
|
|
18281
18327
|
mkdirSync as mkdirSync15,
|
|
18282
18328
|
mkdtempSync as mkdtempSync5,
|
|
18283
18329
|
rmSync as rmSync13,
|
|
18284
18330
|
statSync as statSync9,
|
|
18285
|
-
writeFileSync as
|
|
18331
|
+
writeFileSync as writeFileSync11
|
|
18286
18332
|
} from "node:fs";
|
|
18287
18333
|
import { renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
|
|
18288
18334
|
import { dirname as dirname15, join as join39 } from "node:path";
|
|
@@ -18345,7 +18391,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18345
18391
|
#linkFile;
|
|
18346
18392
|
#committed = false;
|
|
18347
18393
|
constructor(destinationDir, options = {}) {
|
|
18348
|
-
if (
|
|
18394
|
+
if (existsSync26(destinationDir)) {
|
|
18349
18395
|
throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
|
|
18350
18396
|
}
|
|
18351
18397
|
this.destinationDir = destinationDir;
|
|
@@ -18362,7 +18408,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18362
18408
|
);
|
|
18363
18409
|
}
|
|
18364
18410
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
18365
|
-
if (
|
|
18411
|
+
if (existsSync26(destinationPath)) return;
|
|
18366
18412
|
mkdirSync15(dirname15(destinationPath), { recursive: true });
|
|
18367
18413
|
const stagingDir = mkdtempSync5(join39(dirname15(destinationPath), ".plan-v2-blob-"));
|
|
18368
18414
|
const temporaryPath = join39(stagingDir, "blob");
|
|
@@ -18380,13 +18426,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18380
18426
|
}
|
|
18381
18427
|
async commitManifest(manifestBytes) {
|
|
18382
18428
|
for (const digest of new Set(manifestDigests(manifestBytes))) {
|
|
18383
|
-
if (!
|
|
18429
|
+
if (!existsSync26(planV2BlobPath(this.temporaryDir, digest))) {
|
|
18384
18430
|
throw new PlanV2IntegrityError(
|
|
18385
18431
|
`cannot commit manifest before referenced blob is durable: ${digest}`
|
|
18386
18432
|
);
|
|
18387
18433
|
}
|
|
18388
18434
|
}
|
|
18389
|
-
|
|
18435
|
+
writeFileSync11(join39(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
18390
18436
|
renameWithRetry9(this.temporaryDir, this.destinationDir);
|
|
18391
18437
|
this.#committed = true;
|
|
18392
18438
|
}
|
|
@@ -18459,7 +18505,7 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
18459
18505
|
}
|
|
18460
18506
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
18461
18507
|
const videoRoot = join40(executionPlanDir, "video-frames");
|
|
18462
|
-
if (!
|
|
18508
|
+
if (!existsSync27(videoRoot)) return;
|
|
18463
18509
|
for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
|
|
18464
18510
|
if (!videoEntry.isDirectory()) continue;
|
|
18465
18511
|
const videoDir = join40(videoRoot, videoEntry.name);
|
|
@@ -18560,7 +18606,7 @@ function parsePlanVideosJson2(value) {
|
|
|
18560
18606
|
}
|
|
18561
18607
|
function materializeExtractedVideoDirectories(planDir) {
|
|
18562
18608
|
const videosPath = join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18563
|
-
if (!
|
|
18609
|
+
if (!existsSync27(videosPath)) return;
|
|
18564
18610
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
18565
18611
|
for (const video of videos.extracted) {
|
|
18566
18612
|
mkdirSync16(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
@@ -18594,9 +18640,9 @@ function parseChunkSlices(value) {
|
|
|
18594
18640
|
}
|
|
18595
18641
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
18596
18642
|
const videoRoot = join40(executionPlanDir, "video-frames");
|
|
18597
|
-
const hasExtractedFrames =
|
|
18643
|
+
const hasExtractedFrames = existsSync27(videoRoot) && listFiles(videoRoot).length > 0;
|
|
18598
18644
|
const videosPath = join40(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18599
|
-
if (!
|
|
18645
|
+
if (!existsSync27(videosPath)) {
|
|
18600
18646
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
18601
18647
|
}
|
|
18602
18648
|
const chunksPath = join40(executionPlanDir, "meta", "chunks.json");
|
|
@@ -18653,7 +18699,7 @@ function computeManifestHash(manifest) {
|
|
|
18653
18699
|
return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
|
|
18654
18700
|
}
|
|
18655
18701
|
function writeBlob(sourcePath, destinationPath) {
|
|
18656
|
-
if (
|
|
18702
|
+
if (existsSync27(destinationPath)) return;
|
|
18657
18703
|
mkdirSync16(dirname16(destinationPath), { recursive: true });
|
|
18658
18704
|
const temporaryDir = mkdtempSync6(join40(dirname16(destinationPath), ".plan-v2-blob-"));
|
|
18659
18705
|
const temporaryPath = join40(temporaryDir, "blob");
|
|
@@ -18693,7 +18739,7 @@ async function putTasks(tasks) {
|
|
|
18693
18739
|
}
|
|
18694
18740
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
18695
18741
|
const executionPlanPath = join40(executionPlanDir, "plan.json");
|
|
18696
|
-
if (!
|
|
18742
|
+
if (!existsSync27(executionPlanPath)) {
|
|
18697
18743
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
18698
18744
|
}
|
|
18699
18745
|
const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
|
|
@@ -18773,7 +18819,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
18773
18819
|
};
|
|
18774
18820
|
}
|
|
18775
18821
|
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
18776
|
-
if (
|
|
18822
|
+
if (existsSync27(planV2Dir)) {
|
|
18777
18823
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
18778
18824
|
}
|
|
18779
18825
|
const publication = buildPlanV2Publication(executionPlanDir);
|
|
@@ -18786,7 +18832,7 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
18786
18832
|
for (const blob of publication.blobs) {
|
|
18787
18833
|
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
18788
18834
|
}
|
|
18789
|
-
|
|
18835
|
+
writeFileSync12(
|
|
18790
18836
|
join40(tempDir, "plan.json"),
|
|
18791
18837
|
canonicalJsonStringify(publication.manifest),
|
|
18792
18838
|
"utf-8"
|
|
@@ -18859,7 +18905,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
18859
18905
|
}
|
|
18860
18906
|
}
|
|
18861
18907
|
async function planV2(projectDir, config, planV2Dir) {
|
|
18862
|
-
if (
|
|
18908
|
+
if (existsSync27(planV2Dir)) {
|
|
18863
18909
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
18864
18910
|
}
|
|
18865
18911
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
@@ -19013,7 +19059,7 @@ function parsePlanV2Manifest(value) {
|
|
|
19013
19059
|
}
|
|
19014
19060
|
function readPlanV2Manifest(planV2Dir) {
|
|
19015
19061
|
const manifestPath = join40(planV2Dir, "plan.json");
|
|
19016
|
-
if (!
|
|
19062
|
+
if (!existsSync27(manifestPath)) {
|
|
19017
19063
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
19018
19064
|
}
|
|
19019
19065
|
const value = readJsonFile(manifestPath, "v2 manifest");
|
|
@@ -19032,7 +19078,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
|
|
|
19032
19078
|
}
|
|
19033
19079
|
function verifyBlob(planV2Dir, artifact) {
|
|
19034
19080
|
const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
|
|
19035
|
-
if (!
|
|
19081
|
+
if (!existsSync27(sourcePath)) {
|
|
19036
19082
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
19037
19083
|
}
|
|
19038
19084
|
const stats = statSync10(sourcePath);
|
|
@@ -19045,7 +19091,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
19045
19091
|
return sourcePath;
|
|
19046
19092
|
}
|
|
19047
19093
|
function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
|
|
19048
|
-
if (
|
|
19094
|
+
if (existsSync27(destinationDir)) {
|
|
19049
19095
|
throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
|
|
19050
19096
|
}
|
|
19051
19097
|
const manifest = readPlanV2Manifest(planV2Dir);
|
|
@@ -19066,7 +19112,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
19066
19112
|
copyFileSync8(sourcePath, destinationPath);
|
|
19067
19113
|
}
|
|
19068
19114
|
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
19069
|
-
|
|
19115
|
+
writeFileSync12(
|
|
19070
19116
|
join40(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
19071
19117
|
canonicalJsonStringify({
|
|
19072
19118
|
manifest,
|
|
@@ -19110,7 +19156,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
19110
19156
|
}
|
|
19111
19157
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
19112
19158
|
const markerPath = join40(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
19113
|
-
if (!
|
|
19159
|
+
if (!existsSync27(markerPath)) return null;
|
|
19114
19160
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
19115
19161
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
19116
19162
|
throw new PlanV2IntegrityError("malformed materialization marker");
|
|
@@ -19128,7 +19174,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
19128
19174
|
for (const artifact of required) {
|
|
19129
19175
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
19130
19176
|
const path = join40(planDir, ...artifact.path.split("/"));
|
|
19131
|
-
if (!
|
|
19177
|
+
if (!existsSync27(path) || statSync10(path).size !== artifact.sizeBytes) {
|
|
19132
19178
|
throw new PlanV2IntegrityError(
|
|
19133
19179
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
19134
19180
|
);
|
|
@@ -19268,7 +19314,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19268
19314
|
const planJsonPath = join41(planDir, "plan.json");
|
|
19269
19315
|
const encoderJsonPath = join41(planDir, "meta", "encoder.json");
|
|
19270
19316
|
const chunksJsonPath = join41(planDir, "meta", "chunks.json");
|
|
19271
|
-
if (!
|
|
19317
|
+
if (!existsSync28(planJsonPath)) {
|
|
19272
19318
|
throw new RenderChunkValidationError(
|
|
19273
19319
|
MISSING_PLAN_ARTIFACT,
|
|
19274
19320
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
@@ -19284,7 +19330,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19284
19330
|
const v2Manifest = v2Materialization?.manifest ?? null;
|
|
19285
19331
|
const remoteSources = v2Materialization?.remoteSources ?? {};
|
|
19286
19332
|
for (const required of [encoderJsonPath, chunksJsonPath]) {
|
|
19287
|
-
if (!
|
|
19333
|
+
if (!existsSync28(required)) {
|
|
19288
19334
|
throw new RenderChunkValidationError(
|
|
19289
19335
|
MISSING_PLAN_ARTIFACT,
|
|
19290
19336
|
`[renderChunk] planDir is missing required artifact: ${required}`
|
|
@@ -19295,7 +19341,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19295
19341
|
const chunks = JSON.parse(readFileSync13(chunksJsonPath, "utf-8"));
|
|
19296
19342
|
const videosJsonPath = join41(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
19297
19343
|
let planVideos = null;
|
|
19298
|
-
if (
|
|
19344
|
+
if (existsSync28(videosJsonPath)) {
|
|
19299
19345
|
try {
|
|
19300
19346
|
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync13(videosJsonPath, "utf-8")));
|
|
19301
19347
|
} catch (err) {
|
|
@@ -19326,7 +19372,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19326
19372
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
19327
19373
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
19328
19374
|
const compiledDir = join41(planDir, "compiled");
|
|
19329
|
-
if (!
|
|
19375
|
+
if (!existsSync28(compiledDir)) {
|
|
19330
19376
|
throw new RenderChunkValidationError(
|
|
19331
19377
|
MISSING_PLAN_ARTIFACT,
|
|
19332
19378
|
`[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
|
|
@@ -19576,10 +19622,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19576
19622
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
19577
19623
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
19578
19624
|
if (isPngSequence) {
|
|
19579
|
-
if (!
|
|
19625
|
+
if (!existsSync28(outputChunkPath)) mkdirSync17(outputChunkPath, { recursive: true });
|
|
19580
19626
|
} else {
|
|
19581
19627
|
const outDir = join41(outputChunkPath, "..");
|
|
19582
|
-
if (!
|
|
19628
|
+
if (!existsSync28(outDir)) mkdirSync17(outDir, { recursive: true });
|
|
19583
19629
|
}
|
|
19584
19630
|
const encodeInput = {
|
|
19585
19631
|
job,
|
|
@@ -19789,7 +19835,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19789
19835
|
producerVersion: plan2.producerVersion,
|
|
19790
19836
|
ffmpegVersion
|
|
19791
19837
|
};
|
|
19792
|
-
|
|
19838
|
+
writeFileSync13(perfPath, `${JSON.stringify(perfPayload, null, 2)}
|
|
19793
19839
|
`, "utf-8");
|
|
19794
19840
|
try {
|
|
19795
19841
|
rmSync15(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
@@ -20479,11 +20525,11 @@ import {
|
|
|
20479
20525
|
|
|
20480
20526
|
// src/server.ts
|
|
20481
20527
|
import {
|
|
20482
|
-
existsSync as
|
|
20528
|
+
existsSync as existsSync31,
|
|
20483
20529
|
mkdirSync as mkdirSync19,
|
|
20484
20530
|
statSync as statSync12,
|
|
20485
20531
|
mkdtempSync as mkdtempSync8,
|
|
20486
|
-
writeFileSync as
|
|
20532
|
+
writeFileSync as writeFileSync14,
|
|
20487
20533
|
rmSync as rmSync17,
|
|
20488
20534
|
createReadStream as createReadStream5
|
|
20489
20535
|
} from "node:fs";
|
|
@@ -20502,7 +20548,7 @@ import {
|
|
|
20502
20548
|
|
|
20503
20549
|
// src/services/hyperframeLint.ts
|
|
20504
20550
|
import {
|
|
20505
|
-
existsSync as
|
|
20551
|
+
existsSync as existsSync29,
|
|
20506
20552
|
readFileSync as readFileSync15,
|
|
20507
20553
|
statSync as statSync11,
|
|
20508
20554
|
openSync as openSync7,
|
|
@@ -20556,7 +20602,7 @@ function readEntryHtml(filePath) {
|
|
|
20556
20602
|
}
|
|
20557
20603
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
20558
20604
|
const absProjectDir = resolve11(projectDir);
|
|
20559
|
-
if (!
|
|
20605
|
+
if (!existsSync29(absProjectDir) || !statSync11(absProjectDir).isDirectory()) {
|
|
20560
20606
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
20561
20607
|
}
|
|
20562
20608
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -20625,7 +20671,7 @@ async function runHyperframeLint(prepared) {
|
|
|
20625
20671
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
20626
20672
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
20627
20673
|
import { dirname as dirname17, join as join44 } from "node:path";
|
|
20628
|
-
import { existsSync as
|
|
20674
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
20629
20675
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
20630
20676
|
async function startHealthWorker(options = {}) {
|
|
20631
20677
|
const log = options.logger ?? defaultLogger2();
|
|
@@ -20702,7 +20748,7 @@ function resolveWorkerEntry2() {
|
|
|
20702
20748
|
const here = dirname17(fileURLToPath6(import.meta.url));
|
|
20703
20749
|
const candidates = [join44(here, "healthWorkerThread.js"), join44(here, "healthWorkerThread.ts")];
|
|
20704
20750
|
for (const candidate of candidates) {
|
|
20705
|
-
if (
|
|
20751
|
+
if (existsSync30(candidate)) return candidate;
|
|
20706
20752
|
}
|
|
20707
20753
|
return void 0;
|
|
20708
20754
|
}
|
|
@@ -20848,7 +20894,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
|
20848
20894
|
const { input, cleanupProjectDir } = prepared;
|
|
20849
20895
|
const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
|
|
20850
20896
|
const outputDir = dirname18(absoluteOutputPath);
|
|
20851
|
-
if (!
|
|
20897
|
+
if (!existsSync31(outputDir)) mkdirSync19(outputDir, { recursive: true });
|
|
20852
20898
|
return { input, cleanupProjectDir, absoluteOutputPath };
|
|
20853
20899
|
}
|
|
20854
20900
|
function validateRenderOverrides(body) {
|
|
@@ -20889,11 +20935,11 @@ function prepareProjectDirectory(projectDir, options) {
|
|
|
20889
20935
|
const candidate = nonEmptyString(projectDir);
|
|
20890
20936
|
if (!candidate) return null;
|
|
20891
20937
|
const absProjectDir = resolve12(candidate);
|
|
20892
|
-
if (!
|
|
20938
|
+
if (!existsSync31(absProjectDir) || !statSync12(absProjectDir).isDirectory()) {
|
|
20893
20939
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
20894
20940
|
}
|
|
20895
20941
|
const entry = options.entryFile || "index.html";
|
|
20896
|
-
if (!
|
|
20942
|
+
if (!existsSync31(resolve12(absProjectDir, entry))) {
|
|
20897
20943
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
20898
20944
|
}
|
|
20899
20945
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -20915,7 +20961,7 @@ async function resolveInlineRenderHtml(body) {
|
|
|
20915
20961
|
function materializeInlineProject(html, options) {
|
|
20916
20962
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir5();
|
|
20917
20963
|
const tempProjectDir = mkdtempSync8(join45(tempRoot, "producer-project-"));
|
|
20918
|
-
|
|
20964
|
+
writeFileSync14(join45(tempProjectDir, "index.html"), html, "utf-8");
|
|
20919
20965
|
return {
|
|
20920
20966
|
prepared: {
|
|
20921
20967
|
input: { projectDir: tempProjectDir, ...options },
|
|
@@ -20985,7 +21031,7 @@ function cleanupTempDir(dir, log) {
|
|
|
20985
21031
|
}
|
|
20986
21032
|
}
|
|
20987
21033
|
function outputFileSize(path) {
|
|
20988
|
-
return
|
|
21034
|
+
return existsSync31(path) ? statSync12(path).size : 0;
|
|
20989
21035
|
}
|
|
20990
21036
|
function createBlockingProgressReporter(log, requestId) {
|
|
20991
21037
|
let lastLoggedPct = -10;
|
|
@@ -21289,7 +21335,7 @@ function createRenderHandlers(options = {}) {
|
|
|
21289
21335
|
if (!artifact) {
|
|
21290
21336
|
return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
|
|
21291
21337
|
}
|
|
21292
|
-
if (!
|
|
21338
|
+
if (!existsSync31(artifact.path)) {
|
|
21293
21339
|
store.delete(token);
|
|
21294
21340
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
21295
21341
|
}
|
|
@@ -21395,14 +21441,14 @@ import {
|
|
|
21395
21441
|
} from "@hyperframes/engine";
|
|
21396
21442
|
import {
|
|
21397
21443
|
cpSync as cpSync3,
|
|
21398
|
-
existsSync as
|
|
21444
|
+
existsSync as existsSync32,
|
|
21399
21445
|
mkdirSync as mkdirSync20,
|
|
21400
21446
|
mkdtempSync as mkdtempSync9,
|
|
21401
21447
|
readFileSync as readFileSync16,
|
|
21402
21448
|
readdirSync as readdirSync11,
|
|
21403
21449
|
rmSync as rmSync18,
|
|
21404
21450
|
statSync as statSync13,
|
|
21405
|
-
writeFileSync as
|
|
21451
|
+
writeFileSync as writeFileSync15
|
|
21406
21452
|
} from "node:fs";
|
|
21407
21453
|
import { dirname as dirname19, join as join46 } from "node:path";
|
|
21408
21454
|
import {
|
|
@@ -21450,7 +21496,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21450
21496
|
}
|
|
21451
21497
|
const format = plan2.dimensions.format;
|
|
21452
21498
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
21453
|
-
if (!
|
|
21499
|
+
if (!existsSync32(dirname19(outputPath))) {
|
|
21454
21500
|
mkdirSync20(dirname19(outputPath), { recursive: true });
|
|
21455
21501
|
}
|
|
21456
21502
|
const workDir = mkdtempSync9(`${outputPath}.assemble-work-`);
|
|
@@ -21474,7 +21520,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21474
21520
|
try {
|
|
21475
21521
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
21476
21522
|
const finalPath = join46(workDir, `output.${format}`);
|
|
21477
|
-
const audio = audioPath !== null &&
|
|
21523
|
+
const audio = audioPath !== null && existsSync32(audioPath) ? audioPath : null;
|
|
21478
21524
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
21479
21525
|
renameWithRetry11(finalPath, outputPath);
|
|
21480
21526
|
log.info("[assemble] stage timings", {
|
|
@@ -21492,7 +21538,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21492
21538
|
});
|
|
21493
21539
|
}
|
|
21494
21540
|
}
|
|
21495
|
-
const fileSize =
|
|
21541
|
+
const fileSize = existsSync32(outputPath) ? statSync13(outputPath).size : 0;
|
|
21496
21542
|
return {
|
|
21497
21543
|
...referenceBackgroundResult8(plan2[REFERENCE_BACKGROUND_FIELD6]),
|
|
21498
21544
|
outputPath,
|
|
@@ -21504,13 +21550,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21504
21550
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
21505
21551
|
const planJsonPath = join46(planDir, "plan.json");
|
|
21506
21552
|
const chunksJsonPath = join46(planDir, "meta", "chunks.json");
|
|
21507
|
-
if (!
|
|
21553
|
+
if (!existsSync32(planJsonPath)) {
|
|
21508
21554
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
21509
21555
|
}
|
|
21510
21556
|
const plan2 = JSON.parse(readFileSync16(planJsonPath, "utf-8"));
|
|
21511
21557
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
21512
21558
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
21513
|
-
if (!
|
|
21559
|
+
if (!existsSync32(chunksJsonPath)) {
|
|
21514
21560
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
21515
21561
|
}
|
|
21516
21562
|
const chunks = JSON.parse(readFileSync16(chunksJsonPath, "utf-8"));
|
|
@@ -21525,7 +21571,7 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
21525
21571
|
}
|
|
21526
21572
|
}
|
|
21527
21573
|
for (const path of chunkPaths.flat()) {
|
|
21528
|
-
if (!
|
|
21574
|
+
if (!existsSync32(path)) {
|
|
21529
21575
|
throw new Error(`[assemble] chunk path does not exist: ${path}`);
|
|
21530
21576
|
}
|
|
21531
21577
|
}
|
|
@@ -21623,7 +21669,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
21623
21669
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
21624
21670
|
const listPath = join46(workDir, "concat-list.txt");
|
|
21625
21671
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
21626
|
-
|
|
21672
|
+
writeFileSync15(listPath, `${body}
|
|
21627
21673
|
`, "utf-8");
|
|
21628
21674
|
return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
|
|
21629
21675
|
}
|
|
@@ -21664,7 +21710,7 @@ function planFormat(planDir) {
|
|
|
21664
21710
|
}
|
|
21665
21711
|
function lockedAudio(planDir) {
|
|
21666
21712
|
const encoderJsonPath = join46(planDir, "meta", "encoder.json");
|
|
21667
|
-
if (!
|
|
21713
|
+
if (!existsSync32(encoderJsonPath)) return {};
|
|
21668
21714
|
return JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
|
|
21669
21715
|
}
|
|
21670
21716
|
function readCfrEncoder(planDir, format) {
|
|
@@ -21674,7 +21720,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
21674
21720
|
);
|
|
21675
21721
|
}
|
|
21676
21722
|
const encoderJsonPath = join46(planDir, "meta", "encoder.json");
|
|
21677
|
-
if (!
|
|
21723
|
+
if (!existsSync32(encoderJsonPath)) {
|
|
21678
21724
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
21679
21725
|
}
|
|
21680
21726
|
const encoderJson = JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
|
|
@@ -21686,7 +21732,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
21686
21732
|
return encoderJson;
|
|
21687
21733
|
}
|
|
21688
21734
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
21689
|
-
if (
|
|
21735
|
+
if (existsSync32(outputPath)) rmSync18(outputPath, { recursive: true, force: true });
|
|
21690
21736
|
mkdirSync20(outputPath, { recursive: true });
|
|
21691
21737
|
let globalIdx = 0;
|
|
21692
21738
|
for (const chunkDir of chunkPaths) {
|
|
@@ -21710,7 +21756,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
21710
21756
|
`[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
|
|
21711
21757
|
);
|
|
21712
21758
|
}
|
|
21713
|
-
if (audioPath !== null &&
|
|
21759
|
+
if (audioPath !== null && existsSync32(audioPath)) {
|
|
21714
21760
|
const sidecar = join46(outputPath, MIXED_AUDIO_FILENAME4);
|
|
21715
21761
|
cpSync3(audioPath, sidecar);
|
|
21716
21762
|
}
|