@hyperframes/producer 0.9.19 → 0.9.20
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 +235 -125
- package/dist/hyperframe.manifest.json +1 -1
- package/dist/hyperframe.runtime.iife.js +73 -73
- package/dist/index.js +259 -170
- package/dist/public-server.js +178 -100
- package/dist/services/render/stages/captureStreamingStage.d.ts +11 -2
- package/dist/services/renderOrchestrator.d.ts +3 -2
- package/dist/utils/paths.d.ts +2 -0
- package/package.json +7 -7
package/dist/distributed.js
CHANGED
|
@@ -8,13 +8,13 @@ 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 existsSync19,
|
|
12
12
|
linkSync,
|
|
13
13
|
mkdirSync as mkdirSync10,
|
|
14
14
|
rmSync as rmSync7,
|
|
15
15
|
writeFileSync as writeFileSync8
|
|
16
16
|
} from "node:fs";
|
|
17
|
-
import { extname as extname4, isAbsolute as isAbsolute3, join as join25, relative as relative5, sep as
|
|
17
|
+
import { extname as extname4, isAbsolute as isAbsolute3, join as join25, relative as relative5, sep as sep3 } from "node:path";
|
|
18
18
|
import {
|
|
19
19
|
fpsToNumber as fpsToNumber9,
|
|
20
20
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
@@ -71,7 +71,7 @@ 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 existsSync14,
|
|
75
75
|
mkdirSync as mkdirSync6,
|
|
76
76
|
readdirSync as readdirSync3,
|
|
77
77
|
rmSync as rmSync5,
|
|
@@ -887,7 +887,7 @@ function chunkGopFrames(framesInChunk, intervalFrames) {
|
|
|
887
887
|
import {
|
|
888
888
|
copyFileSync,
|
|
889
889
|
cpSync,
|
|
890
|
-
existsSync as
|
|
890
|
+
existsSync as existsSync4,
|
|
891
891
|
mkdirSync,
|
|
892
892
|
rmSync,
|
|
893
893
|
symlinkSync,
|
|
@@ -907,7 +907,9 @@ import {
|
|
|
907
907
|
relative as nodeRelative,
|
|
908
908
|
isAbsolute as nodeIsAbsolute
|
|
909
909
|
} from "node:path";
|
|
910
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
910
911
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
912
|
+
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
911
913
|
var DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? // fileURLToPath (not URL.pathname): on Windows .pathname is "/D:/..." which
|
|
912
914
|
// resolves to a bogus renders dir.
|
|
913
915
|
nodeResolve(fileURLToPath2(import.meta.url), "../../..", "renders");
|
|
@@ -938,6 +940,13 @@ function formatCaptureFrameName(index, ext) {
|
|
|
938
940
|
function formatExportFrameName(index, ext) {
|
|
939
941
|
return `frame_${String(index + 1).padStart(6, "0")}.${ext}`;
|
|
940
942
|
}
|
|
943
|
+
function resolveCompositionSrc(projectDir, src) {
|
|
944
|
+
const rooted = (value) => nodeResolve(projectDir, value.replace(/^(?:\.?[\\/])+/, ""));
|
|
945
|
+
const decoded = rooted(decodedUrlPath(src));
|
|
946
|
+
if (!isPathInside2(decoded, projectDir)) return null;
|
|
947
|
+
const raw = rooted(src);
|
|
948
|
+
return !existsSync3(decoded) && isPathInside2(raw, projectDir) && existsSync3(raw) ? raw : decoded;
|
|
949
|
+
}
|
|
941
950
|
|
|
942
951
|
// src/services/render/shared.ts
|
|
943
952
|
var BROWSER_MEDIA_EPSILON = 1e-4;
|
|
@@ -1116,7 +1125,7 @@ var materializePathModule = {
|
|
|
1116
1125
|
isAbsolute
|
|
1117
1126
|
};
|
|
1118
1127
|
var materializeFileSystem = {
|
|
1119
|
-
existsSync:
|
|
1128
|
+
existsSync: existsSync4,
|
|
1120
1129
|
mkdirSync,
|
|
1121
1130
|
symlinkSync,
|
|
1122
1131
|
cpSync,
|
|
@@ -1455,11 +1464,80 @@ function createStallClock(now = () => Date.now()) {
|
|
|
1455
1464
|
|
|
1456
1465
|
// src/services/render/stages/captureStreamingStage.ts
|
|
1457
1466
|
var DEFAULT_CAPTURE_STALL_MS = 6e4;
|
|
1467
|
+
var SESSION_INIT_STALL_MS = 3e5;
|
|
1468
|
+
var DE_STALL_POLL_MS = 5e3;
|
|
1469
|
+
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.";
|
|
1458
1470
|
function resolveCaptureStallTimeoutMs() {
|
|
1459
1471
|
const raw = process.env.HF_DE_STALL_MS ?? process.env.HF_DE_PARALLEL_STALL_MS;
|
|
1460
1472
|
const parsed = raw ? Number(raw) : Number.NaN;
|
|
1461
1473
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_CAPTURE_STALL_MS;
|
|
1462
1474
|
}
|
|
1475
|
+
function captureModeLabel(mode) {
|
|
1476
|
+
if (mode === "drawelement") return "drawElement";
|
|
1477
|
+
if (mode === "beginframe") return "BeginFrame";
|
|
1478
|
+
return "screenshot";
|
|
1479
|
+
}
|
|
1480
|
+
var SequentialCaptureStallError = class extends Error {
|
|
1481
|
+
captureMode;
|
|
1482
|
+
constructor(session, stallTimeoutMs, frameIndex, totalFrames) {
|
|
1483
|
+
const step = session.captureStep ? ` while ${session.captureStep}` : "";
|
|
1484
|
+
super(
|
|
1485
|
+
`[Render] Sequential ${captureModeLabel(session.captureMode)} capture stalled: no frame progress for ${stallTimeoutMs}ms (stuck at frame ${frameIndex}/${totalFrames}${step}). ` + STALL_ADVICE
|
|
1486
|
+
);
|
|
1487
|
+
this.name = "SequentialCaptureStallError";
|
|
1488
|
+
this.captureMode = session.captureMode;
|
|
1489
|
+
}
|
|
1490
|
+
};
|
|
1491
|
+
function raceAgainstStall(promise, deadlineMs, input, signal) {
|
|
1492
|
+
return new Promise((resolve10, reject) => {
|
|
1493
|
+
const clock = createStallClock();
|
|
1494
|
+
const trip = () => reject(
|
|
1495
|
+
signal?.aborted ? new Error(
|
|
1496
|
+
`[Render] Sequential ${captureModeLabel(input.session.captureMode)} capture aborted while a capture call was in flight (frame ${input.frameIndex}/${input.totalFrames}).`
|
|
1497
|
+
) : new SequentialCaptureStallError(
|
|
1498
|
+
input.session,
|
|
1499
|
+
input.stallTimeoutMs,
|
|
1500
|
+
input.frameIndex,
|
|
1501
|
+
input.totalFrames
|
|
1502
|
+
)
|
|
1503
|
+
);
|
|
1504
|
+
const deadline = Math.max(0, deadlineMs);
|
|
1505
|
+
let wait = Math.min(deadline, DE_STALL_POLL_MS);
|
|
1506
|
+
let timer;
|
|
1507
|
+
const look = () => {
|
|
1508
|
+
const idle = clock.idle(wait);
|
|
1509
|
+
if (idle >= deadline) return trip();
|
|
1510
|
+
wait = Math.min(deadline - idle, DE_STALL_POLL_MS);
|
|
1511
|
+
timer = setTimeout(look, wait);
|
|
1512
|
+
};
|
|
1513
|
+
timer = setTimeout(look, wait);
|
|
1514
|
+
promise.then(
|
|
1515
|
+
(value) => {
|
|
1516
|
+
clearTimeout(timer);
|
|
1517
|
+
resolve10(value);
|
|
1518
|
+
},
|
|
1519
|
+
(error) => {
|
|
1520
|
+
clearTimeout(timer);
|
|
1521
|
+
reject(error);
|
|
1522
|
+
}
|
|
1523
|
+
);
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
function createSessionInitBudget(input) {
|
|
1527
|
+
const initStallMs = Math.max(SESSION_INIT_STALL_MS, resolveCaptureStallTimeoutMs());
|
|
1528
|
+
const setup = createStallClock();
|
|
1529
|
+
return (work, stalled) => raceAgainstStall(
|
|
1530
|
+
work,
|
|
1531
|
+
initStallMs - setup.idle(0),
|
|
1532
|
+
{
|
|
1533
|
+
session: stalled,
|
|
1534
|
+
frameIndex: input.frameIndex,
|
|
1535
|
+
totalFrames: input.totalFrames,
|
|
1536
|
+
stallTimeoutMs: initStallMs
|
|
1537
|
+
},
|
|
1538
|
+
input.abortSignal
|
|
1539
|
+
);
|
|
1540
|
+
}
|
|
1463
1541
|
|
|
1464
1542
|
// src/services/render/captureCost.ts
|
|
1465
1543
|
import { fpsToNumber as fpsToNumber4 } from "@hyperframes/core";
|
|
@@ -1674,7 +1752,7 @@ var ALLOWED_STRING_DATA_KEYS = /* @__PURE__ */ new Set([
|
|
|
1674
1752
|
"stagePhase",
|
|
1675
1753
|
// Full-fidelity fast-capture fallback trigger — the specific reason
|
|
1676
1754
|
// drawElement gated off ("filter:blur", "filter:drop-shadow",
|
|
1677
|
-
// "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", …).
|
|
1755
|
+
// "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", "webgl_unavailable", …).
|
|
1678
1756
|
// Populated on the `capture_fallback_profile` checkpoint emitted by
|
|
1679
1757
|
// `fallbackCaptureProfile.ts` when the operator opts into
|
|
1680
1758
|
// `HF_PROFILE_FALLBACK_CAPTURE=true` so downstream metric consumers can
|
|
@@ -1993,17 +2071,17 @@ import {
|
|
|
1993
2071
|
} from "@hyperframes/engine";
|
|
1994
2072
|
|
|
1995
2073
|
// src/services/render/stages/compileStage.ts
|
|
1996
|
-
import { existsSync as
|
|
2074
|
+
import { existsSync as existsSync8, writeFileSync as writeFileSync3 } from "node:fs";
|
|
1997
2075
|
import { join as join8 } from "node:path";
|
|
1998
2076
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
1999
2077
|
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
2000
2078
|
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
2001
2079
|
|
|
2002
2080
|
// src/services/htmlCompiler.ts
|
|
2003
|
-
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
2081
|
+
import { decodedUrlPath as decodedUrlPath2 } from "@hyperframes/parsers/asset-paths";
|
|
2004
2082
|
import { createHash as createHash4 } from "node:crypto";
|
|
2005
|
-
import { createReadStream as createReadStream2, existsSync as
|
|
2006
|
-
import { join as join7, dirname as dirname6, resolve as resolve6, basename as basename3, relative as relative2 } from "path";
|
|
2083
|
+
import { createReadStream as createReadStream2, existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4 } from "fs";
|
|
2084
|
+
import { join as join7, dirname as dirname6, resolve as resolve6, basename as basename3, relative as relative2, sep as sep2 } from "path";
|
|
2007
2085
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
2008
2086
|
import {
|
|
2009
2087
|
COMPOSITION_SOURCE_URL,
|
|
@@ -2249,7 +2327,7 @@ import {
|
|
|
2249
2327
|
|
|
2250
2328
|
// src/services/animatedGifPrep.ts
|
|
2251
2329
|
import { createHash as createHash2 } from "node:crypto";
|
|
2252
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
2330
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync2, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
|
|
2253
2331
|
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join5, resolve as resolve4 } from "node:path";
|
|
2254
2332
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
2255
2333
|
import { parseAnimatedGifMetadata } from "@hyperframes/core";
|
|
@@ -2288,14 +2366,14 @@ function resolveLocalImageSourcePath(src, options) {
|
|
|
2288
2366
|
const { basePath } = splitUrlSuffix(trimmed);
|
|
2289
2367
|
const normalizedBase = normalizeRelPath(basePath);
|
|
2290
2368
|
const mapped = options.sourceAssets?.get(trimmed) ?? options.sourceAssets?.get(basePath) ?? options.sourceAssets?.get(normalizedBase);
|
|
2291
|
-
if (mapped &&
|
|
2369
|
+
if (mapped && existsSync5(mapped)) return mapped;
|
|
2292
2370
|
if (isHttpUrl(trimmed)) return null;
|
|
2293
2371
|
const projectRelative = basePath.startsWith("/") ? basePath.slice(1) : basePath;
|
|
2294
2372
|
const candidates = [
|
|
2295
2373
|
isAbsolute2(basePath) ? basePath : resolve4(options.projectDir, projectRelative),
|
|
2296
2374
|
resolve4(options.downloadDir, normalizedBase)
|
|
2297
2375
|
];
|
|
2298
|
-
return candidates.find((candidate) =>
|
|
2376
|
+
return candidates.find((candidate) => existsSync5(candidate)) ?? null;
|
|
2299
2377
|
}
|
|
2300
2378
|
function isUsableFile(path) {
|
|
2301
2379
|
try {
|
|
@@ -2406,10 +2484,10 @@ async function runAnimatedGifTranscode(request) {
|
|
|
2406
2484
|
throw result.error ?? new Error(`Animated GIF transcode failed (${result.exitCode}): ${result.stderr.slice(-500)}`);
|
|
2407
2485
|
}
|
|
2408
2486
|
async function ensurePreparedWebm(input) {
|
|
2409
|
-
if (!
|
|
2487
|
+
if (!existsSync5(dirname4(input.cachePath))) {
|
|
2410
2488
|
mkdirSync2(dirname4(input.cachePath), { recursive: true });
|
|
2411
2489
|
}
|
|
2412
|
-
if (!
|
|
2490
|
+
if (!existsSync5(dirname4(input.outputPath))) {
|
|
2413
2491
|
mkdirSync2(dirname4(input.outputPath), { recursive: true });
|
|
2414
2492
|
}
|
|
2415
2493
|
if (!isUsableFile(input.cachePath)) {
|
|
@@ -2884,7 +2962,7 @@ import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-serve
|
|
|
2884
2962
|
import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
|
|
2885
2963
|
|
|
2886
2964
|
// src/services/assetMediaType.ts
|
|
2887
|
-
import { existsSync as
|
|
2965
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
2888
2966
|
import {
|
|
2889
2967
|
fingerprintElementId,
|
|
2890
2968
|
isNotMediaPayload,
|
|
@@ -2957,7 +3035,7 @@ async function preflightCompositionAssetMediaTypes(input) {
|
|
|
2957
3035
|
input.projectDir,
|
|
2958
3036
|
input.compiledDir
|
|
2959
3037
|
);
|
|
2960
|
-
if (!
|
|
3038
|
+
if (!existsSync6(resolvedPath)) continue;
|
|
2961
3039
|
byPath.set(resolvedPath, [...byPath.get(resolvedPath) ?? [], reference]);
|
|
2962
3040
|
}
|
|
2963
3041
|
const mismatches = [];
|
|
@@ -3045,6 +3123,14 @@ Check that each file referenced by data-composition-src contains valid HTML with
|
|
|
3045
3123
|
this.problems = problems;
|
|
3046
3124
|
}
|
|
3047
3125
|
};
|
|
3126
|
+
function normalizeCompositionSrcs(html, projectDir) {
|
|
3127
|
+
return html.replace(/(\bdata-composition-src=)(["'])([^"']*)\2/g, (whole, name, quote, value) => {
|
|
3128
|
+
if (!value.includes("%") && !value.startsWith("/")) return whole;
|
|
3129
|
+
const file = resolveCompositionSrc(projectDir, value);
|
|
3130
|
+
if (file === null) return whole;
|
|
3131
|
+
return `${name}${quote}${relative2(projectDir, file).split(sep2).join("/")}${quote}`;
|
|
3132
|
+
});
|
|
3133
|
+
}
|
|
3048
3134
|
function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */ new Set()) {
|
|
3049
3135
|
const { document: document2 } = parseHTML5(html);
|
|
3050
3136
|
const hosts = [...document2.querySelectorAll("[data-composition-src]")];
|
|
@@ -3053,7 +3139,11 @@ function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */
|
|
|
3053
3139
|
const srcPath = el.getAttribute("data-composition-src");
|
|
3054
3140
|
if (!srcPath) continue;
|
|
3055
3141
|
if (isUnresolvedAssetPlaceholder(srcPath)) continue;
|
|
3056
|
-
const filePath =
|
|
3142
|
+
const filePath = resolveCompositionSrc(projectDir, srcPath);
|
|
3143
|
+
if (filePath === null) {
|
|
3144
|
+
problems.push({ srcPath, detail: "it is outside the project folder" });
|
|
3145
|
+
continue;
|
|
3146
|
+
}
|
|
3057
3147
|
if (visited.has(filePath)) continue;
|
|
3058
3148
|
const read = readProjectFile(filePath);
|
|
3059
3149
|
if (read.kind === "missing") {
|
|
@@ -3199,7 +3289,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
3199
3289
|
let filePath = src;
|
|
3200
3290
|
if (isHttpUrl(src)) {
|
|
3201
3291
|
const remoteDir = join7(downloadDir, REMOTE_MEDIA_SUBDIR);
|
|
3202
|
-
if (!
|
|
3292
|
+
if (!existsSync7(remoteDir)) mkdirSync4(remoteDir, { recursive: true });
|
|
3203
3293
|
try {
|
|
3204
3294
|
filePath = await downloadToTemp(src, remoteDir, void 0, void 0, void 0, {
|
|
3205
3295
|
onTelemetry: logRemoteDownloadTelemetry
|
|
@@ -3208,11 +3298,11 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
3208
3298
|
return { duration: null, resolvedPath: src };
|
|
3209
3299
|
}
|
|
3210
3300
|
} else {
|
|
3211
|
-
filePath =
|
|
3301
|
+
filePath = decodedUrlPath2(src);
|
|
3212
3302
|
if (!filePath.startsWith("/")) filePath = join7(baseDir, filePath);
|
|
3213
3303
|
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
3214
3304
|
}
|
|
3215
|
-
if (!isHttpUrl(filePath) && !
|
|
3305
|
+
if (!isHttpUrl(filePath) && !existsSync7(filePath)) {
|
|
3216
3306
|
return { duration: null, resolvedPath: filePath };
|
|
3217
3307
|
}
|
|
3218
3308
|
const withSrcContext = (error) => {
|
|
@@ -3353,7 +3443,8 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
3353
3443
|
for (const el of compEls) {
|
|
3354
3444
|
const srcPath = el.getAttribute("data-composition-src");
|
|
3355
3445
|
if (!srcPath) continue;
|
|
3356
|
-
const filePath =
|
|
3446
|
+
const filePath = resolveCompositionSrc(projectDir, srcPath);
|
|
3447
|
+
if (filePath === null) continue;
|
|
3357
3448
|
if (visited.has(filePath)) {
|
|
3358
3449
|
continue;
|
|
3359
3450
|
}
|
|
@@ -3361,7 +3452,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
3361
3452
|
if (read.kind !== "file") {
|
|
3362
3453
|
continue;
|
|
3363
3454
|
}
|
|
3364
|
-
const rawSubHtml = read.text;
|
|
3455
|
+
const rawSubHtml = normalizeCompositionSrcs(read.text, projectDir);
|
|
3365
3456
|
const nestedVisited = new Set(visited);
|
|
3366
3457
|
nestedVisited.add(filePath);
|
|
3367
3458
|
workItems.push({ srcPath, filePath, rawSubHtml, nestedVisited });
|
|
@@ -3549,8 +3640,9 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
|
|
|
3549
3640
|
resolveHtml: (srcPath) => {
|
|
3550
3641
|
let compHtml = subCompositions.get(srcPath) || null;
|
|
3551
3642
|
if (!compHtml) {
|
|
3552
|
-
const
|
|
3553
|
-
|
|
3643
|
+
const filePath = resolveCompositionSrc(projectDir, srcPath);
|
|
3644
|
+
const read = filePath ? readProjectFile(filePath) : null;
|
|
3645
|
+
if (read?.kind === "file") {
|
|
3554
3646
|
compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
|
|
3555
3647
|
}
|
|
3556
3648
|
}
|
|
@@ -3559,7 +3651,7 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
|
|
|
3559
3651
|
parseHtml: (htmlStr) => parseHTML5(htmlStr).document,
|
|
3560
3652
|
// Mirrors the preview bundler: a sub-composition's SIBLING assets resolve
|
|
3561
3653
|
// against its own directory, project-root refs stay as authored.
|
|
3562
|
-
assetExists: (path) =>
|
|
3654
|
+
assetExists: (path) => existsSync7(resolve6(projectDir, path)),
|
|
3563
3655
|
// Preserve the authored root wrapper as a child of the host, matching
|
|
3564
3656
|
// the preview bundler's shape (htmlBundler.ts's prepareFlattenedInnerRoot,
|
|
3565
3657
|
// which the runtime compositionLoader mirrors with its own copy for the
|
|
@@ -3750,7 +3842,7 @@ function collectExternalAssets(html, projectDir) {
|
|
|
3750
3842
|
if (isPathInside2(absPath, absProjectDir)) {
|
|
3751
3843
|
return null;
|
|
3752
3844
|
}
|
|
3753
|
-
if (!
|
|
3845
|
+
if (!existsSync7(absPath)) return null;
|
|
3754
3846
|
const safeKey = toExternalAssetKey(absPath);
|
|
3755
3847
|
externalAssets.set(safeKey, absPath);
|
|
3756
3848
|
return safeKey;
|
|
@@ -3800,7 +3892,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
|
|
|
3800
3892
|
var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
|
|
3801
3893
|
async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
|
|
3802
3894
|
if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
|
|
3803
|
-
if (!
|
|
3895
|
+
if (!existsSync7(remoteDir)) mkdirSync4(remoteDir, { recursive: true });
|
|
3804
3896
|
const urlToLocal = /* @__PURE__ */ new Map();
|
|
3805
3897
|
await Promise.all(
|
|
3806
3898
|
[...urlSet].map(async (url) => {
|
|
@@ -3982,14 +4074,14 @@ function projectStylesheetFontFamilies(html, projectDir) {
|
|
|
3982
4074
|
for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
|
|
3983
4075
|
const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
3984
4076
|
if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
|
|
3985
|
-
const sheet = resolve6(projectDir,
|
|
3986
|
-
if (!isPathInside2(sheet, projectDir) || !
|
|
4077
|
+
const sheet = resolve6(projectDir, decodedUrlPath2(href).replace(/^(?:\.?\/)+/, ""));
|
|
4078
|
+
if (!isPathInside2(sheet, projectDir) || !existsSync7(sheet)) continue;
|
|
3987
4079
|
for (const face of extractFontFaceBlocks(readFileSync4(sheet, "utf8"))) {
|
|
3988
4080
|
const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
3989
4081
|
const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
|
|
3990
|
-
const path =
|
|
4082
|
+
const path = decodedUrlPath2(url);
|
|
3991
4083
|
const file = path.startsWith("/") ? resolve6(projectDir, `.${path}`) : resolve6(dirname6(sheet), path);
|
|
3992
|
-
return isPathInside2(file, projectDir) &&
|
|
4084
|
+
return isPathInside2(file, projectDir) && existsSync7(file);
|
|
3993
4085
|
});
|
|
3994
4086
|
if (family && shipsFile) families.push(family);
|
|
3995
4087
|
}
|
|
@@ -4124,7 +4216,7 @@ function renderGsapDist() {
|
|
|
4124
4216
|
}
|
|
4125
4217
|
function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
|
|
4126
4218
|
let gsapDist;
|
|
4127
|
-
const exists = (path) =>
|
|
4219
|
+
const exists = (path) => existsSync7(resolve6(projectDir, path));
|
|
4128
4220
|
return html.replace(
|
|
4129
4221
|
/(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
|
|
4130
4222
|
(full, prefix, src, file, suffix) => {
|
|
@@ -4141,7 +4233,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
4141
4233
|
const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
|
|
4142
4234
|
if (!entryPath.includes("/")) return html;
|
|
4143
4235
|
const { document: document2 } = parseHTML5(html);
|
|
4144
|
-
const assetExists = (path) =>
|
|
4236
|
+
const assetExists = (path) => existsSync7(resolve6(projectDir, path));
|
|
4145
4237
|
rewriteAssetPaths(
|
|
4146
4238
|
document2.querySelectorAll("[src], [href], [srcset]"),
|
|
4147
4239
|
entryPath,
|
|
@@ -4163,7 +4255,10 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
4163
4255
|
}
|
|
4164
4256
|
async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
|
|
4165
4257
|
const entryHtml = rebaseDirectEntryAssetPaths(readTextFile(htmlPath), projectDir, htmlPath);
|
|
4166
|
-
const rawHtml =
|
|
4258
|
+
const rawHtml = normalizeCompositionSrcs(
|
|
4259
|
+
rewriteUnresolvableGsapToCdn(entryHtml, projectDir),
|
|
4260
|
+
projectDir
|
|
4261
|
+
);
|
|
4167
4262
|
assertSubCompositionsUsable(rawHtml, projectDir);
|
|
4168
4263
|
const probes = /* @__PURE__ */ new Map();
|
|
4169
4264
|
const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
|
|
@@ -4708,7 +4803,7 @@ function resolveEntryHtmlPath(input) {
|
|
|
4708
4803
|
const rawEntry = readTextFile2(htmlPath);
|
|
4709
4804
|
if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
|
|
4710
4805
|
const projectIndexPath = join8(projectDir, "index.html");
|
|
4711
|
-
if (!
|
|
4806
|
+
if (!existsSync8(projectIndexPath)) {
|
|
4712
4807
|
throw new Error(
|
|
4713
4808
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
4714
4809
|
);
|
|
@@ -5574,7 +5669,7 @@ function validateDistributedDuration(input) {
|
|
|
5574
5669
|
}
|
|
5575
5670
|
|
|
5576
5671
|
// src/services/render/stages/extractVideosStage.ts
|
|
5577
|
-
import { existsSync as
|
|
5672
|
+
import { existsSync as existsSync9 } from "node:fs";
|
|
5578
5673
|
import { join as join10 } from "node:path";
|
|
5579
5674
|
import {
|
|
5580
5675
|
classifyVideoExtractionError,
|
|
@@ -5849,7 +5944,7 @@ async function runExtractVideosStage(input) {
|
|
|
5849
5944
|
const probeFailures = await Promise.all(
|
|
5850
5945
|
composition.videos.map(async (v) => {
|
|
5851
5946
|
const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
|
|
5852
|
-
if (!
|
|
5947
|
+
if (!existsSync9(videoPath)) return null;
|
|
5853
5948
|
try {
|
|
5854
5949
|
const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry2(() => extractMediaMetadata3(videoPath), {
|
|
5855
5950
|
signal: abortSignal,
|
|
@@ -5887,7 +5982,7 @@ async function runExtractVideosStage(input) {
|
|
|
5887
5982
|
const probed = await Promise.all(
|
|
5888
5983
|
composition.images.map(async (img) => {
|
|
5889
5984
|
const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
|
|
5890
|
-
if (!
|
|
5985
|
+
if (!existsSync9(imgPath)) return null;
|
|
5891
5986
|
const colorSpace = await probeColorSpaceSafely(imgPath, log);
|
|
5892
5987
|
if (isHdrColorSpace2(colorSpace)) {
|
|
5893
5988
|
nativeHdrImageIds.add(img.id);
|
|
@@ -6007,7 +6102,7 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
6007
6102
|
videos.map(async (video) => {
|
|
6008
6103
|
const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
|
|
6009
6104
|
const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
|
|
6010
|
-
if (!isHttpUrl2(readFrom) && !
|
|
6105
|
+
if (!isHttpUrl2(readFrom) && !existsSync9(readFrom)) return null;
|
|
6011
6106
|
const metadata = await extractMediaMetadata3(readFrom);
|
|
6012
6107
|
const window2 = await resolveFinalFrameExtractionWindow2(
|
|
6013
6108
|
readFrom,
|
|
@@ -6035,7 +6130,7 @@ import {
|
|
|
6035
6130
|
} from "@hyperframes/engine";
|
|
6036
6131
|
|
|
6037
6132
|
// src/services/render/stages/audioStage.ts
|
|
6038
|
-
import { existsSync as
|
|
6133
|
+
import { existsSync as existsSync11 } from "node:fs";
|
|
6039
6134
|
import { join as join13 } from "node:path";
|
|
6040
6135
|
import {
|
|
6041
6136
|
MIXED_AUDIO_FILENAME,
|
|
@@ -6046,7 +6141,7 @@ import {
|
|
|
6046
6141
|
} from "@hyperframes/engine";
|
|
6047
6142
|
|
|
6048
6143
|
// src/services/render/audioDeliveryLimit.ts
|
|
6049
|
-
import { existsSync as
|
|
6144
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
6050
6145
|
import { dirname as dirname8, join as join12 } from "node:path";
|
|
6051
6146
|
import {
|
|
6052
6147
|
applyAudioFxChain,
|
|
@@ -6555,7 +6650,7 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
6555
6650
|
applyChain: applyAudioFxChain,
|
|
6556
6651
|
encode: encodeAac2,
|
|
6557
6652
|
rename: renameWithRetry6,
|
|
6558
|
-
sourceExists:
|
|
6653
|
+
sourceExists: existsSync10
|
|
6559
6654
|
}) {
|
|
6560
6655
|
const { audioPath, sourcePath, signal, timeoutMs } = input;
|
|
6561
6656
|
if (!deps.sourceExists(sourcePath)) return { success: true };
|
|
@@ -6650,7 +6745,7 @@ async function runAudioStageScoped(input) {
|
|
|
6650
6745
|
);
|
|
6651
6746
|
audioLoweredDb = guard.loweredDb;
|
|
6652
6747
|
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
6653
|
-
if (input.keepDeliverySource && !guard.sourceStale &&
|
|
6748
|
+
if (input.keepDeliverySource && !guard.sourceStale && existsSync11(pcmSourcePath)) {
|
|
6654
6749
|
deliverySourcePath = pcmSourcePath;
|
|
6655
6750
|
}
|
|
6656
6751
|
}
|
|
@@ -6778,7 +6873,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
6778
6873
|
}
|
|
6779
6874
|
|
|
6780
6875
|
// src/services/render/stages/captureStage.ts
|
|
6781
|
-
import { existsSync as
|
|
6876
|
+
import { existsSync as existsSync12, readdirSync, statfsSync } from "node:fs";
|
|
6782
6877
|
import { join as join14 } from "node:path";
|
|
6783
6878
|
import {
|
|
6784
6879
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
@@ -6830,7 +6925,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
6830
6925
|
}
|
|
6831
6926
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
6832
6927
|
let total = sampleDirectoryBytes(framesDir);
|
|
6833
|
-
if (!
|
|
6928
|
+
if (!existsSync12(workDir)) return total;
|
|
6834
6929
|
for (const attempt of readdirSync(workDir)) {
|
|
6835
6930
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
6836
6931
|
total += sampleDirectoryBytes(join14(workDir, attempt));
|
|
@@ -7015,7 +7110,11 @@ async function runSequentialDiskCapture(input, captureOptions, captureCfg, check
|
|
|
7015
7110
|
prepareCaptureSessionForReuse3(session, framesDir, videoInjector);
|
|
7016
7111
|
probeSession = null;
|
|
7017
7112
|
}
|
|
7018
|
-
await ensureSessionReady(session
|
|
7113
|
+
await ensureSessionReady(session, {
|
|
7114
|
+
frameIndex: resumeFrom,
|
|
7115
|
+
totalFrames,
|
|
7116
|
+
abortSignal: input.abortSignal
|
|
7117
|
+
});
|
|
7019
7118
|
assertAttemptActive();
|
|
7020
7119
|
lastBrowserConsole = session.browserConsoleBuffer;
|
|
7021
7120
|
await captureSessionFrames(session, {
|
|
@@ -7067,13 +7166,13 @@ function planTransientResume(error, ctx) {
|
|
|
7067
7166
|
if (!firstMissing) return null;
|
|
7068
7167
|
return isTransientCaptureRetryEligible(failure.kind, missing, ctx.retriesUsed) ? firstMissing.startFrame : null;
|
|
7069
7168
|
}
|
|
7070
|
-
async function ensureSessionReady(session) {
|
|
7169
|
+
async function ensureSessionReady(session, budget) {
|
|
7071
7170
|
if (!session.isInitialized) {
|
|
7072
7171
|
await initializeSession5(session);
|
|
7073
7172
|
return;
|
|
7074
7173
|
}
|
|
7075
7174
|
if (process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true") {
|
|
7076
|
-
await completeDeferredDrawElementInit3(session);
|
|
7175
|
+
await createSessionInitBudget(budget)(completeDeferredDrawElementInit3(session), session);
|
|
7077
7176
|
}
|
|
7078
7177
|
}
|
|
7079
7178
|
async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resumeFrom, checkDiskProjection }) {
|
|
@@ -7167,7 +7266,7 @@ import {
|
|
|
7167
7266
|
// src/services/render/stages/encodeStage.ts
|
|
7168
7267
|
import {
|
|
7169
7268
|
copyFileSync as copyFileSync4,
|
|
7170
|
-
existsSync as
|
|
7269
|
+
existsSync as existsSync13,
|
|
7171
7270
|
mkdirSync as mkdirSync5,
|
|
7172
7271
|
readdirSync as readdirSync2,
|
|
7173
7272
|
readFileSync as readFileSync5,
|
|
@@ -7301,7 +7400,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
7301
7400
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
7302
7401
|
if (failure) return failure;
|
|
7303
7402
|
}
|
|
7304
|
-
const fileSize =
|
|
7403
|
+
const fileSize = existsSync13(outputPath) ? statSync4(outputPath).size : 0;
|
|
7305
7404
|
return {
|
|
7306
7405
|
success: true,
|
|
7307
7406
|
outputPath,
|
|
@@ -7462,7 +7561,7 @@ async function runEncodeStage(input) {
|
|
|
7462
7561
|
const stage5Start = Date.now();
|
|
7463
7562
|
if (isPngSequence) {
|
|
7464
7563
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
7465
|
-
if (!
|
|
7564
|
+
if (!existsSync13(outputPath)) mkdirSync5(outputPath, { recursive: true });
|
|
7466
7565
|
const captured = readdirSync2(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
7467
7566
|
if (captured.length === 0) {
|
|
7468
7567
|
throw new Error(
|
|
@@ -7473,7 +7572,7 @@ async function runEncodeStage(input) {
|
|
|
7473
7572
|
const dst = join15(outputPath, formatExportFrameName(i, "png"));
|
|
7474
7573
|
copyFileSync4(join15(framesDir, name), dst);
|
|
7475
7574
|
});
|
|
7476
|
-
if (hasAudio && audioOutputPath &&
|
|
7575
|
+
if (hasAudio && audioOutputPath && existsSync13(audioOutputPath)) {
|
|
7477
7576
|
copyFileSync4(audioOutputPath, join15(outputPath, MIXED_AUDIO_FILENAME2));
|
|
7478
7577
|
log.info(
|
|
7479
7578
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
@@ -7636,7 +7735,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
7636
7735
|
let rangeStart = null;
|
|
7637
7736
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7638
7737
|
const framePath = join16(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7639
|
-
const missing = !
|
|
7738
|
+
const missing = !existsSync14(framePath) || statSync5(framePath).size <= 8;
|
|
7640
7739
|
if (missing && rangeStart === null) {
|
|
7641
7740
|
rangeStart = frameIndex;
|
|
7642
7741
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -7726,7 +7825,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
|
7726
7825
|
let captured = 0;
|
|
7727
7826
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
7728
7827
|
const framePath = join16(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
7729
|
-
if (
|
|
7828
|
+
if (existsSync14(framePath)) captured++;
|
|
7730
7829
|
}
|
|
7731
7830
|
return captured;
|
|
7732
7831
|
}
|
|
@@ -7953,7 +8052,7 @@ import {
|
|
|
7953
8052
|
} from "@hyperframes/engine";
|
|
7954
8053
|
|
|
7955
8054
|
// src/services/distributed/chunkFrames.ts
|
|
7956
|
-
import { existsSync as
|
|
8055
|
+
import { existsSync as existsSync16, readdirSync as readdirSync4 } from "node:fs";
|
|
7957
8056
|
import { extname as extname2, join as join18 } from "node:path";
|
|
7958
8057
|
import {
|
|
7959
8058
|
compositionRequiresWebGpu as compositionRequiresWebGpu3,
|
|
@@ -7963,7 +8062,7 @@ import {
|
|
|
7963
8062
|
// src/services/distributed/shared.ts
|
|
7964
8063
|
import { execFile as execFileCallback } from "node:child_process";
|
|
7965
8064
|
import { dirname as dirname11, join as join17 } from "node:path";
|
|
7966
|
-
import { existsSync as
|
|
8065
|
+
import { existsSync as existsSync15, readFileSync as readFileSync6 } from "node:fs";
|
|
7967
8066
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
7968
8067
|
import { promisify as promisify2 } from "node:util";
|
|
7969
8068
|
import {
|
|
@@ -7979,7 +8078,7 @@ function isPlanAudioArtifactPath(path) {
|
|
|
7979
8078
|
function resolvePlanAudioPath(planDir) {
|
|
7980
8079
|
for (const name of [PLAN_AUDIO_RELATIVE_PATH, PLAN_AUDIO_LEGACY_RELATIVE_PATH]) {
|
|
7981
8080
|
const candidate = join17(planDir, name);
|
|
7982
|
-
if (
|
|
8081
|
+
if (existsSync15(candidate)) return candidate;
|
|
7983
8082
|
}
|
|
7984
8083
|
return null;
|
|
7985
8084
|
}
|
|
@@ -8240,7 +8339,7 @@ function readProducerVersion() {
|
|
|
8240
8339
|
let current = startDir;
|
|
8241
8340
|
for (let i = 0; i < 10; i++) {
|
|
8242
8341
|
const candidate = join17(current, "package.json");
|
|
8243
|
-
if (
|
|
8342
|
+
if (existsSync15(candidate)) {
|
|
8244
8343
|
try {
|
|
8245
8344
|
const pkg = JSON.parse(readFileSync6(candidate, "utf-8"));
|
|
8246
8345
|
if (pkg.name === "@hyperframes/producer" && typeof pkg.version === "string") {
|
|
@@ -8294,7 +8393,7 @@ function rebuildExtractedFramesFromPlanDir(planDir, videos, indexMode = "dense-v
|
|
|
8294
8393
|
const result = [];
|
|
8295
8394
|
for (const v of videos) {
|
|
8296
8395
|
const outputDir = join18(planDir, "video-frames", v.videoId);
|
|
8297
|
-
if (!
|
|
8396
|
+
if (!existsSync16(outputDir)) {
|
|
8298
8397
|
throw new Error(
|
|
8299
8398
|
`[renderChunk] planDir missing extracted video frames for ${JSON.stringify(v.videoId)}: ${outputDir} not present. plan() should have written frames here; the planDir is malformed.`
|
|
8300
8399
|
);
|
|
@@ -8393,7 +8492,7 @@ async function extractDeferredVideoFramesForChunk(input) {
|
|
|
8393
8492
|
const range = planned.get(video.videoId).deferredRange;
|
|
8394
8493
|
const remoteUrl = remoteSources[range.sourcePath];
|
|
8395
8494
|
const sourcePath = remoteUrl ?? join18(input.planDir, ...range.sourcePath.split("/"));
|
|
8396
|
-
if (remoteUrl === void 0 && !
|
|
8495
|
+
if (remoteUrl === void 0 && !existsSync16(sourcePath)) {
|
|
8397
8496
|
throw new RenderChunkValidationError(
|
|
8398
8497
|
MISSING_PLAN_ARTIFACT,
|
|
8399
8498
|
`[renderChunk] planDir is missing the source for video ${JSON.stringify(video.videoId)}: ${range.sourcePath}`
|
|
@@ -8593,7 +8692,7 @@ async function readPlanReferenceBackground(input) {
|
|
|
8593
8692
|
}
|
|
8594
8693
|
|
|
8595
8694
|
// src/services/render/stages/freezePlan.ts
|
|
8596
|
-
import { existsSync as
|
|
8695
|
+
import { existsSync as existsSync17, mkdirSync as mkdirSync7, readFileSync as readFileSync7, readdirSync as readdirSync5, writeFileSync as writeFileSync6 } from "node:fs";
|
|
8597
8696
|
import { join as join21, relative as relative3, resolve as resolve8 } from "node:path";
|
|
8598
8697
|
import {
|
|
8599
8698
|
REFERENCE_BACKGROUND_FIELD,
|
|
@@ -8787,7 +8886,7 @@ function planFileSha256(absolutePath, digests) {
|
|
|
8787
8886
|
return sha256;
|
|
8788
8887
|
}
|
|
8789
8888
|
async function prehashPlanFiles(dir, digests) {
|
|
8790
|
-
if (!
|
|
8889
|
+
if (!existsSync17(dir)) return;
|
|
8791
8890
|
for (const { absolutePath } of listPlanFiles(dir)) {
|
|
8792
8891
|
if (!digests.has(absolutePath)) digests.set(absolutePath, await sha256FileAsync(absolutePath));
|
|
8793
8892
|
}
|
|
@@ -8857,15 +8956,15 @@ function writePlanRemoteFiles(planDir, files) {
|
|
|
8857
8956
|
}
|
|
8858
8957
|
function readPlanRemoteFiles(planDir) {
|
|
8859
8958
|
const path = join21(planDir, PLAN_REMOTE_FILES_RELATIVE_PATH);
|
|
8860
|
-
return
|
|
8959
|
+
return existsSync17(path) ? JSON.parse(readFileSync7(path, "utf-8")) : [];
|
|
8861
8960
|
}
|
|
8862
8961
|
function recomputePlanHashFromPlanDir(planDir, digests) {
|
|
8863
8962
|
const planJsonPath = join21(planDir, "plan.json");
|
|
8864
8963
|
const encoderJsonPath = join21(planDir, "meta", "encoder.json");
|
|
8865
|
-
if (!
|
|
8964
|
+
if (!existsSync17(planJsonPath)) {
|
|
8866
8965
|
throw new Error(`[freezePlan] plan.json missing: ${planJsonPath}`);
|
|
8867
8966
|
}
|
|
8868
|
-
if (!
|
|
8967
|
+
if (!existsSync17(encoderJsonPath)) {
|
|
8869
8968
|
throw new Error(`[freezePlan] meta/encoder.json missing: ${encoderJsonPath}`);
|
|
8870
8969
|
}
|
|
8871
8970
|
const planJson = JSON.parse(readFileSync7(planJsonPath, "utf-8"));
|
|
@@ -8895,11 +8994,11 @@ async function freezePlan(input) {
|
|
|
8895
8994
|
hasAudio,
|
|
8896
8995
|
digests
|
|
8897
8996
|
} = input;
|
|
8898
|
-
if (!
|
|
8997
|
+
if (!existsSync17(planDir)) {
|
|
8899
8998
|
throw new Error(`[freezePlan] planDir does not exist: ${planDir}`);
|
|
8900
8999
|
}
|
|
8901
9000
|
const metaDir = join21(planDir, "meta");
|
|
8902
|
-
if (!
|
|
9001
|
+
if (!existsSync17(metaDir)) mkdirSync7(metaDir, { recursive: true });
|
|
8903
9002
|
writeFileSync6(
|
|
8904
9003
|
join21(metaDir, "composition.json"),
|
|
8905
9004
|
`${JSON.stringify(composition, null, 2)}
|
|
@@ -8976,7 +9075,7 @@ function applyRuntimeEnvSnapshot(snapshot, env = process.env) {
|
|
|
8976
9075
|
}
|
|
8977
9076
|
|
|
8978
9077
|
// src/services/distributed/chunkAudio.ts
|
|
8979
|
-
import { closeSync as closeSync4, existsSync as
|
|
9078
|
+
import { closeSync as closeSync4, existsSync as existsSync18, ftruncateSync, mkdirSync as mkdirSync9, openSync as openSync4, readFileSync as readFileSync8 } from "node:fs";
|
|
8980
9079
|
import { writeFileSync as writeFileSync7, writeSync } from "node:fs";
|
|
8981
9080
|
import { join as join23 } from "node:path";
|
|
8982
9081
|
import { appendRenderProvenanceArgs, describeFfmpegFailure as describeFfmpegFailure4, runFfmpeg as runFfmpeg7 } from "@hyperframes/engine";
|
|
@@ -9467,7 +9566,7 @@ async function writeChunkAudioSlices(input) {
|
|
|
9467
9566
|
}
|
|
9468
9567
|
function chunkAudioSource(planDir, chunkIndex, scratchPath) {
|
|
9469
9568
|
const layoutPath = join23(planDir, SLICE_LAYOUT);
|
|
9470
|
-
if (!
|
|
9569
|
+
if (!existsSync18(layoutPath)) return resolvePlanAudioPath(planDir);
|
|
9471
9570
|
const layout = JSON.parse(readFileSync8(layoutPath, "utf-8"));
|
|
9472
9571
|
const frame = readFileSync8(join23(planDir, SLICE_FRAME));
|
|
9473
9572
|
const fd = openSync4(scratchPath, "w");
|
|
@@ -10017,7 +10116,7 @@ function pathInside(root, path) {
|
|
|
10017
10116
|
function stableSourcePath(roots, srcPath) {
|
|
10018
10117
|
for (const [root, prefix] of roots) {
|
|
10019
10118
|
const rel = pathInside(root, srcPath);
|
|
10020
|
-
if (rel !== null) return [prefix, ...rel.split(
|
|
10119
|
+
if (rel !== null) return [prefix, ...rel.split(sep3)].filter(Boolean).join("/");
|
|
10021
10120
|
}
|
|
10022
10121
|
return srcPath;
|
|
10023
10122
|
}
|
|
@@ -10031,8 +10130,8 @@ function createVideoSourceShipper(input) {
|
|
|
10031
10130
|
let result = null;
|
|
10032
10131
|
for (const root of stagedRel === null ? [input.workCompiledDir, input.projectDir] : []) {
|
|
10033
10132
|
const rel = pathInside(root, srcPath);
|
|
10034
|
-
if (rel !== null && (
|
|
10035
|
-
result = ["compiled", ...rel.split(
|
|
10133
|
+
if (rel !== null && (existsSync19(join25(input.planDir, "compiled", rel)) || input.isRemote(srcPath))) {
|
|
10134
|
+
result = ["compiled", ...rel.split(sep3)].join("/");
|
|
10036
10135
|
break;
|
|
10037
10136
|
}
|
|
10038
10137
|
}
|
|
@@ -10059,7 +10158,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
10059
10158
|
useGpu: false,
|
|
10060
10159
|
browserGpuMode: "software"
|
|
10061
10160
|
});
|
|
10062
|
-
if (!
|
|
10161
|
+
if (!existsSync19(planDir)) mkdirSync10(planDir, { recursive: true });
|
|
10063
10162
|
const log = config.logger ?? defaultLogger;
|
|
10064
10163
|
const sizeLimitBytes = options.executionPlanSizeLimitBytes ?? config.planDirSizeLimitBytes ?? PLAN_DIR_SIZE_LIMIT_BYTES;
|
|
10065
10164
|
const abortSignal = config.abortSignal;
|
|
@@ -10089,7 +10188,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
10089
10188
|
});
|
|
10090
10189
|
const entryFile = config.entryFile ?? "index.html";
|
|
10091
10190
|
const htmlPath = join25(projectDir, entryFile);
|
|
10092
|
-
if (!
|
|
10191
|
+
if (!existsSync19(htmlPath)) {
|
|
10093
10192
|
throw new Error(`[plan] entry file not found: ${htmlPath}`);
|
|
10094
10193
|
}
|
|
10095
10194
|
const workDir = join25(planDir, ".plan-work");
|
|
@@ -10104,7 +10203,7 @@ async function compilePlanComposition(projectDir, config, planDir, options) {
|
|
|
10104
10203
|
filter: (src) => {
|
|
10105
10204
|
const rel = relative5(projectDir, src);
|
|
10106
10205
|
if (rel === "" || rel.startsWith("..")) return true;
|
|
10107
|
-
const firstSegment = rel.split(
|
|
10206
|
+
const firstSegment = rel.split(sep3, 1)[0];
|
|
10108
10207
|
return firstSegment === void 0 || !PLAN_PROJECT_DIR_SKIP_SEGMENTS.has(firstSegment);
|
|
10109
10208
|
}
|
|
10110
10209
|
});
|
|
@@ -10317,15 +10416,15 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10317
10416
|
logPlanStage(log, "extract", stageStarted);
|
|
10318
10417
|
const stagedVideoFrames = join25(compiledDir, "__hyperframes_video_frames");
|
|
10319
10418
|
const videoFramesDst = join25(planDir, "video-frames");
|
|
10320
|
-
if (
|
|
10419
|
+
if (existsSync19(videoFramesDst)) rmSync7(videoFramesDst, { recursive: true, force: true });
|
|
10321
10420
|
const videoSourcesDir = join25(planDir, "video-sources");
|
|
10322
10421
|
rmSync7(videoSourcesDir, { recursive: true, force: true });
|
|
10323
|
-
if (
|
|
10422
|
+
if (existsSync19(stagedVideoFrames)) {
|
|
10324
10423
|
renameWithRetry8(stagedVideoFrames, videoFramesDst);
|
|
10325
10424
|
} else {
|
|
10326
10425
|
mkdirSync10(videoFramesDst, { recursive: true });
|
|
10327
10426
|
}
|
|
10328
|
-
if (
|
|
10427
|
+
if (existsSync19(finalCompiledDir)) rmSync7(finalCompiledDir, { recursive: true, force: true });
|
|
10329
10428
|
renameWithRetry8(compiledDir, finalCompiledDir);
|
|
10330
10429
|
const shipVideoSource = createVideoSourceShipper({
|
|
10331
10430
|
planDir,
|
|
@@ -10445,10 +10544,10 @@ async function buildLocalExecutionPlan(projectDir, config, executionPlanDir, opt
|
|
|
10445
10544
|
applyDistributedAudioWarningPolicy(job, audioResult.audioError, audioResult.audioFailures, log);
|
|
10446
10545
|
}
|
|
10447
10546
|
const planAudioPath = join25(planDir, PLAN_AUDIO_RELATIVE_PATH);
|
|
10448
|
-
if (audioResult?.hasAudio &&
|
|
10547
|
+
if (audioResult?.hasAudio && existsSync19(audioResult.audioOutputPath)) {
|
|
10449
10548
|
renameWithRetry8(audioResult.audioOutputPath, planAudioPath);
|
|
10450
10549
|
}
|
|
10451
|
-
if (chunksCarryAudio &&
|
|
10550
|
+
if (chunksCarryAudio && existsSync19(planAudioPath)) {
|
|
10452
10551
|
if (options.sliceChunkAudio) {
|
|
10453
10552
|
stageStarted = Date.now();
|
|
10454
10553
|
await writeChunkAudioSlices({
|
|
@@ -10561,7 +10660,7 @@ async function buildPlanAudio(projectDir, config, workRoot, outputPath, options
|
|
|
10561
10660
|
if (mixed.audioError) {
|
|
10562
10661
|
applyDistributedAudioWarningPolicy(c.job, mixed.audioError, mixed.audioFailures, c.log);
|
|
10563
10662
|
}
|
|
10564
|
-
if (!mixed.hasAudio || !
|
|
10663
|
+
if (!mixed.hasAudio || !existsSync19(mixed.audioOutputPath)) return { hasAudio: false };
|
|
10565
10664
|
renameWithRetry8(mixed.audioOutputPath, outputPath);
|
|
10566
10665
|
return { hasAudio: true };
|
|
10567
10666
|
} finally {
|
|
@@ -10576,7 +10675,7 @@ import {
|
|
|
10576
10675
|
} from "@hyperframes/engine";
|
|
10577
10676
|
import {
|
|
10578
10677
|
copyFileSync as copyFileSync8,
|
|
10579
|
-
existsSync as
|
|
10678
|
+
existsSync as existsSync21,
|
|
10580
10679
|
lstatSync as lstatSync2,
|
|
10581
10680
|
mkdirSync as mkdirSync12,
|
|
10582
10681
|
mkdtempSync as mkdtempSync4,
|
|
@@ -10587,14 +10686,14 @@ import {
|
|
|
10587
10686
|
writeFileSync as writeFileSync10
|
|
10588
10687
|
} from "node:fs";
|
|
10589
10688
|
import { tmpdir } from "node:os";
|
|
10590
|
-
import { dirname as dirname14, isAbsolute as isAbsolute4, join as join28, relative as relative6, resolve as resolve9, sep as
|
|
10689
|
+
import { dirname as dirname14, isAbsolute as isAbsolute4, join as join28, relative as relative6, resolve as resolve9, sep as sep4 } from "node:path";
|
|
10591
10690
|
import { DISTRIBUTED_FPS, isDistributedFps } from "@hyperframes/core";
|
|
10592
10691
|
import { createFrameLookupTable as createFrameLookupTable5, renameWithRetry as renameWithRetry10 } from "@hyperframes/engine";
|
|
10593
10692
|
|
|
10594
10693
|
// src/services/distributed/planV2Publisher.ts
|
|
10595
10694
|
import {
|
|
10596
10695
|
copyFileSync as copyFileSync7,
|
|
10597
|
-
existsSync as
|
|
10696
|
+
existsSync as existsSync20,
|
|
10598
10697
|
linkSync as linkSync2,
|
|
10599
10698
|
mkdirSync as mkdirSync11,
|
|
10600
10699
|
mkdtempSync as mkdtempSync3,
|
|
@@ -10663,7 +10762,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10663
10762
|
#linkFile;
|
|
10664
10763
|
#committed = false;
|
|
10665
10764
|
constructor(destinationDir, options = {}) {
|
|
10666
|
-
if (
|
|
10765
|
+
if (existsSync20(destinationDir)) {
|
|
10667
10766
|
throw new PlanV2IntegrityError(`output directory already exists: ${destinationDir}`);
|
|
10668
10767
|
}
|
|
10669
10768
|
this.destinationDir = destinationDir;
|
|
@@ -10680,7 +10779,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10680
10779
|
);
|
|
10681
10780
|
}
|
|
10682
10781
|
const destinationPath = planV2BlobPath(this.temporaryDir, digest);
|
|
10683
|
-
if (
|
|
10782
|
+
if (existsSync20(destinationPath)) return;
|
|
10684
10783
|
mkdirSync11(dirname13(destinationPath), { recursive: true });
|
|
10685
10784
|
const stagingDir = mkdtempSync3(join27(dirname13(destinationPath), ".plan-v2-blob-"));
|
|
10686
10785
|
const temporaryPath = join27(stagingDir, "blob");
|
|
@@ -10698,7 +10797,7 @@ var LocalPlanV2ArtifactPublisher = class {
|
|
|
10698
10797
|
}
|
|
10699
10798
|
async commitManifest(manifestBytes) {
|
|
10700
10799
|
for (const digest of new Set(manifestDigests(manifestBytes))) {
|
|
10701
|
-
if (!
|
|
10800
|
+
if (!existsSync20(planV2BlobPath(this.temporaryDir, digest))) {
|
|
10702
10801
|
throw new PlanV2IntegrityError(
|
|
10703
10802
|
`cannot commit manifest before referenced blob is durable: ${digest}`
|
|
10704
10803
|
);
|
|
@@ -10754,7 +10853,7 @@ function listFiles(root) {
|
|
|
10754
10853
|
walk(absolutePath);
|
|
10755
10854
|
} else if (entry.isFile()) {
|
|
10756
10855
|
files.push({
|
|
10757
|
-
path: relative6(rootResolved, absolutePath).split(
|
|
10856
|
+
path: relative6(rootResolved, absolutePath).split(sep4).join("/"),
|
|
10758
10857
|
absolutePath
|
|
10759
10858
|
});
|
|
10760
10859
|
} else {
|
|
@@ -10777,7 +10876,7 @@ function assertValidExtractionCacheCompleteSentinel(path) {
|
|
|
10777
10876
|
}
|
|
10778
10877
|
function validateExtractionCacheCompleteSentinels(executionPlanDir) {
|
|
10779
10878
|
const videoRoot = join28(executionPlanDir, "video-frames");
|
|
10780
|
-
if (!
|
|
10879
|
+
if (!existsSync21(videoRoot)) return;
|
|
10781
10880
|
for (const videoEntry of readdirSync7(videoRoot, { withFileTypes: true })) {
|
|
10782
10881
|
if (!videoEntry.isDirectory()) continue;
|
|
10783
10882
|
const videoDir = join28(videoRoot, videoEntry.name);
|
|
@@ -10794,7 +10893,7 @@ function isExtractionCacheCompleteSentinelPath(path) {
|
|
|
10794
10893
|
function resolveExtractedVideoOutputDir(planDir, videoId) {
|
|
10795
10894
|
const videoRoot = resolve9(planDir, "video-frames");
|
|
10796
10895
|
const outputDir = resolve9(videoRoot, videoId);
|
|
10797
|
-
if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${
|
|
10896
|
+
if (outputDir === videoRoot || !outputDir.startsWith(`${videoRoot}${sep4}`)) {
|
|
10798
10897
|
throw new PlanV2IntegrityError(`unsafe extracted video id: ${JSON.stringify(videoId)}`);
|
|
10799
10898
|
}
|
|
10800
10899
|
return outputDir;
|
|
@@ -10878,7 +10977,7 @@ function parsePlanVideosJson2(value) {
|
|
|
10878
10977
|
}
|
|
10879
10978
|
function materializeExtractedVideoDirectories(planDir) {
|
|
10880
10979
|
const videosPath = join28(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10881
|
-
if (!
|
|
10980
|
+
if (!existsSync21(videosPath)) return;
|
|
10882
10981
|
const videos = parsePlanVideosJson2(readJsonFile(videosPath, PLAN_VIDEOS_META_RELATIVE_PATH));
|
|
10883
10982
|
for (const video of videos.extracted) {
|
|
10884
10983
|
mkdirSync12(resolveExtractedVideoOutputDir(planDir, video.videoId), { recursive: true });
|
|
@@ -10912,9 +11011,9 @@ function parseChunkSlices(value) {
|
|
|
10912
11011
|
}
|
|
10913
11012
|
function buildVideoChunkDependencies(executionPlanDir, dimensions) {
|
|
10914
11013
|
const videoRoot = join28(executionPlanDir, "video-frames");
|
|
10915
|
-
const hasExtractedFrames =
|
|
11014
|
+
const hasExtractedFrames = existsSync21(videoRoot) && listFiles(videoRoot).length > 0;
|
|
10916
11015
|
const videosPath = join28(executionPlanDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
10917
|
-
if (!
|
|
11016
|
+
if (!existsSync21(videosPath)) {
|
|
10918
11017
|
return hasExtractedFrames ? { mode: "full-source-pack", dependencies: null } : { mode: "exact-rendered-frames", dependencies: /* @__PURE__ */ new Map() };
|
|
10919
11018
|
}
|
|
10920
11019
|
const chunksPath = join28(executionPlanDir, "meta", "chunks.json");
|
|
@@ -10954,7 +11053,7 @@ function collectChunkVideoDependencies(input) {
|
|
|
10954
11053
|
for (let frame = chunk.startFrame; frame < chunk.endFrame; frame++) {
|
|
10955
11054
|
const globalTime = frame * input.fpsDen / input.fpsNum;
|
|
10956
11055
|
for (const [videoId, payload] of input.table.getActiveFramePayloads(globalTime)) {
|
|
10957
|
-
const path = input.deferredSources.get(videoId) ?? relative6(resolve9(input.executionPlanDir), payload.framePath).split(
|
|
11056
|
+
const path = input.deferredSources.get(videoId) ?? relative6(resolve9(input.executionPlanDir), payload.framePath).split(sep4).join("/");
|
|
10958
11057
|
assertSafeRelativePath(path);
|
|
10959
11058
|
const indexes = mutable.get(path) ?? /* @__PURE__ */ new Set();
|
|
10960
11059
|
indexes.add(chunk.index);
|
|
@@ -10971,7 +11070,7 @@ function computeManifestHash(manifest) {
|
|
|
10971
11070
|
return sha256Hex(`${PLAN_V2_HASH_PREFIX}${canonicalJsonStringify(manifestPayload(manifest))}`);
|
|
10972
11071
|
}
|
|
10973
11072
|
function writeBlob(sourcePath, destinationPath) {
|
|
10974
|
-
if (
|
|
11073
|
+
if (existsSync21(destinationPath)) return;
|
|
10975
11074
|
mkdirSync12(dirname14(destinationPath), { recursive: true });
|
|
10976
11075
|
const temporaryDir = mkdtempSync4(join28(dirname14(destinationPath), ".plan-v2-blob-"));
|
|
10977
11076
|
const temporaryPath = join28(temporaryDir, "blob");
|
|
@@ -11011,7 +11110,7 @@ async function putTasks(tasks) {
|
|
|
11011
11110
|
}
|
|
11012
11111
|
function buildPlanV2Publication(executionPlanDir, digests) {
|
|
11013
11112
|
const executionPlanPath = join28(executionPlanDir, "plan.json");
|
|
11014
|
-
if (!
|
|
11113
|
+
if (!existsSync21(executionPlanPath)) {
|
|
11015
11114
|
throw new PlanV2IntegrityError(`execution plan is missing plan.json: ${executionPlanPath}`);
|
|
11016
11115
|
}
|
|
11017
11116
|
const executionPlanValue = readJsonFile(executionPlanPath, "execution plan.json");
|
|
@@ -11091,7 +11190,7 @@ function buildPlanV2Publication(executionPlanDir, digests) {
|
|
|
11091
11190
|
};
|
|
11092
11191
|
}
|
|
11093
11192
|
function createPlanV2FromExecutionPlan(executionPlanDir, planV2Dir) {
|
|
11094
|
-
if (
|
|
11193
|
+
if (existsSync21(planV2Dir)) {
|
|
11095
11194
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
11096
11195
|
}
|
|
11097
11196
|
const publication = buildPlanV2Publication(executionPlanDir);
|
|
@@ -11177,7 +11276,7 @@ async function planV2WithPublisher(projectDir, config, publisher, options = {})
|
|
|
11177
11276
|
}
|
|
11178
11277
|
}
|
|
11179
11278
|
async function planV2(projectDir, config, planV2Dir) {
|
|
11180
|
-
if (
|
|
11279
|
+
if (existsSync21(planV2Dir)) {
|
|
11181
11280
|
throw new PlanV2IntegrityError(`output directory already exists: ${planV2Dir}`);
|
|
11182
11281
|
}
|
|
11183
11282
|
const publisher = new LocalPlanV2ArtifactPublisher(planV2Dir);
|
|
@@ -11331,7 +11430,7 @@ function parsePlanV2Manifest(value) {
|
|
|
11331
11430
|
}
|
|
11332
11431
|
function readPlanV2Manifest(planV2Dir) {
|
|
11333
11432
|
const manifestPath = join28(planV2Dir, "plan.json");
|
|
11334
|
-
if (!
|
|
11433
|
+
if (!existsSync21(manifestPath)) {
|
|
11335
11434
|
throw new PlanV2IntegrityError(`missing v2 manifest: ${manifestPath}`);
|
|
11336
11435
|
}
|
|
11337
11436
|
const value = readJsonFile(manifestPath, "v2 manifest");
|
|
@@ -11350,7 +11449,7 @@ function listPlanV2ArtifactsForTarget(manifest, target) {
|
|
|
11350
11449
|
}
|
|
11351
11450
|
function verifyBlob(planV2Dir, artifact) {
|
|
11352
11451
|
const sourcePath = planV2BlobPath(planV2Dir, artifact.sha256);
|
|
11353
|
-
if (!
|
|
11452
|
+
if (!existsSync21(sourcePath)) {
|
|
11354
11453
|
throw new PlanV2IntegrityError(`missing content-addressed artifact ${artifact.sha256}`);
|
|
11355
11454
|
}
|
|
11356
11455
|
const stats = statSync7(sourcePath);
|
|
@@ -11363,7 +11462,7 @@ function verifyBlob(planV2Dir, artifact) {
|
|
|
11363
11462
|
return sourcePath;
|
|
11364
11463
|
}
|
|
11365
11464
|
function materializePlanV2Target(planV2Dir, target, destinationDir, options = {}) {
|
|
11366
|
-
if (
|
|
11465
|
+
if (existsSync21(destinationDir)) {
|
|
11367
11466
|
throw new PlanV2IntegrityError(`materialization destination already exists: ${destinationDir}`);
|
|
11368
11467
|
}
|
|
11369
11468
|
const manifest = readPlanV2Manifest(planV2Dir);
|
|
@@ -11428,7 +11527,7 @@ function validatePlanV2MaterializedTarget(planDir, expectedTarget) {
|
|
|
11428
11527
|
}
|
|
11429
11528
|
function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
11430
11529
|
const markerPath = join28(planDir, PLAN_V2_MATERIALIZATION_MARKER);
|
|
11431
|
-
if (!
|
|
11530
|
+
if (!existsSync21(markerPath)) return null;
|
|
11432
11531
|
const marker = readJsonFile(markerPath, "materialization marker");
|
|
11433
11532
|
if (!isRecord3(marker) || !isRecord3(marker.manifest) || !isRecord3(marker.target)) {
|
|
11434
11533
|
throw new PlanV2IntegrityError("malformed materialization marker");
|
|
@@ -11446,7 +11545,7 @@ function readValidatedPlanV2Materialization(planDir, expectedTarget) {
|
|
|
11446
11545
|
for (const artifact of required) {
|
|
11447
11546
|
if (Object.hasOwn(remoteSources, artifact.path)) continue;
|
|
11448
11547
|
const path = join28(planDir, ...artifact.path.split("/"));
|
|
11449
|
-
if (!
|
|
11548
|
+
if (!existsSync21(path) || statSync7(path).size !== artifact.sizeBytes) {
|
|
11450
11549
|
throw new PlanV2IntegrityError(
|
|
11451
11550
|
`materialized artifact missing or truncated: ${artifact.path}`
|
|
11452
11551
|
);
|
|
@@ -11470,7 +11569,7 @@ import {
|
|
|
11470
11569
|
} from "@hyperframes/engine";
|
|
11471
11570
|
import {
|
|
11472
11571
|
cpSync as cpSync3,
|
|
11473
|
-
existsSync as
|
|
11572
|
+
existsSync as existsSync22,
|
|
11474
11573
|
mkdirSync as mkdirSync13,
|
|
11475
11574
|
mkdtempSync as mkdtempSync5,
|
|
11476
11575
|
readFileSync as readFileSync10,
|
|
@@ -11525,7 +11624,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11525
11624
|
}
|
|
11526
11625
|
const format = plan.dimensions.format;
|
|
11527
11626
|
const cfrEncoder = cfr ? readCfrEncoder(planDir, format) : null;
|
|
11528
|
-
if (!
|
|
11627
|
+
if (!existsSync22(dirname15(outputPath))) {
|
|
11529
11628
|
mkdirSync13(dirname15(outputPath), { recursive: true });
|
|
11530
11629
|
}
|
|
11531
11630
|
const workDir = mkdtempSync5(`${outputPath}.assemble-work-`);
|
|
@@ -11549,7 +11648,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11549
11648
|
try {
|
|
11550
11649
|
const videoSources = cfrEncoder ? [await reencodeCfr(ctx, chunkPaths, cfrEncoder)] : chunkPaths;
|
|
11551
11650
|
const finalPath = join29(workDir, `output.${format}`);
|
|
11552
|
-
const audio = audioPath !== null &&
|
|
11651
|
+
const audio = audioPath !== null && existsSync22(audioPath) ? audioPath : null;
|
|
11553
11652
|
await writeJoined(ctx, videoSources, audio, finalPath);
|
|
11554
11653
|
renameWithRetry11(finalPath, outputPath);
|
|
11555
11654
|
log.info("[assemble] stage timings", {
|
|
@@ -11567,7 +11666,7 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11567
11666
|
});
|
|
11568
11667
|
}
|
|
11569
11668
|
}
|
|
11570
|
-
const fileSize =
|
|
11669
|
+
const fileSize = existsSync22(outputPath) ? statSync8(outputPath).size : 0;
|
|
11571
11670
|
return {
|
|
11572
11671
|
...referenceBackgroundResult6(plan[REFERENCE_BACKGROUND_FIELD4]),
|
|
11573
11672
|
outputPath,
|
|
@@ -11579,13 +11678,13 @@ async function assemble(planDir, chunkOutputs, audioPath, outputPath, options) {
|
|
|
11579
11678
|
function readAssemblePlan(planDir, chunkPaths) {
|
|
11580
11679
|
const planJsonPath = join29(planDir, "plan.json");
|
|
11581
11680
|
const chunksJsonPath = join29(planDir, "meta", "chunks.json");
|
|
11582
|
-
if (!
|
|
11681
|
+
if (!existsSync22(planJsonPath)) {
|
|
11583
11682
|
throw new Error(`[assemble] planDir missing plan.json: ${planJsonPath}`);
|
|
11584
11683
|
}
|
|
11585
11684
|
const plan = JSON.parse(readFileSync10(planJsonPath, "utf-8"));
|
|
11586
11685
|
readPlanProtocolV1(plan, DISTRIBUTED_RENDER_CAPABILITIES.roles.assembler);
|
|
11587
11686
|
validatePlanV2MaterializedTarget(planDir, { role: "assembler" });
|
|
11588
|
-
if (!
|
|
11687
|
+
if (!existsSync22(chunksJsonPath)) {
|
|
11589
11688
|
throw new Error(`[assemble] planDir missing meta/chunks.json: ${chunksJsonPath}`);
|
|
11590
11689
|
}
|
|
11591
11690
|
const chunks = JSON.parse(readFileSync10(chunksJsonPath, "utf-8"));
|
|
@@ -11600,7 +11699,7 @@ function readAssemblePlan(planDir, chunkPaths) {
|
|
|
11600
11699
|
}
|
|
11601
11700
|
}
|
|
11602
11701
|
for (const path of chunkPaths.flat()) {
|
|
11603
|
-
if (!
|
|
11702
|
+
if (!existsSync22(path)) {
|
|
11604
11703
|
throw new Error(`[assemble] chunk path does not exist: ${path}`);
|
|
11605
11704
|
}
|
|
11606
11705
|
}
|
|
@@ -11739,7 +11838,7 @@ function planFormat(planDir) {
|
|
|
11739
11838
|
}
|
|
11740
11839
|
function lockedAudio(planDir) {
|
|
11741
11840
|
const encoderJsonPath = join29(planDir, "meta", "encoder.json");
|
|
11742
|
-
if (!
|
|
11841
|
+
if (!existsSync22(encoderJsonPath)) return {};
|
|
11743
11842
|
return JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
11744
11843
|
}
|
|
11745
11844
|
function readCfrEncoder(planDir, format) {
|
|
@@ -11749,7 +11848,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
11749
11848
|
);
|
|
11750
11849
|
}
|
|
11751
11850
|
const encoderJsonPath = join29(planDir, "meta", "encoder.json");
|
|
11752
|
-
if (!
|
|
11851
|
+
if (!existsSync22(encoderJsonPath)) {
|
|
11753
11852
|
throw new Error(`[assemble] planDir missing meta/encoder.json: ${encoderJsonPath}`);
|
|
11754
11853
|
}
|
|
11755
11854
|
const encoderJson = JSON.parse(readFileSync10(encoderJsonPath, "utf-8"));
|
|
@@ -11761,7 +11860,7 @@ function readCfrEncoder(planDir, format) {
|
|
|
11761
11860
|
return encoderJson;
|
|
11762
11861
|
}
|
|
11763
11862
|
function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, startTimeMs) {
|
|
11764
|
-
if (
|
|
11863
|
+
if (existsSync22(outputPath)) rmSync10(outputPath, { recursive: true, force: true });
|
|
11765
11864
|
mkdirSync13(outputPath, { recursive: true });
|
|
11766
11865
|
let globalIdx = 0;
|
|
11767
11866
|
for (const chunkDir of chunkPaths) {
|
|
@@ -11785,7 +11884,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11785
11884
|
`[assemble] png-sequence frame count mismatch: merged ${globalIdx} frames vs plan.totalFrames=${totalFrames}. Using on-disk count.`
|
|
11786
11885
|
);
|
|
11787
11886
|
}
|
|
11788
|
-
if (audioPath !== null &&
|
|
11887
|
+
if (audioPath !== null && existsSync22(audioPath)) {
|
|
11789
11888
|
const sidecar = join29(outputPath, MIXED_AUDIO_FILENAME4);
|
|
11790
11889
|
cpSync3(audioPath, sidecar);
|
|
11791
11890
|
}
|
|
@@ -11806,7 +11905,7 @@ function mergePngFrameDirs(chunkPaths, outputPath, totalFrames, audioPath, start
|
|
|
11806
11905
|
|
|
11807
11906
|
// src/services/distributed/renderChunk.ts
|
|
11808
11907
|
import { randomBytes } from "node:crypto";
|
|
11809
|
-
import { existsSync as
|
|
11908
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync14, readFileSync as readFileSync11, readdirSync as readdirSync9, rmSync as rmSync11, writeFileSync as writeFileSync12 } from "node:fs";
|
|
11810
11909
|
import { cpus } from "node:os";
|
|
11811
11910
|
import { join as join30 } from "node:path";
|
|
11812
11911
|
import {
|
|
@@ -11912,13 +12011,24 @@ async function runCaptureWithScreenshotFallback(input) {
|
|
|
11912
12011
|
try {
|
|
11913
12012
|
return await input.run(input.forceScreenshot);
|
|
11914
12013
|
} catch (error) {
|
|
11915
|
-
if (input.forceScreenshot || await input.canRetry?.() === false)
|
|
11916
|
-
|
|
12014
|
+
if (input.forceScreenshot || await input.canRetry?.() === false)
|
|
12015
|
+
throw imageMissingIn(error) ?? error;
|
|
12016
|
+
if (!shouldRetryChunkCaptureWithScreenshot(error)) throw imageMissingIn(error) ?? error;
|
|
11917
12017
|
input.onFallback?.(error);
|
|
11918
12018
|
await input.resetForScreenshotRetry();
|
|
11919
|
-
return await input.run(true)
|
|
12019
|
+
return await input.run(true).catch((retryError) => {
|
|
12020
|
+
throw imageMissingIn(retryError) ?? retryError;
|
|
12021
|
+
});
|
|
11920
12022
|
}
|
|
11921
12023
|
}
|
|
12024
|
+
function imageMissingIn(error) {
|
|
12025
|
+
let e = error;
|
|
12026
|
+
for (let depth = 0; depth < 5 && typeof e === "object" && e !== null; depth++) {
|
|
12027
|
+
if (e.name === "ImageMissingFromFrameError") return e;
|
|
12028
|
+
e = e.cause;
|
|
12029
|
+
}
|
|
12030
|
+
return void 0;
|
|
12031
|
+
}
|
|
11922
12032
|
async function beginFrameSessionNeedsScreenshotFallback(session, probe = probeBeginFrameLiveness2) {
|
|
11923
12033
|
if (session.launchCaptureMode !== "beginframe") return false;
|
|
11924
12034
|
const timeoutMs = Number(process.env.PRODUCER_BEGINFRAME_PROBE_TIMEOUT_MS) > 0 ? Number(process.env.PRODUCER_BEGINFRAME_PROBE_TIMEOUT_MS) : 3e4;
|
|
@@ -11952,7 +12062,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11952
12062
|
const planJsonPath = join30(planDir, "plan.json");
|
|
11953
12063
|
const encoderJsonPath = join30(planDir, "meta", "encoder.json");
|
|
11954
12064
|
const chunksJsonPath = join30(planDir, "meta", "chunks.json");
|
|
11955
|
-
if (!
|
|
12065
|
+
if (!existsSync23(planJsonPath)) {
|
|
11956
12066
|
throw new RenderChunkValidationError(
|
|
11957
12067
|
MISSING_PLAN_ARTIFACT,
|
|
11958
12068
|
`[renderChunk] planDir is missing required artifact: ${planJsonPath}`
|
|
@@ -11968,7 +12078,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11968
12078
|
const v2Manifest = v2Materialization?.manifest ?? null;
|
|
11969
12079
|
const remoteSources = v2Materialization?.remoteSources ?? {};
|
|
11970
12080
|
for (const required of [encoderJsonPath, chunksJsonPath]) {
|
|
11971
|
-
if (!
|
|
12081
|
+
if (!existsSync23(required)) {
|
|
11972
12082
|
throw new RenderChunkValidationError(
|
|
11973
12083
|
MISSING_PLAN_ARTIFACT,
|
|
11974
12084
|
`[renderChunk] planDir is missing required artifact: ${required}`
|
|
@@ -11979,7 +12089,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
11979
12089
|
const chunks = JSON.parse(readFileSync11(chunksJsonPath, "utf-8"));
|
|
11980
12090
|
const videosJsonPath = join30(planDir, PLAN_VIDEOS_META_RELATIVE_PATH);
|
|
11981
12091
|
let planVideos = null;
|
|
11982
|
-
if (
|
|
12092
|
+
if (existsSync23(videosJsonPath)) {
|
|
11983
12093
|
try {
|
|
11984
12094
|
planVideos = validatePlanVideosForChunk(JSON.parse(readFileSync11(videosJsonPath, "utf-8")));
|
|
11985
12095
|
} catch (err) {
|
|
@@ -12010,7 +12120,7 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12010
12120
|
const range = resolveChunkFrameRange(slice, chunkIndex, options?.frameRange);
|
|
12011
12121
|
const framesInChunk = range.endFrame - range.startFrame;
|
|
12012
12122
|
const compiledDir = join30(planDir, "compiled");
|
|
12013
|
-
if (!
|
|
12123
|
+
if (!existsSync23(compiledDir)) {
|
|
12014
12124
|
throw new RenderChunkValidationError(
|
|
12015
12125
|
MISSING_PLAN_ARTIFACT,
|
|
12016
12126
|
`[renderChunk] planDir missing compiled/ directory: ${compiledDir}`
|
|
@@ -12260,10 +12370,10 @@ async function renderChunk(planDir, chunkIndex, outputChunkPath, options) {
|
|
|
12260
12370
|
const effectiveBitrate = encoder.crf != null ? void 0 : encoder.bitrate;
|
|
12261
12371
|
const videoOnlyPath = encoder.chunksCarryAudio ? `${outputChunkPath}.video.mp4` : outputChunkPath;
|
|
12262
12372
|
if (isPngSequence) {
|
|
12263
|
-
if (!
|
|
12373
|
+
if (!existsSync23(outputChunkPath)) mkdirSync14(outputChunkPath, { recursive: true });
|
|
12264
12374
|
} else {
|
|
12265
12375
|
const outDir = join30(outputChunkPath, "..");
|
|
12266
|
-
if (!
|
|
12376
|
+
if (!existsSync23(outDir)) mkdirSync14(outDir, { recursive: true });
|
|
12267
12377
|
}
|
|
12268
12378
|
const encodeInput = {
|
|
12269
12379
|
job,
|