hyperframes 0.4.6 → 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 +1864 -715
- package/dist/templates/_shared/CLAUDE.md +7 -5
- package/package.json +1 -1
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);
|
|
@@ -21335,7 +21388,7 @@ var init_runFfmpeg = __esm({
|
|
|
21335
21388
|
import { spawn as spawn4 } from "child_process";
|
|
21336
21389
|
import { copyFileSync, existsSync as existsSync15, mkdirSync as mkdirSync9, readdirSync as readdirSync7, statSync as statSync4, writeFileSync as writeFileSync8 } from "fs";
|
|
21337
21390
|
import { join as join19, dirname as dirname5 } from "path";
|
|
21338
|
-
function getEncoderPreset(quality, format = "mp4") {
|
|
21391
|
+
function getEncoderPreset(quality, format = "mp4", hdr) {
|
|
21339
21392
|
const base = ENCODER_PRESETS[quality];
|
|
21340
21393
|
if (format === "webm") {
|
|
21341
21394
|
return {
|
|
@@ -21353,6 +21406,15 @@ function getEncoderPreset(quality, format = "mp4") {
|
|
|
21353
21406
|
pixelFormat: "yuva444p10le"
|
|
21354
21407
|
};
|
|
21355
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
|
+
}
|
|
21356
21418
|
return { ...base, pixelFormat: "yuv420p" };
|
|
21357
21419
|
}
|
|
21358
21420
|
function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
@@ -21410,6 +21472,9 @@ function buildEncoderArgs(options, inputArgs, outputPath, gpuEncoder = null) {
|
|
|
21410
21472
|
args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}`);
|
|
21411
21473
|
}
|
|
21412
21474
|
}
|
|
21475
|
+
if (codec === "h265") {
|
|
21476
|
+
args.push("-tag:v", "hvc1");
|
|
21477
|
+
}
|
|
21413
21478
|
} else if (codec === "vp9") {
|
|
21414
21479
|
args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
|
|
21415
21480
|
args.push("-deadline", preset === "ultrafast" ? "realtime" : "good");
|
|
@@ -21727,35 +21792,91 @@ var init_chunkEncoder = __esm({
|
|
|
21727
21792
|
}
|
|
21728
21793
|
});
|
|
21729
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
|
+
|
|
21730
21840
|
// ../engine/src/services/streamingEncoder.ts
|
|
21731
21841
|
import { spawn as spawn5 } from "child_process";
|
|
21732
21842
|
import { existsSync as existsSync16, mkdirSync as mkdirSync10, statSync as statSync5 } from "fs";
|
|
21733
21843
|
import { dirname as dirname6 } from "path";
|
|
21734
21844
|
function createFrameReorderBuffer(startFrame, endFrame) {
|
|
21735
|
-
let
|
|
21736
|
-
|
|
21737
|
-
const
|
|
21738
|
-
|
|
21739
|
-
|
|
21740
|
-
|
|
21741
|
-
|
|
21742
|
-
|
|
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);
|
|
21743
21853
|
}
|
|
21744
21854
|
};
|
|
21745
|
-
|
|
21746
|
-
|
|
21747
|
-
|
|
21748
|
-
|
|
21749
|
-
|
|
21750
|
-
|
|
21751
|
-
|
|
21752
|
-
|
|
21753
|
-
|
|
21754
|
-
|
|
21755
|
-
|
|
21756
|
-
|
|
21757
|
-
|
|
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);
|
|
21758
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 };
|
|
21759
21880
|
}
|
|
21760
21881
|
function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
21761
21882
|
const {
|
|
@@ -21768,19 +21889,36 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
21768
21889
|
useGpu = false,
|
|
21769
21890
|
imageFormat = "jpeg"
|
|
21770
21891
|
} = options;
|
|
21771
|
-
const
|
|
21772
|
-
|
|
21773
|
-
|
|
21774
|
-
"
|
|
21775
|
-
|
|
21776
|
-
|
|
21777
|
-
|
|
21778
|
-
|
|
21779
|
-
|
|
21780
|
-
|
|
21781
|
-
|
|
21782
|
-
|
|
21783
|
-
|
|
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));
|
|
21784
21922
|
const shouldUseGpu = useGpu && gpuEncoder !== null;
|
|
21785
21923
|
if (codec === "h264" || codec === "h265") {
|
|
21786
21924
|
if (shouldUseGpu) {
|
|
@@ -21818,12 +21956,15 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
21818
21956
|
if (bitrate) args.push("-b:v", bitrate);
|
|
21819
21957
|
else args.push("-crf", String(quality));
|
|
21820
21958
|
const xParamsFlag = codec === "h264" ? "-x264-params" : "-x265-params";
|
|
21821
|
-
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";
|
|
21822
21960
|
if (preset === "ultrafast") {
|
|
21823
21961
|
args.push(xParamsFlag, `aq-mode=3:${colorParams}`);
|
|
21824
21962
|
} else {
|
|
21825
21963
|
args.push(xParamsFlag, `aq-mode=3:aq-strength=0.8:deblock=1,1:${colorParams}`);
|
|
21826
21964
|
}
|
|
21965
|
+
if (codec === "h265") {
|
|
21966
|
+
args.push("-tag:v", "hvc1");
|
|
21967
|
+
}
|
|
21827
21968
|
}
|
|
21828
21969
|
} else if (codec === "vp9") {
|
|
21829
21970
|
args.push("-c:v", "libvpx-vp9", "-b:v", bitrate || "0", "-crf", String(quality));
|
|
@@ -21839,17 +21980,31 @@ function buildStreamingArgs(options, outputPath, gpuEncoder = null) {
|
|
|
21839
21980
|
return [...args, "-y", outputPath];
|
|
21840
21981
|
}
|
|
21841
21982
|
if (codec === "h264" || codec === "h265") {
|
|
21842
|
-
|
|
21843
|
-
|
|
21844
|
-
|
|
21845
|
-
|
|
21846
|
-
|
|
21847
|
-
|
|
21848
|
-
|
|
21849
|
-
|
|
21850
|
-
|
|
21851
|
-
|
|
21852
|
-
|
|
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") {
|
|
21853
22008
|
const vfIdx = args.indexOf("-vf");
|
|
21854
22009
|
if (vfIdx !== -1) {
|
|
21855
22010
|
args[vfIdx + 1] = `scale=in_range=pc:out_range=tv,${args[vfIdx + 1]}`;
|
|
@@ -21919,14 +22074,16 @@ Process error: ${err.message}`;
|
|
|
21919
22074
|
if (exitStatus !== "running" || !ffmpeg.stdin || ffmpeg.stdin.destroyed) {
|
|
21920
22075
|
return false;
|
|
21921
22076
|
}
|
|
21922
|
-
|
|
22077
|
+
const copy = Buffer.from(buffer);
|
|
22078
|
+
return ffmpeg.stdin.write(copy);
|
|
21923
22079
|
},
|
|
21924
22080
|
close: async () => {
|
|
21925
22081
|
clearTimeout(timer);
|
|
21926
22082
|
if (signal) signal.removeEventListener("abort", onAbort);
|
|
21927
|
-
|
|
22083
|
+
const stdin = ffmpeg.stdin;
|
|
22084
|
+
if (stdin && !stdin.destroyed) {
|
|
21928
22085
|
await new Promise((resolve35) => {
|
|
21929
|
-
|
|
22086
|
+
stdin.end(() => resolve35());
|
|
21930
22087
|
});
|
|
21931
22088
|
}
|
|
21932
22089
|
await exitPromise;
|
|
@@ -21958,6 +22115,7 @@ var init_streamingEncoder = __esm({
|
|
|
21958
22115
|
"../engine/src/services/streamingEncoder.ts"() {
|
|
21959
22116
|
"use strict";
|
|
21960
22117
|
init_gpuEncoder();
|
|
22118
|
+
init_hdr();
|
|
21961
22119
|
init_config2();
|
|
21962
22120
|
}
|
|
21963
22121
|
});
|
|
@@ -22030,6 +22188,10 @@ async function extractVideoMetadata(filePath) {
|
|
|
22030
22188
|
const avgFps = parseFrameRate(videoStream.avg_frame_rate);
|
|
22031
22189
|
const fps = avgFps || rFps;
|
|
22032
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);
|
|
22033
22195
|
return {
|
|
22034
22196
|
durationSeconds: output.format.duration ? parseFloat(output.format.duration) : 0,
|
|
22035
22197
|
width: videoStream.width || 0,
|
|
@@ -22037,7 +22199,8 @@ async function extractVideoMetadata(filePath) {
|
|
|
22037
22199
|
fps,
|
|
22038
22200
|
videoCodec: videoStream.codec_name || "unknown",
|
|
22039
22201
|
hasAudio: output.streams.some((s2) => s2.codec_type === "audio"),
|
|
22040
|
-
isVFR
|
|
22202
|
+
isVFR,
|
|
22203
|
+
colorSpace: hasColorInfo ? { colorTransfer, colorPrimaries, colorSpace: colorSpaceVal } : null
|
|
22041
22204
|
};
|
|
22042
22205
|
})();
|
|
22043
22206
|
videoMetadataCache.set(filePath, probePromise);
|
|
@@ -22262,18 +22425,20 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
22262
22425
|
const metadata = await extractVideoMetadata(videoPath);
|
|
22263
22426
|
const framePattern = `frame_%05d.${format}`;
|
|
22264
22427
|
const outputPattern = join21(videoOutputDir, framePattern);
|
|
22265
|
-
const
|
|
22266
|
-
|
|
22267
|
-
|
|
22268
|
-
|
|
22269
|
-
|
|
22270
|
-
|
|
22271
|
-
|
|
22272
|
-
|
|
22273
|
-
|
|
22274
|
-
"
|
|
22275
|
-
|
|
22276
|
-
|
|
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");
|
|
22277
22442
|
if (format === "png") args.push("-compression_level", "6");
|
|
22278
22443
|
args.push("-y", outputPattern);
|
|
22279
22444
|
return new Promise((resolve35, reject) => {
|
|
@@ -22333,30 +22498,100 @@ async function extractVideoFramesRange(videoPath, videoId, startTime, duration,
|
|
|
22333
22498
|
});
|
|
22334
22499
|
});
|
|
22335
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
|
+
}
|
|
22336
22532
|
async function extractAllVideoFrames(videos, baseDir, options, signal, config, compiledDir) {
|
|
22337
22533
|
const startTime = Date.now();
|
|
22338
22534
|
const extracted = [];
|
|
22339
22535
|
const errors = [];
|
|
22340
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
|
+
}
|
|
22341
22589
|
const results = await Promise.all(
|
|
22342
|
-
|
|
22590
|
+
resolvedVideos.map(async ({ video, videoPath }) => {
|
|
22343
22591
|
if (signal?.aborted) {
|
|
22344
22592
|
throw new Error("Video frame extraction cancelled");
|
|
22345
22593
|
}
|
|
22346
22594
|
try {
|
|
22347
|
-
let videoPath = video.src;
|
|
22348
|
-
if (!videoPath.startsWith("/") && !isHttpUrl(videoPath)) {
|
|
22349
|
-
const fromCompiled = compiledDir ? join21(compiledDir, videoPath) : null;
|
|
22350
|
-
videoPath = fromCompiled && existsSync18(fromCompiled) ? fromCompiled : join21(baseDir, videoPath);
|
|
22351
|
-
}
|
|
22352
|
-
if (isHttpUrl(videoPath)) {
|
|
22353
|
-
const downloadDir = join21(options.outputDir, "_downloads");
|
|
22354
|
-
mkdirSync12(downloadDir, { recursive: true });
|
|
22355
|
-
videoPath = await downloadToTemp(videoPath, downloadDir);
|
|
22356
|
-
}
|
|
22357
|
-
if (!existsSync18(videoPath)) {
|
|
22358
|
-
return { error: { videoId: video.id, error: `Video file not found: ${videoPath}` } };
|
|
22359
|
-
}
|
|
22360
22595
|
let videoDuration = video.end - video.start;
|
|
22361
22596
|
if (!Number.isFinite(videoDuration) || videoDuration <= 0) {
|
|
22362
22597
|
const metadata = await extractVideoMetadata(videoPath);
|
|
@@ -22423,7 +22658,9 @@ var init_videoFrameExtractor = __esm({
|
|
|
22423
22658
|
"use strict";
|
|
22424
22659
|
init_esm10();
|
|
22425
22660
|
init_ffprobe();
|
|
22661
|
+
init_hdr();
|
|
22426
22662
|
init_urlDownloader();
|
|
22663
|
+
init_runFfmpeg();
|
|
22427
22664
|
init_config2();
|
|
22428
22665
|
FrameLookupTable = class {
|
|
22429
22666
|
videos = /* @__PURE__ */ new Map();
|
|
@@ -22601,6 +22838,112 @@ function createVideoFrameInjector(frameLookup, config) {
|
|
|
22601
22838
|
}
|
|
22602
22839
|
};
|
|
22603
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
|
+
}
|
|
22604
22947
|
var init_videoFrameInjector = __esm({
|
|
22605
22948
|
"../engine/src/services/videoFrameInjector.ts"() {
|
|
22606
22949
|
"use strict";
|
|
@@ -23255,6 +23598,515 @@ var init_fileServer = __esm({
|
|
|
23255
23598
|
}
|
|
23256
23599
|
});
|
|
23257
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
|
+
|
|
23258
24110
|
// ../engine/src/index.ts
|
|
23259
24111
|
var src_exports = {};
|
|
23260
24112
|
__export(src_exports, {
|
|
@@ -23264,13 +24116,19 @@ __export(src_exports, {
|
|
|
23264
24116
|
FrameLookupTable: () => FrameLookupTable,
|
|
23265
24117
|
MEDIA_VISUAL_STYLE_PROPERTIES: () => MEDIA_VISUAL_STYLE_PROPERTIES,
|
|
23266
24118
|
acquireBrowser: () => acquireBrowser,
|
|
24119
|
+
analyzeCompositionHdr: () => analyzeCompositionHdr,
|
|
23267
24120
|
analyzeKeyframeIntervals: () => analyzeKeyframeIntervals,
|
|
23268
24121
|
applyFaststart: () => applyFaststart,
|
|
23269
24122
|
beginFrameCapture: () => beginFrameCapture,
|
|
24123
|
+
blitRgb48leRegion: () => blitRgb48leRegion,
|
|
24124
|
+
blitRgba8OverRgb48le: () => blitRgba8OverRgb48le,
|
|
23270
24125
|
buildChromeArgs: () => buildChromeArgs,
|
|
24126
|
+
buildHdrChromeArgs: () => buildHdrChromeArgs,
|
|
23271
24127
|
calculateOptimalWorkers: () => calculateOptimalWorkers,
|
|
24128
|
+
captureAlphaPng: () => captureAlphaPng,
|
|
23272
24129
|
captureFrame: () => captureFrame,
|
|
23273
24130
|
captureFrameToBuffer: () => captureFrameToBuffer,
|
|
24131
|
+
captureScreenshotWithAlpha: () => captureScreenshotWithAlpha,
|
|
23274
24132
|
cdpSessionCache: () => cdpSessionCache,
|
|
23275
24133
|
closeCaptureSession: () => closeCaptureSession,
|
|
23276
24134
|
createCaptureSession: () => createCaptureSession,
|
|
@@ -23278,7 +24136,10 @@ __export(src_exports, {
|
|
|
23278
24136
|
createFrameLookupTable: () => createFrameLookupTable,
|
|
23279
24137
|
createFrameReorderBuffer: () => createFrameReorderBuffer,
|
|
23280
24138
|
createVideoFrameInjector: () => createVideoFrameInjector,
|
|
24139
|
+
decodePng: () => decodePng,
|
|
24140
|
+
decodePngToRgb48le: () => decodePngToRgb48le,
|
|
23281
24141
|
detectGpuEncoder: () => detectGpuEncoder,
|
|
24142
|
+
detectTransfer: () => detectTransfer,
|
|
23282
24143
|
distributeFrames: () => distributeFrames,
|
|
23283
24144
|
downloadToTemp: () => downloadToTemp,
|
|
23284
24145
|
encodeFramesChunkedConcat: () => encodeFramesChunkedConcat,
|
|
@@ -23288,15 +24149,24 @@ __export(src_exports, {
|
|
|
23288
24149
|
extractAudioMetadata: () => extractAudioMetadata,
|
|
23289
24150
|
extractVideoFramesRange: () => extractVideoFramesRange,
|
|
23290
24151
|
extractVideoMetadata: () => extractVideoMetadata,
|
|
24152
|
+
float16ToPqRgb: () => float16ToPqRgb,
|
|
23291
24153
|
getCapturePerfSummary: () => getCapturePerfSummary,
|
|
23292
24154
|
getCdpSession: () => getCdpSession,
|
|
23293
24155
|
getCompositionDuration: () => getCompositionDuration,
|
|
23294
24156
|
getEncoderPreset: () => getEncoderPreset,
|
|
23295
24157
|
getFrameAtTime: () => getFrameAtTime,
|
|
24158
|
+
getHdrEncoderColorParams: () => getHdrEncoderColorParams,
|
|
24159
|
+
getSrgbToHdrLut: () => getSrgbToHdrLut,
|
|
23296
24160
|
getSystemResources: () => getSystemResources,
|
|
24161
|
+
groupIntoLayers: () => groupIntoLayers,
|
|
24162
|
+
hideVideoElements: () => hideVideoElements,
|
|
24163
|
+
initHdrReadback: () => initHdrReadback,
|
|
24164
|
+
initTransparentBackground: () => initTransparentBackground,
|
|
23297
24165
|
initializeSession: () => initializeSession,
|
|
23298
24166
|
injectVideoFramesBatch: () => injectVideoFramesBatch,
|
|
24167
|
+
isHdrColorSpace: () => isHdrColorSpace,
|
|
23299
24168
|
isHttpUrl: () => isHttpUrl,
|
|
24169
|
+
launchHdrBrowser: () => launchHdrBrowser,
|
|
23300
24170
|
mergeWorkerFrames: () => mergeWorkerFrames,
|
|
23301
24171
|
muxVideoWithAudio: () => muxVideoWithAudio,
|
|
23302
24172
|
pageScreenshotCapture: () => pageScreenshotCapture,
|
|
@@ -23305,11 +24175,15 @@ __export(src_exports, {
|
|
|
23305
24175
|
prepareCaptureSessionForReuse: () => prepareCaptureSessionForReuse,
|
|
23306
24176
|
processCompositionAudio: () => processCompositionAudio,
|
|
23307
24177
|
quantizeTimeToFrame: () => quantizeTimeToFrame,
|
|
24178
|
+
queryElementStacking: () => queryElementStacking,
|
|
24179
|
+
queryVideoElementBounds: () => queryVideoElementBounds,
|
|
23308
24180
|
releaseBrowser: () => releaseBrowser,
|
|
23309
24181
|
resolveConfig: () => resolveConfig,
|
|
23310
24182
|
resolveHeadlessShellPath: () => resolveHeadlessShellPath,
|
|
24183
|
+
showVideoElements: () => showVideoElements,
|
|
23311
24184
|
spawnStreamingEncoder: () => spawnStreamingEncoder,
|
|
23312
|
-
syncVideoFrameVisibility: () => syncVideoFrameVisibility
|
|
24185
|
+
syncVideoFrameVisibility: () => syncVideoFrameVisibility,
|
|
24186
|
+
uploadAndReadbackHdrFrame: () => uploadAndReadbackHdrFrame
|
|
23313
24187
|
});
|
|
23314
24188
|
var init_src2 = __esm({
|
|
23315
24189
|
"../engine/src/index.ts"() {
|
|
@@ -23328,6 +24202,12 @@ var init_src2 = __esm({
|
|
|
23328
24202
|
init_src();
|
|
23329
24203
|
init_ffprobe();
|
|
23330
24204
|
init_urlDownloader();
|
|
24205
|
+
init_alphaBlit();
|
|
24206
|
+
init_layerCompositor();
|
|
24207
|
+
init_hdrCapture();
|
|
24208
|
+
init_screenshotService();
|
|
24209
|
+
init_videoFrameInjector();
|
|
24210
|
+
init_hdr();
|
|
23331
24211
|
}
|
|
23332
24212
|
});
|
|
23333
24213
|
|
|
@@ -23412,8 +24292,8 @@ var init_staticGuard = __esm({
|
|
|
23412
24292
|
});
|
|
23413
24293
|
|
|
23414
24294
|
// ../core/src/compiler/htmlBundler.ts
|
|
23415
|
-
import { readFileSync as readFileSync15, existsSync as
|
|
23416
|
-
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";
|
|
23417
24297
|
import { transformSync } from "esbuild";
|
|
23418
24298
|
function parseHTMLContent(html) {
|
|
23419
24299
|
const trimmed = html.trimStart().toLowerCase();
|
|
@@ -23481,7 +24361,7 @@ function isRelativeUrl(url) {
|
|
|
23481
24361
|
return !url.startsWith("http://") && !url.startsWith("https://") && !url.startsWith("//") && !url.startsWith("data:") && !isAbsolute2(url);
|
|
23482
24362
|
}
|
|
23483
24363
|
function safeReadFile(filePath) {
|
|
23484
|
-
if (!
|
|
24364
|
+
if (!existsSync23(filePath)) return null;
|
|
23485
24365
|
try {
|
|
23486
24366
|
return readFileSync15(filePath, "utf-8");
|
|
23487
24367
|
} catch {
|
|
@@ -23489,7 +24369,7 @@ function safeReadFile(filePath) {
|
|
|
23489
24369
|
}
|
|
23490
24370
|
}
|
|
23491
24371
|
function safeReadFileBuffer(filePath) {
|
|
23492
|
-
if (!
|
|
24372
|
+
if (!existsSync23(filePath)) return null;
|
|
23493
24373
|
try {
|
|
23494
24374
|
return readFileSync15(filePath);
|
|
23495
24375
|
} catch {
|
|
@@ -23682,8 +24562,8 @@ function stripJsCommentsParserSafe(source) {
|
|
|
23682
24562
|
}
|
|
23683
24563
|
}
|
|
23684
24564
|
async function bundleToSingleHtml(projectDir, options) {
|
|
23685
|
-
const indexPath =
|
|
23686
|
-
if (!
|
|
24565
|
+
const indexPath = join26(projectDir, "index.html");
|
|
24566
|
+
if (!existsSync23(indexPath)) throw new Error("index.html not found in project directory");
|
|
23687
24567
|
const rawHtml = readFileSync15(indexPath, "utf-8");
|
|
23688
24568
|
const compiled = await compileHtml(rawHtml, projectDir, options?.probeMediaDuration);
|
|
23689
24569
|
const staticGuard = validateHyperframeHtmlContract(compiled);
|
|
@@ -23959,7 +24839,7 @@ var init_compiler = __esm({
|
|
|
23959
24839
|
|
|
23960
24840
|
// ../producer/src/services/hyperframeRuntimeLoader.ts
|
|
23961
24841
|
import { createHash as createHash2 } from "crypto";
|
|
23962
|
-
import { existsSync as
|
|
24842
|
+
import { existsSync as existsSync24, readFileSync as readFileSync16 } from "fs";
|
|
23963
24843
|
import { dirname as dirname8, resolve as resolve12 } from "path";
|
|
23964
24844
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
23965
24845
|
function resolveHyperframeManifestPath() {
|
|
@@ -23972,7 +24852,7 @@ function resolveHyperframeManifestPath() {
|
|
|
23972
24852
|
MODULE_RELATIVE_MANIFEST_PATH
|
|
23973
24853
|
];
|
|
23974
24854
|
for (const candidate of candidates) {
|
|
23975
|
-
if (
|
|
24855
|
+
if (existsSync24(candidate)) {
|
|
23976
24856
|
return candidate;
|
|
23977
24857
|
}
|
|
23978
24858
|
}
|
|
@@ -23983,7 +24863,7 @@ function getVerifiedHyperframeRuntimeSource() {
|
|
|
23983
24863
|
}
|
|
23984
24864
|
function resolveVerifiedHyperframeRuntime() {
|
|
23985
24865
|
const manifestPath = resolveHyperframeManifestPath();
|
|
23986
|
-
if (!
|
|
24866
|
+
if (!existsSync24(manifestPath)) {
|
|
23987
24867
|
throw new Error(
|
|
23988
24868
|
`[HyperframeRuntimeLoader] Missing manifest at ${manifestPath}. Build core runtime artifacts before rendering.`
|
|
23989
24869
|
);
|
|
@@ -23997,7 +24877,7 @@ function resolveVerifiedHyperframeRuntime() {
|
|
|
23997
24877
|
);
|
|
23998
24878
|
}
|
|
23999
24879
|
const runtimePath = resolve12(dirname8(manifestPath), runtimeFileName);
|
|
24000
|
-
if (!
|
|
24880
|
+
if (!existsSync24(runtimePath)) {
|
|
24001
24881
|
throw new Error(`[HyperframeRuntimeLoader] Missing runtime artifact at ${runtimePath}.`);
|
|
24002
24882
|
}
|
|
24003
24883
|
const runtimeSource = readFileSync16(runtimePath, "utf8");
|
|
@@ -24039,8 +24919,8 @@ var init_hyperframeRuntimeLoader = __esm({
|
|
|
24039
24919
|
// ../producer/src/services/fileServer.ts
|
|
24040
24920
|
import { Hono as Hono3 } from "hono";
|
|
24041
24921
|
import { serve as serve2 } from "@hono/node-server";
|
|
24042
|
-
import { readFileSync as readFileSync17, existsSync as
|
|
24043
|
-
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";
|
|
24044
24924
|
function stripEmbeddedRuntimeScripts3(html) {
|
|
24045
24925
|
if (!html) return html;
|
|
24046
24926
|
const scriptRe = /<script\b[^>]*>[\s\S]*?<\/script>/gi;
|
|
@@ -24115,7 +24995,7 @@ ${headTags}`);
|
|
|
24115
24995
|
}
|
|
24116
24996
|
function createFileServer2(options) {
|
|
24117
24997
|
const { projectDir, compiledDir, port = 0, stripEmbeddedRuntime = true } = options;
|
|
24118
|
-
const preHeadScripts = options.preHeadScripts ?? [];
|
|
24998
|
+
const preHeadScripts = [HF_EARLY_STUB, ...options.preHeadScripts ?? []];
|
|
24119
24999
|
const headScripts = options.headScripts ?? [getVerifiedHyperframeRuntimeSource()];
|
|
24120
25000
|
const bodyScripts = options.bodyScripts ?? [RENDER_MODE_SCRIPT, HF_BRIDGE_SCRIPT];
|
|
24121
25001
|
const app = new Hono3();
|
|
@@ -24123,12 +25003,12 @@ function createFileServer2(options) {
|
|
|
24123
25003
|
let requestPath = c2.req.path;
|
|
24124
25004
|
if (requestPath === "/") requestPath = "/index.html";
|
|
24125
25005
|
const relativePath = requestPath.replace(/^\//, "");
|
|
24126
|
-
const compiledPath = compiledDir ?
|
|
25006
|
+
const compiledPath = compiledDir ? join27(compiledDir, relativePath) : null;
|
|
24127
25007
|
const hasCompiledFile = Boolean(
|
|
24128
|
-
compiledPath &&
|
|
25008
|
+
compiledPath && existsSync25(compiledPath) && statSync7(compiledPath).isFile()
|
|
24129
25009
|
);
|
|
24130
|
-
const filePath = hasCompiledFile ? compiledPath :
|
|
24131
|
-
if (!
|
|
25010
|
+
const filePath = hasCompiledFile ? compiledPath : join27(projectDir, relativePath);
|
|
25011
|
+
if (!existsSync25(filePath) || !statSync7(filePath).isFile()) {
|
|
24132
25012
|
if (!/favicon\.ico$/i.test(requestPath)) {
|
|
24133
25013
|
console.warn(`[FileServer] 404 Not Found: ${requestPath}`);
|
|
24134
25014
|
}
|
|
@@ -24171,7 +25051,7 @@ function createFileServer2(options) {
|
|
|
24171
25051
|
});
|
|
24172
25052
|
});
|
|
24173
25053
|
}
|
|
24174
|
-
var MIME_TYPES3, VIRTUAL_TIME_SHIM, 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;
|
|
24175
25055
|
var init_fileServer2 = __esm({
|
|
24176
25056
|
"../producer/src/services/fileServer.ts"() {
|
|
24177
25057
|
"use strict";
|
|
@@ -24411,6 +25291,10 @@ var init_fileServer2 = __esm({
|
|
|
24411
25291
|
__realSetTimeout(waitForPlayer, 50);
|
|
24412
25292
|
}
|
|
24413
25293
|
waitForPlayer();
|
|
25294
|
+
})();`;
|
|
25295
|
+
HF_EARLY_STUB = `(function() {
|
|
25296
|
+
if (typeof window === "undefined") return;
|
|
25297
|
+
if (!window.__hf) window.__hf = {};
|
|
24414
25298
|
})();`;
|
|
24415
25299
|
HF_BRIDGE_SCRIPT = `(function() {
|
|
24416
25300
|
var __realSetInterval =
|
|
@@ -24457,20 +25341,24 @@ var init_fileServer2 = __esm({
|
|
|
24457
25341
|
if (!p || typeof p.renderSeek !== "function" || typeof p.getDuration !== "function") {
|
|
24458
25342
|
return false;
|
|
24459
25343
|
}
|
|
24460
|
-
window.__hf
|
|
24461
|
-
|
|
25344
|
+
var hf = window.__hf || {};
|
|
25345
|
+
Object.defineProperty(hf, "duration", {
|
|
25346
|
+
configurable: true,
|
|
25347
|
+
enumerable: true,
|
|
25348
|
+
get: function() {
|
|
24462
25349
|
var d = p.getDuration();
|
|
24463
25350
|
return d > 0 ? d : getDeclaredDuration();
|
|
24464
25351
|
},
|
|
24465
|
-
|
|
24466
|
-
|
|
24467
|
-
|
|
24468
|
-
|
|
24469
|
-
|
|
24470
|
-
|
|
24471
|
-
|
|
24472
|
-
|
|
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);
|
|
24473
25360
|
};
|
|
25361
|
+
window.__hf = hf;
|
|
24474
25362
|
return true;
|
|
24475
25363
|
}
|
|
24476
25364
|
if (bridge()) return;
|
|
@@ -24490,7 +25378,7 @@ var init_ffprobe2 = __esm({
|
|
|
24490
25378
|
});
|
|
24491
25379
|
|
|
24492
25380
|
// ../producer/src/utils/paths.ts
|
|
24493
|
-
import { resolve as resolve13, basename, join as
|
|
25381
|
+
import { resolve as resolve13, basename, join as join28, relative as relative2, isAbsolute as isAbsolute3 } from "path";
|
|
24494
25382
|
function isPathInside(childPath, parentPath) {
|
|
24495
25383
|
const absChild = resolve13(childPath);
|
|
24496
25384
|
const absParent = resolve13(parentPath);
|
|
@@ -24511,7 +25399,7 @@ function toExternalAssetKey(absPath) {
|
|
|
24511
25399
|
function resolveRenderPaths(projectDir, outputPath, rendersDir = DEFAULT_RENDERS_DIR) {
|
|
24512
25400
|
const absoluteProjectDir = resolve13(projectDir);
|
|
24513
25401
|
const projectName = basename(absoluteProjectDir);
|
|
24514
|
-
const resolvedOutputPath = outputPath ??
|
|
25402
|
+
const resolvedOutputPath = outputPath ?? join28(rendersDir, `${projectName}.mp4`);
|
|
24515
25403
|
const absoluteOutputPath = resolve13(resolvedOutputPath);
|
|
24516
25404
|
return { absoluteProjectDir, absoluteOutputPath };
|
|
24517
25405
|
}
|
|
@@ -24584,9 +25472,9 @@ var init_fontData_generated = __esm({
|
|
|
24584
25472
|
});
|
|
24585
25473
|
|
|
24586
25474
|
// ../producer/src/services/deterministicFonts.ts
|
|
24587
|
-
import { existsSync as
|
|
24588
|
-
import { homedir as
|
|
24589
|
-
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";
|
|
24590
25478
|
function normalizeFamilyName(family) {
|
|
24591
25479
|
return family.trim().replace(/^['"]|['"]$/g, "").trim().toLowerCase();
|
|
24592
25480
|
}
|
|
@@ -24697,14 +25585,14 @@ function fontSlug(familyName) {
|
|
|
24697
25585
|
return familyName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
24698
25586
|
}
|
|
24699
25587
|
function fontCacheDir(slug) {
|
|
24700
|
-
const dir =
|
|
24701
|
-
if (!
|
|
25588
|
+
const dir = join29(GOOGLE_FONTS_CACHE_DIR, slug);
|
|
25589
|
+
if (!existsSync26(dir)) {
|
|
24702
25590
|
mkdirSync15(dir, { recursive: true });
|
|
24703
25591
|
}
|
|
24704
25592
|
return dir;
|
|
24705
25593
|
}
|
|
24706
25594
|
function cachedWoff2Path(slug, weight, style) {
|
|
24707
|
-
return
|
|
25595
|
+
return join29(fontCacheDir(slug), `${weight}-${style}.woff2`);
|
|
24708
25596
|
}
|
|
24709
25597
|
async function fetchGoogleFont(familyName) {
|
|
24710
25598
|
const slug = fontSlug(familyName);
|
|
@@ -24730,7 +25618,7 @@ async function fetchGoogleFont(familyName) {
|
|
|
24730
25618
|
const woff2Url = match[3] || "";
|
|
24731
25619
|
if (!woff2Url) continue;
|
|
24732
25620
|
const cachePath2 = cachedWoff2Path(slug, weight, style);
|
|
24733
|
-
if (!
|
|
25621
|
+
if (!existsSync26(cachePath2)) {
|
|
24734
25622
|
try {
|
|
24735
25623
|
const fontRes = await fetch(woff2Url);
|
|
24736
25624
|
if (!fontRes.ok) continue;
|
|
@@ -24916,14 +25804,14 @@ var init_deterministicFonts = __esm({
|
|
|
24916
25804
|
poppins: "poppins",
|
|
24917
25805
|
"segoe ui": "roboto"
|
|
24918
25806
|
};
|
|
24919
|
-
GOOGLE_FONTS_CACHE_DIR =
|
|
25807
|
+
GOOGLE_FONTS_CACHE_DIR = join29(homedir7(), ".cache", "hyperframes", "fonts");
|
|
24920
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";
|
|
24921
25809
|
}
|
|
24922
25810
|
});
|
|
24923
25811
|
|
|
24924
25812
|
// ../producer/src/services/htmlCompiler.ts
|
|
24925
|
-
import { readFileSync as readFileSync19, existsSync as
|
|
24926
|
-
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";
|
|
24927
25815
|
import postcss from "postcss";
|
|
24928
25816
|
function dedupeElementsById(elements) {
|
|
24929
25817
|
const deduped = /* @__PURE__ */ new Map();
|
|
@@ -24965,16 +25853,16 @@ function detectRenderModeHints(html) {
|
|
|
24965
25853
|
async function resolveMediaDuration(src, mediaStart, baseDir, downloadDir, tagName19) {
|
|
24966
25854
|
let filePath = src;
|
|
24967
25855
|
if (isHttpUrl(src)) {
|
|
24968
|
-
if (!
|
|
25856
|
+
if (!existsSync27(downloadDir)) mkdirSync16(downloadDir, { recursive: true });
|
|
24969
25857
|
try {
|
|
24970
25858
|
filePath = await downloadToTemp(src, downloadDir);
|
|
24971
25859
|
} catch {
|
|
24972
25860
|
return { duration: 0, resolvedPath: src };
|
|
24973
25861
|
}
|
|
24974
25862
|
} else if (!filePath.startsWith("/")) {
|
|
24975
|
-
filePath =
|
|
25863
|
+
filePath = join30(baseDir, filePath);
|
|
24976
25864
|
}
|
|
24977
|
-
if (!
|
|
25865
|
+
if (!existsSync27(filePath)) {
|
|
24978
25866
|
return { duration: 0, resolvedPath: filePath };
|
|
24979
25867
|
}
|
|
24980
25868
|
const metadata = tagName19 === "video" ? await extractVideoMetadata(filePath) : await extractAudioMetadata(filePath);
|
|
@@ -25042,7 +25930,7 @@ async function parseSubCompositions(html, projectDir, downloadDir, parentOffset
|
|
|
25042
25930
|
if (visited.has(filePath)) {
|
|
25043
25931
|
continue;
|
|
25044
25932
|
}
|
|
25045
|
-
if (!
|
|
25933
|
+
if (!existsSync27(filePath)) {
|
|
25046
25934
|
continue;
|
|
25047
25935
|
}
|
|
25048
25936
|
const rawSubHtml = readFileSync19(filePath, "utf-8");
|
|
@@ -25239,7 +26127,7 @@ function inlineSubCompositions(html, subCompositions, projectDir) {
|
|
|
25239
26127
|
let compHtml = subCompositions.get(srcPath) || null;
|
|
25240
26128
|
if (!compHtml) {
|
|
25241
26129
|
const filePath = resolve14(projectDir, srcPath);
|
|
25242
|
-
if (
|
|
26130
|
+
if (existsSync27(filePath)) {
|
|
25243
26131
|
compHtml = readFileSync19(filePath, "utf-8");
|
|
25244
26132
|
}
|
|
25245
26133
|
}
|
|
@@ -25463,7 +26351,7 @@ function collectExternalAssets(html, projectDir) {
|
|
|
25463
26351
|
if (isPathInside(absPath, absProjectDir)) {
|
|
25464
26352
|
return null;
|
|
25465
26353
|
}
|
|
25466
|
-
if (!
|
|
26354
|
+
if (!existsSync27(absPath)) return null;
|
|
25467
26355
|
const safeKey = toExternalAssetKey(absPath);
|
|
25468
26356
|
externalAssets.set(safeKey, absPath);
|
|
25469
26357
|
return safeKey;
|
|
@@ -25727,15 +26615,16 @@ var init_logger = __esm({
|
|
|
25727
26615
|
|
|
25728
26616
|
// ../producer/src/services/renderOrchestrator.ts
|
|
25729
26617
|
import {
|
|
25730
|
-
existsSync as
|
|
26618
|
+
existsSync as existsSync28,
|
|
25731
26619
|
mkdirSync as mkdirSync17,
|
|
25732
26620
|
rmSync as rmSync6,
|
|
25733
26621
|
readFileSync as readFileSync20,
|
|
26622
|
+
readdirSync as readdirSync11,
|
|
25734
26623
|
writeFileSync as writeFileSync10,
|
|
25735
26624
|
copyFileSync as copyFileSync2,
|
|
25736
26625
|
appendFileSync
|
|
25737
26626
|
} from "fs";
|
|
25738
|
-
import { join as
|
|
26627
|
+
import { join as join31, dirname as dirname10, resolve as resolve15 } from "path";
|
|
25739
26628
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
25740
26629
|
import { freemem as freemem2 } from "os";
|
|
25741
26630
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -25748,6 +26637,22 @@ async function safeCleanup(label2, fn, log = defaultLogger) {
|
|
|
25748
26637
|
});
|
|
25749
26638
|
}
|
|
25750
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
|
+
}
|
|
25751
26656
|
function updateJobStatus(job, status, stage, progress, onProgress) {
|
|
25752
26657
|
job.status = status;
|
|
25753
26658
|
job.currentStage = stage;
|
|
@@ -25791,16 +26696,16 @@ function installDebugLogger(logPath, log = defaultLogger) {
|
|
|
25791
26696
|
};
|
|
25792
26697
|
}
|
|
25793
26698
|
function writeCompiledArtifacts(compiled, workDir, includeSummary) {
|
|
25794
|
-
const compileDir =
|
|
26699
|
+
const compileDir = join31(workDir, "compiled");
|
|
25795
26700
|
mkdirSync17(compileDir, { recursive: true });
|
|
25796
|
-
writeFileSync10(
|
|
26701
|
+
writeFileSync10(join31(compileDir, "index.html"), compiled.html, "utf-8");
|
|
25797
26702
|
for (const [srcPath, html] of compiled.subCompositions) {
|
|
25798
|
-
const outPath =
|
|
26703
|
+
const outPath = join31(compileDir, srcPath);
|
|
25799
26704
|
mkdirSync17(dirname10(outPath), { recursive: true });
|
|
25800
26705
|
writeFileSync10(outPath, html, "utf-8");
|
|
25801
26706
|
}
|
|
25802
26707
|
for (const [relativePath, absolutePath] of compiled.externalAssets) {
|
|
25803
|
-
const outPath = resolve15(
|
|
26708
|
+
const outPath = resolve15(join31(compileDir, relativePath));
|
|
25804
26709
|
if (!isPathInside(outPath, compileDir)) {
|
|
25805
26710
|
console.warn(`[Render] Skipping external asset with unsafe path: ${relativePath}`);
|
|
25806
26711
|
continue;
|
|
@@ -25830,7 +26735,7 @@ function writeCompiledArtifacts(compiled, workDir, includeSummary) {
|
|
|
25830
26735
|
subCompositions: Array.from(compiled.subCompositions.keys()),
|
|
25831
26736
|
renderModeHints: compiled.renderModeHints
|
|
25832
26737
|
};
|
|
25833
|
-
writeFileSync10(
|
|
26738
|
+
writeFileSync10(join31(compileDir, "summary.json"), JSON.stringify(summary, null, 2), "utf-8");
|
|
25834
26739
|
}
|
|
25835
26740
|
}
|
|
25836
26741
|
function applyRenderModeHints(cfg, compiled, log = defaultLogger) {
|
|
@@ -25883,8 +26788,8 @@ function extractStandaloneEntryFromIndex(indexHtml, entryFile) {
|
|
|
25883
26788
|
async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSignal) {
|
|
25884
26789
|
const moduleDir = dirname10(fileURLToPath3(import.meta.url));
|
|
25885
26790
|
const producerRoot = process.env.PRODUCER_RENDERS_DIR ? resolve15(process.env.PRODUCER_RENDERS_DIR, "..") : resolve15(moduleDir, "../..");
|
|
25886
|
-
const debugDir =
|
|
25887
|
-
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}`);
|
|
25888
26793
|
const pipelineStart = Date.now();
|
|
25889
26794
|
const log = job.config.logger ?? defaultLogger;
|
|
25890
26795
|
let fileServer = null;
|
|
@@ -25892,7 +26797,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25892
26797
|
let lastBrowserConsole = [];
|
|
25893
26798
|
let restoreLogger = null;
|
|
25894
26799
|
const perfStages = {};
|
|
25895
|
-
const perfOutputPath =
|
|
26800
|
+
const perfOutputPath = join31(workDir, "perf-summary.json");
|
|
25896
26801
|
const cfg = { ...job.config.producerConfig ?? resolveConfig() };
|
|
25897
26802
|
const outputFormat = job.config.format ?? "mp4";
|
|
25898
26803
|
const isWebm = outputFormat === "webm";
|
|
@@ -25912,22 +26817,22 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25912
26817
|
};
|
|
25913
26818
|
job.startedAt = /* @__PURE__ */ new Date();
|
|
25914
26819
|
assertNotAborted();
|
|
25915
|
-
if (!
|
|
26820
|
+
if (!existsSync28(workDir)) mkdirSync17(workDir, { recursive: true });
|
|
25916
26821
|
if (job.config.debug) {
|
|
25917
|
-
const logPath =
|
|
26822
|
+
const logPath = join31(workDir, "render.log");
|
|
25918
26823
|
restoreLogger = installDebugLogger(logPath, log);
|
|
25919
26824
|
}
|
|
25920
26825
|
const entryFile = job.config.entryFile || "index.html";
|
|
25921
|
-
let htmlPath =
|
|
25922
|
-
if (!
|
|
26826
|
+
let htmlPath = join31(projectDir, entryFile);
|
|
26827
|
+
if (!existsSync28(htmlPath)) {
|
|
25923
26828
|
throw new Error(`Entry file not found: ${htmlPath}`);
|
|
25924
26829
|
}
|
|
25925
26830
|
assertNotAborted();
|
|
25926
26831
|
const rawEntry = readFileSync20(htmlPath, "utf-8");
|
|
25927
26832
|
if (entryFile !== "index.html" && rawEntry.trimStart().startsWith("<template")) {
|
|
25928
|
-
const wrapperPath =
|
|
25929
|
-
const projectIndexPath =
|
|
25930
|
-
if (!
|
|
26833
|
+
const wrapperPath = join31(workDir, "standalone-entry.html");
|
|
26834
|
+
const projectIndexPath = join31(projectDir, "index.html");
|
|
26835
|
+
if (!existsSync28(projectIndexPath)) {
|
|
25931
26836
|
throw new Error(
|
|
25932
26837
|
`Template entry file "${entryFile}" requires a project index.html to extract its render shell.`
|
|
25933
26838
|
);
|
|
@@ -25950,7 +26855,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25950
26855
|
const stage1Start = Date.now();
|
|
25951
26856
|
updateJobStatus(job, "preprocessing", "Compiling composition", 5, onProgress);
|
|
25952
26857
|
const compileStart = Date.now();
|
|
25953
|
-
let compiled = await compileForRender(projectDir, htmlPath,
|
|
26858
|
+
let compiled = await compileForRender(projectDir, htmlPath, join31(workDir, "downloads"));
|
|
25954
26859
|
assertNotAborted();
|
|
25955
26860
|
perfStages.compileOnlyMs = Date.now() - compileStart;
|
|
25956
26861
|
applyRenderModeHints(cfg, compiled, log);
|
|
@@ -25981,7 +26886,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25981
26886
|
reasons.push(`${compiled.unresolvedCompositions.length} unresolved composition(s)`);
|
|
25982
26887
|
fileServer = await createFileServer2({
|
|
25983
26888
|
projectDir,
|
|
25984
|
-
compiledDir:
|
|
26889
|
+
compiledDir: join31(workDir, "compiled"),
|
|
25985
26890
|
port: 0,
|
|
25986
26891
|
preHeadScripts: [VIRTUAL_TIME_SHIM]
|
|
25987
26892
|
});
|
|
@@ -25995,7 +26900,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
25995
26900
|
};
|
|
25996
26901
|
probeSession = await createCaptureSession(
|
|
25997
26902
|
fileServer.url,
|
|
25998
|
-
|
|
26903
|
+
join31(workDir, "probe"),
|
|
25999
26904
|
captureOpts,
|
|
26000
26905
|
null,
|
|
26001
26906
|
cfg
|
|
@@ -26027,7 +26932,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
26027
26932
|
compiled,
|
|
26028
26933
|
resolutions,
|
|
26029
26934
|
projectDir,
|
|
26030
|
-
|
|
26935
|
+
join31(workDir, "downloads")
|
|
26031
26936
|
);
|
|
26032
26937
|
assertNotAborted();
|
|
26033
26938
|
composition.videos = compiled.videos;
|
|
@@ -26139,7 +27044,10 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
26139
27044
|
}
|
|
26140
27045
|
}
|
|
26141
27046
|
}
|
|
26142
|
-
} 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
|
+
});
|
|
26143
27051
|
diagnostics.push("(Could not gather browser diagnostics \u2014 page may have crashed)");
|
|
26144
27052
|
}
|
|
26145
27053
|
const hint = diagnostics.length > 0 ? "\n\nDiagnostics:\n - " + diagnostics.join("\n - ") : "\n\nCheck that GSAP timelines are registered on window.__timelines.";
|
|
@@ -26162,12 +27070,30 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
26162
27070
|
const stage2Start = Date.now();
|
|
26163
27071
|
updateJobStatus(job, "preprocessing", "Extracting video frames", 10, onProgress);
|
|
26164
27072
|
let frameLookup = null;
|
|
26165
|
-
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
|
+
}
|
|
26166
27092
|
if (composition.videos.length > 0) {
|
|
26167
|
-
|
|
27093
|
+
extractionResult = await extractAllVideoFrames(
|
|
26168
27094
|
composition.videos,
|
|
26169
27095
|
projectDir,
|
|
26170
|
-
{ fps: job.config.fps, outputDir:
|
|
27096
|
+
{ fps: job.config.fps, outputDir: join31(workDir, "video-frames") },
|
|
26171
27097
|
abortSignal,
|
|
26172
27098
|
void 0,
|
|
26173
27099
|
compiledDir
|
|
@@ -26199,15 +27125,32 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
26199
27125
|
} else {
|
|
26200
27126
|
perfStages.videoExtractMs = Date.now() - stage2Start;
|
|
26201
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
|
+
}
|
|
26202
27145
|
const stage3Start = Date.now();
|
|
26203
27146
|
updateJobStatus(job, "preprocessing", "Processing audio tracks", 20, onProgress);
|
|
26204
|
-
const audioOutputPath =
|
|
27147
|
+
const audioOutputPath = join31(workDir, "audio.aac");
|
|
26205
27148
|
let hasAudio = false;
|
|
26206
27149
|
if (composition.audios.length > 0) {
|
|
26207
27150
|
const audioResult = await processCompositionAudio(
|
|
26208
27151
|
composition.audios,
|
|
26209
27152
|
projectDir,
|
|
26210
|
-
|
|
27153
|
+
join31(workDir, "audio-work"),
|
|
26211
27154
|
audioOutputPath,
|
|
26212
27155
|
job.duration,
|
|
26213
27156
|
abortSignal,
|
|
@@ -26225,14 +27168,14 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
26225
27168
|
if (!fileServer) {
|
|
26226
27169
|
fileServer = await createFileServer2({
|
|
26227
27170
|
projectDir,
|
|
26228
|
-
compiledDir:
|
|
27171
|
+
compiledDir: join31(workDir, "compiled"),
|
|
26229
27172
|
port: 0,
|
|
26230
27173
|
preHeadScripts: [VIRTUAL_TIME_SHIM]
|
|
26231
27174
|
});
|
|
26232
27175
|
assertNotAborted();
|
|
26233
27176
|
}
|
|
26234
|
-
const framesDir =
|
|
26235
|
-
if (!
|
|
27177
|
+
const framesDir = join31(workDir, "captured-frames");
|
|
27178
|
+
if (!existsSync28(framesDir)) mkdirSync17(framesDir, { recursive: true });
|
|
26236
27179
|
const captureOptions = {
|
|
26237
27180
|
width,
|
|
26238
27181
|
height,
|
|
@@ -26243,219 +27186,399 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
26243
27186
|
const workerCount = calculateOptimalWorkers(job.totalFrames, job.config.workers, cfg);
|
|
26244
27187
|
const FORMAT_EXT2 = { mp4: ".mp4", webm: ".webm", mov: ".mov" };
|
|
26245
27188
|
const videoExt = FORMAT_EXT2[outputFormat] ?? ".mp4";
|
|
26246
|
-
const videoOnlyPath =
|
|
26247
|
-
const
|
|
26248
|
-
const
|
|
26249
|
-
const
|
|
26250
|
-
const baseEncoderOpts = {
|
|
26251
|
-
fps: job.config.fps,
|
|
26252
|
-
width,
|
|
26253
|
-
height,
|
|
26254
|
-
codec: preset.codec,
|
|
26255
|
-
preset: preset.preset,
|
|
26256
|
-
quality: effectiveQuality,
|
|
26257
|
-
bitrate: effectiveBitrate,
|
|
26258
|
-
pixelFormat: preset.pixelFormat,
|
|
26259
|
-
useGpu: job.config.useGpu
|
|
26260
|
-
};
|
|
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);
|
|
26261
27193
|
job.framesRendered = 0;
|
|
26262
|
-
|
|
26263
|
-
|
|
26264
|
-
|
|
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(
|
|
26265
27219
|
videoOnlyPath,
|
|
26266
27220
|
{
|
|
26267
|
-
|
|
26268
|
-
|
|
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"
|
|
26269
27230
|
},
|
|
26270
|
-
abortSignal
|
|
27231
|
+
abortSignal,
|
|
27232
|
+
{ ffmpegStreamingTimeout: 36e5 }
|
|
26271
27233
|
);
|
|
26272
27234
|
assertNotAborted();
|
|
26273
|
-
|
|
26274
|
-
|
|
26275
|
-
const
|
|
26276
|
-
|
|
26277
|
-
|
|
26278
|
-
const
|
|
26279
|
-
|
|
26280
|
-
|
|
26281
|
-
|
|
26282
|
-
|
|
26283
|
-
|
|
26284
|
-
|
|
26285
|
-
|
|
26286
|
-
|
|
26287
|
-
|
|
26288
|
-
|
|
26289
|
-
|
|
26290
|
-
|
|
26291
|
-
|
|
26292
|
-
|
|
26293
|
-
|
|
26294
|
-
|
|
26295
|
-
|
|
26296
|
-
|
|
26297
|
-
|
|
26298
|
-
|
|
26299
|
-
|
|
26300
|
-
|
|
26301
|
-
|
|
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)
|
|
26302
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
|
+
}
|
|
26303
27339
|
}
|
|
26304
|
-
},
|
|
26305
|
-
onFrameBuffer,
|
|
26306
|
-
cfg
|
|
26307
|
-
);
|
|
26308
|
-
if (probeSession) {
|
|
26309
|
-
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
26310
|
-
await closeCaptureSession(probeSession);
|
|
26311
|
-
probeSession = null;
|
|
26312
|
-
}
|
|
26313
|
-
} else {
|
|
26314
|
-
const videoInjector = createVideoFrameInjector(frameLookup);
|
|
26315
|
-
const session = probeSession ?? await createCaptureSession(
|
|
26316
|
-
fileServer.url,
|
|
26317
|
-
framesDir,
|
|
26318
|
-
captureOptions,
|
|
26319
|
-
videoInjector,
|
|
26320
|
-
cfg
|
|
26321
|
-
);
|
|
26322
|
-
if (probeSession) {
|
|
26323
|
-
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
26324
|
-
probeSession = null;
|
|
26325
|
-
}
|
|
26326
|
-
try {
|
|
26327
|
-
if (!session.isInitialized) {
|
|
26328
|
-
await initializeSession(session);
|
|
26329
27340
|
}
|
|
26330
|
-
|
|
26331
|
-
|
|
26332
|
-
|
|
26333
|
-
assertNotAborted();
|
|
26334
|
-
const time = i2 / job.config.fps;
|
|
26335
|
-
const { buffer } = await captureFrameToBuffer(session, i2, time);
|
|
26336
|
-
await reorderBuffer.waitForFrame(i2);
|
|
26337
|
-
currentEncoder.writeFrame(buffer);
|
|
26338
|
-
reorderBuffer.advanceTo(i2 + 1);
|
|
26339
|
-
job.framesRendered = i2 + 1;
|
|
27341
|
+
hdrEncoder.writeFrame(canvas);
|
|
27342
|
+
job.framesRendered = i2 + 1;
|
|
27343
|
+
if ((i2 + 1) % 10 === 0 || i2 + 1 === job.totalFrames) {
|
|
26340
27344
|
const frameProgress = (i2 + 1) / job.totalFrames;
|
|
26341
|
-
const progress = 25 + frameProgress * 55;
|
|
26342
27345
|
updateJobStatus(
|
|
26343
27346
|
job,
|
|
26344
27347
|
"rendering",
|
|
26345
|
-
`
|
|
26346
|
-
Math.round(
|
|
27348
|
+
`HDR composite frame ${i2 + 1}/${job.totalFrames}`,
|
|
27349
|
+
Math.round(25 + frameProgress * 55),
|
|
26347
27350
|
onProgress
|
|
26348
27351
|
);
|
|
26349
27352
|
}
|
|
26350
|
-
} finally {
|
|
26351
|
-
lastBrowserConsole = session.browserConsoleBuffer;
|
|
26352
|
-
await closeCaptureSession(session);
|
|
26353
27353
|
}
|
|
27354
|
+
} finally {
|
|
27355
|
+
lastBrowserConsole = domSession.browserConsoleBuffer;
|
|
27356
|
+
await closeCaptureSession(domSession);
|
|
26354
27357
|
}
|
|
26355
|
-
const
|
|
27358
|
+
const hdrEncodeResult = await hdrEncoder.close();
|
|
26356
27359
|
assertNotAborted();
|
|
26357
|
-
if (!
|
|
26358
|
-
throw new Error(`
|
|
27360
|
+
if (!hdrEncodeResult.success) {
|
|
27361
|
+
throw new Error(`HDR encode failed: ${hdrEncodeResult.error}`);
|
|
26359
27362
|
}
|
|
26360
27363
|
perfStages.captureMs = Date.now() - stage4Start;
|
|
26361
|
-
perfStages.encodeMs =
|
|
27364
|
+
perfStages.encodeMs = hdrEncodeResult.durationMs;
|
|
26362
27365
|
} else {
|
|
26363
|
-
|
|
26364
|
-
|
|
26365
|
-
await
|
|
26366
|
-
|
|
26367
|
-
|
|
26368
|
-
|
|
26369
|
-
|
|
26370
|
-
|
|
26371
|
-
|
|
26372
|
-
|
|
26373
|
-
|
|
26374
|
-
|
|
26375
|
-
|
|
26376
|
-
|
|
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;
|
|
26377
27454
|
updateJobStatus(
|
|
26378
27455
|
job,
|
|
26379
27456
|
"rendering",
|
|
26380
|
-
`
|
|
26381
|
-
Math.round(
|
|
27457
|
+
`Streaming frame ${i2 + 1}/${job.totalFrames}`,
|
|
27458
|
+
Math.round(progress),
|
|
26382
27459
|
onProgress
|
|
26383
27460
|
);
|
|
26384
27461
|
}
|
|
26385
|
-
}
|
|
26386
|
-
|
|
26387
|
-
|
|
26388
|
-
|
|
26389
|
-
await mergeWorkerFrames(workDir, tasks, framesDir);
|
|
26390
|
-
if (probeSession) {
|
|
26391
|
-
lastBrowserConsole = probeSession.browserConsoleBuffer;
|
|
26392
|
-
await closeCaptureSession(probeSession);
|
|
26393
|
-
probeSession = null;
|
|
27462
|
+
} finally {
|
|
27463
|
+
lastBrowserConsole = session.browserConsoleBuffer;
|
|
27464
|
+
await closeCaptureSession(session);
|
|
27465
|
+
}
|
|
26394
27466
|
}
|
|
26395
|
-
|
|
26396
|
-
|
|
26397
|
-
|
|
26398
|
-
|
|
26399
|
-
framesDir,
|
|
26400
|
-
captureOptions,
|
|
26401
|
-
videoInjector,
|
|
26402
|
-
cfg
|
|
26403
|
-
);
|
|
26404
|
-
if (probeSession) {
|
|
26405
|
-
prepareCaptureSessionForReuse(session, framesDir, videoInjector);
|
|
26406
|
-
probeSession = null;
|
|
27467
|
+
const encodeResult = await currentEncoder.close();
|
|
27468
|
+
assertNotAborted();
|
|
27469
|
+
if (!encodeResult.success) {
|
|
27470
|
+
throw new Error(`Streaming encode failed: ${encodeResult.error}`);
|
|
26407
27471
|
}
|
|
26408
|
-
|
|
26409
|
-
|
|
26410
|
-
|
|
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;
|
|
26411
27506
|
}
|
|
26412
|
-
|
|
26413
|
-
|
|
26414
|
-
|
|
26415
|
-
|
|
26416
|
-
|
|
26417
|
-
|
|
26418
|
-
|
|
26419
|
-
|
|
26420
|
-
|
|
26421
|
-
|
|
26422
|
-
|
|
26423
|
-
|
|
26424
|
-
`Capturing frame ${i2 + 1}/${job.totalFrames}`,
|
|
26425
|
-
Math.round(progress),
|
|
26426
|
-
onProgress
|
|
26427
|
-
);
|
|
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;
|
|
26428
27519
|
}
|
|
26429
|
-
|
|
26430
|
-
|
|
26431
|
-
|
|
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}`);
|
|
26432
27579
|
}
|
|
27580
|
+
perfStages.encodeMs = Date.now() - stage5Start;
|
|
26433
27581
|
}
|
|
26434
|
-
perfStages.captureMs = Date.now() - stage4Start;
|
|
26435
|
-
const stage5Start = Date.now();
|
|
26436
|
-
updateJobStatus(job, "encoding", "Encoding video", 75, onProgress);
|
|
26437
|
-
const frameExt = needsAlpha ? "png" : "jpg";
|
|
26438
|
-
const framePattern = `frame_%06d.${frameExt}`;
|
|
26439
|
-
const encoderOpts = baseEncoderOpts;
|
|
26440
|
-
const encodeResult = enableChunkedEncode ? await encodeFramesChunkedConcat(
|
|
26441
|
-
framesDir,
|
|
26442
|
-
framePattern,
|
|
26443
|
-
videoOnlyPath,
|
|
26444
|
-
encoderOpts,
|
|
26445
|
-
chunkedEncodeSize,
|
|
26446
|
-
abortSignal
|
|
26447
|
-
) : await encodeFramesFromDir(
|
|
26448
|
-
framesDir,
|
|
26449
|
-
framePattern,
|
|
26450
|
-
videoOnlyPath,
|
|
26451
|
-
encoderOpts,
|
|
26452
|
-
abortSignal
|
|
26453
|
-
);
|
|
26454
|
-
assertNotAborted();
|
|
26455
|
-
if (!encodeResult.success) {
|
|
26456
|
-
throw new Error(`Encoding failed: ${encodeResult.error}`);
|
|
26457
|
-
}
|
|
26458
|
-
perfStages.encodeMs = Date.now() - stage5Start;
|
|
26459
27582
|
}
|
|
26460
27583
|
if (probeSession !== null) {
|
|
26461
27584
|
const remainingProbeSession = probeSession;
|
|
@@ -26518,8 +27641,8 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
26518
27641
|
}
|
|
26519
27642
|
}
|
|
26520
27643
|
if (job.config.debug) {
|
|
26521
|
-
if (
|
|
26522
|
-
const debugOutput =
|
|
27644
|
+
if (existsSync28(outputPath)) {
|
|
27645
|
+
const debugOutput = join31(workDir, `output${videoExt}`);
|
|
26523
27646
|
copyFileSync2(outputPath, debugOutput);
|
|
26524
27647
|
}
|
|
26525
27648
|
} else {
|
|
@@ -26602,7 +27725,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
26602
27725
|
await safeCleanup(
|
|
26603
27726
|
"remove workDir (error)",
|
|
26604
27727
|
() => {
|
|
26605
|
-
if (
|
|
27728
|
+
if (existsSync28(workDir)) rmSync6(workDir, { recursive: true, force: true });
|
|
26606
27729
|
},
|
|
26607
27730
|
log
|
|
26608
27731
|
);
|
|
@@ -26611,7 +27734,7 @@ async function executeRenderJob(job, projectDir, outputPath, onProgress, abortSi
|
|
|
26611
27734
|
throw error;
|
|
26612
27735
|
}
|
|
26613
27736
|
}
|
|
26614
|
-
var RenderCancelledError;
|
|
27737
|
+
var frameDirMaxIndexCache, FRAME_FILENAME_RE, RenderCancelledError;
|
|
26615
27738
|
var init_renderOrchestrator = __esm({
|
|
26616
27739
|
"../producer/src/services/renderOrchestrator.ts"() {
|
|
26617
27740
|
"use strict";
|
|
@@ -26621,6 +27744,8 @@ var init_renderOrchestrator = __esm({
|
|
|
26621
27744
|
init_htmlCompiler2();
|
|
26622
27745
|
init_logger();
|
|
26623
27746
|
init_paths();
|
|
27747
|
+
frameDirMaxIndexCache = /* @__PURE__ */ new Map();
|
|
27748
|
+
FRAME_FILENAME_RE = /^frame_(\d+)\.png$/;
|
|
26624
27749
|
RenderCancelledError = class extends Error {
|
|
26625
27750
|
reason;
|
|
26626
27751
|
constructor(message = "render_cancelled", reason = "aborted") {
|
|
@@ -26657,8 +27782,8 @@ var init_config3 = __esm({
|
|
|
26657
27782
|
});
|
|
26658
27783
|
|
|
26659
27784
|
// ../producer/src/services/hyperframeLint.ts
|
|
26660
|
-
import { existsSync as
|
|
26661
|
-
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";
|
|
26662
27787
|
function isStringRecord(value) {
|
|
26663
27788
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
26664
27789
|
return false;
|
|
@@ -26686,7 +27811,7 @@ function pickEntryFile(files, preferredEntryFile) {
|
|
|
26686
27811
|
}
|
|
26687
27812
|
function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
26688
27813
|
const absProjectDir = resolve16(projectDir);
|
|
26689
|
-
if (!
|
|
27814
|
+
if (!existsSync29(absProjectDir) || !statSync8(absProjectDir).isDirectory()) {
|
|
26690
27815
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
26691
27816
|
}
|
|
26692
27817
|
const entryCandidates = [preferredEntryFile, "index.html", "src/index.html"].filter(
|
|
@@ -26697,7 +27822,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
26697
27822
|
if (!absoluteEntryPath.startsWith(absProjectDir)) {
|
|
26698
27823
|
return { error: `Entry file must stay inside project directory: ${entryFile}` };
|
|
26699
27824
|
}
|
|
26700
|
-
if (
|
|
27825
|
+
if (existsSync29(absoluteEntryPath) && statSync8(absoluteEntryPath).isFile()) {
|
|
26701
27826
|
return {
|
|
26702
27827
|
entryFile,
|
|
26703
27828
|
html: readFileSync21(absoluteEntryPath, "utf-8"),
|
|
@@ -26706,7 +27831,7 @@ function readProjectEntryFile(projectDir, preferredEntryFile) {
|
|
|
26706
27831
|
}
|
|
26707
27832
|
}
|
|
26708
27833
|
return {
|
|
26709
|
-
error: `No HTML entry file found in project directory: ${
|
|
27834
|
+
error: `No HTML entry file found in project directory: ${join32(absProjectDir, preferredEntryFile || "index.html")}`
|
|
26710
27835
|
};
|
|
26711
27836
|
}
|
|
26712
27837
|
function prepareHyperframeLintBody(body) {
|
|
@@ -26794,7 +27919,7 @@ var init_semaphore = __esm({
|
|
|
26794
27919
|
|
|
26795
27920
|
// ../producer/src/server.ts
|
|
26796
27921
|
import {
|
|
26797
|
-
existsSync as
|
|
27922
|
+
existsSync as existsSync30,
|
|
26798
27923
|
mkdirSync as mkdirSync18,
|
|
26799
27924
|
statSync as statSync9,
|
|
26800
27925
|
mkdtempSync,
|
|
@@ -26802,7 +27927,7 @@ import {
|
|
|
26802
27927
|
rmSync as rmSync7,
|
|
26803
27928
|
createReadStream
|
|
26804
27929
|
} from "fs";
|
|
26805
|
-
import { resolve as resolve17, dirname as dirname11, join as
|
|
27930
|
+
import { resolve as resolve17, dirname as dirname11, join as join33 } from "path";
|
|
26806
27931
|
import { tmpdir as tmpdir2 } from "os";
|
|
26807
27932
|
import { parseArgs as parseArgs2 } from "util";
|
|
26808
27933
|
import crypto2 from "crypto";
|
|
@@ -26825,11 +27950,11 @@ async function prepareRenderBody(body) {
|
|
|
26825
27950
|
const projectDir = typeof body.projectDir === "string" ? body.projectDir : void 0;
|
|
26826
27951
|
if (projectDir) {
|
|
26827
27952
|
const absProjectDir = resolve17(projectDir);
|
|
26828
|
-
if (!
|
|
27953
|
+
if (!existsSync30(absProjectDir) || !statSync9(absProjectDir).isDirectory()) {
|
|
26829
27954
|
return { error: `Project directory not found: ${absProjectDir}` };
|
|
26830
27955
|
}
|
|
26831
27956
|
const entry = options.entryFile || "index.html";
|
|
26832
|
-
if (!
|
|
27957
|
+
if (!existsSync30(resolve17(absProjectDir, entry))) {
|
|
26833
27958
|
return { error: `Entry file "${entry}" not found in project directory: ${absProjectDir}` };
|
|
26834
27959
|
}
|
|
26835
27960
|
return { prepared: { input: { projectDir: absProjectDir, ...options } } };
|
|
@@ -26854,8 +27979,8 @@ async function prepareRenderBody(body) {
|
|
|
26854
27979
|
}
|
|
26855
27980
|
}
|
|
26856
27981
|
const tempRoot = process.env.PRODUCER_TMP_PROJECT_DIR || tmpdir2();
|
|
26857
|
-
const tempProjectDir = mkdtempSync(
|
|
26858
|
-
writeFileSync11(
|
|
27982
|
+
const tempProjectDir = mkdtempSync(join33(tempRoot, "producer-project-"));
|
|
27983
|
+
writeFileSync11(join33(tempProjectDir, "index.html"), htmlContent, "utf-8");
|
|
26859
27984
|
return {
|
|
26860
27985
|
prepared: {
|
|
26861
27986
|
input: {
|
|
@@ -26978,7 +28103,7 @@ function createRenderHandlers(options = {}) {
|
|
|
26978
28103
|
log
|
|
26979
28104
|
);
|
|
26980
28105
|
const outputDir = dirname11(absoluteOutputPath);
|
|
26981
|
-
if (!
|
|
28106
|
+
if (!existsSync30(outputDir)) mkdirSync18(outputDir, { recursive: true });
|
|
26982
28107
|
const release2 = await renderSemaphore.acquire();
|
|
26983
28108
|
log.info("render started", {
|
|
26984
28109
|
requestId,
|
|
@@ -27005,7 +28130,7 @@ function createRenderHandlers(options = {}) {
|
|
|
27005
28130
|
log.info(`render progress ${pct}%`, { requestId, stage: j2.currentStage, message });
|
|
27006
28131
|
}
|
|
27007
28132
|
});
|
|
27008
|
-
const fileSize =
|
|
28133
|
+
const fileSize = existsSync30(absoluteOutputPath) ? statSync9(absoluteOutputPath).size : 0;
|
|
27009
28134
|
const durationMs = Date.now() - t0;
|
|
27010
28135
|
const outputToken = store.register(absoluteOutputPath);
|
|
27011
28136
|
const outputUrl = `${outputUrlPrefix}/${outputToken}`;
|
|
@@ -27089,7 +28214,7 @@ function createRenderHandlers(options = {}) {
|
|
|
27089
28214
|
log
|
|
27090
28215
|
);
|
|
27091
28216
|
const outputDir = dirname11(absoluteOutputPath);
|
|
27092
|
-
if (!
|
|
28217
|
+
if (!existsSync30(outputDir)) mkdirSync18(outputDir, { recursive: true });
|
|
27093
28218
|
log.info("render-stream started", { requestId, projectDir: input.projectDir });
|
|
27094
28219
|
const job = createRenderJob({
|
|
27095
28220
|
fps: input.fps,
|
|
@@ -27134,7 +28259,7 @@ function createRenderHandlers(options = {}) {
|
|
|
27134
28259
|
},
|
|
27135
28260
|
abortController.signal
|
|
27136
28261
|
);
|
|
27137
|
-
const fileSize =
|
|
28262
|
+
const fileSize = existsSync30(absoluteOutputPath) ? statSync9(absoluteOutputPath).size : 0;
|
|
27138
28263
|
const outputToken = store.register(absoluteOutputPath);
|
|
27139
28264
|
const outputUrl = `${outputUrlPrefix}/${outputToken}`;
|
|
27140
28265
|
log.info("render-stream completed", { requestId, fileSize, perf: job.perfSummary ?? null });
|
|
@@ -27193,7 +28318,7 @@ function createRenderHandlers(options = {}) {
|
|
|
27193
28318
|
if (!artifact) {
|
|
27194
28319
|
return c2.json({ success: false, error: "Output artifact not found or expired" }, 404);
|
|
27195
28320
|
}
|
|
27196
|
-
if (!
|
|
28321
|
+
if (!existsSync30(artifact.path)) {
|
|
27197
28322
|
store.delete(token);
|
|
27198
28323
|
return c2.json({ success: false, error: "Output artifact file missing" }, 404);
|
|
27199
28324
|
}
|
|
@@ -27330,18 +28455,18 @@ __export(studioServer_exports, {
|
|
|
27330
28455
|
});
|
|
27331
28456
|
import { Hono as Hono5 } from "hono";
|
|
27332
28457
|
import { streamSSE as streamSSE3 } from "hono/streaming";
|
|
27333
|
-
import { existsSync as
|
|
27334
|
-
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";
|
|
27335
28460
|
function resolveDistDir() {
|
|
27336
28461
|
const builtPath = resolve18(__dirname, "studio");
|
|
27337
|
-
if (
|
|
28462
|
+
if (existsSync31(resolve18(builtPath, "index.html"))) return builtPath;
|
|
27338
28463
|
const devPath = resolve18(__dirname, "..", "..", "..", "studio", "dist");
|
|
27339
|
-
if (
|
|
28464
|
+
if (existsSync31(resolve18(devPath, "index.html"))) return devPath;
|
|
27340
28465
|
return builtPath;
|
|
27341
28466
|
}
|
|
27342
28467
|
function resolveRuntimePath() {
|
|
27343
28468
|
const builtPath = resolve18(__dirname, "hyperframe-runtime.js");
|
|
27344
|
-
if (
|
|
28469
|
+
if (existsSync31(builtPath)) return builtPath;
|
|
27345
28470
|
const devPath = resolve18(
|
|
27346
28471
|
__dirname,
|
|
27347
28472
|
"..",
|
|
@@ -27351,7 +28476,7 @@ function resolveRuntimePath() {
|
|
|
27351
28476
|
"dist",
|
|
27352
28477
|
"hyperframe.runtime.iife.js"
|
|
27353
28478
|
);
|
|
27354
|
-
if (
|
|
28479
|
+
if (existsSync31(devPath)) return devPath;
|
|
27355
28480
|
return builtPath;
|
|
27356
28481
|
}
|
|
27357
28482
|
async function getThumbnailBrowser() {
|
|
@@ -27413,7 +28538,7 @@ function createStudioServer(options) {
|
|
|
27413
28538
|
return lintHyperframeHtml2(html, opts);
|
|
27414
28539
|
},
|
|
27415
28540
|
runtimeUrl: "/api/runtime.js",
|
|
27416
|
-
rendersDir: () =>
|
|
28541
|
+
rendersDir: () => join34(projectDir, "renders"),
|
|
27417
28542
|
startRender(opts) {
|
|
27418
28543
|
const state = {
|
|
27419
28544
|
id: opts.jobId,
|
|
@@ -27503,7 +28628,7 @@ function createStudioServer(options) {
|
|
|
27503
28628
|
});
|
|
27504
28629
|
});
|
|
27505
28630
|
app.get("/api/runtime.js", (c2) => {
|
|
27506
|
-
if (!
|
|
28631
|
+
if (!existsSync31(runtimePath)) return c2.text("runtime not built", 404);
|
|
27507
28632
|
return c2.body(readFileSync22(runtimePath, "utf-8"), 200, {
|
|
27508
28633
|
"Content-Type": "text/javascript",
|
|
27509
28634
|
"Cache-Control": "no-store"
|
|
@@ -27536,7 +28661,7 @@ function createStudioServer(options) {
|
|
|
27536
28661
|
});
|
|
27537
28662
|
app.get("/assets/*", (c2) => {
|
|
27538
28663
|
const filePath = resolve18(studioDir, c2.req.path.slice(1));
|
|
27539
|
-
if (!
|
|
28664
|
+
if (!existsSync31(filePath) || !statSync10(filePath).isFile()) return c2.text("not found", 404);
|
|
27540
28665
|
const content = readFileSync22(filePath);
|
|
27541
28666
|
return new Response(content, {
|
|
27542
28667
|
headers: { "Content-Type": getMimeType(filePath), "Cache-Control": "no-store" }
|
|
@@ -27544,7 +28669,7 @@ function createStudioServer(options) {
|
|
|
27544
28669
|
});
|
|
27545
28670
|
app.get("/icons/*", (c2) => {
|
|
27546
28671
|
const filePath = resolve18(studioDir, c2.req.path.slice(1));
|
|
27547
|
-
if (!
|
|
28672
|
+
if (!existsSync31(filePath) || !statSync10(filePath).isFile()) return c2.text("not found", 404);
|
|
27548
28673
|
const content = readFileSync22(filePath);
|
|
27549
28674
|
return new Response(content, {
|
|
27550
28675
|
headers: { "Content-Type": getMimeType(filePath), "Cache-Control": "no-store" }
|
|
@@ -27552,7 +28677,7 @@ function createStudioServer(options) {
|
|
|
27552
28677
|
});
|
|
27553
28678
|
app.get("*", (c2) => {
|
|
27554
28679
|
const indexPath = resolve18(studioDir, "index.html");
|
|
27555
|
-
if (!
|
|
28680
|
+
if (!existsSync31(indexPath)) {
|
|
27556
28681
|
return c2.text("Studio not found. Rebuild with: pnpm run build", 500);
|
|
27557
28682
|
}
|
|
27558
28683
|
return c2.html(readFileSync22(indexPath, "utf-8"));
|
|
@@ -27578,20 +28703,20 @@ __export(preview_exports, {
|
|
|
27578
28703
|
examples: () => examples
|
|
27579
28704
|
});
|
|
27580
28705
|
import { spawn as spawn8 } from "child_process";
|
|
27581
|
-
import { existsSync as
|
|
27582
|
-
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";
|
|
27583
28708
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
27584
28709
|
import { createRequire } from "module";
|
|
27585
28710
|
async function runDevMode(dir, projectName) {
|
|
27586
28711
|
const thisFile = fileURLToPath4(import.meta.url);
|
|
27587
28712
|
const repoRoot = resolve19(dirname12(thisFile), "..", "..", "..", "..");
|
|
27588
|
-
const projectsDir =
|
|
28713
|
+
const projectsDir = join35(repoRoot, "packages", "studio", "data", "projects");
|
|
27589
28714
|
const pName = projectName ?? basename3(dir);
|
|
27590
|
-
const symlinkPath =
|
|
28715
|
+
const symlinkPath = join35(projectsDir, pName);
|
|
27591
28716
|
mkdirSync19(projectsDir, { recursive: true });
|
|
27592
28717
|
let createdSymlink = false;
|
|
27593
28718
|
if (dir !== symlinkPath) {
|
|
27594
|
-
if (
|
|
28719
|
+
if (existsSync32(symlinkPath)) {
|
|
27595
28720
|
try {
|
|
27596
28721
|
const stat3 = lstatSync(symlinkPath);
|
|
27597
28722
|
if (stat3.isSymbolicLink()) {
|
|
@@ -27603,7 +28728,7 @@ async function runDevMode(dir, projectName) {
|
|
|
27603
28728
|
} catch {
|
|
27604
28729
|
}
|
|
27605
28730
|
}
|
|
27606
|
-
if (!
|
|
28731
|
+
if (!existsSync32(symlinkPath)) {
|
|
27607
28732
|
symlinkSync(dir, symlinkPath, "dir");
|
|
27608
28733
|
createdSymlink = true;
|
|
27609
28734
|
}
|
|
@@ -27611,7 +28736,7 @@ async function runDevMode(dir, projectName) {
|
|
|
27611
28736
|
Wt2(c.bold("hyperframes preview"));
|
|
27612
28737
|
const s2 = be();
|
|
27613
28738
|
s2.start("Starting studio...");
|
|
27614
|
-
const studioPkgDir =
|
|
28739
|
+
const studioPkgDir = join35(repoRoot, "packages", "studio");
|
|
27615
28740
|
const child = spawn8("pnpm", ["exec", "vite"], {
|
|
27616
28741
|
cwd: studioPkgDir,
|
|
27617
28742
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -27645,7 +28770,7 @@ async function runDevMode(dir, projectName) {
|
|
|
27645
28770
|
if (createdSymlink) {
|
|
27646
28771
|
process.on("exit", () => {
|
|
27647
28772
|
try {
|
|
27648
|
-
if (
|
|
28773
|
+
if (existsSync32(symlinkPath)) unlinkSync5(symlinkPath);
|
|
27649
28774
|
} catch {
|
|
27650
28775
|
}
|
|
27651
28776
|
});
|
|
@@ -27656,7 +28781,7 @@ async function runDevMode(dir, projectName) {
|
|
|
27656
28781
|
}
|
|
27657
28782
|
function hasLocalStudio(dir) {
|
|
27658
28783
|
try {
|
|
27659
|
-
const req = createRequire(
|
|
28784
|
+
const req = createRequire(join35(dir, "package.json"));
|
|
27660
28785
|
req.resolve("@hyperframes/studio/package.json");
|
|
27661
28786
|
return true;
|
|
27662
28787
|
} catch {
|
|
@@ -27664,20 +28789,20 @@ function hasLocalStudio(dir) {
|
|
|
27664
28789
|
}
|
|
27665
28790
|
}
|
|
27666
28791
|
async function runLocalStudioMode(dir, projectName) {
|
|
27667
|
-
const req = createRequire(
|
|
28792
|
+
const req = createRequire(join35(dir, "package.json"));
|
|
27668
28793
|
const studioPkgPath = dirname12(req.resolve("@hyperframes/studio/package.json"));
|
|
27669
28794
|
const pName = projectName ?? basename3(dir);
|
|
27670
|
-
const projectsDir =
|
|
27671
|
-
const symlinkPath =
|
|
28795
|
+
const projectsDir = join35(studioPkgPath, "data", "projects");
|
|
28796
|
+
const symlinkPath = join35(projectsDir, pName);
|
|
27672
28797
|
mkdirSync19(projectsDir, { recursive: true });
|
|
27673
28798
|
let createdSymlink = false;
|
|
27674
28799
|
if (dir !== symlinkPath) {
|
|
27675
|
-
if (
|
|
28800
|
+
if (existsSync32(symlinkPath) && lstatSync(symlinkPath).isSymbolicLink()) {
|
|
27676
28801
|
if (resolve19(readlinkSync(symlinkPath)) !== resolve19(dir)) {
|
|
27677
28802
|
unlinkSync5(symlinkPath);
|
|
27678
28803
|
}
|
|
27679
28804
|
}
|
|
27680
|
-
if (!
|
|
28805
|
+
if (!existsSync32(symlinkPath)) {
|
|
27681
28806
|
symlinkSync(dir, symlinkPath, "dir");
|
|
27682
28807
|
createdSymlink = true;
|
|
27683
28808
|
}
|
|
@@ -27716,7 +28841,7 @@ async function runLocalStudioMode(dir, projectName) {
|
|
|
27716
28841
|
if (createdSymlink) {
|
|
27717
28842
|
process.on("exit", () => {
|
|
27718
28843
|
try {
|
|
27719
|
-
if (
|
|
28844
|
+
if (existsSync32(symlinkPath)) unlinkSync5(symlinkPath);
|
|
27720
28845
|
} catch {
|
|
27721
28846
|
}
|
|
27722
28847
|
});
|
|
@@ -27856,8 +28981,8 @@ var init_preview2 = __esm({
|
|
|
27856
28981
|
const dir = resolve19(rawArg ?? ".");
|
|
27857
28982
|
const isImplicitCwd = !rawArg || rawArg === "." || rawArg === "./";
|
|
27858
28983
|
const projectName = isImplicitCwd ? basename3(process.env.PWD ?? dir) : basename3(dir);
|
|
27859
|
-
const indexPath =
|
|
27860
|
-
if (
|
|
28984
|
+
const indexPath = join35(dir, "index.html");
|
|
28985
|
+
if (existsSync32(indexPath)) {
|
|
27861
28986
|
const project = { dir, name: projectName, indexPath };
|
|
27862
28987
|
const lintResult = lintProject(project);
|
|
27863
28988
|
if (lintResult.totalErrors > 0 || lintResult.totalWarnings > 0) {
|
|
@@ -27886,15 +29011,15 @@ __export(init_exports, {
|
|
|
27886
29011
|
examples: () => examples2
|
|
27887
29012
|
});
|
|
27888
29013
|
import {
|
|
27889
|
-
existsSync as
|
|
29014
|
+
existsSync as existsSync33,
|
|
27890
29015
|
mkdirSync as mkdirSync20,
|
|
27891
29016
|
copyFileSync as copyFileSync3,
|
|
27892
29017
|
cpSync,
|
|
27893
29018
|
writeFileSync as writeFileSync13,
|
|
27894
29019
|
readFileSync as readFileSync23,
|
|
27895
|
-
readdirSync as
|
|
29020
|
+
readdirSync as readdirSync12
|
|
27896
29021
|
} from "fs";
|
|
27897
|
-
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";
|
|
27898
29023
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
27899
29024
|
import { execFileSync as execFileSync4, spawn as spawn9 } from "child_process";
|
|
27900
29025
|
function probeVideo(filePath) {
|
|
@@ -27966,7 +29091,7 @@ function resolveAssetDir(devSegments, builtSegments) {
|
|
|
27966
29091
|
const base = dirname13(fileURLToPath5(import.meta.url));
|
|
27967
29092
|
const devPath = resolve20(base, ...devSegments);
|
|
27968
29093
|
const builtPath = resolve20(base, ...builtSegments);
|
|
27969
|
-
return
|
|
29094
|
+
return existsSync33(devPath) ? devPath : builtPath;
|
|
27970
29095
|
}
|
|
27971
29096
|
function getStaticTemplateDir(templateId) {
|
|
27972
29097
|
return resolveAssetDir(["..", "templates", templateId], ["templates", templateId]);
|
|
@@ -27975,7 +29100,7 @@ function getSharedTemplateDir() {
|
|
|
27975
29100
|
return resolveAssetDir(["..", "templates", "_shared"], ["templates", "_shared"]);
|
|
27976
29101
|
}
|
|
27977
29102
|
function patchVideoSrc(dir, videoFilename, durationSeconds) {
|
|
27978
|
-
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));
|
|
27979
29104
|
for (const file of htmlFiles) {
|
|
27980
29105
|
let content = readFileSync23(file, "utf-8");
|
|
27981
29106
|
if (videoFilename) {
|
|
@@ -28082,7 +29207,7 @@ async function handleVideoFile(videoPath, destDir, interactive) {
|
|
|
28082
29207
|
async function scaffoldProject(destDir, name, templateId, localVideoName, durationSeconds) {
|
|
28083
29208
|
mkdirSync20(destDir, { recursive: true });
|
|
28084
29209
|
const templateDir = getStaticTemplateDir(templateId);
|
|
28085
|
-
if (
|
|
29210
|
+
if (existsSync33(templateDir)) {
|
|
28086
29211
|
cpSync(templateDir, destDir, { recursive: true });
|
|
28087
29212
|
} else {
|
|
28088
29213
|
await fetchRemoteTemplate(templateId, destDir);
|
|
@@ -28101,14 +29226,14 @@ async function scaffoldProject(destDir, name, templateId, localVideoName, durati
|
|
|
28101
29226
|
),
|
|
28102
29227
|
"utf-8"
|
|
28103
29228
|
);
|
|
28104
|
-
if (!
|
|
29229
|
+
if (!existsSync33(resolve20(destDir, "hyperframes.json"))) {
|
|
28105
29230
|
const { writeProjectConfig: writeProjectConfig2, DEFAULT_PROJECT_CONFIG: DEFAULT_PROJECT_CONFIG2 } = await Promise.resolve().then(() => (init_projectConfig(), projectConfig_exports));
|
|
28106
29231
|
writeProjectConfig2(destDir, DEFAULT_PROJECT_CONFIG2);
|
|
28107
29232
|
}
|
|
28108
29233
|
const sharedDir = getSharedTemplateDir();
|
|
28109
|
-
if (
|
|
28110
|
-
for (const entry of
|
|
28111
|
-
const src =
|
|
29234
|
+
if (existsSync33(sharedDir)) {
|
|
29235
|
+
for (const entry of readdirSync12(sharedDir, { withFileTypes: true })) {
|
|
29236
|
+
const src = join36(sharedDir, entry.name);
|
|
28112
29237
|
const dest = resolve20(destDir, entry.name);
|
|
28113
29238
|
if (entry.isFile() || entry.isSymbolicLink()) {
|
|
28114
29239
|
copyFileSync3(src, dest);
|
|
@@ -28222,7 +29347,7 @@ var init_init = __esm({
|
|
|
28222
29347
|
const templateId2 = exampleFlag ?? "blank";
|
|
28223
29348
|
const name2 = args.name ?? "my-video";
|
|
28224
29349
|
const destDir2 = resolve20(name2);
|
|
28225
|
-
if (
|
|
29350
|
+
if (existsSync33(destDir2) && readdirSync12(destDir2).length > 0) {
|
|
28226
29351
|
console.error(c.error(`Directory already exists and is not empty: ${name2}`));
|
|
28227
29352
|
process.exit(1);
|
|
28228
29353
|
}
|
|
@@ -28236,7 +29361,7 @@ var init_init = __esm({
|
|
|
28236
29361
|
}
|
|
28237
29362
|
if (videoFlag) {
|
|
28238
29363
|
const videoPath = resolve20(videoFlag);
|
|
28239
|
-
if (!
|
|
29364
|
+
if (!existsSync33(videoPath)) {
|
|
28240
29365
|
console.error(c.error(`Video file not found: ${videoFlag}`));
|
|
28241
29366
|
process.exit(1);
|
|
28242
29367
|
}
|
|
@@ -28250,7 +29375,7 @@ var init_init = __esm({
|
|
|
28250
29375
|
}
|
|
28251
29376
|
if (audioFlag) {
|
|
28252
29377
|
const audioPath = resolve20(audioFlag);
|
|
28253
|
-
if (!
|
|
29378
|
+
if (!existsSync33(audioPath)) {
|
|
28254
29379
|
console.error(c.error(`Audio file not found: ${audioFlag}`));
|
|
28255
29380
|
process.exit(1);
|
|
28256
29381
|
}
|
|
@@ -28296,11 +29421,11 @@ var init_init = __esm({
|
|
|
28296
29421
|
}
|
|
28297
29422
|
trackInitTemplate(templateId2);
|
|
28298
29423
|
const transcriptFile2 = resolve20(destDir2, "transcript.json");
|
|
28299
|
-
if (
|
|
29424
|
+
if (existsSync33(transcriptFile2)) {
|
|
28300
29425
|
await patchTranscript(destDir2, transcriptFile2);
|
|
28301
29426
|
}
|
|
28302
29427
|
console.log(c.success(`Created ${c.accent(name2 + "/")}`));
|
|
28303
|
-
for (const f3 of
|
|
29428
|
+
for (const f3 of readdirSync12(destDir2).filter((f4) => !f4.startsWith("."))) {
|
|
28304
29429
|
console.log(` ${c.accent(f3)}`);
|
|
28305
29430
|
}
|
|
28306
29431
|
console.log();
|
|
@@ -28348,7 +29473,7 @@ var init_init = __esm({
|
|
|
28348
29473
|
name = nameResult;
|
|
28349
29474
|
}
|
|
28350
29475
|
const destDir = resolve20(name);
|
|
28351
|
-
if (
|
|
29476
|
+
if (existsSync33(destDir) && readdirSync12(destDir).length > 0) {
|
|
28352
29477
|
const overwrite = await Rt({
|
|
28353
29478
|
message: `Directory ${c.accent(name)} already exists and is not empty. Overwrite?`,
|
|
28354
29479
|
initialValue: false
|
|
@@ -28363,7 +29488,7 @@ var init_init = __esm({
|
|
|
28363
29488
|
let videoDuration;
|
|
28364
29489
|
if (videoFlag) {
|
|
28365
29490
|
const videoPath = resolve20(videoFlag);
|
|
28366
|
-
if (!
|
|
29491
|
+
if (!existsSync33(videoPath)) {
|
|
28367
29492
|
R2.error(`File not found: ${videoFlag}`);
|
|
28368
29493
|
Nt("Setup cancelled.");
|
|
28369
29494
|
process.exit(1);
|
|
@@ -28375,7 +29500,7 @@ var init_init = __esm({
|
|
|
28375
29500
|
videoDuration = result.meta.durationSeconds;
|
|
28376
29501
|
} else if (audioFlag) {
|
|
28377
29502
|
const audioPath = resolve20(audioFlag);
|
|
28378
|
-
if (!
|
|
29503
|
+
if (!existsSync33(audioPath)) {
|
|
28379
29504
|
R2.error(`File not found: ${audioFlag}`);
|
|
28380
29505
|
Nt("Setup cancelled.");
|
|
28381
29506
|
process.exit(1);
|
|
@@ -28468,10 +29593,10 @@ ${c.dim("Use --example blank for offline use.")}`
|
|
|
28468
29593
|
}
|
|
28469
29594
|
trackInitTemplate(templateId);
|
|
28470
29595
|
const transcriptFile = resolve20(destDir, "transcript.json");
|
|
28471
|
-
if (
|
|
29596
|
+
if (existsSync33(transcriptFile)) {
|
|
28472
29597
|
await patchTranscript(destDir, transcriptFile);
|
|
28473
29598
|
}
|
|
28474
|
-
const files =
|
|
29599
|
+
const files = readdirSync12(destDir);
|
|
28475
29600
|
Vt2(files.map((f3) => c.accent(f3)).join("\n"), c.success(`Created ${name}/`));
|
|
28476
29601
|
if (!skipSkills) {
|
|
28477
29602
|
const installSkills = await Rt({
|
|
@@ -28515,9 +29640,10 @@ function detectProvider() {
|
|
|
28515
29640
|
{ cmd: "xclip", args: ["-selection", "clipboard"] },
|
|
28516
29641
|
{ cmd: "xsel", args: ["--clipboard", "--input"] }
|
|
28517
29642
|
];
|
|
29643
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
28518
29644
|
for (const p of candidates) {
|
|
28519
|
-
const
|
|
28520
|
-
if (
|
|
29645
|
+
const result = spawnSync(cmd, [p.cmd], { stdio: "ignore" });
|
|
29646
|
+
if (result.status === 0) return p;
|
|
28521
29647
|
}
|
|
28522
29648
|
return void 0;
|
|
28523
29649
|
}
|
|
@@ -28553,7 +29679,7 @@ __export(add_exports, {
|
|
|
28553
29679
|
remapTarget: () => remapTarget,
|
|
28554
29680
|
runAdd: () => runAdd
|
|
28555
29681
|
});
|
|
28556
|
-
import { existsSync as
|
|
29682
|
+
import { existsSync as existsSync34 } from "fs";
|
|
28557
29683
|
import { resolve as resolve21, relative as relative3 } from "path";
|
|
28558
29684
|
function remapTarget(item, originalTarget, paths) {
|
|
28559
29685
|
if (item.type === "hyperframes:block") {
|
|
@@ -28579,8 +29705,8 @@ function buildSnippet(item, relativeTarget) {
|
|
|
28579
29705
|
async function runAdd(opts) {
|
|
28580
29706
|
const projectDir = resolve21(opts.projectDir);
|
|
28581
29707
|
let config = loadProjectConfig(projectDir);
|
|
28582
|
-
const hasConfig =
|
|
28583
|
-
if (!hasConfig &&
|
|
29708
|
+
const hasConfig = existsSync34(projectConfigPath(projectDir));
|
|
29709
|
+
if (!hasConfig && existsSync34(resolve21(projectDir, "index.html"))) {
|
|
28584
29710
|
writeProjectConfig(projectDir, DEFAULT_PROJECT_CONFIG);
|
|
28585
29711
|
config = DEFAULT_PROJECT_CONFIG;
|
|
28586
29712
|
}
|
|
@@ -28679,10 +29805,10 @@ var init_add = __esm({
|
|
|
28679
29805
|
const projectDir = resolve21(args.dir ?? process.cwd());
|
|
28680
29806
|
const json = args.json === true;
|
|
28681
29807
|
const skipClipboard = args["no-clipboard"] === true;
|
|
28682
|
-
const hasConfigBefore =
|
|
29808
|
+
const hasConfigBefore = existsSync34(projectConfigPath(projectDir));
|
|
28683
29809
|
try {
|
|
28684
29810
|
const result = await runAdd({ name: args.name, projectDir, skipClipboard });
|
|
28685
|
-
const wroteConfig = !hasConfigBefore &&
|
|
29811
|
+
const wroteConfig = !hasConfigBefore && existsSync34(projectConfigPath(projectDir));
|
|
28686
29812
|
if (json) {
|
|
28687
29813
|
console.log(JSON.stringify(result));
|
|
28688
29814
|
return;
|
|
@@ -28892,17 +30018,17 @@ var init_format = __esm({
|
|
|
28892
30018
|
});
|
|
28893
30019
|
|
|
28894
30020
|
// src/utils/project.ts
|
|
28895
|
-
import { existsSync as
|
|
30021
|
+
import { existsSync as existsSync35, statSync as statSync11 } from "fs";
|
|
28896
30022
|
import { resolve as resolve23, basename as basename5 } from "path";
|
|
28897
30023
|
function resolveProject(dirArg) {
|
|
28898
30024
|
const dir = resolve23(dirArg ?? ".");
|
|
28899
30025
|
const name = basename5(dir);
|
|
28900
30026
|
const indexPath = resolve23(dir, "index.html");
|
|
28901
|
-
if (!
|
|
30027
|
+
if (!existsSync35(dir) || !statSync11(dir).isDirectory()) {
|
|
28902
30028
|
errorBox("Not a directory: " + dir);
|
|
28903
30029
|
process.exit(1);
|
|
28904
30030
|
}
|
|
28905
|
-
if (!
|
|
30031
|
+
if (!existsSync35(indexPath)) {
|
|
28906
30032
|
errorBox(
|
|
28907
30033
|
"No composition found in " + dir,
|
|
28908
30034
|
"No index.html file found.",
|
|
@@ -28925,7 +30051,7 @@ __export(play_exports, {
|
|
|
28925
30051
|
default: () => play_default,
|
|
28926
30052
|
examples: () => examples5
|
|
28927
30053
|
});
|
|
28928
|
-
import { existsSync as
|
|
30054
|
+
import { existsSync as existsSync36, readFileSync as readFileSync24 } from "fs";
|
|
28929
30055
|
import { resolve as resolve24, dirname as dirname14 } from "path";
|
|
28930
30056
|
function commandDir() {
|
|
28931
30057
|
return dirname14(new URL(import.meta.url).pathname);
|
|
@@ -28940,7 +30066,7 @@ function resolveRuntimePath2() {
|
|
|
28940
30066
|
resolve24(d, "..", "..", "..", "core", "dist", "hyperframe.runtime.iife.js")
|
|
28941
30067
|
];
|
|
28942
30068
|
for (const p of candidates) {
|
|
28943
|
-
if (
|
|
30069
|
+
if (existsSync36(p)) return p;
|
|
28944
30070
|
}
|
|
28945
30071
|
return null;
|
|
28946
30072
|
}
|
|
@@ -28954,7 +30080,7 @@ function resolvePlayerPath() {
|
|
|
28954
30080
|
resolve24(d, "..", "hyperframes-player.global.js")
|
|
28955
30081
|
];
|
|
28956
30082
|
for (const p of candidates) {
|
|
28957
|
-
if (
|
|
30083
|
+
if (existsSync36(p)) return p;
|
|
28958
30084
|
}
|
|
28959
30085
|
return null;
|
|
28960
30086
|
}
|
|
@@ -29056,7 +30182,7 @@ var init_play = __esm({
|
|
|
29056
30182
|
const reqPath = ctx.req.path.replace("/composition/", "");
|
|
29057
30183
|
const filePath = resolve24(project.dir, reqPath);
|
|
29058
30184
|
if (!filePath.startsWith(project.dir)) return ctx.text("Forbidden", 403);
|
|
29059
|
-
if (!
|
|
30185
|
+
if (!existsSync36(filePath)) return ctx.text("Not found", 404);
|
|
29060
30186
|
const content = readFileSync24(filePath, "utf-8");
|
|
29061
30187
|
if (filePath.endsWith(".html")) {
|
|
29062
30188
|
const injected = injectRuntime(content);
|
|
@@ -29172,12 +30298,14 @@ __export(ffmpeg_exports, {
|
|
|
29172
30298
|
import { execSync as execSync2 } from "child_process";
|
|
29173
30299
|
function findFFmpeg() {
|
|
29174
30300
|
try {
|
|
29175
|
-
const
|
|
30301
|
+
const cmd = process.platform === "win32" ? "where ffmpeg" : "which ffmpeg";
|
|
30302
|
+
const output = execSync2(cmd, {
|
|
29176
30303
|
encoding: "utf-8",
|
|
29177
30304
|
stdio: ["pipe", "pipe", "pipe"],
|
|
29178
30305
|
timeout: 5e3
|
|
29179
|
-
})
|
|
29180
|
-
|
|
30306
|
+
});
|
|
30307
|
+
const first = output.split(/\r?\n/).map((s2) => s2.trim()).find(Boolean);
|
|
30308
|
+
return first || void 0;
|
|
29181
30309
|
} catch {
|
|
29182
30310
|
return void 0;
|
|
29183
30311
|
}
|
|
@@ -29206,7 +30334,7 @@ __export(render_exports, {
|
|
|
29206
30334
|
});
|
|
29207
30335
|
import { mkdirSync as mkdirSync21, readFileSync as readFileSync25, statSync as statSync12, writeFileSync as writeFileSync14, rmSync as rmSync8 } from "fs";
|
|
29208
30336
|
import { cpus as cpus3, freemem as freemem3, tmpdir as tmpdir3 } from "os";
|
|
29209
|
-
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";
|
|
29210
30338
|
import { execFileSync as execFileSync5, spawn as spawn10 } from "child_process";
|
|
29211
30339
|
function defaultWorkerCount() {
|
|
29212
30340
|
return Math.max(1, Math.min(Math.floor(CPU_CORE_COUNT * 3 / 4), 8));
|
|
@@ -29243,9 +30371,9 @@ function ensureDockerImage(version, quiet) {
|
|
|
29243
30371
|
}
|
|
29244
30372
|
if (!quiet) console.log(c.dim(` Building Docker image: ${tag}...`));
|
|
29245
30373
|
const dockerfilePath = resolveDockerfilePath();
|
|
29246
|
-
const tmpDir =
|
|
30374
|
+
const tmpDir = join37(tmpdir3(), `hyperframes-docker-${Date.now()}`);
|
|
29247
30375
|
mkdirSync21(tmpDir, { recursive: true });
|
|
29248
|
-
writeFileSync14(
|
|
30376
|
+
writeFileSync14(join37(tmpDir, "Dockerfile"), readFileSync25(dockerfilePath));
|
|
29249
30377
|
try {
|
|
29250
30378
|
execFileSync5(
|
|
29251
30379
|
"docker",
|
|
@@ -29604,7 +30732,7 @@ var init_render2 = __esm({
|
|
|
29604
30732
|
const now = /* @__PURE__ */ new Date();
|
|
29605
30733
|
const datePart = now.toISOString().slice(0, 10);
|
|
29606
30734
|
const timePart = now.toTimeString().slice(0, 8).replace(/:/g, "-");
|
|
29607
|
-
const outputPath = args.output ? resolve25(args.output) :
|
|
30735
|
+
const outputPath = args.output ? resolve25(args.output) : join37(rendersDir, `${project.name}_${datePart}_${timePart}${ext}`);
|
|
29608
30736
|
mkdirSync21(dirname15(outputPath), { recursive: true });
|
|
29609
30737
|
const useDocker = args.docker ?? false;
|
|
29610
30738
|
const useGpu = args.gpu ?? false;
|
|
@@ -29929,12 +31057,12 @@ __export(info_exports, {
|
|
|
29929
31057
|
default: () => info_default,
|
|
29930
31058
|
examples: () => examples8
|
|
29931
31059
|
});
|
|
29932
|
-
import { readFileSync as readFileSync26, readdirSync as
|
|
29933
|
-
import { join as
|
|
31060
|
+
import { readFileSync as readFileSync26, readdirSync as readdirSync13, statSync as statSync13 } from "fs";
|
|
31061
|
+
import { join as join38 } from "path";
|
|
29934
31062
|
function totalSize(dir) {
|
|
29935
31063
|
let total = 0;
|
|
29936
|
-
for (const entry of
|
|
29937
|
-
const path2 =
|
|
31064
|
+
for (const entry of readdirSync13(dir, { withFileTypes: true })) {
|
|
31065
|
+
const path2 = join38(dir, entry.name);
|
|
29938
31066
|
if (entry.isDirectory()) {
|
|
29939
31067
|
total += totalSize(path2);
|
|
29940
31068
|
} else {
|
|
@@ -30022,7 +31150,7 @@ __export(compositions_exports, {
|
|
|
30022
31150
|
default: () => compositions_default,
|
|
30023
31151
|
examples: () => examples9
|
|
30024
31152
|
});
|
|
30025
|
-
import { existsSync as
|
|
31153
|
+
import { existsSync as existsSync37, readFileSync as readFileSync27 } from "fs";
|
|
30026
31154
|
import { resolve as resolve26, dirname as dirname16 } from "path";
|
|
30027
31155
|
function parseCompositions(html, baseDir) {
|
|
30028
31156
|
const parser = new DOMParser();
|
|
@@ -30036,7 +31164,7 @@ function parseCompositions(html, baseDir) {
|
|
|
30036
31164
|
const compositionSrc = div.getAttribute("data-composition-src");
|
|
30037
31165
|
if (compositionSrc) {
|
|
30038
31166
|
const subPath = resolve26(baseDir, compositionSrc);
|
|
30039
|
-
if (
|
|
31167
|
+
if (existsSync37(subPath)) {
|
|
30040
31168
|
const subHtml = readFileSync27(subPath, "utf-8");
|
|
30041
31169
|
const subInfo = parseSubComposition(subHtml, id, width, height);
|
|
30042
31170
|
compositions.push({ ...subInfo, source: compositionSrc });
|
|
@@ -30169,8 +31297,8 @@ __export(benchmark_exports, {
|
|
|
30169
31297
|
default: () => benchmark_default,
|
|
30170
31298
|
examples: () => examples10
|
|
30171
31299
|
});
|
|
30172
|
-
import { existsSync as
|
|
30173
|
-
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";
|
|
30174
31302
|
var examples10, DEFAULT_CONFIGS, benchmark_default;
|
|
30175
31303
|
var init_benchmark = __esm({
|
|
30176
31304
|
"src/commands/benchmark.ts"() {
|
|
@@ -30245,7 +31373,7 @@ var init_benchmark = __esm({
|
|
|
30245
31373
|
s2?.start(`Benchmarking ${config.label}...`);
|
|
30246
31374
|
for (let i2 = 0; i2 < runsPerConfig; i2++) {
|
|
30247
31375
|
s2?.message(`${config.label} \u2014 run ${i2 + 1}/${runsPerConfig}`);
|
|
30248
|
-
const outputPath =
|
|
31376
|
+
const outputPath = join39(
|
|
30249
31377
|
benchDir,
|
|
30250
31378
|
`${config.label.replace(/[^a-zA-Z0-9]/g, "_")}_run${i2}.mp4`
|
|
30251
31379
|
);
|
|
@@ -30259,7 +31387,7 @@ var init_benchmark = __esm({
|
|
|
30259
31387
|
await producer.executeRenderJob(job, project.dir, outputPath);
|
|
30260
31388
|
const elapsedMs = Date.now() - startTime;
|
|
30261
31389
|
let fileSize = null;
|
|
30262
|
-
if (
|
|
31390
|
+
if (existsSync38(outputPath)) {
|
|
30263
31391
|
const stat3 = statSync14(outputPath);
|
|
30264
31392
|
fileSize = stat3.size;
|
|
30265
31393
|
}
|
|
@@ -30475,8 +31603,8 @@ __export(transcribe_exports2, {
|
|
|
30475
31603
|
default: () => transcribe_default,
|
|
30476
31604
|
examples: () => examples12
|
|
30477
31605
|
});
|
|
30478
|
-
import { existsSync as
|
|
30479
|
-
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";
|
|
30480
31608
|
async function importTranscript(inputPath, dir, json) {
|
|
30481
31609
|
const { loadTranscript: loadTranscript2, patchCaptionHtml: patchCaptionHtml2 } = await Promise.resolve().then(() => (init_normalize(), normalize_exports));
|
|
30482
31610
|
const { words, format } = loadTranscript2(inputPath);
|
|
@@ -30484,7 +31612,7 @@ async function importTranscript(inputPath, dir, json) {
|
|
|
30484
31612
|
console.error(c.error("No words found in transcript."));
|
|
30485
31613
|
process.exit(1);
|
|
30486
31614
|
}
|
|
30487
|
-
const outPath =
|
|
31615
|
+
const outPath = join40(dir, "transcript.json");
|
|
30488
31616
|
writeFileSync15(outPath, JSON.stringify(words, null, 2));
|
|
30489
31617
|
patchCaptionHtml2(dir, words);
|
|
30490
31618
|
if (json) {
|
|
@@ -30601,7 +31729,7 @@ var init_transcribe2 = __esm({
|
|
|
30601
31729
|
},
|
|
30602
31730
|
async run({ args }) {
|
|
30603
31731
|
const inputPath = resolve28(args.input);
|
|
30604
|
-
if (!
|
|
31732
|
+
if (!existsSync39(inputPath)) {
|
|
30605
31733
|
console.error(c.error(`File not found: ${args.input}`));
|
|
30606
31734
|
process.exit(1);
|
|
30607
31735
|
}
|
|
@@ -30622,12 +31750,12 @@ var init_transcribe2 = __esm({
|
|
|
30622
31750
|
});
|
|
30623
31751
|
|
|
30624
31752
|
// src/tts/manager.ts
|
|
30625
|
-
import { existsSync as
|
|
30626
|
-
import { homedir as
|
|
30627
|
-
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";
|
|
30628
31756
|
async function ensureModel2(model = DEFAULT_MODEL2, options) {
|
|
30629
|
-
const modelPath =
|
|
30630
|
-
if (
|
|
31757
|
+
const modelPath = join41(MODELS_DIR2, `${model}.onnx`);
|
|
31758
|
+
if (existsSync40(modelPath)) return modelPath;
|
|
30631
31759
|
const url = MODEL_URLS[model];
|
|
30632
31760
|
if (!url) {
|
|
30633
31761
|
throw new Error(
|
|
@@ -30637,18 +31765,18 @@ async function ensureModel2(model = DEFAULT_MODEL2, options) {
|
|
|
30637
31765
|
mkdirSync22(MODELS_DIR2, { recursive: true });
|
|
30638
31766
|
options?.onProgress?.(`Downloading TTS model ${model} (~311 MB)...`);
|
|
30639
31767
|
await downloadFile(url, modelPath);
|
|
30640
|
-
if (!
|
|
31768
|
+
if (!existsSync40(modelPath)) {
|
|
30641
31769
|
throw new Error(`Model download failed: ${model}`);
|
|
30642
31770
|
}
|
|
30643
31771
|
return modelPath;
|
|
30644
31772
|
}
|
|
30645
31773
|
async function ensureVoices(options) {
|
|
30646
|
-
const voicesPath =
|
|
30647
|
-
if (
|
|
31774
|
+
const voicesPath = join41(VOICES_DIR, "voices-v1.0.bin");
|
|
31775
|
+
if (existsSync40(voicesPath)) return voicesPath;
|
|
30648
31776
|
mkdirSync22(VOICES_DIR, { recursive: true });
|
|
30649
31777
|
options?.onProgress?.("Downloading voice data (~27 MB)...");
|
|
30650
31778
|
await downloadFile(VOICES_URL, voicesPath);
|
|
30651
|
-
if (!
|
|
31779
|
+
if (!existsSync40(voicesPath)) {
|
|
30652
31780
|
throw new Error("Voice data download failed");
|
|
30653
31781
|
}
|
|
30654
31782
|
return voicesPath;
|
|
@@ -30658,9 +31786,9 @@ var init_manager3 = __esm({
|
|
|
30658
31786
|
"src/tts/manager.ts"() {
|
|
30659
31787
|
"use strict";
|
|
30660
31788
|
init_download();
|
|
30661
|
-
CACHE_DIR3 =
|
|
30662
|
-
MODELS_DIR2 =
|
|
30663
|
-
VOICES_DIR =
|
|
31789
|
+
CACHE_DIR3 = join41(homedir8(), ".cache", "hyperframes", "tts");
|
|
31790
|
+
MODELS_DIR2 = join41(CACHE_DIR3, "models");
|
|
31791
|
+
VOICES_DIR = join41(CACHE_DIR3, "voices");
|
|
30664
31792
|
DEFAULT_MODEL2 = "kokoro-v1.0";
|
|
30665
31793
|
MODEL_URLS = {
|
|
30666
31794
|
"kokoro-v1.0": "https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx"
|
|
@@ -30686,23 +31814,26 @@ __export(synthesize_exports, {
|
|
|
30686
31814
|
synthesize: () => synthesize
|
|
30687
31815
|
});
|
|
30688
31816
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
30689
|
-
import { existsSync as
|
|
30690
|
-
import { join as
|
|
30691
|
-
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";
|
|
30692
31820
|
function findPython() {
|
|
30693
31821
|
for (const name of ["python3", "python"]) {
|
|
30694
31822
|
try {
|
|
30695
|
-
const
|
|
31823
|
+
const cmd = process.platform === "win32" ? "where" : "which";
|
|
31824
|
+
const output = execFileSync6(cmd, [name], {
|
|
30696
31825
|
encoding: "utf-8",
|
|
30697
31826
|
stdio: ["pipe", "pipe", "pipe"],
|
|
30698
31827
|
timeout: 5e3
|
|
30699
|
-
})
|
|
30700
|
-
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"], {
|
|
30701
31832
|
encoding: "utf-8",
|
|
30702
31833
|
stdio: ["pipe", "pipe", "pipe"],
|
|
30703
31834
|
timeout: 5e3
|
|
30704
31835
|
}).trim();
|
|
30705
|
-
if (version.includes("Python 3")) return
|
|
31836
|
+
if (version.includes("Python 3")) return first;
|
|
30706
31837
|
} catch {
|
|
30707
31838
|
}
|
|
30708
31839
|
}
|
|
@@ -30720,7 +31851,7 @@ function hasPythonPackage(python, pkg) {
|
|
|
30720
31851
|
}
|
|
30721
31852
|
}
|
|
30722
31853
|
function ensureSynthScript() {
|
|
30723
|
-
if (!
|
|
31854
|
+
if (!existsSync41(SCRIPT_PATH)) {
|
|
30724
31855
|
mkdirSync23(SCRIPT_DIR, { recursive: true });
|
|
30725
31856
|
writeFileSync16(SCRIPT_PATH, SYNTH_SCRIPT);
|
|
30726
31857
|
}
|
|
@@ -30761,7 +31892,7 @@ async function synthesize(text, outputPath, options) {
|
|
|
30761
31892
|
stdio: ["pipe", "pipe", "pipe"]
|
|
30762
31893
|
}
|
|
30763
31894
|
);
|
|
30764
|
-
if (!
|
|
31895
|
+
if (!existsSync41(outputPath)) {
|
|
30765
31896
|
throw new Error("Synthesis completed but no output file was created");
|
|
30766
31897
|
}
|
|
30767
31898
|
const lines = stdout2.trim().split("\n");
|
|
@@ -30773,7 +31904,7 @@ async function synthesize(text, outputPath, options) {
|
|
|
30773
31904
|
durationSeconds: result.durationSeconds
|
|
30774
31905
|
};
|
|
30775
31906
|
} catch (err) {
|
|
30776
|
-
if (err instanceof SyntaxError &&
|
|
31907
|
+
if (err instanceof SyntaxError && existsSync41(outputPath)) {
|
|
30777
31908
|
throw new Error(
|
|
30778
31909
|
"Speech was generated but metadata could not be read. Check the output file manually."
|
|
30779
31910
|
);
|
|
@@ -30816,8 +31947,8 @@ print(json.dumps({
|
|
|
30816
31947
|
"durationSeconds": round(duration, 3),
|
|
30817
31948
|
}))
|
|
30818
31949
|
`;
|
|
30819
|
-
SCRIPT_DIR =
|
|
30820
|
-
SCRIPT_PATH =
|
|
31950
|
+
SCRIPT_DIR = join42(homedir9(), ".cache", "hyperframes", "tts");
|
|
31951
|
+
SCRIPT_PATH = join42(SCRIPT_DIR, "synth.py");
|
|
30821
31952
|
}
|
|
30822
31953
|
});
|
|
30823
31954
|
|
|
@@ -30827,7 +31958,7 @@ __export(tts_exports, {
|
|
|
30827
31958
|
default: () => tts_default,
|
|
30828
31959
|
examples: () => examples13
|
|
30829
31960
|
});
|
|
30830
|
-
import { existsSync as
|
|
31961
|
+
import { existsSync as existsSync42, readFileSync as readFileSync28 } from "fs";
|
|
30831
31962
|
import { resolve as resolve29, extname as extname8 } from "path";
|
|
30832
31963
|
function listVoices(json) {
|
|
30833
31964
|
if (json) {
|
|
@@ -30917,7 +32048,7 @@ var init_tts = __esm({
|
|
|
30917
32048
|
}
|
|
30918
32049
|
let text;
|
|
30919
32050
|
const maybeFile = resolve29(args.input);
|
|
30920
|
-
if (
|
|
32051
|
+
if (existsSync42(maybeFile) && extname8(maybeFile).toLowerCase() === ".txt") {
|
|
30921
32052
|
text = readFileSync28(maybeFile, "utf-8").trim();
|
|
30922
32053
|
if (!text) {
|
|
30923
32054
|
console.error(c.error("File is empty."));
|
|
@@ -30983,15 +32114,15 @@ __export(docs_exports, {
|
|
|
30983
32114
|
default: () => docs_default,
|
|
30984
32115
|
examples: () => examples14
|
|
30985
32116
|
});
|
|
30986
|
-
import { readFileSync as readFileSync29, existsSync as
|
|
30987
|
-
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";
|
|
30988
32119
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
30989
32120
|
function docsDir() {
|
|
30990
32121
|
const thisFile = fileURLToPath6(import.meta.url);
|
|
30991
32122
|
const dir = dirname18(thisFile);
|
|
30992
32123
|
const devPath = resolve30(dir, "..", "docs");
|
|
30993
32124
|
const builtPath = resolve30(dir, "docs");
|
|
30994
|
-
return
|
|
32125
|
+
return existsSync43(devPath) ? devPath : builtPath;
|
|
30995
32126
|
}
|
|
30996
32127
|
function formatInlineCode(line) {
|
|
30997
32128
|
return line.replace(/`([^`]+)`/g, (_match, code) => c.accent(code));
|
|
@@ -31088,8 +32219,8 @@ var init_docs = __esm({
|
|
|
31088
32219
|
}
|
|
31089
32220
|
process.exit(1);
|
|
31090
32221
|
}
|
|
31091
|
-
const filePath =
|
|
31092
|
-
if (!
|
|
32222
|
+
const filePath = join43(docsDir(), entry.file);
|
|
32223
|
+
if (!existsSync43(filePath)) {
|
|
31093
32224
|
console.error(c.error(`Doc file not found: ${filePath}`));
|
|
31094
32225
|
process.exit(1);
|
|
31095
32226
|
}
|
|
@@ -31519,8 +32650,8 @@ var validate_exports = {};
|
|
|
31519
32650
|
__export(validate_exports, {
|
|
31520
32651
|
default: () => validate_default
|
|
31521
32652
|
});
|
|
31522
|
-
import { existsSync as
|
|
31523
|
-
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";
|
|
31524
32655
|
import { fileURLToPath as fileURLToPath7 } from "url";
|
|
31525
32656
|
async function getCompositionDuration2(page) {
|
|
31526
32657
|
return page.evaluate(() => {
|
|
@@ -31575,7 +32706,7 @@ async function validateInBrowser(projectDir, opts) {
|
|
|
31575
32706
|
"dist",
|
|
31576
32707
|
"hyperframe.runtime.iife.js"
|
|
31577
32708
|
);
|
|
31578
|
-
if (
|
|
32709
|
+
if (existsSync44(runtimePath)) {
|
|
31579
32710
|
const runtimeSource = readFileSync30(runtimePath, "utf-8");
|
|
31580
32711
|
html = html.replace(
|
|
31581
32712
|
/<script[^>]*data-hyperframes-preview-runtime[^>]*src="[^"]*"[^>]*><\/script>/,
|
|
@@ -31591,8 +32722,8 @@ async function validateInBrowser(projectDir, opts) {
|
|
|
31591
32722
|
res.end(html);
|
|
31592
32723
|
return;
|
|
31593
32724
|
}
|
|
31594
|
-
const filePath =
|
|
31595
|
-
if (
|
|
32725
|
+
const filePath = join44(projectDir, decodeURIComponent(url));
|
|
32726
|
+
if (existsSync44(filePath)) {
|
|
31596
32727
|
res.writeHead(200, { "Content-Type": getMimeType2(filePath) });
|
|
31597
32728
|
res.end(readFileSync30(filePath));
|
|
31598
32729
|
return;
|
|
@@ -31784,8 +32915,8 @@ __export(snapshot_exports, {
|
|
|
31784
32915
|
default: () => snapshot_default,
|
|
31785
32916
|
examples: () => examples18
|
|
31786
32917
|
});
|
|
31787
|
-
import { existsSync as
|
|
31788
|
-
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";
|
|
31789
32920
|
import { fileURLToPath as fileURLToPath8 } from "url";
|
|
31790
32921
|
async function captureSnapshots(projectDir, opts) {
|
|
31791
32922
|
const { bundleToSingleHtml: bundleToSingleHtml2 } = await Promise.resolve().then(() => (init_compiler(), compiler_exports));
|
|
@@ -31801,7 +32932,7 @@ async function captureSnapshots(projectDir, opts) {
|
|
|
31801
32932
|
"dist",
|
|
31802
32933
|
"hyperframe.runtime.iife.js"
|
|
31803
32934
|
);
|
|
31804
|
-
if (
|
|
32935
|
+
if (existsSync45(runtimePath)) {
|
|
31805
32936
|
const runtimeSource = readFileSync31(runtimePath, "utf-8");
|
|
31806
32937
|
html = html.replace(
|
|
31807
32938
|
/<script[^>]*data-hyperframes-preview-runtime[^>]*src="[^"]*"[^>]*><\/script>/,
|
|
@@ -31824,7 +32955,7 @@ async function captureSnapshots(projectDir, opts) {
|
|
|
31824
32955
|
res.end();
|
|
31825
32956
|
return;
|
|
31826
32957
|
}
|
|
31827
|
-
if (
|
|
32958
|
+
if (existsSync45(filePath)) {
|
|
31828
32959
|
res.writeHead(200, { "Content-Type": getMimeType2(filePath) });
|
|
31829
32960
|
res.end(readFileSync31(filePath));
|
|
31830
32961
|
return;
|
|
@@ -31899,7 +33030,7 @@ async function captureSnapshots(projectDir, opts) {
|
|
|
31899
33030
|
return [];
|
|
31900
33031
|
}
|
|
31901
33032
|
const positions = opts.at?.length ? opts.at : numFrames === 1 ? [duration / 2] : Array.from({ length: numFrames }, (_2, i2) => i2 / (numFrames - 1) * duration);
|
|
31902
|
-
const snapshotDir =
|
|
33033
|
+
const snapshotDir = join45(projectDir, "snapshots");
|
|
31903
33034
|
mkdirSync24(snapshotDir, { recursive: true });
|
|
31904
33035
|
for (let i2 = 0; i2 < positions.length; i2++) {
|
|
31905
33036
|
const time = positions[i2];
|
|
@@ -31925,7 +33056,7 @@ async function captureSnapshots(projectDir, opts) {
|
|
|
31925
33056
|
await new Promise((r2) => setTimeout(r2, 200));
|
|
31926
33057
|
const timeLabel = opts.at?.length ? `${time.toFixed(1)}s` : `${Math.round(time / duration * 100)}pct`;
|
|
31927
33058
|
const filename = `frame-${String(i2).padStart(2, "0")}-at-${timeLabel}.png`;
|
|
31928
|
-
const framePath =
|
|
33059
|
+
const framePath = join45(snapshotDir, filename);
|
|
31929
33060
|
await page.screenshot({ path: framePath, type: "png" });
|
|
31930
33061
|
savedPaths.push(`snapshots/${filename}`);
|
|
31931
33062
|
}
|
|
@@ -32010,13 +33141,13 @@ ${c.error("\u2717")} Snapshot failed: ${msg}`);
|
|
|
32010
33141
|
|
|
32011
33142
|
// src/capture/assetDownloader.ts
|
|
32012
33143
|
import { writeFileSync as writeFileSync17, mkdirSync as mkdirSync25 } from "fs";
|
|
32013
|
-
import { join as
|
|
32014
|
-
async function downloadAssets(tokens, outputDir, catalogedAssets) {
|
|
32015
|
-
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");
|
|
32016
33147
|
mkdirSync25(assetsDir, { recursive: true });
|
|
32017
33148
|
const assets = [];
|
|
32018
33149
|
const downloadedUrls = /* @__PURE__ */ new Set();
|
|
32019
|
-
mkdirSync25(
|
|
33150
|
+
mkdirSync25(join46(outputDir, "assets", "svgs"), { recursive: true });
|
|
32020
33151
|
for (let i2 = 0; i2 < tokens.svgs.length && i2 < 30; i2++) {
|
|
32021
33152
|
const svg = tokens.svgs[i2];
|
|
32022
33153
|
if (!svg.outerHTML || svg.outerHTML.length < 50) continue;
|
|
@@ -32024,12 +33155,12 @@ async function downloadAssets(tokens, outputDir, catalogedAssets) {
|
|
|
32024
33155
|
const name = label2 ? slugify(label2) + ".svg" : svg.isLogo ? `logo-${i2}.svg` : `icon-${i2}.svg`;
|
|
32025
33156
|
const localPath = `assets/svgs/${name}`;
|
|
32026
33157
|
try {
|
|
32027
|
-
writeFileSync17(
|
|
33158
|
+
writeFileSync17(join46(outputDir, localPath), svg.outerHTML, "utf-8");
|
|
32028
33159
|
assets.push({ url: "", localPath, type: "svg" });
|
|
32029
33160
|
} catch {
|
|
32030
33161
|
}
|
|
32031
33162
|
}
|
|
32032
|
-
for (const icon of
|
|
33163
|
+
for (const icon of faviconLinks || []) {
|
|
32033
33164
|
if (!icon.href) continue;
|
|
32034
33165
|
try {
|
|
32035
33166
|
const ext = extname9(new URL(icon.href).pathname) || ".ico";
|
|
@@ -32037,7 +33168,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets) {
|
|
|
32037
33168
|
const localPath = `assets/${name}`;
|
|
32038
33169
|
const buffer = await fetchBuffer(icon.href);
|
|
32039
33170
|
if (buffer) {
|
|
32040
|
-
writeFileSync17(
|
|
33171
|
+
writeFileSync17(join46(outputDir, localPath), buffer);
|
|
32041
33172
|
assets.push({ url: icon.href, localPath, type: "favicon" });
|
|
32042
33173
|
break;
|
|
32043
33174
|
}
|
|
@@ -32059,12 +33190,6 @@ async function downloadAssets(tokens, outputDir, catalogedAssets) {
|
|
|
32059
33190
|
const isPoster = a.contexts.includes("video[poster]");
|
|
32060
33191
|
imageUrls.push({ url: a.url, isPoster });
|
|
32061
33192
|
}
|
|
32062
|
-
} else {
|
|
32063
|
-
for (const img of tokens.images) {
|
|
32064
|
-
if (img.width > 200 && img.src.startsWith("http")) {
|
|
32065
|
-
imageUrls.push({ url: img.src, isPoster: false });
|
|
32066
|
-
}
|
|
32067
|
-
}
|
|
32068
33193
|
}
|
|
32069
33194
|
const toDownload = [];
|
|
32070
33195
|
for (const { url, isPoster } of imageUrls) {
|
|
@@ -32100,7 +33225,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets) {
|
|
|
32100
33225
|
const slug = isMeaningful ? slugify(rawName) : `${prefix}-${imgIdx}`;
|
|
32101
33226
|
const name = `${slug}${ext}`;
|
|
32102
33227
|
const localPath = `assets/${name}`;
|
|
32103
|
-
writeFileSync17(
|
|
33228
|
+
writeFileSync17(join46(outputDir, localPath), buffer);
|
|
32104
33229
|
assets.push({ url, localPath, type: "image" });
|
|
32105
33230
|
imgIdx++;
|
|
32106
33231
|
} catch {
|
|
@@ -32113,7 +33238,7 @@ async function downloadAssets(tokens, outputDir, catalogedAssets) {
|
|
|
32113
33238
|
const localPath = `assets/og-image${ext}`;
|
|
32114
33239
|
const buffer = await fetchBuffer(tokens.ogImage);
|
|
32115
33240
|
if (buffer && buffer.length > 5e3) {
|
|
32116
|
-
writeFileSync17(
|
|
33241
|
+
writeFileSync17(join46(outputDir, localPath), buffer);
|
|
32117
33242
|
assets.push({ url: tokens.ogImage, localPath, type: "image" });
|
|
32118
33243
|
}
|
|
32119
33244
|
} catch {
|
|
@@ -32136,7 +33261,7 @@ function normalizeUrl(u) {
|
|
|
32136
33261
|
}
|
|
32137
33262
|
}
|
|
32138
33263
|
async function downloadAndRewriteFonts(css, outputDir) {
|
|
32139
|
-
const assetsDir =
|
|
33264
|
+
const assetsDir = join46(outputDir, "assets", "fonts");
|
|
32140
33265
|
mkdirSync25(assetsDir, { recursive: true });
|
|
32141
33266
|
const fontUrlRegex = /url\(['"]?(https?:\/\/[^'")\s]+\.(?:woff2?|ttf|otf)[^'")\s]*?)['"]?\)/g;
|
|
32142
33267
|
const fontUrls = /* @__PURE__ */ new Set();
|
|
@@ -32172,7 +33297,7 @@ async function downloadAndRewriteFonts(css, outputDir) {
|
|
|
32172
33297
|
try {
|
|
32173
33298
|
const urlObj = new URL(fontUrl);
|
|
32174
33299
|
const filename = urlObj.pathname.split("/").pop() || `font-${count}.woff2`;
|
|
32175
|
-
const localPath =
|
|
33300
|
+
const localPath = join46(assetsDir, filename);
|
|
32176
33301
|
const relativePath = `assets/fonts/${filename}`;
|
|
32177
33302
|
const buffer = await fetchBuffer(fontUrl);
|
|
32178
33303
|
if (buffer) {
|
|
@@ -32237,19 +33362,6 @@ var init_assetDownloader = __esm({
|
|
|
32237
33362
|
// src/capture/htmlExtractor.ts
|
|
32238
33363
|
async function extractHtml(page, opts = {}) {
|
|
32239
33364
|
const settleTime = opts.settleTime ?? DEFAULT_SETTLE_TIME;
|
|
32240
|
-
await page.evaluate(`(async () => {
|
|
32241
|
-
var pageHeight = document.body.scrollHeight;
|
|
32242
|
-
var viewportH = window.innerHeight;
|
|
32243
|
-
var step = Math.floor(viewportH * 0.7);
|
|
32244
|
-
for (var y = 0; y < pageHeight + viewportH; y += step) {
|
|
32245
|
-
window.scrollTo(0, y);
|
|
32246
|
-
await new Promise(function(r) { setTimeout(r, 200); });
|
|
32247
|
-
}
|
|
32248
|
-
window.scrollTo(0, pageHeight);
|
|
32249
|
-
await new Promise(function(r) { setTimeout(r, 300); });
|
|
32250
|
-
window.scrollTo(0, 0);
|
|
32251
|
-
await new Promise(function(r) { setTimeout(r, 300); });
|
|
32252
|
-
})()`);
|
|
32253
33365
|
await new Promise((r2) => setTimeout(r2, settleTime));
|
|
32254
33366
|
const stylesheetUrls = await page.evaluate(`(() => {
|
|
32255
33367
|
return Array.from(document.querySelectorAll('link[rel="stylesheet"][href]')).map(function(l) { return l.href; });
|
|
@@ -32478,12 +33590,49 @@ var init_tokenExtractor = __esm({
|
|
|
32478
33590
|
var ogImgEl = document.querySelector('meta[property="og:image"]');
|
|
32479
33591
|
var ogImage = ogImgEl ? ogImgEl.content : undefined;
|
|
32480
33592
|
|
|
32481
|
-
// 3. Fonts
|
|
32482
|
-
var
|
|
32483
|
-
|
|
32484
|
-
|
|
32485
|
-
|
|
32486
|
-
|
|
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) {}
|
|
32487
33636
|
}
|
|
32488
33637
|
|
|
32489
33638
|
// 4. Colors \u2014 hybrid: DOM computed styles + visual pixel sampling
|
|
@@ -32641,10 +33790,7 @@ var init_tokenExtractor = __esm({
|
|
|
32641
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 };
|
|
32642
33791
|
});
|
|
32643
33792
|
|
|
32644
|
-
// 6.
|
|
32645
|
-
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; });
|
|
32646
|
-
|
|
32647
|
-
// 7. CTAs \u2014 match by class AND by text content patterns
|
|
33793
|
+
// 6. CTAs \u2014 match by class AND by text content patterns
|
|
32648
33794
|
// Conservative class selectors (avoid nav links with "action" or "start" in class)
|
|
32649
33795
|
var ctaSelectors = 'a[class*="btn"], a[class*="button"], a[class*="cta"], button[class*="primary"], button[class*="cta"], [role="button"]';
|
|
32650
33796
|
var ctaEls = Array.from(document.querySelectorAll(ctaSelectors));
|
|
@@ -32715,15 +33861,7 @@ var init_tokenExtractor = __esm({
|
|
|
32715
33861
|
};
|
|
32716
33862
|
}).filter(Boolean).slice(0, 50);
|
|
32717
33863
|
|
|
32718
|
-
// 9.
|
|
32719
|
-
var imgEls = Array.from(document.querySelectorAll("img[src]")).filter(function(img) { return img.naturalWidth > 200 && isVisible(img); }).slice(0, 15);
|
|
32720
|
-
var images = imgEls.map(function(img) { return { src: img.src, alt: img.alt || "", width: img.naturalWidth, height: img.naturalHeight }; });
|
|
32721
|
-
|
|
32722
|
-
// 10. Icons
|
|
32723
|
-
var iconEls = Array.from(document.querySelectorAll('link[rel*="icon"], link[rel="apple-touch-icon"]'));
|
|
32724
|
-
var icons = iconEls.map(function(l) { return { rel: l.rel, href: l.href }; });
|
|
32725
|
-
|
|
32726
|
-
// 11. Sections \u2014 find large visual blocks regardless of HTML tag
|
|
33864
|
+
// 9. Sections \u2014 find large visual blocks regardless of HTML tag
|
|
32727
33865
|
var sectionResults = [];
|
|
32728
33866
|
// Start with semantic elements, then fall back to large direct children of body/main
|
|
32729
33867
|
var candidates = Array.from(document.querySelectorAll(
|
|
@@ -32773,17 +33911,45 @@ var init_tokenExtractor = __esm({
|
|
|
32773
33911
|
}
|
|
32774
33912
|
if (!sectionBg || sectionBg === "rgba(0, 0, 0, 0)" || sectionBg === "transparent") sectionBg = "#FFFFFF";
|
|
32775
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
|
+
}
|
|
32776
33924
|
sectionBg = rgbToHex(sectionBg) || sectionBg;
|
|
32777
|
-
|
|
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);
|
|
32778
33928
|
}
|
|
32779
33929
|
sectionResults.sort(function(a, b) { return a.y - b.y; });
|
|
32780
33930
|
var filtered = sectionResults.filter(function(s, i) { return i === 0 || Math.abs(s.y - sectionResults[i-1].y) > 100; });
|
|
32781
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
|
+
|
|
32782
33948
|
return {
|
|
32783
33949
|
title: title, description: description, ogImage: ogImage,
|
|
32784
|
-
cssVariables:
|
|
32785
|
-
headings: headings,
|
|
32786
|
-
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
|
|
32787
33953
|
};
|
|
32788
33954
|
})()`;
|
|
32789
33955
|
}
|
|
@@ -32928,8 +34094,8 @@ var init_animationCataloger = __esm({
|
|
|
32928
34094
|
});
|
|
32929
34095
|
|
|
32930
34096
|
// src/capture/mediaCapture.ts
|
|
32931
|
-
import { mkdirSync as mkdirSync26, writeFileSync as writeFileSync18, readdirSync as
|
|
32932
|
-
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";
|
|
32933
34099
|
async function saveLottieAnimations(discoveredLotties, lottieDir) {
|
|
32934
34100
|
let savedCount = 0;
|
|
32935
34101
|
const savedHashes = /* @__PURE__ */ new Set();
|
|
@@ -32962,7 +34128,7 @@ async function saveLottieAnimations(discoveredLotties, lottieDir) {
|
|
|
32962
34128
|
const hash = buf.toString("base64").slice(0, 100);
|
|
32963
34129
|
if (savedHashes.has(hash)) continue;
|
|
32964
34130
|
savedHashes.add(hash);
|
|
32965
|
-
writeFileSync18(
|
|
34131
|
+
writeFileSync18(join47(lottieDir, `animation-${savedCount}.lottie`), buf);
|
|
32966
34132
|
savedCount++;
|
|
32967
34133
|
continue;
|
|
32968
34134
|
}
|
|
@@ -32980,7 +34146,7 @@ async function saveLottieAnimations(discoveredLotties, lottieDir) {
|
|
|
32980
34146
|
} catch {
|
|
32981
34147
|
continue;
|
|
32982
34148
|
}
|
|
32983
|
-
writeFileSync18(
|
|
34149
|
+
writeFileSync18(join47(lottieDir, `animation-${savedCount}.json`), jsonData, "utf-8");
|
|
32984
34150
|
savedCount++;
|
|
32985
34151
|
}
|
|
32986
34152
|
} catch {
|
|
@@ -32990,22 +34156,22 @@ async function saveLottieAnimations(discoveredLotties, lottieDir) {
|
|
|
32990
34156
|
}
|
|
32991
34157
|
async function renderLottiePreviews(chromeBrowser, lottieDir, outputDir) {
|
|
32992
34158
|
const manifest = [];
|
|
32993
|
-
const previewDir =
|
|
34159
|
+
const previewDir = join47(lottieDir, "previews");
|
|
32994
34160
|
mkdirSync26(previewDir, { recursive: true });
|
|
32995
|
-
for (const file of
|
|
34161
|
+
for (const file of readdirSync14(lottieDir)) {
|
|
32996
34162
|
if (!file.endsWith(".json")) continue;
|
|
32997
34163
|
try {
|
|
32998
|
-
const raw = JSON.parse(readFileSync32(
|
|
34164
|
+
const raw = JSON.parse(readFileSync32(join47(lottieDir, file), "utf-8"));
|
|
32999
34165
|
const fr = raw.fr || 30;
|
|
33000
34166
|
const dur = ((raw.op || 0) - (raw.ip || 0)) / fr;
|
|
33001
34167
|
const previewName = file.replace(".json", "-preview.png");
|
|
33002
|
-
const fileSize = statSync15(
|
|
34168
|
+
const fileSize = statSync15(join47(lottieDir, file)).size;
|
|
33003
34169
|
if (fileSize > 2e6) continue;
|
|
33004
34170
|
let previewPage;
|
|
33005
34171
|
try {
|
|
33006
34172
|
previewPage = await chromeBrowser.newPage();
|
|
33007
34173
|
await previewPage.setViewport({ width: 400, height: 400 });
|
|
33008
|
-
const animData = JSON.parse(readFileSync32(
|
|
34174
|
+
const animData = JSON.parse(readFileSync32(join47(lottieDir, file), "utf-8"));
|
|
33009
34175
|
const midFrame = Math.floor(((raw.op || 0) - (raw.ip || 0)) * 0.3);
|
|
33010
34176
|
await previewPage.setContent(
|
|
33011
34177
|
`<!DOCTYPE html>
|
|
@@ -33035,7 +34201,7 @@ async function renderLottiePreviews(chromeBrowser, lottieDir, outputDir) {
|
|
|
33035
34201
|
await previewPage.waitForFunction(() => window.__READY === true, { timeout: 5e3 }).catch(() => {
|
|
33036
34202
|
});
|
|
33037
34203
|
await previewPage.screenshot({
|
|
33038
|
-
path:
|
|
34204
|
+
path: join47(previewDir, previewName),
|
|
33039
34205
|
type: "png",
|
|
33040
34206
|
omitBackground: true
|
|
33041
34207
|
});
|
|
@@ -33059,7 +34225,7 @@ async function renderLottiePreviews(chromeBrowser, lottieDir, outputDir) {
|
|
|
33059
34225
|
}
|
|
33060
34226
|
if (manifest.length > 0) {
|
|
33061
34227
|
writeFileSync18(
|
|
33062
|
-
|
|
34228
|
+
join47(outputDir, "extracted", "lottie-manifest.json"),
|
|
33063
34229
|
JSON.stringify(manifest, null, 2),
|
|
33064
34230
|
"utf-8"
|
|
33065
34231
|
);
|
|
@@ -33121,15 +34287,15 @@ async function captureVideoManifest(page, outputDir, progress) {
|
|
|
33121
34287
|
return true;
|
|
33122
34288
|
});
|
|
33123
34289
|
if (uniqueVideos.length > 0) {
|
|
33124
|
-
const videoManifestDir =
|
|
34290
|
+
const videoManifestDir = join47(outputDir, "assets", "videos");
|
|
33125
34291
|
mkdirSync26(videoManifestDir, { recursive: true });
|
|
33126
|
-
const previewDir =
|
|
34292
|
+
const previewDir = join47(videoManifestDir, "previews");
|
|
33127
34293
|
mkdirSync26(previewDir, { recursive: true });
|
|
33128
34294
|
const videoManifest = [];
|
|
33129
34295
|
for (let vi = 0; vi < uniqueVideos.length && vi < 20; vi++) {
|
|
33130
34296
|
const v = uniqueVideos[vi];
|
|
33131
34297
|
const previewName = `video-${vi}-preview.png`;
|
|
33132
|
-
const previewPath =
|
|
34298
|
+
const previewPath = join47(previewDir, previewName);
|
|
33133
34299
|
try {
|
|
33134
34300
|
await page.evaluate(`window.scrollTo(0, ${Math.max(0, v.top - 100)})`);
|
|
33135
34301
|
await new Promise((r2) => setTimeout(r2, 300));
|
|
@@ -33168,7 +34334,7 @@ async function captureVideoManifest(page, outputDir, progress) {
|
|
|
33168
34334
|
}
|
|
33169
34335
|
if (videoManifest.length > 0) {
|
|
33170
34336
|
writeFileSync18(
|
|
33171
|
-
|
|
34337
|
+
join47(outputDir, "extracted", "video-manifest.json"),
|
|
33172
34338
|
JSON.stringify(videoManifest, null, 2),
|
|
33173
34339
|
"utf-8"
|
|
33174
34340
|
);
|
|
@@ -73623,8 +74789,8 @@ ${underline2}`);
|
|
|
73623
74789
|
});
|
|
73624
74790
|
|
|
73625
74791
|
// src/capture/contentExtractor.ts
|
|
73626
|
-
import { readdirSync as
|
|
73627
|
-
import { join as
|
|
74792
|
+
import { readdirSync as readdirSync15, statSync as statSync17, readFileSync as readFileSync33 } from "fs";
|
|
74793
|
+
import { join as join48 } from "path";
|
|
73628
74794
|
async function detectLibraries(page, capturedShaders) {
|
|
73629
74795
|
let detectedLibraries = [];
|
|
73630
74796
|
try {
|
|
@@ -73706,6 +74872,7 @@ async function extractVisibleText(page) {
|
|
|
73706
74872
|
let visibleTextContent = "";
|
|
73707
74873
|
try {
|
|
73708
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;
|
|
73709
74876
|
var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null);
|
|
73710
74877
|
var texts = [];
|
|
73711
74878
|
var node;
|
|
@@ -73718,7 +74885,13 @@ async function extractVisibleText(page) {
|
|
|
73718
74885
|
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') continue;
|
|
73719
74886
|
var tag = el.tagName.toLowerCase();
|
|
73720
74887
|
if (tag === 'script' || tag === 'style' || tag === 'noscript') continue;
|
|
73721
|
-
|
|
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);
|
|
73722
74895
|
}
|
|
73723
74896
|
return texts.join('\\n');
|
|
73724
74897
|
})()`);
|
|
@@ -73737,7 +74910,7 @@ async function captionImagesWithGemini(outputDir, progress, warnings) {
|
|
|
73737
74910
|
try {
|
|
73738
74911
|
const { GoogleGenAI: GoogleGenAI2 } = await Promise.resolve().then(() => (init_node4(), node_exports));
|
|
73739
74912
|
const ai = new GoogleGenAI2({ apiKey: geminiKey });
|
|
73740
|
-
const imageFiles =
|
|
74913
|
+
const imageFiles = readdirSync15(join48(outputDir, "assets")).filter(
|
|
73741
74914
|
(f3) => /\.(png|jpg|jpeg|webp|gif)$/i.test(f3)
|
|
73742
74915
|
);
|
|
73743
74916
|
const model = process.env.HYPERFRAMES_GEMINI_MODEL || "gemini-3.1-flash-lite-preview";
|
|
@@ -73746,7 +74919,7 @@ async function captionImagesWithGemini(outputDir, progress, warnings) {
|
|
|
73746
74919
|
const batch = imageFiles.slice(i2, i2 + BATCH_SIZE);
|
|
73747
74920
|
const results = await Promise.allSettled(
|
|
73748
74921
|
batch.map(async (file) => {
|
|
73749
|
-
const filePath =
|
|
74922
|
+
const filePath = join48(outputDir, "assets", file);
|
|
73750
74923
|
const stat3 = statSync17(filePath);
|
|
73751
74924
|
if (stat3.size > 4e6) return { file, caption: "" };
|
|
73752
74925
|
const buffer = readFileSync33(filePath);
|
|
@@ -73795,11 +74968,11 @@ function generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCap
|
|
|
73795
74968
|
const uncaptionedLines = [];
|
|
73796
74969
|
const svgLines = [];
|
|
73797
74970
|
const fontLines = [];
|
|
73798
|
-
const assetsPath =
|
|
74971
|
+
const assetsPath = join48(outputDir, "assets");
|
|
73799
74972
|
try {
|
|
73800
|
-
for (const file of
|
|
74973
|
+
for (const file of readdirSync15(assetsPath)) {
|
|
73801
74974
|
if (file === "svgs" || file === "fonts" || file === "lottie" || file === "videos") continue;
|
|
73802
|
-
const filePath =
|
|
74975
|
+
const filePath = join48(assetsPath, file);
|
|
73803
74976
|
const stat3 = statSync17(filePath);
|
|
73804
74977
|
if (!stat3.isFile()) continue;
|
|
73805
74978
|
const sizeKb = Math.round(stat3.size / 1024);
|
|
@@ -73828,8 +75001,8 @@ function generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCap
|
|
|
73828
75001
|
} catch {
|
|
73829
75002
|
}
|
|
73830
75003
|
try {
|
|
73831
|
-
const svgsPath =
|
|
73832
|
-
for (const file of
|
|
75004
|
+
const svgsPath = join48(assetsPath, "svgs");
|
|
75005
|
+
for (const file of readdirSync15(svgsPath)) {
|
|
73833
75006
|
if (!file.endsWith(".svg")) continue;
|
|
73834
75007
|
const svgMatch = tokens.svgs.find(
|
|
73835
75008
|
(s2) => s2.label && file.includes(
|
|
@@ -73843,8 +75016,8 @@ function generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCap
|
|
|
73843
75016
|
} catch {
|
|
73844
75017
|
}
|
|
73845
75018
|
try {
|
|
73846
|
-
const fontsPath =
|
|
73847
|
-
for (const file of
|
|
75019
|
+
const fontsPath = join48(assetsPath, "fonts");
|
|
75020
|
+
for (const file of readdirSync15(fontsPath)) {
|
|
73848
75021
|
fontLines.push(`fonts/${file} \u2014 font file`);
|
|
73849
75022
|
}
|
|
73850
75023
|
} catch {
|
|
@@ -73863,111 +75036,71 @@ __export(agentPromptGenerator_exports, {
|
|
|
73863
75036
|
generateAgentPrompt: () => generateAgentPrompt
|
|
73864
75037
|
});
|
|
73865
75038
|
import { writeFileSync as writeFileSync19 } from "fs";
|
|
73866
|
-
import { join as
|
|
73867
|
-
function generateAgentPrompt(outputDir, url, tokens,
|
|
73868
|
-
const prompt = buildPrompt(
|
|
73869
|
-
|
|
73870
|
-
|
|
73871
|
-
|
|
73872
|
-
|
|
73873
|
-
|
|
73874
|
-
|
|
73875
|
-
catalogedAssets
|
|
73876
|
-
);
|
|
73877
|
-
writeFileSync19(join48(outputDir, "CLAUDE.md"), prompt, "utf-8");
|
|
73878
|
-
writeFileSync19(join48(outputDir, ".cursorrules"), prompt, "utf-8");
|
|
73879
|
-
}
|
|
73880
|
-
function buildPrompt(url, tokens, animations, hasScreenshot, hasLottie, hasShaders, catalogedAssets) {
|
|
73881
|
-
const hostname = new URL(url).hostname.replace(/^www\./, "");
|
|
73882
|
-
const title = tokens.title || hostname;
|
|
73883
|
-
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\./, "");
|
|
73884
75048
|
const colorSummary = tokens.colors.slice(0, 10).join(", ");
|
|
73885
|
-
const fontSummary = tokens.fonts.
|
|
73886
|
-
|
|
73887
|
-
|
|
73888
|
-
const
|
|
73889
|
-
|
|
73890
|
-
|
|
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}
|
|
73891
75088
|
|
|
73892
75089
|
Source: ${url}
|
|
73893
75090
|
|
|
73894
|
-
|
|
73895
|
-
|
|
73896
|
-
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.
|
|
73897
|
-
|
|
73898
|
-
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.
|
|
73899
75092
|
|
|
73900
75093
|
## What's in This Capture
|
|
73901
75094
|
|
|
73902
75095
|
| File | Contents |
|
|
73903
75096
|
|------|----------|
|
|
73904
|
-
${
|
|
73905
|
-
| \`extracted/tokens.json\` | Design tokens: ${tokens.colors.length} colors, ${tokens.fonts.length} fonts, ${headingCount} headings, ${ctaCount} CTAs, ${sectionCount} sections |
|
|
73906
|
-
| \`extracted/visible-text.txt\` | All visible text content in DOM order \u2014 use exact strings, never paraphrase |
|
|
73907
|
-
| \`extracted/assets-catalog.json\` | Every asset URL (images, fonts, videos, icons) with HTML context |
|
|
73908
|
-
| \`extracted/animations.json\` | Animation catalog: ${animations?.summary?.webAnimations ?? 0} web animations, ${animations?.summary?.scrollTargets ?? 0} scroll triggers, ${animations?.summary?.canvases ?? 0} canvases |
|
|
73909
|
-
| \`assets/svgs/\` | Extracted inline SVGs (logos, icons, illustrations) |
|
|
73910
|
-
| \`assets/\` | Downloaded images and font files \u2014 **Read every image file to see what it contains** |
|
|
73911
|
-
${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. |" : ""}
|
|
73912
|
-
${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. |" : ""}
|
|
73913
|
-
${hasShaders ? "| `extracted/shaders.json` | Captured WebGL shader source code (GLSL vertex + fragment shaders) |" : ""}
|
|
73914
|
-
| \`extracted/asset-descriptions.md\` | One-line description of every downloaded asset \u2014 read this first |
|
|
73915
|
-
|
|
73916
|
-
> **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")}
|
|
73917
75098
|
|
|
73918
75099
|
## Brand Summary
|
|
73919
75100
|
|
|
73920
|
-
|
|
73921
|
-
- **Fonts**: ${fontSummary}
|
|
73922
|
-
- **Sections**: ${sectionCount} page sections detected
|
|
73923
|
-
- **Headings**: ${headingCount} headings extracted
|
|
73924
|
-
- **CTAs**: ${ctaCount} calls-to-action found
|
|
73925
|
-
${cues.length > 0 ? `
|
|
73926
|
-
## Source Patterns Detected
|
|
73927
|
-
|
|
73928
|
-
${cues.map((c2) => `- ${c2}`).join("\n")}
|
|
73929
|
-
` : ""}
|
|
73930
|
-
## Example Prompts
|
|
73931
|
-
|
|
73932
|
-
Try asking:
|
|
73933
|
-
|
|
73934
|
-
- "Make me a 15-second social ad from this capture"
|
|
73935
|
-
- "Create a 30-second product tour video"
|
|
73936
|
-
- "Turn this into a vertical Instagram reel"
|
|
73937
|
-
- "Build a feature announcement video highlighting the top 3 features"
|
|
75101
|
+
${brandLines.join("\n")}
|
|
73938
75102
|
`;
|
|
73939
75103
|
}
|
|
73940
|
-
function detectImplementationCues(tokens, animations) {
|
|
73941
|
-
const cues = [];
|
|
73942
|
-
if (Object.keys(tokens.cssVariables).length > 10) {
|
|
73943
|
-
cues.push(
|
|
73944
|
-
"CSS custom properties used extensively \u2014 preserve design tokens for colors, spacing, and typography."
|
|
73945
|
-
);
|
|
73946
|
-
}
|
|
73947
|
-
if (tokens.fonts.length > 0) {
|
|
73948
|
-
cues.push(
|
|
73949
|
-
`Typography: ${tokens.fonts.join(", ")}. Match these exact font families and weights.`
|
|
73950
|
-
);
|
|
73951
|
-
}
|
|
73952
|
-
if (animations?.summary) {
|
|
73953
|
-
if (animations.summary.scrollTargets > 20) {
|
|
73954
|
-
cues.push(`${animations.summary.scrollTargets} scroll-triggered animations detected.`);
|
|
73955
|
-
}
|
|
73956
|
-
if (animations.summary.webAnimations > 5) {
|
|
73957
|
-
cues.push(`${animations.summary.webAnimations} active Web Animations detected.`);
|
|
73958
|
-
}
|
|
73959
|
-
if (animations.summary.canvases > 0) {
|
|
73960
|
-
cues.push(`${animations.summary.canvases} Canvas/WebGL elements detected.`);
|
|
73961
|
-
}
|
|
73962
|
-
}
|
|
73963
|
-
const hasMarquee = animations?.cssDeclarations?.some(
|
|
73964
|
-
(d) => d.animation?.name?.toLowerCase().includes("marquee") || d.animation?.name?.toLowerCase().includes("scroll")
|
|
73965
|
-
);
|
|
73966
|
-
if (hasMarquee) {
|
|
73967
|
-
cues.push("Marquee/ticker animation present \u2014 preserve continuous scrolling behavior.");
|
|
73968
|
-
}
|
|
73969
|
-
return cues;
|
|
73970
|
-
}
|
|
73971
75104
|
var init_agentPromptGenerator = __esm({
|
|
73972
75105
|
"src/capture/agentPromptGenerator.ts"() {
|
|
73973
75106
|
"use strict";
|
|
@@ -73975,8 +75108,8 @@ var init_agentPromptGenerator = __esm({
|
|
|
73975
75108
|
});
|
|
73976
75109
|
|
|
73977
75110
|
// src/capture/scaffolding.ts
|
|
73978
|
-
import { existsSync as
|
|
73979
|
-
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";
|
|
73980
75113
|
function loadEnvFile(startDir) {
|
|
73981
75114
|
try {
|
|
73982
75115
|
let dir = resolve33(startDir);
|
|
@@ -74001,9 +75134,9 @@ function loadEnvFile(startDir) {
|
|
|
74001
75134
|
} catch {
|
|
74002
75135
|
}
|
|
74003
75136
|
}
|
|
74004
|
-
async function generateProjectScaffold(outputDir, url, tokens, animationCatalog, hasScreenshots, hasLotties, hasShaders, catalogedAssets, progress, warnings) {
|
|
74005
|
-
const metaPath =
|
|
74006
|
-
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)) {
|
|
74007
75140
|
const hostname = new URL(url).hostname.replace(/^www\./, "");
|
|
74008
75141
|
writeFileSync20(
|
|
74009
75142
|
metaPath,
|
|
@@ -74021,11 +75154,12 @@ async function generateProjectScaffold(outputDir, url, tokens, animationCatalog,
|
|
|
74021
75154
|
hasScreenshots,
|
|
74022
75155
|
hasLotties,
|
|
74023
75156
|
hasShaders,
|
|
74024
|
-
catalogedAssets
|
|
75157
|
+
catalogedAssets,
|
|
75158
|
+
detectedLibraries
|
|
74025
75159
|
);
|
|
74026
|
-
progress("agent", "CLAUDE.md generated");
|
|
75160
|
+
progress("agent", "AGENTS.md + CLAUDE.md generated");
|
|
74027
75161
|
} catch (err) {
|
|
74028
|
-
warnings.push(`CLAUDE.md generation failed: ${err}`);
|
|
75162
|
+
warnings.push(`AGENTS.md/CLAUDE.md generation failed: ${err}`);
|
|
74029
75163
|
}
|
|
74030
75164
|
}
|
|
74031
75165
|
var init_scaffolding = __esm({
|
|
@@ -74040,9 +75174,9 @@ __export(screenshotCapture_exports, {
|
|
|
74040
75174
|
captureScrollScreenshots: () => captureScrollScreenshots
|
|
74041
75175
|
});
|
|
74042
75176
|
import { writeFileSync as writeFileSync21, mkdirSync as mkdirSync27 } from "fs";
|
|
74043
|
-
import { join as
|
|
75177
|
+
import { join as join51 } from "path";
|
|
74044
75178
|
async function captureScrollScreenshots(page, outputDir) {
|
|
74045
|
-
const screenshotsDir =
|
|
75179
|
+
const screenshotsDir = join51(outputDir, "screenshots");
|
|
74046
75180
|
mkdirSync27(screenshotsDir, { recursive: true });
|
|
74047
75181
|
const MAX_SCREENSHOTS = 20;
|
|
74048
75182
|
const filePaths = [];
|
|
@@ -74076,7 +75210,7 @@ async function captureScrollScreenshots(page, outputDir) {
|
|
|
74076
75210
|
finalPositions[i2] / Math.max(1, scrollHeight - viewportHeight) * 100
|
|
74077
75211
|
);
|
|
74078
75212
|
const filename = `scroll-${String(Math.min(pct, 100)).padStart(3, "0")}.png`;
|
|
74079
|
-
const filePath =
|
|
75213
|
+
const filePath = join51(screenshotsDir, filename);
|
|
74080
75214
|
const buffer = await page.screenshot({ type: "png" });
|
|
74081
75215
|
writeFileSync21(filePath, buffer);
|
|
74082
75216
|
filePaths.push(`screenshots/${filename}`);
|
|
@@ -74389,8 +75523,8 @@ var capture_exports = {};
|
|
|
74389
75523
|
__export(capture_exports, {
|
|
74390
75524
|
captureWebsite: () => captureWebsite
|
|
74391
75525
|
});
|
|
74392
|
-
import { mkdirSync as mkdirSync28, writeFileSync as writeFileSync22, existsSync as
|
|
74393
|
-
import { join as
|
|
75526
|
+
import { mkdirSync as mkdirSync28, writeFileSync as writeFileSync22, existsSync as existsSync47 } from "fs";
|
|
75527
|
+
import { join as join52 } from "path";
|
|
74394
75528
|
async function captureWebsite(opts, onProgress) {
|
|
74395
75529
|
const {
|
|
74396
75530
|
url,
|
|
@@ -74407,9 +75541,9 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74407
75541
|
onProgress?.(stage, detail);
|
|
74408
75542
|
};
|
|
74409
75543
|
loadEnvFile(outputDir);
|
|
74410
|
-
mkdirSync28(
|
|
74411
|
-
mkdirSync28(
|
|
74412
|
-
mkdirSync28(
|
|
75544
|
+
mkdirSync28(join52(outputDir, "extracted"), { recursive: true });
|
|
75545
|
+
mkdirSync28(join52(outputDir, "screenshots"), { recursive: true });
|
|
75546
|
+
mkdirSync28(join52(outputDir, "assets"), { recursive: true });
|
|
74413
75547
|
progress("browser", "Launching headless Chrome...");
|
|
74414
75548
|
const { ensureBrowser: ensureBrowser2 } = await Promise.resolve().then(() => (init_manager2(), manager_exports2));
|
|
74415
75549
|
const browser = await ensureBrowser2();
|
|
@@ -74565,7 +75699,7 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74565
75699
|
} catch {
|
|
74566
75700
|
}
|
|
74567
75701
|
if (discoveredLotties.length > 0) {
|
|
74568
|
-
const lottieDir =
|
|
75702
|
+
const lottieDir = join52(outputDir, "assets", "lottie");
|
|
74569
75703
|
mkdirSync28(lottieDir, { recursive: true });
|
|
74570
75704
|
const savedCount = await saveLottieAnimations(discoveredLotties, lottieDir);
|
|
74571
75705
|
if (savedCount > 0) {
|
|
@@ -74585,7 +75719,7 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74585
75719
|
});
|
|
74586
75720
|
capturedShaders = unique;
|
|
74587
75721
|
writeFileSync22(
|
|
74588
|
-
|
|
75722
|
+
join52(outputDir, "extracted", "shaders.json"),
|
|
74589
75723
|
JSON.stringify(unique, null, 2),
|
|
74590
75724
|
"utf-8"
|
|
74591
75725
|
);
|
|
@@ -74596,7 +75730,7 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74596
75730
|
progress("tokens", "Extracting design tokens...");
|
|
74597
75731
|
const tokens = await extractTokens(page1);
|
|
74598
75732
|
writeFileSync22(
|
|
74599
|
-
|
|
75733
|
+
join52(outputDir, "extracted", "tokens.json"),
|
|
74600
75734
|
JSON.stringify(tokens, null, 2),
|
|
74601
75735
|
"utf-8"
|
|
74602
75736
|
);
|
|
@@ -74612,8 +75746,13 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74612
75746
|
const { catalogAssets: catalogAssets2 } = await Promise.resolve().then(() => (init_assetCataloger(), assetCataloger_exports));
|
|
74613
75747
|
catalogedAssets = await catalogAssets2(page1);
|
|
74614
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
|
+
}
|
|
74615
75754
|
} catch (err) {
|
|
74616
|
-
warnings.push(`Asset cataloging failed: ${err}`);
|
|
75755
|
+
warnings.push(`Asset cataloging failed (no images will be downloaded): ${err}`);
|
|
74617
75756
|
}
|
|
74618
75757
|
progress("extract", "Extracting HTML & CSS...");
|
|
74619
75758
|
const extracted = await extractHtml(page1, { settleTime: 1e3 });
|
|
@@ -74646,6 +75785,10 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74646
75785
|
}
|
|
74647
75786
|
const detectedLibraries = await detectLibraries(page1, capturedShaders);
|
|
74648
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
|
+
})()`);
|
|
74649
75792
|
await page1.close();
|
|
74650
75793
|
extracted.headHtml = await downloadAndRewriteFonts(extracted.headHtml, outputDir);
|
|
74651
75794
|
if (animationCatalog) {
|
|
@@ -74661,7 +75804,7 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74661
75804
|
representativeAnimations: representativeAnims
|
|
74662
75805
|
};
|
|
74663
75806
|
writeFileSync22(
|
|
74664
|
-
|
|
75807
|
+
join52(outputDir, "extracted", "animations.json"),
|
|
74665
75808
|
JSON.stringify(leanCatalog, null, 2),
|
|
74666
75809
|
"utf-8"
|
|
74667
75810
|
);
|
|
@@ -74669,21 +75812,21 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74669
75812
|
let assets = [];
|
|
74670
75813
|
if (!skipAssets) {
|
|
74671
75814
|
progress("assets", "Downloading assets...");
|
|
74672
|
-
assets = await downloadAssets(tokens, outputDir, catalogedAssets);
|
|
75815
|
+
assets = await downloadAssets(tokens, outputDir, catalogedAssets, faviconLinks);
|
|
74673
75816
|
}
|
|
74674
75817
|
if (visibleTextContent) {
|
|
74675
|
-
writeFileSync22(
|
|
75818
|
+
writeFileSync22(join52(outputDir, "extracted", "visible-text.txt"), visibleTextContent, "utf-8");
|
|
74676
75819
|
}
|
|
74677
75820
|
if (catalogedAssets.length > 0) {
|
|
74678
75821
|
writeFileSync22(
|
|
74679
|
-
|
|
75822
|
+
join52(outputDir, "extracted", "assets-catalog.json"),
|
|
74680
75823
|
JSON.stringify(catalogedAssets, null, 2),
|
|
74681
75824
|
"utf-8"
|
|
74682
75825
|
);
|
|
74683
75826
|
}
|
|
74684
75827
|
if (detectedLibraries.length > 0) {
|
|
74685
75828
|
writeFileSync22(
|
|
74686
|
-
|
|
75829
|
+
join52(outputDir, "extracted", "detected-libraries.json"),
|
|
74687
75830
|
JSON.stringify(detectedLibraries, null, 2),
|
|
74688
75831
|
"utf-8"
|
|
74689
75832
|
);
|
|
@@ -74694,7 +75837,7 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74694
75837
|
const lines = generateAssetDescriptions(outputDir, tokens, catalogedAssets, geminiCaptions);
|
|
74695
75838
|
if (lines.length > 0) {
|
|
74696
75839
|
writeFileSync22(
|
|
74697
|
-
|
|
75840
|
+
join52(outputDir, "extracted", "asset-descriptions.md"),
|
|
74698
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",
|
|
74699
75842
|
"utf-8"
|
|
74700
75843
|
);
|
|
@@ -74710,10 +75853,11 @@ async function captureWebsite(opts, onProgress) {
|
|
|
74710
75853
|
animationCatalog,
|
|
74711
75854
|
screenshots.length > 0,
|
|
74712
75855
|
discoveredLotties.length > 0,
|
|
74713
|
-
|
|
75856
|
+
existsSync47(join52(outputDir, "extracted", "shaders.json")),
|
|
74714
75857
|
catalogedAssets,
|
|
74715
75858
|
progress,
|
|
74716
|
-
warnings
|
|
75859
|
+
warnings,
|
|
75860
|
+
detectedLibraries
|
|
74717
75861
|
);
|
|
74718
75862
|
progress("done", "Capture complete");
|
|
74719
75863
|
return {
|
|
@@ -74855,7 +75999,8 @@ var init_capture2 = __esm({
|
|
|
74855
75999
|
screenshots: result.screenshots.length,
|
|
74856
76000
|
assets: result.assets.length,
|
|
74857
76001
|
detectedSections: result.tokens.sections.length,
|
|
74858
|
-
fonts: result.tokens.fonts,
|
|
76002
|
+
fonts: result.tokens.fonts.map((f3) => f3.family),
|
|
76003
|
+
fontsDetailed: result.tokens.fonts,
|
|
74859
76004
|
animations: result.animationCatalog?.summary,
|
|
74860
76005
|
warnings: result.warnings
|
|
74861
76006
|
},
|
|
@@ -74871,7 +76016,11 @@ var init_capture2 = __esm({
|
|
|
74871
76016
|
console.log(` ${c2.dim("Screenshots:")} ${result.screenshots.length}`);
|
|
74872
76017
|
console.log(` ${c2.dim("Assets:")} ${result.assets.length}`);
|
|
74873
76018
|
console.log(` ${c2.dim("Sections:")} ${result.tokens.sections.length}`);
|
|
74874
|
-
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
|
+
);
|
|
74875
76024
|
if (result.warnings.length > 0) {
|
|
74876
76025
|
console.log();
|
|
74877
76026
|
for (const w of result.warnings) {
|