@hyperframes/producer 0.9.13 → 0.9.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/distributed.js +197 -132
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +62 -62
- package/dist/index.js +275 -166
- package/dist/public-server.js +188 -82
- 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/captureStreamingStage.d.ts +4 -1
- package/package.json +7 -7
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ 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 existsSync19,
|
|
13
13
|
mkdirSync as mkdirSync10,
|
|
14
14
|
readdirSync as readdirSync5,
|
|
15
15
|
rmSync as rmSync10,
|
|
@@ -3741,6 +3741,7 @@ function createStallClock(now = () => Date.now()) {
|
|
|
3741
3741
|
|
|
3742
3742
|
// src/services/render/stages/captureStreamingStage.ts
|
|
3743
3743
|
var DEFAULT_CAPTURE_STALL_MS = 6e4;
|
|
3744
|
+
var SESSION_INIT_STALL_MS = 3e5;
|
|
3744
3745
|
var DE_STALL_POLL_MS = 5e3;
|
|
3745
3746
|
var STALL_ADVICE = "Chrome stopped answering. If another app is keeping the GPU or CPU busy (a 3D render, a game, another export), let it finish or render one job at a time, then retry.";
|
|
3746
3747
|
function poolFailure(error, probeSession) {
|
|
@@ -3771,6 +3772,14 @@ function captureModeLabel(mode) {
|
|
|
3771
3772
|
if (mode === "beginframe") return "BeginFrame";
|
|
3772
3773
|
return "screenshot";
|
|
3773
3774
|
}
|
|
3775
|
+
var OPENING_CAPTURE_SESSION = {
|
|
3776
|
+
captureMode: "screenshot",
|
|
3777
|
+
captureStep: "opening the capture session"
|
|
3778
|
+
};
|
|
3779
|
+
var OPENING_RISK_SESSION = {
|
|
3780
|
+
captureMode: "screenshot",
|
|
3781
|
+
captureStep: "opening the screenshot session for effect frames"
|
|
3782
|
+
};
|
|
3774
3783
|
var SequentialCaptureStallError = class extends Error {
|
|
3775
3784
|
captureMode;
|
|
3776
3785
|
constructor(session, stallTimeoutMs, frameIndex, totalFrames) {
|
|
@@ -3920,7 +3929,12 @@ async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEnc
|
|
|
3920
3929
|
const guard = createDrainFrameGuard({ log, stats, frameTime });
|
|
3921
3930
|
const guardFrame = (item, buf) => item.trusted ? Promise.resolve(buf) : guard(session, item.idx, buf);
|
|
3922
3931
|
const captureRisk = async (idx) => {
|
|
3923
|
-
await
|
|
3932
|
+
await raceAgainstStall(
|
|
3933
|
+
risk.open(),
|
|
3934
|
+
stallTimeoutMs - quiet.idle(0),
|
|
3935
|
+
{ session: OPENING_RISK_SESSION, frameIndex: idx, totalFrames, stallTimeoutMs },
|
|
3936
|
+
abortSignal
|
|
3937
|
+
);
|
|
3924
3938
|
quiet.reset();
|
|
3925
3939
|
return raceAgainstStall(
|
|
3926
3940
|
risk.capture(idx, frameTime(idx)),
|
|
@@ -4279,19 +4293,38 @@ async function runCaptureStreamingStage(input) {
|
|
|
4279
4293
|
let resumes = 0;
|
|
4280
4294
|
for (; ; ) {
|
|
4281
4295
|
const videoInjector = createRenderVideoFrameInjector();
|
|
4282
|
-
const
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4296
|
+
const initStallMs = Math.max(SESSION_INIT_STALL_MS, resolveCaptureStallTimeoutMs());
|
|
4297
|
+
const setup = createStallClock();
|
|
4298
|
+
const withinInitBudget = (work, stalled) => raceAgainstStall(
|
|
4299
|
+
work,
|
|
4300
|
+
initStallMs - setup.idle(0),
|
|
4301
|
+
{ session: stalled, frameIndex: resume.from, totalFrames, stallTimeoutMs: initStallMs },
|
|
4302
|
+
abortSignal
|
|
4288
4303
|
);
|
|
4304
|
+
const openSession = async () => {
|
|
4305
|
+
const opening = createCaptureSession(
|
|
4306
|
+
fileServer.url,
|
|
4307
|
+
framesDir,
|
|
4308
|
+
buildCaptureOptions(),
|
|
4309
|
+
videoInjector,
|
|
4310
|
+
captureCfg
|
|
4311
|
+
);
|
|
4312
|
+
try {
|
|
4313
|
+
return await withinInitBudget(opening, OPENING_CAPTURE_SESSION);
|
|
4314
|
+
} catch (error) {
|
|
4315
|
+
void opening.then(closeCaptureSession, () => {
|
|
4316
|
+
});
|
|
4317
|
+
throw error;
|
|
4318
|
+
}
|
|
4319
|
+
};
|
|
4320
|
+
const session = probeSession ?? await openSession();
|
|
4289
4321
|
captureBeyondViewport = session.options.captureBeyondViewport;
|
|
4290
4322
|
if (probeSession) {
|
|
4291
4323
|
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
4292
4324
|
probeSession = null;
|
|
4293
4325
|
}
|
|
4294
4326
|
let riskSession = null;
|
|
4327
|
+
let attemptClosed = false;
|
|
4295
4328
|
const openRiskSession = async () => {
|
|
4296
4329
|
const opened = await createCaptureSession(
|
|
4297
4330
|
fileServer.url,
|
|
@@ -4305,6 +4338,10 @@ async function runCaptureStreamingStage(input) {
|
|
|
4305
4338
|
enableBrowserPool: false
|
|
4306
4339
|
}
|
|
4307
4340
|
);
|
|
4341
|
+
if (attemptClosed) {
|
|
4342
|
+
await closeCaptureSession(opened);
|
|
4343
|
+
throw new Error("[Render] Capture attempt ended while its screenshot session opened");
|
|
4344
|
+
}
|
|
4308
4345
|
riskSession = opened;
|
|
4309
4346
|
await initializeSession(opened);
|
|
4310
4347
|
return opened;
|
|
@@ -4312,9 +4349,9 @@ async function runCaptureStreamingStage(input) {
|
|
|
4312
4349
|
let resumed = false;
|
|
4313
4350
|
try {
|
|
4314
4351
|
if (!session.isInitialized) {
|
|
4315
|
-
await initializeSession(session);
|
|
4352
|
+
await withinInitBudget(initializeSession(session), session);
|
|
4316
4353
|
}
|
|
4317
|
-
await completeDeferredDrawElementInit(session);
|
|
4354
|
+
await withinInitBudget(completeDeferredDrawElementInit(session), session);
|
|
4318
4355
|
assertNotAborted();
|
|
4319
4356
|
lastBrowserConsole = session.browserConsoleBuffer;
|
|
4320
4357
|
if (session.workerEncodeEnabled) {
|
|
@@ -4397,6 +4434,7 @@ async function runCaptureStreamingStage(input) {
|
|
|
4397
4434
|
resumes++;
|
|
4398
4435
|
resumed = true;
|
|
4399
4436
|
} finally {
|
|
4437
|
+
attemptClosed = true;
|
|
4400
4438
|
lastBrowserConsole = session.browserConsoleBuffer;
|
|
4401
4439
|
await closeCaptureSession(session);
|
|
4402
4440
|
if (riskSession) await closeCaptureSession(riskSession);
|
|
@@ -5863,7 +5901,7 @@ import { suggestMatchingPreset } from "@hyperframes/core";
|
|
|
5863
5901
|
// src/services/htmlCompiler.ts
|
|
5864
5902
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
5865
5903
|
import { createHash as createHash6 } from "node:crypto";
|
|
5866
|
-
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
|
|
5904
|
+
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7 } from "fs";
|
|
5867
5905
|
import { join as join15, dirname as dirname7, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
|
|
5868
5906
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
5869
5907
|
import {
|
|
@@ -7838,6 +7876,25 @@ ${fontFaceBlocks.join("\n")}
|
|
|
7838
7876
|
}
|
|
7839
7877
|
return result;
|
|
7840
7878
|
}
|
|
7879
|
+
function projectStylesheetFontFamilies(html, projectDir) {
|
|
7880
|
+
const families = [];
|
|
7881
|
+
for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
|
|
7882
|
+
const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
7883
|
+
if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
|
|
7884
|
+
const sheet = resolve7(projectDir, decodedUrlPath(href).replace(/^(?:\.?\/)+/, ""));
|
|
7885
|
+
if (!isPathInside2(sheet, projectDir) || !existsSync10(sheet)) continue;
|
|
7886
|
+
for (const face of extractFontFaceBlocks(readFileSync7(sheet, "utf8"))) {
|
|
7887
|
+
const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
7888
|
+
const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
|
|
7889
|
+
const path = decodedUrlPath(url);
|
|
7890
|
+
const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(dirname7(sheet), path);
|
|
7891
|
+
return isPathInside2(file, projectDir) && existsSync10(file);
|
|
7892
|
+
});
|
|
7893
|
+
if (family && shipsFile) families.push(family);
|
|
7894
|
+
}
|
|
7895
|
+
}
|
|
7896
|
+
return families;
|
|
7897
|
+
}
|
|
7841
7898
|
function collectFontFaceUrls(html) {
|
|
7842
7899
|
const styleBlockRe = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
|
|
7843
7900
|
const urlSet = /* @__PURE__ */ new Set();
|
|
@@ -7869,11 +7926,12 @@ function collectFontFaceUrls(html) {
|
|
|
7869
7926
|
return urlSet;
|
|
7870
7927
|
}
|
|
7871
7928
|
async function localizeRemoteFontFaces(html, downloadDir) {
|
|
7872
|
-
|
|
7873
|
-
|
|
7929
|
+
return localizeFontFaceUrls(await inlineExternalFontStylesheets(html), downloadDir);
|
|
7930
|
+
}
|
|
7931
|
+
function localizeFontFaceUrls(html, downloadDir) {
|
|
7874
7932
|
return downloadAndRewriteUrls(
|
|
7875
|
-
|
|
7876
|
-
|
|
7933
|
+
collectFontFaceUrls(html),
|
|
7934
|
+
html,
|
|
7877
7935
|
join15(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7878
7936
|
"Remote font download failed",
|
|
7879
7937
|
"Localized remote font face(s)",
|
|
@@ -8041,15 +8099,17 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
8041
8099
|
const hasShaderTransitions = detectShaderTransitionUsage(sanitizedHtml);
|
|
8042
8100
|
const usesThreeDTransforms = detectThreeDTransformUsage(sanitizedHtml);
|
|
8043
8101
|
const hasAncestorBackgroundImage = detectAncestorBackgroundImage(sanitizedHtml);
|
|
8102
|
+
const fontSheetHtml = await inlineExternalFontStylesheets(
|
|
8103
|
+
promoteCssImportsToLinkTags(sanitizedHtml)
|
|
8104
|
+
);
|
|
8044
8105
|
const normalizedFontHtml = normalizeSystemFontPrimaryFamilies(
|
|
8045
|
-
injectTextRenderingRule(
|
|
8046
|
-
coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml))
|
|
8047
|
-
)
|
|
8106
|
+
injectTextRenderingRule(coalesceHeadStylesAndBodyScripts(fontSheetHtml))
|
|
8048
8107
|
);
|
|
8049
8108
|
const coalescedHtml = await injectDeterministicFontFaces(normalizedFontHtml, {
|
|
8050
8109
|
logger: defaultLogger,
|
|
8051
8110
|
failClosedFontFetch: options.failClosedFontFetch === true,
|
|
8052
8111
|
allowSystemFontCapture: options.allowSystemFontCapture,
|
|
8112
|
+
projectFontFamilies: projectStylesheetFontFamilies(fontSheetHtml, projectDir),
|
|
8053
8113
|
abortSignal: options.abortSignal
|
|
8054
8114
|
});
|
|
8055
8115
|
const assembledHtml = await inlineExternalScripts(coalescedHtml);
|
|
@@ -8075,7 +8135,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
8075
8135
|
);
|
|
8076
8136
|
const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
|
|
8077
8137
|
const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
|
|
8078
|
-
const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await
|
|
8138
|
+
const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeFontFaceUrls(htmlWithLocalBg, downloadDir);
|
|
8079
8139
|
const {
|
|
8080
8140
|
html: htmlWithJpegs,
|
|
8081
8141
|
preparedAssets: preparedHeifAssets,
|
|
@@ -9926,6 +9986,7 @@ async function planVideoLayerPassthrough(input) {
|
|
|
9926
9986
|
}
|
|
9927
9987
|
|
|
9928
9988
|
// src/services/render/stages/audioStage.ts
|
|
9989
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
9929
9990
|
import { join as join21 } from "node:path";
|
|
9930
9991
|
import {
|
|
9931
9992
|
MIXED_AUDIO_FILENAME,
|
|
@@ -10059,8 +10120,9 @@ var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
|
|
|
10059
10120
|
var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
|
|
10060
10121
|
var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
|
|
10061
10122
|
var MAX_TRUE_PEAK_CORRECTION_PASSES = 3;
|
|
10123
|
+
var gainFilter = (gainDb) => `volume=${gainDb.toFixed(3)}dB`;
|
|
10062
10124
|
function aacTruePeakCorrectionFilter(targetDurationSeconds, attenuationDb) {
|
|
10063
|
-
return
|
|
10125
|
+
return `${gainFilter(attenuationDb)},apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
|
|
10064
10126
|
}
|
|
10065
10127
|
function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
|
|
10066
10128
|
const delta = targetDurationSeconds - sourceDurationSeconds;
|
|
@@ -10098,7 +10160,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10098
10160
|
const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
|
|
10099
10161
|
const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
|
|
10100
10162
|
const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
|
|
10101
|
-
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal);
|
|
10163
|
+
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath);
|
|
10102
10164
|
const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg || input.encodeAac ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
|
|
10103
10165
|
const [videoResult, audioResult] = await Promise.allSettled([
|
|
10104
10166
|
probeVideo(input.videoPath),
|
|
@@ -10146,7 +10208,12 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10146
10208
|
);
|
|
10147
10209
|
try {
|
|
10148
10210
|
for (const step of plan2.steps) {
|
|
10149
|
-
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
|
|
10211
|
+
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
|
|
10212
|
+
input.sourcePath ?? input.audioPath,
|
|
10213
|
+
input.outputPath,
|
|
10214
|
+
targetDurationSeconds,
|
|
10215
|
+
step.filter
|
|
10216
|
+
);
|
|
10150
10217
|
if (!ffmpegResult.success) {
|
|
10151
10218
|
return {
|
|
10152
10219
|
success: false,
|
|
@@ -10175,6 +10242,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10175
10242
|
if (probeTruePeak) {
|
|
10176
10243
|
const correction = await enforceAacTruePeak({
|
|
10177
10244
|
audioPath: input.outputPath,
|
|
10245
|
+
sourcePath: input.sourcePath,
|
|
10178
10246
|
targetDurationSeconds,
|
|
10179
10247
|
signal: input.signal,
|
|
10180
10248
|
probeTruePeak,
|
|
@@ -10210,9 +10278,27 @@ async function constrainAacTruePeak(input) {
|
|
|
10210
10278
|
targetDurationSeconds: input.targetDurationSeconds,
|
|
10211
10279
|
signal: input.signal,
|
|
10212
10280
|
probeTruePeak: input.probeTruePeakDbfs ?? ((audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal })),
|
|
10213
|
-
encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
|
|
10281
|
+
encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath),
|
|
10282
|
+
rewriteSource: input.rewriteSource ?? ((sourcePath, outputPath, filter) => defaultRunFfmpeg(
|
|
10283
|
+
["-i", sourcePath, "-af", filter, "-c:a", "pcm_f32le", "-y", outputPath],
|
|
10284
|
+
timeoutMs,
|
|
10285
|
+
input.signal
|
|
10286
|
+
))
|
|
10214
10287
|
});
|
|
10215
10288
|
}
|
|
10289
|
+
async function cutSource(input, scratchDir, attenuationDb) {
|
|
10290
|
+
if (!input.sourcePath || !input.rewriteSource) return true;
|
|
10291
|
+
const cutPath = join19(scratchDir, "source.wav");
|
|
10292
|
+
try {
|
|
10293
|
+
const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
|
|
10294
|
+
if (!result.success) throw new Error(result.error);
|
|
10295
|
+
renameWithRetry5(cutPath, input.sourcePath, { copyFallback: true });
|
|
10296
|
+
return true;
|
|
10297
|
+
} catch {
|
|
10298
|
+
removeScratch(input.sourcePath);
|
|
10299
|
+
return false;
|
|
10300
|
+
}
|
|
10301
|
+
}
|
|
10216
10302
|
async function enforceAacTruePeak(input) {
|
|
10217
10303
|
let scratchDir;
|
|
10218
10304
|
try {
|
|
@@ -10232,8 +10318,10 @@ async function enforceAacTruePeak(input) {
|
|
|
10232
10318
|
}
|
|
10233
10319
|
if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
|
|
10234
10320
|
if (measuredPath !== correctedPath) return { success: true };
|
|
10321
|
+
const sourceCut = await cutSource(input, scratchDir, attenuationDb);
|
|
10235
10322
|
renameWithRetry5(correctedPath, input.audioPath, { copyFallback: true });
|
|
10236
|
-
|
|
10323
|
+
const loweredDb = Number((-attenuationDb).toFixed(2));
|
|
10324
|
+
return sourceCut ? { success: true, loweredDb } : { success: true, loweredDb, sourceStale: true };
|
|
10237
10325
|
}
|
|
10238
10326
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
10239
10327
|
const headroomDb = AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB * 2 ** pass;
|
|
@@ -10243,7 +10331,7 @@ async function enforceAacTruePeak(input) {
|
|
|
10243
10331
|
const referenceDbfs = Math.max(truePeakDbfs, (sourcePeakDbfs ?? -Infinity) + attenuationDb);
|
|
10244
10332
|
attenuationDb += AAC_DELIVERY_TRUE_PEAK_DBFS - headroomDb - referenceDbfs;
|
|
10245
10333
|
const result = await input.encode(
|
|
10246
|
-
input.audioPath,
|
|
10334
|
+
input.sourcePath ?? input.audioPath,
|
|
10247
10335
|
correctedPath,
|
|
10248
10336
|
input.targetDurationSeconds,
|
|
10249
10337
|
aacTruePeakCorrectionFilter(input.targetDurationSeconds, attenuationDb)
|
|
@@ -10472,36 +10560,54 @@ async function runAudioStageScoped(input) {
|
|
|
10472
10560
|
const stageStart = Date.now();
|
|
10473
10561
|
const pcmSourcePath = join21(input.workDir, "audio-source.wav");
|
|
10474
10562
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
10563
|
+
let deliverySourcePath;
|
|
10475
10564
|
try {
|
|
10476
10565
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
10477
10566
|
let audioLoweredDb;
|
|
10478
10567
|
if (result.hasAudio) {
|
|
10479
10568
|
const guardStart = Date.now();
|
|
10480
|
-
const
|
|
10481
|
-
|
|
10482
|
-
|
|
10483
|
-
|
|
10484
|
-
|
|
10485
|
-
|
|
10486
|
-
|
|
10487
|
-
});
|
|
10488
|
-
if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
|
|
10489
|
-
const peak = await constrainAacTruePeak({
|
|
10490
|
-
audioPath: result.audioOutputPath,
|
|
10491
|
-
sourcePath: pcmSourcePath,
|
|
10492
|
-
targetDurationSeconds: duration,
|
|
10493
|
-
signal: input.abortSignal,
|
|
10494
|
-
timeoutMs: input.ffmpegProcessTimeout
|
|
10495
|
-
});
|
|
10496
|
-
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
10497
|
-
audioLoweredDb = peak.loweredDb;
|
|
10569
|
+
const guard = await guardDeliveryPeaks(
|
|
10570
|
+
input,
|
|
10571
|
+
result.audioOutputPath,
|
|
10572
|
+
pcmSourcePath,
|
|
10573
|
+
duration
|
|
10574
|
+
);
|
|
10575
|
+
audioLoweredDb = guard.loweredDb;
|
|
10498
10576
|
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
10577
|
+
if (input.keepDeliverySource && !guard.sourceStale && existsSync14(pcmSourcePath)) {
|
|
10578
|
+
deliverySourcePath = pcmSourcePath;
|
|
10579
|
+
}
|
|
10499
10580
|
}
|
|
10500
|
-
return {
|
|
10581
|
+
return {
|
|
10582
|
+
...result,
|
|
10583
|
+
audioLoweredDb,
|
|
10584
|
+
deliverySourcePath,
|
|
10585
|
+
audioProcessMs: Date.now() - stageStart
|
|
10586
|
+
};
|
|
10501
10587
|
} finally {
|
|
10502
|
-
removeScratch2(pcmSourcePath);
|
|
10588
|
+
if (!deliverySourcePath) removeScratch2(pcmSourcePath);
|
|
10503
10589
|
}
|
|
10504
10590
|
}
|
|
10591
|
+
async function guardDeliveryPeaks(input, audioPath, sourcePath, duration) {
|
|
10592
|
+
const limited = await limitDeliveryPeaks({
|
|
10593
|
+
audioPath,
|
|
10594
|
+
sourcePath,
|
|
10595
|
+
targetDurationSeconds: duration,
|
|
10596
|
+
signal: input.abortSignal,
|
|
10597
|
+
timeoutMs: input.ffmpegProcessTimeout,
|
|
10598
|
+
warn: (message, details) => input.log?.warn?.(message, details)
|
|
10599
|
+
});
|
|
10600
|
+
if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
|
|
10601
|
+
const peak = await constrainAacTruePeak({
|
|
10602
|
+
audioPath,
|
|
10603
|
+
sourcePath,
|
|
10604
|
+
targetDurationSeconds: duration,
|
|
10605
|
+
signal: input.abortSignal,
|
|
10606
|
+
timeoutMs: input.ffmpegProcessTimeout
|
|
10607
|
+
});
|
|
10608
|
+
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
10609
|
+
return { loweredDb: peak.loweredDb, sourceStale: peak.sourceStale };
|
|
10610
|
+
}
|
|
10505
10611
|
async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
10506
10612
|
const {
|
|
10507
10613
|
projectDir,
|
|
@@ -10596,7 +10702,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
10596
10702
|
}
|
|
10597
10703
|
|
|
10598
10704
|
// src/services/render/stages/captureStage.ts
|
|
10599
|
-
import { existsSync as
|
|
10705
|
+
import { existsSync as existsSync15, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
|
|
10600
10706
|
import { join as join22 } from "node:path";
|
|
10601
10707
|
import {
|
|
10602
10708
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
@@ -10667,7 +10773,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
10667
10773
|
}
|
|
10668
10774
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
10669
10775
|
let total = sampleDirectoryBytes(framesDir);
|
|
10670
|
-
if (!
|
|
10776
|
+
if (!existsSync15(workDir)) return total;
|
|
10671
10777
|
for (const attempt of readdirSync3(workDir)) {
|
|
10672
10778
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
10673
10779
|
total += sampleDirectoryBytes(join22(workDir, attempt));
|
|
@@ -10970,7 +11076,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
|
|
|
10970
11076
|
}
|
|
10971
11077
|
|
|
10972
11078
|
// src/services/render/stages/captureHdrStage.ts
|
|
10973
|
-
import { existsSync as
|
|
11079
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8 } from "node:fs";
|
|
10974
11080
|
import { join as join26 } from "node:path";
|
|
10975
11081
|
import {
|
|
10976
11082
|
closeCaptureSession as closeCaptureSession7,
|
|
@@ -11216,7 +11322,7 @@ import { Worker } from "node:worker_threads";
|
|
|
11216
11322
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
11217
11323
|
import { dirname as dirname10, join as join24 } from "node:path";
|
|
11218
11324
|
import { createRequire } from "node:module";
|
|
11219
|
-
import { existsSync as
|
|
11325
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
11220
11326
|
import { cpus as cpus2 } from "node:os";
|
|
11221
11327
|
function resolveWorkerEntry(explicit) {
|
|
11222
11328
|
if (explicit && explicit.length > 0) {
|
|
@@ -11229,7 +11335,7 @@ function resolveWorkerEntry(explicit) {
|
|
|
11229
11335
|
}
|
|
11230
11336
|
const moduleDir = dirname10(fileURLToPath3(import.meta.url));
|
|
11231
11337
|
const jsPath = join24(moduleDir, "shaderTransitionWorker.js");
|
|
11232
|
-
if (
|
|
11338
|
+
if (existsSync16(jsPath)) return { path: jsPath, isTs: false };
|
|
11233
11339
|
const tsPath = join24(moduleDir, "shaderTransitionWorker.ts");
|
|
11234
11340
|
return { path: tsPath, isTs: true };
|
|
11235
11341
|
}
|
|
@@ -11790,7 +11896,7 @@ async function runCaptureHdrStage(input) {
|
|
|
11790
11896
|
}
|
|
11791
11897
|
const debugDumpEnabled = process.env.KEEP_TEMP === "1";
|
|
11792
11898
|
const debugDumpDir = debugDumpEnabled ? join26(framesDir, "debug-composite") : null;
|
|
11793
|
-
if (debugDumpDir && !
|
|
11899
|
+
if (debugDumpDir && !existsSync17(debugDumpDir)) {
|
|
11794
11900
|
mkdirSync8(debugDumpDir, { recursive: true });
|
|
11795
11901
|
}
|
|
11796
11902
|
const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
|
|
@@ -11947,10 +12053,10 @@ async function runCaptureHdrStage(input) {
|
|
|
11947
12053
|
// src/services/render/stages/encodeStage.ts
|
|
11948
12054
|
import {
|
|
11949
12055
|
copyFileSync as copyFileSync4,
|
|
11950
|
-
existsSync as
|
|
12056
|
+
existsSync as existsSync18,
|
|
11951
12057
|
mkdirSync as mkdirSync9,
|
|
11952
12058
|
readdirSync as readdirSync4,
|
|
11953
|
-
readFileSync as
|
|
12059
|
+
readFileSync as readFileSync8,
|
|
11954
12060
|
rmSync as rmSync9,
|
|
11955
12061
|
statSync as statSync7,
|
|
11956
12062
|
writeFileSync as writeFileSync5
|
|
@@ -12044,7 +12150,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
|
12044
12150
|
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
12045
12151
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
12046
12152
|
}
|
|
12047
|
-
const gif =
|
|
12153
|
+
const gif = readFileSync8(outputPath);
|
|
12048
12154
|
if (!clearGifFramesBeforeNext(gif)) {
|
|
12049
12155
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
12050
12156
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
@@ -12077,11 +12183,11 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
12077
12183
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
12078
12184
|
}
|
|
12079
12185
|
}
|
|
12080
|
-
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(
|
|
12186
|
+
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync8(outputPath)) !== false) {
|
|
12081
12187
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
12082
12188
|
if (failure) return failure;
|
|
12083
12189
|
}
|
|
12084
|
-
const fileSize =
|
|
12190
|
+
const fileSize = existsSync18(outputPath) ? statSync7(outputPath).size : 0;
|
|
12085
12191
|
return {
|
|
12086
12192
|
success: true,
|
|
12087
12193
|
outputPath,
|
|
@@ -12242,7 +12348,7 @@ async function runEncodeStage(input) {
|
|
|
12242
12348
|
const stage5Start = Date.now();
|
|
12243
12349
|
if (isPngSequence) {
|
|
12244
12350
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
12245
|
-
if (!
|
|
12351
|
+
if (!existsSync18(outputPath)) mkdirSync9(outputPath, { recursive: true });
|
|
12246
12352
|
const captured = readdirSync4(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
12247
12353
|
if (captured.length === 0) {
|
|
12248
12354
|
throw new Error(
|
|
@@ -12253,7 +12359,7 @@ async function runEncodeStage(input) {
|
|
|
12253
12359
|
const dst = join27(outputPath, formatExportFrameName(i, "png"));
|
|
12254
12360
|
copyFileSync4(join27(framesDir, name), dst);
|
|
12255
12361
|
});
|
|
12256
|
-
if (hasAudio && audioOutputPath &&
|
|
12362
|
+
if (hasAudio && audioOutputPath && existsSync18(audioOutputPath)) {
|
|
12257
12363
|
copyFileSync4(audioOutputPath, join27(outputPath, MIXED_AUDIO_FILENAME2));
|
|
12258
12364
|
log.info(
|
|
12259
12365
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
@@ -12364,6 +12470,7 @@ async function assembleOutput(input, onSecondsWritten) {
|
|
|
12364
12470
|
const normalizeResult = await padOrTrimAudioToVideoFrameCount({
|
|
12365
12471
|
videoPath: videoOnlyPath,
|
|
12366
12472
|
audioPath: audioOutputPath,
|
|
12473
|
+
sourcePath: input.audioPcmPath,
|
|
12367
12474
|
outputPath: normalizedAudioPath,
|
|
12368
12475
|
signal: abortSignal,
|
|
12369
12476
|
timeoutMs: ffmpegProcessTimeout
|
|
@@ -12645,7 +12752,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
12645
12752
|
let rangeStart = null;
|
|
12646
12753
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12647
12754
|
const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12648
|
-
const missing = !
|
|
12755
|
+
const missing = !existsSync19(framePath) || statSync8(framePath).size <= 8;
|
|
12649
12756
|
if (missing && rangeStart === null) {
|
|
12650
12757
|
rangeStart = frameIndex;
|
|
12651
12758
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -12770,7 +12877,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
|
12770
12877
|
let captured = 0;
|
|
12771
12878
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12772
12879
|
const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12773
|
-
if (
|
|
12880
|
+
if (existsSync19(framePath)) captured++;
|
|
12774
12881
|
}
|
|
12775
12882
|
return captured;
|
|
12776
12883
|
}
|
|
@@ -13435,7 +13542,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
13435
13542
|
validateDeliveryEncoderConfig(job.config);
|
|
13436
13543
|
const debugDir = resolveRenderDebugDir();
|
|
13437
13544
|
const outputDir = dirname12(outputPath);
|
|
13438
|
-
if (!
|
|
13545
|
+
if (!existsSync19(outputDir)) mkdirSync10(outputDir, { recursive: true });
|
|
13439
13546
|
const workDir = job.config.debug ? join28(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
|
|
13440
13547
|
const pipelineStart = Date.now();
|
|
13441
13548
|
const baseLog = job.config.logger ?? defaultLogger;
|
|
@@ -13577,7 +13684,7 @@ async function executeRenderPipeline(input) {
|
|
|
13577
13684
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
13578
13685
|
assertNotAborted();
|
|
13579
13686
|
assertConfiguredFfmpegBinariesExist();
|
|
13580
|
-
if (!
|
|
13687
|
+
if (!existsSync19(workDir)) mkdirSync10(workDir, { recursive: true });
|
|
13581
13688
|
if (job.config.debug) {
|
|
13582
13689
|
log.info("[Render] Debug artifacts enabled", { workDir, logPath });
|
|
13583
13690
|
}
|
|
@@ -13607,7 +13714,7 @@ async function executeRenderPipeline(input) {
|
|
|
13607
13714
|
});
|
|
13608
13715
|
const entryFile = job.config.entryFile || "index.html";
|
|
13609
13716
|
let htmlPath = join28(projectDir, entryFile);
|
|
13610
|
-
if (!
|
|
13717
|
+
if (!existsSync19(htmlPath)) {
|
|
13611
13718
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
13612
13719
|
}
|
|
13613
13720
|
assertNotAborted();
|
|
@@ -13615,7 +13722,7 @@ async function executeRenderPipeline(input) {
|
|
|
13615
13722
|
if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
|
|
13616
13723
|
const wrapperPath = join28(workDir, "standalone-entry.html");
|
|
13617
13724
|
const projectIndexPath = join28(projectDir, "index.html");
|
|
13618
|
-
if (!
|
|
13725
|
+
if (!existsSync19(projectIndexPath)) {
|
|
13619
13726
|
throw new Error(
|
|
13620
13727
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
13621
13728
|
);
|
|
@@ -13926,7 +14033,8 @@ async function executeRenderPipeline(input) {
|
|
|
13926
14033
|
masterFxChain: composition.masterFxChain,
|
|
13927
14034
|
abortSignal: executionSignal,
|
|
13928
14035
|
assertNotAborted,
|
|
13929
|
-
log
|
|
14036
|
+
log,
|
|
14037
|
+
keepDeliverySource: true
|
|
13930
14038
|
})
|
|
13931
14039
|
);
|
|
13932
14040
|
pendingAudio = audioStage.catch(() => void 0);
|
|
@@ -13958,7 +14066,7 @@ async function executeRenderPipeline(input) {
|
|
|
13958
14066
|
throw new Error("File server failed to initialize before frame capture");
|
|
13959
14067
|
}
|
|
13960
14068
|
const framesDir = join28(workDir, "captured-frames");
|
|
13961
|
-
if (!
|
|
14069
|
+
if (!existsSync19(framesDir)) mkdirSync10(framesDir, { recursive: true });
|
|
13962
14070
|
updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
|
|
13963
14071
|
code: "start_browsers"
|
|
13964
14072
|
});
|
|
@@ -14634,7 +14742,7 @@ async function executeRenderPipeline(input) {
|
|
|
14634
14742
|
const effectiveQuality = job.config.crf ?? preset.quality;
|
|
14635
14743
|
const effectiveBitrate = job.config.crf != null ? void 0 : job.config.videoBitrate;
|
|
14636
14744
|
const audioResult = await audioStage;
|
|
14637
|
-
const { audioOutputPath, hasAudio } = audioResult;
|
|
14745
|
+
const { audioOutputPath, deliverySourcePath, hasAudio } = audioResult;
|
|
14638
14746
|
perfStages.audioProcessMs = audioResult.audioProcessMs;
|
|
14639
14747
|
recordLimiterAttenuation(job, audioResult.audioLoweredDb);
|
|
14640
14748
|
if (audioResult.audioError) {
|
|
@@ -15260,6 +15368,7 @@ async function executeRenderPipeline(input) {
|
|
|
15260
15368
|
job,
|
|
15261
15369
|
videoOnlyPath,
|
|
15262
15370
|
audioOutputPath,
|
|
15371
|
+
audioPcmPath: deliverySourcePath,
|
|
15263
15372
|
outputPath: stagedOutputPath,
|
|
15264
15373
|
hasAudio,
|
|
15265
15374
|
format: outputFormat,
|
|
@@ -15285,7 +15394,7 @@ async function executeRenderPipeline(input) {
|
|
|
15285
15394
|
})
|
|
15286
15395
|
);
|
|
15287
15396
|
const totalElapsed = Date.now() - pipelineStart;
|
|
15288
|
-
const tmpPeakBytes =
|
|
15397
|
+
const tmpPeakBytes = existsSync19(workDir) ? sampleDirectoryBytes(workDir) : 0;
|
|
15289
15398
|
recordTransientRetryObservability();
|
|
15290
15399
|
observability.checkpoint("pipeline", "artifact validated", { totalElapsedMs: totalElapsed });
|
|
15291
15400
|
const observabilitySummary = observability.summary({
|
|
@@ -15366,7 +15475,7 @@ async function executeRenderPipeline(input) {
|
|
|
15366
15475
|
}
|
|
15367
15476
|
}
|
|
15368
15477
|
if (job.config.debug) {
|
|
15369
|
-
if (!isPngSequence && !isHls &&
|
|
15478
|
+
if (!isPngSequence && !isHls && existsSync19(stagedOutputPath)) {
|
|
15370
15479
|
const debugOutput = join28(workDir, `output${videoExt}`);
|
|
15371
15480
|
copyFileSync5(stagedOutputPath, debugOutput);
|
|
15372
15481
|
}
|
|
@@ -15481,7 +15590,7 @@ import {
|
|
|
15481
15590
|
REFERENCE_BACKGROUND_FIELD as REFERENCE_BACKGROUND_FIELD4,
|
|
15482
15591
|
referenceBackgroundResult as referenceBackgroundResult6
|
|
15483
15592
|
} from "@hyperframes/engine";
|
|
15484
|
-
import { mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync7, readFileSync as
|
|
15593
|
+
import { mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync7, readFileSync as readFileSync14, rmSync as rmSync16 } from "node:fs";
|
|
15485
15594
|
import { join as join42 } from "node:path";
|
|
15486
15595
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
15487
15596
|
|
|
@@ -15878,7 +15987,7 @@ import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframe
|
|
|
15878
15987
|
import {
|
|
15879
15988
|
copyFileSync as copyFileSync6,
|
|
15880
15989
|
cpSync as cpSync2,
|
|
15881
|
-
existsSync as
|
|
15990
|
+
existsSync as existsSync24,
|
|
15882
15991
|
linkSync,
|
|
15883
15992
|
mkdirSync as mkdirSync14,
|
|
15884
15993
|
rmSync as rmSync12,
|
|
@@ -15909,7 +16018,7 @@ import {
|
|
|
15909
16018
|
} from "@hyperframes/engine";
|
|
15910
16019
|
|
|
15911
16020
|
// src/services/distributed/chunkFrames.ts
|
|
15912
|
-
import { existsSync as
|
|
16021
|
+
import { existsSync as existsSync21, readdirSync as readdirSync6 } from "node:fs";
|
|
15913
16022
|
import { extname as extname4, join as join31 } from "node:path";
|
|
15914
16023
|
import {
|
|
15915
16024
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
@@ -15921,7 +16030,7 @@ import {
|
|
|
15921
16030
|
// src/services/distributed/shared.ts
|
|
15922
16031
|
import { execFile as execFileCallback } from "node:child_process";
|
|
15923
16032
|
import { dirname as dirname14, join as join30 } from "node:path";
|
|
15924
|
-
import { existsSync as
|
|
16033
|
+
import { existsSync as existsSync20, readFileSync as readFileSync9 } from "node:fs";
|
|
15925
16034
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
15926
16035
|
import { promisify as promisify2 } from "node:util";
|
|
15927
16036
|
import {
|
|
@@ -15937,7 +16046,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
15937
16046
|
function resolvePlanAudioPath(planDir) {
|
|
15938
16047
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
15939
16048
|
const candidate = join30(planDir, name);
|
|
15940
|
-
if (
|
|
16049
|
+
if (existsSync20(candidate)) return candidate;
|
|
15941
16050
|
}
|
|
15942
16051
|
return null;
|
|
15943
16052
|
}
|
|
@@ -16198,9 +16307,9 @@ function readProducerVersion() {
|
|
|
16198
16307
|
let current = startDir;
|
|
16199
16308
|
for (let i = 0; i < 10; i++) {
|
|
16200
16309
|
const candidate = join30(current, "package.json");
|
|
16201
|
-
if (
|
|
16310
|
+
if (existsSync20(candidate)) {
|
|
16202
16311
|
try {
|
|
16203
|
-
const pkg = JSON.parse(
|
|
16312
|
+
const pkg = JSON.parse(readFileSync9(candidate, "utf-8"));
|
|
16204
16313
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
16205
16314
|
cachedProducerVersion = pkg.version;
|
|
16206
16315
|
return pkg.version;
|
|
@@ -16252,7 +16361,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
16252
16361
|
const result = [];
|
|
16253
16362
|
for (const v of videos) {
|
|
16254
16363
|
const outputDir = join31(planDir, "video-frames", v.videoId);
|
|
16255
|
-
if (!
|
|
16364
|
+
if (!existsSync21(outputDir)) {
|
|
16256
16365
|
throw new Error(
|
|
16257
16366
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
16258
16367
|
);
|
|
@@ -16368,7 +16477,7 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
16368
16477
|
const range = video.deferredRange;
|
|
16369
16478
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
16370
16479
|
const sourcePath = remoteUrl ?? join31(input.planDir, ...range.sourcePath.split("/"));
|
|
16371
|
-
if (remoteUrl === void 0 && !
|
|
16480
|
+
if (remoteUrl === void 0 && !existsSync21(sourcePath)) {
|
|
16372
16481
|
throw new RenderChunkValidationError(
|
|
16373
16482
|
MISSING_PLAN_ARTIFACT,
|
|
16374
16483
|
`[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
|
|
@@ -16586,7 +16695,7 @@ async function readPlanReferenceBackground(input) {
|
|
|
16586
16695
|
}
|
|
16587
16696
|
|
|
16588
16697
|
// src/services/render/stages/freezePlan.ts
|
|
16589
|
-
import { existsSync as
|
|
16698
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync12, readFileSync as readFileSync10, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
16590
16699
|
import { join as join34, relative as relative3, resolve as resolve9 } from "node:path";
|
|
16591
16700
|
import {
|
|
16592
16701
|
REFERENCE_BACKGROUND_FIELD,
|
|
@@ -16780,7 +16889,7 @@ function planFileSha256(absolutePath, digests) {
|
|
|
16780
16889
|
return sha256;
|
|
16781
16890
|
}
|
|
16782
16891
|
async function prehashPlanFiles(dir, digests) {
|
|
16783
|
-
if (!
|
|
16892
|
+
if (!existsSync22(dir)) return;
|
|
16784
16893
|
for (const { absolutePath } of listPlanFiles(dir)) {
|
|
16785
16894
|
if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
|
|
16786
16895
|
}
|
|
@@ -16826,7 +16935,7 @@ function collectPlanAssetShas(planDir, digests) {
|
|
|
16826
16935
|
const assets = [];
|
|
16827
16936
|
for (const file of files) {
|
|
16828
16937
|
if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
|
|
16829
|
-
compositionHtml =
|
|
16938
|
+
compositionHtml = readFileSync10(file.absolutePath);
|
|
16830
16939
|
continue;
|
|
16831
16940
|
}
|
|
16832
16941
|
if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
|
|
@@ -16850,19 +16959,19 @@ function writePlanRemoteFiles(planDir, files) {
|
|
|
16850
16959
|
}
|
|
16851
16960
|
function readPlanRemoteFiles(planDir) {
|
|
16852
16961
|
const path = join34(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
16853
|
-
return
|
|
16962
|
+
return existsSync22(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
|
|
16854
16963
|
}
|
|
16855
16964
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
16856
16965
|
const planJsonPath = join34(planDir, "plan.json");
|
|
16857
16966
|
const encoderJsonPath = join34(planDir, "meta", "encoder.json");
|
|
16858
|
-
if (!
|
|
16967
|
+
if (!existsSync22(planJsonPath)) {
|
|
16859
16968
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
16860
16969
|
}
|
|
16861
|
-
if (!
|
|
16970
|
+
if (!existsSync22(encoderJsonPath)) {
|
|
16862
16971
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
16863
16972
|
}
|
|
16864
|
-
const planJson = JSON.parse(
|
|
16865
|
-
const encoderConfigCanonicalJson =
|
|
16973
|
+
const planJson = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
|
|
16974
|
+
const encoderConfigCanonicalJson = readFileSync10(encoderJsonPath, "utf-8");
|
|
16866
16975
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
16867
16976
|
return computePlanHash({
|
|
16868
16977
|
compositionHtml,
|
|
@@ -16888,11 +16997,11 @@ async function freezePlan(input) {
|
|
|
16888
16997
|
hasAudio,
|
|
16889
16998
|
digests
|
|
16890
16999
|
} = input;
|
|
16891
|
-
if (!
|
|
17000
|
+
if (!existsSync22(planDir)) {
|
|
16892
17001
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
16893
17002
|
}
|
|
16894
17003
|
const metaDir = join34(planDir, "meta");
|
|
16895
|
-
if (!
|
|
17004
|
+
if (!existsSync22(metaDir)) mkdirSync12(metaDir, { recursive: true });
|
|
16896
17005
|
writeFileSync7(
|
|
16897
17006
|
join34(metaDir, "composition.json"),
|
|
16898
17007
|
`${JSON.stringify(composition, null, 2)}
|
|
@@ -16969,7 +17078,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
16969
17078
|
}
|
|
16970
17079
|
|
|
16971
17080
|
// src/services/distributed/chunkAudio.ts
|
|
16972
|
-
import { closeSync as closeSync6, existsSync as
|
|
17081
|
+
import { closeSync as closeSync6, existsSync as existsSync23, ftruncateSync, mkdirSync as mkdirSync13, openSync as openSync6, readFileSync as readFileSync11 } from "node:fs";
|
|
16973
17082
|
import { writeFileSync as writeFileSync8, writeSync } from "node:fs";
|
|
16974
17083
|
import { join as join35 } from "node:path";
|
|
16975
17084
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
@@ -17057,7 +17166,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
17057
17166
|
offset += track.sizes[offsets.length - 1];
|
|
17058
17167
|
}
|
|
17059
17168
|
});
|
|
17060
|
-
const bytes =
|
|
17169
|
+
const bytes = readFileSync11(input.audioPath);
|
|
17061
17170
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
17062
17171
|
mkdirSync13(join35(input.planDir, SLICE_DIR), { recursive: true });
|
|
17063
17172
|
input.chunks.forEach((_, index) => {
|
|
@@ -17075,9 +17184,9 @@ async function writeChunkAudioSlices(input) {
|
|
|
17075
17184
|
}
|
|
17076
17185
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
17077
17186
|
const layoutPath = join35(planDir, SLICE_LAYOUT);
|
|
17078
|
-
if (!
|
|
17079
|
-
const layout = JSON.parse(
|
|
17080
|
-
const frame =
|
|
17187
|
+
if (!existsSync23(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
17188
|
+
const layout = JSON.parse(readFileSync11(layoutPath, "utf-8"));
|
|
17189
|
+
const frame = readFileSync11(join35(planDir, SLICE_FRAME));
|
|
17081
17190
|
const fd = openSync6(scratchPath, "w");
|
|
17082
17191
|
try {
|
|
17083
17192
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
@@ -17090,7 +17199,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
17090
17199
|
);
|
|
17091
17200
|
const start = layout.partStarts[chunkIndex];
|
|
17092
17201
|
if (start !== void 0) {
|
|
17093
|
-
const part =
|
|
17202
|
+
const part = readFileSync11(join35(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
17094
17203
|
writeSync(fd, part, 0, part.length, start);
|
|
17095
17204
|
}
|
|
17096
17205
|
ftruncateSync(fd, layout.sizeBytes);
|
|
@@ -17635,7 +17744,7 @@ function createVideoSourceShipper(input) {
|
|
|
17635
17744
|
let result = null;
|
|
17636
17745
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
17637
17746
|
const rel = pathInside(root, srcPath);
|
|
17638
|
-
if (rel !== null && (
|
|
17747
|
+
if (rel !== null && (existsSync24(join37(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
17639
17748
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
17640
17749
|
break;
|
|
17641
17750
|
}
|
|
@@ -17663,7 +17772,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
17663
17772
|
useGpu: false,
|
|
17664
17773
|
browserGpuMode: "software"
|
|
17665
17774
|
});
|
|
17666
|
-
if (!
|
|
17775
|
+
if (!existsSync24(planDir)) mkdirSync14(planDir, { recursive: true });
|
|
17667
17776
|
const log = config.logger ?? defaultLogger;
|
|
17668
17777
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
17669
17778
|
const abortSignal = config.abortSignal;
|
|
@@ -17693,7 +17802,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
17693
17802
|
});
|
|
17694
17803
|
const entryFile = config.entryFile ?? "index.html";
|
|
17695
17804
|
const htmlPath = join37(projectDir, entryFile);
|
|
17696
|
-
if (!
|
|
17805
|
+
if (!existsSync24(htmlPath)) {
|
|
17697
17806
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
17698
17807
|
}
|
|
17699
17808
|
const workDir = join37(planDir, ".plan-work");
|
|
@@ -17922,15 +18031,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
17922
18031
|
logPlanStage(log, "extract", stageStarted);
|
|
17923
18032
|
const stagedVideoFrames = join37(compiledDir, "__hyperframes_video_frames");
|
|
17924
18033
|
const videoFramesDst = join37(planDir, "video-frames");
|
|
17925
|
-
if (
|
|
18034
|
+
if (existsSync24(videoFramesDst)) rmSync12(videoFramesDst, { recursive: true, force: true });
|
|
17926
18035
|
const videoSourcesDir = join37(planDir, "video-sources");
|
|
17927
18036
|
rmSync12(videoSourcesDir, { recursive: true, force: true });
|
|
17928
|
-
if (
|
|
18037
|
+
if (existsSync24(stagedVideoFrames)) {
|
|
17929
18038
|
renameWithRetry8(stagedVideoFrames, videoFramesDst);
|
|
17930
18039
|
} else {
|
|
17931
18040
|
mkdirSync14(videoFramesDst, { recursive: true });
|
|
17932
18041
|
}
|
|
17933
|
-
if (
|
|
18042
|
+
if (existsSync24(finalCompiledDir)) rmSync12(finalCompiledDir, { recursive: true, force: true });
|
|
17934
18043
|
renameWithRetry8(compiledDir, finalCompiledDir);
|
|
17935
18044
|
const shipVideoSource = createVideoSourceShipper({
|
|
17936
18045
|
planDir,
|
|
@@ -18050,10 +18159,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
18050
18159
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
18051
18160
|
}
|
|
18052
18161
|
const planAudioPath = join37(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
18053
|
-
if (audioResult?.hasAudio &&
|
|
18162
|
+
if (audioResult?.hasAudio && existsSync24(audioResult.audioOutputPath)) {
|
|
18054
18163
|
renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
|
|
18055
18164
|
}
|
|
18056
|
-
if (chunksCarryAudio &&
|
|
18165
|
+
if (chunksCarryAudio && existsSync24(planAudioPath)) {
|
|
18057
18166
|
if (options.sliceChunkAudio) {
|
|
18058
18167
|
stageStarted = Date.now();
|
|
18059
18168
|
await writeChunkAudioSlices({
|
|
@@ -18171,7 +18280,7 @@ async function plan(projectDir, config, planDir, options = {}) {
|
|
|
18171
18280
|
|
|
18172
18281
|
// src/services/distributed/renderChunk.ts
|
|
18173
18282
|
import { randomBytes } from "node:crypto";
|
|
18174
|
-
import { existsSync as
|
|
18283
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync17, readFileSync as readFileSync13, readdirSync as readdirSync10, rmSync as rmSync15, writeFileSync as writeFileSync12 } from "node:fs";
|
|
18175
18284
|
import { cpus as cpus3 } from "node:os";
|
|
18176
18285
|
import { join as join41 } from "node:path";
|
|
18177
18286
|
import {
|
|
@@ -18198,11 +18307,11 @@ import {
|
|
|
18198
18307
|
} from "@hyperframes/engine";
|
|
18199
18308
|
import {
|
|
18200
18309
|
copyFileSync as copyFileSync8,
|
|
18201
|
-
existsSync as
|
|
18310
|
+
existsSync as existsSync26,
|
|
18202
18311
|
lstatSync as lstatSync3,
|
|
18203
18312
|
mkdirSync as mkdirSync16,
|
|
18204
18313
|
mkdtempSync as mkdtempSync6,
|
|
18205
|
-
readFileSync as
|
|
18314
|
+
readFileSync as readFileSync12,
|
|
18206
18315
|
readdirSync as readdirSync9,
|
|
18207
18316
|
rmSync as rmSync14,
|
|
18208
18317
|
statSync as statSync10,
|
|
@@ -18216,7 +18325,7 @@ import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as r
|
|
|
18216
18325
|
// src/services/distributed/planV2Publisher.ts
|
|
18217
18326
|
import {
|
|
18218
18327
|
copyFileSync as copyFileSync7,
|
|
18219
|
-
existsSync as
|
|
18328
|
+
existsSync as existsSync25,
|
|
18220
18329
|
linkSync as linkSync2,
|
|
18221
18330
|
mkdirSync as mkdirSync15,
|
|
18222
18331
|
mkdtempSync as mkdtempSync5,
|
|
@@ -18285,7 +18394,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18285
18394
|
#linkFile;
|
|
18286
18395
|
#committed = false;
|
|
18287
18396
|
constructor(destinationDir, options = {}) {
|
|
18288
|
-
if (
|
|
18397
|
+
if (existsSync25(destinationDir)) {
|
|
18289
18398
|
throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
|
|
18290
18399
|
}
|
|
18291
18400
|
this.destinationDir = destinationDir;
|
|
@@ -18302,7 +18411,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18302
18411
|
);
|
|
18303
18412
|
}
|
|
18304
18413
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
18305
|
-
if (
|
|
18414
|
+
if (existsSync25(destinationPath)) return;
|
|
18306
18415
|
mkdirSync15(dirname15(destinationPath), { recursive: true });
|
|
18307
18416
|
const stagingDir = mkdtempSync5(join39(dirname15(destinationPath), ".plan-v2-blob-"));
|
|
18308
18417
|
const temporaryPath = join39(stagingDir, "blob");
|
|
@@ -18320,7 +18429,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
18320
18429
|
}
|
|
18321
18430
|
async commitManifest(manifestBytes) {
|
|
18322
18431
|
for (const digest of new Set(manifestDigests(manifestBytes))) {
|
|
18323
|
-
if (!
|
|
18432
|
+
if (!existsSync25(planV2BlobPath(this.temporaryDir, digest))) {
|
|
18324
18433
|
throw new PlanV2IntegrityError(
|
|
18325
18434
|
`cannot commit manifest before referenced blob is durable: ${digest}`
|
|
18326
18435
|
);
|
|
@@ -18351,7 +18460,7 @@ function isNonNegativeIntegerArray(value) {
|
|
|
18351
18460
|
return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
|
|
18352
18461
|
}
|
|
18353
18462
|
function readJsonFile(path, label) {
|
|
18354
|
-
const contents =
|
|
18463
|
+
const contents = readFileSync12(path, "utf-8");
|
|
18355
18464
|
try {
|
|
18356
18465
|
const parsed = JSON.parse(contents);
|
|
18357
18466
|
return parsed;
|
|
@@ -18399,7 +18508,7 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
18399
18508
|
}
|
|
18400
18509
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
18401
18510
|
const videoRoot = join40(executionPlanDir, "video-frames");
|
|
18402
|
-
if (!
|
|
18511
|
+
if (!existsSync26(videoRoot)) return;
|
|
18403
18512
|
for (const videoEntry of readdirSync9(videoRoot, { withFileTypes: true })) {
|
|
18404
18513
|
if (!videoEntry.isDirectory()) continue;
|
|
18405
18514
|
const videoDir = join40(videoRoot, videoEntry.name);
|
|
@@ -18500,7 +18609,7 @@ function parsePlanVideosJson2(value) {
|
|
|
18500
18609
|
}
|
|
18501
18610
|
function materializeExtractedVideoDirectories(planDir) {
|
|
18502
18611
|
const videosPath = join40(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18503
|
-
if (!
|
|
18612
|
+
if (!existsSync26(videosPath)) return;
|
|
18504
18613
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
18505
18614
|
for (const video of videos.extracted) {
|
|
18506
18615
|
mkdirSync16(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
@@ -18534,9 +18643,9 @@ function parseChunkSlices(value) {
|
|
|
18534
18643
|
}
|
|
18535
18644
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
18536
18645
|
const videoRoot = join40(executionPlanDir, "video-frames");
|
|
18537
|
-
const hasExtractedFrames =
|
|
18646
|
+
const hasExtractedFrames = existsSync26(videoRoot) && listFiles(videoRoot).length > 0;
|
|
18538
18647
|
const videosPath = join40(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
18539
|
-
if (!
|
|
18648
|
+
if (!existsSync26(videosPath)) {
|
|
18540
18649
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
18541
18650
|
}
|
|
18542
18651
|
const chunksPath = join40(executionPlanDir, "meta", "chunks.json");
|
|
@@ -18593,7 +18702,7 @@ function computeManifestHash(manifest) {
|
|
|
18593
18702
|
return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
|
|
18594
18703
|
}
|
|
18595
18704
|
function writeBlob(sourcePath, destinationPath) {
|
|
18596
|
-
if (
|
|
18705
|
+
if (existsSync26(destinationPath)) return;
|
|
18597
18706
|
mkdirSync16(dirname16(destinationPath), { recursive: true });
|
|
18598
18707
|
const temporaryDir = mkdtempSync6(join40(dirname16(destinationPath), ".plan-v2-blob-"));
|
|
18599
18708
|
const temporaryPath = join40(temporaryDir, "blob");
|
|
@@ -18633,7 +18742,7 @@ async function putTasks(tasks) {
|
|
|
18633
18742
|
}
|
|
18634
18743
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
18635
18744
|
const executionPlanPath = join40(executionPlanDir, "plan.json");
|
|
18636
|
-
if (!
|
|
18745
|
+
if (!existsSync26(executionPlanPath)) {
|
|
18637
18746
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
18638
18747
|
}
|
|
18639
18748
|
const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
|
|
@@ -18713,7 +18822,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
18713
18822
|
};
|
|
18714
18823
|
}
|
|
18715
18824
|
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
18716
|
-
if (
|
|
18825
|
+
if (existsSync26(planV2Dir)) {
|
|
18717
18826
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
18718
18827
|
}
|
|
18719
18828
|
const publication = buildPlanV2Publication(executionPlanDir);
|
|
@@ -18799,7 +18908,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
18799
18908
|
}
|
|
18800
18909
|
}
|
|
18801
18910
|
async function planV2(projectDir, config, planV2Dir) {
|
|
18802
|
-
if (
|
|
18911
|
+
if (existsSync26(planV2Dir)) {
|
|
18803
18912
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
18804
18913
|
}
|
|
18805
18914
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
@@ -18953,7 +19062,7 @@ function parsePlanV2Manifest(value) {
|
|
|
18953
19062
|
}
|
|
18954
19063
|
function readPlanV2Manifest(planV2Dir) {
|
|
18955
19064
|
const manifestPath = join40(planV2Dir, "plan.json");
|
|
18956
|
-
if (!
|
|
19065
|
+
if (!existsSync26(manifestPath)) {
|
|
18957
19066
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
18958
19067
|
}
|
|
18959
19068
|
const value = readJsonFile(manifestPath, "v2 manifest");
|
|
@@ -18972,7 +19081,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
|
|
|
18972
19081
|
}
|
|
18973
19082
|
function verifyBlob(planV2Dir, artifact) {
|
|
18974
19083
|
const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
|
|
18975
|
-
if (!
|
|
19084
|
+
if (!existsSync26(sourcePath)) {
|
|
18976
19085
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
18977
19086
|
}
|
|
18978
19087
|
const stats = statSync10(sourcePath);
|
|
@@ -18985,7 +19094,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
18985
19094
|
return sourcePath;
|
|
18986
19095
|
}
|
|
18987
19096
|
function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
|
|
18988
|
-
if (
|
|
19097
|
+
if (existsSync26(destinationDir)) {
|
|
18989
19098
|
throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
|
|
18990
19099
|
}
|
|
18991
19100
|
const manifest = readPlanV2Manifest(planV2Dir);
|
|
@@ -19050,7 +19159,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
19050
19159
|
}
|
|
19051
19160
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
19052
19161
|
const markerPath = join40(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
19053
|
-
if (!
|
|
19162
|
+
if (!existsSync26(markerPath)) return null;
|
|
19054
19163
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
19055
19164
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
19056
19165
|
throw new PlanV2IntegrityError("malformed materialization marker");
|
|
@@ -19068,7 +19177,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
19068
19177
|
for (const artifact of required) {
|
|
19069
19178
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
19070
19179
|
const path = join40(planDir, ...artifact.path.split("/"));
|
|
19071
|
-
if (!
|
|
19180
|
+
if (!existsSync26(path) || statSync10(path).size !== artifact.sizeBytes) {
|
|
19072
19181
|
throw new PlanV2IntegrityError(
|
|
19073
19182
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
19074
19183
|
);
|
|
@@ -19185,10 +19294,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
|
|
|
19185
19294
|
return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
|
|
19186
19295
|
}
|
|
19187
19296
|
function hashChunkOutput(outputPath, kind) {
|
|
19188
|
-
if (kind === "file") return sha256Hex(
|
|
19297
|
+
if (kind === "file") return sha256Hex(readFileSync13(outputPath));
|
|
19189
19298
|
const entries = readdirSync10(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
19190
19299
|
const lines = entries.map(
|
|
19191
|
-
(name) => `${name}\0${sha256Hex(
|
|
19300
|
+
(name) => `${name}\0${sha256Hex(readFileSync13(join41(outputPath, name)))}`
|
|
19192
19301
|
);
|
|
19193
19302
|
return sha256Hex(lines.join("\0"));
|
|
19194
19303
|
}
|
|
@@ -19208,13 +19317,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19208
19317
|
const planJsonPath = join41(planDir, "plan.json");
|
|
19209
19318
|
const encoderJsonPath = join41(planDir, "meta", "encoder.json");
|
|
19210
19319
|
const chunksJsonPath = join41(planDir, "meta", "chunks.json");
|
|
19211
|
-
if (!
|
|
19320
|
+
if (!existsSync27(planJsonPath)) {
|
|
19212
19321
|
throw new RenderChunkValidationError(
|
|
19213
19322
|
MISSING_PLAN_ARTIFACT,
|
|
19214
19323
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
19215
19324
|
);
|
|
19216
19325
|
}
|
|
19217
|
-
const plan2 = JSON.parse(
|
|
19326
|
+
const plan2 = JSON.parse(readFileSync13(planJsonPath, "utf-8"));
|
|
19218
19327
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
|
|
19219
19328
|
const planHashStarted = Date.now();
|
|
19220
19329
|
const v2Materialization = readValidatedPlanV2Materialization(planDir, {
|
|
@@ -19224,20 +19333,20 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19224
19333
|
const v2Manifest = v2Materialization?.manifest ?? null;
|
|
19225
19334
|
const remoteSources = v2Materialization?.remoteSources ?? {};
|
|
19226
19335
|
for (const required of [encoderJsonPath, chunksJsonPath]) {
|
|
19227
|
-
if (!
|
|
19336
|
+
if (!existsSync27(required)) {
|
|
19228
19337
|
throw new RenderChunkValidationError(
|
|
19229
19338
|
MISSING_PLAN_ARTIFACT,
|
|
19230
19339
|
`[renderChunk] planDir is missing required artifact: ${required}`
|
|
19231
19340
|
);
|
|
19232
19341
|
}
|
|
19233
19342
|
}
|
|
19234
|
-
const encoder = JSON.parse(
|
|
19235
|
-
const chunks = JSON.parse(
|
|
19343
|
+
const encoder = JSON.parse(readFileSync13(encoderJsonPath, "utf-8"));
|
|
19344
|
+
const chunks = JSON.parse(readFileSync13(chunksJsonPath, "utf-8"));
|
|
19236
19345
|
const videosJsonPath = join41(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
19237
19346
|
let planVideos = null;
|
|
19238
|
-
if (
|
|
19347
|
+
if (existsSync27(videosJsonPath)) {
|
|
19239
19348
|
try {
|
|
19240
|
-
planVideos = validatePlanVideosForChunk(JSON.parse(
|
|
19349
|
+
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync13(videosJsonPath, "utf-8")));
|
|
19241
19350
|
} catch (err) {
|
|
19242
19351
|
if (err instanceof RenderChunkValidationError) throw err;
|
|
19243
19352
|
throw new RenderChunkValidationError(
|
|
@@ -19266,7 +19375,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19266
19375
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
19267
19376
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
19268
19377
|
const compiledDir = join41(planDir, "compiled");
|
|
19269
|
-
if (!
|
|
19378
|
+
if (!existsSync27(compiledDir)) {
|
|
19270
19379
|
throw new RenderChunkValidationError(
|
|
19271
19380
|
MISSING_PLAN_ARTIFACT,
|
|
19272
19381
|
`[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
|
|
@@ -19348,7 +19457,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19348
19457
|
dimensions: plan2.dimensions,
|
|
19349
19458
|
encoder,
|
|
19350
19459
|
videoCount: planVideos?.videos.length ?? 0,
|
|
19351
|
-
html:
|
|
19460
|
+
html: readFileSync13(join41(compiledDir, "index.html"), "utf-8"),
|
|
19352
19461
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
19353
19462
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
19354
19463
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -19516,10 +19625,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19516
19625
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
19517
19626
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
19518
19627
|
if (isPngSequence) {
|
|
19519
|
-
if (!
|
|
19628
|
+
if (!existsSync27(outputChunkPath)) mkdirSync17(outputChunkPath, { recursive: true });
|
|
19520
19629
|
} else {
|
|
19521
19630
|
const outDir = join41(outputChunkPath, "..");
|
|
19522
|
-
if (!
|
|
19631
|
+
if (!existsSync27(outDir)) mkdirSync17(outDir, { recursive: true });
|
|
19523
19632
|
}
|
|
19524
19633
|
const encodeInput = {
|
|
19525
19634
|
job,
|
|
@@ -19810,7 +19919,7 @@ async function renderSpan(projectDir, config, span, outputPath) {
|
|
|
19810
19919
|
planned.totalFrames
|
|
19811
19920
|
);
|
|
19812
19921
|
const slices = JSON.parse(
|
|
19813
|
-
|
|
19922
|
+
readFileSync14(join42(planDir, "meta", "chunks.json"), "utf-8")
|
|
19814
19923
|
);
|
|
19815
19924
|
const parts = [];
|
|
19816
19925
|
for (const slice of slices) {
|
|
@@ -20419,7 +20528,7 @@ import {
|
|
|
20419
20528
|
|
|
20420
20529
|
// src/server.ts
|
|
20421
20530
|
import {
|
|
20422
|
-
existsSync as
|
|
20531
|
+
existsSync as existsSync30,
|
|
20423
20532
|
mkdirSync as mkdirSync19,
|
|
20424
20533
|
statSync as statSync12,
|
|
20425
20534
|
mkdtempSync as mkdtempSync8,
|
|
@@ -20442,8 +20551,8 @@ import {
|
|
|
20442
20551
|
|
|
20443
20552
|
// src/services/hyperframeLint.ts
|
|
20444
20553
|
import {
|
|
20445
|
-
existsSync as
|
|
20446
|
-
readFileSync as
|
|
20554
|
+
existsSync as existsSync28,
|
|
20555
|
+
readFileSync as readFileSync15,
|
|
20447
20556
|
statSync as statSync11,
|
|
20448
20557
|
openSync as openSync7,
|
|
20449
20558
|
fstatSync as fstatSync3,
|
|
@@ -20489,14 +20598,14 @@ function readEntryHtml(filePath) {
|
|
|
20489
20598
|
}
|
|
20490
20599
|
try {
|
|
20491
20600
|
if (!fstatSync3(fd).isFile()) return null;
|
|
20492
|
-
return
|
|
20601
|
+
return readFileSync15(fd, "utf-8");
|
|
20493
20602
|
} finally {
|
|
20494
20603
|
closeSync7(fd);
|
|
20495
20604
|
}
|
|
20496
20605
|
}
|
|
20497
20606
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
20498
20607
|
const absProjectDir = resolve11(projectDir);
|
|
20499
|
-
if (!
|
|
20608
|
+
if (!existsSync28(absProjectDir) || !statSync11(absProjectDir).isDirectory()) {
|
|
20500
20609
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
20501
20610
|
}
|
|
20502
20611
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -20565,7 +20674,7 @@ async function runHyperframeLint(prepared) {
|
|
|
20565
20674
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
20566
20675
|
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
20567
20676
|
import { dirname as dirname17, join as join44 } from "node:path";
|
|
20568
|
-
import { existsSync as
|
|
20677
|
+
import { existsSync as existsSync29 } from "node:fs";
|
|
20569
20678
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
20570
20679
|
async function startHealthWorker(options = {}) {
|
|
20571
20680
|
const log = options.logger ?? defaultLogger2();
|
|
@@ -20642,7 +20751,7 @@ function resolveWorkerEntry2() {
|
|
|
20642
20751
|
const here = dirname17(fileURLToPath6(import.meta.url));
|
|
20643
20752
|
const candidates = [join44(here, "healthWorkerThread.js"), join44(here, "healthWorkerThread.ts")];
|
|
20644
20753
|
for (const candidate of candidates) {
|
|
20645
|
-
if (
|
|
20754
|
+
if (existsSync29(candidate)) return candidate;
|
|
20646
20755
|
}
|
|
20647
20756
|
return void 0;
|
|
20648
20757
|
}
|
|
@@ -20788,7 +20897,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
|
20788
20897
|
const { input, cleanupProjectDir } = prepared;
|
|
20789
20898
|
const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
|
|
20790
20899
|
const outputDir = dirname18(absoluteOutputPath);
|
|
20791
|
-
if (!
|
|
20900
|
+
if (!existsSync30(outputDir)) mkdirSync19(outputDir, { recursive: true });
|
|
20792
20901
|
return { input, cleanupProjectDir, absoluteOutputPath };
|
|
20793
20902
|
}
|
|
20794
20903
|
function validateRenderOverrides(body) {
|
|
@@ -20829,11 +20938,11 @@ function prepareProjectDirectory(projectDir, options) {
|
|
|
20829
20938
|
const candidate = nonEmptyString(projectDir);
|
|
20830
20939
|
if (!candidate) return null;
|
|
20831
20940
|
const absProjectDir = resolve12(candidate);
|
|
20832
|
-
if (!
|
|
20941
|
+
if (!existsSync30(absProjectDir) || !statSync12(absProjectDir).isDirectory()) {
|
|
20833
20942
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
20834
20943
|
}
|
|
20835
20944
|
const entry = options.entryFile || "index.html";
|
|
20836
|
-
if (!
|
|
20945
|
+
if (!existsSync30(resolve12(absProjectDir, entry))) {
|
|
20837
20946
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
20838
20947
|
}
|
|
20839
20948
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -20925,7 +21034,7 @@ function cleanupTempDir(dir, log) {
|
|
|
20925
21034
|
}
|
|
20926
21035
|
}
|
|
20927
21036
|
function outputFileSize(path) {
|
|
20928
|
-
return
|
|
21037
|
+
return existsSync30(path) ? statSync12(path).size : 0;
|
|
20929
21038
|
}
|
|
20930
21039
|
function createBlockingProgressReporter(log, requestId) {
|
|
20931
21040
|
let lastLoggedPct = -10;
|
|
@@ -21229,7 +21338,7 @@ function createRenderHandlers(options = {}) {
|
|
|
21229
21338
|
if (!artifact) {
|
|
21230
21339
|
return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
|
|
21231
21340
|
}
|
|
21232
|
-
if (!
|
|
21341
|
+
if (!existsSync30(artifact.path)) {
|
|
21233
21342
|
store.delete(token);
|
|
21234
21343
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
21235
21344
|
}
|
|
@@ -21335,10 +21444,10 @@ import {
|
|
|
21335
21444
|
} from "@hyperframes/engine";
|
|
21336
21445
|
import {
|
|
21337
21446
|
cpSync as cpSync3,
|
|
21338
|
-
existsSync as
|
|
21447
|
+
existsSync as existsSync31,
|
|
21339
21448
|
mkdirSync as mkdirSync20,
|
|
21340
21449
|
mkdtempSync as mkdtempSync9,
|
|
21341
|
-
readFileSync as
|
|
21450
|
+
readFileSync as readFileSync16,
|
|
21342
21451
|
readdirSync as readdirSync11,
|
|
21343
21452
|
rmSync as rmSync18,
|
|
21344
21453
|
statSync as statSync13,
|
|
@@ -21390,7 +21499,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21390
21499
|
}
|
|
21391
21500
|
const format = plan2.dimensions.format;
|
|
21392
21501
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
21393
|
-
if (!
|
|
21502
|
+
if (!existsSync31(dirname19(outputPath))) {
|
|
21394
21503
|
mkdirSync20(dirname19(outputPath), { recursive: true });
|
|
21395
21504
|
}
|
|
21396
21505
|
const workDir = mkdtempSync9(`${outputPath}.assemble-work-`);
|
|
@@ -21414,7 +21523,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21414
21523
|
try {
|
|
21415
21524
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
21416
21525
|
const finalPath = join46(workDir, `output.${format}`);
|
|
21417
|
-
const audio = audioPath !== null &&
|
|
21526
|
+
const audio = audioPath !== null && existsSync31(audioPath) ? audioPath : null;
|
|
21418
21527
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
21419
21528
|
renameWithRetry11(finalPath, outputPath);
|
|
21420
21529
|
log.info("[assemble] stage timings", {
|
|
@@ -21432,7 +21541,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21432
21541
|
});
|
|
21433
21542
|
}
|
|
21434
21543
|
}
|
|
21435
|
-
const fileSize =
|
|
21544
|
+
const fileSize = existsSync31(outputPath) ? statSync13(outputPath).size : 0;
|
|
21436
21545
|
return {
|
|
21437
21546
|
...referenceBackgroundResult8(plan2[REFERENCE_BACKGROUND_FIELD6]),
|
|
21438
21547
|
outputPath,
|
|
@@ -21444,16 +21553,16 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
21444
21553
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
21445
21554
|
const planJsonPath = join46(planDir, "plan.json");
|
|
21446
21555
|
const chunksJsonPath = join46(planDir, "meta", "chunks.json");
|
|
21447
|
-
if (!
|
|
21556
|
+
if (!existsSync31(planJsonPath)) {
|
|
21448
21557
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
21449
21558
|
}
|
|
21450
|
-
const plan2 = JSON.parse(
|
|
21559
|
+
const plan2 = JSON.parse(readFileSync16(planJsonPath, "utf-8"));
|
|
21451
21560
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
21452
21561
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
21453
|
-
if (!
|
|
21562
|
+
if (!existsSync31(chunksJsonPath)) {
|
|
21454
21563
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
21455
21564
|
}
|
|
21456
|
-
const chunks = JSON.parse(
|
|
21565
|
+
const chunks = JSON.parse(readFileSync16(chunksJsonPath, "utf-8"));
|
|
21457
21566
|
if (chunkPaths.length !== chunks.length) {
|
|
21458
21567
|
throw new Error(
|
|
21459
21568
|
`[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
|
|
@@ -21465,7 +21574,7 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
21465
21574
|
}
|
|
21466
21575
|
}
|
|
21467
21576
|
for (const path of chunkPaths.flat()) {
|
|
21468
|
-
if (!
|
|
21577
|
+
if (!existsSync31(path)) {
|
|
21469
21578
|
throw new Error(`[assemble] chunk path does not exist: ${path}`);
|
|
21470
21579
|
}
|
|
21471
21580
|
}
|
|
@@ -21599,13 +21708,13 @@ function stitchesSeparateAudio(planDir) {
|
|
|
21599
21708
|
return lockedAudio(planDir).separateAudio === true;
|
|
21600
21709
|
}
|
|
21601
21710
|
function planFormat(planDir) {
|
|
21602
|
-
const plan2 = JSON.parse(
|
|
21711
|
+
const plan2 = JSON.parse(readFileSync16(join46(planDir, "plan.json"), "utf-8"));
|
|
21603
21712
|
return plan2.dimensions.format;
|
|
21604
21713
|
}
|
|
21605
21714
|
function lockedAudio(planDir) {
|
|
21606
21715
|
const encoderJsonPath = join46(planDir, "meta", "encoder.json");
|
|
21607
|
-
if (!
|
|
21608
|
-
return JSON.parse(
|
|
21716
|
+
if (!existsSync31(encoderJsonPath)) return {};
|
|
21717
|
+
return JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
|
|
21609
21718
|
}
|
|
21610
21719
|
function readCfrEncoder(planDir, format) {
|
|
21611
21720
|
if (format !== "mp4") {
|
|
@@ -21614,10 +21723,10 @@ function readCfrEncoder(planDir, format) {
|
|
|
21614
21723
|
);
|
|
21615
21724
|
}
|
|
21616
21725
|
const encoderJsonPath = join46(planDir, "meta", "encoder.json");
|
|
21617
|
-
if (!
|
|
21726
|
+
if (!existsSync31(encoderJsonPath)) {
|
|
21618
21727
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
21619
21728
|
}
|
|
21620
|
-
const encoderJson = JSON.parse(
|
|
21729
|
+
const encoderJson = JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
|
|
21621
21730
|
if (encoderJson.encoder === "libx265-software") {
|
|
21622
21731
|
throw new Error(
|
|
21623
21732
|
`[assemble] cfr=true is not yet supported with codec: "h265". The cfr re-encode pass uses libx264 and would silently transcode the h265 chunks. Either disable cfr or render with codec: "h264".`
|
|
@@ -21626,7 +21735,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
21626
21735
|
return encoderJson;
|
|
21627
21736
|
}
|
|
21628
21737
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
21629
|
-
if (
|
|
21738
|
+
if (existsSync31(outputPath)) rmSync18(outputPath, { recursive: true, force: true });
|
|
21630
21739
|
mkdirSync20(outputPath, { recursive: true });
|
|
21631
21740
|
let globalIdx = 0;
|
|
21632
21741
|
for (const chunkDir of chunkPaths) {
|
|
@@ -21650,7 +21759,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
21650
21759
|
`[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
|
|
21651
21760
|
);
|
|
21652
21761
|
}
|
|
21653
|
-
if (audioPath !== null &&
|
|
21762
|
+
if (audioPath !== null && existsSync31(audioPath)) {
|
|
21654
21763
|
const sidecar = join46(outputPath, MIXED_AUDIO_FILENAME4);
|
|
21655
21764
|
cpSync3(audioPath, sidecar);
|
|
21656
21765
|
}
|