@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/distributed.js
CHANGED
|
@@ -8,11 +8,11 @@ import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframe
|
|
|
8
8
|
import {
|
|
9
9
|
copyFileSync as copyFileSync6,
|
|
10
10
|
cpSync as cpSync2,
|
|
11
|
-
existsSync as
|
|
11
|
+
existsSync as existsSync18,
|
|
12
12
|
linkSync,
|
|
13
13
|
mkdirSync as mkdirSync10,
|
|
14
14
|
rmSync as rmSync7,
|
|
15
|
-
writeFileSync as
|
|
15
|
+
writeFileSync as writeFileSync8
|
|
16
16
|
} from "node:fs";
|
|
17
17
|
import { extname as extname4, isAbsolute as isAbsolute3, join as join24, relative as relative5, sep as sep2 } from "node:path";
|
|
18
18
|
import {
|
|
@@ -71,12 +71,12 @@ var defaultLogger = createConsoleLogger("info");
|
|
|
71
71
|
// src/services/renderOrchestrator.ts
|
|
72
72
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
73
73
|
import {
|
|
74
|
-
existsSync as
|
|
74
|
+
existsSync as existsSync13,
|
|
75
75
|
mkdirSync as mkdirSync6,
|
|
76
76
|
readdirSync as readdirSync3,
|
|
77
77
|
rmSync as rmSync5,
|
|
78
78
|
statSync as statSync5,
|
|
79
|
-
writeFileSync as
|
|
79
|
+
writeFileSync as writeFileSync5,
|
|
80
80
|
copyFileSync as copyFileSync5,
|
|
81
81
|
appendFileSync
|
|
82
82
|
} from "fs";
|
|
@@ -90,9 +90,7 @@ import {
|
|
|
90
90
|
toFps as toFps2
|
|
91
91
|
} from "@hyperframes/core";
|
|
92
92
|
import { HF_AUDIO_GROUP_TAG } from "@hyperframes/core/audio-groups";
|
|
93
|
-
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
94
93
|
import { HTML_BODY_CSS_HEIGHT_FIRST_RE, HTML_BODY_CSS_WIDTH_FIRST_RE } from "@hyperframes/parsers";
|
|
95
|
-
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
96
94
|
import {
|
|
97
95
|
extractMediaMetadata as extractMediaMetadata4,
|
|
98
96
|
resolveConfig as resolveConfig2,
|
|
@@ -123,7 +121,8 @@ import {
|
|
|
123
121
|
isDrawElementCaptureError,
|
|
124
122
|
getDrawElementVerificationDetails,
|
|
125
123
|
augmentProtocolTimeoutError,
|
|
126
|
-
augmentPageNavigationTimeoutError
|
|
124
|
+
augmentPageNavigationTimeoutError,
|
|
125
|
+
displaySize
|
|
127
126
|
} from "@hyperframes/engine";
|
|
128
127
|
import {
|
|
129
128
|
referenceBackgroundResult,
|
|
@@ -1914,8 +1913,11 @@ import {
|
|
|
1914
1913
|
} from "@hyperframes/engine";
|
|
1915
1914
|
|
|
1916
1915
|
// src/services/render/stages/compileStage.ts
|
|
1916
|
+
import { existsSync as existsSync7, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1917
1917
|
import { join as join7 } from "node:path";
|
|
1918
1918
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
1919
|
+
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
1920
|
+
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
1919
1921
|
|
|
1920
1922
|
// src/services/htmlCompiler.ts
|
|
1921
1923
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
@@ -4620,11 +4622,36 @@ function adaptAspectAgnosticResolution(requested, aspectAgnostic, width, height,
|
|
|
4620
4622
|
});
|
|
4621
4623
|
return flipped;
|
|
4622
4624
|
}
|
|
4625
|
+
function resolveEntryHtmlPath(input) {
|
|
4626
|
+
const { projectDir, workDir, entryFile, log } = input;
|
|
4627
|
+
const htmlPath = join7(projectDir, entryFile);
|
|
4628
|
+
const rawEntry = readTextFile2(htmlPath);
|
|
4629
|
+
if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
|
|
4630
|
+
const projectIndexPath = join7(projectDir, "index.html");
|
|
4631
|
+
if (!existsSync7(projectIndexPath)) {
|
|
4632
|
+
throw new Error(
|
|
4633
|
+
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
4634
|
+
);
|
|
4635
|
+
}
|
|
4636
|
+
const standaloneHtml = extractStandaloneEntryFromIndex(
|
|
4637
|
+
readTextFile2(projectIndexPath),
|
|
4638
|
+
entryFile,
|
|
4639
|
+
rawEntry
|
|
4640
|
+
);
|
|
4641
|
+
if (!standaloneHtml) {
|
|
4642
|
+
throw new Error(
|
|
4643
|
+
`Entry file "${entryFile}" is not mounted from index.html via data-composition-src, so it cannot be rendered independently.`
|
|
4644
|
+
);
|
|
4645
|
+
}
|
|
4646
|
+
const wrapperPath = join7(workDir, "standalone-entry.html");
|
|
4647
|
+
writeFileSync3(wrapperPath, standaloneHtml, "utf-8");
|
|
4648
|
+
log.info("Extracted standalone entry from index.html host context", { entryFile });
|
|
4649
|
+
return wrapperPath;
|
|
4650
|
+
}
|
|
4623
4651
|
async function runCompileStage(input) {
|
|
4624
4652
|
const {
|
|
4625
4653
|
projectDir,
|
|
4626
4654
|
workDir,
|
|
4627
|
-
htmlPath,
|
|
4628
4655
|
entryFile,
|
|
4629
4656
|
job,
|
|
4630
4657
|
cfg,
|
|
@@ -4635,6 +4662,7 @@ async function runCompileStage(input) {
|
|
|
4635
4662
|
allowSystemFontCapture,
|
|
4636
4663
|
abortSignal
|
|
4637
4664
|
} = input;
|
|
4665
|
+
const htmlPath = resolveEntryHtmlPath({ projectDir, workDir, entryFile, log });
|
|
4638
4666
|
const compileStart = Date.now();
|
|
4639
4667
|
const compiled = await compileForRender(projectDir, htmlPath, join7(workDir, "downloads"), {
|
|
4640
4668
|
log,
|
|
@@ -5466,7 +5494,7 @@ function validateDistributedDuration(input) {
|
|
|
5466
5494
|
}
|
|
5467
5495
|
|
|
5468
5496
|
// src/services/render/stages/extractVideosStage.ts
|
|
5469
|
-
import { existsSync as
|
|
5497
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
5470
5498
|
import { join as join9 } from "node:path";
|
|
5471
5499
|
import {
|
|
5472
5500
|
classifyVideoExtractionError,
|
|
@@ -5741,7 +5769,7 @@ async function runExtractVideosStage(input) {
|
|
|
5741
5769
|
const probeFailures = await Promise.all(
|
|
5742
5770
|
composition.videos.map(async (v) => {
|
|
5743
5771
|
const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
|
|
5744
|
-
if (!
|
|
5772
|
+
if (!existsSync8(videoPath)) return null;
|
|
5745
5773
|
try {
|
|
5746
5774
|
const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry(() => extractMediaMetadata3(videoPath), {
|
|
5747
5775
|
signal: abortSignal,
|
|
@@ -5779,7 +5807,7 @@ async function runExtractVideosStage(input) {
|
|
|
5779
5807
|
const probed = await Promise.all(
|
|
5780
5808
|
composition.images.map(async (img) => {
|
|
5781
5809
|
const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
|
|
5782
|
-
if (!
|
|
5810
|
+
if (!existsSync8(imgPath)) return null;
|
|
5783
5811
|
const colorSpace = await probeColorSpaceSafely(imgPath, log);
|
|
5784
5812
|
if (isHdrColorSpace2(colorSpace)) {
|
|
5785
5813
|
nativeHdrImageIds.add(img.id);
|
|
@@ -5899,7 +5927,7 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
5899
5927
|
videos.map(async (video) => {
|
|
5900
5928
|
const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
|
|
5901
5929
|
const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
|
|
5902
|
-
if (!isHttpUrl2(readFrom) && !
|
|
5930
|
+
if (!isHttpUrl2(readFrom) && !existsSync8(readFrom)) return null;
|
|
5903
5931
|
const metadata = await extractMediaMetadata3(readFrom);
|
|
5904
5932
|
const window2 = await resolveFinalFrameExtractionWindow2(
|
|
5905
5933
|
readFrom,
|
|
@@ -5927,7 +5955,7 @@ import {
|
|
|
5927
5955
|
} from "@hyperframes/engine";
|
|
5928
5956
|
|
|
5929
5957
|
// src/services/render/stages/audioStage.ts
|
|
5930
|
-
import { existsSync as
|
|
5958
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
5931
5959
|
import { join as join12 } from "node:path";
|
|
5932
5960
|
import {
|
|
5933
5961
|
MIXED_AUDIO_FILENAME,
|
|
@@ -5938,7 +5966,7 @@ import {
|
|
|
5938
5966
|
} from "@hyperframes/engine";
|
|
5939
5967
|
|
|
5940
5968
|
// src/services/render/audioDeliveryLimit.ts
|
|
5941
|
-
import { existsSync as
|
|
5969
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
5942
5970
|
import { dirname as dirname7, join as join11 } from "node:path";
|
|
5943
5971
|
import {
|
|
5944
5972
|
applyAudioFxChain,
|
|
@@ -6445,7 +6473,7 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
6445
6473
|
applyChain: applyAudioFxChain,
|
|
6446
6474
|
encode: encodeAac2,
|
|
6447
6475
|
rename: renameWithRetry6,
|
|
6448
|
-
sourceExists:
|
|
6476
|
+
sourceExists: existsSync9
|
|
6449
6477
|
}) {
|
|
6450
6478
|
const { audioPath, sourcePath, signal, timeoutMs } = input;
|
|
6451
6479
|
if (!deps.sourceExists(sourcePath)) return { success: true };
|
|
@@ -6515,7 +6543,7 @@ async function runAudioStageScoped(input) {
|
|
|
6515
6543
|
);
|
|
6516
6544
|
audioLoweredDb = guard.loweredDb;
|
|
6517
6545
|
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
6518
|
-
if (input.keepDeliverySource && !guard.sourceStale &&
|
|
6546
|
+
if (input.keepDeliverySource && !guard.sourceStale && existsSync10(pcmSourcePath)) {
|
|
6519
6547
|
deliverySourcePath = pcmSourcePath;
|
|
6520
6548
|
}
|
|
6521
6549
|
}
|
|
@@ -6643,7 +6671,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
6643
6671
|
}
|
|
6644
6672
|
|
|
6645
6673
|
// src/services/render/stages/captureStage.ts
|
|
6646
|
-
import { existsSync as
|
|
6674
|
+
import { existsSync as existsSync11, readdirSync, statfsSync } from "node:fs";
|
|
6647
6675
|
import { join as join13 } from "node:path";
|
|
6648
6676
|
import {
|
|
6649
6677
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
@@ -6695,7 +6723,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
6695
6723
|
}
|
|
6696
6724
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
6697
6725
|
let total = sampleDirectoryBytes(framesDir);
|
|
6698
|
-
if (!
|
|
6726
|
+
if (!existsSync11(workDir)) return total;
|
|
6699
6727
|
for (const attempt of readdirSync(workDir)) {
|
|
6700
6728
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
6701
6729
|
total += sampleDirectoryBytes(join13(workDir, attempt));
|
|
@@ -7032,13 +7060,13 @@ import {
|
|
|
7032
7060
|
// src/services/render/stages/encodeStage.ts
|
|
7033
7061
|
import {
|
|
7034
7062
|
copyFileSync as copyFileSync4,
|
|
7035
|
-
existsSync as
|
|
7063
|
+
existsSync as existsSync12,
|
|
7036
7064
|
mkdirSync as mkdirSync5,
|
|
7037
7065
|
readdirSync as readdirSync2,
|
|
7038
7066
|
readFileSync as readFileSync5,
|
|
7039
7067
|
rmSync as rmSync4,
|
|
7040
7068
|
statSync as statSync4,
|
|
7041
|
-
writeFileSync as
|
|
7069
|
+
writeFileSync as writeFileSync4
|
|
7042
7070
|
} from "node:fs";
|
|
7043
7071
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
7044
7072
|
import { dirname as dirname8, join as join14 } from "node:path";
|
|
@@ -7134,7 +7162,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
|
7134
7162
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
7135
7163
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
7136
7164
|
}
|
|
7137
|
-
|
|
7165
|
+
writeFileSync4(outputPath, gif);
|
|
7138
7166
|
return null;
|
|
7139
7167
|
}
|
|
7140
7168
|
async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
@@ -7166,7 +7194,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
7166
7194
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
7167
7195
|
if (failure) return failure;
|
|
7168
7196
|
}
|
|
7169
|
-
const fileSize =
|
|
7197
|
+
const fileSize = existsSync12(outputPath) ? statSync4(outputPath).size : 0;
|
|
7170
7198
|
return {
|
|
7171
7199
|
success: true,
|
|
7172
7200
|
outputPath,
|
|
@@ -7327,7 +7355,7 @@ async function runEncodeStage(input) {
|
|
|
7327
7355
|
const stage5Start = Date.now();
|
|
7328
7356
|
if (isPngSequence) {
|
|
7329
7357
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
7330
|
-
if (!
|
|
7358
|
+
if (!existsSync12(outputPath)) mkdirSync5(outputPath, { recursive: true });
|
|
7331
7359
|
const captured = readdirSync2(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
7332
7360
|
if (captured.length === 0) {
|
|
7333
7361
|
throw new Error(
|
|
@@ -7338,7 +7366,7 @@ async function runEncodeStage(input) {
|
|
|
7338
7366
|
const dst = join14(outputPath, formatExportFrameName(i, "png"));
|
|
7339
7367
|
copyFileSync4(join14(framesDir, name), dst);
|
|
7340
7368
|
});
|
|
7341
|
-
if (hasAudio && audioOutputPath &&
|
|
7369
|
+
if (hasAudio && audioOutputPath && existsSync12(audioOutputPath)) {
|
|
7342
7370
|
copyFileSync4(audioOutputPath, join14(outputPath, MIXED_AUDIO_FILENAME2));
|
|
7343
7371
|
log.info(
|
|
7344
7372
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
@@ -7494,18 +7522,14 @@ function hasUsableVideoDimensions(metadata) {
|
|
|
7494
7522
|
return Number.isFinite(metadata.width) && Number.isFinite(metadata.height) && metadata.width > 0 && metadata.height > 0;
|
|
7495
7523
|
}
|
|
7496
7524
|
function collectVideoMetadataHints(extractedVideos) {
|
|
7497
|
-
return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({
|
|
7498
|
-
id: video.videoId,
|
|
7499
|
-
width: video.metadata.width,
|
|
7500
|
-
height: video.metadata.height
|
|
7501
|
-
})).sort((a, b) => a.id.localeCompare(b.id));
|
|
7525
|
+
return extractedVideos.filter((video) => hasUsableVideoDimensions(video.metadata)).map((video) => ({ id: video.videoId, ...displaySize(video.metadata) })).sort((a, b) => a.id.localeCompare(b.id));
|
|
7502
7526
|
}
|
|
7503
7527
|
function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
7504
7528
|
const ranges = [];
|
|
7505
7529
|
let rangeStart = null;
|
|
7506
7530
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7507
7531
|
const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7508
|
-
const missing = !
|
|
7532
|
+
const missing = !existsSync13(framePath) || statSync5(framePath).size <= 8;
|
|
7509
7533
|
if (missing && rangeStart === null) {
|
|
7510
7534
|
rangeStart = frameIndex;
|
|
7511
7535
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -7595,7 +7619,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
|
7595
7619
|
let captured = 0;
|
|
7596
7620
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7597
7621
|
const framePath = join15(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7598
|
-
if (
|
|
7622
|
+
if (existsSync13(framePath)) captured++;
|
|
7599
7623
|
}
|
|
7600
7624
|
return captured;
|
|
7601
7625
|
}
|
|
@@ -7822,7 +7846,7 @@ import {
|
|
|
7822
7846
|
} from "@hyperframes/engine";
|
|
7823
7847
|
|
|
7824
7848
|
// src/services/distributed/chunkFrames.ts
|
|
7825
|
-
import { existsSync as
|
|
7849
|
+
import { existsSync as existsSync15, readdirSync as readdirSync4 } from "node:fs";
|
|
7826
7850
|
import { extname as extname2, join as join17 } from "node:path";
|
|
7827
7851
|
import {
|
|
7828
7852
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
@@ -7834,7 +7858,7 @@ import {
|
|
|
7834
7858
|
// src/services/distributed/shared.ts
|
|
7835
7859
|
import { execFile as execFileCallback } from "node:child_process";
|
|
7836
7860
|
import { dirname as dirname10, join as join16 } from "node:path";
|
|
7837
|
-
import { existsSync as
|
|
7861
|
+
import { existsSync as existsSync14, readFileSync as readFileSync6 } from "node:fs";
|
|
7838
7862
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7839
7863
|
import { promisify as promisify2 } from "node:util";
|
|
7840
7864
|
import {
|
|
@@ -7850,7 +7874,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
7850
7874
|
function resolvePlanAudioPath(planDir) {
|
|
7851
7875
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
7852
7876
|
const candidate = join16(planDir, name);
|
|
7853
|
-
if (
|
|
7877
|
+
if (existsSync14(candidate)) return candidate;
|
|
7854
7878
|
}
|
|
7855
7879
|
return null;
|
|
7856
7880
|
}
|
|
@@ -8111,7 +8135,7 @@ function readProducerVersion() {
|
|
|
8111
8135
|
let current = startDir;
|
|
8112
8136
|
for (let i = 0; i < 10; i++) {
|
|
8113
8137
|
const candidate = join16(current, "package.json");
|
|
8114
|
-
if (
|
|
8138
|
+
if (existsSync14(candidate)) {
|
|
8115
8139
|
try {
|
|
8116
8140
|
const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
|
|
8117
8141
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
@@ -8165,7 +8189,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
8165
8189
|
const result = [];
|
|
8166
8190
|
for (const v of videos) {
|
|
8167
8191
|
const outputDir = join17(planDir, "video-frames", v.videoId);
|
|
8168
|
-
if (!
|
|
8192
|
+
if (!existsSync15(outputDir)) {
|
|
8169
8193
|
throw new Error(
|
|
8170
8194
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
8171
8195
|
);
|
|
@@ -8281,7 +8305,7 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
8281
8305
|
const range = video.deferredRange;
|
|
8282
8306
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
8283
8307
|
const sourcePath = remoteUrl ?? join17(input.planDir, ...range.sourcePath.split("/"));
|
|
8284
|
-
if (remoteUrl === void 0 && !
|
|
8308
|
+
if (remoteUrl === void 0 && !existsSync15(sourcePath)) {
|
|
8285
8309
|
throw new RenderChunkValidationError(
|
|
8286
8310
|
MISSING_PLAN_ARTIFACT,
|
|
8287
8311
|
`[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
|
|
@@ -8499,7 +8523,7 @@ async function readPlanReferenceBackground(input) {
|
|
|
8499
8523
|
}
|
|
8500
8524
|
|
|
8501
8525
|
// src/services/render/stages/freezePlan.ts
|
|
8502
|
-
import { existsSync as
|
|
8526
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
8503
8527
|
import { join as join20, relative as relative3, resolve as resolve8 } from "node:path";
|
|
8504
8528
|
import {
|
|
8505
8529
|
REFERENCE_BACKGROUND_FIELD,
|
|
@@ -8693,7 +8717,7 @@ function planFileSha256(absolutePath, digests) {
|
|
|
8693
8717
|
return sha256;
|
|
8694
8718
|
}
|
|
8695
8719
|
async function prehashPlanFiles(dir, digests) {
|
|
8696
|
-
if (!
|
|
8720
|
+
if (!existsSync16(dir)) return;
|
|
8697
8721
|
for (const { absolutePath } of listPlanFiles(dir)) {
|
|
8698
8722
|
if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
|
|
8699
8723
|
}
|
|
@@ -8759,19 +8783,19 @@ var PLAN_REMOTE_FILES_RELATIVE_PATH = "meta/remote-files.json";
|
|
|
8759
8783
|
function writePlanRemoteFiles(planDir, files) {
|
|
8760
8784
|
if (files.length === 0) return;
|
|
8761
8785
|
const sorted = [...files].sort((a, b) => a.path < b.path ? -1 : 1);
|
|
8762
|
-
|
|
8786
|
+
writeFileSync6(join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH), JSON.stringify(sorted), "utf-8");
|
|
8763
8787
|
}
|
|
8764
8788
|
function readPlanRemoteFiles(planDir) {
|
|
8765
8789
|
const path = join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
8766
|
-
return
|
|
8790
|
+
return existsSync16(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
|
|
8767
8791
|
}
|
|
8768
8792
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
8769
8793
|
const planJsonPath = join20(planDir, "plan.json");
|
|
8770
8794
|
const encoderJsonPath = join20(planDir, "meta", "encoder.json");
|
|
8771
|
-
if (!
|
|
8795
|
+
if (!existsSync16(planJsonPath)) {
|
|
8772
8796
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
8773
8797
|
}
|
|
8774
|
-
if (!
|
|
8798
|
+
if (!existsSync16(encoderJsonPath)) {
|
|
8775
8799
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
8776
8800
|
}
|
|
8777
8801
|
const planJson = JSON.parse(readFileSync7(planJsonPath, "utf-8"));
|
|
@@ -8801,12 +8825,12 @@ async function freezePlan(input) {
|
|
|
8801
8825
|
hasAudio,
|
|
8802
8826
|
digests
|
|
8803
8827
|
} = input;
|
|
8804
|
-
if (!
|
|
8828
|
+
if (!existsSync16(planDir)) {
|
|
8805
8829
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
8806
8830
|
}
|
|
8807
8831
|
const metaDir = join20(planDir, "meta");
|
|
8808
|
-
if (!
|
|
8809
|
-
|
|
8832
|
+
if (!existsSync16(metaDir)) mkdirSync7(metaDir, { recursive: true });
|
|
8833
|
+
writeFileSync6(
|
|
8810
8834
|
join20(metaDir, "composition.json"),
|
|
8811
8835
|
`${JSON.stringify(composition, null, 2)}
|
|
8812
8836
|
`,
|
|
@@ -8814,8 +8838,8 @@ async function freezePlan(input) {
|
|
|
8814
8838
|
);
|
|
8815
8839
|
const encoderForCanonical = stripUndefined(encoder);
|
|
8816
8840
|
const encoderConfigCanonicalJson = canonicalJsonStringify(encoderForCanonical);
|
|
8817
|
-
|
|
8818
|
-
|
|
8841
|
+
writeFileSync6(join20(metaDir, "encoder.json"), encoderConfigCanonicalJson, "utf-8");
|
|
8842
|
+
writeFileSync6(join20(metaDir, "chunks.json"), `${JSON.stringify(chunks, null, 2)}
|
|
8819
8843
|
`, "utf-8");
|
|
8820
8844
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
8821
8845
|
const planHash = computePlanHash({
|
|
@@ -8841,7 +8865,7 @@ async function freezePlan(input) {
|
|
|
8841
8865
|
hasAudio
|
|
8842
8866
|
};
|
|
8843
8867
|
const planJsonPath = join20(planDir, "plan.json");
|
|
8844
|
-
|
|
8868
|
+
writeFileSync6(planJsonPath, `${JSON.stringify(planJson, null, 2)}
|
|
8845
8869
|
`, "utf-8");
|
|
8846
8870
|
return { planJsonPath, planHash };
|
|
8847
8871
|
}
|
|
@@ -8882,8 +8906,8 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
8882
8906
|
}
|
|
8883
8907
|
|
|
8884
8908
|
// src/services/distributed/chunkAudio.ts
|
|
8885
|
-
import { closeSync as closeSync4, existsSync as
|
|
8886
|
-
import { writeFileSync as
|
|
8909
|
+
import { closeSync as closeSync4, existsSync as existsSync17, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
|
|
8910
|
+
import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
|
|
8887
8911
|
import { join as join22 } from "node:path";
|
|
8888
8912
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
8889
8913
|
|
|
@@ -9365,15 +9389,15 @@ async function writeChunkAudioSlices(input) {
|
|
|
9365
9389
|
const start = offsets[cut.firstPacket];
|
|
9366
9390
|
layout.partStarts[index] = start;
|
|
9367
9391
|
const end = offsets[last] + track.sizes[last];
|
|
9368
|
-
|
|
9392
|
+
writeFileSync7(join22(input.planDir, chunkAudioSlicePath(index)), bytes.subarray(start, end));
|
|
9369
9393
|
});
|
|
9370
9394
|
const frame = [bytes.subarray(0, payloadStart), bytes.subarray(payloadEnd)];
|
|
9371
|
-
|
|
9372
|
-
|
|
9395
|
+
writeFileSync7(join22(input.planDir, SLICE_FRAME), Buffer.concat(frame));
|
|
9396
|
+
writeFileSync7(join22(input.planDir, SLICE_LAYOUT), JSON.stringify(layout));
|
|
9373
9397
|
}
|
|
9374
9398
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
9375
9399
|
const layoutPath = join22(planDir, SLICE_LAYOUT);
|
|
9376
|
-
if (!
|
|
9400
|
+
if (!existsSync17(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
9377
9401
|
const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
|
|
9378
9402
|
const frame = readFileSync8(join22(planDir, SLICE_FRAME));
|
|
9379
9403
|
const fd = openSync4(scratchPath, "w");
|
|
@@ -9937,7 +9961,7 @@ function createVideoSourceShipper(input) {
|
|
|
9937
9961
|
let result = null;
|
|
9938
9962
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
9939
9963
|
const rel = pathInside(root, srcPath);
|
|
9940
|
-
if (rel !== null && (
|
|
9964
|
+
if (rel !== null && (existsSync18(join24(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
9941
9965
|
result = ["compiled", ...rel.split(sep2)].join("/");
|
|
9942
9966
|
break;
|
|
9943
9967
|
}
|
|
@@ -9965,7 +9989,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9965
9989
|
useGpu: false,
|
|
9966
9990
|
browserGpuMode: "software"
|
|
9967
9991
|
});
|
|
9968
|
-
if (!
|
|
9992
|
+
if (!existsSync18(planDir)) mkdirSync10(planDir, { recursive: true });
|
|
9969
9993
|
const log = config.logger ?? defaultLogger;
|
|
9970
9994
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
9971
9995
|
const abortSignal = config.abortSignal;
|
|
@@ -9995,7 +10019,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
9995
10019
|
});
|
|
9996
10020
|
const entryFile = config.entryFile ?? "index.html";
|
|
9997
10021
|
const htmlPath = join24(projectDir, entryFile);
|
|
9998
|
-
if (!
|
|
10022
|
+
if (!existsSync18(htmlPath)) {
|
|
9999
10023
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
10000
10024
|
}
|
|
10001
10025
|
const workDir = join24(planDir, ".plan-work");
|
|
@@ -10039,7 +10063,6 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
10039
10063
|
urlByAbsentLocalPath,
|
|
10040
10064
|
projectDir,
|
|
10041
10065
|
workDir,
|
|
10042
|
-
htmlPath,
|
|
10043
10066
|
entryFile,
|
|
10044
10067
|
job,
|
|
10045
10068
|
cfg,
|
|
@@ -10224,15 +10247,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10224
10247
|
logPlanStage(log, "extract", stageStarted);
|
|
10225
10248
|
const stagedVideoFrames = join24(compiledDir, "__hyperframes_video_frames");
|
|
10226
10249
|
const videoFramesDst = join24(planDir, "video-frames");
|
|
10227
|
-
if (
|
|
10250
|
+
if (existsSync18(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
|
|
10228
10251
|
const videoSourcesDir = join24(planDir, "video-sources");
|
|
10229
10252
|
rmSync7(videoSourcesDir, { recursive: true, force: true });
|
|
10230
|
-
if (
|
|
10253
|
+
if (existsSync18(stagedVideoFrames)) {
|
|
10231
10254
|
renameWithRetry8(stagedVideoFrames, videoFramesDst);
|
|
10232
10255
|
} else {
|
|
10233
10256
|
mkdirSync10(videoFramesDst, { recursive: true });
|
|
10234
10257
|
}
|
|
10235
|
-
if (
|
|
10258
|
+
if (existsSync18(finalCompiledDir)) rmSync7(finalCompiledDir, { recursive: true, force: true });
|
|
10236
10259
|
renameWithRetry8(compiledDir, finalCompiledDir);
|
|
10237
10260
|
const shipVideoSource = createVideoSourceShipper({
|
|
10238
10261
|
planDir,
|
|
@@ -10271,7 +10294,7 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10271
10294
|
sizeBytes
|
|
10272
10295
|
}))
|
|
10273
10296
|
);
|
|
10274
|
-
|
|
10297
|
+
writeFileSync8(
|
|
10275
10298
|
join24(planDir, PLAN_VIDEOS_META_RELATIVE_PATH),
|
|
10276
10299
|
JSON.stringify(planVideosJson, null, 2),
|
|
10277
10300
|
"utf-8"
|
|
@@ -10352,10 +10375,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10352
10375
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
10353
10376
|
}
|
|
10354
10377
|
const planAudioPath = join24(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
10355
|
-
if (audioResult?.hasAudio &&
|
|
10378
|
+
if (audioResult?.hasAudio && existsSync18(audioResult.audioOutputPath)) {
|
|
10356
10379
|
renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
|
|
10357
10380
|
}
|
|
10358
|
-
if (chunksCarryAudio &&
|
|
10381
|
+
if (chunksCarryAudio && existsSync18(planAudioPath)) {
|
|
10359
10382
|
if (options.sliceChunkAudio) {
|
|
10360
10383
|
stageStarted = Date.now();
|
|
10361
10384
|
await writeChunkAudioSlices({
|
|
@@ -10468,7 +10491,7 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
|
|
|
10468
10491
|
if (mixed.audioError) {
|
|
10469
10492
|
applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
|
|
10470
10493
|
}
|
|
10471
|
-
if (!mixed.hasAudio || !
|
|
10494
|
+
if (!mixed.hasAudio || !existsSync18(mixed.audioOutputPath)) return { hasAudio: false };
|
|
10472
10495
|
renameWithRetry8(mixed.audioOutputPath, outputPath);
|
|
10473
10496
|
return { hasAudio: true };
|
|
10474
10497
|
} finally {
|
|
@@ -10483,7 +10506,7 @@ import {
|
|
|
10483
10506
|
} from "@hyperframes/engine";
|
|
10484
10507
|
import {
|
|
10485
10508
|
copyFileSync as copyFileSync8,
|
|
10486
|
-
existsSync as
|
|
10509
|
+
existsSync as existsSync20,
|
|
10487
10510
|
lstatSync as lstatSync2,
|
|
10488
10511
|
mkdirSync as mkdirSync12,
|
|
10489
10512
|
mkdtempSync as mkdtempSync4,
|
|
@@ -10491,7 +10514,7 @@ import {
|
|
|
10491
10514
|
readdirSync as readdirSync7,
|
|
10492
10515
|
rmSync as rmSync9,
|
|
10493
10516
|
statSync as statSync7,
|
|
10494
|
-
writeFileSync as
|
|
10517
|
+
writeFileSync as writeFileSync10
|
|
10495
10518
|
} from "node:fs";
|
|
10496
10519
|
import { tmpdir } from "node:os";
|
|
10497
10520
|
import { dirname as dirname13, isAbsolute as isAbsolute4, join as join27, relative as relative6, resolve as resolve9, sep as sep3 } from "node:path";
|
|
@@ -10501,13 +10524,13 @@ import { createFrameLookupTable as createFrameLookupTable4, renameWithRetry as r
|
|
|
10501
10524
|
// src/services/distributed/planV2Publisher.ts
|
|
10502
10525
|
import {
|
|
10503
10526
|
copyFileSync as copyFileSync7,
|
|
10504
|
-
existsSync as
|
|
10527
|
+
existsSync as existsSync19,
|
|
10505
10528
|
linkSync as linkSync2,
|
|
10506
10529
|
mkdirSync as mkdirSync11,
|
|
10507
10530
|
mkdtempSync as mkdtempSync3,
|
|
10508
10531
|
rmSync as rmSync8,
|
|
10509
10532
|
statSync as statSync6,
|
|
10510
|
-
writeFileSync as
|
|
10533
|
+
writeFileSync as writeFileSync9
|
|
10511
10534
|
} from "node:fs";
|
|
10512
10535
|
import { renameWithRetry as renameWithRetry9 } from "@hyperframes/engine";
|
|
10513
10536
|
import { dirname as dirname12, join as join26 } from "node:path";
|
|
@@ -10570,7 +10593,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10570
10593
|
#linkFile;
|
|
10571
10594
|
#committed = false;
|
|
10572
10595
|
constructor(destinationDir, options = {}) {
|
|
10573
|
-
if (
|
|
10596
|
+
if (existsSync19(destinationDir)) {
|
|
10574
10597
|
throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
|
|
10575
10598
|
}
|
|
10576
10599
|
this.destinationDir = destinationDir;
|
|
@@ -10587,7 +10610,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10587
10610
|
);
|
|
10588
10611
|
}
|
|
10589
10612
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
10590
|
-
if (
|
|
10613
|
+
if (existsSync19(destinationPath)) return;
|
|
10591
10614
|
mkdirSync11(dirname12(destinationPath), { recursive: true });
|
|
10592
10615
|
const stagingDir = mkdtempSync3(join26(dirname12(destinationPath), ".plan-v2-blob-"));
|
|
10593
10616
|
const temporaryPath = join26(stagingDir, "blob");
|
|
@@ -10605,13 +10628,13 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10605
10628
|
}
|
|
10606
10629
|
async commitManifest(manifestBytes) {
|
|
10607
10630
|
for (const digest of new Set(manifestDigests(manifestBytes))) {
|
|
10608
|
-
if (!
|
|
10631
|
+
if (!existsSync19(planV2BlobPath(this.temporaryDir, digest))) {
|
|
10609
10632
|
throw new PlanV2IntegrityError(
|
|
10610
10633
|
`cannot commit manifest before referenced blob is durable: ${digest}`
|
|
10611
10634
|
);
|
|
10612
10635
|
}
|
|
10613
10636
|
}
|
|
10614
|
-
|
|
10637
|
+
writeFileSync9(join26(this.temporaryDir, "plan.json"), manifestBytes, "utf-8");
|
|
10615
10638
|
renameWithRetry9(this.temporaryDir, this.destinationDir);
|
|
10616
10639
|
this.#committed = true;
|
|
10617
10640
|
}
|
|
@@ -10684,7 +10707,7 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
10684
10707
|
}
|
|
10685
10708
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
10686
10709
|
const videoRoot = join27(executionPlanDir, "video-frames");
|
|
10687
|
-
if (!
|
|
10710
|
+
if (!existsSync20(videoRoot)) return;
|
|
10688
10711
|
for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
|
|
10689
10712
|
if (!videoEntry.isDirectory()) continue;
|
|
10690
10713
|
const videoDir = join27(videoRoot, videoEntry.name);
|
|
@@ -10785,7 +10808,7 @@ function parsePlanVideosJson2(value) {
|
|
|
10785
10808
|
}
|
|
10786
10809
|
function materializeExtractedVideoDirectories(planDir) {
|
|
10787
10810
|
const videosPath = join27(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10788
|
-
if (!
|
|
10811
|
+
if (!existsSync20(videosPath)) return;
|
|
10789
10812
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
10790
10813
|
for (const video of videos.extracted) {
|
|
10791
10814
|
mkdirSync12(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
@@ -10819,9 +10842,9 @@ function parseChunkSlices(value) {
|
|
|
10819
10842
|
}
|
|
10820
10843
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
10821
10844
|
const videoRoot = join27(executionPlanDir, "video-frames");
|
|
10822
|
-
const hasExtractedFrames =
|
|
10845
|
+
const hasExtractedFrames = existsSync20(videoRoot) && listFiles(videoRoot).length > 0;
|
|
10823
10846
|
const videosPath = join27(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10824
|
-
if (!
|
|
10847
|
+
if (!existsSync20(videosPath)) {
|
|
10825
10848
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
10826
10849
|
}
|
|
10827
10850
|
const chunksPath = join27(executionPlanDir, "meta", "chunks.json");
|
|
@@ -10878,7 +10901,7 @@ function computeManifestHash(manifest) {
|
|
|
10878
10901
|
return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
|
|
10879
10902
|
}
|
|
10880
10903
|
function writeBlob(sourcePath, destinationPath) {
|
|
10881
|
-
if (
|
|
10904
|
+
if (existsSync20(destinationPath)) return;
|
|
10882
10905
|
mkdirSync12(dirname13(destinationPath), { recursive: true });
|
|
10883
10906
|
const temporaryDir = mkdtempSync4(join27(dirname13(destinationPath), ".plan-v2-blob-"));
|
|
10884
10907
|
const temporaryPath = join27(temporaryDir, "blob");
|
|
@@ -10918,7 +10941,7 @@ async function putTasks(tasks) {
|
|
|
10918
10941
|
}
|
|
10919
10942
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
10920
10943
|
const executionPlanPath = join27(executionPlanDir, "plan.json");
|
|
10921
|
-
if (!
|
|
10944
|
+
if (!existsSync20(executionPlanPath)) {
|
|
10922
10945
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
10923
10946
|
}
|
|
10924
10947
|
const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
|
|
@@ -10998,7 +11021,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
10998
11021
|
};
|
|
10999
11022
|
}
|
|
11000
11023
|
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
11001
|
-
if (
|
|
11024
|
+
if (existsSync20(planV2Dir)) {
|
|
11002
11025
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
11003
11026
|
}
|
|
11004
11027
|
const publication = buildPlanV2Publication(executionPlanDir);
|
|
@@ -11011,7 +11034,7 @@ function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
|
11011
11034
|
for (const blob of publication.blobs) {
|
|
11012
11035
|
writeBlob(blob.sourcePath, planV2BlobPath(tempDir, blob.sha256));
|
|
11013
11036
|
}
|
|
11014
|
-
|
|
11037
|
+
writeFileSync10(
|
|
11015
11038
|
join27(tempDir, "plan.json"),
|
|
11016
11039
|
canonicalJsonStringify(publication.manifest),
|
|
11017
11040
|
"utf-8"
|
|
@@ -11084,7 +11107,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
11084
11107
|
}
|
|
11085
11108
|
}
|
|
11086
11109
|
async function planV2(projectDir, config, planV2Dir) {
|
|
11087
|
-
if (
|
|
11110
|
+
if (existsSync20(planV2Dir)) {
|
|
11088
11111
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
11089
11112
|
}
|
|
11090
11113
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
@@ -11238,7 +11261,7 @@ function parsePlanV2Manifest(value) {
|
|
|
11238
11261
|
}
|
|
11239
11262
|
function readPlanV2Manifest(planV2Dir) {
|
|
11240
11263
|
const manifestPath = join27(planV2Dir, "plan.json");
|
|
11241
|
-
if (!
|
|
11264
|
+
if (!existsSync20(manifestPath)) {
|
|
11242
11265
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
11243
11266
|
}
|
|
11244
11267
|
const value = readJsonFile(manifestPath, "v2 manifest");
|
|
@@ -11257,7 +11280,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
|
|
|
11257
11280
|
}
|
|
11258
11281
|
function verifyBlob(planV2Dir, artifact) {
|
|
11259
11282
|
const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
|
|
11260
|
-
if (!
|
|
11283
|
+
if (!existsSync20(sourcePath)) {
|
|
11261
11284
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
11262
11285
|
}
|
|
11263
11286
|
const stats = statSync7(sourcePath);
|
|
@@ -11270,7 +11293,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
11270
11293
|
return sourcePath;
|
|
11271
11294
|
}
|
|
11272
11295
|
function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
|
|
11273
|
-
if (
|
|
11296
|
+
if (existsSync20(destinationDir)) {
|
|
11274
11297
|
throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
|
|
11275
11298
|
}
|
|
11276
11299
|
const manifest = readPlanV2Manifest(planV2Dir);
|
|
@@ -11291,7 +11314,7 @@ function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}
|
|
|
11291
11314
|
copyFileSync8(sourcePath, destinationPath);
|
|
11292
11315
|
}
|
|
11293
11316
|
if (target.role === "chunk") materializeExtractedVideoDirectories(tempDir);
|
|
11294
|
-
|
|
11317
|
+
writeFileSync10(
|
|
11295
11318
|
join27(tempDir, PLAN_V2_MATERIALIZATION_MARKER),
|
|
11296
11319
|
canonicalJsonStringify({
|
|
11297
11320
|
manifest,
|
|
@@ -11335,7 +11358,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
11335
11358
|
}
|
|
11336
11359
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
11337
11360
|
const markerPath = join27(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
11338
|
-
if (!
|
|
11361
|
+
if (!existsSync20(markerPath)) return null;
|
|
11339
11362
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
11340
11363
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
11341
11364
|
throw new PlanV2IntegrityError("malformed materialization marker");
|
|
@@ -11353,7 +11376,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
11353
11376
|
for (const artifact of required) {
|
|
11354
11377
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
11355
11378
|
const path = join27(planDir, ...artifact.path.split("/"));
|
|
11356
|
-
if (!
|
|
11379
|
+
if (!existsSync20(path) || statSync7(path).size !== artifact.sizeBytes) {
|
|
11357
11380
|
throw new PlanV2IntegrityError(
|
|
11358
11381
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
11359
11382
|
);
|
|
@@ -11377,14 +11400,14 @@ import {
|
|
|
11377
11400
|
} from "@hyperframes/engine";
|
|
11378
11401
|
import {
|
|
11379
11402
|
cpSync as cpSync3,
|
|
11380
|
-
existsSync as
|
|
11403
|
+
existsSync as existsSync21,
|
|
11381
11404
|
mkdirSync as mkdirSync13,
|
|
11382
11405
|
mkdtempSync as mkdtempSync5,
|
|
11383
11406
|
readFileSync as readFileSync10,
|
|
11384
11407
|
readdirSync as readdirSync8,
|
|
11385
11408
|
rmSync as rmSync10,
|
|
11386
11409
|
statSync as statSync8,
|
|
11387
|
-
writeFileSync as
|
|
11410
|
+
writeFileSync as writeFileSync11
|
|
11388
11411
|
} from "node:fs";
|
|
11389
11412
|
import { dirname as dirname14, join as join28 } from "node:path";
|
|
11390
11413
|
import {
|
|
@@ -11432,7 +11455,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11432
11455
|
}
|
|
11433
11456
|
const format = plan.dimensions.format;
|
|
11434
11457
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
11435
|
-
if (!
|
|
11458
|
+
if (!existsSync21(dirname14(outputPath))) {
|
|
11436
11459
|
mkdirSync13(dirname14(outputPath), { recursive: true });
|
|
11437
11460
|
}
|
|
11438
11461
|
const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
|
|
@@ -11456,7 +11479,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11456
11479
|
try {
|
|
11457
11480
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
11458
11481
|
const finalPath = join28(workDir, `output.${format}`);
|
|
11459
|
-
const audio = audioPath !== null &&
|
|
11482
|
+
const audio = audioPath !== null && existsSync21(audioPath) ? audioPath : null;
|
|
11460
11483
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
11461
11484
|
renameWithRetry11(finalPath, outputPath);
|
|
11462
11485
|
log.info("[assemble] stage timings", {
|
|
@@ -11474,7 +11497,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11474
11497
|
});
|
|
11475
11498
|
}
|
|
11476
11499
|
}
|
|
11477
|
-
const fileSize =
|
|
11500
|
+
const fileSize = existsSync21(outputPath) ? statSync8(outputPath).size : 0;
|
|
11478
11501
|
return {
|
|
11479
11502
|
...referenceBackgroundResult6(plan[REFERENCE_BACKGROUND_FIELD4]),
|
|
11480
11503
|
outputPath,
|
|
@@ -11486,13 +11509,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11486
11509
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
11487
11510
|
const planJsonPath = join28(planDir, "plan.json");
|
|
11488
11511
|
const chunksJsonPath = join28(planDir, "meta", "chunks.json");
|
|
11489
|
-
if (!
|
|
11512
|
+
if (!existsSync21(planJsonPath)) {
|
|
11490
11513
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
11491
11514
|
}
|
|
11492
11515
|
const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
|
|
11493
11516
|
readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
11494
11517
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
11495
|
-
if (!
|
|
11518
|
+
if (!existsSync21(chunksJsonPath)) {
|
|
11496
11519
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
11497
11520
|
}
|
|
11498
11521
|
const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
|
|
@@ -11507,7 +11530,7 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
11507
11530
|
}
|
|
11508
11531
|
}
|
|
11509
11532
|
for (const path of chunkPaths.flat()) {
|
|
11510
|
-
if (!
|
|
11533
|
+
if (!existsSync21(path)) {
|
|
11511
11534
|
throw new Error(`[assemble] chunk path does not exist: ${path}`);
|
|
11512
11535
|
}
|
|
11513
11536
|
}
|
|
@@ -11605,7 +11628,7 @@ async function writeJoined(ctx, videoSources, audioPath, finalPath) {
|
|
|
11605
11628
|
function concatDemuxerInput(chunkPaths, workDir, fpsArg) {
|
|
11606
11629
|
const listPath = join28(workDir, "concat-list.txt");
|
|
11607
11630
|
const body = chunkPaths.map((path) => `file '${path.replace(/'/g, "'\\''")}'`).join("\n");
|
|
11608
|
-
|
|
11631
|
+
writeFileSync11(listPath, `${body}
|
|
11609
11632
|
`, "utf-8");
|
|
11610
11633
|
return ["-r", fpsArg, "-f", "concat", "-safe", "0", "-i", listPath];
|
|
11611
11634
|
}
|
|
@@ -11646,7 +11669,7 @@ function planFormat(planDir) {
|
|
|
11646
11669
|
}
|
|
11647
11670
|
function lockedAudio(planDir) {
|
|
11648
11671
|
const encoderJsonPath = join28(planDir, "meta", "encoder.json");
|
|
11649
|
-
if (!
|
|
11672
|
+
if (!existsSync21(encoderJsonPath)) return {};
|
|
11650
11673
|
return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
11651
11674
|
}
|
|
11652
11675
|
function readCfrEncoder(planDir, format) {
|
|
@@ -11656,7 +11679,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
11656
11679
|
);
|
|
11657
11680
|
}
|
|
11658
11681
|
const encoderJsonPath = join28(planDir, "meta", "encoder.json");
|
|
11659
|
-
if (!
|
|
11682
|
+
if (!existsSync21(encoderJsonPath)) {
|
|
11660
11683
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
11661
11684
|
}
|
|
11662
11685
|
const encoderJson = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
@@ -11668,7 +11691,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
11668
11691
|
return encoderJson;
|
|
11669
11692
|
}
|
|
11670
11693
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
11671
|
-
if (
|
|
11694
|
+
if (existsSync21(outputPath)) rmSync10(outputPath, { recursive: true, force: true });
|
|
11672
11695
|
mkdirSync13(outputPath, { recursive: true });
|
|
11673
11696
|
let globalIdx = 0;
|
|
11674
11697
|
for (const chunkDir of chunkPaths) {
|
|
@@ -11692,7 +11715,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11692
11715
|
`[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
|
|
11693
11716
|
);
|
|
11694
11717
|
}
|
|
11695
|
-
if (audioPath !== null &&
|
|
11718
|
+
if (audioPath !== null && existsSync21(audioPath)) {
|
|
11696
11719
|
const sidecar = join28(outputPath, MIXED_AUDIO_FILENAME4);
|
|
11697
11720
|
cpSync3(audioPath, sidecar);
|
|
11698
11721
|
}
|
|
@@ -11713,7 +11736,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11713
11736
|
|
|
11714
11737
|
// src/services/distributed/renderChunk.ts
|
|
11715
11738
|
import { randomBytes } from "node:crypto";
|
|
11716
|
-
import { existsSync as
|
|
11739
|
+
import { existsSync as existsSync22, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
|
|
11717
11740
|
import { cpus } from "node:os";
|
|
11718
11741
|
import { join as join29 } from "node:path";
|
|
11719
11742
|
import {
|
|
@@ -11859,7 +11882,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11859
11882
|
const planJsonPath = join29(planDir, "plan.json");
|
|
11860
11883
|
const encoderJsonPath = join29(planDir, "meta", "encoder.json");
|
|
11861
11884
|
const chunksJsonPath = join29(planDir, "meta", "chunks.json");
|
|
11862
|
-
if (!
|
|
11885
|
+
if (!existsSync22(planJsonPath)) {
|
|
11863
11886
|
throw new RenderChunkValidationError(
|
|
11864
11887
|
MISSING_PLAN_ARTIFACT,
|
|
11865
11888
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
@@ -11875,7 +11898,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11875
11898
|
const v2Manifest = v2Materialization?.manifest ?? null;
|
|
11876
11899
|
const remoteSources = v2Materialization?.remoteSources ?? {};
|
|
11877
11900
|
for (const required of [encoderJsonPath, chunksJsonPath]) {
|
|
11878
|
-
if (!
|
|
11901
|
+
if (!existsSync22(required)) {
|
|
11879
11902
|
throw new RenderChunkValidationError(
|
|
11880
11903
|
MISSING_PLAN_ARTIFACT,
|
|
11881
11904
|
`[renderChunk] planDir is missing required artifact: ${required}`
|
|
@@ -11886,7 +11909,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11886
11909
|
const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
|
|
11887
11910
|
const videosJsonPath = join29(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
11888
11911
|
let planVideos = null;
|
|
11889
|
-
if (
|
|
11912
|
+
if (existsSync22(videosJsonPath)) {
|
|
11890
11913
|
try {
|
|
11891
11914
|
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync11(videosJsonPath, "utf-8")));
|
|
11892
11915
|
} catch (err) {
|
|
@@ -11917,7 +11940,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11917
11940
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
11918
11941
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
11919
11942
|
const compiledDir = join29(planDir, "compiled");
|
|
11920
|
-
if (!
|
|
11943
|
+
if (!existsSync22(compiledDir)) {
|
|
11921
11944
|
throw new RenderChunkValidationError(
|
|
11922
11945
|
MISSING_PLAN_ARTIFACT,
|
|
11923
11946
|
`[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
|
|
@@ -12167,10 +12190,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12167
12190
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
12168
12191
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
12169
12192
|
if (isPngSequence) {
|
|
12170
|
-
if (!
|
|
12193
|
+
if (!existsSync22(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
|
|
12171
12194
|
} else {
|
|
12172
12195
|
const outDir = join29(outputChunkPath, "..");
|
|
12173
|
-
if (!
|
|
12196
|
+
if (!existsSync22(outDir)) mkdirSync14(outDir, { recursive: true });
|
|
12174
12197
|
}
|
|
12175
12198
|
const encodeInput = {
|
|
12176
12199
|
job,
|
|
@@ -12380,7 +12403,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12380
12403
|
producerVersion: plan.producerVersion,
|
|
12381
12404
|
ffmpegVersion
|
|
12382
12405
|
};
|
|
12383
|
-
|
|
12406
|
+
writeFileSync12(perfPath, `${JSON.stringify(perfPayload, null, 2)}
|
|
12384
12407
|
`, "utf-8");
|
|
12385
12408
|
try {
|
|
12386
12409
|
rmSync11(workDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
@@ -12797,7 +12820,7 @@ var isMissing = (result) => result.error?.code === "ENOENT";
|
|
|
12797
12820
|
var ffmpegIsMissing = async (signal) => isMissing(await runFfmpeg9(["-version"], { signal }));
|
|
12798
12821
|
|
|
12799
12822
|
// src/services/distributed/probeFacts.ts
|
|
12800
|
-
import { writeFileSync as
|
|
12823
|
+
import { writeFileSync as writeFileSync13 } from "node:fs";
|
|
12801
12824
|
import { extname as extname6 } from "node:path";
|
|
12802
12825
|
import { buildProbeFactsText } from "@hyperframes/engine";
|
|
12803
12826
|
var PROBE_FACTS_EXTENSIONS = /* @__PURE__ */ new Set([".mp4", ".mov", ".m4v", ".webm", ".mkv"]);
|
|
@@ -12806,7 +12829,7 @@ function wantsProbeFacts(path) {
|
|
|
12806
12829
|
}
|
|
12807
12830
|
async function buildProbeFacts(inputPath, outputPath, signal) {
|
|
12808
12831
|
try {
|
|
12809
|
-
|
|
12832
|
+
writeFileSync13(outputPath, await buildProbeFactsText(inputPath, signal));
|
|
12810
12833
|
return "built";
|
|
12811
12834
|
} catch (error) {
|
|
12812
12835
|
if (signal?.aborted) throw error;
|