@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/index.js
CHANGED
|
@@ -1223,27 +1223,25 @@ function createMemorySampler(intervalMs = 250) {
|
|
|
1223
1223
|
};
|
|
1224
1224
|
}
|
|
1225
1225
|
var warnedSymlinkFallback = false;
|
|
1226
|
+
var copyCannotFix = (error) => ["ENOSPC", "EDQUOT", "EEXIST", "ENOENT"].includes(
|
|
1227
|
+
error?.code ?? ""
|
|
1228
|
+
);
|
|
1226
1229
|
function tryWindowsFrameJunction(fileSystem, src, dest) {
|
|
1227
1230
|
if (process.platform !== "win32") return false;
|
|
1228
1231
|
try {
|
|
1229
1232
|
fileSystem.symlinkSync(src, dest, "junction");
|
|
1230
1233
|
return true;
|
|
1231
1234
|
} catch (error) {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
code ?? ""
|
|
1235
|
-
)) {
|
|
1236
|
-
return false;
|
|
1237
|
-
}
|
|
1238
|
-
throw error;
|
|
1235
|
+
if (copyCannotFix(error)) throw error;
|
|
1236
|
+
return false;
|
|
1239
1237
|
}
|
|
1240
1238
|
}
|
|
1241
1239
|
function linkOrCopyFrameDir(fileSystem, src, dest) {
|
|
1242
1240
|
try {
|
|
1243
1241
|
fileSystem.symlinkSync(src, dest, "dir");
|
|
1244
1242
|
} catch (err) {
|
|
1243
|
+
if (copyCannotFix(err)) throw err;
|
|
1245
1244
|
const code = err?.code;
|
|
1246
|
-
if (code !== "EPERM" && code !== "EACCES" && code !== "UNKNOWN") throw err;
|
|
1247
1245
|
if (tryWindowsFrameJunction(fileSystem, src, dest)) return;
|
|
1248
1246
|
if (!warnedSymlinkFallback) {
|
|
1249
1247
|
warnedSymlinkFallback = true;
|
|
@@ -3743,6 +3741,7 @@ function createStallClock(now = () => Date.now()) {
|
|
|
3743
3741
|
|
|
3744
3742
|
// src/services/render/stages/captureStreamingStage.ts
|
|
3745
3743
|
var DEFAULT_CAPTURE_STALL_MS = 6e4;
|
|
3744
|
+
var SESSION_INIT_STALL_MS = 3e5;
|
|
3746
3745
|
var DE_STALL_POLL_MS = 5e3;
|
|
3747
3746
|
var STALL_ADVICE = "Chrome stopped answering. If another app is keeping the GPU or CPU busy (a 3D render, a game, another export), let it finish or render one job at a time, then retry.";
|
|
3748
3747
|
function poolFailure(error, probeSession) {
|
|
@@ -3773,6 +3772,14 @@ function captureModeLabel(mode) {
|
|
|
3773
3772
|
if (mode === "beginframe") return "BeginFrame";
|
|
3774
3773
|
return "screenshot";
|
|
3775
3774
|
}
|
|
3775
|
+
var OPENING_CAPTURE_SESSION = {
|
|
3776
|
+
captureMode: "screenshot",
|
|
3777
|
+
captureStep: "opening the capture session"
|
|
3778
|
+
};
|
|
3779
|
+
var OPENING_RISK_SESSION = {
|
|
3780
|
+
captureMode: "screenshot",
|
|
3781
|
+
captureStep: "opening the screenshot session for effect frames"
|
|
3782
|
+
};
|
|
3776
3783
|
var SequentialCaptureStallError = class extends Error {
|
|
3777
3784
|
captureMode;
|
|
3778
3785
|
constructor(session, stallTimeoutMs, frameIndex, totalFrames) {
|
|
@@ -3922,7 +3929,12 @@ async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEnc
|
|
|
3922
3929
|
const guard = createDrainFrameGuard({ log, stats, frameTime });
|
|
3923
3930
|
const guardFrame = (item, buf) => item.trusted ? Promise.resolve(buf) : guard(session, item.idx, buf);
|
|
3924
3931
|
const captureRisk = async (idx) => {
|
|
3925
|
-
await
|
|
3932
|
+
await raceAgainstStall(
|
|
3933
|
+
risk.open(),
|
|
3934
|
+
stallTimeoutMs - quiet.idle(0),
|
|
3935
|
+
{ session: OPENING_RISK_SESSION, frameIndex: idx, totalFrames, stallTimeoutMs },
|
|
3936
|
+
abortSignal
|
|
3937
|
+
);
|
|
3926
3938
|
quiet.reset();
|
|
3927
3939
|
return raceAgainstStall(
|
|
3928
3940
|
risk.capture(idx, frameTime(idx)),
|
|
@@ -4281,19 +4293,38 @@ async function runCaptureStreamingStage(input) {
|
|
|
4281
4293
|
let resumes = 0;
|
|
4282
4294
|
for (; ; ) {
|
|
4283
4295
|
const videoInjector = createRenderVideoFrameInjector();
|
|
4284
|
-
const
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4296
|
+
const initStallMs = Math.max(SESSION_INIT_STALL_MS, resolveCaptureStallTimeoutMs());
|
|
4297
|
+
const setup = createStallClock();
|
|
4298
|
+
const withinInitBudget = (work, stalled) => raceAgainstStall(
|
|
4299
|
+
work,
|
|
4300
|
+
initStallMs - setup.idle(0),
|
|
4301
|
+
{ session: stalled, frameIndex: resume.from, totalFrames, stallTimeoutMs: initStallMs },
|
|
4302
|
+
abortSignal
|
|
4290
4303
|
);
|
|
4304
|
+
const openSession = async () => {
|
|
4305
|
+
const opening = createCaptureSession(
|
|
4306
|
+
fileServer.url,
|
|
4307
|
+
framesDir,
|
|
4308
|
+
buildCaptureOptions(),
|
|
4309
|
+
videoInjector,
|
|
4310
|
+
captureCfg
|
|
4311
|
+
);
|
|
4312
|
+
try {
|
|
4313
|
+
return await withinInitBudget(opening, OPENING_CAPTURE_SESSION);
|
|
4314
|
+
} catch (error) {
|
|
4315
|
+
void opening.then(closeCaptureSession, () => {
|
|
4316
|
+
});
|
|
4317
|
+
throw error;
|
|
4318
|
+
}
|
|
4319
|
+
};
|
|
4320
|
+
const session = probeSession ?? await openSession();
|
|
4291
4321
|
captureBeyondViewport = session.options.captureBeyondViewport;
|
|
4292
4322
|
if (probeSession) {
|
|
4293
4323
|
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
4294
4324
|
probeSession = null;
|
|
4295
4325
|
}
|
|
4296
4326
|
let riskSession = null;
|
|
4327
|
+
let attemptClosed = false;
|
|
4297
4328
|
const openRiskSession = async () => {
|
|
4298
4329
|
const opened = await createCaptureSession(
|
|
4299
4330
|
fileServer.url,
|
|
@@ -4307,6 +4338,10 @@ async function runCaptureStreamingStage(input) {
|
|
|
4307
4338
|
enableBrowserPool: false
|
|
4308
4339
|
}
|
|
4309
4340
|
);
|
|
4341
|
+
if (attemptClosed) {
|
|
4342
|
+
await closeCaptureSession(opened);
|
|
4343
|
+
throw new Error("[Render] Capture attempt ended while its screenshot session opened");
|
|
4344
|
+
}
|
|
4310
4345
|
riskSession = opened;
|
|
4311
4346
|
await initializeSession(opened);
|
|
4312
4347
|
return opened;
|
|
@@ -4314,9 +4349,9 @@ async function runCaptureStreamingStage(input) {
|
|
|
4314
4349
|
let resumed = false;
|
|
4315
4350
|
try {
|
|
4316
4351
|
if (!session.isInitialized) {
|
|
4317
|
-
await initializeSession(session);
|
|
4352
|
+
await withinInitBudget(initializeSession(session), session);
|
|
4318
4353
|
}
|
|
4319
|
-
await completeDeferredDrawElementInit(session);
|
|
4354
|
+
await withinInitBudget(completeDeferredDrawElementInit(session), session);
|
|
4320
4355
|
assertNotAborted();
|
|
4321
4356
|
lastBrowserConsole = session.browserConsoleBuffer;
|
|
4322
4357
|
if (session.workerEncodeEnabled) {
|
|
@@ -4399,6 +4434,7 @@ async function runCaptureStreamingStage(input) {
|
|
|
4399
4434
|
resumes++;
|
|
4400
4435
|
resumed = true;
|
|
4401
4436
|
} finally {
|
|
4437
|
+
attemptClosed = true;
|
|
4402
4438
|
lastBrowserConsole = session.browserConsoleBuffer;
|
|
4403
4439
|
await closeCaptureSession(session);
|
|
4404
4440
|
if (riskSession) await closeCaptureSession(riskSession);
|
|
@@ -5865,7 +5901,7 @@ import { suggestMatchingPreset } from "@hyperframes/core";
|
|
|
5865
5901
|
// src/services/htmlCompiler.ts
|
|
5866
5902
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
5867
5903
|
import { createHash as createHash6 } from "node:crypto";
|
|
5868
|
-
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
|
|
5904
|
+
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7 } from "fs";
|
|
5869
5905
|
import { join as join15, dirname as dirname7, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
|
|
5870
5906
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
5871
5907
|
import {
|
|
@@ -7840,6 +7876,25 @@ ${fontFaceBlocks.join("\n")}
|
|
|
7840
7876
|
}
|
|
7841
7877
|
return result;
|
|
7842
7878
|
}
|
|
7879
|
+
function projectStylesheetFontFamilies(html, projectDir) {
|
|
7880
|
+
const families = [];
|
|
7881
|
+
for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
|
|
7882
|
+
const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
7883
|
+
if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
|
|
7884
|
+
const sheet = resolve7(projectDir, decodedUrlPath(href).replace(/^(?:\.?\/)+/, ""));
|
|
7885
|
+
if (!isPathInside2(sheet, projectDir) || !existsSync10(sheet)) continue;
|
|
7886
|
+
for (const face of extractFontFaceBlocks(readFileSync7(sheet, "utf8"))) {
|
|
7887
|
+
const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
7888
|
+
const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
|
|
7889
|
+
const path = decodedUrlPath(url);
|
|
7890
|
+
const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(dirname7(sheet), path);
|
|
7891
|
+
return isPathInside2(file, projectDir) && existsSync10(file);
|
|
7892
|
+
});
|
|
7893
|
+
if (family && shipsFile) families.push(family);
|
|
7894
|
+
}
|
|
7895
|
+
}
|
|
7896
|
+
return families;
|
|
7897
|
+
}
|
|
7843
7898
|
function collectFontFaceUrls(html) {
|
|
7844
7899
|
const styleBlockRe = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
|
|
7845
7900
|
const urlSet = /* @__PURE__ */ new Set();
|
|
@@ -7871,11 +7926,12 @@ function collectFontFaceUrls(html) {
|
|
|
7871
7926
|
return urlSet;
|
|
7872
7927
|
}
|
|
7873
7928
|
async function localizeRemoteFontFaces(html, downloadDir) {
|
|
7874
|
-
|
|
7875
|
-
|
|
7929
|
+
return localizeFontFaceUrls(await inlineExternalFontStylesheets(html), downloadDir);
|
|
7930
|
+
}
|
|
7931
|
+
function localizeFontFaceUrls(html, downloadDir) {
|
|
7876
7932
|
return downloadAndRewriteUrls(
|
|
7877
|
-
|
|
7878
|
-
|
|
7933
|
+
collectFontFaceUrls(html),
|
|
7934
|
+
html,
|
|
7879
7935
|
join15(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7880
7936
|
"Remote font download failed",
|
|
7881
7937
|
"Localized remote font face(s)",
|
|
@@ -8043,15 +8099,17 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
8043
8099
|
const hasShaderTransitions = detectShaderTransitionUsage(sanitizedHtml);
|
|
8044
8100
|
const usesThreeDTransforms = detectThreeDTransformUsage(sanitizedHtml);
|
|
8045
8101
|
const hasAncestorBackgroundImage = detectAncestorBackgroundImage(sanitizedHtml);
|
|
8102
|
+
const fontSheetHtml = await inlineExternalFontStylesheets(
|
|
8103
|
+
promoteCssImportsToLinkTags(sanitizedHtml)
|
|
8104
|
+
);
|
|
8046
8105
|
const normalizedFontHtml = normalizeSystemFontPrimaryFamilies(
|
|
8047
|
-
injectTextRenderingRule(
|
|
8048
|
-
coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml))
|
|
8049
|
-
)
|
|
8106
|
+
injectTextRenderingRule(coalesceHeadStylesAndBodyScripts(fontSheetHtml))
|
|
8050
8107
|
);
|
|
8051
8108
|
const coalescedHtml = await injectDeterministicFontFaces(normalizedFontHtml, {
|
|
8052
8109
|
logger: defaultLogger,
|
|
8053
8110
|
failClosedFontFetch: options.failClosedFontFetch === true,
|
|
8054
8111
|
allowSystemFontCapture: options.allowSystemFontCapture,
|
|
8112
|
+
projectFontFamilies: projectStylesheetFontFamilies(fontSheetHtml, projectDir),
|
|
8055
8113
|
abortSignal: options.abortSignal
|
|
8056
8114
|
});
|
|
8057
8115
|
const assembledHtml = await inlineExternalScripts(coalescedHtml);
|
|
@@ -8077,7 +8135,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
8077
8135
|
);
|
|
8078
8136
|
const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
|
|
8079
8137
|
const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
|
|
8080
|
-
const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await
|
|
8138
|
+
const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeFontFaceUrls(htmlWithLocalBg, downloadDir);
|
|
8081
8139
|
const {
|
|
8082
8140
|
html: htmlWithJpegs,
|
|
8083
8141
|
preparedAssets: preparedHeifAssets,
|
|
@@ -9528,17 +9586,24 @@ var VideoExtractionStageError = class extends Error {
|
|
|
9528
9586
|
publicMetadata;
|
|
9529
9587
|
};
|
|
9530
9588
|
function assertVideoExtractionSucceeded(result) {
|
|
9531
|
-
|
|
9589
|
+
throwIfHostFailure(result);
|
|
9532
9590
|
const error = buildVideoExtractionStageError(result);
|
|
9533
9591
|
if (error) throw error;
|
|
9534
9592
|
}
|
|
9535
|
-
function
|
|
9593
|
+
function throwIfHostFailure(result) {
|
|
9536
9594
|
const interrupted = result.errors.find((failure) => failure.kind === "external_interruption");
|
|
9537
|
-
if (
|
|
9538
|
-
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9595
|
+
if (interrupted) {
|
|
9596
|
+
throw encoderFailureError("Video frame extraction failed", {
|
|
9597
|
+
error: String(interrupted.error),
|
|
9598
|
+
failureReason: "external_interruption"
|
|
9599
|
+
});
|
|
9600
|
+
}
|
|
9601
|
+
const full = result.errors.filter((failure) => failure.kind === "disk_full").length;
|
|
9602
|
+
if (full === 0) return;
|
|
9603
|
+
throw Object.assign(
|
|
9604
|
+
new Error(`ENOSPC: no space left on device, extracting the frames of ${full} video source(s)`),
|
|
9605
|
+
{ code: "ENOSPC" }
|
|
9606
|
+
);
|
|
9542
9607
|
}
|
|
9543
9608
|
function buildVideoExtractionStageError(result) {
|
|
9544
9609
|
if (result.success && result.errors.length === 0) return null;
|
|
@@ -9753,7 +9818,7 @@ async function runExtractVideosStage(input) {
|
|
|
9753
9818
|
);
|
|
9754
9819
|
extractionResult.phaseBreakdown.transientRetries = (extractionResult.phaseBreakdown.transientRetries ?? 0) + hdrProbeTransientRetries;
|
|
9755
9820
|
assertNotAborted();
|
|
9756
|
-
|
|
9821
|
+
throwIfHostFailure(extractionResult);
|
|
9757
9822
|
failureToEnforce = applyVideoExtractionFailurePolicy(extractionResult, extractionPolicy, log);
|
|
9758
9823
|
materializeExtractedFramesForCompiledDir(extractionResult.extracted, compiledDir, {
|
|
9759
9824
|
materializeSymlinks
|
|
@@ -11945,7 +12010,7 @@ import {
|
|
|
11945
12010
|
existsSync as existsSync17,
|
|
11946
12011
|
mkdirSync as mkdirSync9,
|
|
11947
12012
|
readdirSync as readdirSync4,
|
|
11948
|
-
readFileSync as
|
|
12013
|
+
readFileSync as readFileSync8,
|
|
11949
12014
|
rmSync as rmSync9,
|
|
11950
12015
|
statSync as statSync7,
|
|
11951
12016
|
writeFileSync as writeFileSync5
|
|
@@ -12039,7 +12104,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
|
12039
12104
|
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
12040
12105
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
12041
12106
|
}
|
|
12042
|
-
const gif =
|
|
12107
|
+
const gif = readFileSync8(outputPath);
|
|
12043
12108
|
if (!clearGifFramesBeforeNext(gif)) {
|
|
12044
12109
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
12045
12110
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
@@ -12072,7 +12137,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
12072
12137
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
12073
12138
|
}
|
|
12074
12139
|
}
|
|
12075
|
-
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(
|
|
12140
|
+
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync8(outputPath)) !== false) {
|
|
12076
12141
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
12077
12142
|
if (failure) return failure;
|
|
12078
12143
|
}
|
|
@@ -12891,6 +12956,22 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
12891
12956
|
}
|
|
12892
12957
|
const remainingCount = countFrameRanges(remaining);
|
|
12893
12958
|
const madeProgress = captureAttemptMadeProgress(frameCount, remainingCount);
|
|
12959
|
+
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)) {
|
|
12960
|
+
initializationRetriesUsed++;
|
|
12961
|
+
const nextWorkers2 = getNextRetryWorkerCount(currentWorkers);
|
|
12962
|
+
options.log.warn(
|
|
12963
|
+
"[Render] Browser initialization timed out; retrying with fewer workers.",
|
|
12964
|
+
{
|
|
12965
|
+
fromWorkers: currentWorkers,
|
|
12966
|
+
toWorkers: nextWorkers2,
|
|
12967
|
+
error: failure.message
|
|
12968
|
+
}
|
|
12969
|
+
);
|
|
12970
|
+
currentWorkers = nextWorkers2;
|
|
12971
|
+
missingRanges = remaining;
|
|
12972
|
+
attempt++;
|
|
12973
|
+
continue;
|
|
12974
|
+
}
|
|
12894
12975
|
if (options.allowRetry && isTransientCaptureRetryEligible(failure.kind, remaining, transientRetriesUsed)) {
|
|
12895
12976
|
transientRetriesUsed++;
|
|
12896
12977
|
options.log.warn(
|
|
@@ -12908,22 +12989,6 @@ async function executeDiskCaptureWithAdaptiveRetry(options) {
|
|
|
12908
12989
|
pendingTransientRetry = true;
|
|
12909
12990
|
continue;
|
|
12910
12991
|
}
|
|
12911
|
-
if (options.allowRetry && !madeProgress && initializationRetriesUsed === 0 && failure.kind === "protocol_timeout" && /\bNetwork\.enable timed out/i.test(failure.message)) {
|
|
12912
|
-
initializationRetriesUsed++;
|
|
12913
|
-
const nextWorkers2 = getNextRetryWorkerCount(currentWorkers);
|
|
12914
|
-
options.log.warn(
|
|
12915
|
-
"[Render] Browser initialization timed out; retrying once with fresh sessions.",
|
|
12916
|
-
{
|
|
12917
|
-
fromWorkers: currentWorkers,
|
|
12918
|
-
toWorkers: nextWorkers2,
|
|
12919
|
-
error: failure.message
|
|
12920
|
-
}
|
|
12921
|
-
);
|
|
12922
|
-
currentWorkers = nextWorkers2;
|
|
12923
|
-
missingRanges = remaining;
|
|
12924
|
-
attempt++;
|
|
12925
|
-
continue;
|
|
12926
|
-
}
|
|
12927
12992
|
if (!madeProgress) {
|
|
12928
12993
|
options.log.warn(
|
|
12929
12994
|
"[Render] Capture attempt made no forward progress; composition is likely structurally broken, not retrying.",
|
|
@@ -15476,7 +15541,7 @@ import {
|
|
|
15476
15541
|
REFERENCE_BACKGROUND_FIELD as REFERENCE_BACKGROUND_FIELD4,
|
|
15477
15542
|
referenceBackgroundResult as referenceBackgroundResult6
|
|
15478
15543
|
} from "@hyperframes/engine";
|
|
15479
|
-
import { mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync7, readFileSync as
|
|
15544
|
+
import { mkdirSync as mkdirSync18, mkdtempSync as mkdtempSync7, readFileSync as readFileSync14, rmSync as rmSync16 } from "node:fs";
|
|
15480
15545
|
import { join as join42 } from "node:path";
|
|
15481
15546
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
15482
15547
|
|
|
@@ -15916,7 +15981,7 @@ import {
|
|
|
15916
15981
|
// src/services/distributed/shared.ts
|
|
15917
15982
|
import { execFile as execFileCallback } from "node:child_process";
|
|
15918
15983
|
import { dirname as dirname14, join as join30 } from "node:path";
|
|
15919
|
-
import { existsSync as existsSync19, readFileSync as
|
|
15984
|
+
import { existsSync as existsSync19, readFileSync as readFileSync9 } from "node:fs";
|
|
15920
15985
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
15921
15986
|
import { promisify as promisify2 } from "node:util";
|
|
15922
15987
|
import {
|
|
@@ -16195,7 +16260,7 @@ function readProducerVersion() {
|
|
|
16195
16260
|
const candidate = join30(current, "package.json");
|
|
16196
16261
|
if (existsSync19(candidate)) {
|
|
16197
16262
|
try {
|
|
16198
|
-
const pkg = JSON.parse(
|
|
16263
|
+
const pkg = JSON.parse(readFileSync9(candidate, "utf-8"));
|
|
16199
16264
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
16200
16265
|
cachedProducerVersion = pkg.version;
|
|
16201
16266
|
return pkg.version;
|
|
@@ -16581,7 +16646,7 @@ async function readPlanReferenceBackground(input) {
|
|
|
16581
16646
|
}
|
|
16582
16647
|
|
|
16583
16648
|
// src/services/render/stages/freezePlan.ts
|
|
16584
|
-
import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as
|
|
16649
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync12, readFileSync as readFileSync10, readdirSync as readdirSync7, writeFileSync as writeFileSync7 } from "node:fs";
|
|
16585
16650
|
import { join as join34, relative as relative3, resolve as resolve9 } from "node:path";
|
|
16586
16651
|
import {
|
|
16587
16652
|
REFERENCE_BACKGROUND_FIELD,
|
|
@@ -16821,7 +16886,7 @@ function collectPlanAssetShas(planDir, digests) {
|
|
|
16821
16886
|
const assets = [];
|
|
16822
16887
|
for (const file of files) {
|
|
16823
16888
|
if (file.planRelativePath === COMPILED_INDEX_RELATIVE_PATH) {
|
|
16824
|
-
compositionHtml =
|
|
16889
|
+
compositionHtml = readFileSync10(file.absolutePath);
|
|
16825
16890
|
continue;
|
|
16826
16891
|
}
|
|
16827
16892
|
if (HASH_EXCLUDED_PLAN_FILES.has(file.planRelativePath)) continue;
|
|
@@ -16845,7 +16910,7 @@ function writePlanRemoteFiles(planDir, files) {
|
|
|
16845
16910
|
}
|
|
16846
16911
|
function readPlanRemoteFiles(planDir) {
|
|
16847
16912
|
const path = join34(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
16848
|
-
return existsSync21(path) ? JSON.parse(
|
|
16913
|
+
return existsSync21(path) ? JSON.parse(readFileSync10(path, "utf-8")) : [];
|
|
16849
16914
|
}
|
|
16850
16915
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
16851
16916
|
const planJsonPath = join34(planDir, "plan.json");
|
|
@@ -16856,8 +16921,8 @@ function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
|
16856
16921
|
if (!existsSync21(encoderJsonPath)) {
|
|
16857
16922
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
16858
16923
|
}
|
|
16859
|
-
const planJson = JSON.parse(
|
|
16860
|
-
const encoderConfigCanonicalJson =
|
|
16924
|
+
const planJson = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
|
|
16925
|
+
const encoderConfigCanonicalJson = readFileSync10(encoderJsonPath, "utf-8");
|
|
16861
16926
|
const { compositionHtml, assets } = collectPlanAssetShas(planDir, digests);
|
|
16862
16927
|
return computePlanHash({
|
|
16863
16928
|
compositionHtml,
|
|
@@ -16964,7 +17029,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
16964
17029
|
}
|
|
16965
17030
|
|
|
16966
17031
|
// src/services/distributed/chunkAudio.ts
|
|
16967
|
-
import { closeSync as closeSync6, existsSync as existsSync22, ftruncateSync, mkdirSync as mkdirSync13, openSync as openSync6, readFileSync as
|
|
17032
|
+
import { closeSync as closeSync6, existsSync as existsSync22, ftruncateSync, mkdirSync as mkdirSync13, openSync as openSync6, readFileSync as readFileSync11 } from "node:fs";
|
|
16968
17033
|
import { writeFileSync as writeFileSync8, writeSync } from "node:fs";
|
|
16969
17034
|
import { join as join35 } from "node:path";
|
|
16970
17035
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
@@ -17052,7 +17117,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
17052
17117
|
offset += track.sizes[offsets.length - 1];
|
|
17053
17118
|
}
|
|
17054
17119
|
});
|
|
17055
|
-
const bytes =
|
|
17120
|
+
const bytes = readFileSync11(input.audioPath);
|
|
17056
17121
|
const layout = { sizeBytes: bytes.length, payloadStart, payloadEnd, partStarts: {} };
|
|
17057
17122
|
mkdirSync13(join35(input.planDir, SLICE_DIR), { recursive: true });
|
|
17058
17123
|
input.chunks.forEach((_, index) => {
|
|
@@ -17071,8 +17136,8 @@ async function writeChunkAudioSlices(input) {
|
|
|
17071
17136
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
17072
17137
|
const layoutPath = join35(planDir, SLICE_LAYOUT);
|
|
17073
17138
|
if (!existsSync22(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
17074
|
-
const layout = JSON.parse(
|
|
17075
|
-
const frame =
|
|
17139
|
+
const layout = JSON.parse(readFileSync11(layoutPath, "utf-8"));
|
|
17140
|
+
const frame = readFileSync11(join35(planDir, SLICE_FRAME));
|
|
17076
17141
|
const fd = openSync6(scratchPath, "w");
|
|
17077
17142
|
try {
|
|
17078
17143
|
writeSync(fd, frame, 0, layout.payloadStart, 0);
|
|
@@ -17085,7 +17150,7 @@ function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
|
17085
17150
|
);
|
|
17086
17151
|
const start = layout.partStarts[chunkIndex];
|
|
17087
17152
|
if (start !== void 0) {
|
|
17088
|
-
const part =
|
|
17153
|
+
const part = readFileSync11(join35(planDir, chunkAudioSlicePath(chunkIndex)));
|
|
17089
17154
|
writeSync(fd, part, 0, part.length, start);
|
|
17090
17155
|
}
|
|
17091
17156
|
ftruncateSync(fd, layout.sizeBytes);
|
|
@@ -18166,7 +18231,7 @@ async function plan(projectDir, config, planDir, options = {}) {
|
|
|
18166
18231
|
|
|
18167
18232
|
// src/services/distributed/renderChunk.ts
|
|
18168
18233
|
import { randomBytes } from "node:crypto";
|
|
18169
|
-
import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as
|
|
18234
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync17, readFileSync as readFileSync13, readdirSync as readdirSync10, rmSync as rmSync15, writeFileSync as writeFileSync12 } from "node:fs";
|
|
18170
18235
|
import { cpus as cpus3 } from "node:os";
|
|
18171
18236
|
import { join as join41 } from "node:path";
|
|
18172
18237
|
import {
|
|
@@ -18197,7 +18262,7 @@ import {
|
|
|
18197
18262
|
lstatSync as lstatSync3,
|
|
18198
18263
|
mkdirSync as mkdirSync16,
|
|
18199
18264
|
mkdtempSync as mkdtempSync6,
|
|
18200
|
-
readFileSync as
|
|
18265
|
+
readFileSync as readFileSync12,
|
|
18201
18266
|
readdirSync as readdirSync9,
|
|
18202
18267
|
rmSync as rmSync14,
|
|
18203
18268
|
statSync as statSync10,
|
|
@@ -18346,7 +18411,7 @@ function isNonNegativeIntegerArray(value) {
|
|
|
18346
18411
|
return Array.isArray(value) && value.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0);
|
|
18347
18412
|
}
|
|
18348
18413
|
function readJsonFile(path, label) {
|
|
18349
|
-
const contents =
|
|
18414
|
+
const contents = readFileSync12(path, "utf-8");
|
|
18350
18415
|
try {
|
|
18351
18416
|
const parsed = JSON.parse(contents);
|
|
18352
18417
|
return parsed;
|
|
@@ -19180,10 +19245,10 @@ async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBe
|
|
|
19180
19245
|
return !await probe(session.page, timeoutMs, probeTick, session.beginFrameIntervalMs);
|
|
19181
19246
|
}
|
|
19182
19247
|
function hashChunkOutput(outputPath, kind) {
|
|
19183
|
-
if (kind === "file") return sha256Hex(
|
|
19248
|
+
if (kind === "file") return sha256Hex(readFileSync13(outputPath));
|
|
19184
19249
|
const entries = readdirSync10(outputPath).filter((name) => /\.(png|jpg|jpeg)$/i.test(name)).sort();
|
|
19185
19250
|
const lines = entries.map(
|
|
19186
|
-
(name) => `${name}\0${sha256Hex(
|
|
19251
|
+
(name) => `${name}\0${sha256Hex(readFileSync13(join41(outputPath, name)))}`
|
|
19187
19252
|
);
|
|
19188
19253
|
return sha256Hex(lines.join("\0"));
|
|
19189
19254
|
}
|
|
@@ -19209,7 +19274,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19209
19274
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
19210
19275
|
);
|
|
19211
19276
|
}
|
|
19212
|
-
const plan2 = JSON.parse(
|
|
19277
|
+
const plan2 = JSON.parse(readFileSync13(planJsonPath, "utf-8"));
|
|
19213
19278
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.chunk);
|
|
19214
19279
|
const planHashStarted = Date.now();
|
|
19215
19280
|
const v2Materialization = readValidatedPlanV2Materialization(planDir, {
|
|
@@ -19226,13 +19291,13 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19226
19291
|
);
|
|
19227
19292
|
}
|
|
19228
19293
|
}
|
|
19229
|
-
const encoder = JSON.parse(
|
|
19230
|
-
const chunks = JSON.parse(
|
|
19294
|
+
const encoder = JSON.parse(readFileSync13(encoderJsonPath, "utf-8"));
|
|
19295
|
+
const chunks = JSON.parse(readFileSync13(chunksJsonPath, "utf-8"));
|
|
19231
19296
|
const videosJsonPath = join41(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
19232
19297
|
let planVideos = null;
|
|
19233
19298
|
if (existsSync26(videosJsonPath)) {
|
|
19234
19299
|
try {
|
|
19235
|
-
planVideos = validatePlanVideosForChunk(JSON.parse(
|
|
19300
|
+
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync13(videosJsonPath, "utf-8")));
|
|
19236
19301
|
} catch (err) {
|
|
19237
19302
|
if (err instanceof RenderChunkValidationError) throw err;
|
|
19238
19303
|
throw new RenderChunkValidationError(
|
|
@@ -19343,7 +19408,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
19343
19408
|
dimensions: plan2.dimensions,
|
|
19344
19409
|
encoder,
|
|
19345
19410
|
videoCount: planVideos?.videos.length ?? 0,
|
|
19346
|
-
html:
|
|
19411
|
+
html: readFileSync13(join41(compiledDir, "index.html"), "utf-8"),
|
|
19347
19412
|
// The plan ships a deferred source only to chunks that show it; don't wait on the rest.
|
|
19348
19413
|
skipReadinessVideoIds: deferredVideos.filter(
|
|
19349
19414
|
(video) => !shownFrames.get(video.videoId)?.size || remoteVideoIds.has(video.videoId)
|
|
@@ -19805,7 +19870,7 @@ async function renderSpan(projectDir, config, span, outputPath) {
|
|
|
19805
19870
|
planned.totalFrames
|
|
19806
19871
|
);
|
|
19807
19872
|
const slices = JSON.parse(
|
|
19808
|
-
|
|
19873
|
+
readFileSync14(join42(planDir, "meta", "chunks.json"), "utf-8")
|
|
19809
19874
|
);
|
|
19810
19875
|
const parts = [];
|
|
19811
19876
|
for (const slice of slices) {
|
|
@@ -20438,7 +20503,7 @@ import {
|
|
|
20438
20503
|
// src/services/hyperframeLint.ts
|
|
20439
20504
|
import {
|
|
20440
20505
|
existsSync as existsSync27,
|
|
20441
|
-
readFileSync as
|
|
20506
|
+
readFileSync as readFileSync15,
|
|
20442
20507
|
statSync as statSync11,
|
|
20443
20508
|
openSync as openSync7,
|
|
20444
20509
|
fstatSync as fstatSync3,
|
|
@@ -20484,7 +20549,7 @@ function readEntryHtml(filePath) {
|
|
|
20484
20549
|
}
|
|
20485
20550
|
try {
|
|
20486
20551
|
if (!fstatSync3(fd).isFile()) return null;
|
|
20487
|
-
return
|
|
20552
|
+
return readFileSync15(fd, "utf-8");
|
|
20488
20553
|
} finally {
|
|
20489
20554
|
closeSync7(fd);
|
|
20490
20555
|
}
|
|
@@ -21333,7 +21398,7 @@ import {
|
|
|
21333
21398
|
existsSync as existsSync30,
|
|
21334
21399
|
mkdirSync as mkdirSync20,
|
|
21335
21400
|
mkdtempSync as mkdtempSync9,
|
|
21336
|
-
readFileSync as
|
|
21401
|
+
readFileSync as readFileSync16,
|
|
21337
21402
|
readdirSync as readdirSync11,
|
|
21338
21403
|
rmSync as rmSync18,
|
|
21339
21404
|
statSync as statSync13,
|
|
@@ -21442,13 +21507,13 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
21442
21507
|
if (!existsSync30(planJsonPath)) {
|
|
21443
21508
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
21444
21509
|
}
|
|
21445
|
-
const plan2 = JSON.parse(
|
|
21510
|
+
const plan2 = JSON.parse(readFileSync16(planJsonPath, "utf-8"));
|
|
21446
21511
|
readPlanProtocolV1(plan2, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
21447
21512
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
21448
21513
|
if (!existsSync30(chunksJsonPath)) {
|
|
21449
21514
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
21450
21515
|
}
|
|
21451
|
-
const chunks = JSON.parse(
|
|
21516
|
+
const chunks = JSON.parse(readFileSync16(chunksJsonPath, "utf-8"));
|
|
21452
21517
|
if (chunkPaths.length !== chunks.length) {
|
|
21453
21518
|
throw new Error(
|
|
21454
21519
|
`[assemble] chunkPaths length (${chunkPaths.length}) does not match chunks.json length (${chunks.length}). Adapters must pass one path per chunk, ordered by index.`
|
|
@@ -21594,13 +21659,13 @@ function stitchesSeparateAudio(planDir) {
|
|
|
21594
21659
|
return lockedAudio(planDir).separateAudio === true;
|
|
21595
21660
|
}
|
|
21596
21661
|
function planFormat(planDir) {
|
|
21597
|
-
const plan2 = JSON.parse(
|
|
21662
|
+
const plan2 = JSON.parse(readFileSync16(join46(planDir, "plan.json"), "utf-8"));
|
|
21598
21663
|
return plan2.dimensions.format;
|
|
21599
21664
|
}
|
|
21600
21665
|
function lockedAudio(planDir) {
|
|
21601
21666
|
const encoderJsonPath = join46(planDir, "meta", "encoder.json");
|
|
21602
21667
|
if (!existsSync30(encoderJsonPath)) return {};
|
|
21603
|
-
return JSON.parse(
|
|
21668
|
+
return JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
|
|
21604
21669
|
}
|
|
21605
21670
|
function readCfrEncoder(planDir, format) {
|
|
21606
21671
|
if (format !== "mp4") {
|
|
@@ -21612,7 +21677,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
21612
21677
|
if (!existsSync30(encoderJsonPath)) {
|
|
21613
21678
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
21614
21679
|
}
|
|
21615
|
-
const encoderJson = JSON.parse(
|
|
21680
|
+
const encoderJson = JSON.parse(readFileSync16(encoderJsonPath, "utf-8"));
|
|
21616
21681
|
if (encoderJson.encoder === "libx265-software") {
|
|
21617
21682
|
throw new Error(
|
|
21618
21683
|
`[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".`
|