@hyperframes/producer 0.9.13 → 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/public-server.js
CHANGED
|
@@ -3754,6 +3754,7 @@ function createStallClock(now = () => Date.now()) {
|
|
|
3754
3754
|
|
|
3755
3755
|
// src/services/render/stages/captureStreamingStage.ts
|
|
3756
3756
|
var DEFAULT_CAPTURE_STALL_MS = 6e4;
|
|
3757
|
+
var SESSION_INIT_STALL_MS = 3e5;
|
|
3757
3758
|
var DE_STALL_POLL_MS = 5e3;
|
|
3758
3759
|
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.";
|
|
3759
3760
|
function poolFailure(error, probeSession) {
|
|
@@ -3784,6 +3785,14 @@ function captureModeLabel(mode) {
|
|
|
3784
3785
|
if (mode === "beginframe") return "BeginFrame";
|
|
3785
3786
|
return "screenshot";
|
|
3786
3787
|
}
|
|
3788
|
+
var OPENING_CAPTURE_SESSION = {
|
|
3789
|
+
captureMode: "screenshot",
|
|
3790
|
+
captureStep: "opening the capture session"
|
|
3791
|
+
};
|
|
3792
|
+
var OPENING_RISK_SESSION = {
|
|
3793
|
+
captureMode: "screenshot",
|
|
3794
|
+
captureStep: "opening the screenshot session for effect frames"
|
|
3795
|
+
};
|
|
3787
3796
|
var SequentialCaptureStallError = class extends Error {
|
|
3788
3797
|
captureMode;
|
|
3789
3798
|
constructor(session, stallTimeoutMs, frameIndex, totalFrames) {
|
|
@@ -3933,7 +3942,12 @@ async function runWorkerEncodePipelineLoop(session, totalFrames, job, currentEnc
|
|
|
3933
3942
|
const guard = createDrainFrameGuard({ log, stats, frameTime });
|
|
3934
3943
|
const guardFrame = (item, buf) => item.trusted ? Promise.resolve(buf) : guard(session, item.idx, buf);
|
|
3935
3944
|
const captureRisk = async (idx) => {
|
|
3936
|
-
await
|
|
3945
|
+
await raceAgainstStall(
|
|
3946
|
+
risk.open(),
|
|
3947
|
+
stallTimeoutMs - quiet.idle(0),
|
|
3948
|
+
{ session: OPENING_RISK_SESSION, frameIndex: idx, totalFrames, stallTimeoutMs },
|
|
3949
|
+
abortSignal
|
|
3950
|
+
);
|
|
3937
3951
|
quiet.reset();
|
|
3938
3952
|
return raceAgainstStall(
|
|
3939
3953
|
risk.capture(idx, frameTime(idx)),
|
|
@@ -4292,19 +4306,38 @@ async function runCaptureStreamingStage(input) {
|
|
|
4292
4306
|
let resumes = 0;
|
|
4293
4307
|
for (; ; ) {
|
|
4294
4308
|
const videoInjector = createRenderVideoFrameInjector();
|
|
4295
|
-
const
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4309
|
+
const initStallMs = Math.max(SESSION_INIT_STALL_MS, resolveCaptureStallTimeoutMs());
|
|
4310
|
+
const setup = createStallClock();
|
|
4311
|
+
const withinInitBudget = (work, stalled) => raceAgainstStall(
|
|
4312
|
+
work,
|
|
4313
|
+
initStallMs - setup.idle(0),
|
|
4314
|
+
{ session: stalled, frameIndex: resume.from, totalFrames, stallTimeoutMs: initStallMs },
|
|
4315
|
+
abortSignal
|
|
4301
4316
|
);
|
|
4317
|
+
const openSession = async () => {
|
|
4318
|
+
const opening = createCaptureSession(
|
|
4319
|
+
fileServer.url,
|
|
4320
|
+
framesDir,
|
|
4321
|
+
buildCaptureOptions(),
|
|
4322
|
+
videoInjector,
|
|
4323
|
+
captureCfg
|
|
4324
|
+
);
|
|
4325
|
+
try {
|
|
4326
|
+
return await withinInitBudget(opening, OPENING_CAPTURE_SESSION);
|
|
4327
|
+
} catch (error) {
|
|
4328
|
+
void opening.then(closeCaptureSession, () => {
|
|
4329
|
+
});
|
|
4330
|
+
throw error;
|
|
4331
|
+
}
|
|
4332
|
+
};
|
|
4333
|
+
const session = probeSession ?? await openSession();
|
|
4302
4334
|
captureBeyondViewport = session.options.captureBeyondViewport;
|
|
4303
4335
|
if (probeSession) {
|
|
4304
4336
|
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
4305
4337
|
probeSession = null;
|
|
4306
4338
|
}
|
|
4307
4339
|
let riskSession = null;
|
|
4340
|
+
let attemptClosed = false;
|
|
4308
4341
|
const openRiskSession = async () => {
|
|
4309
4342
|
const opened = await createCaptureSession(
|
|
4310
4343
|
fileServer.url,
|
|
@@ -4318,6 +4351,10 @@ async function runCaptureStreamingStage(input) {
|
|
|
4318
4351
|
enableBrowserPool: false
|
|
4319
4352
|
}
|
|
4320
4353
|
);
|
|
4354
|
+
if (attemptClosed) {
|
|
4355
|
+
await closeCaptureSession(opened);
|
|
4356
|
+
throw new Error("[Render] Capture attempt ended while its screenshot session opened");
|
|
4357
|
+
}
|
|
4321
4358
|
riskSession = opened;
|
|
4322
4359
|
await initializeSession(opened);
|
|
4323
4360
|
return opened;
|
|
@@ -4325,9 +4362,9 @@ async function runCaptureStreamingStage(input) {
|
|
|
4325
4362
|
let resumed = false;
|
|
4326
4363
|
try {
|
|
4327
4364
|
if (!session.isInitialized) {
|
|
4328
|
-
await initializeSession(session);
|
|
4365
|
+
await withinInitBudget(initializeSession(session), session);
|
|
4329
4366
|
}
|
|
4330
|
-
await completeDeferredDrawElementInit(session);
|
|
4367
|
+
await withinInitBudget(completeDeferredDrawElementInit(session), session);
|
|
4331
4368
|
assertNotAborted();
|
|
4332
4369
|
lastBrowserConsole = session.browserConsoleBuffer;
|
|
4333
4370
|
if (session.workerEncodeEnabled) {
|
|
@@ -4410,6 +4447,7 @@ async function runCaptureStreamingStage(input) {
|
|
|
4410
4447
|
resumes++;
|
|
4411
4448
|
resumed = true;
|
|
4412
4449
|
} finally {
|
|
4450
|
+
attemptClosed = true;
|
|
4413
4451
|
lastBrowserConsole = session.browserConsoleBuffer;
|
|
4414
4452
|
await closeCaptureSession(session);
|
|
4415
4453
|
if (riskSession) await closeCaptureSession(riskSession);
|
|
@@ -5876,7 +5914,7 @@ import { suggestMatchingPreset } from "@hyperframes/core";
|
|
|
5876
5914
|
// src/services/htmlCompiler.ts
|
|
5877
5915
|
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
5878
5916
|
import { createHash as createHash6 } from "node:crypto";
|
|
5879
|
-
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7 } from "fs";
|
|
5917
|
+
import { createReadStream as createReadStream2, existsSync as existsSync10, mkdirSync as mkdirSync7, readFileSync as readFileSync7 } from "fs";
|
|
5880
5918
|
import { join as join15, dirname as dirname7, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
|
|
5881
5919
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
5882
5920
|
import {
|
|
@@ -7851,6 +7889,25 @@ ${fontFaceBlocks.join("\n")}
|
|
|
7851
7889
|
}
|
|
7852
7890
|
return result;
|
|
7853
7891
|
}
|
|
7892
|
+
function projectStylesheetFontFamilies(html, projectDir) {
|
|
7893
|
+
const families = [];
|
|
7894
|
+
for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
|
|
7895
|
+
const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
7896
|
+
if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
|
|
7897
|
+
const sheet = resolve7(projectDir, decodedUrlPath(href).replace(/^(?:\.?\/)+/, ""));
|
|
7898
|
+
if (!isPathInside2(sheet, projectDir) || !existsSync10(sheet)) continue;
|
|
7899
|
+
for (const face of extractFontFaceBlocks(readFileSync7(sheet, "utf8"))) {
|
|
7900
|
+
const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
7901
|
+
const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
|
|
7902
|
+
const path = decodedUrlPath(url);
|
|
7903
|
+
const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(dirname7(sheet), path);
|
|
7904
|
+
return isPathInside2(file, projectDir) && existsSync10(file);
|
|
7905
|
+
});
|
|
7906
|
+
if (family && shipsFile) families.push(family);
|
|
7907
|
+
}
|
|
7908
|
+
}
|
|
7909
|
+
return families;
|
|
7910
|
+
}
|
|
7854
7911
|
function collectFontFaceUrls(html) {
|
|
7855
7912
|
const styleBlockRe = /<style\b[^>]*>([\s\S]*?)<\/style>/gi;
|
|
7856
7913
|
const urlSet = /* @__PURE__ */ new Set();
|
|
@@ -7881,12 +7938,10 @@ function collectFontFaceUrls(html) {
|
|
|
7881
7938
|
}
|
|
7882
7939
|
return urlSet;
|
|
7883
7940
|
}
|
|
7884
|
-
|
|
7885
|
-
const processed = await inlineExternalFontStylesheets(html);
|
|
7886
|
-
const urlSet = collectFontFaceUrls(processed);
|
|
7941
|
+
function localizeFontFaceUrls(html, downloadDir) {
|
|
7887
7942
|
return downloadAndRewriteUrls(
|
|
7888
|
-
|
|
7889
|
-
|
|
7943
|
+
collectFontFaceUrls(html),
|
|
7944
|
+
html,
|
|
7890
7945
|
join15(downloadDir, REMOTE_MEDIA_SUBDIR),
|
|
7891
7946
|
"Remote font download failed",
|
|
7892
7947
|
"Localized remote font face(s)",
|
|
@@ -8054,15 +8109,17 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
8054
8109
|
const hasShaderTransitions = detectShaderTransitionUsage(sanitizedHtml);
|
|
8055
8110
|
const usesThreeDTransforms = detectThreeDTransformUsage(sanitizedHtml);
|
|
8056
8111
|
const hasAncestorBackgroundImage = detectAncestorBackgroundImage(sanitizedHtml);
|
|
8112
|
+
const fontSheetHtml = await inlineExternalFontStylesheets(
|
|
8113
|
+
promoteCssImportsToLinkTags(sanitizedHtml)
|
|
8114
|
+
);
|
|
8057
8115
|
const normalizedFontHtml = normalizeSystemFontPrimaryFamilies(
|
|
8058
|
-
injectTextRenderingRule(
|
|
8059
|
-
coalesceHeadStylesAndBodyScripts(promoteCssImportsToLinkTags(sanitizedHtml))
|
|
8060
|
-
)
|
|
8116
|
+
injectTextRenderingRule(coalesceHeadStylesAndBodyScripts(fontSheetHtml))
|
|
8061
8117
|
);
|
|
8062
8118
|
const coalescedHtml = await injectDeterministicFontFaces(normalizedFontHtml, {
|
|
8063
8119
|
logger: defaultLogger,
|
|
8064
8120
|
failClosedFontFetch: options.failClosedFontFetch === true,
|
|
8065
8121
|
allowSystemFontCapture: options.allowSystemFontCapture,
|
|
8122
|
+
projectFontFamilies: projectStylesheetFontFamilies(fontSheetHtml, projectDir),
|
|
8066
8123
|
abortSignal: options.abortSignal
|
|
8067
8124
|
});
|
|
8068
8125
|
const assembledHtml = await inlineExternalScripts(coalescedHtml);
|
|
@@ -8088,7 +8145,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir, options = {})
|
|
|
8088
8145
|
);
|
|
8089
8146
|
const { html: htmlWithLocalImages, remoteMediaAssets: remoteImageAssets } = await localizeRemoteImageSources(htmlWithLocalMedia, downloadDir);
|
|
8090
8147
|
const { html: htmlWithLocalBg, remoteMediaAssets: remoteBgAssets } = await localizeRemoteBackgroundImages(htmlWithLocalImages, downloadDir);
|
|
8091
|
-
const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await
|
|
8148
|
+
const { html: htmlWithLocalizedFonts, remoteMediaAssets: remoteFontAssets } = await localizeFontFaceUrls(htmlWithLocalBg, downloadDir);
|
|
8092
8149
|
const {
|
|
8093
8150
|
html: htmlWithJpegs,
|
|
8094
8151
|
preparedAssets: preparedHeifAssets,
|
|
@@ -11926,7 +11983,7 @@ import {
|
|
|
11926
11983
|
existsSync as existsSync17,
|
|
11927
11984
|
mkdirSync as mkdirSync9,
|
|
11928
11985
|
readdirSync as readdirSync4,
|
|
11929
|
-
readFileSync as
|
|
11986
|
+
readFileSync as readFileSync8,
|
|
11930
11987
|
rmSync as rmSync9,
|
|
11931
11988
|
statSync as statSync7,
|
|
11932
11989
|
writeFileSync as writeFileSync5
|
|
@@ -12020,7 +12077,7 @@ async function reencodeWithClearedWholeFrames(argsInput, run, startTime) {
|
|
|
12020
12077
|
const error = formatFfmpegError(result.exitCode, result.stderr);
|
|
12021
12078
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
12022
12079
|
}
|
|
12023
|
-
const gif =
|
|
12080
|
+
const gif = readFileSync8(outputPath);
|
|
12024
12081
|
if (!clearGifFramesBeforeNext(gif)) {
|
|
12025
12082
|
const error = "[FFmpeg] GIF output could not be parsed to clear its frames";
|
|
12026
12083
|
return gifEncodeFailure(startTime, outputPath, error);
|
|
@@ -12053,7 +12110,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
12053
12110
|
return gifEncodeFailure(startTime, outputPath, error, result.failureReason);
|
|
12054
12111
|
}
|
|
12055
12112
|
}
|
|
12056
|
-
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(
|
|
12113
|
+
if (input.preserveAlpha && gifClearsAfterLeavingFrameInPlace(readFileSync8(outputPath)) !== false) {
|
|
12057
12114
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
12058
12115
|
if (failure) return failure;
|
|
12059
12116
|
}
|
|
@@ -15455,7 +15512,7 @@ async function executeRenderPipeline(input) {
|
|
|
15455
15512
|
// src/services/hyperframeLint.ts
|
|
15456
15513
|
import {
|
|
15457
15514
|
existsSync as existsSync19,
|
|
15458
|
-
readFileSync as
|
|
15515
|
+
readFileSync as readFileSync9,
|
|
15459
15516
|
statSync as statSync9,
|
|
15460
15517
|
openSync as openSync5,
|
|
15461
15518
|
fstatSync as fstatSync3,
|
|
@@ -15501,7 +15558,7 @@ function readEntryHtml(filePath) {
|
|
|
15501
15558
|
}
|
|
15502
15559
|
try {
|
|
15503
15560
|
if (!fstatSync3(fd).isFile()) return null;
|
|
15504
|
-
return
|
|
15561
|
+
return readFileSync9(fd, "utf-8");
|
|
15505
15562
|
} finally {
|
|
15506
15563
|
closeSync5(fd);
|
|
15507
15564
|
}
|
|
@@ -44,7 +44,10 @@ import type { ProducerLogger } from "../../../logger.js";
|
|
|
44
44
|
import type { ProgressCallback, RenderJob } from "../../renderOrchestrator.js";
|
|
45
45
|
import type { SdrStreamingCapturePlan } from "../capturePlan.js";
|
|
46
46
|
export declare function resolveCaptureStallTimeoutMs(): number;
|
|
47
|
-
type StalledSession =
|
|
47
|
+
type StalledSession = {
|
|
48
|
+
captureMode: CaptureSession["captureMode"];
|
|
49
|
+
captureStep?: string;
|
|
50
|
+
};
|
|
48
51
|
export declare function raceAgainstStall<T>(promise: Promise<T>, deadlineMs: number, input: {
|
|
49
52
|
session: StalledSession;
|
|
50
53
|
frameIndex: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperframes/producer",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.14",
|
|
4
4
|
"description": "HTML-to-video rendering engine using Chrome's BeginFrame API",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,11 +35,11 @@
|
|
|
35
35
|
"postcss": "^8.4.0",
|
|
36
36
|
"puppeteer": "~25.11.0",
|
|
37
37
|
"puppeteer-core": "^25.11.0",
|
|
38
|
-
"@hyperframes/
|
|
39
|
-
"@hyperframes/
|
|
40
|
-
"@hyperframes/engine": "0.9.
|
|
41
|
-
"@hyperframes/
|
|
42
|
-
"@hyperframes/
|
|
38
|
+
"@hyperframes/core": "0.9.14",
|
|
39
|
+
"@hyperframes/lint": "0.9.14",
|
|
40
|
+
"@hyperframes/engine": "0.9.14",
|
|
41
|
+
"@hyperframes/studio-server": "0.9.14",
|
|
42
|
+
"@hyperframes/parsers": "0.9.14"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/node": "^25.0.10",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"tsx": "^4.21.0",
|
|
49
49
|
"typescript": "^5.7.2",
|
|
50
50
|
"vitest": "^4.1.11",
|
|
51
|
-
"@hyperframes/aws-lambda": "0.9.
|
|
51
|
+
"@hyperframes/aws-lambda": "0.9.14"
|
|
52
52
|
},
|
|
53
53
|
"engines": {
|
|
54
54
|
"node": ">=22"
|