@hyperframes/producer 0.9.18 → 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/public-server.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/server.ts
|
|
4
4
|
import {
|
|
5
|
-
existsSync as
|
|
5
|
+
existsSync as existsSync24,
|
|
6
6
|
mkdirSync as mkdirSync13,
|
|
7
7
|
statSync as statSync11,
|
|
8
8
|
mkdtempSync as mkdtempSync4,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
// src/services/renderOrchestrator.ts
|
|
27
27
|
import { audioFailureDiagnostics as audioFailureDiagnostics2 } from "@hyperframes/engine";
|
|
28
28
|
import {
|
|
29
|
-
existsSync as
|
|
29
|
+
existsSync as existsSync21,
|
|
30
30
|
mkdirSync as mkdirSync12,
|
|
31
31
|
readdirSync as readdirSync6,
|
|
32
32
|
rmSync as rmSync12,
|
|
@@ -932,7 +932,7 @@ function resolveDeliveryEncoderOptions(config) {
|
|
|
932
932
|
import {
|
|
933
933
|
copyFileSync,
|
|
934
934
|
cpSync,
|
|
935
|
-
existsSync as
|
|
935
|
+
existsSync as existsSync4,
|
|
936
936
|
mkdirSync,
|
|
937
937
|
rmSync,
|
|
938
938
|
symlinkSync,
|
|
@@ -952,7 +952,9 @@ import {
|
|
|
952
952
|
relative as nodeRelative,
|
|
953
953
|
isAbsolute as nodeIsAbsolute
|
|
954
954
|
} from "node:path";
|
|
955
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
955
956
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
957
|
+
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
956
958
|
var DEFAULT_RENDERS_DIR = process.env.PRODUCER_RENDERS_DIR ?? // fileURLToPath (not URL.pathname): on Windows .pathname is "/D:/..." which
|
|
957
959
|
// resolves to a bogus renders dir.
|
|
958
960
|
nodeResolve(fileURLToPath2(import.meta.url), "../../..", "renders");
|
|
@@ -990,6 +992,13 @@ function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS
|
|
|
990
992
|
const absoluteOutputPath = nodeResolve(resolvedOutputPath);
|
|
991
993
|
return { absoluteProjectDir, absoluteOutputPath };
|
|
992
994
|
}
|
|
995
|
+
function resolveCompositionSrc(projectDir, src) {
|
|
996
|
+
const rooted = (value) => nodeResolve(projectDir, value.replace(/^(?:\.?[\\/])+/, ""));
|
|
997
|
+
const decoded = rooted(decodedUrlPath(src));
|
|
998
|
+
if (!isPathInside2(decoded, projectDir)) return null;
|
|
999
|
+
const raw = rooted(src);
|
|
1000
|
+
return !existsSync3(decoded) && isPathInside2(raw, projectDir) && existsSync3(raw) ? raw : decoded;
|
|
1001
|
+
}
|
|
993
1002
|
|
|
994
1003
|
// src/services/render/shared.ts
|
|
995
1004
|
var BROWSER_MEDIA_EPSILON = 1e-4;
|
|
@@ -1193,7 +1202,7 @@ var materializePathModule = {
|
|
|
1193
1202
|
isAbsolute
|
|
1194
1203
|
};
|
|
1195
1204
|
var materializeFileSystem = {
|
|
1196
|
-
existsSync:
|
|
1205
|
+
existsSync: existsSync4,
|
|
1197
1206
|
mkdirSync,
|
|
1198
1207
|
symlinkSync,
|
|
1199
1208
|
cpSync,
|
|
@@ -1495,7 +1504,7 @@ var RenderExecutionContext = class {
|
|
|
1495
1504
|
};
|
|
1496
1505
|
|
|
1497
1506
|
// src/services/render/artifactTransaction.ts
|
|
1498
|
-
import { closeSync as closeSync2, existsSync as
|
|
1507
|
+
import { closeSync as closeSync2, existsSync as existsSync6, fstatSync as fstatSync2, openSync as openSync2, readSync, readdirSync as readdirSync2, rmSync as rmSync3 } from "node:fs";
|
|
1499
1508
|
import { basename as basename3, dirname as dirname4, extname as extname2, join as join5, resolve as resolve4 } from "node:path";
|
|
1500
1509
|
import { HLS_MASTER_PLAYLIST, HLS_VIDEO_PLAYLIST, renameWithRetry } from "@hyperframes/engine";
|
|
1501
1510
|
|
|
@@ -1508,7 +1517,7 @@ import {
|
|
|
1508
1517
|
|
|
1509
1518
|
// src/services/render/renderDirOwner.ts
|
|
1510
1519
|
import {
|
|
1511
|
-
existsSync as
|
|
1520
|
+
existsSync as existsSync5,
|
|
1512
1521
|
lstatSync,
|
|
1513
1522
|
mkdtempSync,
|
|
1514
1523
|
readFileSync as readFileSync2,
|
|
@@ -1583,7 +1592,7 @@ function newestWriteMs(path) {
|
|
|
1583
1592
|
}
|
|
1584
1593
|
function isAbandoned(dir, ownerlessIdleMs, now) {
|
|
1585
1594
|
try {
|
|
1586
|
-
if (!lstatSync(dir).isDirectory() ||
|
|
1595
|
+
if (!lstatSync(dir).isDirectory() || existsSync5(join4(dir, TRANSACTION_BACKUP))) return false;
|
|
1587
1596
|
const owner = ownerState(dir);
|
|
1588
1597
|
if (owner === "gone") return true;
|
|
1589
1598
|
return owner === "none" && ownerlessIdleMs !== void 0 && now - newestWriteMs(dir) >= ownerlessIdleMs;
|
|
@@ -1615,7 +1624,7 @@ function createRenderWorkDir(prefix, outputDir) {
|
|
|
1615
1624
|
|
|
1616
1625
|
// src/services/render/artifactTransaction.ts
|
|
1617
1626
|
var defaultFileSystem = {
|
|
1618
|
-
existsSync:
|
|
1627
|
+
existsSync: existsSync6,
|
|
1619
1628
|
renameSync: (source, destination) => renameWithRetry(source, destination),
|
|
1620
1629
|
rmSync: rmSync3
|
|
1621
1630
|
};
|
|
@@ -1769,7 +1778,7 @@ var ArtifactTransaction = class {
|
|
|
1769
1778
|
async assertDirectoryExpectation(expected, files) {
|
|
1770
1779
|
for (const required of expected.requiredFiles ?? []) {
|
|
1771
1780
|
const path = join5(this.stagingPath, required);
|
|
1772
|
-
if (!
|
|
1781
|
+
if (!existsSync6(path)) {
|
|
1773
1782
|
throw new Error(`Render artifact directory is missing ${required}: ${this.stagingPath}`);
|
|
1774
1783
|
}
|
|
1775
1784
|
assertReadableNonEmptyFile(path);
|
|
@@ -2018,7 +2027,7 @@ function replanAfterFailure(plan, failure) {
|
|
|
2018
2027
|
}
|
|
2019
2028
|
|
|
2020
2029
|
// src/services/render/stages/captureSegmentedStage.ts
|
|
2021
|
-
import { existsSync as
|
|
2030
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, rmSync as rmSync6, statSync as statSync3 } from "node:fs";
|
|
2022
2031
|
import { join as join10 } from "node:path";
|
|
2023
2032
|
import {
|
|
2024
2033
|
captureFrameToBuffer as captureFrameToBuffer2,
|
|
@@ -2315,6 +2324,19 @@ function aggregateBeginFrameReuse(perfs) {
|
|
|
2315
2324
|
if (noDamageFrames + hasDamageFrames === 0) return void 0;
|
|
2316
2325
|
return { noDamageFrames, hasDamageFrames };
|
|
2317
2326
|
}
|
|
2327
|
+
function aggregateMotionBlur(perfs) {
|
|
2328
|
+
const ran = perfs.flatMap((p) => p.motionBlur ?? []).filter((m) => m.tier || m.failedFrames > 0);
|
|
2329
|
+
const first = ran[0];
|
|
2330
|
+
if (!first) return void 0;
|
|
2331
|
+
return {
|
|
2332
|
+
tier: first.tier ?? ran.find((m) => m.tier)?.tier ?? null,
|
|
2333
|
+
reasonCode: first.reasonCode,
|
|
2334
|
+
movers: first.movers,
|
|
2335
|
+
failedFrames: ran.reduce((sum, m) => sum + m.failedFrames, 0),
|
|
2336
|
+
readEvents: ran.some((m) => m.readEvents),
|
|
2337
|
+
fingerprint: ran.find((m) => m.fingerprint)?.fingerprint ?? null
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2318
2340
|
function buildRenderPerfSummary(input) {
|
|
2319
2341
|
return {
|
|
2320
2342
|
renderId: input.job.id,
|
|
@@ -2365,6 +2387,7 @@ function buildRenderPerfSummary(input) {
|
|
|
2365
2387
|
staticDedup: aggregateDedup(input.dedupPerfs),
|
|
2366
2388
|
chromeMemory: aggregateChromeMemory(input.dedupPerfs),
|
|
2367
2389
|
beginFrameReuse: aggregateBeginFrameReuse(input.dedupPerfs),
|
|
2390
|
+
motionBlur: aggregateMotionBlur(input.dedupPerfs),
|
|
2368
2391
|
drawElement: aggregateDrawElement(
|
|
2369
2392
|
input.dedupPerfs,
|
|
2370
2393
|
input.drawElement ?? { selfVerifyFallback: false }
|
|
@@ -3839,6 +3862,21 @@ function raceAgainstStall(promise, deadlineMs, input, signal) {
|
|
|
3839
3862
|
);
|
|
3840
3863
|
});
|
|
3841
3864
|
}
|
|
3865
|
+
function createSessionInitBudget(input) {
|
|
3866
|
+
const initStallMs = Math.max(SESSION_INIT_STALL_MS, resolveCaptureStallTimeoutMs());
|
|
3867
|
+
const setup = createStallClock();
|
|
3868
|
+
return (work, stalled) => raceAgainstStall(
|
|
3869
|
+
work,
|
|
3870
|
+
initStallMs - setup.idle(0),
|
|
3871
|
+
{
|
|
3872
|
+
session: stalled,
|
|
3873
|
+
frameIndex: input.frameIndex,
|
|
3874
|
+
totalFrames: input.totalFrames,
|
|
3875
|
+
stallTimeoutMs: initStallMs
|
|
3876
|
+
},
|
|
3877
|
+
input.abortSignal
|
|
3878
|
+
);
|
|
3879
|
+
}
|
|
3842
3880
|
function createDrainFrameGuard(args) {
|
|
3843
3881
|
const { log, stats, frameTime } = args;
|
|
3844
3882
|
const verifyMinDb = resolveDeVerifyMinDb();
|
|
@@ -4311,14 +4349,11 @@ async function runCaptureStreamingStage(input) {
|
|
|
4311
4349
|
let resumes = 0;
|
|
4312
4350
|
for (; ; ) {
|
|
4313
4351
|
const videoInjector = createRenderVideoFrameInjector();
|
|
4314
|
-
const
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
work,
|
|
4318
|
-
initStallMs - setup.idle(0),
|
|
4319
|
-
{ session: stalled, frameIndex: resume.from, totalFrames, stallTimeoutMs: initStallMs },
|
|
4352
|
+
const withinInitBudget = createSessionInitBudget({
|
|
4353
|
+
frameIndex: resume.from,
|
|
4354
|
+
totalFrames,
|
|
4320
4355
|
abortSignal
|
|
4321
|
-
);
|
|
4356
|
+
});
|
|
4322
4357
|
const openSession = async () => {
|
|
4323
4358
|
const opening = createCaptureSession(
|
|
4324
4359
|
fileServer.url,
|
|
@@ -4596,7 +4631,7 @@ async function captureSegmentToEncoder(ctx, segment) {
|
|
|
4596
4631
|
path: segmentPath,
|
|
4597
4632
|
// Recorded so resume can prove the file on disk is the one that
|
|
4598
4633
|
// finished; absent only in unit tests, whose fake encoder writes none.
|
|
4599
|
-
bytes:
|
|
4634
|
+
bytes: existsSync7(segmentPath) ? statSync3(segmentPath).size : 0
|
|
4600
4635
|
});
|
|
4601
4636
|
return encodeResult;
|
|
4602
4637
|
} finally {
|
|
@@ -4626,14 +4661,29 @@ function defaultSessionFactory(input) {
|
|
|
4626
4661
|
});
|
|
4627
4662
|
probeSession = null;
|
|
4628
4663
|
}
|
|
4629
|
-
const
|
|
4664
|
+
const withinInitBudget = createSessionInitBudget({
|
|
4665
|
+
frameIndex: input.job.framesRendered ?? 0,
|
|
4666
|
+
totalFrames: input.totalFrames,
|
|
4667
|
+
abortSignal: input.abortSignal
|
|
4668
|
+
});
|
|
4669
|
+
const openSession = async () => {
|
|
4670
|
+
const opening = openSegmentedSession(input, injector);
|
|
4671
|
+
try {
|
|
4672
|
+
return await withinInitBudget(opening, OPENING_CAPTURE_SESSION);
|
|
4673
|
+
} catch (error) {
|
|
4674
|
+
void opening.then(closeCaptureSession2, () => {
|
|
4675
|
+
});
|
|
4676
|
+
throw error;
|
|
4677
|
+
}
|
|
4678
|
+
};
|
|
4679
|
+
const session = probeSession ?? await openSession();
|
|
4630
4680
|
if (probeSession) {
|
|
4631
4681
|
openSegmentedSessionReuse(input, probeSession, injector);
|
|
4632
4682
|
probeSession = null;
|
|
4633
4683
|
}
|
|
4634
4684
|
try {
|
|
4635
|
-
if (!session.isInitialized) await initializeSession2(session);
|
|
4636
|
-
await completeDeferredDrawElementInit2(session);
|
|
4685
|
+
if (!session.isInitialized) await withinInitBudget(initializeSession2(session), session);
|
|
4686
|
+
await withinInitBudget(completeDeferredDrawElementInit2(session), session);
|
|
4637
4687
|
} catch (error) {
|
|
4638
4688
|
await closeCaptureSession2(session).catch(() => {
|
|
4639
4689
|
});
|
|
@@ -5565,7 +5615,7 @@ function trackSegmentFramePeak(frames, rootDir, onPeak) {
|
|
|
5565
5615
|
|
|
5566
5616
|
// src/services/render/segmentManifest.ts
|
|
5567
5617
|
import { createHash as createHash2 } from "node:crypto";
|
|
5568
|
-
import { existsSync as
|
|
5618
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync4, statSync as statSync5, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5569
5619
|
import { join as join15 } from "node:path";
|
|
5570
5620
|
import { renameWithRetry as renameWithRetry2 } from "@hyperframes/engine";
|
|
5571
5621
|
var MANIFEST_FILENAME = "segments.json";
|
|
@@ -5613,7 +5663,7 @@ function isManifest(value) {
|
|
|
5613
5663
|
}
|
|
5614
5664
|
function readSegmentManifest(segmentDir) {
|
|
5615
5665
|
const path = join15(segmentDir, MANIFEST_FILENAME);
|
|
5616
|
-
if (!
|
|
5666
|
+
if (!existsSync8(path)) return null;
|
|
5617
5667
|
try {
|
|
5618
5668
|
const parsed = JSON.parse(readFileSync4(path, "utf-8"));
|
|
5619
5669
|
return isManifest(parsed) ? parsed : null;
|
|
@@ -5642,7 +5692,7 @@ async function validateCompletedSegments(manifest, expectedHash, probeFrames) {
|
|
|
5642
5692
|
const ok = /* @__PURE__ */ new Set();
|
|
5643
5693
|
if (manifest.planHash !== expectedHash) return ok;
|
|
5644
5694
|
for (const entry of manifest.completed) {
|
|
5645
|
-
if (!
|
|
5695
|
+
if (!existsSync8(entry.path)) continue;
|
|
5646
5696
|
const size = statSync5(entry.path).size;
|
|
5647
5697
|
if (size <= 0 || size !== entry.bytes) continue;
|
|
5648
5698
|
const frames = await probeFrames(entry.path);
|
|
@@ -5782,7 +5832,7 @@ var ALLOWED_STRING_DATA_KEYS = /* @__PURE__ */ new Set([
|
|
|
5782
5832
|
"stagePhase",
|
|
5783
5833
|
// Full-fidelity fast-capture fallback trigger — the specific reason
|
|
5784
5834
|
// drawElement gated off ("filter:blur", "filter:drop-shadow",
|
|
5785
|
-
// "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", …).
|
|
5835
|
+
// "backdrop-filter", "clip-path", "at_risk_timeline", "swiftshader", "webgl_unavailable", …).
|
|
5786
5836
|
// Populated on the `capture_fallback_profile` checkpoint emitted by
|
|
5787
5837
|
// `fallbackCaptureProfile.ts` when the operator opts into
|
|
5788
5838
|
// `HF_PROFILE_FALLBACK_CAPTURE=true` so downstream metric consumers can
|
|
@@ -6221,17 +6271,17 @@ function countAuthoredTimedClips(html) {
|
|
|
6221
6271
|
}
|
|
6222
6272
|
|
|
6223
6273
|
// src/services/render/stages/compileStage.ts
|
|
6224
|
-
import { existsSync as
|
|
6274
|
+
import { existsSync as existsSync12, writeFileSync as writeFileSync5 } from "node:fs";
|
|
6225
6275
|
import { join as join19 } from "node:path";
|
|
6226
6276
|
import { suggestMatchingPreset } from "@hyperframes/core";
|
|
6227
6277
|
import { extractStandaloneEntryFromIndex, isTemplateEntry } from "@hyperframes/core/compiler";
|
|
6228
6278
|
import { readTextFile as readTextFile2 } from "@hyperframes/parsers/asset-resolution";
|
|
6229
6279
|
|
|
6230
6280
|
// src/services/htmlCompiler.ts
|
|
6231
|
-
import { decodedUrlPath } from "@hyperframes/parsers/asset-paths";
|
|
6281
|
+
import { decodedUrlPath as decodedUrlPath2 } from "@hyperframes/parsers/asset-paths";
|
|
6232
6282
|
import { createHash as createHash6 } from "node:crypto";
|
|
6233
|
-
import { createReadStream as createReadStream2, existsSync as
|
|
6234
|
-
import { join as join18, dirname as dirname8, resolve as resolve7, basename as basename4, relative as relative2 } from "path";
|
|
6283
|
+
import { createReadStream as createReadStream2, existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync7 } from "fs";
|
|
6284
|
+
import { join as join18, dirname as dirname8, resolve as resolve7, basename as basename4, relative as relative2, sep as sep2 } from "path";
|
|
6235
6285
|
import { parseHTML as parseHTML5 } from "linkedom";
|
|
6236
6286
|
import {
|
|
6237
6287
|
COMPOSITION_SOURCE_URL,
|
|
@@ -6477,7 +6527,7 @@ import {
|
|
|
6477
6527
|
|
|
6478
6528
|
// src/services/animatedGifPrep.ts
|
|
6479
6529
|
import { createHash as createHash4 } from "node:crypto";
|
|
6480
|
-
import { copyFileSync as copyFileSync2, existsSync as
|
|
6530
|
+
import { copyFileSync as copyFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync5, rmSync as rmSync9, statSync as statSync6 } from "node:fs";
|
|
6481
6531
|
import { dirname as dirname6, isAbsolute as isAbsolute2, join as join16, resolve as resolve5 } from "node:path";
|
|
6482
6532
|
import { parseHTML as parseHTML3 } from "linkedom";
|
|
6483
6533
|
import { parseAnimatedGifMetadata } from "@hyperframes/core";
|
|
@@ -6516,14 +6566,14 @@ function resolveLocalImageSourcePath(src, options) {
|
|
|
6516
6566
|
const { basePath } = splitUrlSuffix(trimmed);
|
|
6517
6567
|
const normalizedBase = normalizeRelPath(basePath);
|
|
6518
6568
|
const mapped = options.sourceAssets?.get(trimmed) ?? options.sourceAssets?.get(basePath) ?? options.sourceAssets?.get(normalizedBase);
|
|
6519
|
-
if (mapped &&
|
|
6569
|
+
if (mapped && existsSync9(mapped)) return mapped;
|
|
6520
6570
|
if (isHttpUrl(trimmed)) return null;
|
|
6521
6571
|
const projectRelative = basePath.startsWith("/") ? basePath.slice(1) : basePath;
|
|
6522
6572
|
const candidates = [
|
|
6523
6573
|
isAbsolute2(basePath) ? basePath : resolve5(options.projectDir, projectRelative),
|
|
6524
6574
|
resolve5(options.downloadDir, normalizedBase)
|
|
6525
6575
|
];
|
|
6526
|
-
return candidates.find((candidate) =>
|
|
6576
|
+
return candidates.find((candidate) => existsSync9(candidate)) ?? null;
|
|
6527
6577
|
}
|
|
6528
6578
|
function isUsableFile(path) {
|
|
6529
6579
|
try {
|
|
@@ -6634,10 +6684,10 @@ async function runAnimatedGifTranscode(request) {
|
|
|
6634
6684
|
throw result.error ?? new Error(`Animated GIF transcode failed (${result.exitCode}): ${result.stderr.slice(-500)}`);
|
|
6635
6685
|
}
|
|
6636
6686
|
async function ensurePreparedWebm(input) {
|
|
6637
|
-
if (!
|
|
6687
|
+
if (!existsSync9(dirname6(input.cachePath))) {
|
|
6638
6688
|
mkdirSync7(dirname6(input.cachePath), { recursive: true });
|
|
6639
6689
|
}
|
|
6640
|
-
if (!
|
|
6690
|
+
if (!existsSync9(dirname6(input.outputPath))) {
|
|
6641
6691
|
mkdirSync7(dirname6(input.outputPath), { recursive: true });
|
|
6642
6692
|
}
|
|
6643
6693
|
if (!isUsableFile(input.cachePath)) {
|
|
@@ -7112,7 +7162,7 @@ import { createStudioPositionSeekReapplyScript } from "@hyperframes/studio-serve
|
|
|
7112
7162
|
import { getPositionEditsRenderScript } from "@hyperframes/core/runtime/position-edits-render";
|
|
7113
7163
|
|
|
7114
7164
|
// src/services/assetMediaType.ts
|
|
7115
|
-
import { existsSync as
|
|
7165
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
7116
7166
|
import {
|
|
7117
7167
|
fingerprintElementId,
|
|
7118
7168
|
isNotMediaPayload,
|
|
@@ -7185,7 +7235,7 @@ async function preflightCompositionAssetMediaTypes(input) {
|
|
|
7185
7235
|
input.projectDir,
|
|
7186
7236
|
input.compiledDir
|
|
7187
7237
|
);
|
|
7188
|
-
if (!
|
|
7238
|
+
if (!existsSync10(resolvedPath)) continue;
|
|
7189
7239
|
byPath.set(resolvedPath, [...byPath.get(resolvedPath) ?? [], reference]);
|
|
7190
7240
|
}
|
|
7191
7241
|
const mismatches = [];
|
|
@@ -7273,6 +7323,14 @@ Check that each file referenced by data-composition-src contains valid HTML with
|
|
|
7273
7323
|
this.problems = problems;
|
|
7274
7324
|
}
|
|
7275
7325
|
};
|
|
7326
|
+
function normalizeCompositionSrcs(html, projectDir) {
|
|
7327
|
+
return html.replace(/(\bdata-composition-src=)(["'])([^"']*)\2/g, (whole, name, quote, value) => {
|
|
7328
|
+
if (!value.includes("%") && !value.startsWith("/")) return whole;
|
|
7329
|
+
const file = resolveCompositionSrc(projectDir, value);
|
|
7330
|
+
if (file === null) return whole;
|
|
7331
|
+
return `${name}${quote}${relative2(projectDir, file).split(sep2).join("/")}${quote}`;
|
|
7332
|
+
});
|
|
7333
|
+
}
|
|
7276
7334
|
function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */ new Set()) {
|
|
7277
7335
|
const { document: document2 } = parseHTML5(html);
|
|
7278
7336
|
const hosts = [...document2.querySelectorAll("[data-composition-src]")];
|
|
@@ -7281,7 +7339,11 @@ function assertSubCompositionsUsable(html, projectDir, visited = /* @__PURE__ */
|
|
|
7281
7339
|
const srcPath = el.getAttribute("data-composition-src");
|
|
7282
7340
|
if (!srcPath) continue;
|
|
7283
7341
|
if (isUnresolvedAssetPlaceholder(srcPath)) continue;
|
|
7284
|
-
const filePath =
|
|
7342
|
+
const filePath = resolveCompositionSrc(projectDir, srcPath);
|
|
7343
|
+
if (filePath === null) {
|
|
7344
|
+
problems.push({ srcPath, detail: "it is outside the project folder" });
|
|
7345
|
+
continue;
|
|
7346
|
+
}
|
|
7285
7347
|
if (visited.has(filePath)) continue;
|
|
7286
7348
|
const read = readProjectFile(filePath);
|
|
7287
7349
|
if (read.kind === "missing") {
|
|
@@ -7427,7 +7489,7 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
7427
7489
|
let filePath = src;
|
|
7428
7490
|
if (isHttpUrl(src)) {
|
|
7429
7491
|
const remoteDir = join18(downloadDir, REMOTE_MEDIA_SUBDIR);
|
|
7430
|
-
if (!
|
|
7492
|
+
if (!existsSync11(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
|
|
7431
7493
|
try {
|
|
7432
7494
|
filePath = await downloadToTemp(src, remoteDir, void 0, void 0, void 0, {
|
|
7433
7495
|
onTelemetry: logRemoteDownloadTelemetry
|
|
@@ -7436,11 +7498,11 @@ async function resolveMediaDuration(src, mediaStart, playbackRate, baseDir, down
|
|
|
7436
7498
|
return { duration: null, resolvedPath: src };
|
|
7437
7499
|
}
|
|
7438
7500
|
} else {
|
|
7439
|
-
filePath =
|
|
7501
|
+
filePath = decodedUrlPath2(src);
|
|
7440
7502
|
if (!filePath.startsWith("/")) filePath = join18(baseDir, filePath);
|
|
7441
7503
|
filePath = urlByAbsentLocalPath?.get(filePath) ?? filePath;
|
|
7442
7504
|
}
|
|
7443
|
-
if (!isHttpUrl(filePath) && !
|
|
7505
|
+
if (!isHttpUrl(filePath) && !existsSync11(filePath)) {
|
|
7444
7506
|
return { duration: null, resolvedPath: filePath };
|
|
7445
7507
|
}
|
|
7446
7508
|
const withSrcContext = (error) => {
|
|
@@ -7581,7 +7643,8 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
7581
7643
|
for (const el of compEls) {
|
|
7582
7644
|
const srcPath = el.getAttribute("data-composition-src");
|
|
7583
7645
|
if (!srcPath) continue;
|
|
7584
|
-
const filePath =
|
|
7646
|
+
const filePath = resolveCompositionSrc(projectDir, srcPath);
|
|
7647
|
+
if (filePath === null) continue;
|
|
7585
7648
|
if (visited.has(filePath)) {
|
|
7586
7649
|
continue;
|
|
7587
7650
|
}
|
|
@@ -7589,7 +7652,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, visited = /*
|
|
|
7589
7652
|
if (read.kind !== "file") {
|
|
7590
7653
|
continue;
|
|
7591
7654
|
}
|
|
7592
|
-
const rawSubHtml = read.text;
|
|
7655
|
+
const rawSubHtml = normalizeCompositionSrcs(read.text, projectDir);
|
|
7593
7656
|
const nestedVisited = new Set(visited);
|
|
7594
7657
|
nestedVisited.add(filePath);
|
|
7595
7658
|
workItems.push({ srcPath, filePath, rawSubHtml, nestedVisited });
|
|
@@ -7777,8 +7840,9 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
|
|
|
7777
7840
|
resolveHtml: (srcPath) => {
|
|
7778
7841
|
let compHtml = subCompositions.get(srcPath) || null;
|
|
7779
7842
|
if (!compHtml) {
|
|
7780
|
-
const
|
|
7781
|
-
|
|
7843
|
+
const filePath = resolveCompositionSrc(projectDir, srcPath);
|
|
7844
|
+
const read = filePath ? readProjectFile(filePath) : null;
|
|
7845
|
+
if (read?.kind === "file") {
|
|
7782
7846
|
compHtml = rewriteUnresolvableGsapToCdn(read.text, projectDir, srcPath);
|
|
7783
7847
|
}
|
|
7784
7848
|
}
|
|
@@ -7787,7 +7851,7 @@ function inlineSubCompositions(html, subCompositions, projectDir, variableOverri
|
|
|
7787
7851
|
parseHtml: (htmlStr) => parseHTML5(htmlStr).document,
|
|
7788
7852
|
// Mirrors the preview bundler: a sub-composition's SIBLING assets resolve
|
|
7789
7853
|
// against its own directory, project-root refs stay as authored.
|
|
7790
|
-
assetExists: (path) =>
|
|
7854
|
+
assetExists: (path) => existsSync11(resolve7(projectDir, path)),
|
|
7791
7855
|
// Preserve the authored root wrapper as a child of the host, matching
|
|
7792
7856
|
// the preview bundler's shape (htmlBundler.ts's prepareFlattenedInnerRoot,
|
|
7793
7857
|
// which the runtime compositionLoader mirrors with its own copy for the
|
|
@@ -7978,7 +8042,7 @@ function collectExternalAssets(html, projectDir) {
|
|
|
7978
8042
|
if (isPathInside2(absPath, absProjectDir)) {
|
|
7979
8043
|
return null;
|
|
7980
8044
|
}
|
|
7981
|
-
if (!
|
|
8045
|
+
if (!existsSync11(absPath)) return null;
|
|
7982
8046
|
const safeKey = toExternalAssetKey(absPath);
|
|
7983
8047
|
externalAssets.set(safeKey, absPath);
|
|
7984
8048
|
return safeKey;
|
|
@@ -8028,7 +8092,7 @@ var REMOTE_SOURCE_TAG_RE = /<source\b[^>]*?\bsrc\s*=\s*["'](https?:\/\/[^"']+)["
|
|
|
8028
8092
|
var REMOTE_IMG_TAG_RE = /<img\b[^>]*?(?<![\w-])src\s*=\s*["'](https?:\/\/[^"']+)["'][^>]*>/gi;
|
|
8029
8093
|
async function downloadAndRewriteUrls(urlSet, html, remoteDir, warnLabel, logLabel, extraRewrite) {
|
|
8030
8094
|
if (urlSet.size === 0) return { html, remoteMediaAssets: /* @__PURE__ */ new Map() };
|
|
8031
|
-
if (!
|
|
8095
|
+
if (!existsSync11(remoteDir)) mkdirSync9(remoteDir, { recursive: true });
|
|
8032
8096
|
const urlToLocal = /* @__PURE__ */ new Map();
|
|
8033
8097
|
await Promise.all(
|
|
8034
8098
|
[...urlSet].map(async (url) => {
|
|
@@ -8210,14 +8274,14 @@ function projectStylesheetFontFamilies(html, projectDir) {
|
|
|
8210
8274
|
for (const [tag] of html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*>/gi)) {
|
|
8211
8275
|
const href = /\bhref=["']([^"']+)["']/i.exec(tag)?.[1];
|
|
8212
8276
|
if (!href || /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(href)) continue;
|
|
8213
|
-
const sheet = resolve7(projectDir,
|
|
8214
|
-
if (!isPathInside2(sheet, projectDir) || !
|
|
8277
|
+
const sheet = resolve7(projectDir, decodedUrlPath2(href).replace(/^(?:\.?\/)+/, ""));
|
|
8278
|
+
if (!isPathInside2(sheet, projectDir) || !existsSync11(sheet)) continue;
|
|
8215
8279
|
for (const face of extractFontFaceBlocks(readFileSync7(sheet, "utf8"))) {
|
|
8216
8280
|
const family = /font-family\s*:\s*([^;}]+)/i.exec(face)?.[1]?.trim().replace(/^['"]|['"]$/g, "");
|
|
8217
8281
|
const shipsFile = [...face.matchAll(LOCAL_FONTFACE_URL_RE)].some(([, url = ""]) => {
|
|
8218
|
-
const path =
|
|
8282
|
+
const path = decodedUrlPath2(url);
|
|
8219
8283
|
const file = path.startsWith("/") ? resolve7(projectDir, `.${path}`) : resolve7(dirname8(sheet), path);
|
|
8220
|
-
return isPathInside2(file, projectDir) &&
|
|
8284
|
+
return isPathInside2(file, projectDir) && existsSync11(file);
|
|
8221
8285
|
});
|
|
8222
8286
|
if (family && shipsFile) families.push(family);
|
|
8223
8287
|
}
|
|
@@ -8352,7 +8416,7 @@ function renderGsapDist() {
|
|
|
8352
8416
|
}
|
|
8353
8417
|
function rewriteUnresolvableGsapToCdn(html, projectDir, compSrcPath) {
|
|
8354
8418
|
let gsapDist;
|
|
8355
|
-
const exists = (path) =>
|
|
8419
|
+
const exists = (path) => existsSync11(resolve7(projectDir, path));
|
|
8356
8420
|
return html.replace(
|
|
8357
8421
|
/(<script\b[^>]*\bsrc=["'])([^"']*gsap[^"']*\/dist\/([^"']+))(["'][^>]*>)/gi,
|
|
8358
8422
|
(full, prefix, src, file, suffix) => {
|
|
@@ -8369,7 +8433,7 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
8369
8433
|
const entryPath = relative2(projectDir, htmlPath).replace(/\\/g, "/");
|
|
8370
8434
|
if (!entryPath.includes("/")) return html;
|
|
8371
8435
|
const { document: document2 } = parseHTML5(html);
|
|
8372
|
-
const assetExists = (path) =>
|
|
8436
|
+
const assetExists = (path) => existsSync11(resolve7(projectDir, path));
|
|
8373
8437
|
rewriteAssetPaths(
|
|
8374
8438
|
document2.querySelectorAll("[src], [href], [srcset]"),
|
|
8375
8439
|
entryPath,
|
|
@@ -8391,7 +8455,10 @@ function rebaseDirectEntryAssetPaths(html, projectDir, htmlPath) {
|
|
|
8391
8455
|
}
|
|
8392
8456
|
async function compileForRender(projectDir, htmlPath, downloadDir, options = {}) {
|
|
8393
8457
|
const entryHtml = rebaseDirectEntryAssetPaths(readTextFile(htmlPath), projectDir, htmlPath);
|
|
8394
|
-
const rawHtml =
|
|
8458
|
+
const rawHtml = normalizeCompositionSrcs(
|
|
8459
|
+
rewriteUnresolvableGsapToCdn(entryHtml, projectDir),
|
|
8460
|
+
projectDir
|
|
8461
|
+
);
|
|
8395
8462
|
assertSubCompositionsUsable(rawHtml, projectDir);
|
|
8396
8463
|
const probes = /* @__PURE__ */ new Map();
|
|
8397
8464
|
const { html: compiledHtml, unresolvedCompositions } = await compileHtmlFile(
|
|
@@ -8936,7 +9003,7 @@ function resolveEntryHtmlPath(input) {
|
|
|
8936
9003
|
const rawEntry = readTextFile2(htmlPath);
|
|
8937
9004
|
if (entryFile === "index.html" || !isTemplateEntry(rawEntry)) return htmlPath;
|
|
8938
9005
|
const projectIndexPath = join19(projectDir, "index.html");
|
|
8939
|
-
if (!
|
|
9006
|
+
if (!existsSync12(projectIndexPath)) {
|
|
8940
9007
|
throw new Error(
|
|
8941
9008
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
8942
9009
|
);
|
|
@@ -9770,7 +9837,7 @@ function validateRenderDuration(input) {
|
|
|
9770
9837
|
}
|
|
9771
9838
|
|
|
9772
9839
|
// src/services/render/stages/extractVideosStage.ts
|
|
9773
|
-
import { existsSync as
|
|
9840
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
9774
9841
|
import { join as join21 } from "node:path";
|
|
9775
9842
|
import {
|
|
9776
9843
|
classifyVideoExtractionError,
|
|
@@ -10040,7 +10107,7 @@ async function runExtractVideosStage(input) {
|
|
|
10040
10107
|
const probeFailures = await Promise.all(
|
|
10041
10108
|
composition.videos.map(async (v) => {
|
|
10042
10109
|
const videoPath = resolveProjectRelativeSrc3(v.src, projectDir, compiledDir);
|
|
10043
|
-
if (!
|
|
10110
|
+
if (!existsSync13(videoPath)) return null;
|
|
10044
10111
|
try {
|
|
10045
10112
|
const attempted = extractionPolicy.maxTransientRetries === 0 ? { result: await extractMediaMetadata3(videoPath), retries: 0 } : await runVideoExtractionWithRetry2(() => extractMediaMetadata3(videoPath), {
|
|
10046
10113
|
signal: abortSignal,
|
|
@@ -10078,7 +10145,7 @@ async function runExtractVideosStage(input) {
|
|
|
10078
10145
|
const probed = await Promise.all(
|
|
10079
10146
|
composition.images.map(async (img) => {
|
|
10080
10147
|
const imgPath = resolveProjectRelativeSrc3(img.src, projectDir, compiledDir);
|
|
10081
|
-
if (!
|
|
10148
|
+
if (!existsSync13(imgPath)) return null;
|
|
10082
10149
|
const colorSpace = await probeColorSpaceSafely(imgPath, log);
|
|
10083
10150
|
if (isHdrColorSpace2(colorSpace)) {
|
|
10084
10151
|
nativeHdrImageIds.add(img.id);
|
|
@@ -10198,7 +10265,7 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
10198
10265
|
videos.map(async (video) => {
|
|
10199
10266
|
const path = isHttpUrl2(video.src) ? video.src : resolveProjectRelativeSrc3(video.src, projectDir, compiledDir);
|
|
10200
10267
|
const readFrom = urlByAbsentLocalPath?.get(path) ?? path;
|
|
10201
|
-
if (!isHttpUrl2(readFrom) && !
|
|
10268
|
+
if (!isHttpUrl2(readFrom) && !existsSync13(readFrom)) return null;
|
|
10202
10269
|
const metadata = await extractMediaMetadata3(readFrom);
|
|
10203
10270
|
const window2 = await resolveFinalFrameExtractionWindow2(
|
|
10204
10271
|
readFrom,
|
|
@@ -10218,7 +10285,7 @@ async function appendVideoAudioFromProbe(input) {
|
|
|
10218
10285
|
}
|
|
10219
10286
|
|
|
10220
10287
|
// src/services/render/videoLayerPassthroughPlan.ts
|
|
10221
|
-
import { existsSync as
|
|
10288
|
+
import { existsSync as existsSync14 } from "node:fs";
|
|
10222
10289
|
import {
|
|
10223
10290
|
buildVideoLayerInput,
|
|
10224
10291
|
isHttpUrl as isHttpUrl3,
|
|
@@ -10286,7 +10353,7 @@ async function planVideoLayerPassthrough(input) {
|
|
|
10286
10353
|
const { video } = q;
|
|
10287
10354
|
if (isHttpUrl3(video.src)) return { reason: "remote_source" };
|
|
10288
10355
|
const path = resolveProjectRelativeSrc4(video.src, input.projectDir, input.compiledDir);
|
|
10289
|
-
if (!
|
|
10356
|
+
if (!existsSync14(path)) return { reason: "source_missing" };
|
|
10290
10357
|
const layout = await probeVideoLayout(input.page, video.id, input.canvas, input.duration);
|
|
10291
10358
|
if ("reason" in layout) return layout;
|
|
10292
10359
|
const layer = await buildVideoLayerInput(
|
|
@@ -10301,7 +10368,7 @@ async function planVideoLayerPassthrough(input) {
|
|
|
10301
10368
|
}
|
|
10302
10369
|
|
|
10303
10370
|
// src/services/render/stages/audioStage.ts
|
|
10304
|
-
import { existsSync as
|
|
10371
|
+
import { existsSync as existsSync16 } from "node:fs";
|
|
10305
10372
|
import { join as join24 } from "node:path";
|
|
10306
10373
|
import {
|
|
10307
10374
|
MIXED_AUDIO_FILENAME,
|
|
@@ -10312,7 +10379,7 @@ import {
|
|
|
10312
10379
|
} from "@hyperframes/engine";
|
|
10313
10380
|
|
|
10314
10381
|
// src/services/render/audioDeliveryLimit.ts
|
|
10315
|
-
import { existsSync as
|
|
10382
|
+
import { existsSync as existsSync15 } from "node:fs";
|
|
10316
10383
|
import { dirname as dirname10, join as join23 } from "node:path";
|
|
10317
10384
|
import {
|
|
10318
10385
|
applyAudioFxChain,
|
|
@@ -10821,7 +10888,7 @@ async function limitDeliveryPeaks(input, deps = {
|
|
|
10821
10888
|
applyChain: applyAudioFxChain,
|
|
10822
10889
|
encode: encodeAac2,
|
|
10823
10890
|
rename: renameWithRetry6,
|
|
10824
|
-
sourceExists:
|
|
10891
|
+
sourceExists: existsSync15
|
|
10825
10892
|
}) {
|
|
10826
10893
|
const { audioPath, sourcePath, signal, timeoutMs } = input;
|
|
10827
10894
|
if (!deps.sourceExists(sourcePath)) return { success: true };
|
|
@@ -10916,7 +10983,7 @@ async function runAudioStageScoped(input) {
|
|
|
10916
10983
|
);
|
|
10917
10984
|
audioLoweredDb = guard.loweredDb;
|
|
10918
10985
|
input.log?.info("[audio] phases", { ...result.audioPhaseMs, guard: Date.now() - guardStart });
|
|
10919
|
-
if (input.keepDeliverySource && !guard.sourceStale &&
|
|
10986
|
+
if (input.keepDeliverySource && !guard.sourceStale && existsSync16(pcmSourcePath)) {
|
|
10920
10987
|
deliverySourcePath = pcmSourcePath;
|
|
10921
10988
|
}
|
|
10922
10989
|
}
|
|
@@ -11044,7 +11111,7 @@ async function mixAudioForDelivery(input, duration, pcmSourcePath) {
|
|
|
11044
11111
|
}
|
|
11045
11112
|
|
|
11046
11113
|
// src/services/render/stages/captureStage.ts
|
|
11047
|
-
import { existsSync as
|
|
11114
|
+
import { existsSync as existsSync17, readdirSync as readdirSync4, statfsSync as statfsSync2 } from "node:fs";
|
|
11048
11115
|
import { join as join25 } from "node:path";
|
|
11049
11116
|
import {
|
|
11050
11117
|
classifyCaptureFailure as classifyCaptureFailure2,
|
|
@@ -11115,7 +11182,7 @@ function diskCaptureShortfallError(needBytes, freeBytes, framesDir, basis, remed
|
|
|
11115
11182
|
}
|
|
11116
11183
|
function measureCaptureFrameBytes(workDir, framesDir) {
|
|
11117
11184
|
let total = sampleDirectoryBytes(framesDir);
|
|
11118
|
-
if (!
|
|
11185
|
+
if (!existsSync17(workDir)) return total;
|
|
11119
11186
|
for (const attempt of readdirSync4(workDir)) {
|
|
11120
11187
|
if (/^retry-\d+-batch-\d+-worker-\d+$/.test(attempt)) {
|
|
11121
11188
|
total += sampleDirectoryBytes(join25(workDir, attempt));
|
|
@@ -11300,7 +11367,11 @@ async function runSequentialDiskCapture(input, captureOptions, captureCfg, check
|
|
|
11300
11367
|
prepareCaptureSessionForReuse3(session, framesDir, videoInjector);
|
|
11301
11368
|
probeSession = null;
|
|
11302
11369
|
}
|
|
11303
|
-
await ensureSessionReady(session
|
|
11370
|
+
await ensureSessionReady(session, {
|
|
11371
|
+
frameIndex: resumeFrom,
|
|
11372
|
+
totalFrames,
|
|
11373
|
+
abortSignal: input.abortSignal
|
|
11374
|
+
});
|
|
11304
11375
|
assertAttemptActive();
|
|
11305
11376
|
lastBrowserConsole = session.browserConsoleBuffer;
|
|
11306
11377
|
await captureSessionFrames(session, {
|
|
@@ -11352,13 +11423,13 @@ function planTransientResume(error, ctx) {
|
|
|
11352
11423
|
if (!firstMissing) return null;
|
|
11353
11424
|
return isTransientCaptureRetryEligible(failure.kind, missing, ctx.retriesUsed) ? firstMissing.startFrame : null;
|
|
11354
11425
|
}
|
|
11355
|
-
async function ensureSessionReady(session) {
|
|
11426
|
+
async function ensureSessionReady(session, budget) {
|
|
11356
11427
|
if (!session.isInitialized) {
|
|
11357
11428
|
await initializeSession5(session);
|
|
11358
11429
|
return;
|
|
11359
11430
|
}
|
|
11360
11431
|
if (process.env.PRODUCER_EXPERIMENTAL_FAST_CAPTURE === "true") {
|
|
11361
|
-
await completeDeferredDrawElementInit3(session);
|
|
11432
|
+
await createSessionInitBudget(budget)(completeDeferredDrawElementInit3(session), session);
|
|
11362
11433
|
}
|
|
11363
11434
|
}
|
|
11364
11435
|
async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resumeFrom, checkDiskProjection }) {
|
|
@@ -11418,7 +11489,7 @@ async function captureSessionFrames(session, { input, rangeStart, rangeEnd, resu
|
|
|
11418
11489
|
}
|
|
11419
11490
|
|
|
11420
11491
|
// src/services/render/stages/captureHdrStage.ts
|
|
11421
|
-
import { existsSync as
|
|
11492
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync10 } from "node:fs";
|
|
11422
11493
|
import { join as join29 } from "node:path";
|
|
11423
11494
|
import {
|
|
11424
11495
|
closeCaptureSession as closeCaptureSession7,
|
|
@@ -11664,7 +11735,7 @@ import { Worker } from "node:worker_threads";
|
|
|
11664
11735
|
import { fileURLToPath as fileURLToPath3, pathToFileURL } from "node:url";
|
|
11665
11736
|
import { dirname as dirname11, join as join27 } from "node:path";
|
|
11666
11737
|
import { createRequire } from "node:module";
|
|
11667
|
-
import { existsSync as
|
|
11738
|
+
import { existsSync as existsSync18 } from "node:fs";
|
|
11668
11739
|
import { cpus as cpus2 } from "node:os";
|
|
11669
11740
|
function resolveWorkerEntry(explicit) {
|
|
11670
11741
|
if (explicit && explicit.length > 0) {
|
|
@@ -11677,7 +11748,7 @@ function resolveWorkerEntry(explicit) {
|
|
|
11677
11748
|
}
|
|
11678
11749
|
const moduleDir = dirname11(fileURLToPath3(import.meta.url));
|
|
11679
11750
|
const jsPath = join27(moduleDir, "shaderTransitionWorker.js");
|
|
11680
|
-
if (
|
|
11751
|
+
if (existsSync18(jsPath)) return { path: jsPath, isTs: false };
|
|
11681
11752
|
const tsPath = join27(moduleDir, "shaderTransitionWorker.ts");
|
|
11682
11753
|
return { path: tsPath, isTs: true };
|
|
11683
11754
|
}
|
|
@@ -12238,7 +12309,7 @@ async function runCaptureHdrStage(input) {
|
|
|
12238
12309
|
}
|
|
12239
12310
|
const debugDumpEnabled = process.env.KEEP_TEMP === "1";
|
|
12240
12311
|
const debugDumpDir = debugDumpEnabled ? join29(framesDir, "debug-composite") : null;
|
|
12241
|
-
if (debugDumpDir && !
|
|
12312
|
+
if (debugDumpDir && !existsSync19(debugDumpDir)) {
|
|
12242
12313
|
mkdirSync10(debugDumpDir, { recursive: true });
|
|
12243
12314
|
}
|
|
12244
12315
|
const compositeTransfer = resolveCompositeTransfer(hasHdrContent, effectiveHdr);
|
|
@@ -12395,7 +12466,7 @@ async function runCaptureHdrStage(input) {
|
|
|
12395
12466
|
// src/services/render/stages/encodeStage.ts
|
|
12396
12467
|
import {
|
|
12397
12468
|
copyFileSync as copyFileSync4,
|
|
12398
|
-
existsSync as
|
|
12469
|
+
existsSync as existsSync20,
|
|
12399
12470
|
mkdirSync as mkdirSync11,
|
|
12400
12471
|
readdirSync as readdirSync5,
|
|
12401
12472
|
readFileSync as readFileSync8,
|
|
@@ -12529,7 +12600,7 @@ async function encodeGifFromDir(framesDir, framePattern, outputPath, input) {
|
|
|
12529
12600
|
const failure = await reencodeWithClearedWholeFrames(argsInput, run, startTime);
|
|
12530
12601
|
if (failure) return failure;
|
|
12531
12602
|
}
|
|
12532
|
-
const fileSize =
|
|
12603
|
+
const fileSize = existsSync20(outputPath) ? statSync8(outputPath).size : 0;
|
|
12533
12604
|
return {
|
|
12534
12605
|
success: true,
|
|
12535
12606
|
outputPath,
|
|
@@ -12690,7 +12761,7 @@ async function runEncodeStage(input) {
|
|
|
12690
12761
|
const stage5Start = Date.now();
|
|
12691
12762
|
if (isPngSequence) {
|
|
12692
12763
|
const encoded = startEncodeProgress(job, "Writing PNG sequence", onProgress);
|
|
12693
|
-
if (!
|
|
12764
|
+
if (!existsSync20(outputPath)) mkdirSync11(outputPath, { recursive: true });
|
|
12694
12765
|
const captured = readdirSync5(framesDir).filter((name) => name.endsWith(".png")).sort();
|
|
12695
12766
|
if (captured.length === 0) {
|
|
12696
12767
|
throw new Error(
|
|
@@ -12701,7 +12772,7 @@ async function runEncodeStage(input) {
|
|
|
12701
12772
|
const dst = join30(outputPath, formatExportFrameName(i, "png"));
|
|
12702
12773
|
copyFileSync4(join30(framesDir, name), dst);
|
|
12703
12774
|
});
|
|
12704
|
-
if (hasAudio && audioOutputPath &&
|
|
12775
|
+
if (hasAudio && audioOutputPath && existsSync20(audioOutputPath)) {
|
|
12705
12776
|
copyFileSync4(audioOutputPath, join30(outputPath, MIXED_AUDIO_FILENAME2));
|
|
12706
12777
|
log.info(
|
|
12707
12778
|
`[Render] png-sequence: ${MIXED_AUDIO_FILENAME2} sidecar written to ${outputPath}/${MIXED_AUDIO_FILENAME2}`
|
|
@@ -13090,7 +13161,7 @@ function findMissingFrameRanges(totalFrames, framesDir, frameExt) {
|
|
|
13090
13161
|
let rangeStart = null;
|
|
13091
13162
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
13092
13163
|
const framePath = join31(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
13093
|
-
const missing = !
|
|
13164
|
+
const missing = !existsSync21(framePath) || statSync9(framePath).size <= 8;
|
|
13094
13165
|
if (missing && rangeStart === null) {
|
|
13095
13166
|
rangeStart = frameIndex;
|
|
13096
13167
|
} else if (!missing && rangeStart !== null) {
|
|
@@ -13215,7 +13286,7 @@ function countCapturedFrames(totalFrames, framesDir, frameExt) {
|
|
|
13215
13286
|
let captured = 0;
|
|
13216
13287
|
for (let frameIndex = 0; frameIndex < totalFrames; frameIndex++) {
|
|
13217
13288
|
const framePath = join31(framesDir, formatCaptureFrameName(frameIndex, frameExt));
|
|
13218
|
-
if (
|
|
13289
|
+
if (existsSync21(framePath)) captured++;
|
|
13219
13290
|
}
|
|
13220
13291
|
return captured;
|
|
13221
13292
|
}
|
|
@@ -13880,7 +13951,7 @@ async function executeRenderJob(job, projectDir, outputPath, progressSink, abort
|
|
|
13880
13951
|
validateDeliveryEncoderConfig(job.config);
|
|
13881
13952
|
const debugDir = resolveRenderDebugDir();
|
|
13882
13953
|
const outputDir = dirname13(outputPath);
|
|
13883
|
-
if (!
|
|
13954
|
+
if (!existsSync21(outputDir)) mkdirSync12(outputDir, { recursive: true });
|
|
13884
13955
|
const workDir = job.config.debug ? join31(debugDir, job.id) : createRenderWorkDir(resolveRenderWorkDirPrefix(outputPath, job.id), outputDir);
|
|
13885
13956
|
const pipelineStart = Date.now();
|
|
13886
13957
|
const baseLog = job.config.logger ?? defaultLogger;
|
|
@@ -14022,7 +14093,7 @@ async function executeRenderPipeline(input) {
|
|
|
14022
14093
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
14023
14094
|
assertNotAborted();
|
|
14024
14095
|
assertConfiguredFfmpegBinariesExist();
|
|
14025
|
-
if (!
|
|
14096
|
+
if (!existsSync21(workDir)) mkdirSync12(workDir, { recursive: true });
|
|
14026
14097
|
if (job.config.debug) {
|
|
14027
14098
|
log.info("[Render] Debug artifacts enabled", { workDir, logPath });
|
|
14028
14099
|
}
|
|
@@ -14052,7 +14123,7 @@ async function executeRenderPipeline(input) {
|
|
|
14052
14123
|
});
|
|
14053
14124
|
const entryFile = job.config.entryFile || "index.html";
|
|
14054
14125
|
const htmlPath = join31(projectDir, entryFile);
|
|
14055
|
-
if (!
|
|
14126
|
+
if (!existsSync21(htmlPath)) {
|
|
14056
14127
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
14057
14128
|
}
|
|
14058
14129
|
assertNotAborted();
|
|
@@ -14367,9 +14438,11 @@ async function executeRenderPipeline(input) {
|
|
|
14367
14438
|
);
|
|
14368
14439
|
pendingAudio = audioStage.catch(() => void 0);
|
|
14369
14440
|
const stage4Start = Date.now();
|
|
14370
|
-
|
|
14441
|
+
const EFFECT_SCAN_STAGE = "Checking effects";
|
|
14442
|
+
const startingFrameCapture = () => updateJobStatus(job, "rendering", "Starting frame capture", 25, onProgress, {
|
|
14371
14443
|
code: "start_browsers"
|
|
14372
14444
|
});
|
|
14445
|
+
startingFrameCapture();
|
|
14373
14446
|
if (!fileServer) {
|
|
14374
14447
|
const fileServerStart = observability.stageStart("file_server", { reused: false });
|
|
14375
14448
|
try {
|
|
@@ -14394,7 +14467,7 @@ async function executeRenderPipeline(input) {
|
|
|
14394
14467
|
throw new Error("File server failed to initialize before frame capture");
|
|
14395
14468
|
}
|
|
14396
14469
|
const framesDir = join31(workDir, "captured-frames");
|
|
14397
|
-
if (!
|
|
14470
|
+
if (!existsSync21(framesDir)) mkdirSync12(framesDir, { recursive: true });
|
|
14398
14471
|
updateJobStatus(job, "rendering", "Checking browser GPU", 25, onProgress, {
|
|
14399
14472
|
code: "start_browsers"
|
|
14400
14473
|
});
|
|
@@ -14402,6 +14475,7 @@ async function executeRenderPipeline(input) {
|
|
|
14402
14475
|
chromePath: resolveHeadlessShellPath(cfg),
|
|
14403
14476
|
browserTimeout: cfg.browserTimeout
|
|
14404
14477
|
});
|
|
14478
|
+
startingFrameCapture();
|
|
14405
14479
|
captureForceScreenshot = applyConcreteGpuScreenshotClamp(
|
|
14406
14480
|
captureForceScreenshot,
|
|
14407
14481
|
resolvedBrowserGpuMode,
|
|
@@ -14450,10 +14524,13 @@ async function executeRenderPipeline(input) {
|
|
|
14450
14524
|
onEffectScanProgress: (scanned, total) => updateJobStatus(
|
|
14451
14525
|
job,
|
|
14452
14526
|
"rendering",
|
|
14453
|
-
|
|
14527
|
+
`${EFFECT_SCAN_STAGE}, ${scanned} of ${total} frames`,
|
|
14454
14528
|
25,
|
|
14455
14529
|
onProgress
|
|
14456
14530
|
),
|
|
14531
|
+
onEffectScanEnd: () => {
|
|
14532
|
+
if (job.currentStage.startsWith(EFFECT_SCAN_STAGE)) startingFrameCapture();
|
|
14533
|
+
},
|
|
14457
14534
|
requiresWebGpu: compositionRequiresWebGpu2(compiled.html),
|
|
14458
14535
|
// Live route for Chrome memory (spec §5 Phase −1). The aggregate route
|
|
14459
14536
|
// through CapturePerfSummary only exists on success; a mid-capture
|
|
@@ -14721,6 +14798,7 @@ async function executeRenderPipeline(input) {
|
|
|
14721
14798
|
} finally {
|
|
14722
14799
|
calibrationFrames?.release();
|
|
14723
14800
|
}
|
|
14801
|
+
startingFrameCapture();
|
|
14724
14802
|
captureCalibration = outcome.calibration;
|
|
14725
14803
|
calibrationEvaluateTimedOut = outcome.calibrationEvaluateTimedOut;
|
|
14726
14804
|
captureForceScreenshot = outcome.forceScreenshot;
|
|
@@ -15779,7 +15857,7 @@ async function executeRenderPipeline(input) {
|
|
|
15779
15857
|
})
|
|
15780
15858
|
);
|
|
15781
15859
|
const totalElapsed = Date.now() - pipelineStart;
|
|
15782
|
-
const tmpPeakBytes =
|
|
15860
|
+
const tmpPeakBytes = existsSync21(workDir) ? sampleDirectoryBytes(workDir) : 0;
|
|
15783
15861
|
recordTransientRetryObservability();
|
|
15784
15862
|
observability.checkpoint("pipeline", "artifact validated", { totalElapsedMs: totalElapsed });
|
|
15785
15863
|
const observabilitySummary = observability.summary({
|
|
@@ -15860,7 +15938,7 @@ async function executeRenderPipeline(input) {
|
|
|
15860
15938
|
}
|
|
15861
15939
|
}
|
|
15862
15940
|
if (job.config.debug) {
|
|
15863
|
-
if (!isPngSequence && !isHls &&
|
|
15941
|
+
if (!isPngSequence && !isHls && existsSync21(stagedOutputPath)) {
|
|
15864
15942
|
const debugOutput = join31(workDir, `output${videoExt}`);
|
|
15865
15943
|
copyFileSync5(stagedOutputPath, debugOutput);
|
|
15866
15944
|
}
|
|
@@ -15972,7 +16050,7 @@ async function executeRenderPipeline(input) {
|
|
|
15972
16050
|
|
|
15973
16051
|
// src/services/hyperframeLint.ts
|
|
15974
16052
|
import {
|
|
15975
|
-
existsSync as
|
|
16053
|
+
existsSync as existsSync22,
|
|
15976
16054
|
readFileSync as readFileSync9,
|
|
15977
16055
|
statSync as statSync10,
|
|
15978
16056
|
openSync as openSync5,
|
|
@@ -15980,7 +16058,7 @@ import {
|
|
|
15980
16058
|
closeSync as closeSync5,
|
|
15981
16059
|
constants as constants3
|
|
15982
16060
|
} from "node:fs";
|
|
15983
|
-
import { resolve as resolve9, join as join32, relative as relative3, isAbsolute as isAbsolute3, sep as
|
|
16061
|
+
import { resolve as resolve9, join as join32, relative as relative3, isAbsolute as isAbsolute3, sep as sep3 } from "node:path";
|
|
15984
16062
|
import { lintHyperframeHtml } from "@hyperframes/lint";
|
|
15985
16063
|
function isStringRecord(value) {
|
|
15986
16064
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -16026,7 +16104,7 @@ function readEntryHtml(filePath) {
|
|
|
16026
16104
|
}
|
|
16027
16105
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
16028
16106
|
const absProjectDir = resolve9(projectDir);
|
|
16029
|
-
if (!
|
|
16107
|
+
if (!existsSync22(absProjectDir) || !statSync10(absProjectDir).isDirectory()) {
|
|
16030
16108
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
16031
16109
|
}
|
|
16032
16110
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -16035,7 +16113,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
16035
16113
|
for (const entryFile of entryCandidates) {
|
|
16036
16114
|
const absoluteEntryPath = resolve9(absProjectDir, entryFile);
|
|
16037
16115
|
const entryRelativePath = relative3(absProjectDir, absoluteEntryPath);
|
|
16038
|
-
if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${
|
|
16116
|
+
if (entryRelativePath === ".." || entryRelativePath.startsWith(`..${sep3}`) || isAbsolute3(entryRelativePath)) {
|
|
16039
16117
|
return { error: `Entry file must stay inside project directory: ${entryFile}` };
|
|
16040
16118
|
}
|
|
16041
16119
|
const html = readEntryHtml(absoluteEntryPath);
|
|
@@ -16095,7 +16173,7 @@ async function runHyperframeLint(prepared) {
|
|
|
16095
16173
|
import { Worker as Worker2 } from "node:worker_threads";
|
|
16096
16174
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
16097
16175
|
import { dirname as dirname14, join as join33 } from "node:path";
|
|
16098
|
-
import { existsSync as
|
|
16176
|
+
import { existsSync as existsSync23 } from "node:fs";
|
|
16099
16177
|
var DEFAULT_HEALTH_PORT = 9848;
|
|
16100
16178
|
async function startHealthWorker(options = {}) {
|
|
16101
16179
|
const log = options.logger ?? defaultLogger2();
|
|
@@ -16172,7 +16250,7 @@ function resolveWorkerEntry2() {
|
|
|
16172
16250
|
const here = dirname14(fileURLToPath5(import.meta.url));
|
|
16173
16251
|
const candidates = [join33(here, "healthWorkerThread.js"), join33(here, "healthWorkerThread.ts")];
|
|
16174
16252
|
for (const candidate of candidates) {
|
|
16175
|
-
if (
|
|
16253
|
+
if (existsSync23(candidate)) return candidate;
|
|
16176
16254
|
}
|
|
16177
16255
|
return void 0;
|
|
16178
16256
|
}
|
|
@@ -16212,7 +16290,7 @@ import {
|
|
|
16212
16290
|
createReferenceBackgroundRecorder as createReferenceBackgroundRecorder2
|
|
16213
16291
|
} from "@hyperframes/engine";
|
|
16214
16292
|
import { audioFailureDiagnostics as audioFailureDiagnostics3 } from "@hyperframes/engine";
|
|
16215
|
-
import { extname as extname4, isAbsolute as isAbsolute4, join as join34, relative as relative4, sep as
|
|
16293
|
+
import { extname as extname4, isAbsolute as isAbsolute4, join as join34, relative as relative4, sep as sep4 } from "node:path";
|
|
16216
16294
|
import {
|
|
16217
16295
|
fpsToNumber as fpsToNumber9,
|
|
16218
16296
|
HF_COLOR_GRADING_ATTR as HF_COLOR_GRADING_ATTR2
|
|
@@ -16720,7 +16798,7 @@ function resolvePreparedRenderOutput(prepared, rendersDir, log) {
|
|
|
16720
16798
|
const { input, cleanupProjectDir } = prepared;
|
|
16721
16799
|
const absoluteOutputPath = resolveOutputPath(input.projectDir, input.outputPath, rendersDir, log);
|
|
16722
16800
|
const outputDir = dirname15(absoluteOutputPath);
|
|
16723
|
-
if (!
|
|
16801
|
+
if (!existsSync24(outputDir)) mkdirSync13(outputDir, { recursive: true });
|
|
16724
16802
|
return { input, cleanupProjectDir, absoluteOutputPath };
|
|
16725
16803
|
}
|
|
16726
16804
|
function validateRenderOverrides(body) {
|
|
@@ -16761,11 +16839,11 @@ function prepareProjectDirectory(projectDir, options) {
|
|
|
16761
16839
|
const candidate = nonEmptyString(projectDir);
|
|
16762
16840
|
if (!candidate) return null;
|
|
16763
16841
|
const absProjectDir = resolve10(candidate);
|
|
16764
|
-
if (!
|
|
16842
|
+
if (!existsSync24(absProjectDir) || !statSync11(absProjectDir).isDirectory()) {
|
|
16765
16843
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
16766
16844
|
}
|
|
16767
16845
|
const entry = options.entryFile || "index.html";
|
|
16768
|
-
if (!
|
|
16846
|
+
if (!existsSync24(resolve10(absProjectDir, entry))) {
|
|
16769
16847
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
16770
16848
|
}
|
|
16771
16849
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -16857,7 +16935,7 @@ function cleanupTempDir(dir, log) {
|
|
|
16857
16935
|
}
|
|
16858
16936
|
}
|
|
16859
16937
|
function outputFileSize(path) {
|
|
16860
|
-
return
|
|
16938
|
+
return existsSync24(path) ? statSync11(path).size : 0;
|
|
16861
16939
|
}
|
|
16862
16940
|
function createBlockingProgressReporter(log, requestId) {
|
|
16863
16941
|
let lastLoggedPct = -10;
|
|
@@ -17161,7 +17239,7 @@ function createRenderHandlers(options = {}) {
|
|
|
17161
17239
|
if (!artifact) {
|
|
17162
17240
|
return c.json({ success: false, error: "Output artifact not found or expired" }, 404);
|
|
17163
17241
|
}
|
|
17164
|
-
if (!
|
|
17242
|
+
if (!existsSync24(artifact.path)) {
|
|
17165
17243
|
store.delete(token);
|
|
17166
17244
|
return c.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
17167
17245
|
}
|