@hyperframes/producer 0.9.12 → 0.9.14
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
CHANGED
|
@@ -1124,27 +1124,25 @@ var materializeFileSystem = {
|
|
|
1124
1124
|
rmSync
|
|
1125
1125
|
};
|
|
1126
1126
|
var warnedSymlinkFallback = false;
|
|
1127
|
+
var copyCannotFix = (error) => ["ENOSPC", "EDQUOT", "EEXIST", "ENOENT"].includes(
|
|
1128
|
+
error?.code ?? ""
|
|
1129
|
+
);
|
|
1127
1130
|
function tryWindowsFrameJunction(fileSystem, src, dest) {
|
|
1128
1131
|
if (process.platform !== "win32") return false;
|
|
1129
1132
|
try {
|
|
1130
1133
|
fileSystem.symlinkSync(src, dest, "junction");
|
|
1131
1134
|
return true;
|
|
1132
1135
|
} catch (error) {
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
code ?? ""
|
|
1136
|
-
)) {
|
|
1137
|
-
return false;
|
|
1138
|
-
}
|
|
1139
|
-
throw error;
|
|
1136
|
+
if (copyCannotFix(error)) throw error;
|
|
1137
|
+
return false;
|
|
1140
1138
|
}
|
|
1141
1139
|
}
|
|
1142
1140
|
function linkOrCopyFrameDir(fileSystem, src, dest) {
|
|
1143
1141
|
try {
|
|
1144
1142
|
fileSystem.symlinkSync(src, dest, "dir");
|
|
1145
1143
|
} catch (err) {
|
|
1144
|
+
if (copyCannotFix(err)) throw err;
|
|
1146
1145
|
const code = err?.code;
|
|
1147
|
-
if (code !== "EPERM" && code !== "EACCES" && code !== "UNKNOWN") throw err;
|
|
1148
1146
|
if (tryWindowsFrameJunction(fileSystem, src, dest)) return;
|
|
1149
1147
|
if (!warnedSymlinkFallback) {
|
|
1150
1148
|
warnedSymlinkFallback = true;
|
|
@@ -1922,7 +1920,7 @@ import { suggestMatchingPreset } from "@hyperframes/core";
|
|
|
1922
1920
|
// src/services/htmlCompiler.ts
|
|
1923
1921
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
1924
1922
|
import { createHash as createHash4 } from "node:crypto";
|
|
1925
|
-
import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync4 } from "fs";
|
|
1923
|
+
import { createReadStream as createReadStream2, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
|
|
1926
1924
|
import { join as join6, dirname as dirname5, resolve as resolve6, basename as basename3, relative as relative2 } from "path";
|
|
1927
1925
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
1928
1926
|
import {
|
|
@@ -3897,6 +3895,25 @@ ${fontFaceBlocks.join("\n")}
|
|
|
3897
3895
|
}
|
|
3898
3896
|
return result;
|
|
3899
3897
|
}
|
|
3898
|
+
function projectStylesheetFontFamilies(html, projectDir) {
|
|
3899
|
+
const families = [];
|
|
3900
|
+
for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
|
|
3901
|
+
const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
3902
|
+
if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
|
|
3903
|
+
const sheet = resolve6(projectDir, decodedUrlPath(href).replace(/^(?:\.?\/)+/, ""));
|
|
3904
|
+
if (!isPathInside2(sheet, projectDir) || !existsSync6(sheet)) continue;
|
|
3905
|
+
for (const face of extractFontFaceBlocks(readFileSync4(sheet, "utf8"))) {
|
|
3906
|
+
const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
3907
|
+
const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
|
|
3908
|
+
const path = decodedUrlPath(url);
|
|
3909
|
+
const file = path.startsWith("/") ? resolve6(projectDir, `.${path}`) : resolve6(dirname5(sheet), path);
|
|
3910
|
+
return isPathInside2(file, projectDir) && existsSync6(file);
|
|
3911
|
+
});
|
|
3912
|
+
if (family && shipsFile) families.push(family);
|
|
3913
|
+
}
|
|
3914
|
+
}
|
|
3915
|
+
return families;
|
|
3916
|
+
}
|
|
3900
3917
|
function collectFontFaceUrls(html) {
|
|
3901
3918
|
const styleBlockRe = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
|
|
3902
3919
|
const urlSet = /* @__PURE__ */ new Set();
|
|
@@ -3927,12 +3944,10 @@ function collectFontFaceUrls(html) {
|
|
|
3927
3944
|
}
|
|
3928
3945
|
return urlSet;
|
|
3929
3946
|
}
|
|
3930
|
-
|
|
3931
|
-
const processed = await inlineExternalFontStylesheets(html);
|
|
3932
|
-
const urlSet = collectFontFaceUrls(processed);
|
|
3947
|
+
function localizeFontFaceUrls(html, downloadDir) {
|
|
3933
3948
|
return downloadAndRewriteUrls(
|
|
3934
|
-
|
|
3935
|
-
|
|
3949
|
+
collectFontFaceUrls(html),
|
|
3950
|
+
html,
|
|
3936
3951
|
join6(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
3937
3952
|
"Remote font download failed",
|
|
3938
3953
|
"Localized remote font face(s)",
|
|
@@ -4100,15 +4115,17 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
4100
4115
|
const hasShaderTransitions = detectShaderTransitionUsage(sanitizedHtml);
|
|
4101
4116
|
const usesThreeDTransforms = detectThreeDTransformUsage(sanitizedHtml);
|
|
4102
4117
|
const hasAncestorBackgroundImage = detectAncestorBackgroundImage(sanitizedHtml);
|
|
4118
|
+
const fontSheetHtml = await inlineExternalFontStylesheets(
|
|
4119
|
+
promoteCssImportsToLinkTags(sanitizedHtml)
|
|
4120
|
+
);
|
|
4103
4121
|
const normalizedFontHtml = normalizeSystemFontPrimaryFamilies(
|
|
4104
|
-
injectTextRenderingRule(
|
|
4105
|
-
coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml))
|
|
4106
|
-
)
|
|
4122
|
+
injectTextRenderingRule(coalesceHeadStylesAndBodyScripts(fontSheetHtml))
|
|
4107
4123
|
);
|
|
4108
4124
|
const coalescedHtml = await injectDeterministicFontFaces(normalizedFontHtml, {
|
|
4109
4125
|
logger: defaultLogger,
|
|
4110
4126
|
failClosedFontFetch: options.failClosedFontFetch === true,
|
|
4111
4127
|
allowSystemFontCapture: options.allowSystemFontCapture,
|
|
4128
|
+
projectFontFamilies: projectStylesheetFontFamilies(fontSheetHtml, projectDir),
|
|
4112
4129
|
abortSignal: options.abortSignal
|
|
4113
4130
|
});
|
|
4114
4131
|
const assembledHtml = await inlineExternalScripts(coalescedHtml);
|
|
@@ -4134,7 +4151,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
4134
4151
|
);
|
|
4135
4152
|
const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
|
|
4136
4153
|
const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
|
|
4137
|
-
const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await
|
|
4154
|
+
const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeFontFaceUrls(htmlWithLocalBg, downloadDir);
|
|
4138
4155
|
const {
|
|
4139
4156
|
html: htmlWithJpegs,
|
|
4140
4157
|
preparedAssets: preparedHeifAssets,
|
|
@@ -5585,17 +5602,24 @@ var VideoExtractionStageError = class extends Error {
|
|
|
5585
5602
|
publicMetadata;
|
|
5586
5603
|
};
|
|
5587
5604
|
function assertVideoExtractionSucceeded(result) {
|
|
5588
|
-
|
|
5605
|
+
throwIfHostFailure(result);
|
|
5589
5606
|
const error = buildVideoExtractionStageError(result);
|
|
5590
5607
|
if (error) throw error;
|
|
5591
5608
|
}
|
|
5592
|
-
function
|
|
5609
|
+
function throwIfHostFailure(result) {
|
|
5593
5610
|
const interrupted = result.errors.find((failure) => failure.kind === "external_interruption");
|
|
5594
|
-
if (
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5611
|
+
if (interrupted) {
|
|
5612
|
+
throw encoderFailureError("Video frame extraction failed", {
|
|
5613
|
+
error: String(interrupted.error),
|
|
5614
|
+
failureReason: "external_interruption"
|
|
5615
|
+
});
|
|
5616
|
+
}
|
|
5617
|
+
const full = result.errors.filter((failure) => failure.kind === "disk_full").length;
|
|
5618
|
+
if (full === 0) return;
|
|
5619
|
+
throw Object.assign(
|
|
5620
|
+
new Error(`ENOSPC: no space left on device, extracting the frames of ${full} video source(s)`),
|
|
5621
|
+
{ code: "ENOSPC" }
|
|
5622
|
+
);
|
|
5599
5623
|
}
|
|
5600
5624
|
function buildVideoExtractionStageError(result) {
|
|
5601
5625
|
if (result.success && result.errors.length === 0) return null;
|
|
@@ -5810,7 +5834,7 @@ async function runExtractVideosStage(input) {
|
|
|
5810
5834
|
);
|
|
5811
5835
|
extractionResult.phaseBreakdown.transientRetries = (extractionResult.phaseBreakdown.transientRetries ?? 0) + hdrProbeTransientRetries;
|
|
5812
5836
|
assertNotAborted();
|
|
5813
|
-
|
|
5837
|
+
throwIfHostFailure(extractionResult);
|
|
5814
5838
|
failureToEnforce = applyVideoExtractionFailurePolicy(extractionResult, extractionPolicy, log);
|
|
5815
5839
|
materializeExtractedFramesForCompiledDir(extractionResult.extracted, compiledDir, {
|
|
5816
5840
|
materializeSymlinks
|
|
@@ -6965,7 +6989,7 @@ import {
|
|
|
6965
6989
|
existsSync as existsSync10,
|
|
6966
6990
|
mkdirSync as mkdirSync5,
|
|
6967
6991
|
readdirSync as readdirSync2,
|
|
6968
|
-
readFileSync as
|
|
6992
|
+
readFileSync as readFileSync5,
|
|
6969
6993
|
rmSync as rmSync4,
|
|
6970
6994
|
statSync as statSync4,
|
|
6971
6995
|
writeFileSync as writeFileSync3
|
|
@@ -7059,7 +7083,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
|
7059
7083
|
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
7060
7084
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
7061
7085
|
}
|
|
7062
|
-
const gif =
|
|
7086
|
+
const gif = readFileSync5(outputPath);
|
|
7063
7087
|
if (!clearGifFramesBeforeNext(gif)) {
|
|
7064
7088
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
7065
7089
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
@@ -7092,7 +7116,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
7092
7116
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
7093
7117
|
}
|
|
7094
7118
|
}
|
|
7095
|
-
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(
|
|
7119
|
+
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync5(outputPath)) !== false) {
|
|
7096
7120
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
7097
7121
|
if (failure) return failure;
|
|
7098
7122
|
}
|
|
@@ -7651,6 +7675,22 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
7651
7675
|
}
|
|
7652
7676
|
const remainingCount = countFrameRanges(remaining);
|
|
7653
7677
|
const madeProgress = captureAttemptMadeProgress(frameCount, remainingCount);
|
|
7678
|
+
if (options.allowRetry && !madeProgress && (currentWorkers > 1 || initializationRetriesUsed === 0 && failure.kind === "protocol_timeout") && /\bNetwork\.enable timed out|Navigation timeout of \d+ ms exceeded/i.test(failure.message)) {
|
|
7679
|
+
initializationRetriesUsed++;
|
|
7680
|
+
const nextWorkers2 = getNextRetryWorkerCount(currentWorkers);
|
|
7681
|
+
options.log.warn(
|
|
7682
|
+
"[Render] Browser initialization timed out; retrying with fewer workers.",
|
|
7683
|
+
{
|
|
7684
|
+
fromWorkers: currentWorkers,
|
|
7685
|
+
toWorkers: nextWorkers2,
|
|
7686
|
+
error: failure.message
|
|
7687
|
+
}
|
|
7688
|
+
);
|
|
7689
|
+
currentWorkers = nextWorkers2;
|
|
7690
|
+
missingRanges = remaining;
|
|
7691
|
+
attempt++;
|
|
7692
|
+
continue;
|
|
7693
|
+
}
|
|
7654
7694
|
if (options.allowRetry && isTransientCaptureRetryEligible(failure.kind, remaining, transientRetriesUsed)) {
|
|
7655
7695
|
transientRetriesUsed++;
|
|
7656
7696
|
options.log.warn(
|
|
@@ -7668,22 +7708,6 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
7668
7708
|
pendingTransientRetry = true;
|
|
7669
7709
|
continue;
|
|
7670
7710
|
}
|
|
7671
|
-
if (options.allowRetry && !madeProgress && initializationRetriesUsed === 0 && failure.kind === "protocol_timeout" && /\bNetwork\.enable timed out/i.test(failure.message)) {
|
|
7672
|
-
initializationRetriesUsed++;
|
|
7673
|
-
const nextWorkers2 = getNextRetryWorkerCount(currentWorkers);
|
|
7674
|
-
options.log.warn(
|
|
7675
|
-
"[Render] Browser initialization timed out; retrying once with fresh sessions.",
|
|
7676
|
-
{
|
|
7677
|
-
fromWorkers: currentWorkers,
|
|
7678
|
-
toWorkers: nextWorkers2,
|
|
7679
|
-
error: failure.message
|
|
7680
|
-
}
|
|
7681
|
-
);
|
|
7682
|
-
currentWorkers = nextWorkers2;
|
|
7683
|
-
missingRanges = remaining;
|
|
7684
|
-
attempt++;
|
|
7685
|
-
continue;
|
|
7686
|
-
}
|
|
7687
7711
|
if (!madeProgress) {
|
|
7688
7712
|
options.log.warn(
|
|
7689
7713
|
"[Render] Capture attempt made no forward progress; composition is likely structurally broken, not retrying.",
|
|
@@ -7764,7 +7788,7 @@ import {
|
|
|
7764
7788
|
// src/services/distributed/shared.ts
|
|
7765
7789
|
import { execFile as execFileCallback } from "node:child_process";
|
|
7766
7790
|
import { dirname as dirname10, join as join16 } from "node:path";
|
|
7767
|
-
import { existsSync as existsSync12, readFileSync as
|
|
7791
|
+
import { existsSync as existsSync12, readFileSync as readFileSync6 } from "node:fs";
|
|
7768
7792
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7769
7793
|
import { promisify as promisify2 } from "node:util";
|
|
7770
7794
|
import {
|
|
@@ -8043,7 +8067,7 @@ function readProducerVersion() {
|
|
|
8043
8067
|
const candidate = join16(current, "package.json");
|
|
8044
8068
|
if (existsSync12(candidate)) {
|
|
8045
8069
|
try {
|
|
8046
|
-
const pkg = JSON.parse(
|
|
8070
|
+
const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
|
|
8047
8071
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
8048
8072
|
cachedProducerVersion = pkg.version;
|
|
8049
8073
|
return pkg.version;
|
|
@@ -8429,7 +8453,7 @@ async function readPlanReferenceBackground(input) {
|
|
|
8429
8453
|
}
|
|
8430
8454
|
|
|
8431
8455
|
// src/services/render/stages/freezePlan.ts
|
|
8432
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as
|
|
8456
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8433
8457
|
import { join as join20, relative as relative3, resolve as resolve8 } from "node:path";
|
|
8434
8458
|
import {
|
|
8435
8459
|
REFERENCE_BACKGROUND_FIELD,
|
|
@@ -8669,7 +8693,7 @@ function collectPlanAssetShas(planDir, digests) {
|
|
|
8669
8693
|
const assets = [];
|
|
8670
8694
|
for (const file of files) {
|
|
8671
8695
|
if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
|
|
8672
|
-
compositionHtml =
|
|
8696
|
+
compositionHtml = readFileSync7(file.absolutePath);
|
|
8673
8697
|
continue;
|
|
8674
8698
|
}
|
|
8675
8699
|
if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
|
|
@@ -8693,7 +8717,7 @@ function writePlanRemoteFiles(planDir, files) {
|
|
|
8693
8717
|
}
|
|
8694
8718
|
function readPlanRemoteFiles(planDir) {
|
|
8695
8719
|
const path = join20(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
8696
|
-
return existsSync14(path) ? JSON.parse(
|
|
8720
|
+
return existsSync14(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
|
|
8697
8721
|
}
|
|
8698
8722
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
8699
8723
|
const planJsonPath = join20(planDir, "plan.json");
|
|
@@ -8704,8 +8728,8 @@ function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
|
8704
8728
|
if (!existsSync14(encoderJsonPath)) {
|
|
8705
8729
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
8706
8730
|
}
|
|
8707
|
-
const planJson = JSON.parse(
|
|
8708
|
-
const encoderConfigCanonicalJson =
|
|
8731
|
+
const planJson = JSON.parse(readFileSync7(planJsonPath, "utf-8"));
|
|
8732
|
+
const encoderConfigCanonicalJson = readFileSync7(encoderJsonPath, "utf-8");
|
|
8709
8733
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
8710
8734
|
return computePlanHash({
|
|
8711
8735
|
compositionHtml,
|
|
@@ -8812,7 +8836,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
8812
8836
|
}
|
|
8813
8837
|
|
|
8814
8838
|
// src/services/distributed/chunkAudio.ts
|
|
8815
|
-
import { closeSync as closeSync4, existsSync as existsSync15, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as
|
|
8839
|
+
import { closeSync as closeSync4, existsSync as existsSync15, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
|
|
8816
8840
|
import { writeFileSync as writeFileSync6, writeSync } from "node:fs";
|
|
8817
8841
|
import { join as join22 } from "node:path";
|
|
8818
8842
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
@@ -9285,7 +9309,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
9285
9309
|
offset += track.sizes[offsets.length - 1];
|
|
9286
9310
|
}
|
|
9287
9311
|
});
|
|
9288
|
-
const bytes =
|
|
9312
|
+
const bytes = readFileSync8(input.audioPath);
|
|
9289
9313
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
9290
9314
|
mkdirSync9(join22(input.planDir, SLICE_DIR), { recursive: true });
|
|
9291
9315
|
input.chunks.forEach((_, index) => {
|
|
@@ -9304,8 +9328,8 @@ async function writeChunkAudioSlices(input) {
|
|
|
9304
9328
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
9305
9329
|
const layoutPath = join22(planDir, SLICE_LAYOUT);
|
|
9306
9330
|
if (!existsSync15(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
9307
|
-
const layout = JSON.parse(
|
|
9308
|
-
const frame =
|
|
9331
|
+
const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
|
|
9332
|
+
const frame = readFileSync8(join22(planDir, SLICE_FRAME));
|
|
9309
9333
|
const fd = openSync4(scratchPath, "w");
|
|
9310
9334
|
try {
|
|
9311
9335
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
@@ -9318,7 +9342,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
9318
9342
|
);
|
|
9319
9343
|
const start = layout.partStarts[chunkIndex];
|
|
9320
9344
|
if (start !== void 0) {
|
|
9321
|
-
const part =
|
|
9345
|
+
const part = readFileSync8(join22(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
9322
9346
|
writeSync(fd, part, 0, part.length, start);
|
|
9323
9347
|
}
|
|
9324
9348
|
ftruncateSync(fd, layout.sizeBytes);
|
|
@@ -10417,7 +10441,7 @@ import {
|
|
|
10417
10441
|
lstatSync as lstatSync2,
|
|
10418
10442
|
mkdirSync as mkdirSync12,
|
|
10419
10443
|
mkdtempSync as mkdtempSync4,
|
|
10420
|
-
readFileSync as
|
|
10444
|
+
readFileSync as readFileSync9,
|
|
10421
10445
|
readdirSync as readdirSync7,
|
|
10422
10446
|
rmSync as rmSync9,
|
|
10423
10447
|
statSync as statSync7,
|
|
@@ -10566,7 +10590,7 @@ function isNonNegativeIntegerArray(value) {
|
|
|
10566
10590
|
return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
|
|
10567
10591
|
}
|
|
10568
10592
|
function readJsonFile(path, label) {
|
|
10569
|
-
const contents =
|
|
10593
|
+
const contents = readFileSync9(path, "utf-8");
|
|
10570
10594
|
try {
|
|
10571
10595
|
const parsed = JSON.parse(contents);
|
|
10572
10596
|
return parsed;
|
|
@@ -11310,7 +11334,7 @@ import {
|
|
|
11310
11334
|
existsSync as existsSync19,
|
|
11311
11335
|
mkdirSync as mkdirSync13,
|
|
11312
11336
|
mkdtempSync as mkdtempSync5,
|
|
11313
|
-
readFileSync as
|
|
11337
|
+
readFileSync as readFileSync10,
|
|
11314
11338
|
readdirSync as readdirSync8,
|
|
11315
11339
|
rmSync as rmSync10,
|
|
11316
11340
|
statSync as statSync8,
|
|
@@ -11419,13 +11443,13 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
11419
11443
|
if (!existsSync19(planJsonPath)) {
|
|
11420
11444
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
11421
11445
|
}
|
|
11422
|
-
const plan = JSON.parse(
|
|
11446
|
+
const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
|
|
11423
11447
|
readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
11424
11448
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
11425
11449
|
if (!existsSync19(chunksJsonPath)) {
|
|
11426
11450
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
11427
11451
|
}
|
|
11428
|
-
const chunks = JSON.parse(
|
|
11452
|
+
const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
|
|
11429
11453
|
if (chunkPaths.length !== chunks.length) {
|
|
11430
11454
|
throw new Error(
|
|
11431
11455
|
`[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
|
|
@@ -11571,13 +11595,13 @@ function stitchesSeparateAudio(planDir) {
|
|
|
11571
11595
|
return lockedAudio(planDir).separateAudio === true;
|
|
11572
11596
|
}
|
|
11573
11597
|
function planFormat(planDir) {
|
|
11574
|
-
const plan = JSON.parse(
|
|
11598
|
+
const plan = JSON.parse(readFileSync10(join28(planDir, "plan.json"), "utf-8"));
|
|
11575
11599
|
return plan.dimensions.format;
|
|
11576
11600
|
}
|
|
11577
11601
|
function lockedAudio(planDir) {
|
|
11578
11602
|
const encoderJsonPath = join28(planDir, "meta", "encoder.json");
|
|
11579
11603
|
if (!existsSync19(encoderJsonPath)) return {};
|
|
11580
|
-
return JSON.parse(
|
|
11604
|
+
return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
11581
11605
|
}
|
|
11582
11606
|
function readCfrEncoder(planDir, format) {
|
|
11583
11607
|
if (format !== "mp4") {
|
|
@@ -11589,7 +11613,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
11589
11613
|
if (!existsSync19(encoderJsonPath)) {
|
|
11590
11614
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
11591
11615
|
}
|
|
11592
|
-
const encoderJson = JSON.parse(
|
|
11616
|
+
const encoderJson = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
11593
11617
|
if (encoderJson.encoder === "libx265-software") {
|
|
11594
11618
|
throw new Error(
|
|
11595
11619
|
`[assemble] cfr=true is not yet supported with codec: "h265". The cfr re-encode pass uses libx264 and would silently transcode the h265 chunks. Either disable cfr or render with codec: "h264".`
|
|
@@ -11643,7 +11667,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11643
11667
|
|
|
11644
11668
|
// src/services/distributed/renderChunk.ts
|
|
11645
11669
|
import { randomBytes } from "node:crypto";
|
|
11646
|
-
import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as
|
|
11670
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync11 } from "node:fs";
|
|
11647
11671
|
import { cpus } from "node:os";
|
|
11648
11672
|
import { join as join29 } from "node:path";
|
|
11649
11673
|
import {
|
|
@@ -11766,10 +11790,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
|
|
|
11766
11790
|
return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
|
|
11767
11791
|
}
|
|
11768
11792
|
function hashChunkOutput(outputPath, kind) {
|
|
11769
|
-
if (kind === "file") return sha256Hex(
|
|
11793
|
+
if (kind === "file") return sha256Hex(readFileSync11(outputPath));
|
|
11770
11794
|
const entries = readdirSync9(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
11771
11795
|
const lines = entries.map(
|
|
11772
|
-
(name) => `${name}\0${sha256Hex(
|
|
11796
|
+
(name) => `${name}\0${sha256Hex(readFileSync11(join29(outputPath, name)))}`
|
|
11773
11797
|
);
|
|
11774
11798
|
return sha256Hex(lines.join("\0"));
|
|
11775
11799
|
}
|
|
@@ -11795,7 +11819,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11795
11819
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
11796
11820
|
);
|
|
11797
11821
|
}
|
|
11798
|
-
const plan = JSON.parse(
|
|
11822
|
+
const plan = JSON.parse(readFileSync11(planJsonPath, "utf-8"));
|
|
11799
11823
|
readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
|
|
11800
11824
|
const planHashStarted = Date.now();
|
|
11801
11825
|
const v2Materialization = readValidatedPlanV2Materialization(planDir, {
|
|
@@ -11812,13 +11836,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11812
11836
|
);
|
|
11813
11837
|
}
|
|
11814
11838
|
}
|
|
11815
|
-
const encoder = JSON.parse(
|
|
11816
|
-
const chunks = JSON.parse(
|
|
11839
|
+
const encoder = JSON.parse(readFileSync11(encoderJsonPath, "utf-8"));
|
|
11840
|
+
const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
|
|
11817
11841
|
const videosJsonPath = join29(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
11818
11842
|
let planVideos = null;
|
|
11819
11843
|
if (existsSync20(videosJsonPath)) {
|
|
11820
11844
|
try {
|
|
11821
|
-
planVideos = validatePlanVideosForChunk(JSON.parse(
|
|
11845
|
+
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync11(videosJsonPath, "utf-8")));
|
|
11822
11846
|
} catch (err) {
|
|
11823
11847
|
if (err instanceof RenderChunkValidationError) throw err;
|
|
11824
11848
|
throw new RenderChunkValidationError(
|
|
@@ -11929,7 +11953,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11929
11953
|
dimensions: plan.dimensions,
|
|
11930
11954
|
encoder,
|
|
11931
11955
|
videoCount: planVideos?.videos.length ?? 0,
|
|
11932
|
-
html:
|
|
11956
|
+
html: readFileSync11(join29(compiledDir, "index.html"), "utf-8"),
|
|
11933
11957
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
11934
11958
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
11935
11959
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": "0.1.0",
|
|
3
3
|
"buildId": "dev",
|
|
4
|
-
"sha256": "
|
|
4
|
+
"sha256": "fabca5558bf6c2986cf22d51b64beb3bfe533a577864888bfecd242f66363a73",
|
|
5
5
|
"artifacts": {
|
|
6
6
|
"iife": "hyperframe.runtime.iife.js",
|
|
7
7
|
"esm": "hyperframe.runtime.mjs"
|