hyperframes 0.4.5 → 0.4.7
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/cli.js
CHANGED
|
@@ -54,7 +54,7 @@ var VERSION;
|
|
|
54
54
|
var init_version = __esm({
|
|
55
55
|
"src/version.ts"() {
|
|
56
56
|
"use strict";
|
|
57
|
-
VERSION = true ? "0.4.
|
|
57
|
+
VERSION = true ? "0.4.7" : "0.0.0-dev";
|
|
58
58
|
}
|
|
59
59
|
});
|
|
60
60
|
|
|
@@ -5571,12 +5571,14 @@ function getModelUrl(model) {
|
|
|
5571
5571
|
}
|
|
5572
5572
|
function whichBinary(name) {
|
|
5573
5573
|
try {
|
|
5574
|
-
const
|
|
5574
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
5575
|
+
const output = execFileSync(cmd, [name], {
|
|
5575
5576
|
encoding: "utf-8",
|
|
5576
5577
|
stdio: ["pipe", "pipe", "pipe"],
|
|
5577
5578
|
timeout: 5e3
|
|
5578
|
-
})
|
|
5579
|
-
|
|
5579
|
+
});
|
|
5580
|
+
const first = output.split(/\r?\n/).map((s2) => s2.trim()).find(Boolean);
|
|
5581
|
+
return first || void 0;
|
|
5580
5582
|
} catch {
|
|
5581
5583
|
return void 0;
|
|
5582
5584
|
}
|
|
@@ -6544,19 +6546,22 @@ import http from "http";
|
|
|
6544
6546
|
import { execFile } from "child_process";
|
|
6545
6547
|
import { promisify } from "util";
|
|
6546
6548
|
import { resolve as resolve6 } from "path";
|
|
6547
|
-
function isPortAvailableOnHost(port, host) {
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
})
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
resolve35(true);
|
|
6557
|
-
});
|
|
6549
|
+
async function isPortAvailableOnHost(port, host) {
|
|
6550
|
+
const probe = net.createServer();
|
|
6551
|
+
probe.unref();
|
|
6552
|
+
const bindError = await new Promise((settle) => {
|
|
6553
|
+
const handleError = (err) => settle(err);
|
|
6554
|
+
probe.once("error", handleError);
|
|
6555
|
+
probe.listen({ port, host }, () => {
|
|
6556
|
+
probe.removeListener("error", handleError);
|
|
6557
|
+
settle(null);
|
|
6558
6558
|
});
|
|
6559
6559
|
});
|
|
6560
|
+
if (bindError !== null) {
|
|
6561
|
+
return bindError.code !== "EADDRINUSE";
|
|
6562
|
+
}
|
|
6563
|
+
await new Promise((done) => probe.close(() => done()));
|
|
6564
|
+
return true;
|
|
6560
6565
|
}
|
|
6561
6566
|
async function testPortOnAllHosts(port, probe = isPortAvailableOnHost) {
|
|
6562
6567
|
for (const host of PORT_PROBE_HOSTS) {
|
|
@@ -20272,12 +20277,14 @@ function setBrowserPath(path2) {
|
|
|
20272
20277
|
}
|
|
20273
20278
|
function whichBinary2(name) {
|
|
20274
20279
|
try {
|
|
20275
|
-
const
|
|
20280
|
+
const cmd = process.platform === "win32" ? `where ${name}` : `which ${name}`;
|
|
20281
|
+
const output = execSync(cmd, {
|
|
20276
20282
|
encoding: "utf-8",
|
|
20277
20283
|
stdio: ["pipe", "pipe", "pipe"],
|
|
20278
20284
|
timeout: 5e3
|
|
20279
|
-
})
|
|
20280
|
-
|
|
20285
|
+
});
|
|
20286
|
+
const first = output.split(/\r?\n/).map((s2) => s2.trim()).find(Boolean);
|
|
20287
|
+
return first || void 0;
|
|
20281
20288
|
} catch {
|
|
20282
20289
|
return void 0;
|
|
20283
20290
|
}
|
|
@@ -20410,6 +20417,12 @@ function resolveConfig(overrides) {
|
|
|
20410
20417
|
"FFMPEG_STREAMING_TIMEOUT_MS",
|
|
20411
20418
|
DEFAULT_CONFIG2.ffmpegStreamingTimeout
|
|
20412
20419
|
),
|
|
20420
|
+
hdr: (() => {
|
|
20421
|
+
const raw = env("PRODUCER_HDR_TRANSFER");
|
|
20422
|
+
if (raw === "hlg" || raw === "pq") return { transfer: raw };
|
|
20423
|
+
return void 0;
|
|
20424
|
+
})(),
|
|
20425
|
+
hdrAutoDetect: envBool("PRODUCER_HDR_AUTO_DETECT", DEFAULT_CONFIG2.hdrAutoDetect),
|
|
20413
20426
|
audioGain: envNum("PRODUCER_AUDIO_GAIN", DEFAULT_CONFIG2.audioGain),
|
|
20414
20427
|
frameDataUriCacheLimit: Math.max(
|
|
20415
20428
|
32,
|
|
@@ -20457,6 +20470,8 @@ var init_config2 = __esm({
|
|
|
20457
20470
|
ffmpegEncodeTimeout: 6e5,
|
|
20458
20471
|
ffmpegProcessTimeout: 3e5,
|
|
20459
20472
|
ffmpegStreamingTimeout: 6e5,
|
|
20473
|
+
hdr: false,
|
|
20474
|
+
hdrAutoDetect: true,
|
|
20460
20475
|
audioGain: 1.35,
|
|
20461
20476
|
frameDataUriCacheLimit: 256,
|
|
20462
20477
|
playerReadyTimeout: 45e3,
|
|
@@ -20623,7 +20638,8 @@ function buildChromeArgs(options, config) {
|
|
|
20623
20638
|
"--font-render-hinting=none",
|
|
20624
20639
|
"--force-color-profile=srgb",
|
|
20625
20640
|
`--window-size=${options.width},${options.height}`,
|
|
20626
|
-
//
|
|
20641
|
+
// Prevent Chrome from throttling background tabs/timers — critical when the
|
|
20642
|
+
// page is offscreen during headless capture
|
|
20627
20643
|
"--disable-background-timer-throttling",
|
|
20628
20644
|
"--disable-backgrounding-occluded-windows",
|
|
20629
20645
|
"--disable-renderer-backgrounding",
|
|
@@ -20763,6 +20779,44 @@ async function pageScreenshotCapture(page, options) {
|
|
|
20763
20779
|
});
|
|
20764
20780
|
return Buffer.from(result.data, "base64");
|
|
20765
20781
|
}
|
|
20782
|
+
async function captureScreenshotWithAlpha(page, width, height) {
|
|
20783
|
+
const client = await getCdpSession(page);
|
|
20784
|
+
await client.send("Emulation.setDefaultBackgroundColorOverride", {
|
|
20785
|
+
color: { r: 0, g: 0, b: 0, a: 0 }
|
|
20786
|
+
});
|
|
20787
|
+
try {
|
|
20788
|
+
const result = await client.send("Page.captureScreenshot", {
|
|
20789
|
+
format: "png",
|
|
20790
|
+
fromSurface: true,
|
|
20791
|
+
captureBeyondViewport: false,
|
|
20792
|
+
optimizeForSpeed: false,
|
|
20793
|
+
// `true` uses a zero-alpha-aware fast path that crushes real alpha values — observed empirically, CDP docs don't spell it out
|
|
20794
|
+
clip: { x: 0, y: 0, width, height, scale: 1 }
|
|
20795
|
+
});
|
|
20796
|
+
return Buffer.from(result.data, "base64");
|
|
20797
|
+
} finally {
|
|
20798
|
+
await client.send("Emulation.setDefaultBackgroundColorOverride", {}).catch(() => {
|
|
20799
|
+
});
|
|
20800
|
+
}
|
|
20801
|
+
}
|
|
20802
|
+
async function initTransparentBackground(page) {
|
|
20803
|
+
const client = await getCdpSession(page);
|
|
20804
|
+
await client.send("Emulation.setDefaultBackgroundColorOverride", {
|
|
20805
|
+
color: { r: 0, g: 0, b: 0, a: 0 }
|
|
20806
|
+
});
|
|
20807
|
+
}
|
|
20808
|
+
async function captureAlphaPng(page, width, height) {
|
|
20809
|
+
const client = await getCdpSession(page);
|
|
20810
|
+
const result = await client.send("Page.captureScreenshot", {
|
|
20811
|
+
format: "png",
|
|
20812
|
+
fromSurface: true,
|
|
20813
|
+
captureBeyondViewport: false,
|
|
20814
|
+
optimizeForSpeed: false,
|
|
20815
|
+
// must be false to preserve alpha
|
|
20816
|
+
clip: { x: 0, y: 0, width, height, scale: 1 }
|
|
20817
|
+
});
|
|
20818
|
+
return Buffer.from(result.data, "base64");
|
|
20819
|
+
}
|
|
20766
20820
|
async function injectVideoFramesBatch(page, updates) {
|
|
20767
20821
|
if (updates.length === 0) return;
|
|
20768
20822
|
await page.evaluate(
|
|
@@ -20784,16 +20838,7 @@ async function injectVideoFramesBatch(page, updates) {
|
|
|
20784
20838
|
video.parentNode?.insertBefore(img, video.nextSibling);
|
|
20785
20839
|
}
|
|
20786
20840
|
if (!img) continue;
|
|
20787
|
-
|
|
20788
|
-
img.style.position = computedStyle.position;
|
|
20789
|
-
img.style.width = computedStyle.width;
|
|
20790
|
-
img.style.height = computedStyle.height;
|
|
20791
|
-
img.style.top = computedStyle.top;
|
|
20792
|
-
img.style.left = computedStyle.left;
|
|
20793
|
-
img.style.right = computedStyle.right;
|
|
20794
|
-
img.style.bottom = computedStyle.bottom;
|
|
20795
|
-
img.style.inset = computedStyle.inset;
|
|
20796
|
-
} else {
|
|
20841
|
+
{
|
|
20797
20842
|
const videoRect = video.getBoundingClientRect();
|
|
20798
20843
|
const offsetLeft = Number.isFinite(video.offsetLeft) ? video.offsetLeft : 0;
|
|
20799
20844
|
const offsetTop = Number.isFinite(video.offsetTop) ? video.offsetTop : 0;
|
|
@@ -20844,14 +20889,22 @@ async function syncVideoFrameVisibility(page, activeVideoIds) {
|
|
|
20844
20889
|
const active = new Set(ids);
|
|
20845
20890
|
const videos = Array.from(document.querySelectorAll("video[data-start]"));
|
|
20846
20891
|
for (const video of videos) {
|
|
20847
|
-
if (active.has(video.id)) continue;
|
|
20848
|
-
video.style.removeProperty("display");
|
|
20849
|
-
video.style.setProperty("visibility", "hidden", "important");
|
|
20850
|
-
video.style.setProperty("opacity", "0", "important");
|
|
20851
|
-
video.style.setProperty("pointer-events", "none", "important");
|
|
20852
20892
|
const img = video.nextElementSibling;
|
|
20853
|
-
|
|
20854
|
-
|
|
20893
|
+
const hasImg = img && img.classList.contains("__render_frame__");
|
|
20894
|
+
if (active.has(video.id)) {
|
|
20895
|
+
video.style.setProperty("visibility", "hidden", "important");
|
|
20896
|
+
video.style.setProperty("pointer-events", "none", "important");
|
|
20897
|
+
if (hasImg) {
|
|
20898
|
+
img.style.visibility = "visible";
|
|
20899
|
+
}
|
|
20900
|
+
} else {
|
|
20901
|
+
video.style.removeProperty("display");
|
|
20902
|
+
video.style.setProperty("visibility", "hidden", "important");
|
|
20903
|
+
video.style.setProperty("opacity", "0", "important");
|
|
20904
|
+
video.style.setProperty("pointer-events", "none", "important");
|
|
20905
|
+
if (hasImg) {
|
|
20906
|
+
img.style.visibility = "hidden";
|
|
20907
|
+
}
|
|
20855
20908
|
}
|
|
20856
20909
|
}
|
|
20857
20910
|
}, activeVideoIds);
|
|
@@ -20938,6 +20991,15 @@ function isFontResourceError(type, text, locationUrl) {
|
|
|
20938
20991
|
`${locationUrl} ${text}`
|
|
20939
20992
|
);
|
|
20940
20993
|
}
|
|
20994
|
+
async function pollPageExpression(page, expression, timeoutMs, intervalMs = 100) {
|
|
20995
|
+
const deadline = Date.now() + timeoutMs;
|
|
20996
|
+
while (Date.now() < deadline) {
|
|
20997
|
+
const ready = Boolean(await page.evaluate(expression));
|
|
20998
|
+
if (ready) return true;
|
|
20999
|
+
await new Promise((resolve35) => setTimeout(resolve35, intervalMs));
|
|
21000
|
+
}
|
|
21001
|
+
return Boolean(await page.evaluate(expression));
|
|
21002
|
+
}
|
|
20941
21003
|
async function initializeSession(session) {
|
|
20942
21004
|
const { page, serverUrl } = session;
|
|
20943
21005
|
page.on("console", (msg) => {
|
|
@@ -20967,14 +21029,26 @@ async function initializeSession(session) {
|
|
|
20967
21029
|
if (session.captureMode === "screenshot") {
|
|
20968
21030
|
await page.goto(url, { waitUntil: "domcontentloaded", timeout: 6e4 });
|
|
20969
21031
|
const pageReadyTimeout2 = session.config?.playerReadyTimeout ?? DEFAULT_CONFIG2.playerReadyTimeout;
|
|
20970
|
-
await
|
|
21032
|
+
const pageReady2 = await pollPageExpression(
|
|
21033
|
+
page,
|
|
20971
21034
|
`!!(window.__hf && typeof window.__hf.seek === "function" && window.__hf.duration > 0)`,
|
|
20972
|
-
|
|
21035
|
+
pageReadyTimeout2
|
|
20973
21036
|
);
|
|
20974
|
-
|
|
21037
|
+
if (!pageReady2) {
|
|
21038
|
+
throw new Error(
|
|
21039
|
+
`[FrameCapture] window.__hf not ready after ${pageReadyTimeout2}ms. Page must expose window.__hf = { duration, seek }.`
|
|
21040
|
+
);
|
|
21041
|
+
}
|
|
21042
|
+
const videosReady = await pollPageExpression(
|
|
21043
|
+
page,
|
|
20975
21044
|
`document.querySelectorAll("video").length === 0 || Array.from(document.querySelectorAll("video")).every(v => v.readyState >= 1)`,
|
|
20976
|
-
|
|
21045
|
+
pageReadyTimeout2
|
|
20977
21046
|
);
|
|
21047
|
+
if (!videosReady) {
|
|
21048
|
+
throw new Error(
|
|
21049
|
+
`[FrameCapture] video metadata not ready after ${pageReadyTimeout2}ms. Video elements must load metadata before capture starts.`
|
|
21050
|
+
);
|
|
21051
|
+
}
|
|
20978
21052
|
await page.evaluate(`document.fonts?.ready`);
|
|
20979
21053
|
session.isInitialized = true;
|
|
20980
21054
|
return;
|
|
@@ -21314,7 +21388,7 @@ var init_runFfmpeg = __esm({
|
|
|
21314
21388
|
import { spawn as spawn4 } from "child_process";
|
|
21315
21389
|
import { copyFileSync, existsSync as existsSync15, mkdirSync as mkdirSync9, readdirSync as readdirSync7, statSync as statSync4, writeFileSync as writeFileSync8 } from "fs";
|
|
21316
21390
|
import { join as join19, dirname as dirname5 } from "path";
|
|
21317
|
-
function getEncoderPreset(quality, format = "mp4") {
|
|
21391
|
+
function getEncoderPreset(quality, format = "mp4", hdr) {
|
|
21318
21392
|
const base = ENCODER_PRESETS[quality];
|
|
21319
21393
|
if (format === "webm") {
|
|
21320
21394
|
return {
|
|
@@ -21332,6 +21406,15 @@ function getEncoderPreset(quality, format = "mp4") {
|
|
|
21332
21406
|
pixelFormat: "yuva444p10le"
|
|
21333
21407
|
};
|
|
21334
21408
|
}
|
|
21409
|
+
if (hdr) {
|
|
21410
|
+
return {
|
|
21411
|
+
preset: base.preset === "ultrafast" ? "fast" : base.preset,
|
|
21412
|
+
quality: base.quality,
|
|
21413
|
+
codec: "h265",
|
|
21414
|
+
pixelFormat: "yuv420p10le",
|
|
21415
|
+
hdr
|
|
21416
|
+
};
|
|
21417
|
+
}
|
|
21335
21418
|
return { ...base, pixelFormat: "yuv420p" };
|
|
21336
21419
|
}
|
|
21337
21420
|
function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
@@ -21389,6 +21472,9 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
|
21389
21472
|
args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}`);
|
|
21390
21473
|
}
|
|
21391
21474
|
}
|
|
21475
|
+
if (codec === "h265") {
|
|
21476
|
+
args.push("-tag:v", "hvc1");
|
|
21477
|
+
}
|
|
21392
21478
|
} else if (codec === "vp9") {
|
|
21393
21479
|
args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
|
|
21394
21480
|
args.push("-deadline", preset === "ultrafast" ? "realtime" : "good");
|
|
@@ -21706,35 +21792,91 @@ var init_chunkEncoder = __esm({
|
|
|
21706
21792
|
}
|
|
21707
21793
|
});
|
|
21708
21794
|
|
|
21795
|
+
// ../engine/src/utils/hdr.ts
|
|
21796
|
+
function isHdrColorSpace(cs) {
|
|
21797
|
+
if (!cs) return false;
|
|
21798
|
+
return cs.colorPrimaries.includes("bt2020") || cs.colorSpace.includes("bt2020") || cs.colorTransfer === "smpte2084" || cs.colorTransfer === "arib-std-b67";
|
|
21799
|
+
}
|
|
21800
|
+
function detectTransfer(cs) {
|
|
21801
|
+
if (cs?.colorTransfer === "smpte2084") return "pq";
|
|
21802
|
+
return "hlg";
|
|
21803
|
+
}
|
|
21804
|
+
function getHdrEncoderColorParams(transfer, mastering = DEFAULT_HDR10_MASTERING) {
|
|
21805
|
+
const colorTrc = transfer === "pq" ? "smpte2084" : "arib-std-b67";
|
|
21806
|
+
const tagging = `colorprim=bt2020:transfer=${colorTrc}:colormatrix=bt2020nc`;
|
|
21807
|
+
const metadata = `master-display=${mastering.masterDisplay}:max-cll=${mastering.maxCll}`;
|
|
21808
|
+
return {
|
|
21809
|
+
colorPrimaries: "bt2020",
|
|
21810
|
+
colorTrc,
|
|
21811
|
+
colorspace: "bt2020nc",
|
|
21812
|
+
pixelFormat: "yuv420p10le",
|
|
21813
|
+
x265ColorParams: `${tagging}:${metadata}`,
|
|
21814
|
+
mastering
|
|
21815
|
+
};
|
|
21816
|
+
}
|
|
21817
|
+
function analyzeCompositionHdr(colorSpaces) {
|
|
21818
|
+
let hasPq = false;
|
|
21819
|
+
let hasHdr = false;
|
|
21820
|
+
for (const cs of colorSpaces) {
|
|
21821
|
+
if (!isHdrColorSpace(cs)) continue;
|
|
21822
|
+
hasHdr = true;
|
|
21823
|
+
if (cs?.colorTransfer === "smpte2084") hasPq = true;
|
|
21824
|
+
}
|
|
21825
|
+
if (!hasHdr) return { hasHdr: false, dominantTransfer: null };
|
|
21826
|
+
const dominantTransfer = hasPq ? "pq" : "hlg";
|
|
21827
|
+
return { hasHdr: true, dominantTransfer };
|
|
21828
|
+
}
|
|
21829
|
+
var DEFAULT_HDR10_MASTERING;
|
|
21830
|
+
var init_hdr = __esm({
|
|
21831
|
+
"../engine/src/utils/hdr.ts"() {
|
|
21832
|
+
"use strict";
|
|
21833
|
+
DEFAULT_HDR10_MASTERING = {
|
|
21834
|
+
masterDisplay: "G(13250,34500)B(7500,3000)R(34000,16000)WP(15635,16450)L(10000000,1)",
|
|
21835
|
+
maxCll: "1000,400"
|
|
21836
|
+
};
|
|
21837
|
+
}
|
|
21838
|
+
});
|
|
21839
|
+
|
|
21709
21840
|
// ../engine/src/services/streamingEncoder.ts
|
|
21710
21841
|
import { spawn as spawn5 } from "child_process";
|
|
21711
21842
|
import { existsSync as existsSync16, mkdirSync as mkdirSync10, statSync as statSync5 } from "fs";
|
|
21712
21843
|
import { dirname as dirname6 } from "path";
|
|
21713
21844
|
function createFrameReorderBuffer(startFrame, endFrame) {
|
|
21714
|
-
let
|
|
21715
|
-
|
|
21716
|
-
const
|
|
21717
|
-
|
|
21718
|
-
|
|
21719
|
-
|
|
21720
|
-
|
|
21721
|
-
|
|
21845
|
+
let cursor = startFrame;
|
|
21846
|
+
const pending = /* @__PURE__ */ new Map();
|
|
21847
|
+
const enqueueAt = (frame, resolve35) => {
|
|
21848
|
+
const list = pending.get(frame);
|
|
21849
|
+
if (list === void 0) {
|
|
21850
|
+
pending.set(frame, [resolve35]);
|
|
21851
|
+
} else {
|
|
21852
|
+
list.push(resolve35);
|
|
21722
21853
|
}
|
|
21723
21854
|
};
|
|
21724
|
-
|
|
21725
|
-
|
|
21726
|
-
|
|
21727
|
-
|
|
21728
|
-
|
|
21729
|
-
|
|
21730
|
-
|
|
21731
|
-
|
|
21732
|
-
|
|
21733
|
-
|
|
21734
|
-
|
|
21735
|
-
|
|
21736
|
-
|
|
21855
|
+
const flushAt = (frame) => {
|
|
21856
|
+
const list = pending.get(frame);
|
|
21857
|
+
if (list === void 0) return;
|
|
21858
|
+
pending.delete(frame);
|
|
21859
|
+
for (const resolve35 of list) resolve35();
|
|
21860
|
+
};
|
|
21861
|
+
const waitForFrame = (frame) => new Promise((resolve35) => {
|
|
21862
|
+
if (frame === cursor) {
|
|
21863
|
+
resolve35();
|
|
21864
|
+
return;
|
|
21865
|
+
}
|
|
21866
|
+
enqueueAt(frame, resolve35);
|
|
21867
|
+
});
|
|
21868
|
+
const advanceTo = (frame) => {
|
|
21869
|
+
cursor = frame;
|
|
21870
|
+
flushAt(frame);
|
|
21737
21871
|
};
|
|
21872
|
+
const waitForAllDone = () => new Promise((resolve35) => {
|
|
21873
|
+
if (cursor >= endFrame) {
|
|
21874
|
+
resolve35();
|
|
21875
|
+
return;
|
|
21876
|
+
}
|
|
21877
|
+
enqueueAt(endFrame, resolve35);
|
|
21878
|
+
});
|
|
21879
|
+
return { waitForFrame, advanceTo, waitForAllDone };
|
|
21738
21880
|
}
|
|
21739
21881
|
function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
21740
21882
|
const {
|
|
@@ -21747,19 +21889,36 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
21747
21889
|
useGpu = false,
|
|
21748
21890
|
imageFormat = "jpeg"
|
|
21749
21891
|
} = options;
|
|
21750
|
-
const
|
|
21751
|
-
|
|
21752
|
-
|
|
21753
|
-
"
|
|
21754
|
-
|
|
21755
|
-
|
|
21756
|
-
|
|
21757
|
-
|
|
21758
|
-
|
|
21759
|
-
|
|
21760
|
-
|
|
21761
|
-
|
|
21762
|
-
|
|
21892
|
+
const args = [];
|
|
21893
|
+
if (options.rawInputFormat) {
|
|
21894
|
+
const hdrTransfer = options.hdr?.transfer;
|
|
21895
|
+
const inputColorTrc = hdrTransfer === "pq" ? "smpte2084" : hdrTransfer === "hlg" ? "arib-std-b67" : void 0;
|
|
21896
|
+
args.push(
|
|
21897
|
+
"-f",
|
|
21898
|
+
"rawvideo",
|
|
21899
|
+
"-pix_fmt",
|
|
21900
|
+
options.rawInputFormat,
|
|
21901
|
+
"-s",
|
|
21902
|
+
`${options.width}x${options.height}`,
|
|
21903
|
+
"-framerate",
|
|
21904
|
+
String(fps)
|
|
21905
|
+
);
|
|
21906
|
+
if (inputColorTrc) {
|
|
21907
|
+
args.push(
|
|
21908
|
+
"-color_primaries",
|
|
21909
|
+
"bt2020",
|
|
21910
|
+
"-color_trc",
|
|
21911
|
+
inputColorTrc,
|
|
21912
|
+
"-colorspace",
|
|
21913
|
+
"bt2020nc"
|
|
21914
|
+
);
|
|
21915
|
+
}
|
|
21916
|
+
args.push("-i", "-");
|
|
21917
|
+
} else {
|
|
21918
|
+
const inputCodec = imageFormat === "png" ? "png" : "mjpeg";
|
|
21919
|
+
args.push("-f", "image2pipe", "-vcodec", inputCodec, "-framerate", String(fps), "-i", "-");
|
|
21920
|
+
}
|
|
21921
|
+
args.push("-r", String(fps));
|
|
21763
21922
|
const shouldUseGpu = useGpu && gpuEncoder !== null;
|
|
21764
21923
|
if (codec === "h264" || codec === "h265") {
|
|
21765
21924
|
if (shouldUseGpu) {
|
|
@@ -21797,12 +21956,15 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
21797
21956
|
if (bitrate) args.push("-b:v", bitrate);
|
|
21798
21957
|
else args.push("-crf", String(quality));
|
|
21799
21958
|
const xParamsFlag = codec === "h264" ? "-x264-params" : "-x265-params";
|
|
21800
|
-
const colorParams = "colorprim=bt709:transfer=bt709:colormatrix=bt709";
|
|
21959
|
+
const colorParams = options.rawInputFormat && options.hdr ? getHdrEncoderColorParams(options.hdr.transfer).x265ColorParams : "colorprim=bt709:transfer=bt709:colormatrix=bt709";
|
|
21801
21960
|
if (preset === "ultrafast") {
|
|
21802
21961
|
args.push(xParamsFlag, `aq-mode=3:${colorParams}`);
|
|
21803
21962
|
} else {
|
|
21804
21963
|
args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}`);
|
|
21805
21964
|
}
|
|
21965
|
+
if (codec === "h265") {
|
|
21966
|
+
args.push("-tag:v", "hvc1");
|
|
21967
|
+
}
|
|
21806
21968
|
}
|
|
21807
21969
|
} else if (codec === "vp9") {
|
|
21808
21970
|
args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
|
|
@@ -21818,17 +21980,31 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
21818
21980
|
return [...args, "-y", outputPath];
|
|
21819
21981
|
}
|
|
21820
21982
|
if (codec === "h264" || codec === "h265") {
|
|
21821
|
-
|
|
21822
|
-
|
|
21823
|
-
|
|
21824
|
-
|
|
21825
|
-
|
|
21826
|
-
|
|
21827
|
-
|
|
21828
|
-
|
|
21829
|
-
|
|
21830
|
-
|
|
21831
|
-
|
|
21983
|
+
if (options.rawInputFormat && options.hdr) {
|
|
21984
|
+
args.push(
|
|
21985
|
+
"-colorspace:v",
|
|
21986
|
+
"bt2020nc",
|
|
21987
|
+
"-color_primaries:v",
|
|
21988
|
+
"bt2020",
|
|
21989
|
+
"-color_trc:v",
|
|
21990
|
+
options.hdr.transfer === "pq" ? "smpte2084" : "arib-std-b67",
|
|
21991
|
+
"-color_range",
|
|
21992
|
+
"tv"
|
|
21993
|
+
);
|
|
21994
|
+
} else {
|
|
21995
|
+
args.push(
|
|
21996
|
+
"-colorspace:v",
|
|
21997
|
+
"bt709",
|
|
21998
|
+
"-color_primaries:v",
|
|
21999
|
+
"bt709",
|
|
22000
|
+
"-color_trc:v",
|
|
22001
|
+
"bt709",
|
|
22002
|
+
"-color_range",
|
|
22003
|
+
"tv"
|
|
22004
|
+
);
|
|
22005
|
+
}
|
|
22006
|
+
if (options.rawInputFormat) {
|
|
22007
|
+
} else if (gpuEncoder === "vaapi") {
|
|
21832
22008
|
const vfIdx = args.indexOf("-vf");
|
|
21833
22009
|
if (vfIdx !== -1) {
|
|
21834
22010
|
args[vfIdx + 1] = `scale=in_range=pc:out_range=tv,${args[vfIdx + 1]}`;
|
|
@@ -21898,14 +22074,16 @@ Process error: ${err.message}`;
|
|
|
21898
22074
|
if (exitStatus !== "running" || !ffmpeg.stdin || ffmpeg.stdin.destroyed) {
|
|
21899
22075
|
return false;
|
|
21900
22076
|
}
|
|
21901
|
-
|
|
22077
|
+
const copy = Buffer.from(buffer);
|
|
22078
|
+
return ffmpeg.stdin.write(copy);
|
|
21902
22079
|
},
|
|
21903
22080
|
close: async () => {
|
|
21904
22081
|
clearTimeout(timer);
|
|
21905
22082
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
21906
|
-
|
|
22083
|
+
const stdin = ffmpeg.stdin;
|
|
22084
|
+
if (stdin && !stdin.destroyed) {
|
|
21907
22085
|
await new Promise((resolve35) => {
|
|
21908
|
-
|
|
22086
|
+
stdin.end(() => resolve35());
|
|
21909
22087
|
});
|
|
21910
22088
|
}
|
|
21911
22089
|
await exitPromise;
|
|
@@ -21937,6 +22115,7 @@ var init_streamingEncoder = __esm({
|
|
|
21937
22115
|
"../engine/src/services/streamingEncoder.ts"() {
|
|
21938
22116
|
"use strict";
|
|
21939
22117
|
init_gpuEncoder();
|
|
22118
|
+
init_hdr();
|
|
21940
22119
|
init_config2();
|
|
21941
22120
|
}
|
|
21942
22121
|
});
|
|
@@ -22009,6 +22188,10 @@ async function extractVideoMetadata(filePath) {
|
|
|
22009
22188
|
const avgFps = parseFrameRate(videoStream.avg_frame_rate);
|
|
22010
22189
|
const fps = avgFps || rFps;
|
|
22011
22190
|
const isVFR = rFps > 0 && avgFps > 0 && Math.abs(rFps - avgFps) / Math.max(rFps, avgFps) > 0.1;
|
|
22191
|
+
const colorTransfer = videoStream.color_transfer || "";
|
|
22192
|
+
const colorPrimaries = videoStream.color_primaries || "";
|
|
22193
|
+
const colorSpaceVal = videoStream.color_space || "";
|
|
22194
|
+
const hasColorInfo = !!(colorTransfer || colorPrimaries || colorSpaceVal);
|
|
22012
22195
|
return {
|
|
22013
22196
|
durationSeconds: output.format.duration ? parseFloat(output.format.duration) : 0,
|
|
22014
22197
|
width: videoStream.width || 0,
|
|
@@ -22016,7 +22199,8 @@ async function extractVideoMetadata(filePath) {
|
|
|
22016
22199
|
fps,
|
|
22017
22200
|
videoCodec: videoStream.codec_name || "unknown",
|
|
22018
22201
|
hasAudio: output.streams.some((s2) => s2.codec_type === "audio"),
|
|
22019
|
-
isVFR
|
|
22202
|
+
isVFR,
|
|
22203
|
+
colorSpace: hasColorInfo ? { colorTransfer, colorPrimaries, colorSpace: colorSpaceVal } : null
|
|
22020
22204
|
};
|
|
22021
22205
|
})();
|
|
22022
22206
|
videoMetadataCache.set(filePath, probePromise);
|
|
@@ -22241,18 +22425,20 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
22241
22425
|
const metadata = await extractVideoMetadata(videoPath);
|
|
22242
22426
|
const framePattern = `frame_%05d.${format}`;
|
|
22243
22427
|
const outputPattern = join21(videoOutputDir, framePattern);
|
|
22244
|
-
const
|
|
22245
|
-
|
|
22246
|
-
|
|
22247
|
-
|
|
22248
|
-
|
|
22249
|
-
|
|
22250
|
-
|
|
22251
|
-
|
|
22252
|
-
|
|
22253
|
-
"
|
|
22254
|
-
|
|
22255
|
-
|
|
22428
|
+
const isHdr = isHdrColorSpace(metadata.colorSpace);
|
|
22429
|
+
const isMacOS = process.platform === "darwin";
|
|
22430
|
+
const args = [];
|
|
22431
|
+
if (isHdr && isMacOS) {
|
|
22432
|
+
args.push("-hwaccel", "videotoolbox");
|
|
22433
|
+
}
|
|
22434
|
+
args.push("-ss", String(startTime), "-i", videoPath, "-t", String(duration));
|
|
22435
|
+
const vfFilters = [];
|
|
22436
|
+
if (isHdr && isMacOS) {
|
|
22437
|
+
vfFilters.push("format=nv12");
|
|
22438
|
+
}
|
|
22439
|
+
vfFilters.push(`fps=${fps}`);
|
|
22440
|
+
args.push("-vf", vfFilters.join(","));
|
|
22441
|
+
args.push("-q:v", format === "jpg" ? String(Math.ceil((100 - quality) / 3)) : "0");
|
|
22256
22442
|
if (format === "png") args.push("-compression_level", "6");
|
|
22257
22443
|
args.push("-y", outputPattern);
|
|
22258
22444
|
return new Promise((resolve35, reject) => {
|
|
@@ -22312,30 +22498,100 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
22312
22498
|
});
|
|
22313
22499
|
});
|
|
22314
22500
|
}
|
|
22501
|
+
async function convertSdrToHdr(inputPath, outputPath, signal, config) {
|
|
22502
|
+
const timeout = config?.ffmpegProcessTimeout ?? DEFAULT_CONFIG2.ffmpegProcessTimeout;
|
|
22503
|
+
const args = [
|
|
22504
|
+
"-i",
|
|
22505
|
+
inputPath,
|
|
22506
|
+
"-vf",
|
|
22507
|
+
"colorspace=all=bt2020:iall=bt709:range=tv",
|
|
22508
|
+
"-color_primaries",
|
|
22509
|
+
"bt2020",
|
|
22510
|
+
"-color_trc",
|
|
22511
|
+
"arib-std-b67",
|
|
22512
|
+
"-colorspace",
|
|
22513
|
+
"bt2020nc",
|
|
22514
|
+
"-c:v",
|
|
22515
|
+
"libx264",
|
|
22516
|
+
"-preset",
|
|
22517
|
+
"fast",
|
|
22518
|
+
"-crf",
|
|
22519
|
+
"16",
|
|
22520
|
+
"-c:a",
|
|
22521
|
+
"copy",
|
|
22522
|
+
"-y",
|
|
22523
|
+
outputPath
|
|
22524
|
+
];
|
|
22525
|
+
const result = await runFfmpeg(args, { signal, timeout });
|
|
22526
|
+
if (!result.success) {
|
|
22527
|
+
throw new Error(
|
|
22528
|
+
`SDR\u2192HDR conversion failed (exit ${result.exitCode}): ${result.stderr.slice(-300)}`
|
|
22529
|
+
);
|
|
22530
|
+
}
|
|
22531
|
+
}
|
|
22315
22532
|
async function extractAllVideoFrames(videos, baseDir, options, signal, config, compiledDir) {
|
|
22316
22533
|
const startTime = Date.now();
|
|
22317
22534
|
const extracted = [];
|
|
22318
22535
|
const errors = [];
|
|
22319
22536
|
let totalFramesExtracted = 0;
|
|
22537
|
+
const resolvedVideos = [];
|
|
22538
|
+
for (const video of videos) {
|
|
22539
|
+
if (signal?.aborted) break;
|
|
22540
|
+
try {
|
|
22541
|
+
let videoPath = video.src;
|
|
22542
|
+
if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
|
|
22543
|
+
const fromCompiled = compiledDir ? join21(compiledDir, videoPath) : null;
|
|
22544
|
+
videoPath = fromCompiled && existsSync18(fromCompiled) ? fromCompiled : join21(baseDir, videoPath);
|
|
22545
|
+
}
|
|
22546
|
+
if (isHttpUrl(videoPath)) {
|
|
22547
|
+
const downloadDir = join21(options.outputDir, "_downloads");
|
|
22548
|
+
mkdirSync12(downloadDir, { recursive: true });
|
|
22549
|
+
videoPath = await downloadToTemp(videoPath, downloadDir);
|
|
22550
|
+
}
|
|
22551
|
+
if (!existsSync18(videoPath)) {
|
|
22552
|
+
errors.push({ videoId: video.id, error: `Video file not found: ${videoPath}` });
|
|
22553
|
+
continue;
|
|
22554
|
+
}
|
|
22555
|
+
resolvedVideos.push({ video, videoPath });
|
|
22556
|
+
} catch (err) {
|
|
22557
|
+
errors.push({ videoId: video.id, error: err instanceof Error ? err.message : String(err) });
|
|
22558
|
+
}
|
|
22559
|
+
}
|
|
22560
|
+
const videoColorSpaces = await Promise.all(
|
|
22561
|
+
resolvedVideos.map(async ({ videoPath }) => {
|
|
22562
|
+
const metadata = await extractVideoMetadata(videoPath);
|
|
22563
|
+
return metadata.colorSpace;
|
|
22564
|
+
})
|
|
22565
|
+
);
|
|
22566
|
+
const hasAnyHdr = videoColorSpaces.some(isHdrColorSpace);
|
|
22567
|
+
if (hasAnyHdr) {
|
|
22568
|
+
const convertDir = join21(options.outputDir, "_hdr_normalized");
|
|
22569
|
+
mkdirSync12(convertDir, { recursive: true });
|
|
22570
|
+
for (let i2 = 0; i2 < resolvedVideos.length; i2++) {
|
|
22571
|
+
if (signal?.aborted) break;
|
|
22572
|
+
const cs = videoColorSpaces[i2] ?? null;
|
|
22573
|
+
if (!isHdrColorSpace(cs)) {
|
|
22574
|
+
const entry = resolvedVideos[i2];
|
|
22575
|
+
if (!entry) continue;
|
|
22576
|
+
const convertedPath = join21(convertDir, `${entry.video.id}_hdr.mp4`);
|
|
22577
|
+
try {
|
|
22578
|
+
await convertSdrToHdr(entry.videoPath, convertedPath, signal, config);
|
|
22579
|
+
entry.videoPath = convertedPath;
|
|
22580
|
+
} catch (err) {
|
|
22581
|
+
errors.push({
|
|
22582
|
+
videoId: entry.video.id,
|
|
22583
|
+
error: `SDR\u2192HDR conversion failed: ${err instanceof Error ? err.message : String(err)}`
|
|
22584
|
+
});
|
|
22585
|
+
}
|
|
22586
|
+
}
|
|
22587
|
+
}
|
|
22588
|
+
}
|
|
22320
22589
|
const results = await Promise.all(
|
|
22321
|
-
|
|
22590
|
+
resolvedVideos.map(async ({ video, videoPath }) => {
|
|
22322
22591
|
if (signal?.aborted) {
|
|
22323
22592
|
throw new Error("Video frame extraction cancelled");
|
|
22324
22593
|
}
|
|
22325
22594
|
try {
|
|
22326
|
-
let videoPath = video.src;
|
|
22327
|
-
if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
|
|
22328
|
-
const fromCompiled = compiledDir ? join21(compiledDir, videoPath) : null;
|
|
22329
|
-
videoPath = fromCompiled && existsSync18(fromCompiled) ? fromCompiled : join21(baseDir, videoPath);
|
|
22330
|
-
}
|
|
22331
|
-
if (isHttpUrl(videoPath)) {
|
|
22332
|
-
const downloadDir = join21(options.outputDir, "_downloads");
|
|
22333
|
-
mkdirSync12(downloadDir, { recursive: true });
|
|
22334
|
-
videoPath = await downloadToTemp(videoPath, downloadDir);
|
|
22335
|
-
}
|
|
22336
|
-
if (!existsSync18(videoPath)) {
|
|
22337
|
-
return { error: { videoId: video.id, error: `Video file not found: ${videoPath}` } };
|
|
22338
|
-
}
|
|
22339
22595
|
let videoDuration = video.end - video.start;
|
|
22340
22596
|
if (!Number.isFinite(videoDuration) || videoDuration <= 0) {
|
|
22341
22597
|
const metadata = await extractVideoMetadata(videoPath);
|
|
@@ -22402,7 +22658,9 @@ var init_videoFrameExtractor = __esm({
|
|
|
22402
22658
|
"use strict";
|
|
22403
22659
|
init_esm10();
|
|
22404
22660
|
init_ffprobe();
|
|
22661
|
+
init_hdr();
|
|
22405
22662
|
init_urlDownloader();
|
|
22663
|
+
init_runFfmpeg();
|
|
22406
22664
|
init_config2();
|
|
22407
22665
|
FrameLookupTable = class {
|
|
22408
22666
|
videos = /* @__PURE__ */ new Map();
|
|
@@ -22580,6 +22838,112 @@ function createVideoFrameInjector(frameLookup, config) {
|
|
|
22580
22838
|
}
|
|
22581
22839
|
};
|
|
22582
22840
|
}
|
|
22841
|
+
async function hideVideoElements(page, videoIds) {
|
|
22842
|
+
if (videoIds.length === 0) return;
|
|
22843
|
+
await page.evaluate((ids) => {
|
|
22844
|
+
for (const id of ids) {
|
|
22845
|
+
const el = document.getElementById(id);
|
|
22846
|
+
if (el) {
|
|
22847
|
+
el.style.setProperty("visibility", "hidden", "important");
|
|
22848
|
+
el.style.setProperty("opacity", "0", "important");
|
|
22849
|
+
const img = document.getElementById(`__render_frame_${id}__`);
|
|
22850
|
+
if (img) img.style.setProperty("visibility", "hidden", "important");
|
|
22851
|
+
}
|
|
22852
|
+
}
|
|
22853
|
+
}, videoIds);
|
|
22854
|
+
}
|
|
22855
|
+
async function showVideoElements(page, videoIds) {
|
|
22856
|
+
if (videoIds.length === 0) return;
|
|
22857
|
+
await page.evaluate((ids) => {
|
|
22858
|
+
for (const id of ids) {
|
|
22859
|
+
const el = document.getElementById(id);
|
|
22860
|
+
if (el) {
|
|
22861
|
+
el.style.removeProperty("visibility");
|
|
22862
|
+
el.style.removeProperty("opacity");
|
|
22863
|
+
const img = document.getElementById(`__render_frame_${id}__`);
|
|
22864
|
+
if (img) img.style.removeProperty("visibility");
|
|
22865
|
+
}
|
|
22866
|
+
}
|
|
22867
|
+
}, videoIds);
|
|
22868
|
+
}
|
|
22869
|
+
async function queryVideoElementBounds(page, videoIds) {
|
|
22870
|
+
if (videoIds.length === 0) return [];
|
|
22871
|
+
return page.evaluate((ids) => {
|
|
22872
|
+
return ids.map((id) => {
|
|
22873
|
+
const el = document.getElementById(id);
|
|
22874
|
+
if (!el) {
|
|
22875
|
+
return {
|
|
22876
|
+
videoId: id,
|
|
22877
|
+
x: 0,
|
|
22878
|
+
y: 0,
|
|
22879
|
+
width: 0,
|
|
22880
|
+
height: 0,
|
|
22881
|
+
opacity: 0,
|
|
22882
|
+
transform: "none",
|
|
22883
|
+
zIndex: 0,
|
|
22884
|
+
visible: false
|
|
22885
|
+
};
|
|
22886
|
+
}
|
|
22887
|
+
const rect = el.getBoundingClientRect();
|
|
22888
|
+
const style = window.getComputedStyle(el);
|
|
22889
|
+
const zIndex = parseInt(style.zIndex) || 0;
|
|
22890
|
+
const opacity = parseFloat(style.opacity) || 1;
|
|
22891
|
+
const transform = style.transform || "none";
|
|
22892
|
+
const visible = style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
|
|
22893
|
+
return {
|
|
22894
|
+
videoId: id,
|
|
22895
|
+
x: Math.round(rect.x),
|
|
22896
|
+
y: Math.round(rect.y),
|
|
22897
|
+
width: Math.round(rect.width),
|
|
22898
|
+
height: Math.round(rect.height),
|
|
22899
|
+
opacity,
|
|
22900
|
+
transform,
|
|
22901
|
+
zIndex,
|
|
22902
|
+
visible
|
|
22903
|
+
};
|
|
22904
|
+
});
|
|
22905
|
+
}, videoIds);
|
|
22906
|
+
}
|
|
22907
|
+
async function queryElementStacking(page, nativeHdrVideoIds) {
|
|
22908
|
+
const hdrIds = Array.from(nativeHdrVideoIds);
|
|
22909
|
+
return page.evaluate((hdrIdList) => {
|
|
22910
|
+
const hdrSet = new Set(hdrIdList);
|
|
22911
|
+
const elements = document.querySelectorAll("[data-start]");
|
|
22912
|
+
const results = [];
|
|
22913
|
+
function getEffectiveZIndex(node) {
|
|
22914
|
+
let current = node;
|
|
22915
|
+
while (current) {
|
|
22916
|
+
const cs = window.getComputedStyle(current);
|
|
22917
|
+
const pos = cs.position;
|
|
22918
|
+
const z3 = parseInt(cs.zIndex);
|
|
22919
|
+
if (!Number.isNaN(z3) && pos !== "static") return z3;
|
|
22920
|
+
current = current.parentElement;
|
|
22921
|
+
}
|
|
22922
|
+
return 0;
|
|
22923
|
+
}
|
|
22924
|
+
for (const el of elements) {
|
|
22925
|
+
const id = el.id;
|
|
22926
|
+
if (!id) continue;
|
|
22927
|
+
const rect = el.getBoundingClientRect();
|
|
22928
|
+
const style = window.getComputedStyle(el);
|
|
22929
|
+
const zIndex = getEffectiveZIndex(el);
|
|
22930
|
+
const opacity = parseFloat(style.opacity) || 1;
|
|
22931
|
+
const visible = style.visibility !== "hidden" && style.display !== "none" && rect.width > 0 && rect.height > 0;
|
|
22932
|
+
results.push({
|
|
22933
|
+
id,
|
|
22934
|
+
zIndex,
|
|
22935
|
+
x: Math.round(rect.x),
|
|
22936
|
+
y: Math.round(rect.y),
|
|
22937
|
+
width: Math.round(rect.width),
|
|
22938
|
+
height: Math.round(rect.height),
|
|
22939
|
+
opacity,
|
|
22940
|
+
visible,
|
|
22941
|
+
isHdr: hdrSet.has(id)
|
|
22942
|
+
});
|
|
22943
|
+
}
|
|
22944
|
+
return results;
|
|
22945
|
+
}, hdrIds);
|
|
22946
|
+
}
|
|
22583
22947
|
var init_videoFrameInjector = __esm({
|
|
22584
22948
|
"../engine/src/services/videoFrameInjector.ts"() {
|
|
22585
22949
|
"use strict";
|
|
@@ -23234,6 +23598,515 @@ var init_fileServer = __esm({
|
|
|
23234
23598
|
}
|
|
23235
23599
|
});
|
|
23236
23600
|
|
|
23601
|
+
// ../engine/src/utils/alphaBlit.ts
|
|
23602
|
+
import { inflateSync } from "zlib";
|
|
23603
|
+
function paeth(a, b, c2) {
|
|
23604
|
+
const p = a + b - c2;
|
|
23605
|
+
const pa = Math.abs(p - a);
|
|
23606
|
+
const pb = Math.abs(p - b);
|
|
23607
|
+
const pc2 = Math.abs(p - c2);
|
|
23608
|
+
if (pa <= pb && pa <= pc2) return a;
|
|
23609
|
+
if (pb <= pc2) return b;
|
|
23610
|
+
return c2;
|
|
23611
|
+
}
|
|
23612
|
+
function decodePngRaw(buf, caller) {
|
|
23613
|
+
if (buf[0] !== 137 || buf[1] !== 80 || buf[2] !== 78 || buf[3] !== 71 || buf[4] !== 13 || buf[5] !== 10 || buf[6] !== 26 || buf[7] !== 10) {
|
|
23614
|
+
throw new Error(`${caller}: not a PNG file`);
|
|
23615
|
+
}
|
|
23616
|
+
let pos = 8;
|
|
23617
|
+
let width = 0;
|
|
23618
|
+
let height = 0;
|
|
23619
|
+
let bitDepth = 0;
|
|
23620
|
+
let colorType = 0;
|
|
23621
|
+
let interlace = 0;
|
|
23622
|
+
let sawIhdr = false;
|
|
23623
|
+
const idatChunks = [];
|
|
23624
|
+
while (pos + 12 <= buf.length) {
|
|
23625
|
+
const chunkLen = buf.readUInt32BE(pos);
|
|
23626
|
+
const chunkType = buf.toString("ascii", pos + 4, pos + 8);
|
|
23627
|
+
const chunkData = buf.subarray(pos + 8, pos + 8 + chunkLen);
|
|
23628
|
+
if (chunkType === "IHDR") {
|
|
23629
|
+
width = chunkData.readUInt32BE(0);
|
|
23630
|
+
height = chunkData.readUInt32BE(4);
|
|
23631
|
+
bitDepth = chunkData[8] ?? 0;
|
|
23632
|
+
colorType = chunkData[9] ?? 0;
|
|
23633
|
+
interlace = chunkData[12] ?? 0;
|
|
23634
|
+
sawIhdr = true;
|
|
23635
|
+
} else if (chunkType === "IDAT") {
|
|
23636
|
+
idatChunks.push(Buffer.from(chunkData));
|
|
23637
|
+
} else if (chunkType === "IEND") {
|
|
23638
|
+
break;
|
|
23639
|
+
}
|
|
23640
|
+
pos += 12 + chunkLen;
|
|
23641
|
+
}
|
|
23642
|
+
if (!sawIhdr) {
|
|
23643
|
+
throw new Error(`${caller}: PNG missing IHDR chunk`);
|
|
23644
|
+
}
|
|
23645
|
+
if (colorType !== 2 && colorType !== 6) {
|
|
23646
|
+
throw new Error(`${caller}: unsupported color type ${colorType} (expected 2=RGB or 6=RGBA)`);
|
|
23647
|
+
}
|
|
23648
|
+
if (interlace !== 0) {
|
|
23649
|
+
throw new Error(
|
|
23650
|
+
`${caller}: Adam7-interlaced PNGs are not supported (interlace method ${interlace})`
|
|
23651
|
+
);
|
|
23652
|
+
}
|
|
23653
|
+
const channels = colorType === 6 ? 4 : 3;
|
|
23654
|
+
const bpp = channels * (bitDepth / 8);
|
|
23655
|
+
const stride = width * bpp;
|
|
23656
|
+
const compressed = Buffer.concat(idatChunks);
|
|
23657
|
+
const decompressed = inflateSync(compressed);
|
|
23658
|
+
const rawPixels = Buffer.allocUnsafe(height * stride);
|
|
23659
|
+
const prevRow = new Uint8Array(stride);
|
|
23660
|
+
const currRow = new Uint8Array(stride);
|
|
23661
|
+
let srcPos = 0;
|
|
23662
|
+
for (let y2 = 0; y2 < height; y2++) {
|
|
23663
|
+
const filterType = decompressed[srcPos++] ?? 0;
|
|
23664
|
+
const rawRow = decompressed.subarray(srcPos, srcPos + stride);
|
|
23665
|
+
srcPos += stride;
|
|
23666
|
+
switch (filterType) {
|
|
23667
|
+
case 0:
|
|
23668
|
+
currRow.set(rawRow);
|
|
23669
|
+
break;
|
|
23670
|
+
case 1:
|
|
23671
|
+
for (let x4 = 0; x4 < stride; x4++) {
|
|
23672
|
+
currRow[x4] = (rawRow[x4] ?? 0) + (x4 >= bpp ? currRow[x4 - bpp] ?? 0 : 0) & 255;
|
|
23673
|
+
}
|
|
23674
|
+
break;
|
|
23675
|
+
case 2:
|
|
23676
|
+
for (let x4 = 0; x4 < stride; x4++) {
|
|
23677
|
+
currRow[x4] = (rawRow[x4] ?? 0) + (prevRow[x4] ?? 0) & 255;
|
|
23678
|
+
}
|
|
23679
|
+
break;
|
|
23680
|
+
case 3:
|
|
23681
|
+
for (let x4 = 0; x4 < stride; x4++) {
|
|
23682
|
+
const left = x4 >= bpp ? currRow[x4 - bpp] ?? 0 : 0;
|
|
23683
|
+
const up = prevRow[x4] ?? 0;
|
|
23684
|
+
currRow[x4] = (rawRow[x4] ?? 0) + Math.floor((left + up) / 2) & 255;
|
|
23685
|
+
}
|
|
23686
|
+
break;
|
|
23687
|
+
case 4:
|
|
23688
|
+
for (let x4 = 0; x4 < stride; x4++) {
|
|
23689
|
+
const left = x4 >= bpp ? currRow[x4 - bpp] ?? 0 : 0;
|
|
23690
|
+
const up = prevRow[x4] ?? 0;
|
|
23691
|
+
const upLeft = x4 >= bpp ? prevRow[x4 - bpp] ?? 0 : 0;
|
|
23692
|
+
currRow[x4] = (rawRow[x4] ?? 0) + paeth(left, up, upLeft) & 255;
|
|
23693
|
+
}
|
|
23694
|
+
break;
|
|
23695
|
+
default:
|
|
23696
|
+
throw new Error(`${caller}: unknown filter type ${filterType} at row ${y2}`);
|
|
23697
|
+
}
|
|
23698
|
+
rawPixels.set(currRow, y2 * stride);
|
|
23699
|
+
prevRow.set(currRow);
|
|
23700
|
+
}
|
|
23701
|
+
return { width, height, bitDepth, colorType, rawPixels };
|
|
23702
|
+
}
|
|
23703
|
+
function decodePng(buf) {
|
|
23704
|
+
const { width, height, bitDepth, colorType, rawPixels } = decodePngRaw(buf, "decodePng");
|
|
23705
|
+
if (bitDepth !== 8) {
|
|
23706
|
+
throw new Error(`decodePng: unsupported bit depth ${bitDepth} (expected 8)`);
|
|
23707
|
+
}
|
|
23708
|
+
const output = new Uint8Array(width * height * 4);
|
|
23709
|
+
if (colorType === 6) {
|
|
23710
|
+
output.set(rawPixels);
|
|
23711
|
+
} else {
|
|
23712
|
+
for (let i2 = 0; i2 < width * height; i2++) {
|
|
23713
|
+
output[i2 * 4 + 0] = rawPixels[i2 * 3 + 0] ?? 0;
|
|
23714
|
+
output[i2 * 4 + 1] = rawPixels[i2 * 3 + 1] ?? 0;
|
|
23715
|
+
output[i2 * 4 + 2] = rawPixels[i2 * 3 + 2] ?? 0;
|
|
23716
|
+
output[i2 * 4 + 3] = 255;
|
|
23717
|
+
}
|
|
23718
|
+
}
|
|
23719
|
+
return { width, height, data: output };
|
|
23720
|
+
}
|
|
23721
|
+
function decodePngToRgb48le(buf) {
|
|
23722
|
+
const { width, height, bitDepth, colorType, rawPixels } = decodePngRaw(buf, "decodePngToRgb48le");
|
|
23723
|
+
if (bitDepth !== 16) {
|
|
23724
|
+
throw new Error(`decodePngToRgb48le: unsupported bit depth ${bitDepth} (expected 16)`);
|
|
23725
|
+
}
|
|
23726
|
+
const bpp = colorType === 6 ? 8 : 6;
|
|
23727
|
+
const output = Buffer.allocUnsafe(width * height * 6);
|
|
23728
|
+
for (let y2 = 0; y2 < height; y2++) {
|
|
23729
|
+
const dstBase = y2 * width * 6;
|
|
23730
|
+
const srcRowBase = y2 * width * bpp;
|
|
23731
|
+
for (let x4 = 0; x4 < width; x4++) {
|
|
23732
|
+
const srcBase = srcRowBase + x4 * bpp;
|
|
23733
|
+
output[dstBase + x4 * 6 + 0] = rawPixels[srcBase + 1] ?? 0;
|
|
23734
|
+
output[dstBase + x4 * 6 + 1] = rawPixels[srcBase + 0] ?? 0;
|
|
23735
|
+
output[dstBase + x4 * 6 + 2] = rawPixels[srcBase + 3] ?? 0;
|
|
23736
|
+
output[dstBase + x4 * 6 + 3] = rawPixels[srcBase + 2] ?? 0;
|
|
23737
|
+
output[dstBase + x4 * 6 + 4] = rawPixels[srcBase + 5] ?? 0;
|
|
23738
|
+
output[dstBase + x4 * 6 + 5] = rawPixels[srcBase + 4] ?? 0;
|
|
23739
|
+
}
|
|
23740
|
+
}
|
|
23741
|
+
return { width, height, data: output };
|
|
23742
|
+
}
|
|
23743
|
+
function buildSrgbToHdrLut(transfer) {
|
|
23744
|
+
const lut = new Uint16Array(256);
|
|
23745
|
+
const hlgA = 0.17883277;
|
|
23746
|
+
const hlgB = 1 - 4 * hlgA;
|
|
23747
|
+
const hlgC = 0.5 - hlgA * Math.log(4 * hlgA);
|
|
23748
|
+
const pqM1 = 0.1593017578125;
|
|
23749
|
+
const pqM2 = 78.84375;
|
|
23750
|
+
const pqC1 = 0.8359375;
|
|
23751
|
+
const pqC2 = 18.8515625;
|
|
23752
|
+
const pqC3 = 18.6875;
|
|
23753
|
+
const pqMaxNits = 1e4;
|
|
23754
|
+
const sdrNits = 203;
|
|
23755
|
+
for (let i2 = 0; i2 < 256; i2++) {
|
|
23756
|
+
const v = i2 / 255;
|
|
23757
|
+
const linear = v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
|
|
23758
|
+
let signal;
|
|
23759
|
+
if (transfer === "hlg") {
|
|
23760
|
+
signal = linear <= 1 / 12 ? Math.sqrt(3 * linear) : hlgA * Math.log(12 * linear - hlgB) + hlgC;
|
|
23761
|
+
} else {
|
|
23762
|
+
const Lp = Math.max(0, linear * sdrNits / pqMaxNits);
|
|
23763
|
+
const Lm1 = Math.pow(Lp, pqM1);
|
|
23764
|
+
signal = Math.pow((pqC1 + pqC2 * Lm1) / (1 + pqC3 * Lm1), pqM2);
|
|
23765
|
+
}
|
|
23766
|
+
lut[i2] = Math.min(65535, Math.round(signal * 65535));
|
|
23767
|
+
}
|
|
23768
|
+
return lut;
|
|
23769
|
+
}
|
|
23770
|
+
function getSrgbToHdrLut(transfer) {
|
|
23771
|
+
return transfer === "pq" ? SRGB_TO_PQ : SRGB_TO_HLG;
|
|
23772
|
+
}
|
|
23773
|
+
function blitRgba8OverRgb48le(domRgba, canvas, width, height, transfer = "hlg") {
|
|
23774
|
+
const pixelCount = width * height;
|
|
23775
|
+
const lut = getSrgbToHdrLut(transfer);
|
|
23776
|
+
for (let i2 = 0; i2 < pixelCount; i2++) {
|
|
23777
|
+
const da = domRgba[i2 * 4 + 3] ?? 0;
|
|
23778
|
+
if (da === 0) {
|
|
23779
|
+
continue;
|
|
23780
|
+
} else if (da === 255) {
|
|
23781
|
+
const r16 = lut[domRgba[i2 * 4 + 0] ?? 0] ?? 0;
|
|
23782
|
+
const g16 = lut[domRgba[i2 * 4 + 1] ?? 0] ?? 0;
|
|
23783
|
+
const b16 = lut[domRgba[i2 * 4 + 2] ?? 0] ?? 0;
|
|
23784
|
+
canvas.writeUInt16LE(r16, i2 * 6);
|
|
23785
|
+
canvas.writeUInt16LE(g16, i2 * 6 + 2);
|
|
23786
|
+
canvas.writeUInt16LE(b16, i2 * 6 + 4);
|
|
23787
|
+
} else {
|
|
23788
|
+
const alpha = da / 255;
|
|
23789
|
+
const invAlpha = 1 - alpha;
|
|
23790
|
+
const hdrR = (canvas[i2 * 6 + 0] ?? 0) | (canvas[i2 * 6 + 1] ?? 0) << 8;
|
|
23791
|
+
const hdrG = (canvas[i2 * 6 + 2] ?? 0) | (canvas[i2 * 6 + 3] ?? 0) << 8;
|
|
23792
|
+
const hdrB = (canvas[i2 * 6 + 4] ?? 0) | (canvas[i2 * 6 + 5] ?? 0) << 8;
|
|
23793
|
+
const domR = lut[domRgba[i2 * 4 + 0] ?? 0] ?? 0;
|
|
23794
|
+
const domG = lut[domRgba[i2 * 4 + 1] ?? 0] ?? 0;
|
|
23795
|
+
const domB = lut[domRgba[i2 * 4 + 2] ?? 0] ?? 0;
|
|
23796
|
+
canvas.writeUInt16LE(Math.round(domR * alpha + hdrR * invAlpha), i2 * 6);
|
|
23797
|
+
canvas.writeUInt16LE(Math.round(domG * alpha + hdrG * invAlpha), i2 * 6 + 2);
|
|
23798
|
+
canvas.writeUInt16LE(Math.round(domB * alpha + hdrB * invAlpha), i2 * 6 + 4);
|
|
23799
|
+
}
|
|
23800
|
+
}
|
|
23801
|
+
}
|
|
23802
|
+
function cornerAlpha(px, py, cx, cy, r2) {
|
|
23803
|
+
const dx = px - cx;
|
|
23804
|
+
const dy = py - cy;
|
|
23805
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
23806
|
+
if (dist > r2 + 0.5) return 0;
|
|
23807
|
+
if (dist > r2 - 0.5) return r2 + 0.5 - dist;
|
|
23808
|
+
return 1;
|
|
23809
|
+
}
|
|
23810
|
+
function roundedRectAlpha(px, py, w, h3, radii) {
|
|
23811
|
+
const [tl, tr, br, bl] = radii;
|
|
23812
|
+
if (px < tl && py < tl) return cornerAlpha(px, py, tl, tl, tl);
|
|
23813
|
+
if (px >= w - tr && py < tr) return cornerAlpha(px, py, w - tr, tr, tr);
|
|
23814
|
+
if (px >= w - br && py >= h3 - br) return cornerAlpha(px, py, w - br, h3 - br, br);
|
|
23815
|
+
if (px < bl && py >= h3 - bl) return cornerAlpha(px, py, bl, h3 - bl, bl);
|
|
23816
|
+
return 1;
|
|
23817
|
+
}
|
|
23818
|
+
function blitRgb48leRegion(canvas, source, dx, dy, sw, sh, canvasWidth, canvasHeight, opacity, borderRadius) {
|
|
23819
|
+
if (sw <= 0 || sh <= 0) return;
|
|
23820
|
+
const op = opacity ?? 1;
|
|
23821
|
+
const x0 = Math.max(0, dx);
|
|
23822
|
+
const y0 = Math.max(0, dy);
|
|
23823
|
+
const x1 = Math.min(canvasWidth, dx + sw);
|
|
23824
|
+
const y1 = Math.min(canvasHeight, dy + sh);
|
|
23825
|
+
if (x0 >= x1 || y0 >= y1) return;
|
|
23826
|
+
const clippedW = x1 - x0;
|
|
23827
|
+
const srcOffsetX = x0 - dx;
|
|
23828
|
+
const srcOffsetY = y0 - dy;
|
|
23829
|
+
const hasMask = borderRadius !== void 0;
|
|
23830
|
+
if (op >= 0.999 && !hasMask) {
|
|
23831
|
+
for (let y2 = 0; y2 < y1 - y0; y2++) {
|
|
23832
|
+
const srcRowOff = ((srcOffsetY + y2) * sw + srcOffsetX) * 6;
|
|
23833
|
+
const dstRowOff = ((y0 + y2) * canvasWidth + x0) * 6;
|
|
23834
|
+
source.copy(canvas, dstRowOff, srcRowOff, srcRowOff + clippedW * 6);
|
|
23835
|
+
}
|
|
23836
|
+
} else {
|
|
23837
|
+
for (let y2 = 0; y2 < y1 - y0; y2++) {
|
|
23838
|
+
for (let x4 = 0; x4 < clippedW; x4++) {
|
|
23839
|
+
let effectiveOp = op;
|
|
23840
|
+
if (hasMask) {
|
|
23841
|
+
const ma = roundedRectAlpha(srcOffsetX + x4, srcOffsetY + y2, sw, sh, borderRadius);
|
|
23842
|
+
if (ma <= 0) continue;
|
|
23843
|
+
effectiveOp *= ma;
|
|
23844
|
+
}
|
|
23845
|
+
const srcOff = ((srcOffsetY + y2) * sw + srcOffsetX + x4) * 6;
|
|
23846
|
+
const dstOff = ((y0 + y2) * canvasWidth + x0 + x4) * 6;
|
|
23847
|
+
if (effectiveOp >= 0.999) {
|
|
23848
|
+
source.copy(canvas, dstOff, srcOff, srcOff + 6);
|
|
23849
|
+
} else {
|
|
23850
|
+
const invEff = 1 - effectiveOp;
|
|
23851
|
+
const sr = source.readUInt16LE(srcOff);
|
|
23852
|
+
const sg = source.readUInt16LE(srcOff + 2);
|
|
23853
|
+
const sb = source.readUInt16LE(srcOff + 4);
|
|
23854
|
+
const dr = canvas.readUInt16LE(dstOff);
|
|
23855
|
+
const dg = canvas.readUInt16LE(dstOff + 2);
|
|
23856
|
+
const db = canvas.readUInt16LE(dstOff + 4);
|
|
23857
|
+
canvas.writeUInt16LE(Math.round(sr * effectiveOp + dr * invEff), dstOff);
|
|
23858
|
+
canvas.writeUInt16LE(Math.round(sg * effectiveOp + dg * invEff), dstOff + 2);
|
|
23859
|
+
canvas.writeUInt16LE(Math.round(sb * effectiveOp + db * invEff), dstOff + 4);
|
|
23860
|
+
}
|
|
23861
|
+
}
|
|
23862
|
+
}
|
|
23863
|
+
}
|
|
23864
|
+
}
|
|
23865
|
+
var SRGB_TO_HLG, SRGB_TO_PQ;
|
|
23866
|
+
var init_alphaBlit = __esm({
|
|
23867
|
+
"../engine/src/utils/alphaBlit.ts"() {
|
|
23868
|
+
"use strict";
|
|
23869
|
+
SRGB_TO_HLG = buildSrgbToHdrLut("hlg");
|
|
23870
|
+
SRGB_TO_PQ = buildSrgbToHdrLut("pq");
|
|
23871
|
+
}
|
|
23872
|
+
});
|
|
23873
|
+
|
|
23874
|
+
// ../engine/src/utils/layerCompositor.ts
|
|
23875
|
+
function groupIntoLayers(elements) {
|
|
23876
|
+
const sorted = [...elements].sort((a, b) => a.zIndex - b.zIndex);
|
|
23877
|
+
const layers = [];
|
|
23878
|
+
for (const el of sorted) {
|
|
23879
|
+
if (el.isHdr) {
|
|
23880
|
+
layers.push({ type: "hdr", element: el });
|
|
23881
|
+
} else {
|
|
23882
|
+
const last = layers[layers.length - 1];
|
|
23883
|
+
if (last && last.type === "dom") {
|
|
23884
|
+
last.elementIds.push(el.id);
|
|
23885
|
+
} else {
|
|
23886
|
+
layers.push({ type: "dom", elementIds: [el.id] });
|
|
23887
|
+
}
|
|
23888
|
+
}
|
|
23889
|
+
}
|
|
23890
|
+
return layers;
|
|
23891
|
+
}
|
|
23892
|
+
var init_layerCompositor = __esm({
|
|
23893
|
+
"../engine/src/utils/layerCompositor.ts"() {
|
|
23894
|
+
"use strict";
|
|
23895
|
+
}
|
|
23896
|
+
});
|
|
23897
|
+
|
|
23898
|
+
// ../engine/src/services/hdrCapture.ts
|
|
23899
|
+
import { existsSync as existsSync22, readdirSync as readdirSync10 } from "fs";
|
|
23900
|
+
import { join as join25 } from "path";
|
|
23901
|
+
import { homedir as homedir6 } from "os";
|
|
23902
|
+
function linearToPQ(L2) {
|
|
23903
|
+
const Lp = Math.max(0, L2 * SDR_NITS / PQ_MAX_NITS);
|
|
23904
|
+
const Lm1 = Math.pow(Lp, PQ_M1);
|
|
23905
|
+
return Math.pow((PQ_C1 + PQ_C2 * Lm1) / (1 + PQ_C3 * Lm1), PQ_M2);
|
|
23906
|
+
}
|
|
23907
|
+
function float16Decode(h3) {
|
|
23908
|
+
const sign = h3 >> 15 & 1;
|
|
23909
|
+
const exp = h3 >> 10 & 31;
|
|
23910
|
+
const frac = h3 & 1023;
|
|
23911
|
+
if (exp === 0) return (sign ? -1 : 1) * Math.pow(2, -14) * (frac / 1024);
|
|
23912
|
+
if (exp === 31) return frac ? NaN : sign ? -Infinity : Infinity;
|
|
23913
|
+
return (sign ? -1 : 1) * Math.pow(2, exp - 15) * (1 + frac / 1024);
|
|
23914
|
+
}
|
|
23915
|
+
async function initHdrReadback(page, width, height) {
|
|
23916
|
+
return page.evaluate(
|
|
23917
|
+
async (w, h3) => {
|
|
23918
|
+
if (!navigator.gpu) return false;
|
|
23919
|
+
const adapter2 = await navigator.gpu.requestAdapter();
|
|
23920
|
+
if (!adapter2) return false;
|
|
23921
|
+
const device = await adapter2.requestDevice();
|
|
23922
|
+
const bytesPerPixel = 8;
|
|
23923
|
+
const bytesPerRow = Math.ceil(w * bytesPerPixel / 256) * 256;
|
|
23924
|
+
const renderTexture = device.createTexture({
|
|
23925
|
+
size: [w, h3],
|
|
23926
|
+
format: "rgba16float",
|
|
23927
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING
|
|
23928
|
+
});
|
|
23929
|
+
const readBuffer = device.createBuffer({
|
|
23930
|
+
size: bytesPerRow * h3,
|
|
23931
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
|
|
23932
|
+
});
|
|
23933
|
+
const captureRuntime = {
|
|
23934
|
+
device,
|
|
23935
|
+
renderTexture,
|
|
23936
|
+
readBuffer,
|
|
23937
|
+
bytesPerRow,
|
|
23938
|
+
width: w,
|
|
23939
|
+
height: h3,
|
|
23940
|
+
/**
|
|
23941
|
+
* Upload pre-converted float16 RGBA data and read it back.
|
|
23942
|
+
* The float16 data must be row-aligned to bytesPerRow.
|
|
23943
|
+
*
|
|
23944
|
+
* Input: base64-encoded Uint16Array (float16 RGBA, row-padded)
|
|
23945
|
+
* Output: base64-encoded readback of the same texture
|
|
23946
|
+
*/
|
|
23947
|
+
async uploadAndReadback(float16Base64) {
|
|
23948
|
+
const binary = atob(float16Base64);
|
|
23949
|
+
const bytes = new Uint8Array(binary.length);
|
|
23950
|
+
for (let i2 = 0; i2 < binary.length; i2++) bytes[i2] = binary.charCodeAt(i2);
|
|
23951
|
+
device.queue.writeTexture(
|
|
23952
|
+
{ texture: renderTexture },
|
|
23953
|
+
bytes.buffer,
|
|
23954
|
+
{ bytesPerRow, rowsPerImage: h3 },
|
|
23955
|
+
[w, h3]
|
|
23956
|
+
);
|
|
23957
|
+
const encoder = device.createCommandEncoder();
|
|
23958
|
+
encoder.copyTextureToBuffer(
|
|
23959
|
+
{ texture: renderTexture },
|
|
23960
|
+
{ buffer: readBuffer, bytesPerRow },
|
|
23961
|
+
[w, h3]
|
|
23962
|
+
);
|
|
23963
|
+
device.queue.submit([encoder.finish()]);
|
|
23964
|
+
await readBuffer.mapAsync(GPUMapMode.READ);
|
|
23965
|
+
const readBytes = new Uint8Array(readBuffer.getMappedRange().slice(0));
|
|
23966
|
+
readBuffer.unmap();
|
|
23967
|
+
let b64 = "";
|
|
23968
|
+
const chunkSize = 32768;
|
|
23969
|
+
for (let i2 = 0; i2 < readBytes.length; i2 += chunkSize) {
|
|
23970
|
+
const slice = readBytes.subarray(i2, Math.min(i2 + chunkSize, readBytes.length));
|
|
23971
|
+
b64 += String.fromCharCode(...slice);
|
|
23972
|
+
}
|
|
23973
|
+
return { base64: btoa(b64), bytesPerRow };
|
|
23974
|
+
}
|
|
23975
|
+
};
|
|
23976
|
+
window.__hfHdrCapture = captureRuntime;
|
|
23977
|
+
return true;
|
|
23978
|
+
},
|
|
23979
|
+
width,
|
|
23980
|
+
height
|
|
23981
|
+
);
|
|
23982
|
+
}
|
|
23983
|
+
async function uploadAndReadbackHdrFrame(page, float16Base64) {
|
|
23984
|
+
const result = await page.evaluate(
|
|
23985
|
+
async (b64) => {
|
|
23986
|
+
const hdr = window.__hfHdrCapture;
|
|
23987
|
+
if (!hdr) throw new Error("HDR capture not initialized");
|
|
23988
|
+
return hdr.uploadAndReadback(b64);
|
|
23989
|
+
},
|
|
23990
|
+
float16Base64
|
|
23991
|
+
);
|
|
23992
|
+
return {
|
|
23993
|
+
rawBuffer: Buffer.from(result.base64, "base64"),
|
|
23994
|
+
bytesPerRow: result.bytesPerRow
|
|
23995
|
+
};
|
|
23996
|
+
}
|
|
23997
|
+
function float16ToPqRgb(rawBuffer, bytesPerRow, width, height) {
|
|
23998
|
+
const data = new Uint16Array(rawBuffer.buffer, rawBuffer.byteOffset, rawBuffer.byteLength / 2);
|
|
23999
|
+
const channelsPerRow = bytesPerRow / 2;
|
|
24000
|
+
const output = Buffer.alloc(width * height * 6);
|
|
24001
|
+
for (let y2 = 0; y2 < height; y2++) {
|
|
24002
|
+
for (let x4 = 0; x4 < width; x4++) {
|
|
24003
|
+
const srcIdx = y2 * channelsPerRow + x4 * 4;
|
|
24004
|
+
const r2 = float16Decode(data[srcIdx] ?? 0);
|
|
24005
|
+
const g = float16Decode(data[srcIdx + 1] ?? 0);
|
|
24006
|
+
const b = float16Decode(data[srcIdx + 2] ?? 0);
|
|
24007
|
+
const dstIdx = (y2 * width + x4) * 6;
|
|
24008
|
+
output.writeUInt16LE(Math.round(Math.min(1, linearToPQ(r2)) * 65535), dstIdx);
|
|
24009
|
+
output.writeUInt16LE(Math.round(Math.min(1, linearToPQ(g)) * 65535), dstIdx + 2);
|
|
24010
|
+
output.writeUInt16LE(Math.round(Math.min(1, linearToPQ(b)) * 65535), dstIdx + 4);
|
|
24011
|
+
}
|
|
24012
|
+
}
|
|
24013
|
+
return output;
|
|
24014
|
+
}
|
|
24015
|
+
function resolveHeadedChromePath() {
|
|
24016
|
+
const baseDir = join25(homedir6(), ".cache", "puppeteer", "chrome");
|
|
24017
|
+
if (!existsSync22(baseDir)) return void 0;
|
|
24018
|
+
const versions = readdirSync10(baseDir).sort().reverse();
|
|
24019
|
+
for (const version of versions) {
|
|
24020
|
+
const candidates = [
|
|
24021
|
+
join25(
|
|
24022
|
+
baseDir,
|
|
24023
|
+
version,
|
|
24024
|
+
"chrome-mac-arm64",
|
|
24025
|
+
"Google Chrome for Testing.app",
|
|
24026
|
+
"Contents",
|
|
24027
|
+
"MacOS",
|
|
24028
|
+
"Google Chrome for Testing"
|
|
24029
|
+
),
|
|
24030
|
+
join25(
|
|
24031
|
+
baseDir,
|
|
24032
|
+
version,
|
|
24033
|
+
"chrome-mac-x64",
|
|
24034
|
+
"Google Chrome for Testing.app",
|
|
24035
|
+
"Contents",
|
|
24036
|
+
"MacOS",
|
|
24037
|
+
"Google Chrome for Testing"
|
|
24038
|
+
),
|
|
24039
|
+
join25(baseDir, version, "chrome-linux64", "chrome"),
|
|
24040
|
+
join25(baseDir, version, "chrome-win64", "chrome.exe")
|
|
24041
|
+
];
|
|
24042
|
+
for (const binary of candidates) {
|
|
24043
|
+
if (existsSync22(binary)) return binary;
|
|
24044
|
+
}
|
|
24045
|
+
}
|
|
24046
|
+
return void 0;
|
|
24047
|
+
}
|
|
24048
|
+
async function launchHdrBrowser(width, height) {
|
|
24049
|
+
let ppt;
|
|
24050
|
+
try {
|
|
24051
|
+
const mod = await import("puppeteer");
|
|
24052
|
+
ppt = mod.default;
|
|
24053
|
+
} catch (err) {
|
|
24054
|
+
const code = err?.code;
|
|
24055
|
+
if (code !== "ERR_MODULE_NOT_FOUND" && code !== "MODULE_NOT_FOUND") {
|
|
24056
|
+
throw err;
|
|
24057
|
+
}
|
|
24058
|
+
const mod = await import("puppeteer-core");
|
|
24059
|
+
ppt = mod.default;
|
|
24060
|
+
}
|
|
24061
|
+
if (!ppt) throw new Error("Neither puppeteer nor puppeteer-core found");
|
|
24062
|
+
const chromePath = resolveHeadedChromePath();
|
|
24063
|
+
if (!chromePath) {
|
|
24064
|
+
throw new Error(
|
|
24065
|
+
"[HDR] No Chrome binary found. Install: npx @puppeteer/browsers install chrome@stable"
|
|
24066
|
+
);
|
|
24067
|
+
}
|
|
24068
|
+
const browser = await ppt.launch({
|
|
24069
|
+
headless: false,
|
|
24070
|
+
executablePath: chromePath,
|
|
24071
|
+
args: buildHdrChromeArgs(width, height)
|
|
24072
|
+
});
|
|
24073
|
+
const page = await browser.newPage();
|
|
24074
|
+
await page.setViewport({ width, height });
|
|
24075
|
+
return { browser, page };
|
|
24076
|
+
}
|
|
24077
|
+
function buildHdrChromeArgs(width, height) {
|
|
24078
|
+
return [
|
|
24079
|
+
"--enable-unsafe-webgpu",
|
|
24080
|
+
"--no-sandbox",
|
|
24081
|
+
"--disable-setuid-sandbox",
|
|
24082
|
+
"--window-position=-10000,-10000",
|
|
24083
|
+
`--window-size=${width},${height}`,
|
|
24084
|
+
"--disable-background-timer-throttling",
|
|
24085
|
+
"--disable-backgrounding-occluded-windows",
|
|
24086
|
+
"--disable-renderer-backgrounding",
|
|
24087
|
+
"--disable-background-media-suspend",
|
|
24088
|
+
"--disable-extensions",
|
|
24089
|
+
"--disable-component-update",
|
|
24090
|
+
"--disable-default-apps",
|
|
24091
|
+
"--disable-sync",
|
|
24092
|
+
"--no-zygote",
|
|
24093
|
+
"--force-gpu-mem-available-mb=4096"
|
|
24094
|
+
];
|
|
24095
|
+
}
|
|
24096
|
+
var PQ_M1, PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_MAX_NITS, SDR_NITS;
|
|
24097
|
+
var init_hdrCapture = __esm({
|
|
24098
|
+
"../engine/src/services/hdrCapture.ts"() {
|
|
24099
|
+
"use strict";
|
|
24100
|
+
PQ_M1 = 0.1593017578125;
|
|
24101
|
+
PQ_M2 = 78.84375;
|
|
24102
|
+
PQ_C1 = 0.8359375;
|
|
24103
|
+
PQ_C2 = 18.8515625;
|
|
24104
|
+
PQ_C3 = 18.6875;
|
|
24105
|
+
PQ_MAX_NITS = 1e4;
|
|
24106
|
+
SDR_NITS = 203;
|
|
24107
|
+
}
|
|
24108
|
+
});
|
|
24109
|
+
|
|
23237
24110
|
// ../engine/src/index.ts
|
|
23238
24111
|
var src_exports = {};
|
|
23239
24112
|
__export(src_exports, {
|
|
@@ -23243,13 +24116,19 @@ __export(src_exports, {
|
|
|
23243
24116
|
FrameLookupTable: () => FrameLookupTable,
|
|
23244
24117
|
MEDIA_VISUAL_STYLE_PROPERTIES: () => MEDIA_VISUAL_STYLE_PROPERTIES,
|
|
23245
24118
|
acquireBrowser: () => acquireBrowser,
|
|
24119
|
+
analyzeCompositionHdr: () => analyzeCompositionHdr,
|
|
23246
24120
|
analyzeKeyframeIntervals: () => analyzeKeyframeIntervals,
|
|
23247
24121
|
applyFaststart: () => applyFaststart,
|
|
23248
24122
|
beginFrameCapture: () => beginFrameCapture,
|
|
24123
|
+
blitRgb48leRegion: () => blitRgb48leRegion,
|
|
24124
|
+
blitRgba8OverRgb48le: () => blitRgba8OverRgb48le,
|
|
23249
24125
|
buildChromeArgs: () => buildChromeArgs,
|
|
24126
|
+
buildHdrChromeArgs: () => buildHdrChromeArgs,
|
|
23250
24127
|
calculateOptimalWorkers: () => calculateOptimalWorkers,
|
|
24128
|
+
captureAlphaPng: () => captureAlphaPng,
|
|
23251
24129
|
captureFrame: () => captureFrame,
|
|
23252
24130
|
captureFrameToBuffer: () => captureFrameToBuffer,
|
|
24131
|
+
captureScreenshotWithAlpha: () => captureScreenshotWithAlpha,
|
|
23253
24132
|
cdpSessionCache: () => cdpSessionCache,
|
|
23254
24133
|
closeCaptureSession: () => closeCaptureSession,
|
|
23255
24134
|
createCaptureSession: () => createCaptureSession,
|
|
@@ -23257,7 +24136,10 @@ __export(src_exports, {
|
|
|
23257
24136
|
createFrameLookupTable: () => createFrameLookupTable,
|
|
23258
24137
|
createFrameReorderBuffer: () => createFrameReorderBuffer,
|
|
23259
24138
|
createVideoFrameInjector: () => createVideoFrameInjector,
|
|
24139
|
+
decodePng: () => decodePng,
|
|
24140
|
+
decodePngToRgb48le: () => decodePngToRgb48le,
|
|
23260
24141
|
detectGpuEncoder: () => detectGpuEncoder,
|
|
24142
|
+
detectTransfer: () => detectTransfer,
|
|
23261
24143
|
distributeFrames: () => distributeFrames,
|
|
23262
24144
|
downloadToTemp: () => downloadToTemp,
|
|
23263
24145
|
encodeFramesChunkedConcat: () => encodeFramesChunkedConcat,
|
|
@@ -23267,15 +24149,24 @@ __export(src_exports, {
|
|
|
23267
24149
|
extractAudioMetadata: () => extractAudioMetadata,
|
|
23268
24150
|
extractVideoFramesRange: () => extractVideoFramesRange,
|
|
23269
24151
|
extractVideoMetadata: () => extractVideoMetadata,
|
|
24152
|
+
float16ToPqRgb: () => float16ToPqRgb,
|
|
23270
24153
|
getCapturePerfSummary: () => getCapturePerfSummary,
|
|
23271
24154
|
getCdpSession: () => getCdpSession,
|
|
23272
24155
|
getCompositionDuration: () => getCompositionDuration,
|
|
23273
24156
|
getEncoderPreset: () => getEncoderPreset,
|
|
23274
24157
|
getFrameAtTime: () => getFrameAtTime,
|
|
24158
|
+
getHdrEncoderColorParams: () => getHdrEncoderColorParams,
|
|
24159
|
+
getSrgbToHdrLut: () => getSrgbToHdrLut,
|
|
23275
24160
|
getSystemResources: () => getSystemResources,
|
|
24161
|
+
groupIntoLayers: () => groupIntoLayers,
|
|
24162
|
+
hideVideoElements: () => hideVideoElements,
|
|
24163
|
+
initHdrReadback: () => initHdrReadback,
|
|
24164
|
+
initTransparentBackground: () => initTransparentBackground,
|
|
23276
24165
|
initializeSession: () => initializeSession,
|
|
23277
24166
|
injectVideoFramesBatch: () => injectVideoFramesBatch,
|
|
24167
|
+
isHdrColorSpace: () => isHdrColorSpace,
|
|
23278
24168
|
isHttpUrl: () => isHttpUrl,
|
|
24169
|
+
launchHdrBrowser: () => launchHdrBrowser,
|
|
23279
24170
|
mergeWorkerFrames: () => mergeWorkerFrames,
|
|
23280
24171
|
muxVideoWithAudio: () => muxVideoWithAudio,
|
|
23281
24172
|
pageScreenshotCapture: () => pageScreenshotCapture,
|
|
@@ -23284,11 +24175,15 @@ __export(src_exports, {
|
|
|
23284
24175
|
prepareCaptureSessionForReuse: () => prepareCaptureSessionForReuse,
|
|
23285
24176
|
processCompositionAudio: () => processCompositionAudio,
|
|
23286
24177
|
quantizeTimeToFrame: () => quantizeTimeToFrame,
|
|
24178
|
+
queryElementStacking: () => queryElementStacking,
|
|
24179
|
+
queryVideoElementBounds: () => queryVideoElementBounds,
|
|
23287
24180
|
releaseBrowser: () => releaseBrowser,
|
|
23288
24181
|
resolveConfig: () => resolveConfig,
|
|
23289
24182
|
resolveHeadlessShellPath: () => resolveHeadlessShellPath,
|
|
24183
|
+
showVideoElements: () => showVideoElements,
|
|
23290
24184
|
spawnStreamingEncoder: () => spawnStreamingEncoder,
|
|
23291
|
-
syncVideoFrameVisibility: () => syncVideoFrameVisibility
|
|
24185
|
+
syncVideoFrameVisibility: () => syncVideoFrameVisibility,
|
|
24186
|
+
uploadAndReadbackHdrFrame: () => uploadAndReadbackHdrFrame
|
|
23292
24187
|
});
|
|
23293
24188
|
var init_src2 = __esm({
|
|
23294
24189
|
"../engine/src/index.ts"() {
|
|
@@ -23307,6 +24202,12 @@ var init_src2 = __esm({
|
|
|
23307
24202
|
init_src();
|
|
23308
24203
|
init_ffprobe();
|
|
23309
24204
|
init_urlDownloader();
|
|
24205
|
+
init_alphaBlit();
|
|
24206
|
+
init_layerCompositor();
|
|
24207
|
+
init_hdrCapture();
|
|
24208
|
+
init_screenshotService();
|
|
24209
|
+
init_videoFrameInjector();
|
|
24210
|
+
init_hdr();
|
|
23310
24211
|
}
|
|
23311
24212
|
});
|
|
23312
24213
|
|
|
@@ -23391,8 +24292,8 @@ var init_staticGuard = __esm({
|
|
|
23391
24292
|
});
|
|
23392
24293
|
|
|
23393
24294
|
// ../core/src/compiler/htmlBundler.ts
|
|
23394
|
-
import { readFileSync as readFileSync15, existsSync as
|
|
23395
|
-
import { join as
|
|
24295
|
+
import { readFileSync as readFileSync15, existsSync as existsSync23 } from "fs";
|
|
24296
|
+
import { join as join26, resolve as resolve11, isAbsolute as isAbsolute2, sep as sep2 } from "path";
|
|
23396
24297
|
import { transformSync } from "esbuild";
|
|
23397
24298
|
function parseHTMLContent(html) {
|
|
23398
24299
|
const trimmed = html.trimStart().toLowerCase();
|
|
@@ -23460,7 +24361,7 @@ function isRelativeUrl(url) {
|
|
|
23460
24361
|
return !url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("//") && !url.startsWith("data:") && !isAbsolute2(url);
|
|
23461
24362
|
}
|
|
23462
24363
|
function safeReadFile(filePath) {
|
|
23463
|
-
if (!
|
|
24364
|
+
if (!existsSync23(filePath)) return null;
|
|
23464
24365
|
try {
|
|
23465
24366
|
return readFileSync15(filePath, "utf-8");
|
|
23466
24367
|
} catch {
|
|
@@ -23468,7 +24369,7 @@ function safeReadFile(filePath) {
|
|
|
23468
24369
|
}
|
|
23469
24370
|
}
|
|
23470
24371
|
function safeReadFileBuffer(filePath) {
|
|
23471
|
-
if (!
|
|
24372
|
+
if (!existsSync23(filePath)) return null;
|
|
23472
24373
|
try {
|
|
23473
24374
|
return readFileSync15(filePath);
|
|
23474
24375
|
} catch {
|
|
@@ -23661,8 +24562,8 @@ function stripJsCommentsParserSafe(source) {
|
|
|
23661
24562
|
}
|
|
23662
24563
|
}
|
|
23663
24564
|
async function bundleToSingleHtml(projectDir, options) {
|
|
23664
|
-
const indexPath =
|
|
23665
|
-
if (!
|
|
24565
|
+
const indexPath = join26(projectDir, "index.html");
|
|
24566
|
+
if (!existsSync23(indexPath)) throw new Error("index.html not found in project directory");
|
|
23666
24567
|
const rawHtml = readFileSync15(indexPath, "utf-8");
|
|
23667
24568
|
const compiled = await compileHtml(rawHtml, projectDir, options?.probeMediaDuration);
|
|
23668
24569
|
const staticGuard = validateHyperframeHtmlContract(compiled);
|
|
@@ -23938,7 +24839,7 @@ var init_compiler = __esm({
|
|
|
23938
24839
|
|
|
23939
24840
|
// ../producer/src/services/hyperframeRuntimeLoader.ts
|
|
23940
24841
|
import { createHash as createHash2 } from "crypto";
|
|
23941
|
-
import { existsSync as
|
|
24842
|
+
import { existsSync as existsSync24, readFileSync as readFileSync16 } from "fs";
|
|
23942
24843
|
import { dirname as dirname8, resolve as resolve12 } from "path";
|
|
23943
24844
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
23944
24845
|
function resolveHyperframeManifestPath() {
|
|
@@ -23951,7 +24852,7 @@ function resolveHyperframeManifestPath() {
|
|
|
23951
24852
|
MODULE_RELATIVE_MANIFEST_PATH
|
|
23952
24853
|
];
|
|
23953
24854
|
for (const candidate of candidates) {
|
|
23954
|
-
if (
|
|
24855
|
+
if (existsSync24(candidate)) {
|
|
23955
24856
|
return candidate;
|
|
23956
24857
|
}
|
|
23957
24858
|
}
|
|
@@ -23962,7 +24863,7 @@ function getVerifiedHyperframeRuntimeSource() {
|
|
|
23962
24863
|
}
|
|
23963
24864
|
function resolveVerifiedHyperframeRuntime() {
|
|
23964
24865
|
const manifestPath = resolveHyperframeManifestPath();
|
|
23965
|
-
if (!
|
|
24866
|
+
if (!existsSync24(manifestPath)) {
|
|
23966
24867
|
throw new Error(
|
|
23967
24868
|
`[HyperframeRuntimeLoader] Missing manifest at ${manifestPath}. Build core runtime artifacts before rendering.`
|
|
23968
24869
|
);
|
|
@@ -23976,7 +24877,7 @@ function resolveVerifiedHyperframeRuntime() {
|
|
|
23976
24877
|
);
|
|
23977
24878
|
}
|
|
23978
24879
|
const runtimePath = resolve12(dirname8(manifestPath), runtimeFileName);
|
|
23979
|
-
if (!
|
|
24880
|
+
if (!existsSync24(runtimePath)) {
|
|
23980
24881
|
throw new Error(`[HyperframeRuntimeLoader] Missing runtime artifact at ${runtimePath}.`);
|
|
23981
24882
|
}
|
|
23982
24883
|
const runtimeSource = readFileSync16(runtimePath, "utf8");
|
|
@@ -24018,8 +24919,8 @@ var init_hyperframeRuntimeLoader = __esm({
|
|
|
24018
24919
|
// ../producer/src/services/fileServer.ts
|
|
24019
24920
|
import { Hono as Hono3 } from "hono";
|
|
24020
24921
|
import { serve as serve2 } from "@hono/node-server";
|
|
24021
|
-
import { readFileSync as readFileSync17, existsSync as
|
|
24022
|
-
import { join as
|
|
24922
|
+
import { readFileSync as readFileSync17, existsSync as existsSync25, statSync as statSync7 } from "fs";
|
|
24923
|
+
import { join as join27, extname as extname6 } from "path";
|
|
24023
24924
|
function stripEmbeddedRuntimeScripts3(html) {
|
|
24024
24925
|
if (!html) return html;
|
|
24025
24926
|
const scriptRe = /<script\b[^>]*>[\s\S]*?<\/script>/gi;
|
|
@@ -24079,8 +24980,22 @@ function injectScriptsIntoHtml2(html, headScripts, bodyScripts, stripEmbedded) {
|
|
|
24079
24980
|
}
|
|
24080
24981
|
return html;
|
|
24081
24982
|
}
|
|
24983
|
+
function injectScriptsAtHeadStart(html, scripts) {
|
|
24984
|
+
if (scripts.length === 0) return html;
|
|
24985
|
+
const headTags = scripts.map((src) => `<script>${src}</script>`).join("\n");
|
|
24986
|
+
if (html.includes("<head")) {
|
|
24987
|
+
return html.replace(/<head\b[^>]*>/i, (match) => `${match}
|
|
24988
|
+
${headTags}`);
|
|
24989
|
+
}
|
|
24990
|
+
if (html.includes("<body")) {
|
|
24991
|
+
return html.replace("<body", () => `${headTags}
|
|
24992
|
+
<body`);
|
|
24993
|
+
}
|
|
24994
|
+
return headTags + "\n" + html;
|
|
24995
|
+
}
|
|
24082
24996
|
function createFileServer2(options) {
|
|
24083
24997
|
const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
|
|
24998
|
+
const preHeadScripts = [HF_EARLY_STUB, ...options.preHeadScripts ?? []];
|
|
24084
24999
|
const headScripts = options.headScripts ?? [getVerifiedHyperframeRuntimeSource()];
|
|
24085
25000
|
const bodyScripts = options.bodyScripts ?? [RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT];
|
|
24086
25001
|
const app = new Hono3();
|
|
@@ -24088,12 +25003,12 @@ function createFileServer2(options) {
|
|
|
24088
25003
|
let requestPath = c2.req.path;
|
|
24089
25004
|
if (requestPath === "/") requestPath = "/index.html";
|
|
24090
25005
|
const relativePath = requestPath.replace(/^\//, "");
|
|
24091
|
-
const compiledPath = compiledDir ?
|
|
25006
|
+
const compiledPath = compiledDir ? join27(compiledDir, relativePath) : null;
|
|
24092
25007
|
const hasCompiledFile = Boolean(
|
|
24093
|
-
compiledPath &&
|
|
25008
|
+
compiledPath && existsSync25(compiledPath) && statSync7(compiledPath).isFile()
|
|
24094
25009
|
);
|
|
24095
|
-
const filePath = hasCompiledFile ? compiledPath :
|
|
24096
|
-
if (!
|
|
25010
|
+
const filePath = hasCompiledFile ? compiledPath : join27(projectDir, relativePath);
|
|
25011
|
+
if (!existsSync25(filePath) || !statSync7(filePath).isFile()) {
|
|
24097
25012
|
if (!/favicon\.ico$/i.test(requestPath)) {
|
|
24098
25013
|
console.warn(`[FileServer] 404 Not Found: ${requestPath}`);
|
|
24099
25014
|
}
|
|
@@ -24104,7 +25019,11 @@ function createFileServer2(options) {
|
|
|
24104
25019
|
if (ext === ".html") {
|
|
24105
25020
|
const rawHtml = readFileSync17(filePath, "utf-8");
|
|
24106
25021
|
const isIndex = relativePath === "index.html";
|
|
24107
|
-
|
|
25022
|
+
let html = rawHtml;
|
|
25023
|
+
if (preHeadScripts.length > 0) {
|
|
25024
|
+
html = injectScriptsAtHeadStart(html, preHeadScripts);
|
|
25025
|
+
}
|
|
25026
|
+
html = isIndex ? injectScriptsIntoHtml2(html, headScripts, bodyScripts, stripEmbeddedRuntime) : html;
|
|
24108
25027
|
return c2.text(html, 200, { "Content-Type": contentType });
|
|
24109
25028
|
}
|
|
24110
25029
|
const content = readFileSync17(filePath);
|
|
@@ -24132,7 +25051,7 @@ function createFileServer2(options) {
|
|
|
24132
25051
|
});
|
|
24133
25052
|
});
|
|
24134
25053
|
}
|
|
24135
|
-
var MIME_TYPES3, RENDER_SEEK_MODE, RENDER_SEEK_DIAGNOSTICS, RENDER_SEEK_STEP, RENDER_SEEK_OFFSET_FRACTION, RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT;
|
|
25054
|
+
var MIME_TYPES3, VIRTUAL_TIME_SHIM, RENDER_SEEK_MODE, RENDER_SEEK_DIAGNOSTICS, RENDER_SEEK_STEP, RENDER_SEEK_OFFSET_FRACTION, RENDER_MODE_SCRIPT, HF_EARLY_STUB, HF_BRIDGE_SCRIPT;
|
|
24136
25055
|
var init_fileServer2 = __esm({
|
|
24137
25056
|
"../producer/src/services/fileServer.ts"() {
|
|
24138
25057
|
"use strict";
|
|
@@ -24159,6 +25078,102 @@ var init_fileServer2 = __esm({
|
|
|
24159
25078
|
".ttf": "font/ttf",
|
|
24160
25079
|
".otf": "font/otf"
|
|
24161
25080
|
};
|
|
25081
|
+
VIRTUAL_TIME_SHIM = String.raw`(function() {
|
|
25082
|
+
if (window.__HF_VIRTUAL_TIME__) return;
|
|
25083
|
+
|
|
25084
|
+
var virtualNowMs = 0;
|
|
25085
|
+
var rafId = 1;
|
|
25086
|
+
var rafQueue = [];
|
|
25087
|
+
var OriginalDate = Date;
|
|
25088
|
+
var originalSetTimeout = window.setTimeout.bind(window);
|
|
25089
|
+
var originalClearTimeout = window.clearTimeout.bind(window);
|
|
25090
|
+
var originalSetInterval = window.setInterval.bind(window);
|
|
25091
|
+
var originalClearInterval = window.clearInterval.bind(window);
|
|
25092
|
+
var originalRequestAnimationFrame = window.requestAnimationFrame
|
|
25093
|
+
? window.requestAnimationFrame.bind(window)
|
|
25094
|
+
: null;
|
|
25095
|
+
var originalCancelAnimationFrame = window.cancelAnimationFrame
|
|
25096
|
+
? window.cancelAnimationFrame.bind(window)
|
|
25097
|
+
: null;
|
|
25098
|
+
|
|
25099
|
+
function flushAnimationFrame() {
|
|
25100
|
+
if (!rafQueue.length) return;
|
|
25101
|
+
var current = rafQueue.slice();
|
|
25102
|
+
rafQueue.length = 0;
|
|
25103
|
+
for (var i = 0; i < current.length; i++) {
|
|
25104
|
+
var entry = current[i];
|
|
25105
|
+
if (entry.cancelled) continue;
|
|
25106
|
+
try {
|
|
25107
|
+
entry.callback(virtualNowMs);
|
|
25108
|
+
} catch {}
|
|
25109
|
+
}
|
|
25110
|
+
}
|
|
25111
|
+
|
|
25112
|
+
function VirtualDate() {
|
|
25113
|
+
var args = Array.prototype.slice.call(arguments);
|
|
25114
|
+
if (!(this instanceof VirtualDate)) {
|
|
25115
|
+
return OriginalDate.apply(null, args.length ? args : [virtualNowMs]);
|
|
25116
|
+
}
|
|
25117
|
+
var instance = args.length ? new (Function.prototype.bind.apply(OriginalDate, [null].concat(args)))() : new OriginalDate(virtualNowMs);
|
|
25118
|
+
Object.setPrototypeOf(instance, VirtualDate.prototype);
|
|
25119
|
+
return instance;
|
|
25120
|
+
}
|
|
25121
|
+
|
|
25122
|
+
VirtualDate.prototype = OriginalDate.prototype;
|
|
25123
|
+
Object.setPrototypeOf(VirtualDate, OriginalDate);
|
|
25124
|
+
VirtualDate.now = function() { return virtualNowMs; };
|
|
25125
|
+
VirtualDate.parse = OriginalDate.parse.bind(OriginalDate);
|
|
25126
|
+
VirtualDate.UTC = OriginalDate.UTC.bind(OriginalDate);
|
|
25127
|
+
|
|
25128
|
+
try {
|
|
25129
|
+
Object.defineProperty(window, "Date", {
|
|
25130
|
+
configurable: true,
|
|
25131
|
+
writable: true,
|
|
25132
|
+
value: VirtualDate,
|
|
25133
|
+
});
|
|
25134
|
+
} catch {}
|
|
25135
|
+
|
|
25136
|
+
if (window.performance && typeof window.performance.now === "function") {
|
|
25137
|
+
try {
|
|
25138
|
+
Object.defineProperty(window.performance, "now", {
|
|
25139
|
+
configurable: true,
|
|
25140
|
+
value: function() { return virtualNowMs; },
|
|
25141
|
+
});
|
|
25142
|
+
} catch {}
|
|
25143
|
+
}
|
|
25144
|
+
|
|
25145
|
+
window.requestAnimationFrame = function(callback) {
|
|
25146
|
+
if (typeof callback !== "function") return 0;
|
|
25147
|
+
var entry = { id: rafId++, callback: callback, cancelled: false };
|
|
25148
|
+
rafQueue.push(entry);
|
|
25149
|
+
return entry.id;
|
|
25150
|
+
};
|
|
25151
|
+
window.cancelAnimationFrame = function(id) {
|
|
25152
|
+
for (var i = 0; i < rafQueue.length; i++) {
|
|
25153
|
+
if (rafQueue[i].id === id) {
|
|
25154
|
+
rafQueue[i].cancelled = true;
|
|
25155
|
+
}
|
|
25156
|
+
}
|
|
25157
|
+
};
|
|
25158
|
+
|
|
25159
|
+
window.__HF_VIRTUAL_TIME__ = {
|
|
25160
|
+
originalSetTimeout: originalSetTimeout,
|
|
25161
|
+
originalClearTimeout: originalClearTimeout,
|
|
25162
|
+
originalSetInterval: originalSetInterval,
|
|
25163
|
+
originalClearInterval: originalClearInterval,
|
|
25164
|
+
originalRequestAnimationFrame: originalRequestAnimationFrame,
|
|
25165
|
+
originalCancelAnimationFrame: originalCancelAnimationFrame,
|
|
25166
|
+
seekToTime: function(nextTimeMs) {
|
|
25167
|
+
var safeTimeMs = Math.max(0, Number(nextTimeMs) || 0);
|
|
25168
|
+
virtualNowMs = safeTimeMs;
|
|
25169
|
+
flushAnimationFrame();
|
|
25170
|
+
return virtualNowMs;
|
|
25171
|
+
},
|
|
25172
|
+
getTime: function() {
|
|
25173
|
+
return virtualNowMs;
|
|
25174
|
+
},
|
|
25175
|
+
};
|
|
25176
|
+
})();`;
|
|
24162
25177
|
RENDER_SEEK_MODE = process.env.PRODUCER_RUNTIME_RENDER_SEEK_MODE === "strict-boundary" ? "strict-boundary" : "preview-phase";
|
|
24163
25178
|
RENDER_SEEK_DIAGNOSTICS = process.env.PRODUCER_DEBUG_SEEK_DIAGNOSTICS === "true";
|
|
24164
25179
|
RENDER_SEEK_STEP = Math.max(
|
|
@@ -24170,6 +25185,10 @@ var init_fileServer2 = __esm({
|
|
|
24170
25185
|
Math.min(0.95, Number(process.env.PRODUCER_RUNTIME_RENDER_SEEK_OFFSET_FRACTION || 0.5))
|
|
24171
25186
|
);
|
|
24172
25187
|
RENDER_MODE_SCRIPT = `(function() {
|
|
25188
|
+
var __realSetTimeout =
|
|
25189
|
+
window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetTimeout === "function"
|
|
25190
|
+
? window.__HF_VIRTUAL_TIME__.originalSetTimeout
|
|
25191
|
+
: window.setTimeout.bind(window);
|
|
24173
25192
|
var __seekMode = ${JSON.stringify(RENDER_SEEK_MODE)};
|
|
24174
25193
|
var __seekDiagnostics = ${RENDER_SEEK_DIAGNOSTICS ? "true" : "false"};
|
|
24175
25194
|
var __seekStep = ${RENDER_SEEK_STEP};
|
|
@@ -24263,40 +25282,88 @@ var init_fileServer2 = __esm({
|
|
|
24263
25282
|
window.__renderReady = true;
|
|
24264
25283
|
return;
|
|
24265
25284
|
}
|
|
24266
|
-
|
|
25285
|
+
__realSetTimeout(waitForPlayer, 50);
|
|
24267
25286
|
return;
|
|
24268
25287
|
}
|
|
24269
25288
|
if (installMediaFallbackPlayer()) {
|
|
24270
25289
|
return;
|
|
24271
25290
|
}
|
|
24272
|
-
|
|
25291
|
+
__realSetTimeout(waitForPlayer, 50);
|
|
24273
25292
|
}
|
|
24274
25293
|
waitForPlayer();
|
|
25294
|
+
})();`;
|
|
25295
|
+
HF_EARLY_STUB = `(function() {
|
|
25296
|
+
if (typeof window === "undefined") return;
|
|
25297
|
+
if (!window.__hf) window.__hf = {};
|
|
24275
25298
|
})();`;
|
|
24276
25299
|
HF_BRIDGE_SCRIPT = `(function() {
|
|
25300
|
+
var __realSetInterval =
|
|
25301
|
+
window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalSetInterval === "function"
|
|
25302
|
+
? window.__HF_VIRTUAL_TIME__.originalSetInterval
|
|
25303
|
+
: window.setInterval.bind(window);
|
|
25304
|
+
var __realClearInterval =
|
|
25305
|
+
window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.originalClearInterval === "function"
|
|
25306
|
+
? window.__HF_VIRTUAL_TIME__.originalClearInterval
|
|
25307
|
+
: window.clearInterval.bind(window);
|
|
24277
25308
|
function getDeclaredDuration() {
|
|
24278
25309
|
var root = document.querySelector('[data-composition-id]');
|
|
24279
25310
|
if (!root) return 0;
|
|
24280
25311
|
var d = Number(root.getAttribute('data-duration'));
|
|
24281
25312
|
return Number.isFinite(d) && d > 0 ? d : 0;
|
|
24282
25313
|
}
|
|
25314
|
+
function seekSameOriginChildFrames(frameWindow, nextTimeMs) {
|
|
25315
|
+
var frames;
|
|
25316
|
+
try {
|
|
25317
|
+
frames = frameWindow.frames;
|
|
25318
|
+
} catch (_error) {
|
|
25319
|
+
return;
|
|
25320
|
+
}
|
|
25321
|
+
if (!frames || typeof frames.length !== "number") return;
|
|
25322
|
+
for (var i = 0; i < frames.length; i++) {
|
|
25323
|
+
var childWindow = null;
|
|
25324
|
+
try {
|
|
25325
|
+
childWindow = frames[i];
|
|
25326
|
+
if (!childWindow || childWindow === frameWindow) continue;
|
|
25327
|
+
if (
|
|
25328
|
+
childWindow.__HF_VIRTUAL_TIME__ &&
|
|
25329
|
+
typeof childWindow.__HF_VIRTUAL_TIME__.seekToTime === "function"
|
|
25330
|
+
) {
|
|
25331
|
+
childWindow.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);
|
|
25332
|
+
}
|
|
25333
|
+
} catch (_error) {
|
|
25334
|
+
continue;
|
|
25335
|
+
}
|
|
25336
|
+
seekSameOriginChildFrames(childWindow, nextTimeMs);
|
|
25337
|
+
}
|
|
25338
|
+
}
|
|
24283
25339
|
function bridge() {
|
|
24284
25340
|
var p = window.__player;
|
|
24285
25341
|
if (!p || typeof p.renderSeek !== "function" || typeof p.getDuration !== "function") {
|
|
24286
25342
|
return false;
|
|
24287
25343
|
}
|
|
24288
|
-
window.__hf
|
|
24289
|
-
|
|
25344
|
+
var hf = window.__hf || {};
|
|
25345
|
+
Object.defineProperty(hf, "duration", {
|
|
25346
|
+
configurable: true,
|
|
25347
|
+
enumerable: true,
|
|
25348
|
+
get: function() {
|
|
24290
25349
|
var d = p.getDuration();
|
|
24291
25350
|
return d > 0 ? d : getDeclaredDuration();
|
|
24292
25351
|
},
|
|
24293
|
-
|
|
25352
|
+
});
|
|
25353
|
+
hf.seek = function(t) {
|
|
25354
|
+
p.renderSeek(t);
|
|
25355
|
+
var nextTimeMs = (Math.max(0, Number(t) || 0)) * 1000;
|
|
25356
|
+
if (window.__HF_VIRTUAL_TIME__ && typeof window.__HF_VIRTUAL_TIME__.seekToTime === "function") {
|
|
25357
|
+
window.__HF_VIRTUAL_TIME__.seekToTime(nextTimeMs);
|
|
25358
|
+
}
|
|
25359
|
+
seekSameOriginChildFrames(window, nextTimeMs);
|
|
24294
25360
|
};
|
|
25361
|
+
window.__hf = hf;
|
|
24295
25362
|
return true;
|
|
24296
25363
|
}
|
|
24297
25364
|
if (bridge()) return;
|
|
24298
|
-
var iv =
|
|
24299
|
-
if (bridge())
|
|
25365
|
+
var iv = __realSetInterval(function() {
|
|
25366
|
+
if (bridge()) __realClearInterval(iv);
|
|
24300
25367
|
}, 50);
|
|
24301
25368
|
})();`;
|
|
24302
25369
|
}
|
|
@@ -24311,7 +25378,7 @@ var init_ffprobe2 = __esm({
|
|
|
24311
25378
|
});
|
|
24312
25379
|
|
|
24313
25380
|
// ../producer/src/utils/paths.ts
|
|
24314
|
-
import { resolve as resolve13, basename, join as
|
|
25381
|
+
import { resolve as resolve13, basename, join as join28, relative as relative2, isAbsolute as isAbsolute3 } from "path";
|
|
24315
25382
|
function isPathInside(childPath, parentPath) {
|
|
24316
25383
|
const absChild = resolve13(childPath);
|
|
24317
25384
|
const absParent = resolve13(parentPath);
|
|
@@ -24332,7 +25399,7 @@ function toExternalAssetKey(absPath) {
|
|
|
24332
25399
|
function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS_DIR) {
|
|
24333
25400
|
const absoluteProjectDir = resolve13(projectDir);
|
|
24334
25401
|
const projectName = basename(absoluteProjectDir);
|
|
24335
|
-
const resolvedOutputPath = outputPath ??
|
|
25402
|
+
const resolvedOutputPath = outputPath ?? join28(rendersDir, `${projectName}.mp4`);
|
|
24336
25403
|
const absoluteOutputPath = resolve13(resolvedOutputPath);
|
|
24337
25404
|
return { absoluteProjectDir, absoluteOutputPath };
|
|
24338
25405
|
}
|
|
@@ -24405,9 +25472,9 @@ var init_fontData_generated = __esm({
|
|
|
24405
25472
|
});
|
|
24406
25473
|
|
|
24407
25474
|
// ../producer/src/services/deterministicFonts.ts
|
|
24408
|
-
import { existsSync as
|
|
24409
|
-
import { homedir as
|
|
24410
|
-
import { join as
|
|
25475
|
+
import { existsSync as existsSync26, mkdirSync as mkdirSync15, readFileSync as readFileSync18, writeFileSync as writeFileSync9 } from "fs";
|
|
25476
|
+
import { homedir as homedir7 } from "os";
|
|
25477
|
+
import { join as join29 } from "path";
|
|
24411
25478
|
function normalizeFamilyName(family) {
|
|
24412
25479
|
return family.trim().replace(/^['"]|['"]$/g, "").trim().toLowerCase();
|
|
24413
25480
|
}
|
|
@@ -24518,14 +25585,14 @@ function fontSlug(familyName) {
|
|
|
24518
25585
|
return familyName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
24519
25586
|
}
|
|
24520
25587
|
function fontCacheDir(slug) {
|
|
24521
|
-
const dir =
|
|
24522
|
-
if (!
|
|
25588
|
+
const dir = join29(GOOGLE_FONTS_CACHE_DIR, slug);
|
|
25589
|
+
if (!existsSync26(dir)) {
|
|
24523
25590
|
mkdirSync15(dir, { recursive: true });
|
|
24524
25591
|
}
|
|
24525
25592
|
return dir;
|
|
24526
25593
|
}
|
|
24527
25594
|
function cachedWoff2Path(slug, weight, style) {
|
|
24528
|
-
return
|
|
25595
|
+
return join29(fontCacheDir(slug), `${weight}-${style}.woff2`);
|
|
24529
25596
|
}
|
|
24530
25597
|
async function fetchGoogleFont(familyName) {
|
|
24531
25598
|
const slug = fontSlug(familyName);
|
|
@@ -24551,7 +25618,7 @@ async function fetchGoogleFont(familyName) {
|
|
|
24551
25618
|
const woff2Url = match[3] || "";
|
|
24552
25619
|
if (!woff2Url) continue;
|
|
24553
25620
|
const cachePath2 = cachedWoff2Path(slug, weight, style);
|
|
24554
|
-
if (!
|
|
25621
|
+
if (!existsSync26(cachePath2)) {
|
|
24555
25622
|
try {
|
|
24556
25623
|
const fontRes = await fetch(woff2Url);
|
|
24557
25624
|
if (!fontRes.ok) continue;
|
|
@@ -24737,14 +25804,14 @@ var init_deterministicFonts = __esm({
|
|
|
24737
25804
|
poppins: "poppins",
|
|
24738
25805
|
"segoe ui": "roboto"
|
|
24739
25806
|
};
|
|
24740
|
-
GOOGLE_FONTS_CACHE_DIR =
|
|
25807
|
+
GOOGLE_FONTS_CACHE_DIR = join29(homedir7(), ".cache", "hyperframes", "fonts");
|
|
24741
25808
|
WOFF2_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
|
24742
25809
|
}
|
|
24743
25810
|
});
|
|
24744
25811
|
|
|
24745
25812
|
// ../producer/src/services/htmlCompiler.ts
|
|
24746
|
-
import { readFileSync as readFileSync19, existsSync as
|
|
24747
|
-
import { join as
|
|
25813
|
+
import { readFileSync as readFileSync19, existsSync as existsSync27, mkdirSync as mkdirSync16 } from "fs";
|
|
25814
|
+
import { join as join30, dirname as dirname9, resolve as resolve14 } from "path";
|
|
24748
25815
|
import postcss from "postcss";
|
|
24749
25816
|
function dedupeElementsById(elements) {
|
|
24750
25817
|
const deduped = /* @__PURE__ */ new Map();
|
|
@@ -24753,19 +25820,49 @@ function dedupeElementsById(elements) {
|
|
|
24753
25820
|
}
|
|
24754
25821
|
return Array.from(deduped.values());
|
|
24755
25822
|
}
|
|
25823
|
+
function stripJsComments(source) {
|
|
25824
|
+
return source.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
|
|
25825
|
+
}
|
|
25826
|
+
function detectRenderModeHints(html) {
|
|
25827
|
+
const reasons = [];
|
|
25828
|
+
const { document: document2 } = parseHTML(html);
|
|
25829
|
+
if (document2.querySelector("iframe")) {
|
|
25830
|
+
reasons.push({
|
|
25831
|
+
code: "iframe",
|
|
25832
|
+
message: "Detected <iframe> in the composition DOM. Nested iframe animation is routed through screenshot capture mode for compatibility."
|
|
25833
|
+
});
|
|
25834
|
+
}
|
|
25835
|
+
let scriptMatch;
|
|
25836
|
+
const scriptPattern = new RegExp(INLINE_SCRIPT_PATTERN.source, INLINE_SCRIPT_PATTERN.flags);
|
|
25837
|
+
while ((scriptMatch = scriptPattern.exec(html)) !== null) {
|
|
25838
|
+
const attrs = scriptMatch[1] || "";
|
|
25839
|
+
if (/\bsrc\s*=/i.test(attrs)) continue;
|
|
25840
|
+
const content = stripJsComments(scriptMatch[2] || "");
|
|
25841
|
+
if (!/requestAnimationFrame\s*\(/.test(content)) continue;
|
|
25842
|
+
reasons.push({
|
|
25843
|
+
code: "requestAnimationFrame",
|
|
25844
|
+
message: "Detected raw requestAnimationFrame() in an inline script. This render is routed through screenshot capture mode with virtual time enabled."
|
|
25845
|
+
});
|
|
25846
|
+
break;
|
|
25847
|
+
}
|
|
25848
|
+
return {
|
|
25849
|
+
recommendScreenshot: reasons.length > 0,
|
|
25850
|
+
reasons
|
|
25851
|
+
};
|
|
25852
|
+
}
|
|
24756
25853
|
async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagName19) {
|
|
24757
25854
|
let filePath = src;
|
|
24758
25855
|
if (isHttpUrl(src)) {
|
|
24759
|
-
if (!
|
|
25856
|
+
if (!existsSync27(downloadDir)) mkdirSync16(downloadDir, { recursive: true });
|
|
24760
25857
|
try {
|
|
24761
25858
|
filePath = await downloadToTemp(src, downloadDir);
|
|
24762
25859
|
} catch {
|
|
24763
25860
|
return { duration: 0, resolvedPath: src };
|
|
24764
25861
|
}
|
|
24765
25862
|
} else if (!filePath.startsWith("/")) {
|
|
24766
|
-
filePath =
|
|
25863
|
+
filePath = join30(baseDir, filePath);
|
|
24767
25864
|
}
|
|
24768
|
-
if (!
|
|
25865
|
+
if (!existsSync27(filePath)) {
|
|
24769
25866
|
return { duration: 0, resolvedPath: filePath };
|
|
24770
25867
|
}
|
|
24771
25868
|
const metadata = tagName19 === "video" ? await extractVideoMetadata(filePath) : await extractAudioMetadata(filePath);
|
|
@@ -24833,7 +25930,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, parentOffset
|
|
|
24833
25930
|
if (visited.has(filePath)) {
|
|
24834
25931
|
continue;
|
|
24835
25932
|
}
|
|
24836
|
-
if (!
|
|
25933
|
+
if (!existsSync27(filePath)) {
|
|
24837
25934
|
continue;
|
|
24838
25935
|
}
|
|
24839
25936
|
const rawSubHtml = readFileSync19(filePath, "utf-8");
|
|
@@ -25030,7 +26127,7 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
|
|
|
25030
26127
|
let compHtml = subCompositions.get(srcPath) || null;
|
|
25031
26128
|
if (!compHtml) {
|
|
25032
26129
|
const filePath = resolve14(projectDir, srcPath);
|
|
25033
|
-
if (
|
|
26130
|
+
if (existsSync27(filePath)) {
|
|
25034
26131
|
compHtml = readFileSync19(filePath, "utf-8");
|
|
25035
26132
|
}
|
|
25036
26133
|
}
|
|
@@ -25254,7 +26351,7 @@ function collectExternalAssets(html, projectDir) {
|
|
|
25254
26351
|
if (isPathInside(absPath, absProjectDir)) {
|
|
25255
26352
|
return null;
|
|
25256
26353
|
}
|
|
25257
|
-
if (!
|
|
26354
|
+
if (!existsSync27(absPath)) return null;
|
|
25258
26355
|
const safeKey = toExternalAssetKey(absPath);
|
|
25259
26356
|
externalAssets.set(safeKey, absPath);
|
|
25260
26357
|
return safeKey;
|
|
@@ -25316,6 +26413,7 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
|
|
|
25316
26413
|
/(<(?:video|audio)\b[^>]*?)\s+preload\s*=\s*["']none["']/gi,
|
|
25317
26414
|
"$1"
|
|
25318
26415
|
);
|
|
26416
|
+
const renderModeHints = detectRenderModeHints(sanitizedHtml);
|
|
25319
26417
|
const coalescedHtml = await injectDeterministicFontFaces(
|
|
25320
26418
|
coalesceHeadStylesAndBodyScripts2(promoteCssImportsToLinkTags(sanitizedHtml))
|
|
25321
26419
|
);
|
|
@@ -25359,7 +26457,8 @@ async function compileForRender(projectDir, htmlPath, downloadDir) {
|
|
|
25359
26457
|
externalAssets,
|
|
25360
26458
|
width,
|
|
25361
26459
|
height,
|
|
25362
|
-
staticDuration
|
|
26460
|
+
staticDuration,
|
|
26461
|
+
renderModeHints
|
|
25363
26462
|
};
|
|
25364
26463
|
}
|
|
25365
26464
|
async function discoverMediaFromBrowser(page) {
|
|
@@ -25453,9 +26552,11 @@ async function recompileWithResolutions(compiled, resolutions, projectDir, downl
|
|
|
25453
26552
|
subCompositions,
|
|
25454
26553
|
videos,
|
|
25455
26554
|
audios,
|
|
25456
|
-
unresolvedCompositions: remaining
|
|
26555
|
+
unresolvedCompositions: remaining,
|
|
26556
|
+
renderModeHints: compiled.renderModeHints
|
|
25457
26557
|
};
|
|
25458
26558
|
}
|
|
26559
|
+
var INLINE_SCRIPT_PATTERN;
|
|
25459
26560
|
var init_htmlCompiler2 = __esm({
|
|
25460
26561
|
"../producer/src/services/htmlCompiler.ts"() {
|
|
25461
26562
|
"use strict";
|
|
@@ -25466,6 +26567,7 @@ var init_htmlCompiler2 = __esm({
|
|
|
25466
26567
|
init_src2();
|
|
25467
26568
|
init_urlDownloader2();
|
|
25468
26569
|
init_deterministicFonts();
|
|
26570
|
+
INLINE_SCRIPT_PATTERN = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
|
|
25469
26571
|
}
|
|
25470
26572
|
});
|
|
25471
26573
|
|
|
@@ -25513,15 +26615,16 @@ var init_logger = __esm({
|
|
|
25513
26615
|
|
|
25514
26616
|
// ../producer/src/services/renderOrchestrator.ts
|
|
25515
26617
|
import {
|
|
25516
|
-
existsSync as
|
|
26618
|
+
existsSync as existsSync28,
|
|
25517
26619
|
mkdirSync as mkdirSync17,
|
|
25518
26620
|
rmSync as rmSync6,
|
|
25519
26621
|
readFileSync as readFileSync20,
|
|
26622
|
+
readdirSync as readdirSync11,
|
|
25520
26623
|
writeFileSync as writeFileSync10,
|
|
25521
26624
|
copyFileSync as copyFileSync2,
|
|
25522
26625
|
appendFileSync
|
|
25523
26626
|
} from "fs";
|
|
25524
|
-
import { join as
|
|
26627
|
+
import { join as join31, dirname as dirname10, resolve as resolve15 } from "path";
|
|
25525
26628
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
25526
26629
|
import { freemem as freemem2 } from "os";
|
|
25527
26630
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -25534,6 +26637,22 @@ async function safeCleanup(label2, fn, log = defaultLogger) {
|
|
|
25534
26637
|
});
|
|
25535
26638
|
}
|
|
25536
26639
|
}
|
|
26640
|
+
function getMaxFrameIndex(frameDir) {
|
|
26641
|
+
const cached2 = frameDirMaxIndexCache.get(frameDir);
|
|
26642
|
+
if (cached2 !== void 0) return cached2;
|
|
26643
|
+
let max = 0;
|
|
26644
|
+
try {
|
|
26645
|
+
for (const name of readdirSync11(frameDir)) {
|
|
26646
|
+
const m2 = FRAME_FILENAME_RE.exec(name);
|
|
26647
|
+
if (!m2) continue;
|
|
26648
|
+
const n = Number(m2[1]);
|
|
26649
|
+
if (Number.isFinite(n) && n > max) max = n;
|
|
26650
|
+
}
|
|
26651
|
+
} catch {
|
|
26652
|
+
}
|
|
26653
|
+
frameDirMaxIndexCache.set(frameDir, max);
|
|
26654
|
+
return max;
|
|
26655
|
+
}
|
|
25537
26656
|
function updateJobStatus(job, status, stage, progress, onProgress) {
|
|
25538
26657
|
job.status = status;
|
|
25539
26658
|
job.currentStage = stage;
|
|
@@ -25577,16 +26696,16 @@ function installDebugLogger(logPath, log = defaultLogger) {
|
|
|
25577
26696
|
};
|
|
25578
26697
|
}
|
|
25579
26698
|
function writeCompiledArtifacts(compiled, workDir, includeSummary) {
|
|
25580
|
-
const compileDir =
|
|
26699
|
+
const compileDir = join31(workDir, "compiled");
|
|
25581
26700
|
mkdirSync17(compileDir, { recursive: true });
|
|
25582
|
-
writeFileSync10(
|
|
26701
|
+
writeFileSync10(join31(compileDir, "index.html"), compiled.html, "utf-8");
|
|
25583
26702
|
for (const [srcPath, html] of compiled.subCompositions) {
|
|
25584
|
-
const outPath =
|
|
26703
|
+
const outPath = join31(compileDir, srcPath);
|
|
25585
26704
|
mkdirSync17(dirname10(outPath), { recursive: true });
|
|
25586
26705
|
writeFileSync10(outPath, html, "utf-8");
|
|
25587
26706
|
}
|
|
25588
26707
|
for (const [relativePath, absolutePath] of compiled.externalAssets) {
|
|
25589
|
-
const outPath = resolve15(
|
|
26708
|
+
const outPath = resolve15(join31(compileDir, relativePath));
|
|
25590
26709
|
if (!isPathInside(outPath, compileDir)) {
|
|
25591
26710
|
console.warn(`[Render] Skipping external asset with unsafe path: ${relativePath}`);
|
|
25592
26711
|
continue;
|
|
@@ -25613,11 +26732,20 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
|
|
|
25613
26732
|
end: a.end,
|
|
25614
26733
|
mediaStart: a.mediaStart
|
|
25615
26734
|
})),
|
|
25616
|
-
subCompositions: Array.from(compiled.subCompositions.keys())
|
|
26735
|
+
subCompositions: Array.from(compiled.subCompositions.keys()),
|
|
26736
|
+
renderModeHints: compiled.renderModeHints
|
|
25617
26737
|
};
|
|
25618
|
-
writeFileSync10(
|
|
26738
|
+
writeFileSync10(join31(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
|
|
25619
26739
|
}
|
|
25620
26740
|
}
|
|
26741
|
+
function applyRenderModeHints(cfg, compiled, log = defaultLogger) {
|
|
26742
|
+
if (cfg.forceScreenshot || !compiled.renderModeHints.recommendScreenshot) return;
|
|
26743
|
+
cfg.forceScreenshot = true;
|
|
26744
|
+
log.warn("Auto-selected screenshot capture mode for render compatibility", {
|
|
26745
|
+
reasonCodes: compiled.renderModeHints.reasons.map((reason) => reason.code),
|
|
26746
|
+
reasons: compiled.renderModeHints.reasons.map((reason) => reason.message)
|
|
26747
|
+
});
|
|
26748
|
+
}
|
|
25621
26749
|
function createRenderJob(config) {
|
|
25622
26750
|
return {
|
|
25623
26751
|
id: randomUUID2(),
|
|
@@ -25660,8 +26788,8 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
|
|
|
25660
26788
|
async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
|
|
25661
26789
|
const moduleDir = dirname10(fileURLToPath3(import.meta.url));
|
|
25662
26790
|
const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve15(process.env.PRODUCER_RENDERS_DIR, "..") : resolve15(moduleDir, "../..");
|
|
25663
|
-
const debugDir =
|
|
25664
|
-
const workDir = job.config.debug ?
|
|
26791
|
+
const debugDir = join31(producerRoot, ".debug");
|
|
26792
|
+
const workDir = job.config.debug ? join31(debugDir, job.id) : join31(dirname10(outputPath), `work-${job.id}`);
|
|
25665
26793
|
const pipelineStart = Date.now();
|
|
25666
26794
|
const log = job.config.logger ?? defaultLogger;
|
|
25667
26795
|
let fileServer = null;
|
|
@@ -25669,7 +26797,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25669
26797
|
let lastBrowserConsole = [];
|
|
25670
26798
|
let restoreLogger = null;
|
|
25671
26799
|
const perfStages = {};
|
|
25672
|
-
const perfOutputPath =
|
|
26800
|
+
const perfOutputPath = join31(workDir, "perf-summary.json");
|
|
25673
26801
|
const cfg = { ...job.config.producerConfig ?? resolveConfig() };
|
|
25674
26802
|
const outputFormat = job.config.format ?? "mp4";
|
|
25675
26803
|
const isWebm = outputFormat === "webm";
|
|
@@ -25689,22 +26817,22 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25689
26817
|
};
|
|
25690
26818
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
25691
26819
|
assertNotAborted();
|
|
25692
|
-
if (!
|
|
26820
|
+
if (!existsSync28(workDir)) mkdirSync17(workDir, { recursive: true });
|
|
25693
26821
|
if (job.config.debug) {
|
|
25694
|
-
const logPath =
|
|
26822
|
+
const logPath = join31(workDir, "render.log");
|
|
25695
26823
|
restoreLogger = installDebugLogger(logPath, log);
|
|
25696
26824
|
}
|
|
25697
26825
|
const entryFile = job.config.entryFile || "index.html";
|
|
25698
|
-
let htmlPath =
|
|
25699
|
-
if (!
|
|
26826
|
+
let htmlPath = join31(projectDir, entryFile);
|
|
26827
|
+
if (!existsSync28(htmlPath)) {
|
|
25700
26828
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
25701
26829
|
}
|
|
25702
26830
|
assertNotAborted();
|
|
25703
26831
|
const rawEntry = readFileSync20(htmlPath, "utf-8");
|
|
25704
26832
|
if (entryFile !== "index.html" && rawEntry.trimStart().startsWith("<template")) {
|
|
25705
|
-
const wrapperPath =
|
|
25706
|
-
const projectIndexPath =
|
|
25707
|
-
if (!
|
|
26833
|
+
const wrapperPath = join31(workDir, "standalone-entry.html");
|
|
26834
|
+
const projectIndexPath = join31(projectDir, "index.html");
|
|
26835
|
+
if (!existsSync28(projectIndexPath)) {
|
|
25708
26836
|
throw new Error(
|
|
25709
26837
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
25710
26838
|
);
|
|
@@ -25727,9 +26855,10 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25727
26855
|
const stage1Start = Date.now();
|
|
25728
26856
|
updateJobStatus(job, "preprocessing", "Compiling composition", 5, onProgress);
|
|
25729
26857
|
const compileStart = Date.now();
|
|
25730
|
-
let compiled = await compileForRender(projectDir, htmlPath,
|
|
26858
|
+
let compiled = await compileForRender(projectDir, htmlPath, join31(workDir, "downloads"));
|
|
25731
26859
|
assertNotAborted();
|
|
25732
26860
|
perfStages.compileOnlyMs = Date.now() - compileStart;
|
|
26861
|
+
applyRenderModeHints(cfg, compiled, log);
|
|
25733
26862
|
writeCompiledArtifacts(compiled, workDir, Boolean(job.config.debug));
|
|
25734
26863
|
log.info("Compiled composition metadata", {
|
|
25735
26864
|
entryFile,
|
|
@@ -25737,7 +26866,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25737
26866
|
width: compiled.width,
|
|
25738
26867
|
height: compiled.height,
|
|
25739
26868
|
videoCount: compiled.videos.length,
|
|
25740
|
-
audioCount: compiled.audios.length
|
|
26869
|
+
audioCount: compiled.audios.length,
|
|
26870
|
+
renderModeHints: compiled.renderModeHints
|
|
25741
26871
|
});
|
|
25742
26872
|
const composition = {
|
|
25743
26873
|
duration: compiled.staticDuration,
|
|
@@ -25756,8 +26886,9 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25756
26886
|
reasons.push(`${compiled.unresolvedCompositions.length} unresolved composition(s)`);
|
|
25757
26887
|
fileServer = await createFileServer2({
|
|
25758
26888
|
projectDir,
|
|
25759
|
-
compiledDir:
|
|
25760
|
-
port: 0
|
|
26889
|
+
compiledDir: join31(workDir, "compiled"),
|
|
26890
|
+
port: 0,
|
|
26891
|
+
preHeadScripts: [VIRTUAL_TIME_SHIM]
|
|
25761
26892
|
});
|
|
25762
26893
|
assertNotAborted();
|
|
25763
26894
|
const captureOpts = {
|
|
@@ -25769,7 +26900,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25769
26900
|
};
|
|
25770
26901
|
probeSession = await createCaptureSession(
|
|
25771
26902
|
fileServer.url,
|
|
25772
|
-
|
|
26903
|
+
join31(workDir, "probe"),
|
|
25773
26904
|
captureOpts,
|
|
25774
26905
|
null,
|
|
25775
26906
|
cfg
|
|
@@ -25801,7 +26932,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25801
26932
|
compiled,
|
|
25802
26933
|
resolutions,
|
|
25803
26934
|
projectDir,
|
|
25804
|
-
|
|
26935
|
+
join31(workDir, "downloads")
|
|
25805
26936
|
);
|
|
25806
26937
|
assertNotAborted();
|
|
25807
26938
|
composition.videos = compiled.videos;
|
|
@@ -25913,7 +27044,10 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25913
27044
|
}
|
|
25914
27045
|
}
|
|
25915
27046
|
}
|
|
25916
|
-
} catch {
|
|
27047
|
+
} catch (err) {
|
|
27048
|
+
log.warn("Failed to gather browser diagnostics for zero-duration composition", {
|
|
27049
|
+
error: err instanceof Error ? err.message : String(err)
|
|
27050
|
+
});
|
|
25917
27051
|
diagnostics.push("(Could not gather browser diagnostics \u2014 page may have crashed)");
|
|
25918
27052
|
}
|
|
25919
27053
|
const hint = diagnostics.length > 0 ? "\n\nDiagnostics:\n - " + diagnostics.join("\n - ") : "\n\nCheck that GSAP timelines are registered on window.__timelines.";
|
|
@@ -25936,12 +27070,30 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25936
27070
|
const stage2Start = Date.now();
|
|
25937
27071
|
updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress);
|
|
25938
27072
|
let frameLookup = null;
|
|
25939
|
-
const compiledDir =
|
|
27073
|
+
const compiledDir = join31(workDir, "compiled");
|
|
27074
|
+
let extractionResult = null;
|
|
27075
|
+
const nativeHdrVideoIds = /* @__PURE__ */ new Set();
|
|
27076
|
+
if (composition.videos.length > 0) {
|
|
27077
|
+
await Promise.all(
|
|
27078
|
+
composition.videos.map(async (v) => {
|
|
27079
|
+
let videoPath = v.src;
|
|
27080
|
+
if (!videoPath.startsWith("/")) {
|
|
27081
|
+
const fromCompiled = existsSync28(join31(compiledDir, videoPath)) ? join31(compiledDir, videoPath) : join31(projectDir, videoPath);
|
|
27082
|
+
videoPath = fromCompiled;
|
|
27083
|
+
}
|
|
27084
|
+
if (!existsSync28(videoPath)) return;
|
|
27085
|
+
const meta = await extractVideoMetadata(videoPath);
|
|
27086
|
+
if (isHdrColorSpace(meta.colorSpace)) {
|
|
27087
|
+
nativeHdrVideoIds.add(v.id);
|
|
27088
|
+
}
|
|
27089
|
+
})
|
|
27090
|
+
);
|
|
27091
|
+
}
|
|
25940
27092
|
if (composition.videos.length > 0) {
|
|
25941
|
-
|
|
27093
|
+
extractionResult = await extractAllVideoFrames(
|
|
25942
27094
|
composition.videos,
|
|
25943
27095
|
projectDir,
|
|
25944
|
-
{ fps: job.config.fps, outputDir:
|
|
27096
|
+
{ fps: job.config.fps, outputDir: join31(workDir, "video-frames") },
|
|
25945
27097
|
abortSignal,
|
|
25946
27098
|
void 0,
|
|
25947
27099
|
compiledDir
|
|
@@ -25973,15 +27125,32 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25973
27125
|
} else {
|
|
25974
27126
|
perfStages.videoExtractMs = Date.now() - stage2Start;
|
|
25975
27127
|
}
|
|
27128
|
+
let effectiveHdr;
|
|
27129
|
+
if (frameLookup) {
|
|
27130
|
+
const colorSpaces = (extractionResult?.extracted ?? []).map((ext) => ext.metadata.colorSpace);
|
|
27131
|
+
const info = analyzeCompositionHdr(colorSpaces);
|
|
27132
|
+
if (info.hasHdr && info.dominantTransfer) {
|
|
27133
|
+
effectiveHdr = { transfer: info.dominantTransfer };
|
|
27134
|
+
}
|
|
27135
|
+
}
|
|
27136
|
+
if (effectiveHdr && outputFormat !== "mp4") {
|
|
27137
|
+
log.info(`[Render] HDR source detected but format is ${outputFormat} \u2014 using SDR`);
|
|
27138
|
+
effectiveHdr = void 0;
|
|
27139
|
+
}
|
|
27140
|
+
if (effectiveHdr) {
|
|
27141
|
+
log.info(
|
|
27142
|
+
`[Render] HDR source detected \u2014 output: ${effectiveHdr.transfer.toUpperCase()} (BT.2020, 10-bit H.265)`
|
|
27143
|
+
);
|
|
27144
|
+
}
|
|
25976
27145
|
const stage3Start = Date.now();
|
|
25977
27146
|
updateJobStatus(job, "preprocessing", "Processing audio tracks", 20, onProgress);
|
|
25978
|
-
const audioOutputPath =
|
|
27147
|
+
const audioOutputPath = join31(workDir, "audio.aac");
|
|
25979
27148
|
let hasAudio = false;
|
|
25980
27149
|
if (composition.audios.length > 0) {
|
|
25981
27150
|
const audioResult = await processCompositionAudio(
|
|
25982
27151
|
composition.audios,
|
|
25983
27152
|
projectDir,
|
|
25984
|
-
|
|
27153
|
+
join31(workDir, "audio-work"),
|
|
25985
27154
|
audioOutputPath,
|
|
25986
27155
|
job.duration,
|
|
25987
27156
|
abortSignal,
|
|
@@ -25999,13 +27168,14 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25999
27168
|
if (!fileServer) {
|
|
26000
27169
|
fileServer = await createFileServer2({
|
|
26001
27170
|
projectDir,
|
|
26002
|
-
compiledDir:
|
|
26003
|
-
port: 0
|
|
27171
|
+
compiledDir: join31(workDir, "compiled"),
|
|
27172
|
+
port: 0,
|
|
27173
|
+
preHeadScripts: [VIRTUAL_TIME_SHIM]
|
|
26004
27174
|
});
|
|
26005
27175
|
assertNotAborted();
|
|
26006
27176
|
}
|
|
26007
|
-
const framesDir =
|
|
26008
|
-
if (!
|
|
27177
|
+
const framesDir = join31(workDir, "captured-frames");
|
|
27178
|
+
if (!existsSync28(framesDir)) mkdirSync17(framesDir, { recursive: true });
|
|
26009
27179
|
const captureOptions = {
|
|
26010
27180
|
width,
|
|
26011
27181
|
height,
|
|
@@ -26016,219 +27186,399 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
26016
27186
|
const workerCount = calculateOptimalWorkers(job.totalFrames, job.config.workers, cfg);
|
|
26017
27187
|
const FORMAT_EXT2 = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
|
|
26018
27188
|
const videoExt = FORMAT_EXT2[outputFormat] ?? ".mp4";
|
|
26019
|
-
const videoOnlyPath =
|
|
26020
|
-
const
|
|
26021
|
-
const
|
|
26022
|
-
const
|
|
26023
|
-
const baseEncoderOpts = {
|
|
26024
|
-
fps: job.config.fps,
|
|
26025
|
-
width,
|
|
26026
|
-
height,
|
|
26027
|
-
codec: preset.codec,
|
|
26028
|
-
preset: preset.preset,
|
|
26029
|
-
quality: effectiveQuality,
|
|
26030
|
-
bitrate: effectiveBitrate,
|
|
26031
|
-
pixelFormat: preset.pixelFormat,
|
|
26032
|
-
useGpu: job.config.useGpu
|
|
26033
|
-
};
|
|
27189
|
+
const videoOnlyPath = join31(workDir, `video-only${videoExt}`);
|
|
27190
|
+
const hasHdrVideo = effectiveHdr && composition.videos.length > 0 && frameLookup;
|
|
27191
|
+
const encoderHdr = hasHdrVideo ? effectiveHdr : void 0;
|
|
27192
|
+
const preset = getEncoderPreset(job.config.quality, outputFormat, encoderHdr);
|
|
26034
27193
|
job.framesRendered = 0;
|
|
26035
|
-
|
|
26036
|
-
|
|
26037
|
-
|
|
27194
|
+
if (hasHdrVideo) {
|
|
27195
|
+
log.info("[Render] HDR layered composite: z-ordered DOM + native HLG video layers");
|
|
27196
|
+
const hdrVideoIds = composition.videos.filter((v) => nativeHdrVideoIds.has(v.id)).map((v) => v.id);
|
|
27197
|
+
const hdrVideoSrcPaths = /* @__PURE__ */ new Map();
|
|
27198
|
+
for (const v of composition.videos) {
|
|
27199
|
+
if (!hdrVideoIds.includes(v.id)) continue;
|
|
27200
|
+
let srcPath = v.src;
|
|
27201
|
+
if (!srcPath.startsWith("/")) {
|
|
27202
|
+
const fromCompiled = join31(compiledDir, srcPath);
|
|
27203
|
+
srcPath = existsSync28(fromCompiled) ? fromCompiled : join31(projectDir, srcPath);
|
|
27204
|
+
}
|
|
27205
|
+
hdrVideoSrcPaths.set(v.id, srcPath);
|
|
27206
|
+
}
|
|
27207
|
+
const domSession = await createCaptureSession(
|
|
27208
|
+
fileServer.url,
|
|
27209
|
+
framesDir,
|
|
27210
|
+
captureOptions,
|
|
27211
|
+
createVideoFrameInjector(frameLookup),
|
|
27212
|
+
cfg
|
|
27213
|
+
);
|
|
27214
|
+
await initializeSession(domSession);
|
|
27215
|
+
assertNotAborted();
|
|
27216
|
+
lastBrowserConsole = domSession.browserConsoleBuffer;
|
|
27217
|
+
await initTransparentBackground(domSession.page);
|
|
27218
|
+
const hdrEncoder = await spawnStreamingEncoder(
|
|
26038
27219
|
videoOnlyPath,
|
|
26039
27220
|
{
|
|
26040
|
-
|
|
26041
|
-
|
|
27221
|
+
fps: job.config.fps,
|
|
27222
|
+
width,
|
|
27223
|
+
height,
|
|
27224
|
+
codec: preset.codec,
|
|
27225
|
+
preset: preset.preset,
|
|
27226
|
+
quality: preset.quality,
|
|
27227
|
+
pixelFormat: preset.pixelFormat,
|
|
27228
|
+
hdr: preset.hdr,
|
|
27229
|
+
rawInputFormat: "rgb48le"
|
|
26042
27230
|
},
|
|
26043
|
-
abortSignal
|
|
27231
|
+
abortSignal,
|
|
27232
|
+
{ ffmpegStreamingTimeout: 36e5 }
|
|
26044
27233
|
);
|
|
26045
27234
|
assertNotAborted();
|
|
26046
|
-
|
|
26047
|
-
|
|
26048
|
-
const
|
|
26049
|
-
|
|
26050
|
-
|
|
26051
|
-
const
|
|
26052
|
-
|
|
26053
|
-
|
|
26054
|
-
|
|
26055
|
-
|
|
26056
|
-
|
|
26057
|
-
|
|
26058
|
-
|
|
26059
|
-
|
|
26060
|
-
|
|
26061
|
-
|
|
26062
|
-
|
|
26063
|
-
|
|
26064
|
-
|
|
26065
|
-
|
|
26066
|
-
|
|
26067
|
-
|
|
26068
|
-
|
|
26069
|
-
|
|
26070
|
-
|
|
26071
|
-
|
|
26072
|
-
|
|
26073
|
-
|
|
26074
|
-
|
|
27235
|
+
const { execSync: execSync5 } = await import("child_process");
|
|
27236
|
+
const hdrFrameDirs = /* @__PURE__ */ new Map();
|
|
27237
|
+
for (const [videoId, srcPath] of hdrVideoSrcPaths) {
|
|
27238
|
+
const video = composition.videos.find((v) => v.id === videoId);
|
|
27239
|
+
if (!video) continue;
|
|
27240
|
+
const frameDir = join31(framesDir, `hdr_${videoId}`);
|
|
27241
|
+
mkdirSync17(frameDir, { recursive: true });
|
|
27242
|
+
const duration = video.end - video.start;
|
|
27243
|
+
try {
|
|
27244
|
+
execSync5(
|
|
27245
|
+
`ffmpeg -ss ${video.mediaStart} -i "${srcPath}" -t ${duration} -r ${job.config.fps} -vf "scale=${width}:${height}:force_original_aspect_ratio=increase,crop=${width}:${height}" -pix_fmt rgb48le -c:v png "${join31(frameDir, "frame_%04d.png")}"`,
|
|
27246
|
+
{ maxBuffer: 1024 * 1024, stdio: ["pipe", "pipe", "pipe"] }
|
|
27247
|
+
);
|
|
27248
|
+
} catch (err) {
|
|
27249
|
+
log.warn("HDR frame pre-extraction failed; loop will fill with black", {
|
|
27250
|
+
videoId,
|
|
27251
|
+
srcPath,
|
|
27252
|
+
error: err instanceof Error ? err.message : String(err)
|
|
27253
|
+
});
|
|
27254
|
+
}
|
|
27255
|
+
hdrFrameDirs.set(videoId, frameDir);
|
|
27256
|
+
}
|
|
27257
|
+
assertNotAborted();
|
|
27258
|
+
try {
|
|
27259
|
+
const beforeCaptureHook = domSession.onBeforeCapture;
|
|
27260
|
+
for (let i2 = 0; i2 < job.totalFrames; i2++) {
|
|
27261
|
+
assertNotAborted();
|
|
27262
|
+
const time = i2 / job.config.fps;
|
|
27263
|
+
await domSession.page.evaluate((t3) => {
|
|
27264
|
+
if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t3);
|
|
27265
|
+
}, time);
|
|
27266
|
+
if (beforeCaptureHook) {
|
|
27267
|
+
await beforeCaptureHook(domSession.page, time);
|
|
27268
|
+
}
|
|
27269
|
+
const stackingInfo = await queryElementStacking(domSession.page, nativeHdrVideoIds);
|
|
27270
|
+
const layers = groupIntoLayers(stackingInfo);
|
|
27271
|
+
if (i2 % 30 === 0) {
|
|
27272
|
+
const hdrEl = stackingInfo.find((e2) => e2.isHdr);
|
|
27273
|
+
const hdrInLayers = layers.some((l) => l.type === "hdr");
|
|
27274
|
+
log.debug("[Render] HDR layer composite frame", {
|
|
27275
|
+
frame: i2,
|
|
27276
|
+
time: time.toFixed(2),
|
|
27277
|
+
hdrElement: hdrEl ? { z: hdrEl.zIndex, visible: hdrEl.visible, width: hdrEl.width } : null,
|
|
27278
|
+
hdrLayerPresent: hdrInLayers,
|
|
27279
|
+
layerCount: layers.length
|
|
27280
|
+
});
|
|
27281
|
+
}
|
|
27282
|
+
const canvas = Buffer.alloc(width * height * 6);
|
|
27283
|
+
for (const layer of layers) {
|
|
27284
|
+
if (layer.type === "hdr") {
|
|
27285
|
+
const el = layer.element;
|
|
27286
|
+
const frameDir = hdrFrameDirs.get(el.id);
|
|
27287
|
+
const video = composition.videos.find((v) => v.id === el.id);
|
|
27288
|
+
if (!frameDir || !video) continue;
|
|
27289
|
+
const videoFrameIndex = Math.round((time - video.start) * job.config.fps) + 1;
|
|
27290
|
+
const maxIndex = getMaxFrameIndex(frameDir);
|
|
27291
|
+
const inBounds = videoFrameIndex >= 1 && (maxIndex === 0 || videoFrameIndex <= maxIndex);
|
|
27292
|
+
const framePath = inBounds ? join31(frameDir, `frame_${String(videoFrameIndex).padStart(4, "0")}.png`) : null;
|
|
27293
|
+
if (framePath !== null && existsSync28(framePath)) {
|
|
27294
|
+
try {
|
|
27295
|
+
const hdrRgb = decodePngToRgb48le(readFileSync20(framePath)).data;
|
|
27296
|
+
blitRgb48leRegion(
|
|
27297
|
+
canvas,
|
|
27298
|
+
hdrRgb,
|
|
27299
|
+
el.x,
|
|
27300
|
+
el.y,
|
|
27301
|
+
el.width,
|
|
27302
|
+
el.height,
|
|
27303
|
+
width,
|
|
27304
|
+
height,
|
|
27305
|
+
el.opacity < 0.999 ? el.opacity : void 0
|
|
27306
|
+
);
|
|
27307
|
+
} catch (err) {
|
|
27308
|
+
log.warn("HDR layer decode/blit failed; skipping layer for frame", {
|
|
27309
|
+
frameIndex: i2,
|
|
27310
|
+
videoId: el.id,
|
|
27311
|
+
framePath,
|
|
27312
|
+
error: err instanceof Error ? err.message : String(err)
|
|
27313
|
+
});
|
|
27314
|
+
}
|
|
27315
|
+
}
|
|
27316
|
+
} else {
|
|
27317
|
+
const allElementIds = stackingInfo.map((e2) => e2.id);
|
|
27318
|
+
const layerIds = new Set(layer.elementIds);
|
|
27319
|
+
const hideIds = allElementIds.filter(
|
|
27320
|
+
(id) => !layerIds.has(id) || nativeHdrVideoIds.has(id)
|
|
26075
27321
|
);
|
|
27322
|
+
await hideVideoElements(domSession.page, hideIds);
|
|
27323
|
+
const domPng = await captureAlphaPng(domSession.page, width, height);
|
|
27324
|
+
await showVideoElements(domSession.page, hideIds);
|
|
27325
|
+
await domSession.page.evaluate((t3) => {
|
|
27326
|
+
if (window.__hf && typeof window.__hf.seek === "function") window.__hf.seek(t3);
|
|
27327
|
+
}, time);
|
|
27328
|
+
try {
|
|
27329
|
+
const { data: domRgba } = decodePng(domPng);
|
|
27330
|
+
const hdrTransfer = effectiveHdr ? effectiveHdr.transfer : "hlg";
|
|
27331
|
+
blitRgba8OverRgb48le(domRgba, canvas, width, height, hdrTransfer);
|
|
27332
|
+
} catch (err) {
|
|
27333
|
+
log.warn("DOM layer decode/blit failed; skipping overlay for frame", {
|
|
27334
|
+
frameIndex: i2,
|
|
27335
|
+
layerIds: layer.elementIds,
|
|
27336
|
+
error: err instanceof Error ? err.message : String(err)
|
|
27337
|
+
});
|
|
27338
|
+
}
|
|
26076
27339
|
}
|
|
26077
|
-
},
|
|
26078
|
-
onFrameBuffer,
|
|
26079
|
-
cfg
|
|
26080
|
-
);
|
|
26081
|
-
if (probeSession) {
|
|
26082
|
-
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
26083
|
-
await closeCaptureSession(probeSession);
|
|
26084
|
-
probeSession = null;
|
|
26085
|
-
}
|
|
26086
|
-
} else {
|
|
26087
|
-
const videoInjector = createVideoFrameInjector(frameLookup);
|
|
26088
|
-
const session = probeSession ?? await createCaptureSession(
|
|
26089
|
-
fileServer.url,
|
|
26090
|
-
framesDir,
|
|
26091
|
-
captureOptions,
|
|
26092
|
-
videoInjector,
|
|
26093
|
-
cfg
|
|
26094
|
-
);
|
|
26095
|
-
if (probeSession) {
|
|
26096
|
-
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
26097
|
-
probeSession = null;
|
|
26098
|
-
}
|
|
26099
|
-
try {
|
|
26100
|
-
if (!session.isInitialized) {
|
|
26101
|
-
await initializeSession(session);
|
|
26102
27340
|
}
|
|
26103
|
-
|
|
26104
|
-
|
|
26105
|
-
|
|
26106
|
-
assertNotAborted();
|
|
26107
|
-
const time = i2 / job.config.fps;
|
|
26108
|
-
const { buffer } = await captureFrameToBuffer(session, i2, time);
|
|
26109
|
-
await reorderBuffer.waitForFrame(i2);
|
|
26110
|
-
currentEncoder.writeFrame(buffer);
|
|
26111
|
-
reorderBuffer.advanceTo(i2 + 1);
|
|
26112
|
-
job.framesRendered = i2 + 1;
|
|
27341
|
+
hdrEncoder.writeFrame(canvas);
|
|
27342
|
+
job.framesRendered = i2 + 1;
|
|
27343
|
+
if ((i2 + 1) % 10 === 0 || i2 + 1 === job.totalFrames) {
|
|
26113
27344
|
const frameProgress = (i2 + 1) / job.totalFrames;
|
|
26114
|
-
const progress = 25 + frameProgress * 55;
|
|
26115
27345
|
updateJobStatus(
|
|
26116
27346
|
job,
|
|
26117
27347
|
"rendering",
|
|
26118
|
-
`
|
|
26119
|
-
Math.round(
|
|
27348
|
+
`HDR composite frame ${i2 + 1}/${job.totalFrames}`,
|
|
27349
|
+
Math.round(25 + frameProgress * 55),
|
|
26120
27350
|
onProgress
|
|
26121
27351
|
);
|
|
26122
27352
|
}
|
|
26123
|
-
} finally {
|
|
26124
|
-
lastBrowserConsole = session.browserConsoleBuffer;
|
|
26125
|
-
await closeCaptureSession(session);
|
|
26126
27353
|
}
|
|
27354
|
+
} finally {
|
|
27355
|
+
lastBrowserConsole = domSession.browserConsoleBuffer;
|
|
27356
|
+
await closeCaptureSession(domSession);
|
|
26127
27357
|
}
|
|
26128
|
-
const
|
|
27358
|
+
const hdrEncodeResult = await hdrEncoder.close();
|
|
26129
27359
|
assertNotAborted();
|
|
26130
|
-
if (!
|
|
26131
|
-
throw new Error(`
|
|
27360
|
+
if (!hdrEncodeResult.success) {
|
|
27361
|
+
throw new Error(`HDR encode failed: ${hdrEncodeResult.error}`);
|
|
26132
27362
|
}
|
|
26133
27363
|
perfStages.captureMs = Date.now() - stage4Start;
|
|
26134
|
-
perfStages.encodeMs =
|
|
27364
|
+
perfStages.encodeMs = hdrEncodeResult.durationMs;
|
|
26135
27365
|
} else {
|
|
26136
|
-
|
|
26137
|
-
|
|
26138
|
-
await
|
|
26139
|
-
|
|
26140
|
-
|
|
26141
|
-
|
|
26142
|
-
|
|
26143
|
-
|
|
26144
|
-
|
|
26145
|
-
|
|
26146
|
-
|
|
26147
|
-
|
|
26148
|
-
|
|
26149
|
-
|
|
27366
|
+
let streamingEncoder = null;
|
|
27367
|
+
if (enableStreamingEncode) {
|
|
27368
|
+
streamingEncoder = await spawnStreamingEncoder(
|
|
27369
|
+
videoOnlyPath,
|
|
27370
|
+
{
|
|
27371
|
+
fps: job.config.fps,
|
|
27372
|
+
width,
|
|
27373
|
+
height,
|
|
27374
|
+
codec: preset.codec,
|
|
27375
|
+
preset: preset.preset,
|
|
27376
|
+
quality: preset.quality,
|
|
27377
|
+
pixelFormat: preset.pixelFormat,
|
|
27378
|
+
useGpu: job.config.useGpu,
|
|
27379
|
+
imageFormat: captureOptions.format || "jpeg",
|
|
27380
|
+
hdr: preset.hdr
|
|
27381
|
+
},
|
|
27382
|
+
abortSignal
|
|
27383
|
+
);
|
|
27384
|
+
assertNotAborted();
|
|
27385
|
+
}
|
|
27386
|
+
if (enableStreamingEncode && streamingEncoder) {
|
|
27387
|
+
const reorderBuffer = createFrameReorderBuffer(0, job.totalFrames);
|
|
27388
|
+
const currentEncoder = streamingEncoder;
|
|
27389
|
+
if (workerCount > 1) {
|
|
27390
|
+
const tasks = distributeFrames(job.totalFrames, workerCount, workDir);
|
|
27391
|
+
const onFrameBuffer = async (frameIndex, buffer) => {
|
|
27392
|
+
await reorderBuffer.waitForFrame(frameIndex);
|
|
27393
|
+
currentEncoder.writeFrame(buffer);
|
|
27394
|
+
reorderBuffer.advanceTo(frameIndex + 1);
|
|
27395
|
+
};
|
|
27396
|
+
await executeParallelCapture(
|
|
27397
|
+
fileServer.url,
|
|
27398
|
+
workDir,
|
|
27399
|
+
tasks,
|
|
27400
|
+
captureOptions,
|
|
27401
|
+
() => createVideoFrameInjector(frameLookup),
|
|
27402
|
+
abortSignal,
|
|
27403
|
+
(progress) => {
|
|
27404
|
+
job.framesRendered = progress.capturedFrames;
|
|
27405
|
+
const frameProgress = progress.capturedFrames / progress.totalFrames;
|
|
27406
|
+
const progressPct = 25 + frameProgress * 55;
|
|
27407
|
+
if (progress.capturedFrames % 30 === 0 || progress.capturedFrames === progress.totalFrames) {
|
|
27408
|
+
updateJobStatus(
|
|
27409
|
+
job,
|
|
27410
|
+
"rendering",
|
|
27411
|
+
`Streaming frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
|
|
27412
|
+
Math.round(progressPct),
|
|
27413
|
+
onProgress
|
|
27414
|
+
);
|
|
27415
|
+
}
|
|
27416
|
+
},
|
|
27417
|
+
onFrameBuffer,
|
|
27418
|
+
cfg
|
|
27419
|
+
);
|
|
27420
|
+
if (probeSession) {
|
|
27421
|
+
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
27422
|
+
await closeCaptureSession(probeSession);
|
|
27423
|
+
probeSession = null;
|
|
27424
|
+
}
|
|
27425
|
+
} else {
|
|
27426
|
+
const videoInjector = createVideoFrameInjector(frameLookup);
|
|
27427
|
+
const session = probeSession ?? await createCaptureSession(
|
|
27428
|
+
fileServer.url,
|
|
27429
|
+
framesDir,
|
|
27430
|
+
captureOptions,
|
|
27431
|
+
videoInjector,
|
|
27432
|
+
cfg
|
|
27433
|
+
);
|
|
27434
|
+
if (probeSession) {
|
|
27435
|
+
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
27436
|
+
probeSession = null;
|
|
27437
|
+
}
|
|
27438
|
+
try {
|
|
27439
|
+
if (!session.isInitialized) {
|
|
27440
|
+
await initializeSession(session);
|
|
27441
|
+
}
|
|
27442
|
+
assertNotAborted();
|
|
27443
|
+
lastBrowserConsole = session.browserConsoleBuffer;
|
|
27444
|
+
for (let i2 = 0; i2 < job.totalFrames; i2++) {
|
|
27445
|
+
assertNotAborted();
|
|
27446
|
+
const time = i2 / job.config.fps;
|
|
27447
|
+
const { buffer } = await captureFrameToBuffer(session, i2, time);
|
|
27448
|
+
await reorderBuffer.waitForFrame(i2);
|
|
27449
|
+
currentEncoder.writeFrame(buffer);
|
|
27450
|
+
reorderBuffer.advanceTo(i2 + 1);
|
|
27451
|
+
job.framesRendered = i2 + 1;
|
|
27452
|
+
const frameProgress = (i2 + 1) / job.totalFrames;
|
|
27453
|
+
const progress = 25 + frameProgress * 55;
|
|
26150
27454
|
updateJobStatus(
|
|
26151
27455
|
job,
|
|
26152
27456
|
"rendering",
|
|
26153
|
-
`
|
|
26154
|
-
Math.round(
|
|
27457
|
+
`Streaming frame ${i2 + 1}/${job.totalFrames}`,
|
|
27458
|
+
Math.round(progress),
|
|
26155
27459
|
onProgress
|
|
26156
27460
|
);
|
|
26157
27461
|
}
|
|
26158
|
-
}
|
|
26159
|
-
|
|
26160
|
-
|
|
26161
|
-
|
|
26162
|
-
await mergeWorkerFrames(workDir, tasks, framesDir);
|
|
26163
|
-
if (probeSession) {
|
|
26164
|
-
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
26165
|
-
await closeCaptureSession(probeSession);
|
|
26166
|
-
probeSession = null;
|
|
27462
|
+
} finally {
|
|
27463
|
+
lastBrowserConsole = session.browserConsoleBuffer;
|
|
27464
|
+
await closeCaptureSession(session);
|
|
27465
|
+
}
|
|
26167
27466
|
}
|
|
26168
|
-
|
|
26169
|
-
|
|
26170
|
-
|
|
26171
|
-
|
|
26172
|
-
framesDir,
|
|
26173
|
-
captureOptions,
|
|
26174
|
-
videoInjector,
|
|
26175
|
-
cfg
|
|
26176
|
-
);
|
|
26177
|
-
if (probeSession) {
|
|
26178
|
-
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
26179
|
-
probeSession = null;
|
|
27467
|
+
const encodeResult = await currentEncoder.close();
|
|
27468
|
+
assertNotAborted();
|
|
27469
|
+
if (!encodeResult.success) {
|
|
27470
|
+
throw new Error(`Streaming encode failed: ${encodeResult.error}`);
|
|
26180
27471
|
}
|
|
26181
|
-
|
|
26182
|
-
|
|
26183
|
-
|
|
27472
|
+
perfStages.captureMs = Date.now() - stage4Start;
|
|
27473
|
+
perfStages.encodeMs = encodeResult.durationMs;
|
|
27474
|
+
} else {
|
|
27475
|
+
if (workerCount > 1) {
|
|
27476
|
+
const tasks = distributeFrames(job.totalFrames, workerCount, workDir);
|
|
27477
|
+
await executeParallelCapture(
|
|
27478
|
+
fileServer.url,
|
|
27479
|
+
workDir,
|
|
27480
|
+
tasks,
|
|
27481
|
+
captureOptions,
|
|
27482
|
+
() => createVideoFrameInjector(frameLookup),
|
|
27483
|
+
abortSignal,
|
|
27484
|
+
(progress) => {
|
|
27485
|
+
job.framesRendered = progress.capturedFrames;
|
|
27486
|
+
const frameProgress = progress.capturedFrames / progress.totalFrames;
|
|
27487
|
+
const progressPct = 25 + frameProgress * 45;
|
|
27488
|
+
if (progress.capturedFrames % 30 === 0 || progress.capturedFrames === progress.totalFrames) {
|
|
27489
|
+
updateJobStatus(
|
|
27490
|
+
job,
|
|
27491
|
+
"rendering",
|
|
27492
|
+
`Capturing frame ${progress.capturedFrames}/${progress.totalFrames} (${workerCount} workers)`,
|
|
27493
|
+
Math.round(progressPct),
|
|
27494
|
+
onProgress
|
|
27495
|
+
);
|
|
27496
|
+
}
|
|
27497
|
+
},
|
|
27498
|
+
void 0,
|
|
27499
|
+
cfg
|
|
27500
|
+
);
|
|
27501
|
+
await mergeWorkerFrames(workDir, tasks, framesDir);
|
|
27502
|
+
if (probeSession) {
|
|
27503
|
+
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
27504
|
+
await closeCaptureSession(probeSession);
|
|
27505
|
+
probeSession = null;
|
|
26184
27506
|
}
|
|
26185
|
-
|
|
26186
|
-
|
|
26187
|
-
|
|
26188
|
-
|
|
26189
|
-
|
|
26190
|
-
|
|
26191
|
-
|
|
26192
|
-
|
|
26193
|
-
|
|
26194
|
-
|
|
26195
|
-
|
|
26196
|
-
|
|
26197
|
-
`Capturing frame ${i2 + 1}/${job.totalFrames}`,
|
|
26198
|
-
Math.round(progress),
|
|
26199
|
-
onProgress
|
|
26200
|
-
);
|
|
27507
|
+
} else {
|
|
27508
|
+
const videoInjector = createVideoFrameInjector(frameLookup);
|
|
27509
|
+
const session = probeSession ?? await createCaptureSession(
|
|
27510
|
+
fileServer.url,
|
|
27511
|
+
framesDir,
|
|
27512
|
+
captureOptions,
|
|
27513
|
+
videoInjector,
|
|
27514
|
+
cfg
|
|
27515
|
+
);
|
|
27516
|
+
if (probeSession) {
|
|
27517
|
+
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
27518
|
+
probeSession = null;
|
|
26201
27519
|
}
|
|
26202
|
-
|
|
26203
|
-
|
|
26204
|
-
|
|
27520
|
+
try {
|
|
27521
|
+
if (!session.isInitialized) {
|
|
27522
|
+
await initializeSession(session);
|
|
27523
|
+
}
|
|
27524
|
+
assertNotAborted();
|
|
27525
|
+
lastBrowserConsole = session.browserConsoleBuffer;
|
|
27526
|
+
for (let i2 = 0; i2 < job.totalFrames; i2++) {
|
|
27527
|
+
assertNotAborted();
|
|
27528
|
+
const time = i2 / job.config.fps;
|
|
27529
|
+
await captureFrame(session, i2, time);
|
|
27530
|
+
job.framesRendered = i2 + 1;
|
|
27531
|
+
const frameProgress = (i2 + 1) / job.totalFrames;
|
|
27532
|
+
const progress = 25 + frameProgress * 45;
|
|
27533
|
+
updateJobStatus(
|
|
27534
|
+
job,
|
|
27535
|
+
"rendering",
|
|
27536
|
+
`Capturing frame ${i2 + 1}/${job.totalFrames}`,
|
|
27537
|
+
Math.round(progress),
|
|
27538
|
+
onProgress
|
|
27539
|
+
);
|
|
27540
|
+
}
|
|
27541
|
+
} finally {
|
|
27542
|
+
lastBrowserConsole = session.browserConsoleBuffer;
|
|
27543
|
+
await closeCaptureSession(session);
|
|
27544
|
+
}
|
|
27545
|
+
}
|
|
27546
|
+
perfStages.captureMs = Date.now() - stage4Start;
|
|
27547
|
+
const stage5Start = Date.now();
|
|
27548
|
+
updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
|
|
27549
|
+
const frameExt = needsAlpha ? "png" : "jpg";
|
|
27550
|
+
const framePattern = `frame_%06d.${frameExt}`;
|
|
27551
|
+
const encoderOpts = {
|
|
27552
|
+
fps: job.config.fps,
|
|
27553
|
+
width,
|
|
27554
|
+
height,
|
|
27555
|
+
codec: preset.codec,
|
|
27556
|
+
preset: preset.preset,
|
|
27557
|
+
quality: preset.quality,
|
|
27558
|
+
pixelFormat: preset.pixelFormat,
|
|
27559
|
+
useGpu: job.config.useGpu,
|
|
27560
|
+
hdr: preset.hdr
|
|
27561
|
+
};
|
|
27562
|
+
const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
|
|
27563
|
+
framesDir,
|
|
27564
|
+
framePattern,
|
|
27565
|
+
videoOnlyPath,
|
|
27566
|
+
encoderOpts,
|
|
27567
|
+
chunkedEncodeSize,
|
|
27568
|
+
abortSignal
|
|
27569
|
+
) : await encodeFramesFromDir(
|
|
27570
|
+
framesDir,
|
|
27571
|
+
framePattern,
|
|
27572
|
+
videoOnlyPath,
|
|
27573
|
+
encoderOpts,
|
|
27574
|
+
abortSignal
|
|
27575
|
+
);
|
|
27576
|
+
assertNotAborted();
|
|
27577
|
+
if (!encodeResult.success) {
|
|
27578
|
+
throw new Error(`Encoding failed: ${encodeResult.error}`);
|
|
26205
27579
|
}
|
|
27580
|
+
perfStages.encodeMs = Date.now() - stage5Start;
|
|
26206
27581
|
}
|
|
26207
|
-
perfStages.captureMs = Date.now() - stage4Start;
|
|
26208
|
-
const stage5Start = Date.now();
|
|
26209
|
-
updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
|
|
26210
|
-
const frameExt = needsAlpha ? "png" : "jpg";
|
|
26211
|
-
const framePattern = `frame_%06d.${frameExt}`;
|
|
26212
|
-
const encoderOpts = baseEncoderOpts;
|
|
26213
|
-
const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
|
|
26214
|
-
framesDir,
|
|
26215
|
-
framePattern,
|
|
26216
|
-
videoOnlyPath,
|
|
26217
|
-
encoderOpts,
|
|
26218
|
-
chunkedEncodeSize,
|
|
26219
|
-
abortSignal
|
|
26220
|
-
) : await encodeFramesFromDir(
|
|
26221
|
-
framesDir,
|
|
26222
|
-
framePattern,
|
|
26223
|
-
videoOnlyPath,
|
|
26224
|
-
encoderOpts,
|
|
26225
|
-
abortSignal
|
|
26226
|
-
);
|
|
26227
|
-
assertNotAborted();
|
|
26228
|
-
if (!encodeResult.success) {
|
|
26229
|
-
throw new Error(`Encoding failed: ${encodeResult.error}`);
|
|
26230
|
-
}
|
|
26231
|
-
perfStages.encodeMs = Date.now() - stage5Start;
|
|
26232
27582
|
}
|
|
26233
27583
|
if (probeSession !== null) {
|
|
26234
27584
|
const remainingProbeSession = probeSession;
|
|
@@ -26291,8 +27641,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
26291
27641
|
}
|
|
26292
27642
|
}
|
|
26293
27643
|
if (job.config.debug) {
|
|
26294
|
-
if (
|
|
26295
|
-
const debugOutput =
|
|
27644
|
+
if (existsSync28(outputPath)) {
|
|
27645
|
+
const debugOutput = join31(workDir, `output${videoExt}`);
|
|
26296
27646
|
copyFileSync2(outputPath, debugOutput);
|
|
26297
27647
|
}
|
|
26298
27648
|
} else {
|
|
@@ -26375,7 +27725,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
26375
27725
|
await safeCleanup(
|
|
26376
27726
|
"remove workDir (error)",
|
|
26377
27727
|
() => {
|
|
26378
|
-
if (
|
|
27728
|
+
if (existsSync28(workDir)) rmSync6(workDir, { recursive: true, force: true });
|
|
26379
27729
|
},
|
|
26380
27730
|
log
|
|
26381
27731
|
);
|
|
@@ -26384,7 +27734,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
26384
27734
|
throw error;
|
|
26385
27735
|
}
|
|
26386
27736
|
}
|
|
26387
|
-
var RenderCancelledError;
|
|
27737
|
+
var frameDirMaxIndexCache, FRAME_FILENAME_RE, RenderCancelledError;
|
|
26388
27738
|
var init_renderOrchestrator = __esm({
|
|
26389
27739
|
"../producer/src/services/renderOrchestrator.ts"() {
|
|
26390
27740
|
"use strict";
|
|
@@ -26394,6 +27744,8 @@ var init_renderOrchestrator = __esm({
|
|
|
26394
27744
|
init_htmlCompiler2();
|
|
26395
27745
|
init_logger();
|
|
26396
27746
|
init_paths();
|
|
27747
|
+
frameDirMaxIndexCache = /* @__PURE__ */ new Map();
|
|
27748
|
+
FRAME_FILENAME_RE = /^frame_(\d+)\.png$/;
|
|
26397
27749
|
RenderCancelledError = class extends Error {
|
|
26398
27750
|
reason;
|
|
26399
27751
|
constructor(message = "render_cancelled", reason = "aborted") {
|
|
@@ -26430,8 +27782,8 @@ var init_config3 = __esm({
|
|
|
26430
27782
|
});
|
|
26431
27783
|
|
|
26432
27784
|
// ../producer/src/services/hyperframeLint.ts
|
|
26433
|
-
import { existsSync as
|
|
26434
|
-
import { resolve as resolve16, join as
|
|
27785
|
+
import { existsSync as existsSync29, readFileSync as readFileSync21, statSync as statSync8 } from "fs";
|
|
27786
|
+
import { resolve as resolve16, join as join32 } from "path";
|
|
26435
27787
|
function isStringRecord(value) {
|
|
26436
27788
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
26437
27789
|
return false;
|
|
@@ -26459,7 +27811,7 @@ function pickEntryFile(files, preferredEntryFile) {
|
|
|
26459
27811
|
}
|
|
26460
27812
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
26461
27813
|
const absProjectDir = resolve16(projectDir);
|
|
26462
|
-
if (!
|
|
27814
|
+
if (!existsSync29(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
|
|
26463
27815
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
26464
27816
|
}
|
|
26465
27817
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -26470,7 +27822,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
26470
27822
|
if (!absoluteEntryPath.startsWith(absProjectDir)) {
|
|
26471
27823
|
return { error: `Entry file must stay inside project directory: ${entryFile}` };
|
|
26472
27824
|
}
|
|
26473
|
-
if (
|
|
27825
|
+
if (existsSync29(absoluteEntryPath) && statSync8(absoluteEntryPath).isFile()) {
|
|
26474
27826
|
return {
|
|
26475
27827
|
entryFile,
|
|
26476
27828
|
html: readFileSync21(absoluteEntryPath, "utf-8"),
|
|
@@ -26479,7 +27831,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
26479
27831
|
}
|
|
26480
27832
|
}
|
|
26481
27833
|
return {
|
|
26482
|
-
error: `No HTML entry file found in project directory: ${
|
|
27834
|
+
error: `No HTML entry file found in project directory: ${join32(absProjectDir, preferredEntryFile || "index.html")}`
|
|
26483
27835
|
};
|
|
26484
27836
|
}
|
|
26485
27837
|
function prepareHyperframeLintBody(body) {
|
|
@@ -26567,7 +27919,7 @@ var init_semaphore = __esm({
|
|
|
26567
27919
|
|
|
26568
27920
|
// ../producer/src/server.ts
|
|
26569
27921
|
import {
|
|
26570
|
-
existsSync as
|
|
27922
|
+
existsSync as existsSync30,
|
|
26571
27923
|
mkdirSync as mkdirSync18,
|
|
26572
27924
|
statSync as statSync9,
|
|
26573
27925
|
mkdtempSync,
|
|
@@ -26575,7 +27927,7 @@ import {
|
|
|
26575
27927
|
rmSync as rmSync7,
|
|
26576
27928
|
createReadStream
|
|
26577
27929
|
} from "fs";
|
|
26578
|
-
import { resolve as resolve17, dirname as dirname11, join as
|
|
27930
|
+
import { resolve as resolve17, dirname as dirname11, join as join33 } from "path";
|
|
26579
27931
|
import { tmpdir as tmpdir2 } from "os";
|
|
26580
27932
|
import { parseArgs as parseArgs2 } from "util";
|
|
26581
27933
|
import crypto2 from "crypto";
|
|
@@ -26598,11 +27950,11 @@ async function prepareRenderBody(body) {
|
|
|
26598
27950
|
const projectDir = typeof body.projectDir === "string" ? body.projectDir : void 0;
|
|
26599
27951
|
if (projectDir) {
|
|
26600
27952
|
const absProjectDir = resolve17(projectDir);
|
|
26601
|
-
if (!
|
|
27953
|
+
if (!existsSync30(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
|
|
26602
27954
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
26603
27955
|
}
|
|
26604
27956
|
const entry = options.entryFile || "index.html";
|
|
26605
|
-
if (!
|
|
27957
|
+
if (!existsSync30(resolve17(absProjectDir, entry))) {
|
|
26606
27958
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
26607
27959
|
}
|
|
26608
27960
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -26627,8 +27979,8 @@ async function prepareRenderBody(body) {
|
|
|
26627
27979
|
}
|
|
26628
27980
|
}
|
|
26629
27981
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir2();
|
|
26630
|
-
const tempProjectDir = mkdtempSync(
|
|
26631
|
-
writeFileSync11(
|
|
27982
|
+
const tempProjectDir = mkdtempSync(join33(tempRoot, "producer-project-"));
|
|
27983
|
+
writeFileSync11(join33(tempProjectDir, "index.html"), htmlContent, "utf-8");
|
|
26632
27984
|
return {
|
|
26633
27985
|
prepared: {
|
|
26634
27986
|
input: {
|
|
@@ -26751,7 +28103,7 @@ function createRenderHandlers(options = {}) {
|
|
|
26751
28103
|
log
|
|
26752
28104
|
);
|
|
26753
28105
|
const outputDir = dirname11(absoluteOutputPath);
|
|
26754
|
-
if (!
|
|
28106
|
+
if (!existsSync30(outputDir)) mkdirSync18(outputDir, { recursive: true });
|
|
26755
28107
|
const release2 = await renderSemaphore.acquire();
|
|
26756
28108
|
log.info("render started", {
|
|
26757
28109
|
requestId,
|
|
@@ -26778,7 +28130,7 @@ function createRenderHandlers(options = {}) {
|
|
|
26778
28130
|
log.info(`render progress ${pct}%`, { requestId, stage: j2.currentStage, message });
|
|
26779
28131
|
}
|
|
26780
28132
|
});
|
|
26781
|
-
const fileSize =
|
|
28133
|
+
const fileSize = existsSync30(absoluteOutputPath) ? statSync9(absoluteOutputPath).size : 0;
|
|
26782
28134
|
const durationMs = Date.now() - t0;
|
|
26783
28135
|
const outputToken = store.register(absoluteOutputPath);
|
|
26784
28136
|
const outputUrl = `${outputUrlPrefix}/${outputToken}`;
|
|
@@ -26862,7 +28214,7 @@ function createRenderHandlers(options = {}) {
|
|
|
26862
28214
|
log
|
|
26863
28215
|
);
|
|
26864
28216
|
const outputDir = dirname11(absoluteOutputPath);
|
|
26865
|
-
if (!
|
|
28217
|
+
if (!existsSync30(outputDir)) mkdirSync18(outputDir, { recursive: true });
|
|
26866
28218
|
log.info("render-stream started", { requestId, projectDir: input.projectDir });
|
|
26867
28219
|
const job = createRenderJob({
|
|
26868
28220
|
fps: input.fps,
|
|
@@ -26907,7 +28259,7 @@ function createRenderHandlers(options = {}) {
|
|
|
26907
28259
|
},
|
|
26908
28260
|
abortController.signal
|
|
26909
28261
|
);
|
|
26910
|
-
const fileSize =
|
|
28262
|
+
const fileSize = existsSync30(absoluteOutputPath) ? statSync9(absoluteOutputPath).size : 0;
|
|
26911
28263
|
const outputToken = store.register(absoluteOutputPath);
|
|
26912
28264
|
const outputUrl = `${outputUrlPrefix}/${outputToken}`;
|
|
26913
28265
|
log.info("render-stream completed", { requestId, fileSize, perf: job.perfSummary ?? null });
|
|
@@ -26966,7 +28318,7 @@ function createRenderHandlers(options = {}) {
|
|
|
26966
28318
|
if (!artifact) {
|
|
26967
28319
|
return c2.json({ success: false, error: "Output artifact not found or expired" }, 404);
|
|
26968
28320
|
}
|
|
26969
|
-
if (!
|
|
28321
|
+
if (!existsSync30(artifact.path)) {
|
|
26970
28322
|
store.delete(token);
|
|
26971
28323
|
return c2.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
26972
28324
|
}
|
|
@@ -27103,18 +28455,18 @@ __export(studioServer_exports, {
|
|
|
27103
28455
|
});
|
|
27104
28456
|
import { Hono as Hono5 } from "hono";
|
|
27105
28457
|
import { streamSSE as streamSSE3 } from "hono/streaming";
|
|
27106
|
-
import { existsSync as
|
|
27107
|
-
import { resolve as resolve18, join as
|
|
28458
|
+
import { existsSync as existsSync31, readFileSync as readFileSync22, writeFileSync as writeFileSync12, statSync as statSync10 } from "fs";
|
|
28459
|
+
import { resolve as resolve18, join as join34, basename as basename2 } from "path";
|
|
27108
28460
|
function resolveDistDir() {
|
|
27109
28461
|
const builtPath = resolve18(__dirname, "studio");
|
|
27110
|
-
if (
|
|
28462
|
+
if (existsSync31(resolve18(builtPath, "index.html"))) return builtPath;
|
|
27111
28463
|
const devPath = resolve18(__dirname, "..", "..", "..", "studio", "dist");
|
|
27112
|
-
if (
|
|
28464
|
+
if (existsSync31(resolve18(devPath, "index.html"))) return devPath;
|
|
27113
28465
|
return builtPath;
|
|
27114
28466
|
}
|
|
27115
28467
|
function resolveRuntimePath() {
|
|
27116
28468
|
const builtPath = resolve18(__dirname, "hyperframe-runtime.js");
|
|
27117
|
-
if (
|
|
28469
|
+
if (existsSync31(builtPath)) return builtPath;
|
|
27118
28470
|
const devPath = resolve18(
|
|
27119
28471
|
__dirname,
|
|
27120
28472
|
"..",
|
|
@@ -27124,7 +28476,7 @@ function resolveRuntimePath() {
|
|
|
27124
28476
|
"dist",
|
|
27125
28477
|
"hyperframe.runtime.iife.js"
|
|
27126
28478
|
);
|
|
27127
|
-
if (
|
|
28479
|
+
if (existsSync31(devPath)) return devPath;
|
|
27128
28480
|
return builtPath;
|
|
27129
28481
|
}
|
|
27130
28482
|
async function getThumbnailBrowser() {
|
|
@@ -27186,7 +28538,7 @@ function createStudioServer(options) {
|
|
|
27186
28538
|
return lintHyperframeHtml2(html, opts);
|
|
27187
28539
|
},
|
|
27188
28540
|
runtimeUrl: "/api/runtime.js",
|
|
27189
|
-
rendersDir: () =>
|
|
28541
|
+
rendersDir: () => join34(projectDir, "renders"),
|
|
27190
28542
|
startRender(opts) {
|
|
27191
28543
|
const state = {
|
|
27192
28544
|
id: opts.jobId,
|
|
@@ -27276,7 +28628,7 @@ function createStudioServer(options) {
|
|
|
27276
28628
|
});
|
|
27277
28629
|
});
|
|
27278
28630
|
app.get("/api/runtime.js", (c2) => {
|
|
27279
|
-
if (!
|
|
28631
|
+
if (!existsSync31(runtimePath)) return c2.text("runtime not built", 404);
|
|
27280
28632
|
return c2.body(readFileSync22(runtimePath, "utf-8"), 200, {
|
|
27281
28633
|
"Content-Type": "text/javascript",
|
|
27282
28634
|
"Cache-Control": "no-store"
|
|
@@ -27309,7 +28661,7 @@ function createStudioServer(options) {
|
|
|
27309
28661
|
});
|
|
27310
28662
|
app.get("/assets/*", (c2) => {
|
|
27311
28663
|
const filePath = resolve18(studioDir, c2.req.path.slice(1));
|
|
27312
|
-
if (!
|
|
28664
|
+
if (!existsSync31(filePath) || !statSync10(filePath).isFile()) return c2.text("not found", 404);
|
|
27313
28665
|
const content = readFileSync22(filePath);
|
|
27314
28666
|
return new Response(content, {
|
|
27315
28667
|
headers: { "Content-Type": getMimeType(filePath), "Cache-Control": "no-store" }
|
|
@@ -27317,7 +28669,7 @@ function createStudioServer(options) {
|
|
|
27317
28669
|
});
|
|
27318
28670
|
app.get("/icons/*", (c2) => {
|
|
27319
28671
|
const filePath = resolve18(studioDir, c2.req.path.slice(1));
|
|
27320
|
-
if (!
|
|
28672
|
+
if (!existsSync31(filePath) || !statSync10(filePath).isFile()) return c2.text("not found", 404);
|
|
27321
28673
|
const content = readFileSync22(filePath);
|
|
27322
28674
|
return new Response(content, {
|
|
27323
28675
|
headers: { "Content-Type": getMimeType(filePath), "Cache-Control": "no-store" }
|
|
@@ -27325,7 +28677,7 @@ function createStudioServer(options) {
|
|
|
27325
28677
|
});
|
|
27326
28678
|
app.get("*", (c2) => {
|
|
27327
28679
|
const indexPath = resolve18(studioDir, "index.html");
|
|
27328
|
-
if (!
|
|
28680
|
+
if (!existsSync31(indexPath)) {
|
|
27329
28681
|
return c2.text("Studio not found. Rebuild with: pnpm run build", 500);
|
|
27330
28682
|
}
|
|
27331
28683
|
return c2.html(readFileSync22(indexPath, "utf-8"));
|
|
@@ -27351,20 +28703,20 @@ __export(preview_exports, {
|
|
|
27351
28703
|
examples: () => examples
|
|
27352
28704
|
});
|
|
27353
28705
|
import { spawn as spawn8 } from "child_process";
|
|
27354
|
-
import { existsSync as
|
|
27355
|
-
import { resolve as resolve19, dirname as dirname12, basename as basename3, join as
|
|
28706
|
+
import { existsSync as existsSync32, lstatSync, symlinkSync, unlinkSync as unlinkSync5, readlinkSync, mkdirSync as mkdirSync19 } from "fs";
|
|
28707
|
+
import { resolve as resolve19, dirname as dirname12, basename as basename3, join as join35 } from "path";
|
|
27356
28708
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
27357
28709
|
import { createRequire } from "module";
|
|
27358
28710
|
async function runDevMode(dir, projectName) {
|
|
27359
28711
|
const thisFile = fileURLToPath4(import.meta.url);
|
|
27360
28712
|
const repoRoot = resolve19(dirname12(thisFile), "..", "..", "..", "..");
|
|
27361
|
-
const projectsDir =
|
|
28713
|
+
const projectsDir = join35(repoRoot, "packages", "studio", "data", "projects");
|
|
27362
28714
|
const pName = projectName ?? basename3(dir);
|
|
27363
|
-
const symlinkPath =
|
|
28715
|
+
const symlinkPath = join35(projectsDir, pName);
|
|
27364
28716
|
mkdirSync19(projectsDir, { recursive: true });
|
|
27365
28717
|
let createdSymlink = false;
|
|
27366
28718
|
if (dir !== symlinkPath) {
|
|
27367
|
-
if (
|
|
28719
|
+
if (existsSync32(symlinkPath)) {
|
|
27368
28720
|
try {
|
|
27369
28721
|
const stat3 = lstatSync(symlinkPath);
|
|
27370
28722
|
if (stat3.isSymbolicLink()) {
|
|
@@ -27376,7 +28728,7 @@ async function runDevMode(dir, projectName) {
|
|
|
27376
28728
|
} catch {
|
|
27377
28729
|
}
|
|
27378
28730
|
}
|
|
27379
|
-
if (!
|
|
28731
|
+
if (!existsSync32(symlinkPath)) {
|
|
27380
28732
|
symlinkSync(dir, symlinkPath, "dir");
|
|
27381
28733
|
createdSymlink = true;
|
|
27382
28734
|
}
|
|
@@ -27384,7 +28736,7 @@ async function runDevMode(dir, projectName) {
|
|
|
27384
28736
|
Wt2(c.bold("hyperframes preview"));
|
|
27385
28737
|
const s2 = be();
|
|
27386
28738
|
s2.start("Starting studio...");
|
|
27387
|
-
const studioPkgDir =
|
|
28739
|
+
const studioPkgDir = join35(repoRoot, "packages", "studio");
|
|
27388
28740
|
const child = spawn8("pnpm", ["exec", "vite"], {
|
|
27389
28741
|
cwd: studioPkgDir,
|
|
27390
28742
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -27418,7 +28770,7 @@ async function runDevMode(dir, projectName) {
|
|
|
27418
28770
|
if (createdSymlink) {
|
|
27419
28771
|
process.on("exit", () => {
|
|
27420
28772
|
try {
|
|
27421
|
-
if (
|
|
28773
|
+
if (existsSync32(symlinkPath)) unlinkSync5(symlinkPath);
|
|
27422
28774
|
} catch {
|
|
27423
28775
|
}
|
|
27424
28776
|
});
|
|
@@ -27429,7 +28781,7 @@ async function runDevMode(dir, projectName) {
|
|
|
27429
28781
|
}
|
|
27430
28782
|
function hasLocalStudio(dir) {
|
|
27431
28783
|
try {
|
|
27432
|
-
const req = createRequire(
|
|
28784
|
+
const req = createRequire(join35(dir, "package.json"));
|
|
27433
28785
|
req.resolve("@hyperframes/studio/package.json");
|
|
27434
28786
|
return true;
|
|
27435
28787
|
} catch {
|
|
@@ -27437,20 +28789,20 @@ function hasLocalStudio(dir) {
|
|
|
27437
28789
|
}
|
|
27438
28790
|
}
|
|
27439
28791
|
async function runLocalStudioMode(dir, projectName) {
|
|
27440
|
-
const req = createRequire(
|
|
28792
|
+
const req = createRequire(join35(dir, "package.json"));
|
|
27441
28793
|
const studioPkgPath = dirname12(req.resolve("@hyperframes/studio/package.json"));
|
|
27442
28794
|
const pName = projectName ?? basename3(dir);
|
|
27443
|
-
const projectsDir =
|
|
27444
|
-
const symlinkPath =
|
|
28795
|
+
const projectsDir = join35(studioPkgPath, "data", "projects");
|
|
28796
|
+
const symlinkPath = join35(projectsDir, pName);
|
|
27445
28797
|
mkdirSync19(projectsDir, { recursive: true });
|
|
27446
28798
|
let createdSymlink = false;
|
|
27447
28799
|
if (dir !== symlinkPath) {
|
|
27448
|
-
if (
|
|
28800
|
+
if (existsSync32(symlinkPath) && lstatSync(symlinkPath).isSymbolicLink()) {
|
|
27449
28801
|
if (resolve19(readlinkSync(symlinkPath)) !== resolve19(dir)) {
|
|
27450
28802
|
unlinkSync5(symlinkPath);
|
|
27451
28803
|
}
|
|
27452
28804
|
}
|
|
27453
|
-
if (!
|
|
28805
|
+
if (!existsSync32(symlinkPath)) {
|
|
27454
28806
|
symlinkSync(dir, symlinkPath, "dir");
|
|
27455
28807
|
createdSymlink = true;
|
|
27456
28808
|
}
|
|
@@ -27489,7 +28841,7 @@ async function runLocalStudioMode(dir, projectName) {
|
|
|
27489
28841
|
if (createdSymlink) {
|
|
27490
28842
|
process.on("exit", () => {
|
|
27491
28843
|
try {
|
|
27492
|
-
if (
|
|
28844
|
+
if (existsSync32(symlinkPath)) unlinkSync5(symlinkPath);
|
|
27493
28845
|
} catch {
|
|
27494
28846
|
}
|
|
27495
28847
|
});
|
|
@@ -27629,8 +28981,8 @@ var init_preview2 = __esm({
|
|
|
27629
28981
|
const dir = resolve19(rawArg ?? ".");
|
|
27630
28982
|
const isImplicitCwd = !rawArg || rawArg === "." || rawArg === "./";
|
|
27631
28983
|
const projectName = isImplicitCwd ? basename3(process.env.PWD ?? dir) : basename3(dir);
|
|
27632
|
-
const indexPath =
|
|
27633
|
-
if (
|
|
28984
|
+
const indexPath = join35(dir, "index.html");
|
|
28985
|
+
if (existsSync32(indexPath)) {
|
|
27634
28986
|
const project = { dir, name: projectName, indexPath };
|
|
27635
28987
|
const lintResult = lintProject(project);
|
|
27636
28988
|
if (lintResult.totalErrors > 0 || lintResult.totalWarnings > 0) {
|
|
@@ -27659,15 +29011,15 @@ __export(init_exports, {
|
|
|
27659
29011
|
examples: () => examples2
|
|
27660
29012
|
});
|
|
27661
29013
|
import {
|
|
27662
|
-
existsSync as
|
|
29014
|
+
existsSync as existsSync33,
|
|
27663
29015
|
mkdirSync as mkdirSync20,
|
|
27664
29016
|
copyFileSync as copyFileSync3,
|
|
27665
29017
|
cpSync,
|
|
27666
29018
|
writeFileSync as writeFileSync13,
|
|
27667
29019
|
readFileSync as readFileSync23,
|
|
27668
|
-
readdirSync as
|
|
29020
|
+
readdirSync as readdirSync12
|
|
27669
29021
|
} from "fs";
|
|
27670
|
-
import { resolve as resolve20, basename as basename4, join as
|
|
29022
|
+
import { resolve as resolve20, basename as basename4, join as join36, dirname as dirname13 } from "path";
|
|
27671
29023
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
27672
29024
|
import { execFileSync as execFileSync4, spawn as spawn9 } from "child_process";
|
|
27673
29025
|
function probeVideo(filePath) {
|
|
@@ -27739,7 +29091,7 @@ function resolveAssetDir(devSegments, builtSegments) {
|
|
|
27739
29091
|
const base = dirname13(fileURLToPath5(import.meta.url));
|
|
27740
29092
|
const devPath = resolve20(base, ...devSegments);
|
|
27741
29093
|
const builtPath = resolve20(base, ...builtSegments);
|
|
27742
|
-
return
|
|
29094
|
+
return existsSync33(devPath) ? devPath : builtPath;
|
|
27743
29095
|
}
|
|
27744
29096
|
function getStaticTemplateDir(templateId) {
|
|
27745
29097
|
return resolveAssetDir(["..", "templates", templateId], ["templates", templateId]);
|
|
@@ -27748,7 +29100,7 @@ function getSharedTemplateDir() {
|
|
|
27748
29100
|
return resolveAssetDir(["..", "templates", "_shared"], ["templates", "_shared"]);
|
|
27749
29101
|
}
|
|
27750
29102
|
function patchVideoSrc(dir, videoFilename, durationSeconds) {
|
|
27751
|
-
const htmlFiles =
|
|
29103
|
+
const htmlFiles = readdirSync12(dir, { withFileTypes: true, recursive: true }).filter((e2) => e2.isFile() && e2.name.endsWith(".html")).map((e2) => join36(e2.parentPath ?? e2.path, e2.name));
|
|
27752
29104
|
for (const file of htmlFiles) {
|
|
27753
29105
|
let content = readFileSync23(file, "utf-8");
|
|
27754
29106
|
if (videoFilename) {
|
|
@@ -27855,7 +29207,7 @@ async function handleVideoFile(videoPath, destDir, interactive) {
|
|
|
27855
29207
|
async function scaffoldProject(destDir, name, templateId, localVideoName, durationSeconds) {
|
|
27856
29208
|
mkdirSync20(destDir, { recursive: true });
|
|
27857
29209
|
const templateDir = getStaticTemplateDir(templateId);
|
|
27858
|
-
if (
|
|
29210
|
+
if (existsSync33(templateDir)) {
|
|
27859
29211
|
cpSync(templateDir, destDir, { recursive: true });
|
|
27860
29212
|
} else {
|
|
27861
29213
|
await fetchRemoteTemplate(templateId, destDir);
|
|
@@ -27874,14 +29226,14 @@ async function scaffoldProject(destDir, name, templateId, localVideoName, durati
|
|
|
27874
29226
|
),
|
|
27875
29227
|
"utf-8"
|
|
27876
29228
|
);
|
|
27877
|
-
if (!
|
|
29229
|
+
if (!existsSync33(resolve20(destDir, "hyperframes.json"))) {
|
|
27878
29230
|
const { writeProjectConfig: writeProjectConfig2, DEFAULT_PROJECT_CONFIG: DEFAULT_PROJECT_CONFIG2 } = await Promise.resolve().then(() => (init_projectConfig(), projectConfig_exports));
|
|
27879
29231
|
writeProjectConfig2(destDir, DEFAULT_PROJECT_CONFIG2);
|
|
27880
29232
|
}
|
|
27881
29233
|
const sharedDir = getSharedTemplateDir();
|
|
27882
|
-
if (
|
|
27883
|
-
for (const entry of
|
|
27884
|
-
const src =
|
|
29234
|
+
if (existsSync33(sharedDir)) {
|
|
29235
|
+
for (const entry of readdirSync12(sharedDir, { withFileTypes: true })) {
|
|
29236
|
+
const src = join36(sharedDir, entry.name);
|
|
27885
29237
|
const dest = resolve20(destDir, entry.name);
|
|
27886
29238
|
if (entry.isFile() || entry.isSymbolicLink()) {
|
|
27887
29239
|
copyFileSync3(src, dest);
|
|
@@ -27995,7 +29347,7 @@ var init_init = __esm({
|
|
|
27995
29347
|
const templateId2 = exampleFlag ?? "blank";
|
|
27996
29348
|
const name2 = args.name ?? "my-video";
|
|
27997
29349
|
const destDir2 = resolve20(name2);
|
|
27998
|
-
if (
|
|
29350
|
+
if (existsSync33(destDir2) && readdirSync12(destDir2).length > 0) {
|
|
27999
29351
|
console.error(c.error(`Directory already exists and is not empty: ${name2}`));
|
|
28000
29352
|
process.exit(1);
|
|
28001
29353
|
}
|
|
@@ -28009,7 +29361,7 @@ var init_init = __esm({
|
|
|
28009
29361
|
}
|
|
28010
29362
|
if (videoFlag) {
|
|
28011
29363
|
const videoPath = resolve20(videoFlag);
|
|
28012
|
-
if (!
|
|
29364
|
+
if (!existsSync33(videoPath)) {
|
|
28013
29365
|
console.error(c.error(`Video file not found: ${videoFlag}`));
|
|
28014
29366
|
process.exit(1);
|
|
28015
29367
|
}
|
|
@@ -28023,7 +29375,7 @@ var init_init = __esm({
|
|
|
28023
29375
|
}
|
|
28024
29376
|
if (audioFlag) {
|
|
28025
29377
|
const audioPath = resolve20(audioFlag);
|
|
28026
|
-
if (!
|
|
29378
|
+
if (!existsSync33(audioPath)) {
|
|
28027
29379
|
console.error(c.error(`Audio file not found: ${audioFlag}`));
|
|
28028
29380
|
process.exit(1);
|
|
28029
29381
|
}
|
|
@@ -28069,11 +29421,11 @@ var init_init = __esm({
|
|
|
28069
29421
|
}
|
|
28070
29422
|
trackInitTemplate(templateId2);
|
|
28071
29423
|
const transcriptFile2 = resolve20(destDir2, "transcript.json");
|
|
28072
|
-
if (
|
|
29424
|
+
if (existsSync33(transcriptFile2)) {
|
|
28073
29425
|
await patchTranscript(destDir2, transcriptFile2);
|
|
28074
29426
|
}
|
|
28075
29427
|
console.log(c.success(`Created ${c.accent(name2 + "/")}`));
|
|
28076
|
-
for (const f3 of
|
|
29428
|
+
for (const f3 of readdirSync12(destDir2).filter((f4) => !f4.startsWith("."))) {
|
|
28077
29429
|
console.log(` ${c.accent(f3)}`);
|
|
28078
29430
|
}
|
|
28079
29431
|
console.log();
|
|
@@ -28121,7 +29473,7 @@ var init_init = __esm({
|
|
|
28121
29473
|
name = nameResult;
|
|
28122
29474
|
}
|
|
28123
29475
|
const destDir = resolve20(name);
|
|
28124
|
-
if (
|
|
29476
|
+
if (existsSync33(destDir) && readdirSync12(destDir).length > 0) {
|
|
28125
29477
|
const overwrite = await Rt({
|
|
28126
29478
|
message: `Directory ${c.accent(name)} already exists and is not empty. Overwrite?`,
|
|
28127
29479
|
initialValue: false
|
|
@@ -28136,7 +29488,7 @@ var init_init = __esm({
|
|
|
28136
29488
|
let videoDuration;
|
|
28137
29489
|
if (videoFlag) {
|
|
28138
29490
|
const videoPath = resolve20(videoFlag);
|
|
28139
|
-
if (!
|
|
29491
|
+
if (!existsSync33(videoPath)) {
|
|
28140
29492
|
R2.error(`File not found: ${videoFlag}`);
|
|
28141
29493
|
Nt("Setup cancelled.");
|
|
28142
29494
|
process.exit(1);
|
|
@@ -28148,7 +29500,7 @@ var init_init = __esm({
|
|
|
28148
29500
|
videoDuration = result.meta.durationSeconds;
|
|
28149
29501
|
} else if (audioFlag) {
|
|
28150
29502
|
const audioPath = resolve20(audioFlag);
|
|
28151
|
-
if (!
|
|
29503
|
+
if (!existsSync33(audioPath)) {
|
|
28152
29504
|
R2.error(`File not found: ${audioFlag}`);
|
|
28153
29505
|
Nt("Setup cancelled.");
|
|
28154
29506
|
process.exit(1);
|
|
@@ -28241,10 +29593,10 @@ ${c.dim("Use --example blank for offline use.")}`
|
|
|
28241
29593
|
}
|
|
28242
29594
|
trackInitTemplate(templateId);
|
|
28243
29595
|
const transcriptFile = resolve20(destDir, "transcript.json");
|
|
28244
|
-
if (
|
|
29596
|
+
if (existsSync33(transcriptFile)) {
|
|
28245
29597
|
await patchTranscript(destDir, transcriptFile);
|
|
28246
29598
|
}
|
|
28247
|
-
const files =
|
|
29599
|
+
const files = readdirSync12(destDir);
|
|
28248
29600
|
Vt2(files.map((f3) => c.accent(f3)).join("\n"), c.success(`Created ${name}/`));
|
|
28249
29601
|
if (!skipSkills) {
|
|
28250
29602
|
const installSkills = await Rt({
|
|
@@ -28288,9 +29640,10 @@ function detectProvider() {
|
|
|
28288
29640
|
{ cmd: "xclip", args: ["-selection", "clipboard"] },
|
|
28289
29641
|
{ cmd: "xsel", args: ["--clipboard", "--input"] }
|
|
28290
29642
|
];
|
|
29643
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
28291
29644
|
for (const p of candidates) {
|
|
28292
|
-
const
|
|
28293
|
-
if (
|
|
29645
|
+
const result = spawnSync(cmd, [p.cmd], { stdio: "ignore" });
|
|
29646
|
+
if (result.status === 0) return p;
|
|
28294
29647
|
}
|
|
28295
29648
|
return void 0;
|
|
28296
29649
|
}
|
|
@@ -28326,7 +29679,7 @@ __export(add_exports, {
|
|
|
28326
29679
|
remapTarget: () => remapTarget,
|
|
28327
29680
|
runAdd: () => runAdd
|
|
28328
29681
|
});
|
|
28329
|
-
import { existsSync as
|
|
29682
|
+
import { existsSync as existsSync34 } from "fs";
|
|
28330
29683
|
import { resolve as resolve21, relative as relative3 } from "path";
|
|
28331
29684
|
function remapTarget(item, originalTarget, paths) {
|
|
28332
29685
|
if (item.type === "hyperframes:block") {
|
|
@@ -28352,8 +29705,8 @@ function buildSnippet(item, relativeTarget) {
|
|
|
28352
29705
|
async function runAdd(opts) {
|
|
28353
29706
|
const projectDir = resolve21(opts.projectDir);
|
|
28354
29707
|
let config = loadProjectConfig(projectDir);
|
|
28355
|
-
const hasConfig =
|
|
28356
|
-
if (!hasConfig &&
|
|
29708
|
+
const hasConfig = existsSync34(projectConfigPath(projectDir));
|
|
29709
|
+
if (!hasConfig && existsSync34(resolve21(projectDir, "index.html"))) {
|
|
28357
29710
|
writeProjectConfig(projectDir, DEFAULT_PROJECT_CONFIG);
|
|
28358
29711
|
config = DEFAULT_PROJECT_CONFIG;
|
|
28359
29712
|
}
|
|
@@ -28452,10 +29805,10 @@ var init_add = __esm({
|
|
|
28452
29805
|
const projectDir = resolve21(args.dir ?? process.cwd());
|
|
28453
29806
|
const json = args.json === true;
|
|
28454
29807
|
const skipClipboard = args["no-clipboard"] === true;
|
|
28455
|
-
const hasConfigBefore =
|
|
29808
|
+
const hasConfigBefore = existsSync34(projectConfigPath(projectDir));
|
|
28456
29809
|
try {
|
|
28457
29810
|
const result = await runAdd({ name: args.name, projectDir, skipClipboard });
|
|
28458
|
-
const wroteConfig = !hasConfigBefore &&
|
|
29811
|
+
const wroteConfig = !hasConfigBefore && existsSync34(projectConfigPath(projectDir));
|
|
28459
29812
|
if (json) {
|
|
28460
29813
|
console.log(JSON.stringify(result));
|
|
28461
29814
|
return;
|
|
@@ -28665,17 +30018,17 @@ var init_format = __esm({
|
|
|
28665
30018
|
});
|
|
28666
30019
|
|
|
28667
30020
|
// src/utils/project.ts
|
|
28668
|
-
import { existsSync as
|
|
30021
|
+
import { existsSync as existsSync35, statSync as statSync11 } from "fs";
|
|
28669
30022
|
import { resolve as resolve23, basename as basename5 } from "path";
|
|
28670
30023
|
function resolveProject(dirArg) {
|
|
28671
30024
|
const dir = resolve23(dirArg ?? ".");
|
|
28672
30025
|
const name = basename5(dir);
|
|
28673
30026
|
const indexPath = resolve23(dir, "index.html");
|
|
28674
|
-
if (!
|
|
30027
|
+
if (!existsSync35(dir) || !statSync11(dir).isDirectory()) {
|
|
28675
30028
|
errorBox("Not a directory: " + dir);
|
|
28676
30029
|
process.exit(1);
|
|
28677
30030
|
}
|
|
28678
|
-
if (!
|
|
30031
|
+
if (!existsSync35(indexPath)) {
|
|
28679
30032
|
errorBox(
|
|
28680
30033
|
"No composition found in " + dir,
|
|
28681
30034
|
"No index.html file found.",
|
|
@@ -28698,7 +30051,7 @@ __export(play_exports, {
|
|
|
28698
30051
|
default: () => play_default,
|
|
28699
30052
|
examples: () => examples5
|
|
28700
30053
|
});
|
|
28701
|
-
import { existsSync as
|
|
30054
|
+
import { existsSync as existsSync36, readFileSync as readFileSync24 } from "fs";
|
|
28702
30055
|
import { resolve as resolve24, dirname as dirname14 } from "path";
|
|
28703
30056
|
function commandDir() {
|
|
28704
30057
|
return dirname14(new URL(import.meta.url).pathname);
|
|
@@ -28713,7 +30066,7 @@ function resolveRuntimePath2() {
|
|
|
28713
30066
|
resolve24(d, "..", "..", "..", "core", "dist", "hyperframe.runtime.iife.js")
|
|
28714
30067
|
];
|
|
28715
30068
|
for (const p of candidates) {
|
|
28716
|
-
if (
|
|
30069
|
+
if (existsSync36(p)) return p;
|
|
28717
30070
|
}
|
|
28718
30071
|
return null;
|
|
28719
30072
|
}
|
|
@@ -28727,7 +30080,7 @@ function resolvePlayerPath() {
|
|
|
28727
30080
|
resolve24(d, "..", "hyperframes-player.global.js")
|
|
28728
30081
|
];
|
|
28729
30082
|
for (const p of candidates) {
|
|
28730
|
-
if (
|
|
30083
|
+
if (existsSync36(p)) return p;
|
|
28731
30084
|
}
|
|
28732
30085
|
return null;
|
|
28733
30086
|
}
|
|
@@ -28829,7 +30182,7 @@ var init_play = __esm({
|
|
|
28829
30182
|
const reqPath = ctx.req.path.replace("/composition/", "");
|
|
28830
30183
|
const filePath = resolve24(project.dir, reqPath);
|
|
28831
30184
|
if (!filePath.startsWith(project.dir)) return ctx.text("Forbidden", 403);
|
|
28832
|
-
if (!
|
|
30185
|
+
if (!existsSync36(filePath)) return ctx.text("Not found", 404);
|
|
28833
30186
|
const content = readFileSync24(filePath, "utf-8");
|
|
28834
30187
|
if (filePath.endsWith(".html")) {
|
|
28835
30188
|
const injected = injectRuntime(content);
|
|
@@ -28945,12 +30298,14 @@ __export(ffmpeg_exports, {
|
|
|
28945
30298
|
import { execSync as execSync2 } from "child_process";
|
|
28946
30299
|
function findFFmpeg() {
|
|
28947
30300
|
try {
|
|
28948
|
-
const
|
|
30301
|
+
const cmd = process.platform === "win32" ? "where ffmpeg" : "which ffmpeg";
|
|
30302
|
+
const output = execSync2(cmd, {
|
|
28949
30303
|
encoding: "utf-8",
|
|
28950
30304
|
stdio: ["pipe", "pipe", "pipe"],
|
|
28951
30305
|
timeout: 5e3
|
|
28952
|
-
})
|
|
28953
|
-
|
|
30306
|
+
});
|
|
30307
|
+
const first = output.split(/\r?\n/).map((s2) => s2.trim()).find(Boolean);
|
|
30308
|
+
return first || void 0;
|
|
28954
30309
|
} catch {
|
|
28955
30310
|
return void 0;
|
|
28956
30311
|
}
|
|
@@ -28979,7 +30334,7 @@ __export(render_exports, {
|
|
|
28979
30334
|
});
|
|
28980
30335
|
import { mkdirSync as mkdirSync21, readFileSync as readFileSync25, statSync as statSync12, writeFileSync as writeFileSync14, rmSync as rmSync8 } from "fs";
|
|
28981
30336
|
import { cpus as cpus3, freemem as freemem3, tmpdir as tmpdir3 } from "os";
|
|
28982
|
-
import { resolve as resolve25, dirname as dirname15, join as
|
|
30337
|
+
import { resolve as resolve25, dirname as dirname15, join as join37, basename as basename6 } from "path";
|
|
28983
30338
|
import { execFileSync as execFileSync5, spawn as spawn10 } from "child_process";
|
|
28984
30339
|
function defaultWorkerCount() {
|
|
28985
30340
|
return Math.max(1, Math.min(Math.floor(CPU_CORE_COUNT * 3 / 4), 8));
|
|
@@ -29016,9 +30371,9 @@ function ensureDockerImage(version, quiet) {
|
|
|
29016
30371
|
}
|
|
29017
30372
|
if (!quiet) console.log(c.dim(` Building Docker image: ${tag}...`));
|
|
29018
30373
|
const dockerfilePath = resolveDockerfilePath();
|
|
29019
|
-
const tmpDir =
|
|
30374
|
+
const tmpDir = join37(tmpdir3(), `hyperframes-docker-${Date.now()}`);
|
|
29020
30375
|
mkdirSync21(tmpDir, { recursive: true });
|
|
29021
|
-
writeFileSync14(
|
|
30376
|
+
writeFileSync14(join37(tmpDir, "Dockerfile"), readFileSync25(dockerfilePath));
|
|
29022
30377
|
try {
|
|
29023
30378
|
execFileSync5(
|
|
29024
30379
|
"docker",
|
|
@@ -29377,7 +30732,7 @@ var init_render2 = __esm({
|
|
|
29377
30732
|
const now = /* @__PURE__ */ new Date();
|
|
29378
30733
|
const datePart = now.toISOString().slice(0, 10);
|
|
29379
30734
|
const timePart = now.toTimeString().slice(0, 8).replace(/:/g, "-");
|
|
29380
|
-
const outputPath = args.output ? resolve25(args.output) :
|
|
30735
|
+
const outputPath = args.output ? resolve25(args.output) : join37(rendersDir, `${project.name}_${datePart}_${timePart}${ext}`);
|
|
29381
30736
|
mkdirSync21(dirname15(outputPath), { recursive: true });
|
|
29382
30737
|
const useDocker = args.docker ?? false;
|
|
29383
30738
|
const useGpu = args.gpu ?? false;
|
|
@@ -29702,12 +31057,12 @@ __export(info_exports, {
|
|
|
29702
31057
|
default: () => info_default,
|
|
29703
31058
|
examples: () => examples8
|
|
29704
31059
|
});
|
|
29705
|
-
import { readFileSync as readFileSync26, readdirSync as
|
|
29706
|
-
import { join as
|
|
31060
|
+
import { readFileSync as readFileSync26, readdirSync as readdirSync13, statSync as statSync13 } from "fs";
|
|
31061
|
+
import { join as join38 } from "path";
|
|
29707
31062
|
function totalSize(dir) {
|
|
29708
31063
|
let total = 0;
|
|
29709
|
-
for (const entry of
|
|
29710
|
-
const path2 =
|
|
31064
|
+
for (const entry of readdirSync13(dir, { withFileTypes: true })) {
|
|
31065
|
+
const path2 = join38(dir, entry.name);
|
|
29711
31066
|
if (entry.isDirectory()) {
|
|
29712
31067
|
total += totalSize(path2);
|
|
29713
31068
|
} else {
|
|
@@ -29795,7 +31150,7 @@ __export(compositions_exports, {
|
|
|
29795
31150
|
default: () => compositions_default,
|
|
29796
31151
|
examples: () => examples9
|
|
29797
31152
|
});
|
|
29798
|
-
import { existsSync as
|
|
31153
|
+
import { existsSync as existsSync37, readFileSync as readFileSync27 } from "fs";
|
|
29799
31154
|
import { resolve as resolve26, dirname as dirname16 } from "path";
|
|
29800
31155
|
function parseCompositions(html, baseDir) {
|
|
29801
31156
|
const parser = new DOMParser();
|
|
@@ -29809,7 +31164,7 @@ function parseCompositions(html, baseDir) {
|
|
|
29809
31164
|
const compositionSrc = div.getAttribute("data-composition-src");
|
|
29810
31165
|
if (compositionSrc) {
|
|
29811
31166
|
const subPath = resolve26(baseDir, compositionSrc);
|
|
29812
|
-
if (
|
|
31167
|
+
if (existsSync37(subPath)) {
|
|
29813
31168
|
const subHtml = readFileSync27(subPath, "utf-8");
|
|
29814
31169
|
const subInfo = parseSubComposition(subHtml, id, width, height);
|
|
29815
31170
|
compositions.push({ ...subInfo, source: compositionSrc });
|
|
@@ -29942,8 +31297,8 @@ __export(benchmark_exports, {
|
|
|
29942
31297
|
default: () => benchmark_default,
|
|
29943
31298
|
examples: () => examples10
|
|
29944
31299
|
});
|
|
29945
|
-
import { existsSync as
|
|
29946
|
-
import { resolve as resolve27, join as
|
|
31300
|
+
import { existsSync as existsSync38, statSync as statSync14 } from "fs";
|
|
31301
|
+
import { resolve as resolve27, join as join39 } from "path";
|
|
29947
31302
|
var examples10, DEFAULT_CONFIGS, benchmark_default;
|
|
29948
31303
|
var init_benchmark = __esm({
|
|
29949
31304
|
"src/commands/benchmark.ts"() {
|
|
@@ -30018,7 +31373,7 @@ var init_benchmark = __esm({
|
|
|
30018
31373
|
s2?.start(`Benchmarking ${config.label}...`);
|
|
30019
31374
|
for (let i2 = 0; i2 < runsPerConfig; i2++) {
|
|
30020
31375
|
s2?.message(`${config.label} \u2014 run ${i2 + 1}/${runsPerConfig}`);
|
|
30021
|
-
const outputPath =
|
|
31376
|
+
const outputPath = join39(
|
|
30022
31377
|
benchDir,
|
|
30023
31378
|
`${config.label.replace(/[^a-zA-Z0-9]/g, "_")}_run${i2}.mp4`
|
|
30024
31379
|
);
|
|
@@ -30032,7 +31387,7 @@ var init_benchmark = __esm({
|
|
|
30032
31387
|
await producer.executeRenderJob(job, project.dir, outputPath);
|
|
30033
31388
|
const elapsedMs = Date.now() - startTime;
|
|
30034
31389
|
let fileSize = null;
|
|
30035
|
-
if (
|
|
31390
|
+
if (existsSync38(outputPath)) {
|
|
30036
31391
|
const stat3 = statSync14(outputPath);
|
|
30037
31392
|
fileSize = stat3.size;
|
|
30038
31393
|
}
|
|
@@ -30248,8 +31603,8 @@ __export(transcribe_exports2, {
|
|
|
30248
31603
|
default: () => transcribe_default,
|
|
30249
31604
|
examples: () => examples12
|
|
30250
31605
|
});
|
|
30251
|
-
import { existsSync as
|
|
30252
|
-
import { resolve as resolve28, join as
|
|
31606
|
+
import { existsSync as existsSync39, writeFileSync as writeFileSync15 } from "fs";
|
|
31607
|
+
import { resolve as resolve28, join as join40, extname as extname7 } from "path";
|
|
30253
31608
|
async function importTranscript(inputPath, dir, json) {
|
|
30254
31609
|
const { loadTranscript: loadTranscript2, patchCaptionHtml: patchCaptionHtml2 } = await Promise.resolve().then(() => (init_normalize(), normalize_exports));
|
|
30255
31610
|
const { words, format } = loadTranscript2(inputPath);
|
|
@@ -30257,7 +31612,7 @@ async function importTranscript(inputPath, dir, json) {
|
|
|
30257
31612
|
console.error(c.error("No words found in transcript."));
|
|
30258
31613
|
process.exit(1);
|
|
30259
31614
|
}
|
|
30260
|
-
const outPath =
|
|
31615
|
+
const outPath = join40(dir, "transcript.json");
|
|
30261
31616
|
writeFileSync15(outPath, JSON.stringify(words, null, 2));
|
|
30262
31617
|
patchCaptionHtml2(dir, words);
|
|
30263
31618
|
if (json) {
|
|
@@ -30374,7 +31729,7 @@ var init_transcribe2 = __esm({
|
|
|
30374
31729
|
},
|
|
30375
31730
|
async run({ args }) {
|
|
30376
31731
|
const inputPath = resolve28(args.input);
|
|
30377
|
-
if (!
|
|
31732
|
+
if (!existsSync39(inputPath)) {
|
|
30378
31733
|
console.error(c.error(`File not found: ${args.input}`));
|
|
30379
31734
|
process.exit(1);
|
|
30380
31735
|
}
|
|
@@ -30395,12 +31750,12 @@ var init_transcribe2 = __esm({
|
|
|
30395
31750
|
});
|
|
30396
31751
|
|
|
30397
31752
|
// src/tts/manager.ts
|
|
30398
|
-
import { existsSync as
|
|
30399
|
-
import { homedir as
|
|
30400
|
-
import { join as
|
|
31753
|
+
import { existsSync as existsSync40, mkdirSync as mkdirSync22 } from "fs";
|
|
31754
|
+
import { homedir as homedir8 } from "os";
|
|
31755
|
+
import { join as join41 } from "path";
|
|
30401
31756
|
async function ensureModel2(model = DEFAULT_MODEL2, options) {
|
|
30402
|
-
const modelPath =
|
|
30403
|
-
if (
|
|
31757
|
+
const modelPath = join41(MODELS_DIR2, `${model}.onnx`);
|
|
31758
|
+
if (existsSync40(modelPath)) return modelPath;
|
|
30404
31759
|
const url = MODEL_URLS[model];
|
|
30405
31760
|
if (!url) {
|
|
30406
31761
|
throw new Error(
|
|
@@ -30410,18 +31765,18 @@ async function ensureModel2(model = DEFAULT_MODEL2, options) {
|
|
|
30410
31765
|
mkdirSync22(MODELS_DIR2, { recursive: true });
|
|
30411
31766
|
options?.onProgress?.(`Downloading TTS model ${model} (~311 MB)...`);
|
|
30412
31767
|
await downloadFile(url, modelPath);
|
|
30413
|
-
if (!
|
|
31768
|
+
if (!existsSync40(modelPath)) {
|
|
30414
31769
|
throw new Error(`Model download failed: ${model}`);
|
|
30415
31770
|
}
|
|
30416
31771
|
return modelPath;
|
|
30417
31772
|
}
|
|
30418
31773
|
async function ensureVoices(options) {
|
|
30419
|
-
const voicesPath =
|
|
30420
|
-
if (
|
|
31774
|
+
const voicesPath = join41(VOICES_DIR, "voices-v1.0.bin");
|
|
31775
|
+
if (existsSync40(voicesPath)) return voicesPath;
|
|
30421
31776
|
mkdirSync22(VOICES_DIR, { recursive: true });
|
|
30422
31777
|
options?.onProgress?.("Downloading voice data (~27 MB)...");
|
|
30423
31778
|
await downloadFile(VOICES_URL, voicesPath);
|
|
30424
|
-
if (!
|
|
31779
|
+
if (!existsSync40(voicesPath)) {
|
|
30425
31780
|
throw new Error("Voice data download failed");
|
|
30426
31781
|
}
|
|
30427
31782
|
return voicesPath;
|
|
@@ -30431,9 +31786,9 @@ var init_manager3 = __esm({
|
|
|
30431
31786
|
"src/tts/manager.ts"() {
|
|
30432
31787
|
"use strict";
|
|
30433
31788
|
init_download();
|
|
30434
|
-
CACHE_DIR3 =
|
|
30435
|
-
MODELS_DIR2 =
|
|
30436
|
-
VOICES_DIR =
|
|
31789
|
+
CACHE_DIR3 = join41(homedir8(), ".cache", "hyperframes", "tts");
|
|
31790
|
+
MODELS_DIR2 = join41(CACHE_DIR3, "models");
|
|
31791
|
+
VOICES_DIR = join41(CACHE_DIR3, "voices");
|
|
30437
31792
|
DEFAULT_MODEL2 = "kokoro-v1.0";
|
|
30438
31793
|
MODEL_URLS = {
|
|
30439
31794
|
"kokoro-v1.0": "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx"
|
|
@@ -30459,23 +31814,26 @@ __export(synthesize_exports, {
|
|
|
30459
31814
|
synthesize: () => synthesize
|
|
30460
31815
|
});
|
|
30461
31816
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
30462
|
-
import { existsSync as
|
|
30463
|
-
import { join as
|
|
30464
|
-
import { homedir as
|
|
31817
|
+
import { existsSync as existsSync41, writeFileSync as writeFileSync16, mkdirSync as mkdirSync23 } from "fs";
|
|
31818
|
+
import { join as join42, dirname as dirname17 } from "path";
|
|
31819
|
+
import { homedir as homedir9 } from "os";
|
|
30465
31820
|
function findPython() {
|
|
30466
31821
|
for (const name of ["python3", "python"]) {
|
|
30467
31822
|
try {
|
|
30468
|
-
const
|
|
31823
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
31824
|
+
const output = execFileSync6(cmd, [name], {
|
|
30469
31825
|
encoding: "utf-8",
|
|
30470
31826
|
stdio: ["pipe", "pipe", "pipe"],
|
|
30471
31827
|
timeout: 5e3
|
|
30472
|
-
})
|
|
30473
|
-
const
|
|
31828
|
+
});
|
|
31829
|
+
const first = output.split(/\r?\n/).map((s2) => s2.trim()).find(Boolean);
|
|
31830
|
+
if (!first) continue;
|
|
31831
|
+
const version = execFileSync6(first, ["--version"], {
|
|
30474
31832
|
encoding: "utf-8",
|
|
30475
31833
|
stdio: ["pipe", "pipe", "pipe"],
|
|
30476
31834
|
timeout: 5e3
|
|
30477
31835
|
}).trim();
|
|
30478
|
-
if (version.includes("Python 3")) return
|
|
31836
|
+
if (version.includes("Python 3")) return first;
|
|
30479
31837
|
} catch {
|
|
30480
31838
|
}
|
|
30481
31839
|
}
|
|
@@ -30493,7 +31851,7 @@ function hasPythonPackage(python, pkg) {
|
|
|
30493
31851
|
}
|
|
30494
31852
|
}
|
|
30495
31853
|
function ensureSynthScript() {
|
|
30496
|
-
if (!
|
|
31854
|
+
if (!existsSync41(SCRIPT_PATH)) {
|
|
30497
31855
|
mkdirSync23(SCRIPT_DIR, { recursive: true });
|
|
30498
31856
|
writeFileSync16(SCRIPT_PATH, SYNTH_SCRIPT);
|
|
30499
31857
|
}
|
|
@@ -30534,7 +31892,7 @@ async function synthesize(text, outputPath, options) {
|
|
|
30534
31892
|
stdio: ["pipe", "pipe", "pipe"]
|
|
30535
31893
|
}
|
|
30536
31894
|
);
|
|
30537
|
-
if (!
|
|
31895
|
+
if (!existsSync41(outputPath)) {
|
|
30538
31896
|
throw new Error("Synthesis completed but no output file was created");
|
|
30539
31897
|
}
|
|
30540
31898
|
const lines = stdout2.trim().split("\n");
|
|
@@ -30546,7 +31904,7 @@ async function synthesize(text, outputPath, options) {
|
|
|
30546
31904
|
durationSeconds: result.durationSeconds
|
|
30547
31905
|
};
|
|
30548
31906
|
} catch (err) {
|
|
30549
|
-
if (err instanceof SyntaxError &&
|
|
31907
|
+
if (err instanceof SyntaxError && existsSync41(outputPath)) {
|
|
30550
31908
|
throw new Error(
|
|
30551
31909
|
"Speech was generated but metadata could not be read. Check the output file manually."
|
|
30552
31910
|
);
|
|
@@ -30589,8 +31947,8 @@ print(json.dumps({
|
|
|
30589
31947
|
"durationSeconds": round(duration, 3),
|
|
30590
31948
|
}))
|
|
30591
31949
|
`;
|
|
30592
|
-
SCRIPT_DIR =
|
|
30593
|
-
SCRIPT_PATH =
|
|
31950
|
+
SCRIPT_DIR = join42(homedir9(), ".cache", "hyperframes", "tts");
|
|
31951
|
+
SCRIPT_PATH = join42(SCRIPT_DIR, "synth.py");
|
|
30594
31952
|
}
|
|
30595
31953
|
});
|
|
30596
31954
|
|
|
@@ -30600,7 +31958,7 @@ __export(tts_exports, {
|
|
|
30600
31958
|
default: () => tts_default,
|
|
30601
31959
|
examples: () => examples13
|
|
30602
31960
|
});
|
|
30603
|
-
import { existsSync as
|
|
31961
|
+
import { existsSync as existsSync42, readFileSync as readFileSync28 } from "fs";
|
|
30604
31962
|
import { resolve as resolve29, extname as extname8 } from "path";
|
|
30605
31963
|
function listVoices(json) {
|
|
30606
31964
|
if (json) {
|
|
@@ -30690,7 +32048,7 @@ var init_tts = __esm({
|
|
|
30690
32048
|
}
|
|
30691
32049
|
let text;
|
|
30692
32050
|
const maybeFile = resolve29(args.input);
|
|
30693
|
-
if (
|
|
32051
|
+
if (existsSync42(maybeFile) && extname8(maybeFile).toLowerCase() === ".txt") {
|
|
30694
32052
|
text = readFileSync28(maybeFile, "utf-8").trim();
|
|
30695
32053
|
if (!text) {
|
|
30696
32054
|
console.error(c.error("File is empty."));
|
|
@@ -30756,15 +32114,15 @@ __export(docs_exports, {
|
|
|
30756
32114
|
default: () => docs_default,
|
|
30757
32115
|
examples: () => examples14
|
|
30758
32116
|
});
|
|
30759
|
-
import { readFileSync as readFileSync29, existsSync as
|
|
30760
|
-
import { resolve as resolve30, dirname as dirname18, join as
|
|
32117
|
+
import { readFileSync as readFileSync29, existsSync as existsSync43 } from "fs";
|
|
32118
|
+
import { resolve as resolve30, dirname as dirname18, join as join43 } from "path";
|
|
30761
32119
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
30762
32120
|
function docsDir() {
|
|
30763
32121
|
const thisFile = fileURLToPath6(import.meta.url);
|
|
30764
32122
|
const dir = dirname18(thisFile);
|
|
30765
32123
|
const devPath = resolve30(dir, "..", "docs");
|
|
30766
32124
|
const builtPath = resolve30(dir, "docs");
|
|
30767
|
-
return
|
|
32125
|
+
return existsSync43(devPath) ? devPath : builtPath;
|
|
30768
32126
|
}
|
|
30769
32127
|
function formatInlineCode(line) {
|
|
30770
32128
|
return line.replace(/`([^`]+)`/g, (_match, code) => c.accent(code));
|
|
@@ -30861,8 +32219,8 @@ var init_docs = __esm({
|
|
|
30861
32219
|
}
|
|
30862
32220
|
process.exit(1);
|
|
30863
32221
|
}
|
|
30864
|
-
const filePath =
|
|
30865
|
-
if (!
|
|
32222
|
+
const filePath = join43(docsDir(), entry.file);
|
|
32223
|
+
if (!existsSync43(filePath)) {
|
|
30866
32224
|
console.error(c.error(`Doc file not found: ${filePath}`));
|
|
30867
32225
|
process.exit(1);
|
|
30868
32226
|
}
|
|
@@ -31292,8 +32650,8 @@ var validate_exports = {};
|
|
|
31292
32650
|
__export(validate_exports, {
|
|
31293
32651
|
default: () => validate_default
|
|
31294
32652
|
});
|
|
31295
|
-
import { existsSync as
|
|
31296
|
-
import { resolve as resolve31, join as
|
|
32653
|
+
import { existsSync as existsSync44, readFileSync as readFileSync30 } from "fs";
|
|
32654
|
+
import { resolve as resolve31, join as join44, dirname as dirname19 } from "path";
|
|
31297
32655
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
31298
32656
|
async function getCompositionDuration2(page) {
|
|
31299
32657
|
return page.evaluate(() => {
|
|
@@ -31348,7 +32706,7 @@ async function validateInBrowser(projectDir, opts) {
|
|
|
31348
32706
|
"dist",
|
|
31349
32707
|
"hyperframe.runtime.iife.js"
|
|
31350
32708
|
);
|
|
31351
|
-
if (
|
|
32709
|
+
if (existsSync44(runtimePath)) {
|
|
31352
32710
|
const runtimeSource = readFileSync30(runtimePath, "utf-8");
|
|
31353
32711
|
html = html.replace(
|
|
31354
32712
|
/<script[^>]*data-hyperframes-preview-runtime[^>]*src="[^"]*"[^>]*><\/script>/,
|
|
@@ -31364,8 +32722,8 @@ async function validateInBrowser(projectDir, opts) {
|
|
|
31364
32722
|
res.end(html);
|
|
31365
32723
|
return;
|
|
31366
32724
|
}
|
|
31367
|
-
const filePath =
|
|
31368
|
-
if (
|
|
32725
|
+
const filePath = join44(projectDir, decodeURIComponent(url));
|
|
32726
|
+
if (existsSync44(filePath)) {
|
|
31369
32727
|
res.writeHead(200, { "Content-Type": getMimeType2(filePath) });
|
|
31370
32728
|
res.end(readFileSync30(filePath));
|
|
31371
32729
|
return;
|
|
@@ -31557,8 +32915,8 @@ __export(snapshot_exports, {
|
|
|
31557
32915
|
default: () => snapshot_default,
|
|
31558
32916
|
examples: () => examples18
|
|
31559
32917
|
});
|
|
31560
|
-
import { existsSync as
|
|
31561
|
-
import { resolve as resolve32, join as
|
|
32918
|
+
import { existsSync as existsSync45, readFileSync as readFileSync31, mkdirSync as mkdirSync24 } from "fs";
|
|
32919
|
+
import { resolve as resolve32, join as join45, dirname as dirname20, relative as relative4, isAbsolute as isAbsolute4 } from "path";
|
|
31562
32920
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
31563
32921
|
async function captureSnapshots(projectDir, opts) {
|
|
31564
32922
|
const { bundleToSingleHtml: bundleToSingleHtml2 } = await Promise.resolve().then(() => (init_compiler(), compiler_exports));
|
|
@@ -31574,7 +32932,7 @@ async function captureSnapshots(projectDir, opts) {
|
|
|
31574
32932
|
"dist",
|
|
31575
32933
|
"hyperframe.runtime.iife.js"
|
|
31576
32934
|
);
|
|
31577
|
-
if (
|
|
32935
|
+
if (existsSync45(runtimePath)) {
|
|
31578
32936
|
const runtimeSource = readFileSync31(runtimePath, "utf-8");
|
|
31579
32937
|
html = html.replace(
|
|
31580
32938
|
/<script[^>]*data-hyperframes-preview-runtime[^>]*src="[^"]*"[^>]*><\/script>/,
|
|
@@ -31597,7 +32955,7 @@ async function captureSnapshots(projectDir, opts) {
|
|
|
31597
32955
|
res.end();
|
|
31598
32956
|
return;
|
|
31599
32957
|
}
|
|
31600
|
-
if (
|
|
32958
|
+
if (existsSync45(filePath)) {
|
|
31601
32959
|
res.writeHead(200, { "Content-Type": getMimeType2(filePath) });
|
|
31602
32960
|
res.end(readFileSync31(filePath));
|
|
31603
32961
|
return;
|
|
@@ -31672,7 +33030,7 @@ async function captureSnapshots(projectDir, opts) {
|
|
|
31672
33030
|
return [];
|
|
31673
33031
|
}
|
|
31674
33032
|
const positions = opts.at?.length ? opts.at : numFrames === 1 ? [duration / 2] : Array.from({ length: numFrames }, (_2, i2) => i2 / (numFrames - 1) * duration);
|
|
31675
|
-
const snapshotDir =
|
|
33033
|
+
const snapshotDir = join45(projectDir, "snapshots");
|
|
31676
33034
|
mkdirSync24(snapshotDir, { recursive: true });
|
|
31677
33035
|
for (let i2 = 0; i2 < positions.length; i2++) {
|
|
31678
33036
|
const time = positions[i2];
|
|
@@ -31698,7 +33056,7 @@ async function captureSnapshots(projectDir, opts) {
|
|
|
31698
33056
|
await new Promise((r2) => setTimeout(r2, 200));
|
|
31699
33057
|
const timeLabel = opts.at?.length ? `${time.toFixed(1)}s` : `${Math.round(time / duration * 100)}pct`;
|
|
31700
33058
|
const filename = `frame-${String(i2).padStart(2, "0")}-at-${timeLabel}.png`;
|
|
31701
|
-
const framePath =
|
|
33059
|
+
const framePath = join45(snapshotDir, filename);
|
|
31702
33060
|
await page.screenshot({ path: framePath, type: "png" });
|
|
31703
33061
|
savedPaths.push(`snapshots/${filename}`);
|
|
31704
33062
|
}
|
|
@@ -31783,13 +33141,13 @@ ${c.error("\u2717")} Snapshot failed: ${msg}`);
|
|
|
31783
33141
|
|
|
31784
33142
|
// src/capture/assetDownloader.ts
|
|
31785
33143
|
import { writeFileSync as writeFileSync17, mkdirSync as mkdirSync25 } from "fs";
|
|
31786
|
-
import { join as
|
|
31787
|
-
async function downloadAssets(tokens, outputDir, catalogedAssets) {
|
|
31788
|
-
const assetsDir =
|
|
33144
|
+
import { join as join46, extname as extname9 } from "path";
|
|
33145
|
+
async function downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks) {
|
|
33146
|
+
const assetsDir = join46(outputDir, "assets");
|
|
31789
33147
|
mkdirSync25(assetsDir, { recursive: true });
|
|
31790
33148
|
const assets = [];
|
|
31791
33149
|
const downloadedUrls = /* @__PURE__ */ new Set();
|
|
31792
|
-
mkdirSync25(
|
|
33150
|
+
mkdirSync25(join46(outputDir, "assets", "svgs"), { recursive: true });
|
|
31793
33151
|
for (let i2 = 0; i2 < tokens.svgs.length && i2 < 30; i2++) {
|
|
31794
33152
|
const svg = tokens.svgs[i2];
|
|
31795
33153
|
if (!svg.outerHTML || svg.outerHTML.length < 50) continue;
|
|
@@ -31797,12 +33155,12 @@ async function downloadAssets(tokens, outputDir, catalogedAssets) {
|
|
|
31797
33155
|
const name = label2 ? slugify(label2) + ".svg" : svg.isLogo ? `logo-${i2}.svg` : `icon-${i2}.svg`;
|
|
31798
33156
|
const localPath = `assets/svgs/${name}`;
|
|
31799
33157
|
try {
|
|
31800
|
-
writeFileSync17(
|
|
33158
|
+
writeFileSync17(join46(outputDir, localPath), svg.outerHTML, "utf-8");
|
|
31801
33159
|
assets.push({ url: "", localPath, type: "svg" });
|
|
31802
33160
|
} catch {
|
|
31803
33161
|
}
|
|
31804
33162
|
}
|
|
31805
|
-
for (const icon of
|
|
33163
|
+
for (const icon of faviconLinks || []) {
|
|
31806
33164
|
if (!icon.href) continue;
|
|
31807
33165
|
try {
|
|
31808
33166
|
const ext = extname9(new URL(icon.href).pathname) || ".ico";
|
|
@@ -31810,7 +33168,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets) {
|
|
|
31810
33168
|
const localPath = `assets/${name}`;
|
|
31811
33169
|
const buffer = await fetchBuffer(icon.href);
|
|
31812
33170
|
if (buffer) {
|
|
31813
|
-
writeFileSync17(
|
|
33171
|
+
writeFileSync17(join46(outputDir, localPath), buffer);
|
|
31814
33172
|
assets.push({ url: icon.href, localPath, type: "favicon" });
|
|
31815
33173
|
break;
|
|
31816
33174
|
}
|
|
@@ -31832,12 +33190,6 @@ async function downloadAssets(tokens, outputDir, catalogedAssets) {
|
|
|
31832
33190
|
const isPoster = a.contexts.includes("video[poster]");
|
|
31833
33191
|
imageUrls.push({ url: a.url, isPoster });
|
|
31834
33192
|
}
|
|
31835
|
-
} else {
|
|
31836
|
-
for (const img of tokens.images) {
|
|
31837
|
-
if (img.width > 200 && img.src.startsWith("http")) {
|
|
31838
|
-
imageUrls.push({ url: img.src, isPoster: false });
|
|
31839
|
-
}
|
|
31840
|
-
}
|
|
31841
33193
|
}
|
|
31842
33194
|
const toDownload = [];
|
|
31843
33195
|
for (const { url, isPoster } of imageUrls) {
|
|
@@ -31873,7 +33225,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets) {
|
|
|
31873
33225
|
const slug = isMeaningful ? slugify(rawName) : `${prefix}-${imgIdx}`;
|
|
31874
33226
|
const name = `${slug}${ext}`;
|
|
31875
33227
|
const localPath = `assets/${name}`;
|
|
31876
|
-
writeFileSync17(
|
|
33228
|
+
writeFileSync17(join46(outputDir, localPath), buffer);
|
|
31877
33229
|
assets.push({ url, localPath, type: "image" });
|
|
31878
33230
|
imgIdx++;
|
|
31879
33231
|
} catch {
|
|
@@ -31886,7 +33238,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets) {
|
|
|
31886
33238
|
const localPath = `assets/og-image${ext}`;
|
|
31887
33239
|
const buffer = await fetchBuffer(tokens.ogImage);
|
|
31888
33240
|
if (buffer && buffer.length > 5e3) {
|
|
31889
|
-
writeFileSync17(
|
|
33241
|
+
writeFileSync17(join46(outputDir, localPath), buffer);
|
|
31890
33242
|
assets.push({ url: tokens.ogImage, localPath, type: "image" });
|
|
31891
33243
|
}
|
|
31892
33244
|
} catch {
|
|
@@ -31909,7 +33261,7 @@ function normalizeUrl(u) {
|
|
|
31909
33261
|
}
|
|
31910
33262
|
}
|
|
31911
33263
|
async function downloadAndRewriteFonts(css, outputDir) {
|
|
31912
|
-
const assetsDir =
|
|
33264
|
+
const assetsDir = join46(outputDir, "assets", "fonts");
|
|
31913
33265
|
mkdirSync25(assetsDir, { recursive: true });
|
|
31914
33266
|
const fontUrlRegex = /url\(['"]?(https?:\/\/[^'")\s]+\.(?:woff2?|ttf|otf)[^'")\s]*?)['"]?\)/g;
|
|
31915
33267
|
const fontUrls = /* @__PURE__ */ new Set();
|
|
@@ -31945,7 +33297,7 @@ async function downloadAndRewriteFonts(css, outputDir) {
|
|
|
31945
33297
|
try {
|
|
31946
33298
|
const urlObj = new URL(fontUrl);
|
|
31947
33299
|
const filename = urlObj.pathname.split("/").pop() || `font-${count}.woff2`;
|
|
31948
|
-
const localPath =
|
|
33300
|
+
const localPath = join46(assetsDir, filename);
|
|
31949
33301
|
const relativePath = `assets/fonts/${filename}`;
|
|
31950
33302
|
const buffer = await fetchBuffer(fontUrl);
|
|
31951
33303
|
if (buffer) {
|
|
@@ -32010,19 +33362,6 @@ var init_assetDownloader = __esm({
|
|
|
32010
33362
|
// src/capture/htmlExtractor.ts
|
|
32011
33363
|
async function extractHtml(page, opts = {}) {
|
|
32012
33364
|
const settleTime = opts.settleTime ?? DEFAULT_SETTLE_TIME;
|
|
32013
|
-
await page.evaluate(`(async () => {
|
|
32014
|
-
var pageHeight = document.body.scrollHeight;
|
|
32015
|
-
var viewportH = window.innerHeight;
|
|
32016
|
-
var step = Math.floor(viewportH * 0.7);
|
|
32017
|
-
for (var y = 0; y < pageHeight + viewportH; y += step) {
|
|
32018
|
-
window.scrollTo(0, y);
|
|
32019
|
-
await new Promise(function(r) { setTimeout(r, 200); });
|
|
32020
|
-
}
|
|
32021
|
-
window.scrollTo(0, pageHeight);
|
|
32022
|
-
await new Promise(function(r) { setTimeout(r, 300); });
|
|
32023
|
-
window.scrollTo(0, 0);
|
|
32024
|
-
await new Promise(function(r) { setTimeout(r, 300); });
|
|
32025
|
-
})()`);
|
|
32026
33365
|
await new Promise((r2) => setTimeout(r2, settleTime));
|
|
32027
33366
|
const stylesheetUrls = await page.evaluate(`(() => {
|
|
32028
33367
|
return Array.from(document.querySelectorAll('link[rel="stylesheet"][href]')).map(function(l) { return l.href; });
|
|
@@ -32251,12 +33590,49 @@ var init_tokenExtractor = __esm({
|
|
|
32251
33590
|
var ogImgEl = document.querySelector('meta[property="og:image"]');
|
|
32252
33591
|
var ogImage = ogImgEl ? ogImgEl.content : undefined;
|
|
32253
33592
|
|
|
32254
|
-
// 3. Fonts
|
|
32255
|
-
var
|
|
32256
|
-
|
|
32257
|
-
|
|
32258
|
-
|
|
32259
|
-
|
|
33593
|
+
// 3. Fonts \u2014 enumerate loaded FontFaces + supplement with DOM sampling
|
|
33594
|
+
var fontMap = {};
|
|
33595
|
+
function ensureFont(name) {
|
|
33596
|
+
if (!fontMap[name]) fontMap[name] = { family: name, weights: [], variable: false, weightRange: undefined };
|
|
33597
|
+
return fontMap[name];
|
|
33598
|
+
}
|
|
33599
|
+
function addWeight(entry, w) {
|
|
33600
|
+
var n = parseInt(w, 10);
|
|
33601
|
+
if (!isNaN(n) && entry.weights.indexOf(n) === -1) entry.weights.push(n);
|
|
33602
|
+
}
|
|
33603
|
+
var genericFonts = ["serif","sans-serif","monospace","cursive","system-ui","ui-serif","ui-sans-serif","ui-monospace","ui-rounded","emoji","math","fangsong"];
|
|
33604
|
+
try {
|
|
33605
|
+
document.fonts.forEach(function(face) {
|
|
33606
|
+
var name = face.family.replace(/['"]/g, "").trim();
|
|
33607
|
+
if (!name || genericFonts.indexOf(name.toLowerCase()) !== -1) return;
|
|
33608
|
+
// Skip placeholder/fallback fonts (Framer loads hundreds of these)
|
|
33609
|
+
if (name.indexOf("Placeholder") !== -1 || name.indexOf("Fallback") !== -1) return;
|
|
33610
|
+
var entry = ensureFont(name);
|
|
33611
|
+
var w = (face.weight || "").trim();
|
|
33612
|
+
if (w.indexOf(" ") !== -1) {
|
|
33613
|
+
var parts = w.split(" ");
|
|
33614
|
+
var lo = parseInt(parts[0], 10);
|
|
33615
|
+
var hi = parseInt(parts[1], 10);
|
|
33616
|
+
if (!isNaN(lo) && !isNaN(hi)) {
|
|
33617
|
+
entry.variable = true;
|
|
33618
|
+
entry.weightRange = [lo, hi];
|
|
33619
|
+
}
|
|
33620
|
+
} else {
|
|
33621
|
+
addWeight(entry, w);
|
|
33622
|
+
}
|
|
33623
|
+
});
|
|
33624
|
+
} catch(e) {}
|
|
33625
|
+
// Supplement with DOM sampling
|
|
33626
|
+
var domSamples = Array.from(document.querySelectorAll("h1,h2,h3,h4,h5,h6,p,a,button,span,li,strong,b")).slice(0, 100);
|
|
33627
|
+
for (var fi = 0; fi < domSamples.length; fi++) {
|
|
33628
|
+
try {
|
|
33629
|
+
var cs = getComputedStyle(domSamples[fi]);
|
|
33630
|
+
var family = cs.fontFamily.split(",")[0].replace(/['"]/g, "").trim();
|
|
33631
|
+
if (family && genericFonts.indexOf(family.toLowerCase()) === -1) {
|
|
33632
|
+
var entry = ensureFont(family);
|
|
33633
|
+
addWeight(entry, cs.fontWeight);
|
|
33634
|
+
}
|
|
33635
|
+
} catch(e) {}
|
|
32260
33636
|
}
|
|
32261
33637
|
|
|
32262
33638
|
// 4. Colors \u2014 hybrid: DOM computed styles + visual pixel sampling
|
|
@@ -32414,10 +33790,7 @@ var init_tokenExtractor = __esm({
|
|
|
32414
33790
|
return { level: parseInt(h.tagName[1]), text: (h.innerText || h.textContent || "").trim().replace(/\\s+/g, ' ').slice(0, 200), fontSize: s.fontSize, fontWeight: s.fontWeight, color: rgbToHex(s.color) || s.color };
|
|
32415
33791
|
});
|
|
32416
33792
|
|
|
32417
|
-
// 6.
|
|
32418
|
-
var paragraphs = Array.from(document.querySelectorAll("p")).slice(0, 10).map(function(p) { return (p.textContent || "").trim().slice(0, 300); }).filter(function(t) { return t.length > 20; });
|
|
32419
|
-
|
|
32420
|
-
// 7. CTAs \u2014 match by class AND by text content patterns
|
|
33793
|
+
// 6. CTAs \u2014 match by class AND by text content patterns
|
|
32421
33794
|
// Conservative class selectors (avoid nav links with "action" or "start" in class)
|
|
32422
33795
|
var ctaSelectors = 'a[class*="btn"], a[class*="button"], a[class*="cta"], button[class*="primary"], button[class*="cta"], [role="button"]';
|
|
32423
33796
|
var ctaEls = Array.from(document.querySelectorAll(ctaSelectors));
|
|
@@ -32488,15 +33861,7 @@ var init_tokenExtractor = __esm({
|
|
|
32488
33861
|
};
|
|
32489
33862
|
}).filter(Boolean).slice(0, 50);
|
|
32490
33863
|
|
|
32491
|
-
// 9.
|
|
32492
|
-
var imgEls = Array.from(document.querySelectorAll("img[src]")).filter(function(img) { return img.naturalWidth > 200 && isVisible(img); }).slice(0, 15);
|
|
32493
|
-
var images = imgEls.map(function(img) { return { src: img.src, alt: img.alt || "", width: img.naturalWidth, height: img.naturalHeight }; });
|
|
32494
|
-
|
|
32495
|
-
// 10. Icons
|
|
32496
|
-
var iconEls = Array.from(document.querySelectorAll('link[rel*="icon"], link[rel="apple-touch-icon"]'));
|
|
32497
|
-
var icons = iconEls.map(function(l) { return { rel: l.rel, href: l.href }; });
|
|
32498
|
-
|
|
32499
|
-
// 11. Sections \u2014 find large visual blocks regardless of HTML tag
|
|
33864
|
+
// 9. Sections \u2014 find large visual blocks regardless of HTML tag
|
|
32500
33865
|
var sectionResults = [];
|
|
32501
33866
|
// Start with semantic elements, then fall back to large direct children of body/main
|
|
32502
33867
|
var candidates = Array.from(document.querySelectorAll(
|
|
@@ -32546,17 +33911,45 @@ var init_tokenExtractor = __esm({
|
|
|
32546
33911
|
}
|
|
32547
33912
|
if (!sectionBg || sectionBg === "rgba(0, 0, 0, 0)" || sectionBg === "transparent") sectionBg = "#FFFFFF";
|
|
32548
33913
|
}
|
|
33914
|
+
// Check for background-image when color is transparent/default white
|
|
33915
|
+
var sectionBgImage = undefined;
|
|
33916
|
+
var rawBgImg = getComputedStyle(el).backgroundImage;
|
|
33917
|
+
if (rawBgImg && rawBgImg !== "none" && rawBgImg.indexOf("url(") !== -1) {
|
|
33918
|
+
var start = rawBgImg.indexOf("url(") + 4;
|
|
33919
|
+
var end = rawBgImg.indexOf(")", start);
|
|
33920
|
+
if (end > start) {
|
|
33921
|
+
sectionBgImage = rawBgImg.slice(start, end).replace(/['"]/g, "");
|
|
33922
|
+
}
|
|
33923
|
+
}
|
|
32549
33924
|
sectionBg = rgbToHex(sectionBg) || sectionBg;
|
|
32550
|
-
|
|
33925
|
+
var sectionEntry = { selector: selector, type: type, y: Math.round(y), height: Math.round(rect.height), heading: headingText, backgroundColor: sectionBg };
|
|
33926
|
+
if (sectionBgImage) sectionEntry.backgroundImage = sectionBgImage;
|
|
33927
|
+
sectionResults.push(sectionEntry);
|
|
32551
33928
|
}
|
|
32552
33929
|
sectionResults.sort(function(a, b) { return a.y - b.y; });
|
|
32553
33930
|
var filtered = sectionResults.filter(function(s, i) { return i === 0 || Math.abs(s.y - sectionResults[i-1].y) > 100; });
|
|
32554
33931
|
|
|
33932
|
+
// Filter cssVariables \u2014 keep only color-like values or design-relevant names
|
|
33933
|
+
var colorValueRe = /^(#|rgb|hsl|oklch|oklab|lch|lab|color)/i;
|
|
33934
|
+
var designNameRe = /(color|bg|background|border|text|font|radius|shadow)/i;
|
|
33935
|
+
var filteredVars = {};
|
|
33936
|
+
var varKeys = Object.keys(cssVariables);
|
|
33937
|
+
for (var vi = 0; vi < varKeys.length; vi++) {
|
|
33938
|
+
var varName = varKeys[vi];
|
|
33939
|
+
var varVal = cssVariables[varName];
|
|
33940
|
+
if (colorValueRe.test(varVal) || designNameRe.test(varName)) {
|
|
33941
|
+
filteredVars[varName] = varVal;
|
|
33942
|
+
}
|
|
33943
|
+
}
|
|
33944
|
+
|
|
33945
|
+
// Filter sections \u2014 only keep those with a non-empty heading
|
|
33946
|
+
var filteredSections = filtered.filter(function(s) { return s.heading && s.heading.length > 0; });
|
|
33947
|
+
|
|
32555
33948
|
return {
|
|
32556
33949
|
title: title, description: description, ogImage: ogImage,
|
|
32557
|
-
cssVariables:
|
|
32558
|
-
headings: headings,
|
|
32559
|
-
svgs: svgs,
|
|
33950
|
+
cssVariables: filteredVars, fonts: Object.keys(fontMap).map(function(k) { var f = fontMap[k]; f.weights.sort(function(a,b){return a-b;}); return f; }).filter(function(f) { return f.weights.length > 0 || f.variable; }).slice(0, 20), colors: Object.keys(colorSet).sort(function(a,b) { return colorSet[b] - colorSet[a]; }).slice(0, 20),
|
|
33951
|
+
headings: headings, ctas: ctas,
|
|
33952
|
+
svgs: svgs, sections: filteredSections
|
|
32560
33953
|
};
|
|
32561
33954
|
})()`;
|
|
32562
33955
|
}
|
|
@@ -32701,8 +34094,8 @@ var init_animationCataloger = __esm({
|
|
|
32701
34094
|
});
|
|
32702
34095
|
|
|
32703
34096
|
// src/capture/mediaCapture.ts
|
|
32704
|
-
import { mkdirSync as mkdirSync26, writeFileSync as writeFileSync18, readdirSync as
|
|
32705
|
-
import { join as
|
|
34097
|
+
import { mkdirSync as mkdirSync26, writeFileSync as writeFileSync18, readdirSync as readdirSync14, readFileSync as readFileSync32, statSync as statSync15 } from "fs";
|
|
34098
|
+
import { join as join47 } from "path";
|
|
32706
34099
|
async function saveLottieAnimations(discoveredLotties, lottieDir) {
|
|
32707
34100
|
let savedCount = 0;
|
|
32708
34101
|
const savedHashes = /* @__PURE__ */ new Set();
|
|
@@ -32735,7 +34128,7 @@ async function saveLottieAnimations(discoveredLotties, lottieDir) {
|
|
|
32735
34128
|
const hash = buf.toString("base64").slice(0, 100);
|
|
32736
34129
|
if (savedHashes.has(hash)) continue;
|
|
32737
34130
|
savedHashes.add(hash);
|
|
32738
|
-
writeFileSync18(
|
|
34131
|
+
writeFileSync18(join47(lottieDir, `animation-${savedCount}.lottie`), buf);
|
|
32739
34132
|
savedCount++;
|
|
32740
34133
|
continue;
|
|
32741
34134
|
}
|
|
@@ -32753,7 +34146,7 @@ async function saveLottieAnimations(discoveredLotties, lottieDir) {
|
|
|
32753
34146
|
} catch {
|
|
32754
34147
|
continue;
|
|
32755
34148
|
}
|
|
32756
|
-
writeFileSync18(
|
|
34149
|
+
writeFileSync18(join47(lottieDir, `animation-${savedCount}.json`), jsonData, "utf-8");
|
|
32757
34150
|
savedCount++;
|
|
32758
34151
|
}
|
|
32759
34152
|
} catch {
|
|
@@ -32763,22 +34156,22 @@ async function saveLottieAnimations(discoveredLotties, lottieDir) {
|
|
|
32763
34156
|
}
|
|
32764
34157
|
async function renderLottiePreviews(chromeBrowser, lottieDir, outputDir) {
|
|
32765
34158
|
const manifest = [];
|
|
32766
|
-
const previewDir =
|
|
34159
|
+
const previewDir = join47(lottieDir, "previews");
|
|
32767
34160
|
mkdirSync26(previewDir, { recursive: true });
|
|
32768
|
-
for (const file of
|
|
34161
|
+
for (const file of readdirSync14(lottieDir)) {
|
|
32769
34162
|
if (!file.endsWith(".json")) continue;
|
|
32770
34163
|
try {
|
|
32771
|
-
const raw = JSON.parse(readFileSync32(
|
|
34164
|
+
const raw = JSON.parse(readFileSync32(join47(lottieDir, file), "utf-8"));
|
|
32772
34165
|
const fr = raw.fr || 30;
|
|
32773
34166
|
const dur = ((raw.op || 0) - (raw.ip || 0)) / fr;
|
|
32774
34167
|
const previewName = file.replace(".json", "-preview.png");
|
|
32775
|
-
const fileSize = statSync15(
|
|
34168
|
+
const fileSize = statSync15(join47(lottieDir, file)).size;
|
|
32776
34169
|
if (fileSize > 2e6) continue;
|
|
32777
34170
|
let previewPage;
|
|
32778
34171
|
try {
|
|
32779
34172
|
previewPage = await chromeBrowser.newPage();
|
|
32780
34173
|
await previewPage.setViewport({ width: 400, height: 400 });
|
|
32781
|
-
const animData = JSON.parse(readFileSync32(
|
|
34174
|
+
const animData = JSON.parse(readFileSync32(join47(lottieDir, file), "utf-8"));
|
|
32782
34175
|
const midFrame = Math.floor(((raw.op || 0) - (raw.ip || 0)) * 0.3);
|
|
32783
34176
|
await previewPage.setContent(
|
|
32784
34177
|
`<!DOCTYPE html>
|
|
@@ -32808,7 +34201,7 @@ async function renderLottiePreviews(chromeBrowser, lottieDir, outputDir) {
|
|
|
32808
34201
|
await previewPage.waitForFunction(() => window.__READY === true, { timeout: 5e3 }).catch(() => {
|
|
32809
34202
|
});
|
|
32810
34203
|
await previewPage.screenshot({
|
|
32811
|
-
path:
|
|
34204
|
+
path: join47(previewDir, previewName),
|
|
32812
34205
|
type: "png",
|
|
32813
34206
|
omitBackground: true
|
|
32814
34207
|
});
|
|
@@ -32832,7 +34225,7 @@ async function renderLottiePreviews(chromeBrowser, lottieDir, outputDir) {
|
|
|
32832
34225
|
}
|
|
32833
34226
|
if (manifest.length > 0) {
|
|
32834
34227
|
writeFileSync18(
|
|
32835
|
-
|
|
34228
|
+
join47(outputDir, "extracted", "lottie-manifest.json"),
|
|
32836
34229
|
JSON.stringify(manifest, null, 2),
|
|
32837
34230
|
"utf-8"
|
|
32838
34231
|
);
|
|
@@ -32894,15 +34287,15 @@ async function captureVideoManifest(page, outputDir, progress) {
|
|
|
32894
34287
|
return true;
|
|
32895
34288
|
});
|
|
32896
34289
|
if (uniqueVideos.length > 0) {
|
|
32897
|
-
const videoManifestDir =
|
|
34290
|
+
const videoManifestDir = join47(outputDir, "assets", "videos");
|
|
32898
34291
|
mkdirSync26(videoManifestDir, { recursive: true });
|
|
32899
|
-
const previewDir =
|
|
34292
|
+
const previewDir = join47(videoManifestDir, "previews");
|
|
32900
34293
|
mkdirSync26(previewDir, { recursive: true });
|
|
32901
34294
|
const videoManifest = [];
|
|
32902
34295
|
for (let vi = 0; vi < uniqueVideos.length && vi < 20; vi++) {
|
|
32903
34296
|
const v = uniqueVideos[vi];
|
|
32904
34297
|
const previewName = `video-${vi}-preview.png`;
|
|
32905
|
-
const previewPath =
|
|
34298
|
+
const previewPath = join47(previewDir, previewName);
|
|
32906
34299
|
try {
|
|
32907
34300
|
await page.evaluate(`window.scrollTo(0, ${Math.max(0, v.top - 100)})`);
|
|
32908
34301
|
await new Promise((r2) => setTimeout(r2, 300));
|
|
@@ -32941,7 +34334,7 @@ async function captureVideoManifest(page, outputDir, progress) {
|
|
|
32941
34334
|
}
|
|
32942
34335
|
if (videoManifest.length > 0) {
|
|
32943
34336
|
writeFileSync18(
|
|
32944
|
-
|
|
34337
|
+
join47(outputDir, "extracted", "video-manifest.json"),
|
|
32945
34338
|
JSON.stringify(videoManifest, null, 2),
|
|
32946
34339
|
"utf-8"
|
|
32947
34340
|
);
|
|
@@ -73396,8 +74789,8 @@ ${underline2}`);
|
|
|
73396
74789
|
});
|
|
73397
74790
|
|
|
73398
74791
|
// src/capture/contentExtractor.ts
|
|
73399
|
-
import { readdirSync as
|
|
73400
|
-
import { join as
|
|
74792
|
+
import { readdirSync as readdirSync15, statSync as statSync17, readFileSync as readFileSync33 } from "fs";
|
|
74793
|
+
import { join as join48 } from "path";
|
|
73401
74794
|
async function detectLibraries(page, capturedShaders) {
|
|
73402
74795
|
let detectedLibraries = [];
|
|
73403
74796
|
try {
|
|
@@ -73479,6 +74872,7 @@ async function extractVisibleText(page) {
|
|
|
73479
74872
|
let visibleTextContent = "";
|
|
73480
74873
|
try {
|
|
73481
74874
|
visibleTextContent = await page.evaluate(`(() => {
|
|
74875
|
+
var cookieRe = /^(accept|cookie|privacy|that's fine|got it|i agree|reject all|accept all|manage cookies|consent)$/i;
|
|
73482
74876
|
var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null);
|
|
73483
74877
|
var texts = [];
|
|
73484
74878
|
var node;
|
|
@@ -73491,7 +74885,13 @@ async function extractVisibleText(page) {
|
|
|
73491
74885
|
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') continue;
|
|
73492
74886
|
var tag = el.tagName.toLowerCase();
|
|
73493
74887
|
if (tag === 'script' || tag === 'style' || tag === 'noscript') continue;
|
|
73494
|
-
|
|
74888
|
+
// Skip very short text inside nav/footer (catches single-word nav links)
|
|
74889
|
+
// Threshold is 8 chars to preserve footer copy like "\xA9 2026 Stripe" (16 chars)
|
|
74890
|
+
var inNavOrFooter = el.closest('nav, footer, [role="navigation"]');
|
|
74891
|
+
if (inNavOrFooter && text.length < 8) continue;
|
|
74892
|
+
// Skip common cookie/consent patterns
|
|
74893
|
+
if (cookieRe.test(text)) continue;
|
|
74894
|
+
texts.push('[' + tag + '] ' + text);
|
|
73495
74895
|
}
|
|
73496
74896
|
return texts.join('\\n');
|
|
73497
74897
|
})()`);
|
|
@@ -73510,7 +74910,7 @@ async function captionImagesWithGemini(outputDir, progress, warnings) {
|
|
|
73510
74910
|
try {
|
|
73511
74911
|
const { GoogleGenAI: GoogleGenAI2 } = await Promise.resolve().then(() => (init_node4(), node_exports));
|
|
73512
74912
|
const ai = new GoogleGenAI2({ apiKey: geminiKey });
|
|
73513
|
-
const imageFiles =
|
|
74913
|
+
const imageFiles = readdirSync15(join48(outputDir, "assets")).filter(
|
|
73514
74914
|
(f3) => /\.(png|jpg|jpeg|webp|gif)$/i.test(f3)
|
|
73515
74915
|
);
|
|
73516
74916
|
const model = process.env.HYPERFRAMES_GEMINI_MODEL || "gemini-3.1-flash-lite-preview";
|
|
@@ -73519,7 +74919,7 @@ async function captionImagesWithGemini(outputDir, progress, warnings) {
|
|
|
73519
74919
|
const batch = imageFiles.slice(i2, i2 + BATCH_SIZE);
|
|
73520
74920
|
const results = await Promise.allSettled(
|
|
73521
74921
|
batch.map(async (file) => {
|
|
73522
|
-
const filePath =
|
|
74922
|
+
const filePath = join48(outputDir, "assets", file);
|
|
73523
74923
|
const stat3 = statSync17(filePath);
|
|
73524
74924
|
if (stat3.size > 4e6) return { file, caption: "" };
|
|
73525
74925
|
const buffer = readFileSync33(filePath);
|
|
@@ -73568,11 +74968,11 @@ function generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCap
|
|
|
73568
74968
|
const uncaptionedLines = [];
|
|
73569
74969
|
const svgLines = [];
|
|
73570
74970
|
const fontLines = [];
|
|
73571
|
-
const assetsPath =
|
|
74971
|
+
const assetsPath = join48(outputDir, "assets");
|
|
73572
74972
|
try {
|
|
73573
|
-
for (const file of
|
|
74973
|
+
for (const file of readdirSync15(assetsPath)) {
|
|
73574
74974
|
if (file === "svgs" || file === "fonts" || file === "lottie" || file === "videos") continue;
|
|
73575
|
-
const filePath =
|
|
74975
|
+
const filePath = join48(assetsPath, file);
|
|
73576
74976
|
const stat3 = statSync17(filePath);
|
|
73577
74977
|
if (!stat3.isFile()) continue;
|
|
73578
74978
|
const sizeKb = Math.round(stat3.size / 1024);
|
|
@@ -73601,8 +75001,8 @@ function generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCap
|
|
|
73601
75001
|
} catch {
|
|
73602
75002
|
}
|
|
73603
75003
|
try {
|
|
73604
|
-
const svgsPath =
|
|
73605
|
-
for (const file of
|
|
75004
|
+
const svgsPath = join48(assetsPath, "svgs");
|
|
75005
|
+
for (const file of readdirSync15(svgsPath)) {
|
|
73606
75006
|
if (!file.endsWith(".svg")) continue;
|
|
73607
75007
|
const svgMatch = tokens.svgs.find(
|
|
73608
75008
|
(s2) => s2.label && file.includes(
|
|
@@ -73616,8 +75016,8 @@ function generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCap
|
|
|
73616
75016
|
} catch {
|
|
73617
75017
|
}
|
|
73618
75018
|
try {
|
|
73619
|
-
const fontsPath =
|
|
73620
|
-
for (const file of
|
|
75019
|
+
const fontsPath = join48(assetsPath, "fonts");
|
|
75020
|
+
for (const file of readdirSync15(fontsPath)) {
|
|
73621
75021
|
fontLines.push(`fonts/${file} \u2014 font file`);
|
|
73622
75022
|
}
|
|
73623
75023
|
} catch {
|
|
@@ -73636,111 +75036,71 @@ __export(agentPromptGenerator_exports, {
|
|
|
73636
75036
|
generateAgentPrompt: () => generateAgentPrompt
|
|
73637
75037
|
});
|
|
73638
75038
|
import { writeFileSync as writeFileSync19 } from "fs";
|
|
73639
|
-
import { join as
|
|
73640
|
-
function generateAgentPrompt(outputDir, url, tokens,
|
|
73641
|
-
const prompt = buildPrompt(
|
|
73642
|
-
|
|
73643
|
-
|
|
73644
|
-
|
|
73645
|
-
|
|
73646
|
-
|
|
73647
|
-
|
|
73648
|
-
catalogedAssets
|
|
73649
|
-
);
|
|
73650
|
-
writeFileSync19(join48(outputDir, "CLAUDE.md"), prompt, "utf-8");
|
|
73651
|
-
writeFileSync19(join48(outputDir, ".cursorrules"), prompt, "utf-8");
|
|
73652
|
-
}
|
|
73653
|
-
function buildPrompt(url, tokens, animations, hasScreenshot, hasLottie, hasShaders, catalogedAssets) {
|
|
73654
|
-
const hostname = new URL(url).hostname.replace(/^www\./, "");
|
|
73655
|
-
const title = tokens.title || hostname;
|
|
73656
|
-
const cues = detectImplementationCues(tokens, animations);
|
|
75039
|
+
import { join as join49 } from "path";
|
|
75040
|
+
function generateAgentPrompt(outputDir, url, tokens, _animations, hasScreenshot, hasLottie, hasShaders, _catalogedAssets, detectedLibraries) {
|
|
75041
|
+
const prompt = buildPrompt(url, tokens, hasScreenshot, hasLottie, hasShaders, detectedLibraries);
|
|
75042
|
+
writeFileSync19(join49(outputDir, "AGENTS.md"), prompt, "utf-8");
|
|
75043
|
+
writeFileSync19(join49(outputDir, "CLAUDE.md"), prompt, "utf-8");
|
|
75044
|
+
writeFileSync19(join49(outputDir, ".cursorrules"), prompt, "utf-8");
|
|
75045
|
+
}
|
|
75046
|
+
function buildPrompt(url, tokens, hasScreenshot, hasLottie, hasShaders, detectedLibraries) {
|
|
75047
|
+
const title = tokens.title || new URL(url).hostname.replace(/^www\./, "");
|
|
73657
75048
|
const colorSummary = tokens.colors.slice(0, 10).join(", ");
|
|
73658
|
-
const fontSummary = tokens.fonts.
|
|
73659
|
-
|
|
73660
|
-
|
|
73661
|
-
const
|
|
73662
|
-
|
|
73663
|
-
|
|
75049
|
+
const fontSummary = tokens.fonts.map(
|
|
75050
|
+
(f3) => f3.family + (f3.variable && f3.weightRange ? ` (${f3.weightRange[0]}-${f3.weightRange[1]} variable)` : f3.weights.length > 0 ? ` (${f3.weights.join(",")})` : "")
|
|
75051
|
+
).join(", ") || "none detected";
|
|
75052
|
+
const tableRows = [];
|
|
75053
|
+
if (hasScreenshot) {
|
|
75054
|
+
tableRows.push(
|
|
75055
|
+
"| `screenshots/scroll-*.png` | Viewport screenshots of the full page. Start with `scroll-000.png` (hero). |"
|
|
75056
|
+
);
|
|
75057
|
+
}
|
|
75058
|
+
tableRows.push(
|
|
75059
|
+
"| `extracted/asset-descriptions.md` | One-line description of every downloaded asset. **Read this first.** |"
|
|
75060
|
+
);
|
|
75061
|
+
tableRows.push(
|
|
75062
|
+
`| \`extracted/tokens.json\` | Design tokens: ${tokens.colors.length} colors, ${tokens.fonts.length} fonts, ${tokens.headings?.length ?? 0} headings, ${tokens.ctas?.length ?? 0} CTAs |`
|
|
75063
|
+
);
|
|
75064
|
+
tableRows.push(
|
|
75065
|
+
"| `extracted/visible-text.txt` | Page text in DOM order, prefixed with HTML tag (`[h1]`, `[p]`, `[a]`). Use as context \u2014 rephrase freely. |"
|
|
75066
|
+
);
|
|
75067
|
+
if (hasLottie) {
|
|
75068
|
+
tableRows.push(
|
|
75069
|
+
"| `extracted/lottie-manifest.json` | Lottie animations with previews at `assets/lottie/previews/`. |"
|
|
75070
|
+
);
|
|
75071
|
+
}
|
|
75072
|
+
if (hasShaders) {
|
|
75073
|
+
tableRows.push("| `extracted/shaders.json` | WebGL shader source (GLSL). |");
|
|
75074
|
+
}
|
|
75075
|
+
if (detectedLibraries && detectedLibraries.length > 0) {
|
|
75076
|
+
tableRows.push(
|
|
75077
|
+
`| \`extracted/detected-libraries.json\` | Libraries: ${detectedLibraries.join(", ")} |`
|
|
75078
|
+
);
|
|
75079
|
+
}
|
|
75080
|
+
tableRows.push("| `assets/` | Downloaded images, SVGs, and font files. |");
|
|
75081
|
+
const brandLines = [];
|
|
75082
|
+
brandLines.push(`- **Colors**: ${colorSummary || "see tokens.json"}`);
|
|
75083
|
+
brandLines.push(`- **Fonts**: ${fontSummary}`);
|
|
75084
|
+
if (detectedLibraries && detectedLibraries.length > 0) {
|
|
75085
|
+
brandLines.push(`- **Built with**: ${detectedLibraries.join(", ")}`);
|
|
75086
|
+
}
|
|
75087
|
+
return `# ${title}
|
|
73664
75088
|
|
|
73665
75089
|
Source: ${url}
|
|
73666
75090
|
|
|
73667
|
-
|
|
73668
|
-
|
|
73669
|
-
Invoke the \`/website-to-hyperframes\` skill. It walks you through the full workflow: read data \u2192 create DESIGN.md \u2192 plan video \u2192 build compositions \u2192 lint/validate/preview.
|
|
73670
|
-
|
|
73671
|
-
If you don't have the skill installed, run: \`npx skills add heygen-com/hyperframes\`
|
|
75091
|
+
To create a video from this capture, use the \`website-to-hyperframes\` skill.
|
|
73672
75092
|
|
|
73673
75093
|
## What's in This Capture
|
|
73674
75094
|
|
|
73675
75095
|
| File | Contents |
|
|
73676
75096
|
|------|----------|
|
|
73677
|
-
${
|
|
73678
|
-
| \`extracted/tokens.json\` | Design tokens: ${tokens.colors.length} colors, ${tokens.fonts.length} fonts, ${headingCount} headings, ${ctaCount} CTAs, ${sectionCount} sections |
|
|
73679
|
-
| \`extracted/visible-text.txt\` | All visible text content in DOM order \u2014 use exact strings, never paraphrase |
|
|
73680
|
-
| \`extracted/assets-catalog.json\` | Every asset URL (images, fonts, videos, icons) with HTML context |
|
|
73681
|
-
| \`extracted/animations.json\` | Animation catalog: ${animations?.summary?.webAnimations ?? 0} web animations, ${animations?.summary?.scrollTargets ?? 0} scroll triggers, ${animations?.summary?.canvases ?? 0} canvases |
|
|
73682
|
-
| \`assets/svgs/\` | Extracted inline SVGs (logos, icons, illustrations) |
|
|
73683
|
-
| \`assets/\` | Downloaded images and font files \u2014 **Read every image file to see what it contains** |
|
|
73684
|
-
${hasLottie ? "| `extracted/lottie-manifest.json` | Lottie animations found on this site \u2014 read this to see what animations are available (name, dimensions, duration). Embed via `lottie.loadAnimation({ path: 'assets/lottie/animation-0.json' })`. Do NOT read the raw JSON files \u2014 they are machine data. |" : ""}
|
|
73685
|
-
${videoUrls.length > 0 ? "| `extracted/video-manifest.json` | Video manifest: every `<video>` element with its URL, surrounding heading/caption context, and a preview screenshot. **Read this + view each preview image** to understand what each video shows before using it. |" : ""}
|
|
73686
|
-
${hasShaders ? "| `extracted/shaders.json` | Captured WebGL shader source code (GLSL vertex + fragment shaders) |" : ""}
|
|
73687
|
-
| \`extracted/asset-descriptions.md\` | One-line description of every downloaded asset \u2014 read this first |
|
|
73688
|
-
|
|
73689
|
-
> **DESIGN.md does not exist yet.** It will be created when you run the \`/website-to-hyperframes\` workflow. Do not write compositions without it.
|
|
75097
|
+
${tableRows.join("\n")}
|
|
73690
75098
|
|
|
73691
75099
|
## Brand Summary
|
|
73692
75100
|
|
|
73693
|
-
|
|
73694
|
-
- **Fonts**: ${fontSummary}
|
|
73695
|
-
- **Sections**: ${sectionCount} page sections detected
|
|
73696
|
-
- **Headings**: ${headingCount} headings extracted
|
|
73697
|
-
- **CTAs**: ${ctaCount} calls-to-action found
|
|
73698
|
-
${cues.length > 0 ? `
|
|
73699
|
-
## Source Patterns Detected
|
|
73700
|
-
|
|
73701
|
-
${cues.map((c2) => `- ${c2}`).join("\n")}
|
|
73702
|
-
` : ""}
|
|
73703
|
-
## Example Prompts
|
|
73704
|
-
|
|
73705
|
-
Try asking:
|
|
73706
|
-
|
|
73707
|
-
- "Make me a 15-second social ad from this capture"
|
|
73708
|
-
- "Create a 30-second product tour video"
|
|
73709
|
-
- "Turn this into a vertical Instagram reel"
|
|
73710
|
-
- "Build a feature announcement video highlighting the top 3 features"
|
|
75101
|
+
${brandLines.join("\n")}
|
|
73711
75102
|
`;
|
|
73712
75103
|
}
|
|
73713
|
-
function detectImplementationCues(tokens, animations) {
|
|
73714
|
-
const cues = [];
|
|
73715
|
-
if (Object.keys(tokens.cssVariables).length > 10) {
|
|
73716
|
-
cues.push(
|
|
73717
|
-
"CSS custom properties used extensively \u2014 preserve design tokens for colors, spacing, and typography."
|
|
73718
|
-
);
|
|
73719
|
-
}
|
|
73720
|
-
if (tokens.fonts.length > 0) {
|
|
73721
|
-
cues.push(
|
|
73722
|
-
`Typography: ${tokens.fonts.join(", ")}. Match these exact font families and weights.`
|
|
73723
|
-
);
|
|
73724
|
-
}
|
|
73725
|
-
if (animations?.summary) {
|
|
73726
|
-
if (animations.summary.scrollTargets > 20) {
|
|
73727
|
-
cues.push(`${animations.summary.scrollTargets} scroll-triggered animations detected.`);
|
|
73728
|
-
}
|
|
73729
|
-
if (animations.summary.webAnimations > 5) {
|
|
73730
|
-
cues.push(`${animations.summary.webAnimations} active Web Animations detected.`);
|
|
73731
|
-
}
|
|
73732
|
-
if (animations.summary.canvases > 0) {
|
|
73733
|
-
cues.push(`${animations.summary.canvases} Canvas/WebGL elements detected.`);
|
|
73734
|
-
}
|
|
73735
|
-
}
|
|
73736
|
-
const hasMarquee = animations?.cssDeclarations?.some(
|
|
73737
|
-
(d) => d.animation?.name?.toLowerCase().includes("marquee") || d.animation?.name?.toLowerCase().includes("scroll")
|
|
73738
|
-
);
|
|
73739
|
-
if (hasMarquee) {
|
|
73740
|
-
cues.push("Marquee/ticker animation present \u2014 preserve continuous scrolling behavior.");
|
|
73741
|
-
}
|
|
73742
|
-
return cues;
|
|
73743
|
-
}
|
|
73744
75104
|
var init_agentPromptGenerator = __esm({
|
|
73745
75105
|
"src/capture/agentPromptGenerator.ts"() {
|
|
73746
75106
|
"use strict";
|
|
@@ -73748,8 +75108,8 @@ var init_agentPromptGenerator = __esm({
|
|
|
73748
75108
|
});
|
|
73749
75109
|
|
|
73750
75110
|
// src/capture/scaffolding.ts
|
|
73751
|
-
import { existsSync as
|
|
73752
|
-
import { join as
|
|
75111
|
+
import { existsSync as existsSync46, writeFileSync as writeFileSync20, readFileSync as readFileSync34 } from "fs";
|
|
75112
|
+
import { join as join50, resolve as resolve33 } from "path";
|
|
73753
75113
|
function loadEnvFile(startDir) {
|
|
73754
75114
|
try {
|
|
73755
75115
|
let dir = resolve33(startDir);
|
|
@@ -73774,9 +75134,9 @@ function loadEnvFile(startDir) {
|
|
|
73774
75134
|
} catch {
|
|
73775
75135
|
}
|
|
73776
75136
|
}
|
|
73777
|
-
async function generateProjectScaffold(outputDir, url, tokens, animationCatalog, hasScreenshots, hasLotties, hasShaders, catalogedAssets, progress, warnings) {
|
|
73778
|
-
const metaPath =
|
|
73779
|
-
if (!
|
|
75137
|
+
async function generateProjectScaffold(outputDir, url, tokens, animationCatalog, hasScreenshots, hasLotties, hasShaders, catalogedAssets, progress, warnings, detectedLibraries) {
|
|
75138
|
+
const metaPath = join50(outputDir, "meta.json");
|
|
75139
|
+
if (!existsSync46(metaPath)) {
|
|
73780
75140
|
const hostname = new URL(url).hostname.replace(/^www\./, "");
|
|
73781
75141
|
writeFileSync20(
|
|
73782
75142
|
metaPath,
|
|
@@ -73794,11 +75154,12 @@ async function generateProjectScaffold(outputDir, url, tokens, animationCatalog,
|
|
|
73794
75154
|
hasScreenshots,
|
|
73795
75155
|
hasLotties,
|
|
73796
75156
|
hasShaders,
|
|
73797
|
-
catalogedAssets
|
|
75157
|
+
catalogedAssets,
|
|
75158
|
+
detectedLibraries
|
|
73798
75159
|
);
|
|
73799
|
-
progress("agent", "CLAUDE.md generated");
|
|
75160
|
+
progress("agent", "AGENTS.md + CLAUDE.md generated");
|
|
73800
75161
|
} catch (err) {
|
|
73801
|
-
warnings.push(`CLAUDE.md generation failed: ${err}`);
|
|
75162
|
+
warnings.push(`AGENTS.md/CLAUDE.md generation failed: ${err}`);
|
|
73802
75163
|
}
|
|
73803
75164
|
}
|
|
73804
75165
|
var init_scaffolding = __esm({
|
|
@@ -73813,9 +75174,9 @@ __export(screenshotCapture_exports, {
|
|
|
73813
75174
|
captureScrollScreenshots: () => captureScrollScreenshots
|
|
73814
75175
|
});
|
|
73815
75176
|
import { writeFileSync as writeFileSync21, mkdirSync as mkdirSync27 } from "fs";
|
|
73816
|
-
import { join as
|
|
75177
|
+
import { join as join51 } from "path";
|
|
73817
75178
|
async function captureScrollScreenshots(page, outputDir) {
|
|
73818
|
-
const screenshotsDir =
|
|
75179
|
+
const screenshotsDir = join51(outputDir, "screenshots");
|
|
73819
75180
|
mkdirSync27(screenshotsDir, { recursive: true });
|
|
73820
75181
|
const MAX_SCREENSHOTS = 20;
|
|
73821
75182
|
const filePaths = [];
|
|
@@ -73849,7 +75210,7 @@ async function captureScrollScreenshots(page, outputDir) {
|
|
|
73849
75210
|
finalPositions[i2] / Math.max(1, scrollHeight - viewportHeight) * 100
|
|
73850
75211
|
);
|
|
73851
75212
|
const filename = `scroll-${String(Math.min(pct, 100)).padStart(3, "0")}.png`;
|
|
73852
|
-
const filePath =
|
|
75213
|
+
const filePath = join51(screenshotsDir, filename);
|
|
73853
75214
|
const buffer = await page.screenshot({ type: "png" });
|
|
73854
75215
|
writeFileSync21(filePath, buffer);
|
|
73855
75216
|
filePaths.push(`screenshots/${filename}`);
|
|
@@ -74162,8 +75523,8 @@ var capture_exports = {};
|
|
|
74162
75523
|
__export(capture_exports, {
|
|
74163
75524
|
captureWebsite: () => captureWebsite
|
|
74164
75525
|
});
|
|
74165
|
-
import { mkdirSync as mkdirSync28, writeFileSync as writeFileSync22, existsSync as
|
|
74166
|
-
import { join as
|
|
75526
|
+
import { mkdirSync as mkdirSync28, writeFileSync as writeFileSync22, existsSync as existsSync47 } from "fs";
|
|
75527
|
+
import { join as join52 } from "path";
|
|
74167
75528
|
async function captureWebsite(opts, onProgress) {
|
|
74168
75529
|
const {
|
|
74169
75530
|
url,
|
|
@@ -74180,9 +75541,9 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74180
75541
|
onProgress?.(stage, detail);
|
|
74181
75542
|
};
|
|
74182
75543
|
loadEnvFile(outputDir);
|
|
74183
|
-
mkdirSync28(
|
|
74184
|
-
mkdirSync28(
|
|
74185
|
-
mkdirSync28(
|
|
75544
|
+
mkdirSync28(join52(outputDir, "extracted"), { recursive: true });
|
|
75545
|
+
mkdirSync28(join52(outputDir, "screenshots"), { recursive: true });
|
|
75546
|
+
mkdirSync28(join52(outputDir, "assets"), { recursive: true });
|
|
74186
75547
|
progress("browser", "Launching headless Chrome...");
|
|
74187
75548
|
const { ensureBrowser: ensureBrowser2 } = await Promise.resolve().then(() => (init_manager2(), manager_exports2));
|
|
74188
75549
|
const browser = await ensureBrowser2();
|
|
@@ -74338,7 +75699,7 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74338
75699
|
} catch {
|
|
74339
75700
|
}
|
|
74340
75701
|
if (discoveredLotties.length > 0) {
|
|
74341
|
-
const lottieDir =
|
|
75702
|
+
const lottieDir = join52(outputDir, "assets", "lottie");
|
|
74342
75703
|
mkdirSync28(lottieDir, { recursive: true });
|
|
74343
75704
|
const savedCount = await saveLottieAnimations(discoveredLotties, lottieDir);
|
|
74344
75705
|
if (savedCount > 0) {
|
|
@@ -74358,7 +75719,7 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74358
75719
|
});
|
|
74359
75720
|
capturedShaders = unique;
|
|
74360
75721
|
writeFileSync22(
|
|
74361
|
-
|
|
75722
|
+
join52(outputDir, "extracted", "shaders.json"),
|
|
74362
75723
|
JSON.stringify(unique, null, 2),
|
|
74363
75724
|
"utf-8"
|
|
74364
75725
|
);
|
|
@@ -74369,7 +75730,7 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74369
75730
|
progress("tokens", "Extracting design tokens...");
|
|
74370
75731
|
const tokens = await extractTokens(page1);
|
|
74371
75732
|
writeFileSync22(
|
|
74372
|
-
|
|
75733
|
+
join52(outputDir, "extracted", "tokens.json"),
|
|
74373
75734
|
JSON.stringify(tokens, null, 2),
|
|
74374
75735
|
"utf-8"
|
|
74375
75736
|
);
|
|
@@ -74385,8 +75746,13 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74385
75746
|
const { catalogAssets: catalogAssets2 } = await Promise.resolve().then(() => (init_assetCataloger(), assetCataloger_exports));
|
|
74386
75747
|
catalogedAssets = await catalogAssets2(page1);
|
|
74387
75748
|
progress("design", `${catalogedAssets.length} assets cataloged`);
|
|
75749
|
+
if (catalogedAssets.length === 0) {
|
|
75750
|
+
warnings.push(
|
|
75751
|
+
"Asset catalog is empty \u2014 no images will be downloaded. The page may use non-standard image loading."
|
|
75752
|
+
);
|
|
75753
|
+
}
|
|
74388
75754
|
} catch (err) {
|
|
74389
|
-
warnings.push(`Asset cataloging failed: ${err}`);
|
|
75755
|
+
warnings.push(`Asset cataloging failed (no images will be downloaded): ${err}`);
|
|
74390
75756
|
}
|
|
74391
75757
|
progress("extract", "Extracting HTML & CSS...");
|
|
74392
75758
|
const extracted = await extractHtml(page1, { settleTime: 1e3 });
|
|
@@ -74419,6 +75785,10 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74419
75785
|
}
|
|
74420
75786
|
const detectedLibraries = await detectLibraries(page1, capturedShaders);
|
|
74421
75787
|
const visibleTextContent = await extractVisibleText(page1);
|
|
75788
|
+
const faviconLinks = await page1.evaluate(`(() => {
|
|
75789
|
+
var iconEls = Array.from(document.querySelectorAll('link[rel*="icon"], link[rel="apple-touch-icon"]'));
|
|
75790
|
+
return iconEls.map(function(l) { return { rel: l.rel, href: l.href }; });
|
|
75791
|
+
})()`);
|
|
74422
75792
|
await page1.close();
|
|
74423
75793
|
extracted.headHtml = await downloadAndRewriteFonts(extracted.headHtml, outputDir);
|
|
74424
75794
|
if (animationCatalog) {
|
|
@@ -74434,7 +75804,7 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74434
75804
|
representativeAnimations: representativeAnims
|
|
74435
75805
|
};
|
|
74436
75806
|
writeFileSync22(
|
|
74437
|
-
|
|
75807
|
+
join52(outputDir, "extracted", "animations.json"),
|
|
74438
75808
|
JSON.stringify(leanCatalog, null, 2),
|
|
74439
75809
|
"utf-8"
|
|
74440
75810
|
);
|
|
@@ -74442,21 +75812,21 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74442
75812
|
let assets = [];
|
|
74443
75813
|
if (!skipAssets) {
|
|
74444
75814
|
progress("assets", "Downloading assets...");
|
|
74445
|
-
assets = await downloadAssets(tokens, outputDir, catalogedAssets);
|
|
75815
|
+
assets = await downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks);
|
|
74446
75816
|
}
|
|
74447
75817
|
if (visibleTextContent) {
|
|
74448
|
-
writeFileSync22(
|
|
75818
|
+
writeFileSync22(join52(outputDir, "extracted", "visible-text.txt"), visibleTextContent, "utf-8");
|
|
74449
75819
|
}
|
|
74450
75820
|
if (catalogedAssets.length > 0) {
|
|
74451
75821
|
writeFileSync22(
|
|
74452
|
-
|
|
75822
|
+
join52(outputDir, "extracted", "assets-catalog.json"),
|
|
74453
75823
|
JSON.stringify(catalogedAssets, null, 2),
|
|
74454
75824
|
"utf-8"
|
|
74455
75825
|
);
|
|
74456
75826
|
}
|
|
74457
75827
|
if (detectedLibraries.length > 0) {
|
|
74458
75828
|
writeFileSync22(
|
|
74459
|
-
|
|
75829
|
+
join52(outputDir, "extracted", "detected-libraries.json"),
|
|
74460
75830
|
JSON.stringify(detectedLibraries, null, 2),
|
|
74461
75831
|
"utf-8"
|
|
74462
75832
|
);
|
|
@@ -74467,7 +75837,7 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74467
75837
|
const lines = generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCaptions);
|
|
74468
75838
|
if (lines.length > 0) {
|
|
74469
75839
|
writeFileSync22(
|
|
74470
|
-
|
|
75840
|
+
join52(outputDir, "extracted", "asset-descriptions.md"),
|
|
74471
75841
|
"# Asset Descriptions\n\nOne line per file. Read this instead of opening every image individually.\n\n" + lines.map((l) => "- " + l).join("\n") + "\n",
|
|
74472
75842
|
"utf-8"
|
|
74473
75843
|
);
|
|
@@ -74483,10 +75853,11 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74483
75853
|
animationCatalog,
|
|
74484
75854
|
screenshots.length > 0,
|
|
74485
75855
|
discoveredLotties.length > 0,
|
|
74486
|
-
|
|
75856
|
+
existsSync47(join52(outputDir, "extracted", "shaders.json")),
|
|
74487
75857
|
catalogedAssets,
|
|
74488
75858
|
progress,
|
|
74489
|
-
warnings
|
|
75859
|
+
warnings,
|
|
75860
|
+
detectedLibraries
|
|
74490
75861
|
);
|
|
74491
75862
|
progress("done", "Capture complete");
|
|
74492
75863
|
return {
|
|
@@ -74628,7 +75999,8 @@ var init_capture2 = __esm({
|
|
|
74628
75999
|
screenshots: result.screenshots.length,
|
|
74629
76000
|
assets: result.assets.length,
|
|
74630
76001
|
detectedSections: result.tokens.sections.length,
|
|
74631
|
-
fonts: result.tokens.fonts,
|
|
76002
|
+
fonts: result.tokens.fonts.map((f3) => f3.family),
|
|
76003
|
+
fontsDetailed: result.tokens.fonts,
|
|
74632
76004
|
animations: result.animationCatalog?.summary,
|
|
74633
76005
|
warnings: result.warnings
|
|
74634
76006
|
},
|
|
@@ -74644,7 +76016,11 @@ var init_capture2 = __esm({
|
|
|
74644
76016
|
console.log(` ${c2.dim("Screenshots:")} ${result.screenshots.length}`);
|
|
74645
76017
|
console.log(` ${c2.dim("Assets:")} ${result.assets.length}`);
|
|
74646
76018
|
console.log(` ${c2.dim("Sections:")} ${result.tokens.sections.length}`);
|
|
74647
|
-
console.log(
|
|
76019
|
+
console.log(
|
|
76020
|
+
` ${c2.dim("Fonts:")} ${result.tokens.fonts.map(function(f3) {
|
|
76021
|
+
return f3.family + " (" + (f3.variable && f3.weightRange ? f3.weightRange[0] + "-" + f3.weightRange[1] + " variable" : f3.weights.join(",")) + ")";
|
|
76022
|
+
}).join(", ")}`
|
|
76023
|
+
);
|
|
74648
76024
|
if (result.warnings.length > 0) {
|
|
74649
76025
|
console.log();
|
|
74650
76026
|
for (const w of result.warnings) {
|