@hyperframes/producer 0.9.14 → 0.9.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/distributed.js +205 -136
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +68 -68
- package/dist/index.js +233 -187
- package/dist/public-server.js +154 -107
- package/dist/services/render/audioPadTrim.d.ts +6 -0
- package/dist/services/render/stages/assembleStage.d.ts +1 -0
- package/dist/services/render/stages/audioStage.d.ts +4 -0
- package/dist/services/render/stages/compileStage.d.ts +12 -3
- package/dist/services/renderOrchestrator.d.ts +2 -5
- package/package.json +7 -7
package/dist/public-server.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
// src/server.ts
|
|
4
4
|
import {
|
|
5
|
-
existsSync as
|
|
5
|
+
existsSync as existsSync23,
|
|
6
6
|
mkdirSync as mkdirSync11,
|
|
7
7
|
statSync as statSync10,
|
|
8
8
|
mkdtempSync as mkdtempSync4,
|
|
9
|
-
writeFileSync as
|
|
9
|
+
writeFileSync as writeFileSync8,
|
|
10
10
|
rmSync as rmSync11,
|
|
11
11
|
createReadStream as createReadStream3
|
|
12
12
|
} from "node:fs";
|
|
@@ -26,12 +26,12 @@ 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 existsSync20,
|
|
30
30
|
mkdirSync as mkdirSync10,
|
|
31
31
|
readdirSync as readdirSync5,
|
|
32
32
|
rmSync as rmSync10,
|
|
33
33
|
statSync as statSync8,
|
|
34
|
-
writeFileSync as
|
|
34
|
+
writeFileSync as writeFileSync7,
|
|
35
35
|
copyFileSync as copyFileSync5,
|
|
36
36
|
appendFileSync
|
|
37
37
|
} from "fs";
|
|
@@ -46,9 +46,7 @@ import {
|
|
|
46
46
|
toFps as toFps2
|
|
47
47
|
} from "@hyperframes/core";
|
|
48
48
|
import { HF_AUDIO_GROUP_TAG } from "@hyperframes/core/audio-groups";
|
|
49
|
-
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
50
49
|
import { HTML_BODY_CSS_HEIGHT_FIRST_RE, HTML_BODY_CSS_WIDTH_FIRST_RE } from "@hyperframes/parsers";
|
|
51
|
-
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
52
50
|
import {
|
|
53
51
|
extractMediaMetadata as extractMediaMetadata4,
|
|
54
52
|
resolveConfig as resolveConfig2,
|
|
@@ -79,7 +77,8 @@ import {
|
|
|
79
77
|
isDrawElementCaptureError,
|
|
80
78
|
getDrawElementVerificationDetails,
|
|
81
79
|
augmentProtocolTimeoutError,
|
|
82
|
-
augmentPageNavigationTimeoutError
|
|
80
|
+
augmentPageNavigationTimeoutError,
|
|
81
|
+
displaySize
|
|
83
82
|
} from "@hyperframes/engine";
|
|
84
83
|
import {
|
|
85
84
|
referenceBackgroundResult,
|
|
@@ -5908,8 +5907,11 @@ function countAuthoredTimedClips(html) {
|
|
|
5908
5907
|
}
|
|
5909
5908
|
|
|
5910
5909
|
// src/services/render/stages/compileStage.ts
|
|
5910
|
+
import { existsSync as existsSync11, writeFileSync as writeFileSync5 } from "node:fs";
|
|
5911
5911
|
import { join as join16 } from "node:path";
|
|
5912
5912
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
5913
|
+
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
5914
|
+
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
5913
5915
|
|
|
5914
5916
|
// src/services/htmlCompiler.ts
|
|
5915
5917
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
@@ -8614,11 +8616,36 @@ function adaptAspectAgnosticResolution(requested, aspectAgnostic, width, height,
|
|
|
8614
8616
|
});
|
|
8615
8617
|
return flipped;
|
|
8616
8618
|
}
|
|
8619
|
+
function resolveEntryHtmlPath(input) {
|
|
8620
|
+
const { projectDir, workDir, entryFile, log } = input;
|
|
8621
|
+
const htmlPath = join16(projectDir, entryFile);
|
|
8622
|
+
const rawEntry = readTextFile2(htmlPath);
|
|
8623
|
+
if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
|
|
8624
|
+
const projectIndexPath = join16(projectDir, "index.html");
|
|
8625
|
+
if (!existsSync11(projectIndexPath)) {
|
|
8626
|
+
throw new Error(
|
|
8627
|
+
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
8628
|
+
);
|
|
8629
|
+
}
|
|
8630
|
+
const standaloneHtml = extractStandaloneEntryFromIndex(
|
|
8631
|
+
readTextFile2(projectIndexPath),
|
|
8632
|
+
entryFile,
|
|
8633
|
+
rawEntry
|
|
8634
|
+
);
|
|
8635
|
+
if (!standaloneHtml) {
|
|
8636
|
+
throw new Error(
|
|
8637
|
+
`Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
|
|
8638
|
+
);
|
|
8639
|
+
}
|
|
8640
|
+
const wrapperPath = join16(workDir, "standalone-entry.html");
|
|
8641
|
+
writeFileSync5(wrapperPath, standaloneHtml, "utf-8");
|
|
8642
|
+
log.info("Extracted standalone entry from index.html host context", { entryFile });
|
|
8643
|
+
return wrapperPath;
|
|
8644
|
+
}
|
|
8617
8645
|
async function runCompileStage(input) {
|
|
8618
8646
|
const {
|
|
8619
8647
|
projectDir,
|
|
8620
8648
|
workDir,
|
|
8621
|
-
htmlPath,
|
|
8622
8649
|
entryFile,
|
|
8623
8650
|
job,
|
|
8624
8651
|
cfg,
|
|
@@ -8629,6 +8656,7 @@ async function runCompileStage(input) {
|
|
|
8629
8656
|
allowSystemFontCapture,
|
|
8630
8657
|
abortSignal
|
|
8631
8658
|
} = input;
|
|
8659
|
+
const htmlPath = resolveEntryHtmlPath({ projectDir, workDir, entryFile, log });
|
|
8632
8660
|
const compileStart = Date.now();
|
|
8633
8661
|
const compiled = await compileForRender(projectDir, htmlPath, join16(workDir, "downloads"), {
|
|
8634
8662
|
log,
|
|
@@ -9428,7 +9456,7 @@ function validateRenderDuration(input) {
|
|
|
9428
9456
|
}
|
|
9429
9457
|
|
|
9430
9458
|
// src/services/render/stages/extractVideosStage.ts
|
|
9431
|
-
import { existsSync as
|
|
9459
|
+
import { existsSync as existsSync12 } from "node:fs";
|
|
9432
9460
|
import { join as join18 } from "node:path";
|
|
9433
9461
|
import {
|
|
9434
9462
|
classifyVideoExtractionError,
|
|
@@ -9698,7 +9726,7 @@ async function runExtractVideosStage(input) {
|
|
|
9698
9726
|
const probeFailures = await Promise.all(
|
|
9699
9727
|
composition.videos.map(async (v) => {
|
|
9700
9728
|
const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
|
|
9701
|
-
if (!
|
|
9729
|
+
if (!existsSync12(videoPath)) return null;
|
|
9702
9730
|
try {
|
|
9703
9731
|
const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry(() => extractMediaMetadata3(videoPath), {
|
|
9704
9732
|
signal: abortSignal,
|
|
@@ -9736,7 +9764,7 @@ async function runExtractVideosStage(input) {
|
|
|
9736
9764
|
const probed = await Promise.all(
|
|
9737
9765
|
composition.images.map(async (img) => {
|
|
9738
9766
|
const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
|
|
9739
|
-
if (!
|
|
9767
|
+
if (!existsSync12(imgPath)) return null;
|
|
9740
9768
|
const colorSpace = await probeColorSpaceSafely(imgPath, log);
|
|
9741
9769
|
if (isHdrColorSpace2(colorSpace)) {
|
|
9742
9770
|
nativeHdrImageIds.add(img.id);
|
|
@@ -9856,7 +9884,7 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
9856
9884
|
videos.map(async (video) => {
|
|
9857
9885
|
const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
|
|
9858
9886
|
const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
|
|
9859
|
-
if (!isHttpUrl2(readFrom) && !
|
|
9887
|
+
if (!isHttpUrl2(readFrom) && !existsSync12(readFrom)) return null;
|
|
9860
9888
|
const metadata = await extractMediaMetadata3(readFrom);
|
|
9861
9889
|
const window2 = await resolveFinalFrameExtractionWindow2(
|
|
9862
9890
|
readFrom,
|
|
@@ -9876,7 +9904,7 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
9876
9904
|
}
|
|
9877
9905
|
|
|
9878
9906
|
// src/services/render/videoLayerPassthroughPlan.ts
|
|
9879
|
-
import { existsSync as
|
|
9907
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
9880
9908
|
import {
|
|
9881
9909
|
buildVideoLayerInput,
|
|
9882
9910
|
isHttpUrl as isHttpUrl3,
|
|
@@ -9944,7 +9972,7 @@ async function planVideoLayerPassthrough(input) {
|
|
|
9944
9972
|
const { video } = q;
|
|
9945
9973
|
if (isHttpUrl3(video.src)) return { reason: "remote_source" };
|
|
9946
9974
|
const path = resolveProjectRelativeSrc4(video.src, input.projectDir, input.compiledDir);
|
|
9947
|
-
if (!
|
|
9975
|
+
if (!existsSync13(path)) return { reason: "source_missing" };
|
|
9948
9976
|
const layout = await probeVideoLayout(input.page, video.id, input.canvas, input.duration);
|
|
9949
9977
|
if ("reason" in layout) return layout;
|
|
9950
9978
|
const layer = await buildVideoLayerInput(
|
|
@@ -9959,6 +9987,7 @@ async function planVideoLayerPassthrough(input) {
|
|
|
9959
9987
|
}
|
|
9960
9988
|
|
|
9961
9989
|
// src/services/render/stages/audioStage.ts
|
|
9990
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
9962
9991
|
import { join as join21 } from "node:path";
|
|
9963
9992
|
import {
|
|
9964
9993
|
MIXED_AUDIO_FILENAME,
|
|
@@ -9969,7 +9998,7 @@ import {
|
|
|
9969
9998
|
} from "@hyperframes/engine";
|
|
9970
9999
|
|
|
9971
10000
|
// src/services/render/audioDeliveryLimit.ts
|
|
9972
|
-
import { existsSync as
|
|
10001
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
9973
10002
|
import { dirname as dirname9, join as join20 } from "node:path";
|
|
9974
10003
|
import {
|
|
9975
10004
|
applyAudioFxChain,
|
|
@@ -10092,8 +10121,9 @@ var AUDIO_DURATION_TOLERANCE_SECONDS = 1e-3;
|
|
|
10092
10121
|
var AAC_DELIVERY_TRUE_PEAK_DBFS = -1;
|
|
10093
10122
|
var AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB = 0.5;
|
|
10094
10123
|
var MAX_TRUE_PEAK_CORRECTION_PASSES = 3;
|
|
10124
|
+
var gainFilter = (gainDb) => `volume=${gainDb.toFixed(3)}dB`;
|
|
10095
10125
|
function aacTruePeakCorrectionFilter(targetDurationSeconds, attenuationDb) {
|
|
10096
|
-
return
|
|
10126
|
+
return `${gainFilter(attenuationDb)},apad,atrim=0:${formatSeconds(targetDurationSeconds)}`;
|
|
10097
10127
|
}
|
|
10098
10128
|
function buildPadTrimAudioPlan(audioPath, outputPath, sourceDurationSeconds, targetDurationSeconds) {
|
|
10099
10129
|
const delta = targetDurationSeconds - sourceDurationSeconds;
|
|
@@ -10131,7 +10161,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10131
10161
|
const probeAudio = input.probeAudioInfo ?? defaultProbeAudioInfo;
|
|
10132
10162
|
const timeoutMs = input.timeoutMs ?? DEFAULT_CONFIG.ffmpegProcessTimeout;
|
|
10133
10163
|
const runner = input.runFfmpeg ?? ((args) => defaultRunFfmpeg(args, timeoutMs, input.signal));
|
|
10134
|
-
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal);
|
|
10164
|
+
const encode = input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath);
|
|
10135
10165
|
const probeTruePeak = input.probeAudioTruePeakDbfs ?? (input.runFfmpeg || input.encodeAac ? void 0 : (audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal }));
|
|
10136
10166
|
const [videoResult, audioResult] = await Promise.allSettled([
|
|
10137
10167
|
probeVideo(input.videoPath),
|
|
@@ -10179,7 +10209,12 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10179
10209
|
);
|
|
10180
10210
|
try {
|
|
10181
10211
|
for (const step of plan.steps) {
|
|
10182
|
-
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
|
|
10212
|
+
const ffmpegResult = step.kind === "copy" ? await runner(step.args) : await encode(
|
|
10213
|
+
input.sourcePath ?? input.audioPath,
|
|
10214
|
+
input.outputPath,
|
|
10215
|
+
targetDurationSeconds,
|
|
10216
|
+
step.filter
|
|
10217
|
+
);
|
|
10183
10218
|
if (!ffmpegResult.success) {
|
|
10184
10219
|
return {
|
|
10185
10220
|
success: false,
|
|
@@ -10208,6 +10243,7 @@ async function padOrTrimAudioToVideoFrameCount(input) {
|
|
|
10208
10243
|
if (probeTruePeak) {
|
|
10209
10244
|
const correction = await enforceAacTruePeak({
|
|
10210
10245
|
audioPath: input.outputPath,
|
|
10246
|
+
sourcePath: input.sourcePath,
|
|
10211
10247
|
targetDurationSeconds,
|
|
10212
10248
|
signal: input.signal,
|
|
10213
10249
|
probeTruePeak,
|
|
@@ -10243,9 +10279,27 @@ async function constrainAacTruePeak(input) {
|
|
|
10243
10279
|
targetDurationSeconds: input.targetDurationSeconds,
|
|
10244
10280
|
signal: input.signal,
|
|
10245
10281
|
probeTruePeak: input.probeTruePeakDbfs ?? ((audioPath, signal) => measureTruePeakDbfs(audioPath, { timeoutMs, signal })),
|
|
10246
|
-
encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath)
|
|
10282
|
+
encode: input.encodeAac ?? defaultAacEncoder(timeoutMs, input.signal, input.sourcePath),
|
|
10283
|
+
rewriteSource: input.rewriteSource ?? ((sourcePath, outputPath, filter) => defaultRunFfmpeg(
|
|
10284
|
+
["-i", sourcePath, "-af", filter, "-c:a", "pcm_f32le", "-y", outputPath],
|
|
10285
|
+
timeoutMs,
|
|
10286
|
+
input.signal
|
|
10287
|
+
))
|
|
10247
10288
|
});
|
|
10248
10289
|
}
|
|
10290
|
+
async function cutSource(input, scratchDir, attenuationDb) {
|
|
10291
|
+
if (!input.sourcePath || !input.rewriteSource) return true;
|
|
10292
|
+
const cutPath = join19(scratchDir, "source.wav");
|
|
10293
|
+
try {
|
|
10294
|
+
const result = await input.rewriteSource(input.sourcePath, cutPath, gainFilter(attenuationDb));
|
|
10295
|
+
if (!result.success) throw new Error(result.error);
|
|
10296
|
+
renameWithRetry5(cutPath, input.sourcePath, { copyFallback: true });
|
|
10297
|
+
return true;
|
|
10298
|
+
} catch {
|
|
10299
|
+
removeScratch(input.sourcePath);
|
|
10300
|
+
return false;
|
|
10301
|
+
}
|
|
10302
|
+
}
|
|
10249
10303
|
async function enforceAacTruePeak(input) {
|
|
10250
10304
|
let scratchDir;
|
|
10251
10305
|
try {
|
|
@@ -10265,8 +10319,10 @@ async function enforceAacTruePeak(input) {
|
|
|
10265
10319
|
}
|
|
10266
10320
|
if (truePeakDbfs <= AAC_DELIVERY_TRUE_PEAK_DBFS) {
|
|
10267
10321
|
if (measuredPath !== correctedPath) return { success: true };
|
|
10322
|
+
const sourceCut = await cutSource(input, scratchDir, attenuationDb);
|
|
10268
10323
|
renameWithRetry5(correctedPath, input.audioPath, { copyFallback: true });
|
|
10269
|
-
|
|
10324
|
+
const loweredDb = Number((-attenuationDb).toFixed(2));
|
|
10325
|
+
return sourceCut ? { success: true, loweredDb } : { success: true, loweredDb, sourceStale: true };
|
|
10270
10326
|
}
|
|
10271
10327
|
if (pass === MAX_TRUE_PEAK_CORRECTION_PASSES) break;
|
|
10272
10328
|
const headroomDb = AAC_TRUE_PEAK_CORRECTION_HEADROOM_DB * 2 ** pass;
|
|
@@ -10276,7 +10332,7 @@ async function enforceAacTruePeak(input) {
|
|
|
10276
10332
|
const referenceDbfs = Math.max(truePeakDbfs, (sourcePeakDbfs ?? -Infinity) + attenuationDb);
|
|
10277
10333
|
attenuationDb += AAC_DELIVERY_TRUE_PEAK_DBFS - headroomDb - referenceDbfs;
|
|
10278
10334
|
const result = await input.encode(
|
|
10279
|
-
input.audioPath,
|
|
10335
|
+
input.sourcePath ?? input.audioPath,
|
|
10280
10336
|
correctedPath,
|
|
10281
10337
|
input.targetDurationSeconds,
|
|
10282
10338
|
aacTruePeakCorrectionFilter(input.targetDurationSeconds, attenuationDb)
|
|
@@ -10449,7 +10505,7 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
10449
10505
|
applyChain: applyAudioFxChain,
|
|
10450
10506
|
encode: encodeAac2,
|
|
10451
10507
|
rename: renameWithRetry6,
|
|
10452
|
-
sourceExists:
|
|
10508
|
+
sourceExists: existsSync14
|
|
10453
10509
|
}) {
|
|
10454
10510
|
const { audioPath, sourcePath, signal, timeoutMs } = input;
|
|
10455
10511
|
if (!deps.sourceExists(sourcePath)) return { success: true };
|
|
@@ -10505,36 +10561,54 @@ async function runAudioStageScoped(input) {
|
|
|
10505
10561
|
const stageStart = Date.now();
|
|
10506
10562
|
const pcmSourcePath = join21(input.workDir, "audio-source.wav");
|
|
10507
10563
|
const duration = input.totalFrames * input.fps.den / input.fps.num;
|
|
10564
|
+
let deliverySourcePath;
|
|
10508
10565
|
try {
|
|
10509
10566
|
const result = await mixAudioForDelivery(input, duration, pcmSourcePath);
|
|
10510
10567
|
let audioLoweredDb;
|
|
10511
10568
|
if (result.hasAudio) {
|
|
10512
10569
|
const guardStart = Date.now();
|
|
10513
|
-
const
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
});
|
|
10521
|
-
if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
|
|
10522
|
-
const peak = await constrainAacTruePeak({
|
|
10523
|
-
audioPath: result.audioOutputPath,
|
|
10524
|
-
sourcePath: pcmSourcePath,
|
|
10525
|
-
targetDurationSeconds: duration,
|
|
10526
|
-
signal: input.abortSignal,
|
|
10527
|
-
timeoutMs: input.ffmpegProcessTimeout
|
|
10528
|
-
});
|
|
10529
|
-
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
10530
|
-
audioLoweredDb = peak.loweredDb;
|
|
10570
|
+
const guard = await guardDeliveryPeaks(
|
|
10571
|
+
input,
|
|
10572
|
+
result.audioOutputPath,
|
|
10573
|
+
pcmSourcePath,
|
|
10574
|
+
duration
|
|
10575
|
+
);
|
|
10576
|
+
audioLoweredDb = guard.loweredDb;
|
|
10531
10577
|
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
10578
|
+
if (input.keepDeliverySource && !guard.sourceStale && existsSync15(pcmSourcePath)) {
|
|
10579
|
+
deliverySourcePath = pcmSourcePath;
|
|
10580
|
+
}
|
|
10532
10581
|
}
|
|
10533
|
-
return {
|
|
10582
|
+
return {
|
|
10583
|
+
...result,
|
|
10584
|
+
audioLoweredDb,
|
|
10585
|
+
deliverySourcePath,
|
|
10586
|
+
audioProcessMs: Date.now() - stageStart
|
|
10587
|
+
};
|
|
10534
10588
|
} finally {
|
|
10535
|
-
removeScratch2(pcmSourcePath);
|
|
10589
|
+
if (!deliverySourcePath) removeScratch2(pcmSourcePath);
|
|
10536
10590
|
}
|
|
10537
10591
|
}
|
|
10592
|
+
async function guardDeliveryPeaks(input, audioPath, sourcePath, duration) {
|
|
10593
|
+
const limited = await limitDeliveryPeaks({
|
|
10594
|
+
audioPath,
|
|
10595
|
+
sourcePath,
|
|
10596
|
+
targetDurationSeconds: duration,
|
|
10597
|
+
signal: input.abortSignal,
|
|
10598
|
+
timeoutMs: input.ffmpegProcessTimeout,
|
|
10599
|
+
warn: (message, details) => input.log?.warn?.(message, details)
|
|
10600
|
+
});
|
|
10601
|
+
if (!limited.success) throw encoderFailureError("Audio true-peak limit failed", limited);
|
|
10602
|
+
const peak = await constrainAacTruePeak({
|
|
10603
|
+
audioPath,
|
|
10604
|
+
sourcePath,
|
|
10605
|
+
targetDurationSeconds: duration,
|
|
10606
|
+
signal: input.abortSignal,
|
|
10607
|
+
timeoutMs: input.ffmpegProcessTimeout
|
|
10608
|
+
});
|
|
10609
|
+
if (!peak.success) throw encoderFailureError("Audio true-peak check failed", peak);
|
|
10610
|
+
return { loweredDb: peak.loweredDb, sourceStale: peak.sourceStale };
|
|
10611
|
+
}
|
|
10538
10612
|
async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
10539
10613
|
const {
|
|
10540
10614
|
projectDir,
|
|
@@ -10629,7 +10703,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
10629
10703
|
}
|
|
10630
10704
|
|
|
10631
10705
|
// src/services/render/stages/captureStage.ts
|
|
10632
|
-
import { existsSync as
|
|
10706
|
+
import { existsSync as existsSync16, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
|
|
10633
10707
|
import { join as join22 } from "node:path";
|
|
10634
10708
|
import {
|
|
10635
10709
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
@@ -10700,7 +10774,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
10700
10774
|
}
|
|
10701
10775
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
10702
10776
|
let total = sampleDirectoryBytes(framesDir);
|
|
10703
|
-
if (!
|
|
10777
|
+
if (!existsSync16(workDir)) return total;
|
|
10704
10778
|
for (const attempt of readdirSync3(workDir)) {
|
|
10705
10779
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
10706
10780
|
total += sampleDirectoryBytes(join22(workDir, attempt));
|
|
@@ -11003,7 +11077,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
|
|
|
11003
11077
|
}
|
|
11004
11078
|
|
|
11005
11079
|
// src/services/render/stages/captureHdrStage.ts
|
|
11006
|
-
import { existsSync as
|
|
11080
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync8 } from "node:fs";
|
|
11007
11081
|
import { join as join26 } from "node:path";
|
|
11008
11082
|
import {
|
|
11009
11083
|
closeCaptureSession as closeCaptureSession7,
|
|
@@ -11249,7 +11323,7 @@ import { Worker } from "node:worker_threads";
|
|
|
11249
11323
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
11250
11324
|
import { dirname as dirname10, join as join24 } from "node:path";
|
|
11251
11325
|
import { createRequire } from "node:module";
|
|
11252
|
-
import { existsSync as
|
|
11326
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
11253
11327
|
import { cpus as cpus2 } from "node:os";
|
|
11254
11328
|
function resolveWorkerEntry(explicit) {
|
|
11255
11329
|
if (explicit && explicit.length > 0) {
|
|
@@ -11262,7 +11336,7 @@ function resolveWorkerEntry(explicit) {
|
|
|
11262
11336
|
}
|
|
11263
11337
|
const moduleDir = dirname10(fileURLToPath3(import.meta.url));
|
|
11264
11338
|
const jsPath = join24(moduleDir, "shaderTransitionWorker.js");
|
|
11265
|
-
if (
|
|
11339
|
+
if (existsSync17(jsPath)) return { path: jsPath, isTs: false };
|
|
11266
11340
|
const tsPath = join24(moduleDir, "shaderTransitionWorker.ts");
|
|
11267
11341
|
return { path: tsPath, isTs: true };
|
|
11268
11342
|
}
|
|
@@ -11823,7 +11897,7 @@ async function runCaptureHdrStage(input) {
|
|
|
11823
11897
|
}
|
|
11824
11898
|
const debugDumpEnabled = process.env.KEEP_TEMP === "1";
|
|
11825
11899
|
const debugDumpDir = debugDumpEnabled ? join26(framesDir, "debug-composite") : null;
|
|
11826
|
-
if (debugDumpDir && !
|
|
11900
|
+
if (debugDumpDir && !existsSync18(debugDumpDir)) {
|
|
11827
11901
|
mkdirSync8(debugDumpDir, { recursive: true });
|
|
11828
11902
|
}
|
|
11829
11903
|
const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
|
|
@@ -11980,13 +12054,13 @@ async function runCaptureHdrStage(input) {
|
|
|
11980
12054
|
// src/services/render/stages/encodeStage.ts
|
|
11981
12055
|
import {
|
|
11982
12056
|
copyFileSync as copyFileSync4,
|
|
11983
|
-
existsSync as
|
|
12057
|
+
existsSync as existsSync19,
|
|
11984
12058
|
mkdirSync as mkdirSync9,
|
|
11985
12059
|
readdirSync as readdirSync4,
|
|
11986
12060
|
readFileSync as readFileSync8,
|
|
11987
12061
|
rmSync as rmSync9,
|
|
11988
12062
|
statSync as statSync7,
|
|
11989
|
-
writeFileSync as
|
|
12063
|
+
writeFileSync as writeFileSync6
|
|
11990
12064
|
} from "node:fs";
|
|
11991
12065
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
11992
12066
|
import { dirname as dirname11, join as join27 } from "node:path";
|
|
@@ -12082,7 +12156,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
|
12082
12156
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
12083
12157
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
12084
12158
|
}
|
|
12085
|
-
|
|
12159
|
+
writeFileSync6(outputPath, gif);
|
|
12086
12160
|
return null;
|
|
12087
12161
|
}
|
|
12088
12162
|
async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
@@ -12114,7 +12188,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
12114
12188
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
12115
12189
|
if (failure) return failure;
|
|
12116
12190
|
}
|
|
12117
|
-
const fileSize =
|
|
12191
|
+
const fileSize = existsSync19(outputPath) ? statSync7(outputPath).size : 0;
|
|
12118
12192
|
return {
|
|
12119
12193
|
success: true,
|
|
12120
12194
|
outputPath,
|
|
@@ -12275,7 +12349,7 @@ async function runEncodeStage(input) {
|
|
|
12275
12349
|
const stage5Start = Date.now();
|
|
12276
12350
|
if (isPngSequence) {
|
|
12277
12351
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
12278
|
-
if (!
|
|
12352
|
+
if (!existsSync19(outputPath)) mkdirSync9(outputPath, { recursive: true });
|
|
12279
12353
|
const captured = readdirSync4(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
12280
12354
|
if (captured.length === 0) {
|
|
12281
12355
|
throw new Error(
|
|
@@ -12286,7 +12360,7 @@ async function runEncodeStage(input) {
|
|
|
12286
12360
|
const dst = join27(outputPath, formatExportFrameName(i, "png"));
|
|
12287
12361
|
copyFileSync4(join27(framesDir, name), dst);
|
|
12288
12362
|
});
|
|
12289
|
-
if (hasAudio && audioOutputPath &&
|
|
12363
|
+
if (hasAudio && audioOutputPath && existsSync19(audioOutputPath)) {
|
|
12290
12364
|
copyFileSync4(audioOutputPath, join27(outputPath, MIXED_AUDIO_FILENAME2));
|
|
12291
12365
|
log.info(
|
|
12292
12366
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
@@ -12397,6 +12471,7 @@ async function assembleOutput(input, onSecondsWritten) {
|
|
|
12397
12471
|
const normalizeResult = await padOrTrimAudioToVideoFrameCount({
|
|
12398
12472
|
videoPath: videoOnlyPath,
|
|
12399
12473
|
audioPath: audioOutputPath,
|
|
12474
|
+
sourcePath: input.audioPcmPath,
|
|
12400
12475
|
outputPath: normalizedAudioPath,
|
|
12401
12476
|
signal: abortSignal,
|
|
12402
12477
|
timeoutMs: ffmpegProcessTimeout
|
|
@@ -12667,18 +12742,14 @@ function hasUsableVideoDimensions(metadata) {
|
|
|
12667
12742
|
return Number.isFinite(metadata.width) && Number.isFinite(metadata.height) && metadata.width > 0 && metadata.height > 0;
|
|
12668
12743
|
}
|
|
12669
12744
|
function collectVideoMetadataHints(extractedVideos) {
|
|
12670
|
-
return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({
|
|
12671
|
-
id: video.videoId,
|
|
12672
|
-
width: video.metadata.width,
|
|
12673
|
-
height: video.metadata.height
|
|
12674
|
-
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
12745
|
+
return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({ id: video.videoId, ...displaySize(video.metadata) })).sort((a, b) => a.id.localeCompare(b.id));
|
|
12675
12746
|
}
|
|
12676
12747
|
function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
12677
12748
|
const ranges = [];
|
|
12678
12749
|
let rangeStart = null;
|
|
12679
12750
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12680
12751
|
const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12681
|
-
const missing = !
|
|
12752
|
+
const missing = !existsSync20(framePath) || statSync8(framePath).size <= 8;
|
|
12682
12753
|
if (missing && rangeStart === null) {
|
|
12683
12754
|
rangeStart = frameIndex;
|
|
12684
12755
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -12803,7 +12874,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
|
12803
12874
|
let captured = 0;
|
|
12804
12875
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12805
12876
|
const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12806
|
-
if (
|
|
12877
|
+
if (existsSync20(framePath)) captured++;
|
|
12807
12878
|
}
|
|
12808
12879
|
return captured;
|
|
12809
12880
|
}
|
|
@@ -13468,7 +13539,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
13468
13539
|
validateDeliveryEncoderConfig(job.config);
|
|
13469
13540
|
const debugDir = resolveRenderDebugDir();
|
|
13470
13541
|
const outputDir = dirname12(outputPath);
|
|
13471
|
-
if (!
|
|
13542
|
+
if (!existsSync20(outputDir)) mkdirSync10(outputDir, { recursive: true });
|
|
13472
13543
|
const workDir = job.config.debug ? join28(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
|
|
13473
13544
|
const pipelineStart = Date.now();
|
|
13474
13545
|
const baseLog = job.config.logger ?? defaultLogger;
|
|
@@ -13610,7 +13681,7 @@ async function executeRenderPipeline(input) {
|
|
|
13610
13681
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
13611
13682
|
assertNotAborted();
|
|
13612
13683
|
assertConfiguredFfmpegBinariesExist();
|
|
13613
|
-
if (!
|
|
13684
|
+
if (!existsSync20(workDir)) mkdirSync10(workDir, { recursive: true });
|
|
13614
13685
|
if (job.config.debug) {
|
|
13615
13686
|
log.info("[Render] Debug artifacts enabled", { workDir, logPath });
|
|
13616
13687
|
}
|
|
@@ -13639,36 +13710,11 @@ async function executeRenderPipeline(input) {
|
|
|
13639
13710
|
requestedWorkers: job.config.workers ?? "auto"
|
|
13640
13711
|
});
|
|
13641
13712
|
const entryFile = job.config.entryFile || "index.html";
|
|
13642
|
-
|
|
13643
|
-
if (!
|
|
13713
|
+
const htmlPath = join28(projectDir, entryFile);
|
|
13714
|
+
if (!existsSync20(htmlPath)) {
|
|
13644
13715
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
13645
13716
|
}
|
|
13646
13717
|
assertNotAborted();
|
|
13647
|
-
const rawEntry = readTextFile2(htmlPath);
|
|
13648
|
-
if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
|
|
13649
|
-
const wrapperPath = join28(workDir, "standalone-entry.html");
|
|
13650
|
-
const projectIndexPath = join28(projectDir, "index.html");
|
|
13651
|
-
if (!existsSync18(projectIndexPath)) {
|
|
13652
|
-
throw new Error(
|
|
13653
|
-
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
13654
|
-
);
|
|
13655
|
-
}
|
|
13656
|
-
const standaloneHtml = extractStandaloneEntryFromIndex(
|
|
13657
|
-
readTextFile2(projectIndexPath),
|
|
13658
|
-
entryFile,
|
|
13659
|
-
rawEntry
|
|
13660
|
-
);
|
|
13661
|
-
if (!standaloneHtml) {
|
|
13662
|
-
throw new Error(
|
|
13663
|
-
`Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
|
|
13664
|
-
);
|
|
13665
|
-
}
|
|
13666
|
-
writeFileSync6(wrapperPath, standaloneHtml, "utf-8");
|
|
13667
|
-
htmlPath = wrapperPath;
|
|
13668
|
-
log.info("Extracted standalone entry from index.html host context", {
|
|
13669
|
-
entryFile
|
|
13670
|
-
});
|
|
13671
|
-
}
|
|
13672
13718
|
const stage1Start = Date.now();
|
|
13673
13719
|
updateJobStatus(job, "preprocessing", "Compiling composition", 5, onProgress, {
|
|
13674
13720
|
code: "compile"
|
|
@@ -13680,7 +13726,6 @@ async function executeRenderPipeline(input) {
|
|
|
13680
13726
|
() => runCompileStage({
|
|
13681
13727
|
projectDir,
|
|
13682
13728
|
workDir,
|
|
13683
|
-
htmlPath,
|
|
13684
13729
|
entryFile,
|
|
13685
13730
|
job,
|
|
13686
13731
|
cfg,
|
|
@@ -13959,7 +14004,8 @@ async function executeRenderPipeline(input) {
|
|
|
13959
14004
|
masterFxChain: composition.masterFxChain,
|
|
13960
14005
|
abortSignal: executionSignal,
|
|
13961
14006
|
assertNotAborted,
|
|
13962
|
-
log
|
|
14007
|
+
log,
|
|
14008
|
+
keepDeliverySource: true
|
|
13963
14009
|
})
|
|
13964
14010
|
);
|
|
13965
14011
|
pendingAudio = audioStage.catch(() => void 0);
|
|
@@ -13991,7 +14037,7 @@ async function executeRenderPipeline(input) {
|
|
|
13991
14037
|
throw new Error("File server failed to initialize before frame capture");
|
|
13992
14038
|
}
|
|
13993
14039
|
const framesDir = join28(workDir, "captured-frames");
|
|
13994
|
-
if (!
|
|
14040
|
+
if (!existsSync20(framesDir)) mkdirSync10(framesDir, { recursive: true });
|
|
13995
14041
|
updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
|
|
13996
14042
|
code: "start_browsers"
|
|
13997
14043
|
});
|
|
@@ -14667,7 +14713,7 @@ async function executeRenderPipeline(input) {
|
|
|
14667
14713
|
const effectiveQuality = job.config.crf ?? preset.quality;
|
|
14668
14714
|
const effectiveBitrate = job.config.crf != null ? void 0 : job.config.videoBitrate;
|
|
14669
14715
|
const audioResult = await audioStage;
|
|
14670
|
-
const { audioOutputPath, hasAudio } = audioResult;
|
|
14716
|
+
const { audioOutputPath, deliverySourcePath, hasAudio } = audioResult;
|
|
14671
14717
|
perfStages.audioProcessMs = audioResult.audioProcessMs;
|
|
14672
14718
|
recordLimiterAttenuation(job, audioResult.audioLoweredDb);
|
|
14673
14719
|
if (audioResult.audioError) {
|
|
@@ -15293,6 +15339,7 @@ async function executeRenderPipeline(input) {
|
|
|
15293
15339
|
job,
|
|
15294
15340
|
videoOnlyPath,
|
|
15295
15341
|
audioOutputPath,
|
|
15342
|
+
audioPcmPath: deliverySourcePath,
|
|
15296
15343
|
outputPath: stagedOutputPath,
|
|
15297
15344
|
hasAudio,
|
|
15298
15345
|
format: outputFormat,
|
|
@@ -15318,7 +15365,7 @@ async function executeRenderPipeline(input) {
|
|
|
15318
15365
|
})
|
|
15319
15366
|
);
|
|
15320
15367
|
const totalElapsed = Date.now() - pipelineStart;
|
|
15321
|
-
const tmpPeakBytes =
|
|
15368
|
+
const tmpPeakBytes = existsSync20(workDir) ? sampleDirectoryBytes(workDir) : 0;
|
|
15322
15369
|
recordTransientRetryObservability();
|
|
15323
15370
|
observability.checkpoint("pipeline", "artifact validated", { totalElapsedMs: totalElapsed });
|
|
15324
15371
|
const observabilitySummary = observability.summary({
|
|
@@ -15390,7 +15437,7 @@ async function executeRenderPipeline(input) {
|
|
|
15390
15437
|
job.perfSummary = perfSummary;
|
|
15391
15438
|
if (job.config.debug) {
|
|
15392
15439
|
try {
|
|
15393
|
-
|
|
15440
|
+
writeFileSync7(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
|
|
15394
15441
|
} catch (err) {
|
|
15395
15442
|
log.debug("Failed to write perf summary", {
|
|
15396
15443
|
perfOutputPath,
|
|
@@ -15399,7 +15446,7 @@ async function executeRenderPipeline(input) {
|
|
|
15399
15446
|
}
|
|
15400
15447
|
}
|
|
15401
15448
|
if (job.config.debug) {
|
|
15402
|
-
if (!isPngSequence && !isHls &&
|
|
15449
|
+
if (!isPngSequence && !isHls && existsSync20(stagedOutputPath)) {
|
|
15403
15450
|
const debugOutput = join28(workDir, `output${videoExt}`);
|
|
15404
15451
|
copyFileSync5(stagedOutputPath, debugOutput);
|
|
15405
15452
|
}
|
|
@@ -15511,7 +15558,7 @@ async function executeRenderPipeline(input) {
|
|
|
15511
15558
|
|
|
15512
15559
|
// src/services/hyperframeLint.ts
|
|
15513
15560
|
import {
|
|
15514
|
-
existsSync as
|
|
15561
|
+
existsSync as existsSync21,
|
|
15515
15562
|
readFileSync as readFileSync9,
|
|
15516
15563
|
statSync as statSync9,
|
|
15517
15564
|
openSync as openSync5,
|
|
@@ -15565,7 +15612,7 @@ function readEntryHtml(filePath) {
|
|
|
15565
15612
|
}
|
|
15566
15613
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
15567
15614
|
const absProjectDir = resolve9(projectDir);
|
|
15568
|
-
if (!
|
|
15615
|
+
if (!existsSync21(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
|
|
15569
15616
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
15570
15617
|
}
|
|
15571
15618
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -15634,7 +15681,7 @@ async function runHyperframeLint(prepared) {
|
|
|
15634
15681
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
15635
15682
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
15636
15683
|
import { dirname as dirname13, join as join30 } from "node:path";
|
|
15637
|
-
import { existsSync as
|
|
15684
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
15638
15685
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
15639
15686
|
async function startHealthWorker(options = {}) {
|
|
15640
15687
|
const log = options.logger ?? defaultLogger2();
|
|
@@ -15711,7 +15758,7 @@ function resolveWorkerEntry2() {
|
|
|
15711
15758
|
const here = dirname13(fileURLToPath5(import.meta.url));
|
|
15712
15759
|
const candidates = [join30(here, "healthWorkerThread.js"), join30(here, "healthWorkerThread.ts")];
|
|
15713
15760
|
for (const candidate of candidates) {
|
|
15714
|
-
if (
|
|
15761
|
+
if (existsSync22(candidate)) return candidate;
|
|
15715
15762
|
}
|
|
15716
15763
|
return void 0;
|
|
15717
15764
|
}
|
|
@@ -16261,7 +16308,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
|
16261
16308
|
const { input, cleanupProjectDir } = prepared;
|
|
16262
16309
|
const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
|
|
16263
16310
|
const outputDir = dirname14(absoluteOutputPath);
|
|
16264
|
-
if (!
|
|
16311
|
+
if (!existsSync23(outputDir)) mkdirSync11(outputDir, { recursive: true });
|
|
16265
16312
|
return { input, cleanupProjectDir, absoluteOutputPath };
|
|
16266
16313
|
}
|
|
16267
16314
|
function validateRenderOverrides(body) {
|
|
@@ -16302,11 +16349,11 @@ function prepareProjectDirectory(projectDir, options) {
|
|
|
16302
16349
|
const candidate = nonEmptyString(projectDir);
|
|
16303
16350
|
if (!candidate) return null;
|
|
16304
16351
|
const absProjectDir = resolve10(candidate);
|
|
16305
|
-
if (!
|
|
16352
|
+
if (!existsSync23(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
|
|
16306
16353
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
16307
16354
|
}
|
|
16308
16355
|
const entry = options.entryFile || "index.html";
|
|
16309
|
-
if (!
|
|
16356
|
+
if (!existsSync23(resolve10(absProjectDir, entry))) {
|
|
16310
16357
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
16311
16358
|
}
|
|
16312
16359
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -16328,7 +16375,7 @@ async function resolveInlineRenderHtml(body) {
|
|
|
16328
16375
|
function materializeInlineProject(html, options) {
|
|
16329
16376
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
|
|
16330
16377
|
const tempProjectDir = mkdtempSync4(join32(tempRoot, "producer-project-"));
|
|
16331
|
-
|
|
16378
|
+
writeFileSync8(join32(tempProjectDir, "index.html"), html, "utf-8");
|
|
16332
16379
|
return {
|
|
16333
16380
|
prepared: {
|
|
16334
16381
|
input: { projectDir: tempProjectDir, ...options },
|
|
@@ -16398,7 +16445,7 @@ function cleanupTempDir(dir, log) {
|
|
|
16398
16445
|
}
|
|
16399
16446
|
}
|
|
16400
16447
|
function outputFileSize(path) {
|
|
16401
|
-
return
|
|
16448
|
+
return existsSync23(path) ? statSync10(path).size : 0;
|
|
16402
16449
|
}
|
|
16403
16450
|
function createBlockingProgressReporter(log, requestId) {
|
|
16404
16451
|
let lastLoggedPct = -10;
|
|
@@ -16702,7 +16749,7 @@ function createRenderHandlers(options = {}) {
|
|
|
16702
16749
|
if (!artifact) {
|
|
16703
16750
|
return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
|
|
16704
16751
|
}
|
|
16705
|
-
if (!
|
|
16752
|
+
if (!existsSync23(artifact.path)) {
|
|
16706
16753
|
store.delete(token);
|
|
16707
16754
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
16708
16755
|
}
|