@hyperframes/producer 0.9.15 → 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 +135 -112
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +68 -68
- package/dist/index.js +158 -161
- package/dist/public-server.js +79 -81
- 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,7 +9987,7 @@ async function planVideoLayerPassthrough(input) {
|
|
|
9959
9987
|
}
|
|
9960
9988
|
|
|
9961
9989
|
// src/services/render/stages/audioStage.ts
|
|
9962
|
-
import { existsSync as
|
|
9990
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
9963
9991
|
import { join as join21 } from "node:path";
|
|
9964
9992
|
import {
|
|
9965
9993
|
MIXED_AUDIO_FILENAME,
|
|
@@ -9970,7 +9998,7 @@ import {
|
|
|
9970
9998
|
} from "@hyperframes/engine";
|
|
9971
9999
|
|
|
9972
10000
|
// src/services/render/audioDeliveryLimit.ts
|
|
9973
|
-
import { existsSync as
|
|
10001
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
9974
10002
|
import { dirname as dirname9, join as join20 } from "node:path";
|
|
9975
10003
|
import {
|
|
9976
10004
|
applyAudioFxChain,
|
|
@@ -10477,7 +10505,7 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
10477
10505
|
applyChain: applyAudioFxChain,
|
|
10478
10506
|
encode: encodeAac2,
|
|
10479
10507
|
rename: renameWithRetry6,
|
|
10480
|
-
sourceExists:
|
|
10508
|
+
sourceExists: existsSync14
|
|
10481
10509
|
}) {
|
|
10482
10510
|
const { audioPath, sourcePath, signal, timeoutMs } = input;
|
|
10483
10511
|
if (!deps.sourceExists(sourcePath)) return { success: true };
|
|
@@ -10547,7 +10575,7 @@ async function runAudioStageScoped(input) {
|
|
|
10547
10575
|
);
|
|
10548
10576
|
audioLoweredDb = guard.loweredDb;
|
|
10549
10577
|
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
10550
|
-
if (input.keepDeliverySource && !guard.sourceStale &&
|
|
10578
|
+
if (input.keepDeliverySource && !guard.sourceStale && existsSync15(pcmSourcePath)) {
|
|
10551
10579
|
deliverySourcePath = pcmSourcePath;
|
|
10552
10580
|
}
|
|
10553
10581
|
}
|
|
@@ -10675,7 +10703,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
10675
10703
|
}
|
|
10676
10704
|
|
|
10677
10705
|
// src/services/render/stages/captureStage.ts
|
|
10678
|
-
import { existsSync as
|
|
10706
|
+
import { existsSync as existsSync16, readdirSync as readdirSync3, statfsSync as statfsSync2 } from "node:fs";
|
|
10679
10707
|
import { join as join22 } from "node:path";
|
|
10680
10708
|
import {
|
|
10681
10709
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
@@ -10746,7 +10774,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
10746
10774
|
}
|
|
10747
10775
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
10748
10776
|
let total = sampleDirectoryBytes(framesDir);
|
|
10749
|
-
if (!
|
|
10777
|
+
if (!existsSync16(workDir)) return total;
|
|
10750
10778
|
for (const attempt of readdirSync3(workDir)) {
|
|
10751
10779
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
10752
10780
|
total += sampleDirectoryBytes(join22(workDir, attempt));
|
|
@@ -11049,7 +11077,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
|
|
|
11049
11077
|
}
|
|
11050
11078
|
|
|
11051
11079
|
// src/services/render/stages/captureHdrStage.ts
|
|
11052
|
-
import { existsSync as
|
|
11080
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync8 } from "node:fs";
|
|
11053
11081
|
import { join as join26 } from "node:path";
|
|
11054
11082
|
import {
|
|
11055
11083
|
closeCaptureSession as closeCaptureSession7,
|
|
@@ -11295,7 +11323,7 @@ import { Worker } from "node:worker_threads";
|
|
|
11295
11323
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
11296
11324
|
import { dirname as dirname10, join as join24 } from "node:path";
|
|
11297
11325
|
import { createRequire } from "node:module";
|
|
11298
|
-
import { existsSync as
|
|
11326
|
+
import { existsSync as existsSync17 } from "node:fs";
|
|
11299
11327
|
import { cpus as cpus2 } from "node:os";
|
|
11300
11328
|
function resolveWorkerEntry(explicit) {
|
|
11301
11329
|
if (explicit && explicit.length > 0) {
|
|
@@ -11308,7 +11336,7 @@ function resolveWorkerEntry(explicit) {
|
|
|
11308
11336
|
}
|
|
11309
11337
|
const moduleDir = dirname10(fileURLToPath3(import.meta.url));
|
|
11310
11338
|
const jsPath = join24(moduleDir, "shaderTransitionWorker.js");
|
|
11311
|
-
if (
|
|
11339
|
+
if (existsSync17(jsPath)) return { path: jsPath, isTs: false };
|
|
11312
11340
|
const tsPath = join24(moduleDir, "shaderTransitionWorker.ts");
|
|
11313
11341
|
return { path: tsPath, isTs: true };
|
|
11314
11342
|
}
|
|
@@ -11869,7 +11897,7 @@ async function runCaptureHdrStage(input) {
|
|
|
11869
11897
|
}
|
|
11870
11898
|
const debugDumpEnabled = process.env.KEEP_TEMP === "1";
|
|
11871
11899
|
const debugDumpDir = debugDumpEnabled ? join26(framesDir, "debug-composite") : null;
|
|
11872
|
-
if (debugDumpDir && !
|
|
11900
|
+
if (debugDumpDir && !existsSync18(debugDumpDir)) {
|
|
11873
11901
|
mkdirSync8(debugDumpDir, { recursive: true });
|
|
11874
11902
|
}
|
|
11875
11903
|
const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
|
|
@@ -12026,13 +12054,13 @@ async function runCaptureHdrStage(input) {
|
|
|
12026
12054
|
// src/services/render/stages/encodeStage.ts
|
|
12027
12055
|
import {
|
|
12028
12056
|
copyFileSync as copyFileSync4,
|
|
12029
|
-
existsSync as
|
|
12057
|
+
existsSync as existsSync19,
|
|
12030
12058
|
mkdirSync as mkdirSync9,
|
|
12031
12059
|
readdirSync as readdirSync4,
|
|
12032
12060
|
readFileSync as readFileSync8,
|
|
12033
12061
|
rmSync as rmSync9,
|
|
12034
12062
|
statSync as statSync7,
|
|
12035
|
-
writeFileSync as
|
|
12063
|
+
writeFileSync as writeFileSync6
|
|
12036
12064
|
} from "node:fs";
|
|
12037
12065
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
12038
12066
|
import { dirname as dirname11, join as join27 } from "node:path";
|
|
@@ -12128,7 +12156,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
|
12128
12156
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
12129
12157
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
12130
12158
|
}
|
|
12131
|
-
|
|
12159
|
+
writeFileSync6(outputPath, gif);
|
|
12132
12160
|
return null;
|
|
12133
12161
|
}
|
|
12134
12162
|
async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
@@ -12160,7 +12188,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
12160
12188
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
12161
12189
|
if (failure) return failure;
|
|
12162
12190
|
}
|
|
12163
|
-
const fileSize =
|
|
12191
|
+
const fileSize = existsSync19(outputPath) ? statSync7(outputPath).size : 0;
|
|
12164
12192
|
return {
|
|
12165
12193
|
success: true,
|
|
12166
12194
|
outputPath,
|
|
@@ -12321,7 +12349,7 @@ async function runEncodeStage(input) {
|
|
|
12321
12349
|
const stage5Start = Date.now();
|
|
12322
12350
|
if (isPngSequence) {
|
|
12323
12351
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
12324
|
-
if (!
|
|
12352
|
+
if (!existsSync19(outputPath)) mkdirSync9(outputPath, { recursive: true });
|
|
12325
12353
|
const captured = readdirSync4(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
12326
12354
|
if (captured.length === 0) {
|
|
12327
12355
|
throw new Error(
|
|
@@ -12332,7 +12360,7 @@ async function runEncodeStage(input) {
|
|
|
12332
12360
|
const dst = join27(outputPath, formatExportFrameName(i, "png"));
|
|
12333
12361
|
copyFileSync4(join27(framesDir, name), dst);
|
|
12334
12362
|
});
|
|
12335
|
-
if (hasAudio && audioOutputPath &&
|
|
12363
|
+
if (hasAudio && audioOutputPath && existsSync19(audioOutputPath)) {
|
|
12336
12364
|
copyFileSync4(audioOutputPath, join27(outputPath, MIXED_AUDIO_FILENAME2));
|
|
12337
12365
|
log.info(
|
|
12338
12366
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
@@ -12714,18 +12742,14 @@ function hasUsableVideoDimensions(metadata) {
|
|
|
12714
12742
|
return Number.isFinite(metadata.width) && Number.isFinite(metadata.height) && metadata.width > 0 && metadata.height > 0;
|
|
12715
12743
|
}
|
|
12716
12744
|
function collectVideoMetadataHints(extractedVideos) {
|
|
12717
|
-
return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({
|
|
12718
|
-
id: video.videoId,
|
|
12719
|
-
width: video.metadata.width,
|
|
12720
|
-
height: video.metadata.height
|
|
12721
|
-
})).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));
|
|
12722
12746
|
}
|
|
12723
12747
|
function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
12724
12748
|
const ranges = [];
|
|
12725
12749
|
let rangeStart = null;
|
|
12726
12750
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12727
12751
|
const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12728
|
-
const missing = !
|
|
12752
|
+
const missing = !existsSync20(framePath) || statSync8(framePath).size <= 8;
|
|
12729
12753
|
if (missing && rangeStart === null) {
|
|
12730
12754
|
rangeStart = frameIndex;
|
|
12731
12755
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -12850,7 +12874,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
|
12850
12874
|
let captured = 0;
|
|
12851
12875
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
12852
12876
|
const framePath = join28(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
12853
|
-
if (
|
|
12877
|
+
if (existsSync20(framePath)) captured++;
|
|
12854
12878
|
}
|
|
12855
12879
|
return captured;
|
|
12856
12880
|
}
|
|
@@ -13515,7 +13539,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
13515
13539
|
validateDeliveryEncoderConfig(job.config);
|
|
13516
13540
|
const debugDir = resolveRenderDebugDir();
|
|
13517
13541
|
const outputDir = dirname12(outputPath);
|
|
13518
|
-
if (!
|
|
13542
|
+
if (!existsSync20(outputDir)) mkdirSync10(outputDir, { recursive: true });
|
|
13519
13543
|
const workDir = job.config.debug ? join28(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
|
|
13520
13544
|
const pipelineStart = Date.now();
|
|
13521
13545
|
const baseLog = job.config.logger ?? defaultLogger;
|
|
@@ -13657,7 +13681,7 @@ async function executeRenderPipeline(input) {
|
|
|
13657
13681
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
13658
13682
|
assertNotAborted();
|
|
13659
13683
|
assertConfiguredFfmpegBinariesExist();
|
|
13660
|
-
if (!
|
|
13684
|
+
if (!existsSync20(workDir)) mkdirSync10(workDir, { recursive: true });
|
|
13661
13685
|
if (job.config.debug) {
|
|
13662
13686
|
log.info("[Render] Debug artifacts enabled", { workDir, logPath });
|
|
13663
13687
|
}
|
|
@@ -13686,36 +13710,11 @@ async function executeRenderPipeline(input) {
|
|
|
13686
13710
|
requestedWorkers: job.config.workers ?? "auto"
|
|
13687
13711
|
});
|
|
13688
13712
|
const entryFile = job.config.entryFile || "index.html";
|
|
13689
|
-
|
|
13690
|
-
if (!
|
|
13713
|
+
const htmlPath = join28(projectDir, entryFile);
|
|
13714
|
+
if (!existsSync20(htmlPath)) {
|
|
13691
13715
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
13692
13716
|
}
|
|
13693
13717
|
assertNotAborted();
|
|
13694
|
-
const rawEntry = readTextFile2(htmlPath);
|
|
13695
|
-
if (entryFile !== "index.html" && isTemplateEntry(rawEntry)) {
|
|
13696
|
-
const wrapperPath = join28(workDir, "standalone-entry.html");
|
|
13697
|
-
const projectIndexPath = join28(projectDir, "index.html");
|
|
13698
|
-
if (!existsSync19(projectIndexPath)) {
|
|
13699
|
-
throw new Error(
|
|
13700
|
-
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
13701
|
-
);
|
|
13702
|
-
}
|
|
13703
|
-
const standaloneHtml = extractStandaloneEntryFromIndex(
|
|
13704
|
-
readTextFile2(projectIndexPath),
|
|
13705
|
-
entryFile,
|
|
13706
|
-
rawEntry
|
|
13707
|
-
);
|
|
13708
|
-
if (!standaloneHtml) {
|
|
13709
|
-
throw new Error(
|
|
13710
|
-
`Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
|
|
13711
|
-
);
|
|
13712
|
-
}
|
|
13713
|
-
writeFileSync6(wrapperPath, standaloneHtml, "utf-8");
|
|
13714
|
-
htmlPath = wrapperPath;
|
|
13715
|
-
log.info("Extracted standalone entry from index.html host context", {
|
|
13716
|
-
entryFile
|
|
13717
|
-
});
|
|
13718
|
-
}
|
|
13719
13718
|
const stage1Start = Date.now();
|
|
13720
13719
|
updateJobStatus(job, "preprocessing", "Compiling composition", 5, onProgress, {
|
|
13721
13720
|
code: "compile"
|
|
@@ -13727,7 +13726,6 @@ async function executeRenderPipeline(input) {
|
|
|
13727
13726
|
() => runCompileStage({
|
|
13728
13727
|
projectDir,
|
|
13729
13728
|
workDir,
|
|
13730
|
-
htmlPath,
|
|
13731
13729
|
entryFile,
|
|
13732
13730
|
job,
|
|
13733
13731
|
cfg,
|
|
@@ -14039,7 +14037,7 @@ async function executeRenderPipeline(input) {
|
|
|
14039
14037
|
throw new Error("File server failed to initialize before frame capture");
|
|
14040
14038
|
}
|
|
14041
14039
|
const framesDir = join28(workDir, "captured-frames");
|
|
14042
|
-
if (!
|
|
14040
|
+
if (!existsSync20(framesDir)) mkdirSync10(framesDir, { recursive: true });
|
|
14043
14041
|
updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
|
|
14044
14042
|
code: "start_browsers"
|
|
14045
14043
|
});
|
|
@@ -15367,7 +15365,7 @@ async function executeRenderPipeline(input) {
|
|
|
15367
15365
|
})
|
|
15368
15366
|
);
|
|
15369
15367
|
const totalElapsed = Date.now() - pipelineStart;
|
|
15370
|
-
const tmpPeakBytes =
|
|
15368
|
+
const tmpPeakBytes = existsSync20(workDir) ? sampleDirectoryBytes(workDir) : 0;
|
|
15371
15369
|
recordTransientRetryObservability();
|
|
15372
15370
|
observability.checkpoint("pipeline", "artifact validated", { totalElapsedMs: totalElapsed });
|
|
15373
15371
|
const observabilitySummary = observability.summary({
|
|
@@ -15439,7 +15437,7 @@ async function executeRenderPipeline(input) {
|
|
|
15439
15437
|
job.perfSummary = perfSummary;
|
|
15440
15438
|
if (job.config.debug) {
|
|
15441
15439
|
try {
|
|
15442
|
-
|
|
15440
|
+
writeFileSync7(perfOutputPath, JSON.stringify(perfSummary, null, 2), "utf-8");
|
|
15443
15441
|
} catch (err) {
|
|
15444
15442
|
log.debug("Failed to write perf summary", {
|
|
15445
15443
|
perfOutputPath,
|
|
@@ -15448,7 +15446,7 @@ async function executeRenderPipeline(input) {
|
|
|
15448
15446
|
}
|
|
15449
15447
|
}
|
|
15450
15448
|
if (job.config.debug) {
|
|
15451
|
-
if (!isPngSequence && !isHls &&
|
|
15449
|
+
if (!isPngSequence && !isHls && existsSync20(stagedOutputPath)) {
|
|
15452
15450
|
const debugOutput = join28(workDir, `output${videoExt}`);
|
|
15453
15451
|
copyFileSync5(stagedOutputPath, debugOutput);
|
|
15454
15452
|
}
|
|
@@ -15560,7 +15558,7 @@ async function executeRenderPipeline(input) {
|
|
|
15560
15558
|
|
|
15561
15559
|
// src/services/hyperframeLint.ts
|
|
15562
15560
|
import {
|
|
15563
|
-
existsSync as
|
|
15561
|
+
existsSync as existsSync21,
|
|
15564
15562
|
readFileSync as readFileSync9,
|
|
15565
15563
|
statSync as statSync9,
|
|
15566
15564
|
openSync as openSync5,
|
|
@@ -15614,7 +15612,7 @@ function readEntryHtml(filePath) {
|
|
|
15614
15612
|
}
|
|
15615
15613
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
15616
15614
|
const absProjectDir = resolve9(projectDir);
|
|
15617
|
-
if (!
|
|
15615
|
+
if (!existsSync21(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
|
|
15618
15616
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
15619
15617
|
}
|
|
15620
15618
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -15683,7 +15681,7 @@ async function runHyperframeLint(prepared) {
|
|
|
15683
15681
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
15684
15682
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
15685
15683
|
import { dirname as dirname13, join as join30 } from "node:path";
|
|
15686
|
-
import { existsSync as
|
|
15684
|
+
import { existsSync as existsSync22 } from "node:fs";
|
|
15687
15685
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
15688
15686
|
async function startHealthWorker(options = {}) {
|
|
15689
15687
|
const log = options.logger ?? defaultLogger2();
|
|
@@ -15760,7 +15758,7 @@ function resolveWorkerEntry2() {
|
|
|
15760
15758
|
const here = dirname13(fileURLToPath5(import.meta.url));
|
|
15761
15759
|
const candidates = [join30(here, "healthWorkerThread.js"), join30(here, "healthWorkerThread.ts")];
|
|
15762
15760
|
for (const candidate of candidates) {
|
|
15763
|
-
if (
|
|
15761
|
+
if (existsSync22(candidate)) return candidate;
|
|
15764
15762
|
}
|
|
15765
15763
|
return void 0;
|
|
15766
15764
|
}
|
|
@@ -16310,7 +16308,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
|
16310
16308
|
const { input, cleanupProjectDir } = prepared;
|
|
16311
16309
|
const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
|
|
16312
16310
|
const outputDir = dirname14(absoluteOutputPath);
|
|
16313
|
-
if (!
|
|
16311
|
+
if (!existsSync23(outputDir)) mkdirSync11(outputDir, { recursive: true });
|
|
16314
16312
|
return { input, cleanupProjectDir, absoluteOutputPath };
|
|
16315
16313
|
}
|
|
16316
16314
|
function validateRenderOverrides(body) {
|
|
@@ -16351,11 +16349,11 @@ function prepareProjectDirectory(projectDir, options) {
|
|
|
16351
16349
|
const candidate = nonEmptyString(projectDir);
|
|
16352
16350
|
if (!candidate) return null;
|
|
16353
16351
|
const absProjectDir = resolve10(candidate);
|
|
16354
|
-
if (!
|
|
16352
|
+
if (!existsSync23(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
|
|
16355
16353
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
16356
16354
|
}
|
|
16357
16355
|
const entry = options.entryFile || "index.html";
|
|
16358
|
-
if (!
|
|
16356
|
+
if (!existsSync23(resolve10(absProjectDir, entry))) {
|
|
16359
16357
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
16360
16358
|
}
|
|
16361
16359
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -16377,7 +16375,7 @@ async function resolveInlineRenderHtml(body) {
|
|
|
16377
16375
|
function materializeInlineProject(html, options) {
|
|
16378
16376
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir3();
|
|
16379
16377
|
const tempProjectDir = mkdtempSync4(join32(tempRoot, "producer-project-"));
|
|
16380
|
-
|
|
16378
|
+
writeFileSync8(join32(tempProjectDir, "index.html"), html, "utf-8");
|
|
16381
16379
|
return {
|
|
16382
16380
|
prepared: {
|
|
16383
16381
|
input: { projectDir: tempProjectDir, ...options },
|
|
@@ -16447,7 +16445,7 @@ function cleanupTempDir(dir, log) {
|
|
|
16447
16445
|
}
|
|
16448
16446
|
}
|
|
16449
16447
|
function outputFileSize(path) {
|
|
16450
|
-
return
|
|
16448
|
+
return existsSync23(path) ? statSync10(path).size : 0;
|
|
16451
16449
|
}
|
|
16452
16450
|
function createBlockingProgressReporter(log, requestId) {
|
|
16453
16451
|
let lastLoggedPct = -10;
|
|
@@ -16751,7 +16749,7 @@ function createRenderHandlers(options = {}) {
|
|
|
16751
16749
|
if (!artifact) {
|
|
16752
16750
|
return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
|
|
16753
16751
|
}
|
|
16754
|
-
if (!
|
|
16752
|
+
if (!existsSync23(artifact.path)) {
|
|
16755
16753
|
store.delete(token);
|
|
16756
16754
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
16757
16755
|
}
|
|
@@ -42,9 +42,7 @@ import type { RenderJob } from "../../renderOrchestrator.js";
|
|
|
42
42
|
export interface CompileStageInput {
|
|
43
43
|
projectDir: string;
|
|
44
44
|
workDir: string;
|
|
45
|
-
/**
|
|
46
|
-
htmlPath: string;
|
|
47
|
-
/** The relative `entryFile` string, used only for log payloads. */
|
|
45
|
+
/** Entry HTML relative to `projectDir`; a `<template>` sub-composition is isolated inside the index shell. */
|
|
48
46
|
entryFile: string;
|
|
49
47
|
job: RenderJob;
|
|
50
48
|
/**
|
|
@@ -104,4 +102,15 @@ export interface CompileStageResult {
|
|
|
104
102
|
* `3d` | `ancestor_background_image` | `shader_transitions`. Undefined when none fired. */
|
|
105
103
|
deCompileGate?: string;
|
|
106
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* The HTML file to compile for `entryFile`. A `<template>`-wrapped
|
|
107
|
+
* sub-composition has no live root, timeline or duration of its own, so it is
|
|
108
|
+
* rendered through the project's index.html shell with only its mount host kept.
|
|
109
|
+
*/
|
|
110
|
+
export declare function resolveEntryHtmlPath(input: {
|
|
111
|
+
projectDir: string;
|
|
112
|
+
workDir: string;
|
|
113
|
+
entryFile: string;
|
|
114
|
+
log: ProducerLogger;
|
|
115
|
+
}): string;
|
|
107
116
|
export declare function runCompileStage(input: CompileStageInput): Promise<CompileStageResult>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type CanvasResolution, type Fps, type FpsInput, type HfVfxCapture } from "@hyperframes/core";
|
|
2
|
-
import { type EngineConfig, type ExtractionPhaseBreakdown, type VideoFrameFormat, type CaptureOptions, type CaptureVideoMetadataHint, type CaptureSession, type BeforeCaptureHook, type ProresProfile, type ParallelProgress, type WorkerTask, type CapturePerfSummary, type CaptureWarning, type SubTimelineWaitOutcome, type WorkerSizing, type StaticVerificationOutcome, explainDrawElementDisabled, type CaptureFailureKind, type MotionBlurOptions } from "@hyperframes/engine";
|
|
2
|
+
import { type EngineConfig, type ExtractionPhaseBreakdown, type VideoFrameFormat, type CaptureOptions, type CaptureVideoMetadataHint, type CaptureSession, type BeforeCaptureHook, type ProresProfile, type ParallelProgress, type WorkerTask, type CapturePerfSummary, type CaptureWarning, type SubTimelineWaitOutcome, type WorkerSizing, type StaticVerificationOutcome, explainDrawElementDisabled, type CaptureFailureKind, displaySize, type MotionBlurOptions } from "@hyperframes/engine";
|
|
3
3
|
import { type ReferenceBackgroundResult } from "@hyperframes/engine";
|
|
4
4
|
import { type ProducerLogger } from "../logger.js";
|
|
5
5
|
import { type RenderOutputFormat } from "./render/renderFormat.js";
|
|
@@ -480,10 +480,7 @@ export declare function createRenderFileLogger(logPath: string, base?: ProducerL
|
|
|
480
480
|
export declare function collectVideoReadinessSkipIds(nativeHdrVideoIds: ReadonlySet<string>, extractedVideos: readonly ExtractedVideoReadinessInput[]): string[];
|
|
481
481
|
interface ExtractedVideoReadinessInput {
|
|
482
482
|
videoId: string;
|
|
483
|
-
metadata:
|
|
484
|
-
width: number;
|
|
485
|
-
height: number;
|
|
486
|
-
};
|
|
483
|
+
metadata: Parameters<typeof displaySize>[0];
|
|
487
484
|
}
|
|
488
485
|
export declare function collectVideoMetadataHints(extractedVideos: readonly ExtractedVideoReadinessInput[]): CaptureVideoMetadataHint[];
|
|
489
486
|
export declare function findMissingFrameRanges(totalFrames: number, framesDir: string, frameExt: "jpg" | "png"): FrameRange[];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/producer",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.16",
|
|
4
4
|
"description": "HTML-to-video rendering engine using Chrome's BeginFrame API",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
"postcss": "^8.4.0",
|
|
36
36
|
"puppeteer": "~25.11.0",
|
|
37
37
|
"puppeteer-core": "^25.11.0",
|
|
38
|
-
"@hyperframes/
|
|
39
|
-
"@hyperframes/engine": "0.9.
|
|
40
|
-
"@hyperframes/
|
|
41
|
-
"@hyperframes/parsers": "0.9.
|
|
42
|
-
"@hyperframes/studio-server": "0.9.
|
|
38
|
+
"@hyperframes/lint": "0.9.16",
|
|
39
|
+
"@hyperframes/engine": "0.9.16",
|
|
40
|
+
"@hyperframes/core": "0.9.16",
|
|
41
|
+
"@hyperframes/parsers": "0.9.16",
|
|
42
|
+
"@hyperframes/studio-server": "0.9.16"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^25.0.10",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"tsx": "^4.21.0",
|
|
49
49
|
"typescript": "^5.7.2",
|
|
50
50
|
"vitest": "^4.1.11",
|
|
51
|
-
"@hyperframes/aws-lambda": "0.9.
|
|
51
|
+
"@hyperframes/aws-lambda": "0.9.16"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=22"
|