@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/public-server.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/server.ts
|
|
4
4
|
import {
|
|
5
|
-
existsSync as
|
|
5
|
+
existsSync as existsSync22,
|
|
6
6
|
mkdirSync as mkdirSync11,
|
|
7
7
|
statSync as statSync10,
|
|
8
8
|
mkdtempSync as mkdtempSync4,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
// src/services/renderOrchestrator.ts
|
|
27
27
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
28
28
|
import {
|
|
29
|
-
existsSync as
|
|
29
|
+
existsSync as existsSync19,
|
|
30
30
|
mkdirSync as mkdirSync10,
|
|
31
31
|
readdirSync as readdirSync5,
|
|
32
32
|
rmSync as rmSync10,
|
|
@@ -3754,6 +3754,7 @@ function createStallClock(now = () => Date.now()) {
|
|
|
3754
3754
|
|
|
3755
3755
|
// src/services/render/stages/captureStreamingStage.ts
|
|
3756
3756
|
var DEFAULT_CAPTURE_STALL_MS = 6e4;
|
|
3757
|
+
var SESSION_INIT_STALL_MS = 3e5;
|
|
3757
3758
|
var DE_STALL_POLL_MS = 5e3;
|
|
3758
3759
|
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.";
|
|
3759
3760
|
function poolFailure(error, probeSession) {
|
|
@@ -3784,6 +3785,14 @@ function captureModeLabel(mode) {
|
|
|
3784
3785
|
if (mode === "beginframe") return "BeginFrame";
|
|
3785
3786
|
return "screenshot";
|
|
3786
3787
|
}
|
|
3788
|
+
var OPENING_CAPTURE_SESSION = {
|
|
3789
|
+
captureMode: "screenshot",
|
|
3790
|
+
captureStep: "opening the capture session"
|
|
3791
|
+
};
|
|
3792
|
+
var OPENING_RISK_SESSION = {
|
|
3793
|
+
captureMode: "screenshot",
|
|
3794
|
+
captureStep: "opening the screenshot session for effect frames"
|
|
3795
|
+
};
|
|
3787
3796
|
var SequentialCaptureStallError = class extends Error {
|
|
3788
3797
|
captureMode;
|
|
3789
3798
|
constructor(session, stallTimeoutMs, frameIndex, totalFrames) {
|
|
@@ -3933,7 +3942,12 @@ async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEnc
|
|
|
3933
3942
|
const guard = createDrainFrameGuard({ log, stats, frameTime });
|
|
3934
3943
|
const guardFrame = (item, buf) => item.trusted ? Promise.resolve(buf) : guard(session, item.idx, buf);
|
|
3935
3944
|
const captureRisk = async (idx) => {
|
|
3936
|
-
await
|
|
3945
|
+
await raceAgainstStall(
|
|
3946
|
+
risk.open(),
|
|
3947
|
+
stallTimeoutMs - quiet.idle(0),
|
|
3948
|
+
{ session: OPENING_RISK_SESSION, frameIndex: idx, totalFrames, stallTimeoutMs },
|
|
3949
|
+
abortSignal
|
|
3950
|
+
);
|
|
3937
3951
|
quiet.reset();
|
|
3938
3952
|
return raceAgainstStall(
|
|
3939
3953
|
risk.capture(idx, frameTime(idx)),
|
|
@@ -4292,19 +4306,38 @@ async function runCaptureStreamingStage(input) {
|
|
|
4292
4306
|
let resumes = 0;
|
|
4293
4307
|
for (; ; ) {
|
|
4294
4308
|
const videoInjector = createRenderVideoFrameInjector();
|
|
4295
|
-
const
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4309
|
+
const initStallMs = Math.max(SESSION_INIT_STALL_MS, resolveCaptureStallTimeoutMs());
|
|
4310
|
+
const setup = createStallClock();
|
|
4311
|
+
const withinInitBudget = (work, stalled) => raceAgainstStall(
|
|
4312
|
+
work,
|
|
4313
|
+
initStallMs - setup.idle(0),
|
|
4314
|
+
{ session: stalled, frameIndex: resume.from, totalFrames, stallTimeoutMs: initStallMs },
|
|
4315
|
+
abortSignal
|
|
4301
4316
|
);
|
|
4317
|
+
const openSession = async () => {
|
|
4318
|
+
const opening = createCaptureSession(
|
|
4319
|
+
fileServer.url,
|
|
4320
|
+
framesDir,
|
|
4321
|
+
buildCaptureOptions(),
|
|
4322
|
+
videoInjector,
|
|
4323
|
+
captureCfg
|
|
4324
|
+
);
|
|
4325
|
+
try {
|
|
4326
|
+
return await withinInitBudget(opening, OPENING_CAPTURE_SESSION);
|
|
4327
|
+
} catch (error) {
|
|
4328
|
+
void opening.then(closeCaptureSession, () => {
|
|
4329
|
+
});
|
|
4330
|
+
throw error;
|
|
4331
|
+
}
|
|
4332
|
+
};
|
|
4333
|
+
const session = probeSession ?? await openSession();
|
|
4302
4334
|
captureBeyondViewport = session.options.captureBeyondViewport;
|
|
4303
4335
|
if (probeSession) {
|
|
4304
4336
|
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
4305
4337
|
probeSession = null;
|
|
4306
4338
|
}
|
|
4307
4339
|
let riskSession = null;
|
|
4340
|
+
let attemptClosed = false;
|
|
4308
4341
|
const openRiskSession = async () => {
|
|
4309
4342
|
const opened = await createCaptureSession(
|
|
4310
4343
|
fileServer.url,
|
|
@@ -4318,6 +4351,10 @@ async function runCaptureStreamingStage(input) {
|
|
|
4318
4351
|
enableBrowserPool: false
|
|
4319
4352
|
}
|
|
4320
4353
|
);
|
|
4354
|
+
if (attemptClosed) {
|
|
4355
|
+
await closeCaptureSession(opened);
|
|
4356
|
+
throw new Error("[Render] Capture attempt ended while its screenshot session opened");
|
|
4357
|
+
}
|
|
4321
4358
|
riskSession = opened;
|
|
4322
4359
|
await initializeSession(opened);
|
|
4323
4360
|
return opened;
|
|
@@ -4325,9 +4362,9 @@ async function runCaptureStreamingStage(input) {
|
|
|
4325
4362
|
let resumed = false;
|
|
4326
4363
|
try {
|
|
4327
4364
|
if (!session.isInitialized) {
|
|
4328
|
-
await initializeSession(session);
|
|
4365
|
+
await withinInitBudget(initializeSession(session), session);
|
|
4329
4366
|
}
|
|
4330
|
-
await completeDeferredDrawElementInit(session);
|
|
4367
|
+
await withinInitBudget(completeDeferredDrawElementInit(session), session);
|
|
4331
4368
|
assertNotAborted();
|
|
4332
4369
|
lastBrowserConsole = session.browserConsoleBuffer;
|
|
4333
4370
|
if (session.workerEncodeEnabled) {
|
|
@@ -4410,6 +4447,7 @@ async function runCaptureStreamingStage(input) {
|
|
|
4410
4447
|
resumes++;
|
|
4411
4448
|
resumed = true;
|
|
4412
4449
|
} finally {
|
|
4450
|
+
attemptClosed = true;
|
|
4413
4451
|
lastBrowserConsole = session.browserConsoleBuffer;
|
|
4414
4452
|
await closeCaptureSession(session);
|
|
4415
4453
|
if (riskSession) await closeCaptureSession(riskSession);
|
|
@@ -5876,7 +5914,7 @@ import { suggestMatchingPreset } from "@hyperframes/core";
|
|
|
5876
5914
|
// src/services/htmlCompiler.ts
|
|
5877
5915
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
5878
5916
|
import { createHash as createHash6 } from "node:crypto";
|
|
5879
|
-
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
|
|
5917
|
+
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7 } from "fs";
|
|
5880
5918
|
import { join as join15, dirname as dirname7, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
|
|
5881
5919
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
5882
5920
|
import {
|
|
@@ -7851,6 +7889,25 @@ ${fontFaceBlocks.join("\n")}
|
|
|
7851
7889
|
}
|
|
7852
7890
|
return result;
|
|
7853
7891
|
}
|
|
7892
|
+
function projectStylesheetFontFamilies(html, projectDir) {
|
|
7893
|
+
const families = [];
|
|
7894
|
+
for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
|
|
7895
|
+
const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
7896
|
+
if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
|
|
7897
|
+
const sheet = resolve7(projectDir, decodedUrlPath(href).replace(/^(?:\.?\/)+/, ""));
|
|
7898
|
+
if (!isPathInside2(sheet, projectDir) || !existsSync10(sheet)) continue;
|
|
7899
|
+
for (const face of extractFontFaceBlocks(readFileSync7(sheet, "utf8"))) {
|
|
7900
|
+
const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
7901
|
+
const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
|
|
7902
|
+
const path = decodedUrlPath(url);
|
|
7903
|
+
const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(dirname7(sheet), path);
|
|
7904
|
+
return isPathInside2(file, projectDir) && existsSync10(file);
|
|
7905
|
+
});
|
|
7906
|
+
if (family && shipsFile) families.push(family);
|
|
7907
|
+
}
|
|
7908
|
+
}
|
|
7909
|
+
return families;
|
|
7910
|
+
}
|
|
7854
7911
|
function collectFontFaceUrls(html) {
|
|
7855
7912
|
const styleBlockRe = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
|
|
7856
7913
|
const urlSet = /* @__PURE__ */ new Set();
|
|
@@ -7881,12 +7938,10 @@ function collectFontFaceUrls(html) {
|
|
|
7881
7938
|
}
|
|
7882
7939
|
return urlSet;
|
|
7883
7940
|
}
|
|
7884
|
-
|
|
7885
|
-
const processed = await inlineExternalFontStylesheets(html);
|
|
7886
|
-
const urlSet = collectFontFaceUrls(processed);
|
|
7941
|
+
function localizeFontFaceUrls(html, downloadDir) {
|
|
7887
7942
|
return downloadAndRewriteUrls(
|
|
7888
|
-
|
|
7889
|
-
|
|
7943
|
+
collectFontFaceUrls(html),
|
|
7944
|
+
html,
|
|
7890
7945
|
join15(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7891
7946
|
"Remote font download failed",
|
|
7892
7947
|
"Localized remote font face(s)",
|
|
@@ -8054,15 +8109,17 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
8054
8109
|
const hasShaderTransitions = detectShaderTransitionUsage(sanitizedHtml);
|
|
8055
8110
|
const usesThreeDTransforms = detectThreeDTransformUsage(sanitizedHtml);
|
|
8056
8111
|
const hasAncestorBackgroundImage = detectAncestorBackgroundImage(sanitizedHtml);
|
|
8112
|
+
const fontSheetHtml = await inlineExternalFontStylesheets(
|
|
8113
|
+
promoteCssImportsToLinkTags(sanitizedHtml)
|
|
8114
|
+
);
|
|
8057
8115
|
const normalizedFontHtml = normalizeSystemFontPrimaryFamilies(
|
|
8058
|
-
injectTextRenderingRule(
|
|
8059
|
-
coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml))
|
|
8060
|
-
)
|
|
8116
|
+
injectTextRenderingRule(coalesceHeadStylesAndBodyScripts(fontSheetHtml))
|
|
8061
8117
|
);
|
|
8062
8118
|
const coalescedHtml = await injectDeterministicFontFaces(normalizedFontHtml, {
|
|
8063
8119
|
logger: defaultLogger,
|
|
8064
8120
|
failClosedFontFetch: options.failClosedFontFetch === true,
|
|
8065
8121
|
allowSystemFontCapture: options.allowSystemFontCapture,
|
|
8122
|
+
projectFontFamilies: projectStylesheetFontFamilies(fontSheetHtml, projectDir),
|
|
8066
8123
|
abortSignal: options.abortSignal
|
|
8067
8124
|
});
|
|
8068
8125
|
const assembledHtml = await inlineExternalScripts(coalescedHtml);
|
|
@@ -8088,7 +8145,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
8088
8145
|
);
|
|
8089
8146
|
const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
|
|
8090
8147
|
const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
|
|
8091
|
-
const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await
|
|
8148
|
+
const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeFontFaceUrls(htmlWithLocalBg, downloadDir);
|
|
8092
8149
|
const {
|
|
8093
8150
|
html: htmlWithJpegs,
|
|
8094
8151
|
preparedAssets: preparedHeifAssets,
|
|
@@ -9902,6 +9959,7 @@ async function planVideoLayerPassthrough(input) {
|
|
|
9902
9959
|
}
|
|
9903
9960
|
|
|
9904
9961
|
// src/services/render/stages/audioStage.ts
|
|
9962
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
9905
9963
|
import { join as join21 } from "node:path";
|
|
9906
9964
|
import {
|
|
9907
9965
|
MIXED_AUDIO_FILENAME,
|
|
@@ -10035,8 +10093,9 @@ var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
|
|
|
10035
10093
|
var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
|
|
10036
10094
|
var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
|
|
10037
10095
|
var MAX_TRUE_PEAK_CORRECTION_PASSES = 3;
|
|
10096
|
+
var gainFilter = (gainDb) => `volume=${gainDb.toFixed(3)}dB`;
|
|
10038
10097
|
function aacTruePeakCorrectionFilter(targetDurationSeconds, attenuationDb) {
|
|
10039
|
-
return
|
|
10098
|
+
return `${gainFilter(attenuationDb)},apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
|
|
10040
10099
|
}
|
|
10041
10100
|
function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
|
|
10042
10101
|
const delta = targetDurationSeconds - sourceDurationSeconds;
|
|
@@ -10074,7 +10133,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10074
10133
|
const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
|
|
10075
10134
|
const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
|
|
10076
10135
|
const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
|
|
10077
|
-
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal);
|
|
10136
|
+
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath);
|
|
10078
10137
|
const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg || input.encodeAac ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
|
|
10079
10138
|
const [videoResult, audioResult] = await Promise.allSettled([
|
|
10080
10139
|
probeVideo(input.videoPath),
|
|
@@ -10122,7 +10181,12 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10122
10181
|
);
|
|
10123
10182
|
try {
|
|
10124
10183
|
for (const step of plan.steps) {
|
|
10125
|
-
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
|
|
10184
|
+
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
|
|
10185
|
+
input.sourcePath ?? input.audioPath,
|
|
10186
|
+
input.outputPath,
|
|
10187
|
+
targetDurationSeconds,
|
|
10188
|
+
step.filter
|
|
10189
|
+
);
|
|
10126
10190
|
if (!ffmpegResult.success) {
|
|
10127
10191
|
return {
|
|
10128
10192
|
success: false,
|
|
@@ -10151,6 +10215,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10151
10215
|
if (probeTruePeak) {
|
|
10152
10216
|
const correction = await enforceAacTruePeak({
|
|
10153
10217
|
audioPath: input.outputPath,
|
|
10218
|
+
sourcePath: input.sourcePath,
|
|
10154
10219
|
targetDurationSeconds,
|
|
10155
10220
|
signal: input.signal,
|
|
10156
10221
|
probeTruePeak,
|
|
@@ -10186,9 +10251,27 @@ async function constrainAacTruePeak(input) {
|
|
|
10186
10251
|
targetDurationSeconds: input.targetDurationSeconds,
|
|
10187
10252
|
signal: input.signal,
|
|
10188
10253
|
probeTruePeak: input.probeTruePeakDbfs ?? ((audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal })),
|
|
10189
|
-
encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
|
|
10254
|
+
encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath),
|
|
10255
|
+
rewriteSource: input.rewriteSource ?? ((sourcePath, outputPath, filter) => defaultRunFfmpeg(
|
|
10256
|
+
["-i", sourcePath, "-af", filter, "-c:a", "pcm_f32le", "-y", outputPath],
|
|
10257
|
+
timeoutMs,
|
|
10258
|
+
input.signal
|
|
10259
|
+
))
|
|
10190
10260
|
});
|
|
10191
10261
|
}
|
|
10262
|
+
async function cutSource(input, scratchDir, attenuationDb) {
|
|
10263
|
+
if (!input.sourcePath || !input.rewriteSource) return true;
|
|
10264
|
+
const cutPath = join19(scratchDir, "source.wav");
|
|
10265
|
+
try {
|
|
10266
|
+
const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
|
|
10267
|
+
if (!result.success) throw new Error(result.error);
|
|
10268
|
+
renameWithRetry5(cutPath, input.sourcePath, { copyFallback: true });
|
|
10269
|
+
return true;
|
|
10270
|
+
} catch {
|
|
10271
|
+
removeScratch(input.sourcePath);
|
|
10272
|
+
return false;
|
|
10273
|
+
}
|
|
10274
|
+
}
|
|
10192
10275
|
async function enforceAacTruePeak(input) {
|
|
10193
10276
|
let scratchDir;
|
|
10194
10277
|
try {
|
|
@@ -10208,8 +10291,10 @@ async function enforceAacTruePeak(input) {
|
|
|
10208
10291
|
}
|
|
10209
10292
|
if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
|
|
10210
10293
|
if (measuredPath !== correctedPath) return { success: true };
|
|
10294
|
+
const sourceCut = await cutSource(input, scratchDir, attenuationDb);
|
|
10211
10295
|
renameWithRetry5(correctedPath, input.audioPath, { copyFallback: true });
|
|
10212
|
-
|
|
10296
|
+
const loweredDb = Number((-attenuationDb).toFixed(2));
|
|
10297
|
+
return sourceCut ? { success: true, loweredDb } : { success: true, loweredDb, sourceStale: true };
|
|
10213
10298
|
}
|
|
10214
10299
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
10215
10300
|
const headroomDb = AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB * 2 ** pass;
|
|
@@ -10219,7 +10304,7 @@ async function enforceAacTruePeak(input) {
|
|
|
10219
10304
|
const referenceDbfs = Math.max(truePeakDbfs, (sourcePeakDbfs ?? -Infinity) + attenuationDb);
|
|
10220
10305
|
attenuationDb += AAC_DELIVERY_TRUE_PEAK_DBFS - headroomDb - referenceDbfs;
|
|
10221
10306
|
const result = await input.encode(
|
|
10222
|
-
input.audioPath,
|
|
10307
|
+
input.sourcePath ?? input.audioPath,
|
|
10223
10308
|
correctedPath,
|
|
10224
10309
|
input.targetDurationSeconds,
|
|
10225
10310
|
aacTruePeakCorrectionFilter(input.targetDurationSeconds, attenuationDb)
|
|
@@ -10448,36 +10533,54 @@ async function runAudioStageScoped(input) {
|
|
|
10448
10533
|
const stageStart = Date.now();
|
|
10449
10534
|
const pcmSourcePath = join21(input.workDir, "audio-source.wav");
|
|
10450
10535
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
10536
|
+
let deliverySourcePath;
|
|
10451
10537
|
try {
|
|
10452
10538
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
10453
10539
|
let audioLoweredDb;
|
|
10454
10540
|
if (result.hasAudio) {
|
|
10455
10541
|
const guardStart = Date.now();
|
|
10456
|
-
const
|
|
10457
|
-
|
|
10458
|
-
|
|
10459
|
-
|
|
10460
|
-
|
|
10461
|
-
|
|
10462
|
-
|
|
10463
|
-
});
|
|
10464
|
-
if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
|
|
10465
|
-
const peak = await constrainAacTruePeak({
|
|
10466
|
-
audioPath: result.audioOutputPath,
|
|
10467
|
-
sourcePath: pcmSourcePath,
|
|
10468
|
-
targetDurationSeconds: duration,
|
|
10469
|
-
signal: input.abortSignal,
|
|
10470
|
-
timeoutMs: input.ffmpegProcessTimeout
|
|
10471
|
-
});
|
|
10472
|
-
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
10473
|
-
audioLoweredDb = peak.loweredDb;
|
|
10542
|
+
const guard = await guardDeliveryPeaks(
|
|
10543
|
+
input,
|
|
10544
|
+
result.audioOutputPath,
|
|
10545
|
+
pcmSourcePath,
|
|
10546
|
+
duration
|
|
10547
|
+
);
|
|
10548
|
+
audioLoweredDb = guard.loweredDb;
|
|
10474
10549
|
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
10550
|
+
if (input.keepDeliverySource && !guard.sourceStale && existsSync14(pcmSourcePath)) {
|
|
10551
|
+
deliverySourcePath = pcmSourcePath;
|
|
10552
|
+
}
|
|
10475
10553
|
}
|
|
10476
|
-
return {
|
|
10554
|
+
return {
|
|
10555
|
+
...result,
|
|
10556
|
+
audioLoweredDb,
|
|
10557
|
+
deliverySourcePath,
|
|
10558
|
+
audioProcessMs: Date.now() - stageStart
|
|
10559
|
+
};
|
|
10477
10560
|
} finally {
|
|
10478
|
-
removeScratch2(pcmSourcePath);
|
|
10561
|
+
if (!deliverySourcePath) removeScratch2(pcmSourcePath);
|
|
10479
10562
|
}
|
|
10480
10563
|
}
|
|
10564
|
+
async function guardDeliveryPeaks(input, audioPath, sourcePath, duration) {
|
|
10565
|
+
const limited = await limitDeliveryPeaks({
|
|
10566
|
+
audioPath,
|
|
10567
|
+
sourcePath,
|
|
10568
|
+
targetDurationSeconds: duration,
|
|
10569
|
+
signal: input.abortSignal,
|
|
10570
|
+
timeoutMs: input.ffmpegProcessTimeout,
|
|
10571
|
+
warn: (message, details) => input.log?.warn?.(message, details)
|
|
10572
|
+
});
|
|
10573
|
+
if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
|
|
10574
|
+
const peak = await constrainAacTruePeak({
|
|
10575
|
+
audioPath,
|
|
10576
|
+
sourcePath,
|
|
10577
|
+
targetDurationSeconds: duration,
|
|
10578
|
+
signal: input.abortSignal,
|
|
10579
|
+
timeoutMs: input.ffmpegProcessTimeout
|
|
10580
|
+
});
|
|
10581
|
+
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
10582
|
+
return { loweredDb: peak.loweredDb, sourceStale: peak.sourceStale };
|
|
10583
|
+
}
|
|
10481
10584
|
async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
10482
10585
|
const {
|
|
10483
10586
|
projectDir,
|
|
@@ -10572,7 +10675,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
10572
10675
|
}
|
|
10573
10676
|
|
|
10574
10677
|
// src/services/render/stages/captureStage.ts
|
|
10575
|
-
import { existsSync as
|
|
10678
|
+
import { existsSync as existsSync15, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
|
|
10576
10679
|
import { join as join22 } from "node:path";
|
|
10577
10680
|
import {
|
|
10578
10681
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
@@ -10643,7 +10746,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
10643
10746
|
}
|
|
10644
10747
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
10645
10748
|
let total = sampleDirectoryBytes(framesDir);
|
|
10646
|
-
if (!
|
|
10749
|
+
if (!existsSync15(workDir)) return total;
|
|
10647
10750
|
for (const attempt of readdirSync3(workDir)) {
|
|
10648
10751
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
10649
10752
|
total += sampleDirectoryBytes(join22(workDir, attempt));
|
|
@@ -10946,7 +11049,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
|
|
|
10946
11049
|
}
|
|
10947
11050
|
|
|
10948
11051
|
// src/services/render/stages/captureHdrStage.ts
|
|
10949
|
-
import { existsSync as
|
|
11052
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync8 } from "node:fs";
|
|
10950
11053
|
import { join as join26 } from "node:path";
|
|
10951
11054
|
import {
|
|
10952
11055
|
closeCaptureSession as closeCaptureSession7,
|
|
@@ -11192,7 +11295,7 @@ import { Worker } from "node:worker_threads";
|
|
|
11192
11295
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
11193
11296
|
import { dirname as dirname10, join as join24 } from "node:path";
|
|
11194
11297
|
import { createRequire } from "node:module";
|
|
11195
|
-
import { existsSync as
|
|
11298
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
11196
11299
|
import { cpus as cpus2 } from "node:os";
|
|
11197
11300
|
function resolveWorkerEntry(explicit) {
|
|
11198
11301
|
if (explicit && explicit.length > 0) {
|
|
@@ -11205,7 +11308,7 @@ function resolveWorkerEntry(explicit) {
|
|
|
11205
11308
|
}
|
|
11206
11309
|
const moduleDir = dirname10(fileURLToPath3(import.meta.url));
|
|
11207
11310
|
const jsPath = join24(moduleDir, "shaderTransitionWorker.js");
|
|
11208
|
-
if (
|
|
11311
|
+
if (existsSync16(jsPath)) return { path: jsPath, isTs: false };
|
|
11209
11312
|
const tsPath = join24(moduleDir, "shaderTransitionWorker.ts");
|
|
11210
11313
|
return { path: tsPath, isTs: true };
|
|
11211
11314
|
}
|
|
@@ -11766,7 +11869,7 @@ async function runCaptureHdrStage(input) {
|
|
|
11766
11869
|
}
|
|
11767
11870
|
const debugDumpEnabled = process.env.KEEP_TEMP === "1";
|
|
11768
11871
|
const debugDumpDir = debugDumpEnabled ? join26(framesDir, "debug-composite") : null;
|
|
11769
|
-
if (debugDumpDir && !
|
|
11872
|
+
if (debugDumpDir && !existsSync17(debugDumpDir)) {
|
|
11770
11873
|
mkdirSync8(debugDumpDir, { recursive: true });
|
|
11771
11874
|
}
|
|
11772
11875
|
const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
|
|
@@ -11923,10 +12026,10 @@ async function runCaptureHdrStage(input) {
|
|
|
11923
12026
|
// src/services/render/stages/encodeStage.ts
|
|
11924
12027
|
import {
|
|
11925
12028
|
copyFileSync as copyFileSync4,
|
|
11926
|
-
existsSync as
|
|
12029
|
+
existsSync as existsSync18,
|
|
11927
12030
|
mkdirSync as mkdirSync9,
|
|
11928
12031
|
readdirSync as readdirSync4,
|
|
11929
|
-
readFileSync as
|
|
12032
|
+
readFileSync as readFileSync8,
|
|
11930
12033
|
rmSync as rmSync9,
|
|
11931
12034
|
statSync as statSync7,
|
|
11932
12035
|
writeFileSync as writeFileSync5
|
|
@@ -12020,7 +12123,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
|
12020
12123
|
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
12021
12124
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
12022
12125
|
}
|
|
12023
|
-
const gif =
|
|
12126
|
+
const gif = readFileSync8(outputPath);
|
|
12024
12127
|
if (!clearGifFramesBeforeNext(gif)) {
|
|
12025
12128
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
12026
12129
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
@@ -12053,11 +12156,11 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
12053
12156
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
12054
12157
|
}
|
|
12055
12158
|
}
|
|
12056
|
-
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(
|
|
12159
|
+
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync8(outputPath)) !== false) {
|
|
12057
12160
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
12058
12161
|
if (failure) return failure;
|
|
12059
12162
|
}
|
|
12060
|
-
const fileSize =
|
|
12163
|
+
const fileSize = existsSync18(outputPath) ? statSync7(outputPath).size : 0;
|
|
12061
12164
|
return {
|
|
12062
12165
|
success: true,
|
|
12063
12166
|
outputPath,
|
|
@@ -12218,7 +12321,7 @@ async function runEncodeStage(input) {
|
|
|
12218
12321
|
const stage5Start = Date.now();
|
|
12219
12322
|
if (isPngSequence) {
|
|
12220
12323
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
12221
|
-
if (!
|
|
12324
|
+
if (!existsSync18(outputPath)) mkdirSync9(outputPath, { recursive: true });
|
|
12222
12325
|
const captured = readdirSync4(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
12223
12326
|
if (captured.length === 0) {
|
|
12224
12327
|
throw new Error(
|
|
@@ -12229,7 +12332,7 @@ async function runEncodeStage(input) {
|
|
|
12229
12332
|
const dst = join27(outputPath, formatExportFrameName(i, "png"));
|
|
12230
12333
|
copyFileSync4(join27(framesDir, name), dst);
|
|
12231
12334
|
});
|
|
12232
|
-
if (hasAudio && audioOutputPath &&
|
|
12335
|
+
if (hasAudio && audioOutputPath && existsSync18(audioOutputPath)) {
|
|
12233
12336
|
copyFileSync4(audioOutputPath, join27(outputPath, MIXED_AUDIO_FILENAME2));
|
|
12234
12337
|
log.info(
|
|
12235
12338
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
@@ -12340,6 +12443,7 @@ async function assembleOutput(input, onSecondsWritten) {
|
|
|
12340
12443
|
const normalizeResult = await padOrTrimAudioToVideoFrameCount({
|
|
12341
12444
|
videoPath: videoOnlyPath,
|
|
12342
12445
|
audioPath: audioOutputPath,
|
|
12446
|
+
sourcePath: input.audioPcmPath,
|
|
12343
12447
|
outputPath: normalizedAudioPath,
|
|
12344
12448
|
signal: abortSignal,
|
|
12345
12449
|
timeoutMs: ffmpegProcessTimeout
|
|
@@ -12621,7 +12725,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
12621
12725
|
let rangeStart = null;
|
|
12622
12726
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12623
12727
|
const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12624
|
-
const missing = !
|
|
12728
|
+
const missing = !existsSync19(framePath) || statSync8(framePath).size <= 8;
|
|
12625
12729
|
if (missing && rangeStart === null) {
|
|
12626
12730
|
rangeStart = frameIndex;
|
|
12627
12731
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -12746,7 +12850,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
|
12746
12850
|
let captured = 0;
|
|
12747
12851
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12748
12852
|
const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12749
|
-
if (
|
|
12853
|
+
if (existsSync19(framePath)) captured++;
|
|
12750
12854
|
}
|
|
12751
12855
|
return captured;
|
|
12752
12856
|
}
|
|
@@ -13411,7 +13515,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
13411
13515
|
validateDeliveryEncoderConfig(job.config);
|
|
13412
13516
|
const debugDir = resolveRenderDebugDir();
|
|
13413
13517
|
const outputDir = dirname12(outputPath);
|
|
13414
|
-
if (!
|
|
13518
|
+
if (!existsSync19(outputDir)) mkdirSync10(outputDir, { recursive: true });
|
|
13415
13519
|
const workDir = job.config.debug ? join28(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
|
|
13416
13520
|
const pipelineStart = Date.now();
|
|
13417
13521
|
const baseLog = job.config.logger ?? defaultLogger;
|
|
@@ -13553,7 +13657,7 @@ async function executeRenderPipeline(input) {
|
|
|
13553
13657
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
13554
13658
|
assertNotAborted();
|
|
13555
13659
|
assertConfiguredFfmpegBinariesExist();
|
|
13556
|
-
if (!
|
|
13660
|
+
if (!existsSync19(workDir)) mkdirSync10(workDir, { recursive: true });
|
|
13557
13661
|
if (job.config.debug) {
|
|
13558
13662
|
log.info("[Render] Debug artifacts enabled", { workDir, logPath });
|
|
13559
13663
|
}
|
|
@@ -13583,7 +13687,7 @@ async function executeRenderPipeline(input) {
|
|
|
13583
13687
|
});
|
|
13584
13688
|
const entryFile = job.config.entryFile || "index.html";
|
|
13585
13689
|
let htmlPath = join28(projectDir, entryFile);
|
|
13586
|
-
if (!
|
|
13690
|
+
if (!existsSync19(htmlPath)) {
|
|
13587
13691
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
13588
13692
|
}
|
|
13589
13693
|
assertNotAborted();
|
|
@@ -13591,7 +13695,7 @@ async function executeRenderPipeline(input) {
|
|
|
13591
13695
|
if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
|
|
13592
13696
|
const wrapperPath = join28(workDir, "standalone-entry.html");
|
|
13593
13697
|
const projectIndexPath = join28(projectDir, "index.html");
|
|
13594
|
-
if (!
|
|
13698
|
+
if (!existsSync19(projectIndexPath)) {
|
|
13595
13699
|
throw new Error(
|
|
13596
13700
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
13597
13701
|
);
|
|
@@ -13902,7 +14006,8 @@ async function executeRenderPipeline(input) {
|
|
|
13902
14006
|
masterFxChain: composition.masterFxChain,
|
|
13903
14007
|
abortSignal: executionSignal,
|
|
13904
14008
|
assertNotAborted,
|
|
13905
|
-
log
|
|
14009
|
+
log,
|
|
14010
|
+
keepDeliverySource: true
|
|
13906
14011
|
})
|
|
13907
14012
|
);
|
|
13908
14013
|
pendingAudio = audioStage.catch(() => void 0);
|
|
@@ -13934,7 +14039,7 @@ async function executeRenderPipeline(input) {
|
|
|
13934
14039
|
throw new Error("File server failed to initialize before frame capture");
|
|
13935
14040
|
}
|
|
13936
14041
|
const framesDir = join28(workDir, "captured-frames");
|
|
13937
|
-
if (!
|
|
14042
|
+
if (!existsSync19(framesDir)) mkdirSync10(framesDir, { recursive: true });
|
|
13938
14043
|
updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
|
|
13939
14044
|
code: "start_browsers"
|
|
13940
14045
|
});
|
|
@@ -14610,7 +14715,7 @@ async function executeRenderPipeline(input) {
|
|
|
14610
14715
|
const effectiveQuality = job.config.crf ?? preset.quality;
|
|
14611
14716
|
const effectiveBitrate = job.config.crf != null ? void 0 : job.config.videoBitrate;
|
|
14612
14717
|
const audioResult = await audioStage;
|
|
14613
|
-
const { audioOutputPath, hasAudio } = audioResult;
|
|
14718
|
+
const { audioOutputPath, deliverySourcePath, hasAudio } = audioResult;
|
|
14614
14719
|
perfStages.audioProcessMs = audioResult.audioProcessMs;
|
|
14615
14720
|
recordLimiterAttenuation(job, audioResult.audioLoweredDb);
|
|
14616
14721
|
if (audioResult.audioError) {
|
|
@@ -15236,6 +15341,7 @@ async function executeRenderPipeline(input) {
|
|
|
15236
15341
|
job,
|
|
15237
15342
|
videoOnlyPath,
|
|
15238
15343
|
audioOutputPath,
|
|
15344
|
+
audioPcmPath: deliverySourcePath,
|
|
15239
15345
|
outputPath: stagedOutputPath,
|
|
15240
15346
|
hasAudio,
|
|
15241
15347
|
format: outputFormat,
|
|
@@ -15261,7 +15367,7 @@ async function executeRenderPipeline(input) {
|
|
|
15261
15367
|
})
|
|
15262
15368
|
);
|
|
15263
15369
|
const totalElapsed = Date.now() - pipelineStart;
|
|
15264
|
-
const tmpPeakBytes =
|
|
15370
|
+
const tmpPeakBytes = existsSync19(workDir) ? sampleDirectoryBytes(workDir) : 0;
|
|
15265
15371
|
recordTransientRetryObservability();
|
|
15266
15372
|
observability.checkpoint("pipeline", "artifact validated", { totalElapsedMs: totalElapsed });
|
|
15267
15373
|
const observabilitySummary = observability.summary({
|
|
@@ -15342,7 +15448,7 @@ async function executeRenderPipeline(input) {
|
|
|
15342
15448
|
}
|
|
15343
15449
|
}
|
|
15344
15450
|
if (job.config.debug) {
|
|
15345
|
-
if (!isPngSequence && !isHls &&
|
|
15451
|
+
if (!isPngSequence && !isHls && existsSync19(stagedOutputPath)) {
|
|
15346
15452
|
const debugOutput = join28(workDir, `output${videoExt}`);
|
|
15347
15453
|
copyFileSync5(stagedOutputPath, debugOutput);
|
|
15348
15454
|
}
|
|
@@ -15454,8 +15560,8 @@ async function executeRenderPipeline(input) {
|
|
|
15454
15560
|
|
|
15455
15561
|
// src/services/hyperframeLint.ts
|
|
15456
15562
|
import {
|
|
15457
|
-
existsSync as
|
|
15458
|
-
readFileSync as
|
|
15563
|
+
existsSync as existsSync20,
|
|
15564
|
+
readFileSync as readFileSync9,
|
|
15459
15565
|
statSync as statSync9,
|
|
15460
15566
|
openSync as openSync5,
|
|
15461
15567
|
fstatSync as fstatSync3,
|
|
@@ -15501,14 +15607,14 @@ function readEntryHtml(filePath) {
|
|
|
15501
15607
|
}
|
|
15502
15608
|
try {
|
|
15503
15609
|
if (!fstatSync3(fd).isFile()) return null;
|
|
15504
|
-
return
|
|
15610
|
+
return readFileSync9(fd, "utf-8");
|
|
15505
15611
|
} finally {
|
|
15506
15612
|
closeSync5(fd);
|
|
15507
15613
|
}
|
|
15508
15614
|
}
|
|
15509
15615
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
15510
15616
|
const absProjectDir = resolve9(projectDir);
|
|
15511
|
-
if (!
|
|
15617
|
+
if (!existsSync20(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
|
|
15512
15618
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
15513
15619
|
}
|
|
15514
15620
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -15577,7 +15683,7 @@ async function runHyperframeLint(prepared) {
|
|
|
15577
15683
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
15578
15684
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
15579
15685
|
import { dirname as dirname13, join as join30 } from "node:path";
|
|
15580
|
-
import { existsSync as
|
|
15686
|
+
import { existsSync as existsSync21 } from "node:fs";
|
|
15581
15687
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
15582
15688
|
async function startHealthWorker(options = {}) {
|
|
15583
15689
|
const log = options.logger ?? defaultLogger2();
|
|
@@ -15654,7 +15760,7 @@ function resolveWorkerEntry2() {
|
|
|
15654
15760
|
const here = dirname13(fileURLToPath5(import.meta.url));
|
|
15655
15761
|
const candidates = [join30(here, "healthWorkerThread.js"), join30(here, "healthWorkerThread.ts")];
|
|
15656
15762
|
for (const candidate of candidates) {
|
|
15657
|
-
if (
|
|
15763
|
+
if (existsSync21(candidate)) return candidate;
|
|
15658
15764
|
}
|
|
15659
15765
|
return void 0;
|
|
15660
15766
|
}
|
|
@@ -16204,7 +16310,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
|
16204
16310
|
const { input, cleanupProjectDir } = prepared;
|
|
16205
16311
|
const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
|
|
16206
16312
|
const outputDir = dirname14(absoluteOutputPath);
|
|
16207
|
-
if (!
|
|
16313
|
+
if (!existsSync22(outputDir)) mkdirSync11(outputDir, { recursive: true });
|
|
16208
16314
|
return { input, cleanupProjectDir, absoluteOutputPath };
|
|
16209
16315
|
}
|
|
16210
16316
|
function validateRenderOverrides(body) {
|
|
@@ -16245,11 +16351,11 @@ function prepareProjectDirectory(projectDir, options) {
|
|
|
16245
16351
|
const candidate = nonEmptyString(projectDir);
|
|
16246
16352
|
if (!candidate) return null;
|
|
16247
16353
|
const absProjectDir = resolve10(candidate);
|
|
16248
|
-
if (!
|
|
16354
|
+
if (!existsSync22(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
|
|
16249
16355
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
16250
16356
|
}
|
|
16251
16357
|
const entry = options.entryFile || "index.html";
|
|
16252
|
-
if (!
|
|
16358
|
+
if (!existsSync22(resolve10(absProjectDir, entry))) {
|
|
16253
16359
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
16254
16360
|
}
|
|
16255
16361
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -16341,7 +16447,7 @@ function cleanupTempDir(dir, log) {
|
|
|
16341
16447
|
}
|
|
16342
16448
|
}
|
|
16343
16449
|
function outputFileSize(path) {
|
|
16344
|
-
return
|
|
16450
|
+
return existsSync22(path) ? statSync10(path).size : 0;
|
|
16345
16451
|
}
|
|
16346
16452
|
function createBlockingProgressReporter(log, requestId) {
|
|
16347
16453
|
let lastLoggedPct = -10;
|
|
@@ -16645,7 +16751,7 @@ function createRenderHandlers(options = {}) {
|
|
|
16645
16751
|
if (!artifact) {
|
|
16646
16752
|
return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
|
|
16647
16753
|
}
|
|
16648
|
-
if (!
|
|
16754
|
+
if (!existsSync22(artifact.path)) {
|
|
16649
16755
|
store.delete(token);
|
|
16650
16756
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
16651
16757
|
}
|